chore: import upstream snapshot with attribution
CodeQL / Analyze (python) (push) Failing after 1s
Linting and testing / build (3.11) (push) Failing after 0s
Package exe with PyInstaller - Windows / build (push) Failing after 2s
Linting and testing / build (3.10) (push) Failing after 1s
Linting and testing / minimal-install (push) Failing after 1s
Update sites rating and statistics / build (push) Failing after 1s
Build docker image and push to DockerHub / docker (push) Failing after 1s
Linting and testing / build (3.12) (push) Failing after 1s
Linting and testing / build (3.14) (push) Failing after 1s
Linting and testing / build (3.13) (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:10:17 +08:00
commit e52ddb7dd4
154 changed files with 77427 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
.git/
.vscode/
static/
tests/
*.txt
!/requirements.txt
venv/
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
echo 'Activating update_sitesmd hook script...'
poetry run update_sitesmd
echo 'Regenerating db_meta.json...'
poetry run python utils/generate_db_meta.py
git add maigret/resources/db_meta.json
git add maigret/resources/data.json
git add sites.md
+5
View File
@@ -0,0 +1,5 @@
# These are supported funding model platforms
patreon: soxoj
github: soxoj
buy_me_a_coffee: soxoj
+13
View File
@@ -0,0 +1,13 @@
---
name: Add a site
about: I want to add a new site for Maigret checks
title: New site
labels: new-site
assignees: soxoj
---
Link to the site main page: https://example.com
Link to an existing account: https://example.com/users/john
Link to a nonexistent account: https://example.com/users/noonewouldeverusethis7
Tags: photo, us, ...
+28
View File
@@ -0,0 +1,28 @@
---
name: Maigret bug report
about: I want to report a bug in Maigret functionality
title: ''
labels: bug
assignees: soxoj
---
## Checklist
- [ ] I'm reporting a bug in Maigret functionality
- [ ] I've checked for similar bug reports including closed ones
- [ ] I've checked for pull requests that attempt to fix this bug
## Description
Info about Maigret version you are running and environment (`--version`, operation system, ISP provider):
<INSERT VERSION INFO HERE>
How to reproduce this bug (commandline options / conditions):
<INSERT EXAMPLE OF CLI COMMAND HERE>
<DESCRIPTION>
<PASTE SCREENSHOT>
<ATTACH LOG FILE>
@@ -0,0 +1,20 @@
---
name: Report invalid result
about: I want to report invalid result of Maigret search
title: Invalid result
labels: false-result
assignees: soxoj
---
Invalid link: <INSERT LINK HERE>
<!--
Put x into the box
[ ] ==> [x]
-->
- [ ] I'm sure that the link leads to "not found" page
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
+90
View File
@@ -0,0 +1,90 @@
name: Build docker image and push to DockerHub
on:
push:
branches: [ main, dev ]
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
-
name: Extract metadata (CLI)
id: meta_cli
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_HUB_USERNAME }}/maigret
# Order matters: last-listed tag is pushed last by buildx and shows
# up first in Docker Hub UI. So we want latest at the bottom.
tags: |
type=sha,prefix=
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}
-
name: Extract metadata (Web UI)
id: meta_web
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_HUB_USERNAME }}/maigret
# Same ordering trick as above: `web` should rank above its siblings.
tags: |
type=sha,prefix=web-
type=ref,event=branch,suffix=-web
type=raw,value=web,enable={{is_default_branch}}
# Build Web UI first so CLI (`latest`) becomes the most-recent push
# and shows up at the top of the Docker Hub tags list.
-
name: Build and push (Web UI)
id: docker_build_web
uses: docker/build-push-action@v6
with:
push: true
target: web
tags: ${{ steps.meta_web.outputs.tags }}
labels: ${{ steps.meta_web.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
-
name: Build and push (CLI, default)
id: docker_build_cli
uses: docker/build-push-action@v6
with:
push: true
target: cli
tags: ${{ steps.meta_cli.outputs.tags }}
labels: ${{ steps.meta_cli.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
-
name: Image digests
run: |
echo "cli: ${{ steps.docker_build_cli.outputs.digest }}"
echo "web: ${{ steps.docker_build_web.outputs.digest }}"
-
name: Update DockerHub repository description
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
repository: ${{ secrets.DOCKER_HUB_USERNAME }}/maigret
short-description: ${{ github.event.repository.description }}
readme-filepath: ./README.md
enable-url-completion: true
+67
View File
@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
schedule:
- cron: '23 6 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+82
View File
@@ -0,0 +1,82 @@
name: Package exe with PyInstaller - Windows
on:
push:
branches: [main, dev]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# Wine Python (not Linux) runs PyInstaller; altgraph needs pkg_resources — reinstall setuptools after all deps.
- name: Prepare requirements for Wine (setuptools last)
run: |
set -euo pipefail
cp pyinstaller/requirements.txt pyinstaller/requirements-wine.txt
{
echo ""
echo "# CI: setuptools last so pkg_resources exists for PyInstaller/altgraph in Wine"
echo "setuptools==70.0.0"
} >> pyinstaller/requirements-wine.txt
- name: PyInstaller Windows Build
uses: JackMcKew/pyinstaller-action-windows@main
with:
path: pyinstaller
requirements: requirements-wine.txt
- name: Upload PyInstaller Binary to Workflow as Artifact
if: success()
uses: actions/upload-artifact@v4
with:
name: maigret_standalone_win32
path: pyinstaller/dist/windows
- name: Download PyInstaller Binary
if: success()
uses: actions/download-artifact@v4
with:
name: maigret_standalone_win32
- name: Create New Release and Upload PyInstaller Binary to Release
if: success()
uses: ncipollo/release-action@v1.14.0
id: create_release
with:
allowUpdates: true
draft: false
prerelease: false
artifactErrorsFailBuild: true
makeLatest: true
replacesArtifacts: true
artifacts: maigret_standalone.exe
name: Development Windows Release [${{ github.ref_name }}]
tag: ${{ github.ref_name }}
body: |
This is a development release built from the **${{ github.ref_name }}** branch.
Take into account that `dev` releases may be unstable.
Please, use [the development release](https://github.com/soxoj/maigret/releases/tag/main) build from the **main** branch.
## How to run
1. Download the attached `maigret_standalone.exe`.
2. Either:
- **Double-click it** — Maigret will ask you for a username, run a default search, and pause at the end so the output stays visible.
- **Run it from a terminal** for full options. Press `Win+R`, type `cmd`, hit Enter (or open PowerShell), then:
```cmd
cd %USERPROFILE%\Downloads
maigret_standalone.exe USERNAME
maigret_standalone.exe USERNAME --html :: also save an HTML report
maigret_standalone.exe --help :: list all options
```
Video guide: https://youtu.be/qIgwTZOmMmM
Full documentation: https://maigret.readthedocs.io/en/latest/
env:
GITHUB_TOKEN: ${{ github.token }}
+78
View File
@@ -0,0 +1,78 @@
name: Linting and testing
on:
push:
branches: [main]
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcairo2-dev
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install poetry
python -m poetry install --with dev
- name: Test with Coverage and Pytest (fail if coverage is low)
run: |
poetry run coverage run --source=./maigret -m pytest --reruns 3 --reruns-delay 5 tests
poetry run coverage report --fail-under=60
poetry run coverage html
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: htmlcov-${{ strategy.job-index }}
path: htmlcov
minimal-install:
# Verify a fresh `pip install maigret` succeeds and the test suite
# passes WITHOUT the optional [pdf] extra and WITHOUT system cairo.
# Catches regressions where core code accidentally grows a hard
# dependency on xhtml2pdf / pycairo / libcairo2.
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Maigret without [pdf] extra (no libcairo on host)
run: |
python -m pip install --upgrade pip
pip install .
pip install pytest pytest-asyncio pytest-rerunfailures pytest-httpserver
- name: Smoke-check the install
run: |
python -c "import maigret; from maigret.report import save_pdf_report; print('import OK')"
maigret --version
- name: Run tests without [pdf] extra
run: pytest --reruns 3 --reruns-delay 5 tests
+30
View File
@@ -0,0 +1,30 @@
name: Upload Python Package to PyPI when a Release is Published
on:
release:
types: [published]
jobs:
pypi-publish:
name: Publish release to PyPI
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/maigret
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: |
python -m build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
+56
View File
@@ -0,0 +1,56 @@
name: Update sites rating and statistics
on:
push:
branches: [ main ]
concurrency:
group: update-sites-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
- name: Build application
run: |
pip3 install .
python3 ./utils/update_site_data.py --empty-only
- name: Regenerate db_meta.json
run: python3 utils/generate_db_meta.py
- name: Remove ambiguous main tag
run: git tag -d main || true
- name: Check for meaningful changes
id: check
run: |
REAL_CHANGES=$(git diff --unified=0 sites.md | grep '^[+-][^+-]' | grep -v 'The list was updated at' | wc -l)
if [ "$REAL_CHANGES" -gt 0 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
- name: Delete existing PR branch
if: steps.check.outputs.has_changes == 'true'
run: git push origin --delete auto/update-sites-list || true
- name: Create Pull Request
if: steps.check.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Updated site list and statistics"
title: "Automated Sites List Update"
body: "Automated changes to sites.md based on new Alexa rankings/statistics."
branch: "auto/update-sites-list"
base: main
delete-branch: true
+51
View File
@@ -0,0 +1,51 @@
# Virtual Environment
venv/
.venv/
# Editor Configurations
.vscode/
.idea/
# Python
__pycache__/
# Pip
src/
# Jupyter Notebook
.ipynb_checkpoints
*.ipynb
# Logs and backups
*.log
*.bak
# Output files, except requirements.txt
*.txt
!requirements.txt
# Comma-Separated Values (CSV) Reports
*.csv
# MacOS Folder Metadata File
.DS_Store
/reports/
# Testing
.coverage
dist/
htmlcov/
/test_*
# Maigret files
settings.json
# other
*.egg-info
build
LLM
lib
.claude/
# Sphinx i18n: .mo are compiled from .po at build time
docs/source/locale/**/*.mo
+16
View File
@@ -0,0 +1,16 @@
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.10"
sphinx:
configuration: docs/source/conf.py
formats:
- pdf
python:
install:
- requirements: docs/requirements.txt
+990
View File
@@ -0,0 +1,990 @@
# Changelog
## [0.6.2] - 2026-07-01
## What's Changed
* Bump to 0.6.1 by @soxoj in https://github.com/soxoj/maigret/pull/2661
* Document Tor/proxy usage, add Advanced usage docs section (closes #544) by @soxoj in https://github.com/soxoj/maigret/pull/2663
* fix(checking): reject URLs and emails extracted as usernames by @soxoj in https://github.com/soxoj/maigret/pull/2673
* fix(Instagram): refresh rate-limit marker for stale Login title by @soxoj in https://github.com/soxoj/maigret/pull/2674
* build(deps-dev): bump black from 26.3.1 to 26.5.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2676
* build(deps): bump aiodns from 4.0.0 to 4.0.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2677
* build(deps-dev): bump tuna from 0.5.13 to 0.5.15 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2683
* build(deps): bump lxml from 6.1.0 to 6.1.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2681
* build(deps-dev): bump black from 26.5.0 to 26.5.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2680
* Added donation links by @soxoj in https://github.com/soxoj/maigret/pull/2686
* fix(web): sanitize username in report file paths to prevent path traversal by @sebastiondev in https://github.com/soxoj/maigret/pull/2678
* Added 3 sites by @soxoj in https://github.com/soxoj/maigret/pull/2687
* build(deps): bump yarl from 1.23.0 to 1.24.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2685
* Windows version improvements and docs by @soxoj in https://github.com/soxoj/maigret/pull/2690
* Enhance README with AI profiling and analysis details by @soxoj in https://github.com/soxoj/maigret/pull/2691
* build(deps): bump aiodns from 4.0.3 to 4.0.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2684
* build(deps): bump idna from 3.15 to 3.16 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2692
* build(deps): bump certifi from 2026.4.22 to 2026.5.20 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2689
* Add docs FAQ for top search queries; document translation workflow by @soxoj in https://github.com/soxoj/maigret/pull/2694
* Fix docs build: dedup link targets, gate SVG badges to HTML by @soxoj in https://github.com/soxoj/maigret/pull/2695
* build(deps-dev): bump pytest-rerunfailures from 16.2 to 16.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2698
* Fix Chinese name false positives: add CJK regexCheck to 27 sites (#2633) by @soxoj in https://github.com/soxoj/maigret/pull/2699
* Add Cloudflare bypass (FlareSolverr) support to web UI + docs by @soxoj in https://github.com/soxoj/maigret/pull/2700
* build(deps): bump soupsieve from 2.8.3 to 2.8.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2697
* feature: add keywords parameter and filter by its matching #979 by @dmarakom6 in https://github.com/soxoj/maigret/pull/2702
* build(deps): bump socid-extractor from 0.0.28 to 0.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2703
* build(deps-dev): bump pytest-asyncio from 1.3.0 to 1.4.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2704
* build(deps-dev): bump coverage from 7.14.0 to 7.14.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2705
* build(deps): bump platformdirs from 4.9.6 to 4.10.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2708
* Docker build workflow: update README in DockerHub, change order of labels by @soxoj in https://github.com/soxoj/maigret/pull/2712
* Dockerhub build update by @soxoj in https://github.com/soxoj/maigret/pull/2713
* build(deps): bump idna from 3.16 to 3.17 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2711
* Fix site checks: 4 fixed, 1 disabled by @soxoj in https://github.com/soxoj/maigret/pull/2715
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2716
* Fix DNS resolver failures: classify aiodns errors, add --dns-resolver threaded fallback (#2688) by @soxoj in https://github.com/soxoj/maigret/pull/2717
* Graceful Ctrl+C + error UX improvements by @soxoj in https://github.com/soxoj/maigret/pull/2719
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2720
* Fix site checks: 1 fixed, 8 disabled, 2 added by @soxoj in https://github.com/soxoj/maigret/pull/2722
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2723
* Added sponsor logo and text to the README files by @soxoj in https://github.com/soxoj/maigret/pull/2728
* Added sponsor logo and CLI proxy recommendation by @soxoj in https://github.com/soxoj/maigret/pull/2729
* refactor: update query for leetcode by @kaifcodec in https://github.com/soxoj/maigret/pull/2727
* Sponsorship text update for README by @soxoj in https://github.com/soxoj/maigret/pull/2730
* refactor: update instagram entry structure by @kaifcodec in https://github.com/soxoj/maigret/pull/2731
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2733
* build(deps): bump aiohttp from 3.13.5 to 3.14.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2736
* Update PDF dependency for web variant by @7677865466 in https://github.com/soxoj/maigret/pull/2739
* build(deps): bump idna from 3.17 to 3.18 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2735
* Warn on engine dict merge conflicts by @puneetdixit200 in https://github.com/soxoj/maigret/pull/2737
* Fix site checks: 2 fixed, 2 disabled by @soxoj in https://github.com/soxoj/maigret/pull/2745
* Refactor error detection and username extraction by @ashton-andersonaap in https://github.com/soxoj/maigret/pull/2701
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2747
* fix: update_site() now replaces list element instead of local variable by @ashvinctrl in https://github.com/soxoj/maigret/pull/2751
* build(deps): bump aiohttp from 3.14.0 to 3.14.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2748
* fix: pass value v not key k to is_country_tag in generate_report_context by @ashvinctrl in https://github.com/soxoj/maigret/pull/2753
* Add Tor entries for Whonix Forum and Stacker News by @nyxst4ck in https://github.com/soxoj/maigret/pull/2755
* build(deps): bump pypdf from 6.10.2 to 6.12.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2767
* Fix async activation retry handling by @fancyboi999 in https://github.com/soxoj/maigret/pull/2765
* build(deps-dev): bump pytest from 9.0.3 to 9.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2772
* build(deps): bump pyinstaller from 6.20.0 to 6.21.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2773
* fix(data): fix absence string for myjane by @0xseal in https://github.com/soxoj/maigret/pull/2770
* build(deps): bump cryptography from 46.0.7 to 48.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2775
* build(deps): bump pypdf from 6.12.0 to 6.13.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2776
* build(deps): bump svglib from 1.6.0 to 2.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2777
* Add AI Wiki links by @soxoj in https://github.com/soxoj/maigret/pull/2778
* build(deps): bump svglib from 2.0.0 to 2.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2782
* docs: fix typos by @juliosuas in https://github.com/soxoj/maigret/pull/2779
* build(deps): bump certifi from 2026.5.20 to 2026.6.17 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2781
* Fix HackerNews and Rajce.net false positives by @Ashley-996 in https://github.com/soxoj/maigret/pull/2780
* build(deps): bump svglib from 2.0.1 to 2.0.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2783
* build(deps): bump pypdf from 6.13.0 to 6.13.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2784
* Update README with sponsorship details by @soxoj in https://github.com/soxoj/maigret/pull/2785
* ORCID support by @soxoj in https://github.com/soxoj/maigret/pull/2786
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2787
* Fix error-percentage rounding tripping thresholds at whole-percent granularity by @jichaowang02-lang in https://github.com/soxoj/maigret/pull/2788
* Code cleanup and refactoring after ponytail audit by @soxoj in https://github.com/soxoj/maigret/pull/2789
* build(deps-dev): bump pytest from 9.1.0 to 9.1.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2793
* test: cover #2666 TODO-test paths (cookie_jar, extract_ids_from_results) by @aznikline in https://github.com/soxoj/maigret/pull/2795
* build(deps): update stem requirement from >=1.8.1 to >=1.8.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2790
* build(deps): update chardet requirement from >=5.0.0 to >=7.4.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2797
* refactor: drop dead 'if not dictionary' guards across report paths by @aznikline in https://github.com/soxoj/maigret/pull/2796
* build(deps-dev): bump coverage from 7.14.1 to 7.14.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2798
* build(deps): update requests-futures requirement from >=1.0.0 to >=1.0.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2794
* build(deps): update mock requirement from >=4.0.3 to >=5.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2791
* build(deps): update future requirement from >=0.18.3 to >=1.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2792
* Refactor sponsor section in README.md by @soxoj in https://github.com/soxoj/maigret/pull/2799
* Add archive.org and archive.is links to profile URL blocks by @yyq1043-cloud in https://github.com/soxoj/maigret/pull/2803
* Fix get_dict_ascii_tree ignoring new_line=False by @jichaowang02-lang in https://github.com/soxoj/maigret/pull/2805
* Compare raw error percentage against threshold (don't round before the check) by @jichaowang02-lang in https://github.com/soxoj/maigret/pull/2809
* Fix URLMatcher eating leading host characters (unescaped dots in www./m. prefix) by @jichaowang02-lang in https://github.com/soxoj/maigret/pull/2808
* fix: preserve report error reasons by @Sushanth012 in https://github.com/soxoj/maigret/pull/2802
* Add Neo4j Cypher export (--neo4j) by @thunderstornX in https://github.com/soxoj/maigret/pull/2774
* Added docs for Neo4j integration by @soxoj in https://github.com/soxoj/maigret/pull/2818
* Make include-tag site filter case-insensitive (match the exclude filter) by @jichaowang02-lang in https://github.com/soxoj/maigret/pull/2811
* build(deps): bump python-bidi from 0.6.10 to 0.6.11 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2825
* Fix site checks: 6 fixed, 5 disabled by @soxoj in https://github.com/soxoj/maigret/pull/2826
## New Contributors
* @sebastiondev made their first contribution in https://github.com/soxoj/maigret/pull/2678
* @dmarakom6 made their first contribution in https://github.com/soxoj/maigret/pull/2702
* @kaifcodec made their first contribution in https://github.com/soxoj/maigret/pull/2727
* @7677865466 made their first contribution in https://github.com/soxoj/maigret/pull/2739
* @puneetdixit200 made their first contribution in https://github.com/soxoj/maigret/pull/2737
* @ashton-andersonaap made their first contribution in https://github.com/soxoj/maigret/pull/2701
* @ashvinctrl made their first contribution in https://github.com/soxoj/maigret/pull/2751
* @nyxst4ck made their first contribution in https://github.com/soxoj/maigret/pull/2755
* @fancyboi999 made their first contribution in https://github.com/soxoj/maigret/pull/2765
* @0xseal made their first contribution in https://github.com/soxoj/maigret/pull/2770
* @Ashley-996 made their first contribution in https://github.com/soxoj/maigret/pull/2780
* @jichaowang02-lang made their first contribution in https://github.com/soxoj/maigret/pull/2788
* @aznikline made their first contribution in https://github.com/soxoj/maigret/pull/2795
* @yyq1043-cloud made their first contribution in https://github.com/soxoj/maigret/pull/2803
* @Sushanth012 made their first contribution in https://github.com/soxoj/maigret/pull/2802
* @thunderstornX made their first contribution in https://github.com/soxoj/maigret/pull/2774
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.6.1...v0.6.2
## [0.6.1] - 2026-05-15
## What's Changed
* build(deps): bump pypdf from 6.9.2 to 6.10.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2512
* Fix duplicate attribute initialization in SimpleAiohttpChecker.__init__ by @MichaelMVS in https://github.com/soxoj/maigret/pull/2513
* Support Python 3.14 in tests by @soxoj in https://github.com/soxoj/maigret/pull/2515
* build(deps-dev): bump tuna from 0.5.11 to 0.5.13 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2516
* build(deps): bump lxml from 6.0.3 to 6.0.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2519
* build(deps): bump chardet from 7.4.1 to 7.4.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2517
* build(deps-dev): bump mypy from 1.20.0 to 1.20.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2518
* build(deps): bump pillow from 12.1.1 to 12.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2520
* build(deps): bump chardet from 7.4.2 to 7.4.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2521
* build(deps): bump pypdf from 6.10.0 to 6.10.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2527
* Checks fixes by @soxoj in https://github.com/soxoj/maigret/pull/2528
* Update of Readme and documentation by @soxoj in https://github.com/soxoj/maigret/pull/2514
* build(deps): bump lxml from 6.0.4 to 6.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2533
* Fix site checks: recover 6 CF sites via tls_fingerprint, 500px GraphQ… by @soxoj in https://github.com/soxoj/maigret/pull/2535
* fix site checks: 14 sites → ip_reputation, 7 disabled, 5 dead deleted by @soxoj in https://github.com/soxoj/maigret/pull/2536
* Fix site checks: 4 fixed, 14 → ip_reputation, 8 disabled, 5 dead deleted by @soxoj in https://github.com/soxoj/maigret/pull/2537
* Fix site checks: 3 fixed, 2 → ip_reputation, 7 disabled, 1 dead deleted by @soxoj in https://github.com/soxoj/maigret/pull/2539
* Add 3 crypto sites (Polymarket, Zora, Revolut.me), added crypto inves… by @soxoj in https://github.com/soxoj/maigret/pull/2538
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2541
* Fix site checks: 3 fixed, 2 → ip_reputation, 7 disabled, 1 dead deleted by @soxoj in https://github.com/soxoj/maigret/pull/2543
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2545
* Add OnlyFans with activation mechanism; updated site ranks by @soxoj in https://github.com/soxoj/maigret/pull/2546
* build(deps-dev): bump mypy from 1.20.1 to 1.20.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2547
* build(deps): bump idna from 3.11 to 3.12 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2548
* Fix site checks: 3 → ip_reputation, 10 fixed, 6 disabled, 2 dead deleted by @soxoj in https://github.com/soxoj/maigret/pull/2549
* Fix site checks: 12 fixed, 19 disabled; add new protection tags by @soxoj in https://github.com/soxoj/maigret/pull/2550
* build(deps): bump certifi from 2026.2.25 to 2026.4.22 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2552
* AI mode by @soxoj in https://github.com/soxoj/maigret/pull/2529
* Fix site checks: 4 → ip_reputation, 9 fixed, 16 disabled, 3 dead dele… by @soxoj in https://github.com/soxoj/maigret/pull/2555
* Fix Google Cloud Shell launch by @soxoj in https://github.com/soxoj/maigret/pull/2557
* build(deps): bump pyinstaller from 6.19.0 to 6.20.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2554
* build(deps): bump idna from 3.12 to 3.13 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2553
* test: loosen executor timing upper bounds for slower CI by @juliosuas in https://github.com/soxoj/maigret/pull/2558
* Fix site checks: 5 fixed; readme fix by @soxoj in https://github.com/soxoj/maigret/pull/2562
* Add Docker web image with multi-stage building by @soxoj in https://github.com/soxoj/maigret/pull/2564
* Fix site checks: 7 fixed, 1 disabled by @soxoj in https://github.com/soxoj/maigret/pull/2565
* Fix site checks: 5 fixed, 4 disabled; fix UA leak bug by @soxoj in https://github.com/soxoj/maigret/pull/2569
* build(deps): bump arabic-reshaper from 3.0.0 to 3.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2573
* Add site checks: 18 new sites by @soxoj in https://github.com/soxoj/maigret/pull/2575
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2576
* build(deps): bump reportlab from 4.4.10 to 4.5.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2578
* Fix ID extraction crash when regex groups are optional by @egrezeli in https://github.com/soxoj/maigret/pull/2572
* Update CONTRIBUTING.md with instructions for developers by @soxoj in https://github.com/soxoj/maigret/pull/2589
* Fix outdated Google Colab setup and dependency installation by @SayanDey322 in https://github.com/soxoj/maigret/pull/2591
* fix: disable RomanticCollection check by @juliosuas in https://github.com/soxoj/maigret/pull/2588
* docs: add Simplified Chinese (zh-CN) README translation by @whtis in https://github.com/soxoj/maigret/pull/2606
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2607
* Improve startup error message for missing dependencies by @SayanDey322 in https://github.com/soxoj/maigret/pull/2593
* Modernize python package workflow by @SayanDey322 in https://github.com/soxoj/maigret/pull/2594
* Fix site checks: 8 → ip_reputation, 6 fixed, 9 disabled, 1 dead deleted by @soxoj in https://github.com/soxoj/maigret/pull/2611
* Reddit fix by @soxoj in https://github.com/soxoj/maigret/pull/2614
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2615
* Fix site checks: 7 fixed, 1 disabled, 1 dead deleted by @soxoj in https://github.com/soxoj/maigret/pull/2616
* Fixed duplicates of YouTube and Periscope by @soxoj in https://github.com/soxoj/maigret/pull/2618
* Fix network graph height to be viewport-responsive instead of fixed 750px by @SayanDey322 in https://github.com/soxoj/maigret/pull/2590
* Add web interface tests by @soxoj in https://github.com/soxoj/maigret/pull/2619
* refactor:reduces the cognitive complexity of get_ai_analysis by @odanilosalve in https://github.com/soxoj/maigret/pull/2581
* AI mode documentation by @soxoj in https://github.com/soxoj/maigret/pull/2620
* build(deps): bump python-bidi from 0.6.7 to 0.6.9 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2622
* build(deps-dev): bump mypy from 1.20.2 to 2.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2625
* Cloudflare bypass webgate by @soxoj in https://github.com/soxoj/maigret/pull/2628
* Fix context field using class instead of instance in error handling by @disappear00 in https://github.com/soxoj/maigret/pull/2627
* Add test for CheckError bug by @soxoj in https://github.com/soxoj/maigret/pull/2631
* Update download badge links in README.md by @soxoj in https://github.com/soxoj/maigret/pull/2636
* fix(security): harden /reports path containment via send_from_directory by @aaronjmars in https://github.com/soxoj/maigret/pull/2635
* build(deps-dev): bump coverage from 7.13.5 to 7.14.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2638
* build(deps): bump idna from 3.13 to 3.14 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2639
* Update links to the community Telegram bot by @soxoj in https://github.com/soxoj/maigret/pull/2641
* build(deps): bump urllib3 from 2.6.3 to 2.7.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2642
* build(deps-dev): bump mypy from 2.0.0 to 2.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2644
* Refresh stale Duolingo usernameClaimed sample (blue → duolingo) by @razbenya in https://github.com/soxoj/maigret/pull/2650
* Fix linktr.ee detector (status_code, not stale message check) by @razbenya in https://github.com/soxoj/maigret/pull/2649
* Apply --proxy to CurlCffiChecker (tls_fingerprint sites) by @razbenya in https://github.com/soxoj/maigret/pull/2648
* Refresh stale Gravatar usernameClaimed sample (blue → automattic) by @razbenya in https://github.com/soxoj/maigret/pull/2651
* Add regression tests for CurlCffiChecker proxy forwarding (#2648 follow-up) by @razbenya in https://github.com/soxoj/maigret/pull/2652
* build(deps): bump idna from 3.14 to 3.15 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2647
* build(deps): bump reportlab from 4.5.0 to 4.5.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2645
* build(deps): bump requests from 2.33.1 to 2.34.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2656
* build(deps-dev): bump pytest-rerunfailures from 16.1 to 16.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2654
* build(deps): bump python-bidi from 0.6.9 to 0.6.10 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2655
* Make xhtml2pdf optional, fix install on Linux without libcairo by @soxoj in https://github.com/soxoj/maigret/pull/2659
* build(deps): bump requests from 2.34.1 to 2.34.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2658
* Fix site checks: 2 fixed, 3 disabled; add Faceit; fix utils import by @soxoj in https://github.com/soxoj/maigret/pull/2660
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.6.0...v0.6.1
## [0.6.0] - 2025-04-10
## What's Changed
* Updated workflows: added 3.13 to test, updated pypi upload by @soxoj in https://github.com/soxoj/maigret/pull/2111
* Bump pypdf from 5.1.0 to 6.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2122
* Bump coverage from 7.9.2 to 7.10.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2117
* Bump soupsieve from 2.6 to 2.7 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2118
* Bump mock from 5.1.0 to 5.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2116
* Bump pytest-asyncio from 1.0.0 to 1.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2114
* Bump pytest-cov from 6.0.0 to 6.2.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2115
* Bump xhtml2pdf from 0.2.16 to 0.2.17 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2149
* Bump requests from 2.32.4 to 2.32.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2165
* Bump lxml from 5.3.0 to 6.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2146
* Bump aiodns from 3.2.0 to 3.5.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2148
* Bump alive-progress from 3.2.0 to 3.3.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2145
* Bump certifi from 2025.6.15 to 2025.8.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2147
* Disabled some sites giving false positive results by @soxoj in https://github.com/soxoj/maigret/pull/2170
* Bump flask from 3.1.1 to 3.1.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2175
* Bump pyinstaller from 6.11.1 to 6.15.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2174
* Bump mypy from 1.14.1 to 1.17.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2173
* Bump pytest from 8.3.4 to 8.4.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2172
* Bump flake8 from 7.1.1 to 7.3.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2171
* Bump aiohttp from 3.12.14 to 3.12.15 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2181
* Bump coverage from 7.10.3 to 7.10.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2180
* Bump psutil from 6.1.1 to 7.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2179
* Bump lxml from 6.0.0 to 6.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2178
* Bump multidict from 6.6.3 to 6.6.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2177
* Bump soupsieve from 2.7 to 2.8 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2185
* Bump typing-extensions from 4.14.1 to 4.15.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2182
* Bump python-bidi from 0.6.3 to 0.6.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2183
* Bump platformdirs from 4.3.8 to 4.4.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2184
* Make web interface accessible for Docker deployment by default by @soxoj in https://github.com/soxoj/maigret/pull/2189
* Bump coverage from 7.10.5 to 7.10.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2192
* Bump pytest-rerunfailures from 15.1 to 16.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2191
* Bump pytest-rerunfailures from 15.1 to 16.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2193
* Bump pytest from 8.4.1 to 8.4.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2194
* Bump pytest-cov from 6.2.1 to 6.3.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2195
* Bump pytest-cov from 6.3.0 to 7.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2196
* Bump mypy from 1.17.1 to 1.18.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2197
* Bump black from 25.1.0 to 25.9.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2203
* Bump mypy from 1.18.1 to 1.18.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2202
* Bump pytest-asyncio from 1.1.0 to 1.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2200
* Bump pyinstaller from 6.15.0 to 6.16.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2199
* Bump reportlab from 4.4.3 to 4.4.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2206
* Bump coverage from 7.10.6 to 7.10.7 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2207
* Bump psutil from 7.0.0 to 7.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2201
* Bump asgiref from 3.9.1 to 3.9.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2204
* Bump lxml from 6.0.1 to 6.0.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2208
* Bump platformdirs from 4.4.0 to 4.5.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2223
* Bump asgiref from 3.9.2 to 3.10.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2220
* Bump yarl from 1.20.1 to 1.22.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2221
* Bump markupsafe from 3.0.2 to 3.0.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2209
* Bump multidict from 6.6.4 to 6.7.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2224
* Bump idna from 3.10 to 3.11 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2227
* Bump aiohttp from 3.12.15 to 3.13.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2225
* Bump coverage from 7.10.7 to 7.11.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2230
* Bump certifi from 2025.8.3 to 2025.10.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2228
* Bump pytest-rerunfailures from 16.0.1 to 16.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2229
* Bump attrs from 25.3.0 to 25.4.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2226
* Bump aiohttp from 3.13.0 to 3.13.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2237
* Bump pypdf from 6.0.0 to 6.1.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2233
* Bump black from 25.9.0 to 25.11.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2239
* Bump python-bidi from 0.6.6 to 0.6.7 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2234
* Bump psutil from 7.1.0 to 7.1.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2240
* Bump coverage from 7.11.0 to 7.12.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2241
* Bump werkzeug from 3.1.3 to 3.1.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2248
* Bump pypdf from 6.1.3 to 6.4.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2245
* Bump asgiref from 3.10.0 to 3.11.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2243
* Bump pytest-asyncio from 1.2.0 to 1.3.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2242
* Bump aiohttp from 3.13.2 to 3.13.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2261
* Bump pytest from 8.4.2 to 9.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2244
* Bump mypy from 1.18.2 to 1.19.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2250
* ♻️ Refactor: Hardcoded relative path for database file by @tang-vu in https://github.com/soxoj/maigret/pull/2285
* ✨ Quality: Missing tests for settings cascade and override logic by @tang-vu in https://github.com/soxoj/maigret/pull/2287
* ✨ Quality: Unexpanded tilde in file path by @tang-vu in https://github.com/soxoj/maigret/pull/2283
* Bump urllib3 from 2.5.0 to 2.6.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2262
* Bump pillow from 11.0.0 to 12.1.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2271
* Bump black from 25.11.0 to 26.3.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2280
* Bump cryptography from 44.0.1 to 46.0.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2270
* Bump pypdf from 6.4.0 to 6.9.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2281
* Dockerfile fix by @soxoj in https://github.com/soxoj/maigret/pull/2290
* Fixed false positives in top-500 by @soxoj in https://github.com/soxoj/maigret/pull/2292
* Update Telegram bot link in README by @soxoj in https://github.com/soxoj/maigret/pull/2293
* Pyinstaller GitHub workflow fix by @soxoj in https://github.com/soxoj/maigret/pull/2298
* Twitter fixed, mirrors mechanism improvement by @soxoj in https://github.com/soxoj/maigret/pull/2299
* build(deps): bump flask from 3.1.2 to 3.1.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2289
* Bump reportlab from 4.4.4 to 4.4.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2251
* build(deps): bump werkzeug from 3.1.4 to 3.1.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2288
* Bump certifi from 2025.10.5 to 2025.11.12 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2249
* Update Telegram bot link in README by @soxoj in https://github.com/soxoj/maigret/pull/2300
* Improve site-check quality by @soxoj in https://github.com/soxoj/maigret/pull/2301
* feat(sites): fix false positives: disable 74 broken sites, fix 8 with… by @soxoj in https://github.com/soxoj/maigret/pull/2302
* Update sites list workflow by @soxoj in https://github.com/soxoj/maigret/pull/2303
* Bump svglib from 1.5.1 to 1.6.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2205
* feat(workflow): fix update site data workflow dependency by @soxoj in https://github.com/soxoj/maigret/pull/2306
* Re-enable taplink.cc with browser User-Agent to bypass Cloudflare by @Copilot in https://github.com/soxoj/maigret/pull/2308
* feat(workflow): fix update site data workflow err by @soxoj in https://github.com/soxoj/maigret/pull/2312
* Update site data workflow fix: remove ambiguous main tag by @soxoj in https://github.com/soxoj/maigret/pull/2313
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2314
* Fix Love.Mail.ru: update to numeric-only identifiers and new profile URL by @Copilot in https://github.com/soxoj/maigret/pull/2307
* Remove dead site xxxforum.org by @Copilot in https://github.com/soxoj/maigret/pull/2310
* Disable forums.developer.nvidia.com (auth-gated user profiles) by @Copilot in https://github.com/soxoj/maigret/pull/2305
* Pin requests-toolbelt>=1.0.0 to fix urllib3 v2 incompatibility by @Copilot in https://github.com/soxoj/maigret/pull/2316
* build(deps): bump reportlab from 4.4.5 to 4.4.10 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2323
* build(deps-dev): bump coverage from 7.12.0 to 7.13.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2321
* build(deps-dev): bump pytest-cov from 7.0.0 to 7.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2320
* build(deps): bump aiohttp-socks from 0.10.1 to 0.11.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2319
* Disable false-positive site probe: amateurvoyeurforum.com by @Copilot in https://github.com/soxoj/maigret/pull/2332
* Disable forums.stevehoffman.tv due to false positives by @Copilot in https://github.com/soxoj/maigret/pull/2331
* [WIP] Fix false-positive probe for vegalab site by @Copilot in https://github.com/soxoj/maigret/pull/2336
* Fix RoyalCams site check using BongaCams white-label pattern by @Copilot in https://github.com/soxoj/maigret/pull/2334
* Fix Setlist site check: switch to message checkType with proper markers by @Copilot in https://github.com/soxoj/maigret/pull/2333
* [WIP] Fix invalid link on forums.imore.com by @Copilot in https://github.com/soxoj/maigret/pull/2337
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2315
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2339
* Fix false-positive site probe: Re-enable Taplink with message checkType by @Copilot in https://github.com/soxoj/maigret/pull/2326
* build(deps): bump aiodns from 3.5.0 to 4.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2345
* build(deps-dev): bump mypy from 1.19.0 to 1.19.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2347
* Disable Librusec site check (false positive) by @Copilot in https://github.com/soxoj/maigret/pull/2349
* Disable MirTesen site check (false positive) by @Copilot in https://github.com/soxoj/maigret/pull/2350
* build(deps): bump attrs from 25.4.0 to 26.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2344
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2341
* feat: add cybersecurity platforms + re-enable Root-Me by @juliosuas in https://github.com/soxoj/maigret/pull/2318
* Fix club.cnews.ru false positive: switch from status_code to message checkType by @Copilot in https://github.com/soxoj/maigret/pull/2342
* Fix SoundCloud false-positive: switch to message-based check by @Copilot in https://github.com/soxoj/maigret/pull/2355
* build(deps): bump certifi from 2025.11.12 to 2026.2.25 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2346
* feat: add tag blacklisting via `--exclude-tags` by @Copilot in https://github.com/soxoj/maigret/pull/2352
* Fix domain substring matching and NoneType crash in submit dialog by @Copilot in https://github.com/soxoj/maigret/pull/2367
* feat(core): add POST request support, new sites, migrate to Majestic Million ranking by @soxoj in https://github.com/soxoj/maigret/pull/2317
* Fix update-site-data workflow race condition on branch push by @Copilot in https://github.com/soxoj/maigret/pull/2366
* Fix false-positive site checks reported by Maigret Bot by @soxoj in https://github.com/soxoj/maigret/pull/2376
* build(deps): bump pycountry from 24.6.1 to 26.2.16 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2382
* Added Max.ru check; --no-progressbar flag fixed by @soxoj in https://github.com/soxoj/maigret/pull/2386
* build(deps): bump asgiref from 3.11.0 to 3.11.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2384
* build(deps): bump yarl from 1.22.0 to 1.23.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2383
* build(deps): bump pypdf from 6.9.1 to 6.9.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2392
* build(deps-dev): bump pytest-httpserver from 1.1.0 to 1.1.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2397
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2399
* build(deps): bump requests from 2.32.5 to 2.33.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2394
* Readme update: commercial use by @soxoj in https://github.com/soxoj/maigret/pull/2403
* build(deps): bump pyinstaller from 6.16.0 to 6.19.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2405
* build(deps): bump psutil from 7.1.3 to 7.2.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2406
* build(deps-dev): bump pytest from 9.0.1 to 9.0.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2381
* build(deps): bump soupsieve from 2.8 to 2.8.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2404
* Sites re-check by @soxoj in https://github.com/soxoj/maigret/pull/2423
* Add urlProbes by @soxoj in https://github.com/soxoj/maigret/pull/2425
* build(deps): bump cryptography from 46.0.5 to 46.0.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2422
* Tags and site names improvements by @soxoj in https://github.com/soxoj/maigret/pull/2427
* Overhaul site tags and naming: add social tag to 33 networks, fill mi… by @soxoj in https://github.com/soxoj/maigret/pull/2430
* build(deps): bump multidict from 6.7.0 to 6.7.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2396
* build(deps): bump chardet from 5.2.0 to 7.4.0.post2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2436
* build(deps): bump platformdirs from 4.5.0 to 4.9.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2434
* build(deps): bump aiohttp from 3.13.3 to 3.13.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2435
* build(deps): bump pygments from 2.18.0 to 2.20.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2440
* build(deps): bump requests from 2.33.0 to 2.33.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2444
* build(deps-dev): bump mypy from 1.19.1 to 1.20.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2447
* build(deps): bump aiohttp from 3.13.4 to 3.13.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2448
* Add site protection tracking system, fix broken site checks (Instagra… by @soxoj in https://github.com/soxoj/maigret/pull/2452
* Multiple lint and types fixes by @soxoj in https://github.com/soxoj/maigret/pull/2454
* fix(data): update InterPals absence string to match current site response by @juliosuas in https://github.com/soxoj/maigret/pull/2442
* Update of MIT License by @soxoj in https://github.com/soxoj/maigret/pull/2455
* Added Crypto/Web3 site checks by @soxoj in https://github.com/soxoj/maigret/pull/2457
* DB update mechanism by @soxoj in https://github.com/soxoj/maigret/pull/2458
* Fix false positives by @soxoj in https://github.com/soxoj/maigret/pull/2459
* False positive fixes by @soxoj in https://github.com/soxoj/maigret/pull/2460
* build(deps): bump curl-cffi from 0.14.0 to 0.15.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2462
* Add Markdown reports for LLM analysis by @soxoj in https://github.com/soxoj/maigret/pull/2463
* Sites fixes by @soxoj in https://github.com/soxoj/maigret/pull/2464
* Add installation troubleshooting for missing system dependencies by @Copilot in https://github.com/soxoj/maigret/pull/2465
* Fix Spotify, add Spotify Community forum by @soxoj in https://github.com/soxoj/maigret/pull/2467
* Fix crash on `-a --self-check` by adding exception handling to site check coroutines by @Copilot in https://github.com/soxoj/maigret/pull/2466
* Fix failing test for custom DB path resolution by @soxoj in https://github.com/soxoj/maigret/pull/2468
* Bump lxml minimum to 6.0.2 for Python 3.14 compatibility by @ocervell in https://github.com/soxoj/maigret/pull/2279
* build(deps-dev): bump pytest from 9.0.2 to 9.0.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2473
* Update HackTheBox and Wikipedia to use new API endpoints by @Copilot in https://github.com/soxoj/maigret/pull/2470
* Automated Sites List Update by @github-actions[bot] in https://github.com/soxoj/maigret/pull/2474
* build(deps): bump chardet from 7.4.0.post2 to 7.4.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2472
* build(deps): bump cryptography from 46.0.6 to 46.0.7 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2475
* vBulletin cleanup, Flarum sites, engine stats, UA bump by @soxoj in https://github.com/soxoj/maigret/pull/2476
* build(deps): bump platformdirs from 4.9.4 to 4.9.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2477
* Re-enable 69 stale-disabled sites validated via self-check by @soxoj in https://github.com/soxoj/maigret/pull/2478
* Fix false positives by @soxoj in https://github.com/soxoj/maigret/pull/2499
* build(deps): bump socid-extractor from 0.0.27 to 0.0.28 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2502
* build(deps): bump lxml from 6.0.2 to 6.0.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2501
* Disable Kinja.com site check by @Copilot in https://github.com/soxoj/maigret/pull/2503
* Added 3 sites, fixed 6, disabled 8 by @soxoj in https://github.com/soxoj/maigret/pull/2505
* Bump to 0.6.0 by @soxoj in https://github.com/soxoj/maigret/pull/2506
* Update workflow to trigger on published releases by @soxoj in https://github.com/soxoj/maigret/pull/2508
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.5.0...v0.6.0
## [0.5.0] - 2025-08-10
* Site Supression by @C3n7ral051nt4g3ncy in https://github.com/soxoj/maigret/pull/627
* Bump yarl from 1.7.2 to 1.8.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/626
* Streaming sites by @soxoj in https://github.com/soxoj/maigret/pull/628
* Mirrors by @fen0s in https://github.com/soxoj/maigret/pull/630
* Added Instagram scrapers by @soxoj in https://github.com/soxoj/maigret/pull/633
* Bump psutil from 5.9.1 to 5.9.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/624
* Bump pypdf2 from 2.10.4 to 2.10.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/625
* Invalid results fixes by @soxoj in https://github.com/soxoj/maigret/pull/634
* Bump pytest-httpserver from 1.0.5 to 1.0.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/638
* Bump pypdf2 from 2.10.5 to 2.10.8 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/641
* Bump certifi from 2022.6.15 to 2022.9.14 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/644
* Bump idna from 3.3 to 3.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/640
* fix false positives from bot by @fen0s in https://github.com/soxoj/maigret/pull/663
* Add pre commit hook by @fen0s in https://github.com/soxoj/maigret/pull/664
* site deletion by @C3n7ral051nt4g3ncy in https://github.com/soxoj/maigret/pull/648
* Changed docker run to interactive and remove on exit by @dr-BEat in https://github.com/soxoj/maigret/pull/675
* Corrected grammar in README.md by @Trkzi-Omar in https://github.com/soxoj/maigret/pull/674
* fix sites from issues by @fen0s in https://github.com/soxoj/maigret/pull/680
* correct username in usage examples by @LeonGr in https://github.com/soxoj/maigret/pull/673
* Update README.md by @johanburati in https://github.com/soxoj/maigret/pull/669
* Fix typos by @LorenzoSapora in https://github.com/soxoj/maigret/pull/681
* Build docker images for arm64 and amd64 by @krydos in https://github.com/soxoj/maigret/pull/687
* Bump certifi from 2022.9.14 to 2022.9.24 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/652
* Bump aiohttp from 3.8.1 to 3.8.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/651
* Bump arabic-reshaper from 2.1.3 to 2.1.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/650
* Update README.md, Repl.it -> Replit with new badge by @PeterDaveHello in https://github.com/soxoj/maigret/pull/692
* Refactor Dockerfile with best practices by @PeterDaveHello in https://github.com/soxoj/maigret/pull/691
* Improve README.md Installation section by @PeterDaveHello in https://github.com/soxoj/maigret/pull/690
* Bump pytest-cov from 3.0.0 to 4.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/688
* Bump stem from 1.8.0 to 1.8.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/689
* Bump typing-extensions from 4.3.0 to 4.4.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/698
* Typo fixes in error.py by @Ben-Chapman in https://github.com/soxoj/maigret/pull/711
* Fixed docs about tags by @soxoj in https://github.com/soxoj/maigret/pull/715
* Fixed lightstalking.com by @soxoj in https://github.com/soxoj/maigret/pull/716
* Fixed YouTube by @soxoj in https://github.com/soxoj/maigret/pull/717
* Bump pytest-asyncio from 0.19.0 to 0.20.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/732
* Updated snapcraft yaml by @kz6fittycent in https://github.com/soxoj/maigret/pull/720
* Bump colorama from 0.4.5 to 0.4.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/733
* Bump pytest from 7.1.3 to 7.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/734
* disable not working sites by @fen0s in https://github.com/soxoj/maigret/pull/739
* disable broken sites by @fen0s in https://github.com/soxoj/maigret/pull/756
* Bump cloudscraper from 1.2.64 to 1.2.66 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/769
* fix opensea and shutterstock, disable a few dead sites by @fen0s in https://github.com/soxoj/maigret/pull/798
* Fixed documentation URL by @soxoj in https://github.com/soxoj/maigret/pull/799
* Small readme fix by @soxoj in https://github.com/soxoj/maigret/pull/857
* docs spelling error by @Nadeem-05 in https://github.com/soxoj/maigret/pull/866
* Fix Pinterest false positive by @therealchiendat in https://github.com/soxoj/maigret/pull/862
* Added new Websites by @codyMar30 in https://github.com/soxoj/maigret/pull/838
* Update "future" package to v0.18.3 by @PeterDaveHello in https://github.com/soxoj/maigret/pull/834
* Bump certifi from 2022.9.24 to 2022.12.7 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/793
* Update dependency - networkx from v2.5.1 to v2.6 by @PeterDaveHello in https://github.com/soxoj/maigret/pull/738
* Bump reportlab from 3.6.11 to 3.6.12 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/735
* Bump typing-extensions from 4.4.0 to 4.5.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/888
* Bump psutil from 5.9.2 to 5.9.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/741
* Bump attrs from 22.1.0 to 22.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/892
* Bump multidict from 6.0.2 to 6.0.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/891
* Fixed false positives, updated networkx dep, some lint fixes by @soxoj in https://github.com/soxoj/maigret/pull/894
* Bump lxml from 4.9.1 to 4.9.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/900
* Bump yarl from 1.8.1 to 1.8.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/899
* Fixed false positives on Mastodon sites by @soxoj in https://github.com/soxoj/maigret/pull/901
* Added valid regex for Mastodon instances (#848) by @soxoj in https://github.com/soxoj/maigret/pull/906
* Fix missing Mastodon Regex on #906 by @therealchiendat in https://github.com/soxoj/maigret/pull/908
* Bump tqdm from 4.64.1 to 4.65.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/905
* Bump requests from 2.28.1 to 2.28.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/904
* Bump psutil from 5.9.4 to 5.9.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/910
* fix deployment of tests by @noraj in https://github.com/soxoj/maigret/pull/933
* Added 26 ENS and similar domains with tag `crypto` by @soxoj in https://github.com/soxoj/maigret/pull/942
* Bump requests from 2.28.2 to 2.31.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/957
* Update wizard.py by @engNoori in https://github.com/soxoj/maigret/pull/1016
* Improved search through UnstoppableDomains by @soxoj in https://github.com/soxoj/maigret/pull/1040
* Added memory.lol (Twitter usernames archive) by @soxoj in https://github.com/soxoj/maigret/pull/1067
* Disabled and fixed several sites by @soxoj in https://github.com/soxoj/maigret/pull/1132
* Fixed some sites (again) by @soxoj in https://github.com/soxoj/maigret/pull/1133
* fix(sec): upgrade reportlab to 3.6.13 by @realize096 in https://github.com/soxoj/maigret/pull/1051
* Add compatibility with pytest >= 7.3.0 by @tjni in https://github.com/soxoj/maigret/pull/1117
* Additionally fixed sites, win32 build fix by @soxoj in https://github.com/soxoj/maigret/pull/1148
* Sites fixes 250823 by @soxoj in https://github.com/soxoj/maigret/pull/1149
* Bump reportlab from 3.6.12 to 4.0.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1160
* Bump certifi from 2022.12.7 to 2023.7.22 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1070
* fix(sec): upgrade certifi to 2022.12.07 by @realize096 in https://github.com/soxoj/maigret/pull/1173
* Bump cloudscraper from 1.2.66 to 1.2.71 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/914
* Some sites fixed & cloudflare detection by @soxoj in https://github.com/soxoj/maigret/pull/1178
* EasyInstaller because everyone likes saving time :) by @CatchySmile in https://github.com/soxoj/maigret/pull/1212
* Tests fixes + last updates by @soxoj in https://github.com/soxoj/maigret/pull/1228
* Bump pypdf2 from 2.10.8 to 3.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/815
* Bump pyvis from 0.2.1 to 0.3.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/861
* Bump xhtml2pdf from 0.2.8 to 0.2.11 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/935
* Bump flake8 from 5.0.4 to 6.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1091
* Bump aiohttp from 3.8.3 to 3.8.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1222
* Specified pyinstaller version by @soxoj in https://github.com/soxoj/maigret/pull/1230
* Pyinstaller fix by @soxoj in https://github.com/soxoj/maigret/pull/1231
* Test pyinstaller on dev branch by @soxoj in https://github.com/soxoj/maigret/pull/1233
* Update main from dev again by @soxoj in https://github.com/soxoj/maigret/pull/1234
* Bump typing-extensions from 4.5.0 to 4.8.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1239
* Bump pytest-rerunfailures from 10.2 to 12.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1237
* Bump async-timeout from 4.0.2 to 4.0.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1238
* Changed pyinstaller dir by @soxoj in https://github.com/soxoj/maigret/pull/1245
* Bump tqdm from 4.65.0 to 4.66.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1235
* Updating site checkers, disabling suspended sites by @MeowyPouncer in https://github.com/soxoj/maigret/pull/1266
* Updated site statistics by @soxoj in https://github.com/soxoj/maigret/pull/1273
* Compat RegataOS (Opensuse) by @Jeiel0rbit in https://github.com/soxoj/maigret/pull/1308
* fix reddit by @hhhtylerw in https://github.com/soxoj/maigret/pull/1296
* Added Telegram bot link by @soxoj in https://github.com/soxoj/maigret/pull/1321
* Added SOWEL classification by @soxoj in https://github.com/soxoj/maigret/pull/1453
* Bump jinja2 from 3.1.2 to 3.1.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1358
* Fixed/Disabled sites. Update requirements.txt by @rly0nheart in https://github.com/soxoj/maigret/pull/1517
* Fixed 4 sites, added 6 sites, disabled 27 sites by @rly0nheart in https://github.com/soxoj/maigret/pull/1536
* Fixed 3 sites, disabed 3, added by @rly0nheart in https://github.com/soxoj/maigret/pull/1539
* Bump socid-extractor from 0.0.24 to 0.0.26 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1546
* Added code conventions to CONTRIBUTING.md by @Lord-Topa in https://github.com/soxoj/maigret/pull/1589
* Readme by @Lord-Topa in https://github.com/soxoj/maigret/pull/1588
* Update data.json by @ranlo in https://github.com/soxoj/maigret/pull/1559
* Adding permutator feature for usernames by @balestek in https://github.com/soxoj/maigret/pull/1575
* Alik.cz indirectly requests removal by @ppfeister in https://github.com/soxoj/maigret/pull/1671
* Fixed 1 site, PyInstaller workflow, Google Colab example by @Ixve in https://github.com/soxoj/maigret/pull/1558
* Bump soupsieve from 2.5 to 2.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1708
* Added dev documentation, fixed some sites, removed GitHub issue links… by @soxoj in https://github.com/soxoj/maigret/pull/1869
* Bump cryptography from 42.0.7 to 43.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1870
* Bump requests-futures from 1.0.1 to 1.0.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1868
* Bump werkzeug from 3.0.3 to 3.0.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1846
* Added .readthedocs.yaml, fixed Pyinstaller and Docker workflows by @soxoj in https://github.com/soxoj/maigret/pull/1874
* Added GitHub and BuyMeACoffee sponsorships by @soxoj in https://github.com/soxoj/maigret/pull/1875
* Bump psutil from 5.9.5 to 6.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1839
* Bump flake8 from 6.1.0 to 7.1.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1692
* Bump future from 0.18.3 to 1.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1545
* Bump urllib3 from 2.2.1 to 2.2.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1600
* Bump certifi from 2023.11.17 to 2024.8.30 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1840
* Fixed test for aiohttp 3.10 by @soxoj in https://github.com/soxoj/maigret/pull/1876
* Bump aiohttp from 3.9.5 to 3.10.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1721
* Added new badges to README by @soxoj in https://github.com/soxoj/maigret/pull/1877
* Show detailed error statistics for `-v` by @soxoj in https://github.com/soxoj/maigret/pull/1879
* Disabled unavailable sites by @soxoj in https://github.com/soxoj/maigret/pull/1880
* Added 7 sites, implemented integration with Marple, docs update by @soxoj in https://github.com/soxoj/maigret/pull/1881
* Bump pefile from 2022.5.30 to 2024.8.26 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1883
* Bump lxml from 4.9.4 to 5.3.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1884
* New sites added by @soxoj in https://github.com/soxoj/maigret/pull/1888
* Improved self-check mode, added 15 sites by @soxoj in https://github.com/soxoj/maigret/pull/1887
* Bump pyinstaller from 6.1 to 6.11.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1882
* Bump pytest-asyncio from 0.23.7 to 0.23.8 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1885
* Pyinstaller bump & pefile fix by @soxoj in https://github.com/soxoj/maigret/pull/1890
* Bump python-bidi from 0.4.2 to 0.6.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1886
* Sites checks fixes by @soxoj in https://github.com/soxoj/maigret/pull/1896
* Parallel execution optimization by @soxoj in https://github.com/soxoj/maigret/pull/1897
* Maigret bot support (custom progress function fixed) by @soxoj in https://github.com/soxoj/maigret/pull/1898
* Bump markupsafe from 2.1.5 to 3.0.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1895
* Retries set to 0 by default, refactored code of executor with progress by @soxoj in https://github.com/soxoj/maigret/pull/1899
* Bump aiohttp-socks from 0.7.1 to 0.9.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1900
* Bump pycountry from 23.12.11 to 24.6.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1903
* Bump pytest-cov from 4.1.0 to 6.0.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1902
* Bump pyvis from 0.2.1 to 0.3.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1893
* Close http connections (#1595) by @soxoj in https://github.com/soxoj/maigret/pull/1905
* New logo by @soxoj in https://github.com/soxoj/maigret/pull/1906
* Fixed dateutil parsing error for CDT timezone by @soxoj in https://github.com/soxoj/maigret/pull/1907
* Bump alive-progress from 2.4.1 to 3.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1910
* Permutator output and documentation updates by @soxoj in https://github.com/soxoj/maigret/pull/1914
* Bump aiohttp from 3.11.7 to 3.11.8 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1912
* Bump async-timeout from 4.0.3 to 5.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1909
* An recursive search animation in README has been updated by @soxoj in https://github.com/soxoj/maigret/pull/1915
* Bump pytest-rerunfailures from 12.0 to 15.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1911
* Bump attrs from 22.2.0 to 24.2.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1913
* Sites fixes by @soxoj in https://github.com/soxoj/maigret/pull/1917
* Update README.md by @soxoj in https://github.com/soxoj/maigret/pull/1919
* Refactored sites module, updated documentation by @soxoj in https://github.com/soxoj/maigret/pull/1918
* Bump aiohttp from 3.11.8 to 3.11.9 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1920
* Bump pytest from 7.4.4 to 8.3.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1923
* Bump yarl from 1.18.0 to 1.18.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1922
* Bump pytest-asyncio from 0.23.8 to 0.24.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1925
* Documentation update by @soxoj in https://github.com/soxoj/maigret/pull/1926
* Bump mock from 4.0.3 to 5.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1921
* Bump pywin32-ctypes from 0.2.1 to 0.2.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1924
* Installation docs update by @soxoj in https://github.com/soxoj/maigret/pull/1927
* Disabled Figma check by @soxoj in https://github.com/soxoj/maigret/pull/1928
* Put Windows executable in Releases for each dev and main commit by @soxoj in https://github.com/soxoj/maigret/pull/1929
* Updated PyInstaller workflow by @soxoj in https://github.com/soxoj/maigret/pull/1930
* Documentation update by @soxoj in https://github.com/soxoj/maigret/pull/1931
* Fixed Figma check and some bugs by @soxoj in https://github.com/soxoj/maigret/pull/1932
* Bump six from 1.16.0 to 1.17.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1933
* Activation mechanism documentation added by @soxoj in https://github.com/soxoj/maigret/pull/1935
* Readme/docs update based on GH discussions by @soxoj in https://github.com/soxoj/maigret/pull/1936
* Bump aiohttp from 3.11.9 to 3.11.10 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1937
* Weibo site check fix, activation mechanism added by @soxoj in https://github.com/soxoj/maigret/pull/1938
* Fixed Ebay and BongaCams checks by @soxoj in https://github.com/soxoj/maigret/pull/1939
* Sites fixes by @soxoj in https://github.com/soxoj/maigret/pull/1940
* Fixed Linktr and discourse.mozilla.org by @soxoj in https://github.com/soxoj/maigret/pull/1941
* Refactored self-check method, code formatting, small lint fixes by @soxoj in https://github.com/soxoj/maigret/pull/1942
* Refactoring, test coverage increased to 60% by @soxoj in https://github.com/soxoj/maigret/pull/1943
* Added a test for submitter by @soxoj in https://github.com/soxoj/maigret/pull/1944
* Update README.md by @soxoj in https://github.com/soxoj/maigret/pull/1949
* Updated OP.GG checks by @soxoj in https://github.com/soxoj/maigret/pull/1950
* Fixed ProductHunt check by @soxoj in https://github.com/soxoj/maigret/pull/1951
* Improved check feature extraction function, added tests by @soxoj in https://github.com/soxoj/maigret/pull/1952
* Submit improvements and site check fixes by @soxoj in https://github.com/soxoj/maigret/pull/1956
* chore: update submit.py by @eltociear in https://github.com/soxoj/maigret/pull/1957
* Fixed Gravatar parsing (socid_extractor) by @soxoj in https://github.com/soxoj/maigret/pull/1958
* Site check fixes by @soxoj in https://github.com/soxoj/maigret/pull/1962
* fix bad linux filename generation by @overcuriousity in https://github.com/soxoj/maigret/pull/1961
* Bump pytest-asyncio from 0.24.0 to 0.25.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1963
* Fixed flaky tests to check cookies by @soxoj in https://github.com/soxoj/maigret/pull/1965
* Preparation of 0.5.0 alpha version by @soxoj in https://github.com/soxoj/maigret/pull/1966
* Created web frontend launched via --web flag by @overcuriousity in https://github.com/soxoj/maigret/pull/1967
* Bump certifi from 2024.8.30 to 2024.12.14 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1969
* Bump attrs from 24.2.0 to 24.3.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1970
* Added web interface docs by @soxoj in https://github.com/soxoj/maigret/pull/1972
* Small docs and parameters fixes for web interface mode by @soxoj in https://github.com/soxoj/maigret/pull/1973
* [ImgBot] Optimize images by @imgbot[bot] in https://github.com/soxoj/maigret/pull/1974
* Improving the web interface by @overcuriousity in https://github.com/soxoj/maigret/pull/1975
* make graph more meaningful by @overcuriousity in https://github.com/soxoj/maigret/pull/1977
* Async generator-executor for site checks by @soxoj in https://github.com/soxoj/maigret/pull/1978
* Bump aiohttp from 3.11.10 to 3.11.11 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1979
* Bump psutil from 6.1.0 to 6.1.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1980
* Bump aiohttp-socks from 0.9.1 to 0.10.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1985
* Bump mypy from 1.13.0 to 1.14.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1983
* Bump aiohttp-socks from 0.10.0 to 0.10.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1987
* Bump jinja2 from 3.1.4 to 3.1.5 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1982
* Bump coverage from 7.6.9 to 7.6.10 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1986
* Bump pytest-asyncio from 0.25.0 to 0.25.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1989
* Bump mypy from 1.14.0 to 1.14.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1988
* Bump pytest-asyncio from 0.25.1 to 0.25.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/1990
* docs: update usage-examples.rst by @eltociear in https://github.com/soxoj/maigret/pull/1996
* upload-artifact action in python test workflow updated to v4 by @soxoj in https://github.com/soxoj/maigret/pull/2024
* Pass db_file configuration to web interface by @pykereaper in https://github.com/soxoj/maigret/pull/2019
* Fix usage of data.json files from web by @pykereaper in https://github.com/soxoj/maigret/pull/2020
* Bump black from 24.10.0 to 25.1.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2001
* Important Update Installer.bat by @CatchySmile in https://github.com/soxoj/maigret/pull/1994
* Bump cryptography from 44.0.0 to 44.0.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2005
* Bump jinja2 from 3.1.5 to 3.1.6 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2011
* [#2010] Add 6 more websites to manage by @pylapp in https://github.com/soxoj/maigret/pull/2009
* Bump flask from 3.1.0 to 3.1.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2028
* Bump requests from 2.32.3 to 2.32.4 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2026
* Bump pycares from 4.5.0 to 4.9.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2025
* Bump pytest-asyncio from 0.25.2 to 0.26.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2016
* Bump urllib3 from 2.2.3 to 2.5.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2027
* Disable ICQ site by @Echo-Darlyson in https://github.com/soxoj/maigret/pull/1993
* Bump attrs from 24.3.0 to 25.3.0 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2014
* Bump certifi from 2024.12.14 to 2025.1.31 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2004
* Bump typing-extensions from 4.12.2 to 4.14.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2038
* Disable AskFM by @MR-VL in https://github.com/soxoj/maigret/pull/2037
* Bump platformdirs from 4.3.6 to 4.3.8 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2033
* Bump coverage from 7.6.10 to 7.9.2 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2039
* Bump aiohttp from 3.11.11 to 3.12.14 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2041
* Bump yarl from 1.18.3 to 1.20.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2032
* Fixed test dialog_adds_site_negative by @soxoj in https://github.com/soxoj/maigret/pull/2107
* Bump reportlab from 4.2.5 to 4.4.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2063
* Bump asgiref from 3.8.1 to 3.9.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2040
* Bump multidict from 6.1.0 to 6.6.3 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2034
* Bump pytest-rerunfailures from 15.0 to 15.1 by @dependabot[bot] in https://github.com/soxoj/maigret/pull/2030
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.4.4...v0.5.0
## [0.4.4] - 2022-09-03
* Fixed some false positives by @soxoj in https://github.com/soxoj/maigret/pull/433
* Drop Python 3.6 support by @soxoj in https://github.com/soxoj/maigret/pull/434
* Bump xhtml2pdf from 0.2.5 to 0.2.7 by @dependabot in https://github.com/soxoj/maigret/pull/409
* Bump reportlab from 3.6.6 to 3.6.9 by @dependabot in https://github.com/soxoj/maigret/pull/403
* Bump markupsafe from 2.0.1 to 2.1.1 by @dependabot in https://github.com/soxoj/maigret/pull/389
* Bump pycountry from 22.1.10 to 22.3.5 by @dependabot in https://github.com/soxoj/maigret/pull/384
* Bump pypdf2 from 1.26.0 to 1.27.4 by @dependabot in https://github.com/soxoj/maigret/pull/438
* Update GH actions by @soxoj in https://github.com/soxoj/maigret/pull/439
* Bump tqdm from 4.63.0 to 4.64.0 by @dependabot in https://github.com/soxoj/maigret/pull/440
* Bump jinja2 from 3.0.3 to 3.1.1 by @dependabot in https://github.com/soxoj/maigret/pull/441
* Bump soupsieve from 2.3.1 to 2.3.2 by @dependabot in https://github.com/soxoj/maigret/pull/436
* Bump pypdf2 from 1.26.0 to 1.27.4 by @dependabot in https://github.com/soxoj/maigret/pull/442
* Bump pyvis from 0.1.9 to 0.2.0 by @dependabot in https://github.com/soxoj/maigret/pull/443
* Bump pypdf2 from 1.27.4 to 1.27.6 by @dependabot in https://github.com/soxoj/maigret/pull/448
* Bump typing-extensions from 4.1.1 to 4.2.0 by @dependabot in https://github.com/soxoj/maigret/pull/447
* Bump soupsieve from 2.3.2 to 2.3.2.post1 by @dependabot in https://github.com/soxoj/maigret/pull/444
* Bump pypdf2 from 1.27.6 to 1.27.7 by @dependabot in https://github.com/soxoj/maigret/pull/449
* Bump pypdf2 from 1.27.7 to 1.27.8 by @dependabot in https://github.com/soxoj/maigret/pull/450
* XMind 8 report warning and some docs update by @soxoj in https://github.com/soxoj/maigret/pull/452
* False positive fixes 24.04.22 by @soxoj in https://github.com/soxoj/maigret/pull/455
* Bump pypdf2 from 1.27.8 to 1.27.9 by @dependabot in https://github.com/soxoj/maigret/pull/456
* Bump pytest from 7.0.1 to 7.1.2 by @dependabot in https://github.com/soxoj/maigret/pull/457
* Bump jinja2 from 3.1.1 to 3.1.2 by @dependabot in https://github.com/soxoj/maigret/pull/460
* Ubisoft forums addition by @fen0s in https://github.com/soxoj/maigret/pull/461
* Add BYOND, Figma, BeatStars by @fen0s in https://github.com/soxoj/maigret/pull/462
* fix Figma username definition, add a bunch of sites by @fen0s in https://github.com/soxoj/maigret/pull/464
* Bump pypdf2 from 1.27.9 to 1.27.10 by @dependabot in https://github.com/soxoj/maigret/pull/465
* Bump pypdf2 from 1.27.10 to 1.27.12 by @dependabot in https://github.com/soxoj/maigret/pull/466
* Sites fixes 05 05 22 by @soxoj in https://github.com/soxoj/maigret/pull/469
* Bump pyvis from 0.2.0 to 0.2.1 by @dependabot in https://github.com/soxoj/maigret/pull/472
* Social analyzer websites, also fixing presense strs by @fen0s in https://github.com/soxoj/maigret/pull/471
* Updated logic of false positive risk estimating by @soxoj in https://github.com/soxoj/maigret/pull/475
* Improved usability of external progressbar func by @soxoj in https://github.com/soxoj/maigret/pull/476
* New sites added, some tags/rank update by @soxoj in https://github.com/soxoj/maigret/pull/477
* Added new sites by @soxoj in https://github.com/soxoj/maigret/pull/480
* Added new forums, updated ranks, some utils improvements by @soxoj in https://github.com/soxoj/maigret/pull/481
* Disabled sites with false positives results by @soxoj in https://github.com/soxoj/maigret/pull/482
* Bump certifi from 2021.10.8 to 2022.5.18.1 by @dependabot in https://github.com/soxoj/maigret/pull/488
* Bump psutil from 5.9.0 to 5.9.1 by @dependabot in https://github.com/soxoj/maigret/pull/490
* Bump pypdf2 from 1.27.12 to 1.28.1 by @dependabot in https://github.com/soxoj/maigret/pull/491
* Bump pypdf2 from 1.28.1 to 1.28.2 by @dependabot in https://github.com/soxoj/maigret/pull/493
* added and fixed some websites in data.json by @kustermariocoding in https://github.com/soxoj/maigret/pull/494
* Bump pypdf2 from 1.28.2 to 2.0.0 by @dependabot in https://github.com/soxoj/maigret/pull/504
* Bump pefile from 2021.9.3 to 2022.5.30 by @dependabot in https://github.com/soxoj/maigret/pull/499
* Updated sites list, added disabled Anilist by @soxoj in https://github.com/soxoj/maigret/pull/502
* Bump lxml from 4.8.0 to 4.9.0 by @dependabot in https://github.com/soxoj/maigret/pull/503
* Compatibility with Python 10 by @soxoj in https://github.com/soxoj/maigret/pull/509
* feat: add .log & .bak files to gitignore in https://github.com/soxoj/maigret/pull/511
* fix some sites and delete abandoned by @fen0s in https://github.com/soxoj/maigret/pull/526
* Fixesjulyfirst by @fen0s in https://github.com/soxoj/maigret/pull/533
* yazbel, aboutcar, zhihu by @fen0s in https://github.com/soxoj/maigret/pull/531
* Fixes july third by @fen0s in https://github.com/soxoj/maigret/pull/535
* Update data.json by @fen0s in https://github.com/soxoj/maigret/pull/539
* Update data.json by @fen0s in https://github.com/soxoj/maigret/pull/540
* Bump reportlab from 3.6.9 to 3.6.11 by @dependabot in https://github.com/soxoj/maigret/pull/543
* Bump requests from 2.27.1 to 2.28.1 by @dependabot in https://github.com/soxoj/maigret/pull/530
* Bump pypdf2 from 2.0.0 to 2.5.0 by @dependabot in https://github.com/soxoj/maigret/pull/542
* Bump xhtml2pdf from 0.2.7 to 0.2.8 by @dependabot in https://github.com/soxoj/maigret/pull/522
* Bump lxml from 4.9.0 to 4.9.1 by @dependabot in https://github.com/soxoj/maigret/pull/538
* disable yandex music + set utf8 encoding by @fen0s in https://github.com/soxoj/maigret/pull/562
* fix false positives by @fen0s in https://github.com/soxoj/maigret/pull/577
* disable Instagram, fix two false positives by @fen0s in https://github.com/soxoj/maigret/pull/578
* Bump certifi from 2022.5.18.1 to 2022.6.15 by @dependabot in https://github.com/soxoj/maigret/pull/551
* August15 by @fen0s in https://github.com/soxoj/maigret/pull/591
* Bump pytest-httpserver from 1.0.4 to 1.0.5 by @dependabot in https://github.com/soxoj/maigret/pull/583
* Bump typing-extensions from 4.2.0 to 4.3.0 by @dependabot in https://github.com/soxoj/maigret/pull/549
* Bump colorama from 0.4.4 to 0.4.5 by @dependabot in https://github.com/soxoj/maigret/pull/548
* Bump chardet from 4.0.0 to 5.0.0 by @dependabot in https://github.com/soxoj/maigret/pull/550
* Bump cloudscraper from 1.2.60 to 1.2.63 by @dependabot in https://github.com/soxoj/maigret/pull/600
* Bump flake8 from 4.0.1 to 5.0.4 by @dependabot in https://github.com/soxoj/maigret/pull/598
* Bump attrs from 21.4.0 to 22.1.0 by @dependabot in https://github.com/soxoj/maigret/pull/597
* Bump pytest-asyncio from 0.18.2 to 0.19.0 by @dependabot in https://github.com/soxoj/maigret/pull/601
* Bump pypdf2 from 2.5.0 to 2.10.4 by @dependabot in https://github.com/soxoj/maigret/pull/606
* Bump pytest from 7.1.2 to 7.1.3 by @dependabot in https://github.com/soxoj/maigret/pull/613
* Update sites.md -Gitmemory.com suppression by @C3n7ral051nt4g3ncy in https://github.com/soxoj/maigret/pull/610
* Bump cloudscraper from 1.2.63 to 1.2.64 by @dependabot in https://github.com/soxoj/maigret/pull/614
* Bump pycountry from 22.1.10 to 22.3.5 by @dependabot in https://github.com/soxoj/maigret/pull/607
* add ProtonMail, disable 3 broken sites by @fen0s in https://github.com/soxoj/maigret/pull/619
* Bump tqdm from 4.64.0 to 4.64.1 by @dependabot in https://github.com/soxoj/maigret/pull/618
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.4.3...v0.4.4
## [0.4.3] - 2022-04-13
* Added Sites to data.json by @kustermariocoding in https://github.com/soxoj/maigret/pull/386
* added new Websites to data.json by @kustermariocoding in https://github.com/soxoj/maigret/pull/390
* Skipped broken tests by @soxoj in https://github.com/soxoj/maigret/pull/397
* Added new Websites to data.json by @kustermariocoding in https://github.com/soxoj/maigret/pull/401
* Added new Websites to data.json by @kustermariocoding in https://github.com/soxoj/maigret/pull/404
* Updated statistics by @soxoj in https://github.com/soxoj/maigret/pull/406
* Added new Websites to data.json by @kustermariocoding in https://github.com/soxoj/maigret/pull/413
* Disabled houzz.com, updated sites statistics by @soxoj in https://github.com/soxoj/maigret/pull/422
* Fixed last false positives by @soxoj in https://github.com/soxoj/maigret/pull/424
* Fixed actual false positives by @soxoj in https://github.com/soxoj/maigret/pull/431
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.4.2...v0.4.3
## [0.4.2] - 2022-03-07
* [ImgBot] Optimize images by @imgbot in https://github.com/soxoj/maigret/pull/319
* Bump pytest-asyncio from 0.17.0 to 0.17.1 by @dependabot in https://github.com/soxoj/maigret/pull/321
* Bump pytest-asyncio from 0.17.1 to 0.17.2 by @dependabot in https://github.com/soxoj/maigret/pull/323
* Disabled Ruboard by @soxoj in https://github.com/soxoj/maigret/pull/327
* Disable kinooh, sites list update workflow added by @soxoj in https://github.com/soxoj/maigret/pull/329
* Bump multidict from 5.2.0 to 6.0.1 by @dependabot in https://github.com/soxoj/maigret/pull/332
* Bump multidict from 6.0.1 to 6.0.2 by @dependabot in https://github.com/soxoj/maigret/pull/333
* Bump pytest-httpserver from 1.0.3 to 1.0.4 by @dependabot in https://github.com/soxoj/maigret/pull/334
* Bump pytest from 6.2.5 to 7.0.0 by @dependabot in https://github.com/soxoj/maigret/pull/339
* Bump pytest-asyncio from 0.17.2 to 0.18.0 by @dependabot in https://github.com/soxoj/maigret/pull/340
* Bump pytest-asyncio from 0.18.0 to 0.18.1 by @dependabot in https://github.com/soxoj/maigret/pull/343
* Bump pytest from 7.0.0 to 7.0.1 by @dependabot in https://github.com/soxoj/maigret/pull/345
* Bump typing-extensions from 4.0.1 to 4.1.1 by @dependabot in https://github.com/soxoj/maigret/pull/346
* Bump lxml from 4.7.1 to 4.8.0 by @dependabot in https://github.com/soxoj/maigret/pull/350
* Pin reportlab version by @cyb3rk0tik in https://github.com/soxoj/maigret/pull/351
* Fix reportlab not only for testing by @cyb3rk0tik in https://github.com/soxoj/maigret/pull/352
* Added some scripts by @soxoj in https://github.com/soxoj/maigret/pull/355
* Added package publishing instruction by @soxoj in https://github.com/soxoj/maigret/pull/356
* Added DB statistics autoupdate and write to sites.md by @soxoj in https://github.com/soxoj/maigret/pull/357
* CI autoupdate by @soxoj in https://github.com/soxoj/maigret/pull/359
* Op.gg fixes by @soxoj in https://github.com/soxoj/maigret/pull/363
* Wikipedia fix by @soxoj in https://github.com/soxoj/maigret/pull/365
* Disabled Netvibes and LeetCode by @soxoj in https://github.com/soxoj/maigret/pull/366
* Fixed several false positives, improved statistics info by @soxoj in https://github.com/soxoj/maigret/pull/368
* Fix false positives by @soxoj in https://github.com/soxoj/maigret/pull/370
* Fixed the rest of false positives for now by @soxoj in https://github.com/soxoj/maigret/pull/371
* Fix false positive and CI by @soxoj in https://github.com/soxoj/maigret/pull/372
* Added new sites to data.json by @kustermariocoding in https://github.com/soxoj/maigret/pull/375
* Fixed issue with str alexaRank by @soxoj in https://github.com/soxoj/maigret/pull/382
* Bump tqdm from 4.62.3 to 4.63.0 by @dependabot in https://github.com/soxoj/maigret/pull/374
* Bump pytest-asyncio from 0.18.1 to 0.18.2 by @dependabot in https://github.com/soxoj/maigret/pull/380
* @imgbot made their first contribution in https://github.com/soxoj/maigret/pull/319
* @kustermariocoding made their first contribution in https://github.com/soxoj/maigret/pull/375
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.4.1...v0.4.2
## [0.4.1] - 2022-01-15
* Added dozen of sites, improved submit mode by @soxoj in https://github.com/soxoj/maigret/pull/288
* Bump requests from 2.26.0 to 2.27.0 by @dependabot in https://github.com/soxoj/maigret/pull/292
* changed Bayoushooter to use XenForo and foursquare to use correct checkType by @antomarsi in https://github.com/soxoj/maigret/pull/289
* Bump requests from 2.27.0 to 2.27.1 by @dependabot in https://github.com/soxoj/maigret/pull/293
* Added aparat.com by @soxoj in https://github.com/soxoj/maigret/pull/294
* Fixed BongaCams, links parsing improved by @soxoj in https://github.com/soxoj/maigret/pull/297
* Temporary fix for Twitter (#299) by @soxoj in https://github.com/soxoj/maigret/pull/300
* Fixed TikTok checks (#303) by @soxoj in https://github.com/soxoj/maigret/pull/306
* Bump pycountry from 20.7.3 to 22.1.10 by @dependabot in https://github.com/soxoj/maigret/pull/313
* Pornhub search improved by @soxoj in https://github.com/soxoj/maigret/pull/315
* Codacademy fixed by @soxoj in https://github.com/soxoj/maigret/pull/316
* Bump pytest-asyncio from 0.16.0 to 0.17.0 by @dependabot in https://github.com/soxoj/maigret/pull/314
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.4.0...v0.4.1
## [0.4.0] - 2022-01-03
* Delayed import of requests module, speed check command, reqs updated by @soxoj in https://github.com/soxoj/maigret/pull/189
* Snapcraft yaml added by @soxoj in https://github.com/soxoj/maigret/pull/190
* Create codeql-analysis.yml by @soxoj in https://github.com/soxoj/maigret/pull/191
* Move wiki pages to ReadTheDocs by @egornagornov in https://github.com/soxoj/maigret/pull/194
* Created ReadTheDocs requirements file by @soxoj in https://github.com/soxoj/maigret/pull/195
* Fix incompatible version requirements by @JasperJuergensen in https://github.com/soxoj/maigret/pull/196
* Added link to documentation by @soxoj in https://github.com/soxoj/maigret/pull/198
* Upgraded base docker image by @soxoj in https://github.com/soxoj/maigret/pull/199
* Run CodeQL only aflter merge and each Saturday by @soxoj in https://github.com/soxoj/maigret/pull/201
* Added cascade settings loading from /.maigret/settings.json and ./settings.json by @soxoj in https://github.com/soxoj/maigret/pull/200
* Documentation and settings improved by @soxoj in https://github.com/soxoj/maigret/pull/203
* New config options added by @soxoj in https://github.com/soxoj/maigret/pull/204
* Added export of cli entrypoint by @soxoj in https://github.com/soxoj/maigret/pull/207
* Removed redundant logging by @soxoj in https://github.com/soxoj/maigret/pull/210
* PyInstaller workflow by @soxoj in https://github.com/soxoj/maigret/pull/206
* Create bug.md by @soxoj in https://github.com/soxoj/maigret/pull/213
* Fixed path and names of report files by @soxoj in https://github.com/soxoj/maigret/pull/216
* Box drawing logic improved, added new settings by @soxoj in https://github.com/soxoj/maigret/pull/217
* Fixes for win32 release by @soxoj in https://github.com/soxoj/maigret/pull/218
* Bump six from 1.15.0 to 1.16.0 by @dependabot in https://github.com/soxoj/maigret/pull/221
* Bump flake8 from 3.8.4 to 4.0.1 by @dependabot in https://github.com/soxoj/maigret/pull/219
* Bump aiohttp from 3.7.4 to 3.8.0 by @dependabot in https://github.com/soxoj/maigret/pull/220
* Bump aiohttp-socks from 0.5.5 to 0.6.0 by @dependabot in https://github.com/soxoj/maigret/pull/222
* Bump typing-extensions from 3.7.4.3 to 3.10.0.2 by @dependabot in https://github.com/soxoj/maigret/pull/224
* Bump multidict from 5.1.0 to 5.2.0 by @dependabot in https://github.com/soxoj/maigret/pull/225
* Bump idna from 2.10 to 3.3 by @dependabot in https://github.com/soxoj/maigret/pull/228
* Bump pytest-cov from 2.10.1 to 3.0.0 by @dependabot in https://github.com/soxoj/maigret/pull/227
* Bump mock from 4.0.2 to 4.0.3 by @dependabot in https://github.com/soxoj/maigret/pull/226
* Bump certifi from 2020.12.5 to 2021.10.8 by @dependabot in https://github.com/soxoj/maigret/pull/233
* Bump pytest-httpserver from 1.0.0 to 1.0.2 by @dependabot in https://github.com/soxoj/maigret/pull/232
* Bump lxml from 4.6.3 to 4.6.4 by @dependabot in https://github.com/soxoj/maigret/pull/231
* Bump pefile from 2019.4.18 to 2021.9.3 by @dependabot in https://github.com/soxoj/maigret/pull/229
* Bump pytest-rerunfailures from 9.1.1 to 10.2 by @dependabot in https://github.com/soxoj/maigret/pull/230
* Bump yarl from 1.6.3 to 1.7.2 by @dependabot in https://github.com/soxoj/maigret/pull/237
* Bump async-timeout from 4.0.0 to 4.0.1 by @dependabot in https://github.com/soxoj/maigret/pull/236
* Bump psutil from 5.7.0 to 5.8.0 by @dependabot in https://github.com/soxoj/maigret/pull/234
* Bump jinja2 from 3.0.2 to 3.0.3 by @dependabot in https://github.com/soxoj/maigret/pull/235
* Bump pytest from 6.2.4 to 6.2.5 by @dependabot in https://github.com/soxoj/maigret/pull/238
* Bump tqdm from 4.55.0 to 4.62.3 by @dependabot in https://github.com/soxoj/maigret/pull/242
* Bump arabic-reshaper from 2.1.1 to 2.1.3 by @dependabot in https://github.com/soxoj/maigret/pull/243
* Bump pytest-asyncio from 0.14.0 to 0.16.0 by @dependabot in https://github.com/soxoj/maigret/pull/240
* Bump chardet from 3.0.4 to 4.0.0 by @dependabot in https://github.com/soxoj/maigret/pull/241
* Bump soupsieve from 2.1 to 2.3.1 by @dependabot in https://github.com/soxoj/maigret/pull/239
* Bump aiohttp from 3.8.0 to 3.8.1 by @dependabot in https://github.com/soxoj/maigret/pull/246
* Bump typing-extensions from 3.10.0.2 to 4.0.0 by @dependabot in https://github.com/soxoj/maigret/pull/245
* Bump aiohttp-socks from 0.6.0 to 0.6.1 by @dependabot in https://github.com/soxoj/maigret/pull/249
* Bump aiohttp-socks from 0.6.1 to 0.7.1 by @dependabot in https://github.com/soxoj/maigret/pull/250
* Bump typing-extensions from 4.0.0 to 4.0.1 by @dependabot in https://github.com/soxoj/maigret/pull/253
* Fixed some false positives by @soxoj in https://github.com/soxoj/maigret/pull/254
* Disabled non-working sites by @soxoj in https://github.com/soxoj/maigret/pull/255
* Added false results buttons to reports, fixed some falses by @soxoj in https://github.com/soxoj/maigret/pull/256
* Fixed xHamster, added support of proxies to self-check mode by @soxoj in https://github.com/soxoj/maigret/pull/259
* Disabled non-working sites, updated public sites list by @soxoj in https://github.com/soxoj/maigret/pull/263
* Bump lxml from 4.6.4 to 4.6.5 by @dependabot in https://github.com/soxoj/maigret/pull/266
* Bump lxml from 4.6.5 to 4.7.1 by @dependabot in https://github.com/soxoj/maigret/pull/269
* Bump pytest-httpserver from 1.0.2 to 1.0.3 by @dependabot in https://github.com/soxoj/maigret/pull/270
* Fixed failed tests (thx to Meta aka Facebook) by @soxoj in https://github.com/soxoj/maigret/pull/273
* Fixed votetags, updated issue template by @soxoj in https://github.com/soxoj/maigret/pull/278
* Bump async-timeout from 4.0.1 to 4.0.2 by @dependabot in https://github.com/soxoj/maigret/pull/275
* Fixed some false positives by @soxoj in https://github.com/soxoj/maigret/pull/280
* Bump attrs from 21.2.0 to 21.3.0 by @dependabot in https://github.com/soxoj/maigret/pull/281
* Bump psutil from 5.8.0 to 5.9.0 by @dependabot in https://github.com/soxoj/maigret/pull/282
* Bump attrs from 21.3.0 to 21.4.0 by @dependabot in https://github.com/soxoj/maigret/pull/283
**Full Changelog**: https://github.com/soxoj/maigret/compare/v0.3.1...v0.4.0
## [0.3.1] - 2021-10-31
* fixed false positives
* accelerated maigret start time by 3 times
## [0.3.0] - 2021-06-02
* added support of Tor and I2P sites
* added experimental DNS checking feature
* implemented sorting by data points for reports
* reports fixes
## [0.2.4] - 2021-05-18
* cli output report
* various improvements
## [0.2.3] - 2021-05-12
* added Yelp and yelp_userid support
* tags markup stabilization
* improved errors detection
## [0.2.2] - 2021-05-07
* improved ids extractors
* updated sites and engines
* updates CLI options
## [0.2.1] - 2021-05-02
* fixed json reports generation bug, added tests
## [0.2.0] - 2021-05-02
* added `--retries` option
* added `source` feature for sites' mirrors
* improved `submit` mode
* lot of style and logic fixes
## [0.1.20] - 2021-05-02 [YANKED]
## [0.1.19] - 2021-04-14
* added `--no-progressbar` option
* fixed ascii tree bug
* fixed `python -m maigret` run
* fixed requests freeze with timeout async tasks
## [0.1.18] - 2021-03-30
* some API improvements
## [0.1.17] - 2021-03-30
* simplified maigret search API
* improved documentation
* fixed 403 response code ignoring bug
## [0.1.16] - 2021-03-21
* improved URL parsing mode
* improved sites submit mode
* added uID.me uguid support
* improved requests processing
## [0.1.15] - 2021-03-14
* improved HTML reports
* fixed python-3.6-specific error
* false positives fixes
## [0.1.14] - 2021-02-25
* added JSON export formats
* improved tags markup
* realized username detection in userinfo links
* added DB stats CLI option
* added site submit logic and CLI option
* added Spotify parsing activation
* main logic refactoring
* fixed Dockerfile
* fixed requirements
## [0.1.13] - 2021-02-06
* improved sites list filtering
* pretty console messages
* Yandex services updates
* false positives fixes
## [0.1.12] - 2021-01-28
* added support of custom cookies
* fixed lots of false positives
## [0.1.11] - 2021-01-16
* tags and custom data checks bugfixes
* added parsing activation logic
## [0.1.10] - 2021-01-13
* added report static resources into package
## [0.1.9] - 2021-01-11
* added HTML and PDF report export
* fixed support of Python 3.6
* fixed tags filtering and ranking
* more than 2000 sites supported
* refactored sites and engines logic
* added tests
## [0.1.8] - 2020-12-31
* added XMind export
* more than 1500 sites supported
* parallel processing of requests
## [0.1.7] - 2020-12-11
* fixed proxies support
* fixed aiohttp stuff to prevent python 3.7 bugs
* fixed self-checking database saving error
## [0.1.6] - 2020-12-05
* fixed Dockerfile and README
## [0.1.5] - 2020-12-05 [YANKED]
## [0.1.4] - 2020-12-05 [YANKED]
## [0.1.3] - 2020-12-05 [YANKED]
## [0.1.2] - 2020-12-05 [YANKED]
## [0.1.1] - 2020-12-05 [YANKED]
## [0.1.0] - 2020-12-05
* initial release
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://t.me/soxoj.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+196
View File
@@ -0,0 +1,196 @@
# How to contribute
Hey! I'm really glad you're reading this. Maigret contains a lot of sites, and it is very hard to keep all the sites operational. That's why any fix is important.
## Code of Conduct
Please read and follow the [Code of Conduct](CODE_OF_CONDUCT.md) to foster a welcoming and inclusive community.
## Local setup
Install Maigret with development dependencies via [Poetry](https://python-poetry.org/):
```bash
git clone https://github.com/soxoj/maigret && cd maigret
poetry install --with dev
```
Activate the repo's git hooks **once after cloning**:
```bash
git config --local core.hooksPath .githooks/
```
The pre-commit hook does two things every time you commit changes that touch the site database:
- regenerates the database signature `maigret/resources/db_meta.json` (used to detect compatible auto-updates), and
- regenerates `sites.md` (the human-readable list of supported sites with per-engine statistics).
It also auto-stages the regenerated files so they land in the same commit as your edits. **Always run `git commit` from inside the repo so the hook can fire** — without it, your PR will land with a stale signature and a stale `sites.md`, and database auto-update will misbehave for users on your branch.
## How to contribute
There are two main ways to help.
### 1. Add a new site
**Beginner.** Use the `--submit` mode — Maigret takes a single existing-account URL, auto-detects the site engine, picks `presenseStrs` / `absenceStrs`, and offers to add the entry:
```bash
maigret --submit https://example.com/users/alice
```
`--submit` works well when the site has clean status codes and no anti-bot protection. It will *not* discover a public JSON API (`urlProbe`), classify protection (`tls_fingerprint`, `cf_js_challenge`, `ip_reputation`, ...), or recognise SPA / soft-404 pages. For those, fall back to manual editing.
**Advanced.** Edit `maigret/resources/data.json` by hand — see *Editing `data.json` safely* below. There is also an `add-a-site` issue template if you want a maintainer to do it for you.
### 2. Fix existing sites
The most useful work in this project is keeping checks accurate over time. Sites change layout, switch engines, add Cloudflare, redirect to login walls — every fix is welcome.
**Where to start.** Good candidates:
- Issues with the `false-positive` label.
- Sites currently `disabled: true` in `data.json` — many were disabled on a transient symptom and have since healed.
- Sites for which `--self-check --diagnose` reports a problem.
- A focused audit of one engine (vBulletin, XenForo, phpBB, Discourse, Flarum, ...). Engine-wide breakage usually has a single root cause and several sites can be fixed in one PR.
**Diagnose with built-in tools.**
> By default, Maigret skips entries with `disabled: true` in every mode (`--self-check`, `--site`, plain search). Whenever your target is a disabled site — diagnosing it, validating a fix, running the two-filter check below — pass **`--use-disabled-sites`** explicitly. Without the flag, the site is silently dropped from the run and you get an empty result that looks like "everything's fine".
- Per-site diagnosis with recommendations:
```bash
maigret --self-check --site "SiteName" --diagnose
# add --use-disabled-sites if the entry is currently disabled
```
Without `--auto-disable`, this only reports — it never edits the database. Add `--auto-disable` only when you really want to write the result back.
- Single-site comparison of claimed vs unclaimed responses (status, markers, headers):
```bash
python utils/site_check.py --site "SiteName" --diagnose
python utils/site_check.py --site "SiteName" --compare-methods # raw aiohttp vs Maigret's checker
```
- Mass check of top-N sites:
```bash
python utils/check_top_n.py --top 100 --only-broken
```
### Understanding `checkType`
Each site entry uses one of three `checkType` modes to decide whether a profile exists. Picking the right one for your site is the most important data-modeling decision in `data.json`:
- **`message`** (most common, most flexible) — Maigret fetches the page and inspects the HTML body. The profile is reported as found when the body contains at least one substring from `presenseStrs` **and** none of the substrings from `absenceStrs`. Pick narrow, profile-specific markers: a `<title>` fragment unique to profile pages, a CSS class only rendered on profiles (e.g. `"profile-card"`), or a JSON field name from an embedded data blob (`"displayName":`). Avoid generic words (`name`, `email`) and HTML/ARIA boilerplate (`polite`, `alert`, `navigation`, `status`) — they match on every page including error and anti-bot challenge pages, and produce false positives. If the marker contains non-ASCII text, double-check the page is UTF-8 (some legacy sites serve KOI8-R or Windows-1251, in which case byte-level matching silently fails — prefer ASCII markers or a JSON API).
- **`status_code`** — Maigret only looks at the HTTP status code; 2xx means "found", anything else means "not found". Use this only when the site reliably returns proper status codes — typically clean JSON APIs that return HTTP 200 for real users and HTTP 404 for missing ones. Don't use it for sites that return HTTP 200 with a soft "user not found" page (this is the single most common cause of false-positive checks).
- **`response_url`** — Maigret follows the redirect chain and inspects the final URL. Useful when the server reliably redirects missing-user URLs to a different path (e.g. `/login`, `/404`, the homepage) while existing-user URLs stay put. For most sites `message` is a better fit; reach for `response_url` only when a redirect-based signal is genuinely the most stable one.
**`urlProbe` (optional, works with any `checkType`).** If the most reliable signal lives at a different URL than the public profile page — a JSON API, a GraphQL endpoint, a mobile-app route — set `urlProbe` to that URL. Maigret fetches `urlProbe` for the check, but reports continue to show the human-readable `url` so users see a profile link they can click. Examples: GitHub uses `https://github.com/{username}` as `url` and `https://api.github.com/users/{username}` as `urlProbe`; Picsart uses the web profile as `url` and `https://api.picsart.com/users/show/{username}.json` as `urlProbe`. A clean public API is almost always more stable than parsing HTML — it's worth probing for one before settling on `message` against the SPA shell.
**Errors vs absence.** Anything that means "the server can't answer right now" — rate limits, captchas, "Checking your browser", "unusual traffic", maintenance pages — belongs in `errors` (mapping the substring to a human-readable error string), not in `absenceStrs`. The `errors` mechanism produces an UNKNOWN result instead of a false CLAIMED or false AVAILABLE.
Full reference for `checkType`, `urlProbe`, `engine`, and the rest of the `data.json` schema is in the [development guide](docs/source/development.rst), section *How to fix false-positives*.
### Editing `data.json` safely
`data.json` is a single ~36 000-line JSON file. **Make surgical, line-level edits only.** Never rewrite it by reading it into a Python dict and dumping it back — `json.load` + `json.dump` reformats every entry and produces an unreviewable 70 000-line diff. The same rule applies to any helper script that touches the file: it must preserve the original formatting of untouched entries.
If your editor reformats JSON on save, disable that for `data.json` before editing.
### Two-filter validation when re-enabling a site
Removing `disabled: true` requires **two** independent checks. `--self-check` alone is not sufficient — it only verifies the two specific usernames recorded in the entry, so a site that returns CLAIMED for *any* arbitrary username will still pass the self-check.
```bash
# Filter 1: self-check on the recorded claimed/unclaimed pair
maigret --self-check --site "SiteName" --use-disabled-sites
# Filter 2: live probe with a clearly fake username — nothing should match
maigret noonewouldeverusethis7 --site "SiteName" --use-disabled-sites --print-not-found
```
Both filters need `--use-disabled-sites`, since a candidate for re-enable still has `disabled: true` in the working tree until your edit lands. If you forget the flag, both commands silently no-op.
If the second command reports `[+]` for the fake username, the check is a false positive — do not enable. This step takes seconds and is non-negotiable for any re-enable PR.
## Site naming, tags, and protection
- **Site naming conventions** (Title Case by default, brand-specific exceptions, no `www.` prefix, etc.) are documented in the [development guide](docs/source/development.rst), section *Site naming conventions*.
- **Country tags** (`us`, `ru`, `kr`, ...) attribute an account to a country of origin or residence — they're not a traffic-share label. Global services (GitHub, YouTube, Reddit) get **no** country tag; regional services (VK → `ru`, Naver → `kr`) **must** have one. Don't assign a country tag from Alexa/SimilarWeb audience stats.
- **Category tags** must come from the canonical `"tags"` array at the bottom of `data.json`. The `test_tags_validity` test fails if you introduce an unregistered tag. If no existing tag fits well, either pick the closest reasonable match or add the new tag to the canonical list as an explicit, separate change. Don't use platform names (`writefreely`, `pixelfed`) — use category names (`blog`, `photo`).
- **Protection tags** (`tls_fingerprint`, `ip_reputation`, `cf_js_challenge`, `cf_firewall`, `aws_waf_js_challenge`, `ddos_guard_challenge`, `js_challenge`, `custom_bot_protection`) describe the kind of anti-bot protection a site uses. One of them — **`tls_fingerprint`** — is load-bearing: when a site fingerprints the TLS handshake (JA3/JA4) and blocks non-browser clients, tagging it with `tls_fingerprint` makes Maigret automatically swap its HTTP client to [`curl_cffi`](https://github.com/lexiforest/curl_cffi) with Chrome browser emulation, which is usually enough to pass. The site stays `enabled` — no `disabled: true` is needed. Examples: Instagram, NPM, Codepen, Kickstarter, Letterboxd. The remaining tags are documentation-only and pair with `disabled: true` until a per-provider solver is integrated. The full taxonomy and the rules for picking the right tag are in the [development guide](docs/source/development.rst), section *protection (site protection tracking)*. Don't add a protection tag without empirical evidence it applies in the current environment.
## Editing documentation
The docs under `docs/source/` use Sphinx (reStructuredText) with a Simplified Chinese translation in `docs/source/locale/zh_CN/`. **If you edit any `.rst` file, refresh the translation catalogs so the Chinese build does not silently fall back to English on the changed strings:**
```bash
cd docs
make intl-update LANG=zh_CN
```
This regenerates the `.po` files — new strings appear with empty `msgstr ""` and changed ones get a `#, fuzzy` marker. Translating them is optional for a PR (a maintainer or translator can fill them in later), but committing the regenerated `.po` files is **not** optional — otherwise the next person who runs `intl-update` gets a noisy diff for changes that aren't theirs. Full workflow, CJK-specific gotchas, and how to add a new language: [development guide](docs/source/development.rst), section *Translations*.
## Testing
CI runs the same checks on every PR, but please run them locally first:
```bash
make format # auto-format with black
make lint # flake / mypy
make test # pytest with coverage
```
## Submitting changes
Open a [GitHub PR](https://github.com/soxoj/maigret/pulls) against `main`. Always write a clear log message:
```
$ git commit -m "A brief summary of the commit
>
> A paragraph describing what changed and its impact."
```
One-line messages are fine for small changes; bigger changes should explain the *why* in the body.
## Coding conventions
### General
- Follow [PEP 8](https://www.python.org/dev/peps/pep-0008/) for Python.
- Make sure all tests pass before opening the PR.
### Code style
- **Indentation**: 4 spaces per level.
- **Imports**: standard library first, third-party next, project-local last; group them logically.
### Naming
- **Variables and functions**: `snake_case`.
- **Classes**: `CamelCase`.
- **Constants**: `UPPER_CASE`.
Start reading the code and you'll get the hang of it.
## Getting help
If you're stuck on something — a check that won't behave, a setup error, an unclear field in `data.json`, or just want to discuss an approach before opening a PR — there are two places to ask:
- [GitHub Discussions](https://github.com/soxoj/maigret/discussions) — searchable, public, good for technical questions and design ideas. Prefer this for anything other contributors might run into too.
- Telegram: [@soxoj](https://t.me/soxoj) — direct channel to the maintainer, good for quick questions and informal chat.
Bug reports and feature requests still belong in [GitHub Issues](https://github.com/soxoj/maigret/issues).
## License
Maigret is MIT-licensed; by submitting a contribution you agree to publish it under the same license. There is no CLA.
+28
View File
@@ -0,0 +1,28 @@
FROM python:3.11-slim AS base
LABEL maintainer="Soxoj <soxoj@protonmail.com>"
WORKDIR /app
RUN pip install --no-cache-dir --upgrade pip
RUN apt-get update && \
apt-get install --no-install-recommends -y \
build-essential \
python3-dev \
pkg-config \
libcairo2-dev \
libxml2-dev \
libxslt1-dev \
&& rm -rf /var/lib/apt/lists/* /tmp/*
COPY . .
RUN YARL_NO_EXTENSIONS=1 python3 -m pip install --no-cache-dir .
# For production use, set FLASK_HOST to a specific IP address for security
ENV FLASK_HOST=0.0.0.0
# Web UI variant: auto-launches the web interface on $PORT
FROM base AS web
RUN pip install --no-cache-dir '.[pdf]'
ENV PORT=5000
EXPOSE 5000
ENTRYPOINT ["sh", "-c", "exec maigret --web \"$PORT\""]
# Default variant (last stage = `docker build .` target): CLI, backwards-compatible
FROM base AS cli
ENTRYPOINT ["maigret"]
+118
View File
@@ -0,0 +1,118 @@
@echo off
goto check_Permissions
:check_Permissions
net session >nul 2>&1
if %errorLevel% == 0 (
echo Success: Elevated permissions granted.
) else (
echo Failure: Requires elevated permissions.
pause >nul
)
cls
echo --------------------------------------------------------
echo Python 3.8 or higher and pip3 required.
echo --------------------------------------------------------
echo Press [I] to begin installation.
echo Press [R] If already installed.
echo --------------------------------------------------------
choice /c IR
if %errorlevel%==1 goto check_python
if %errorlevel%==2 goto after
:check_python
cls
for /f "tokens=2 delims= " %%i in ('python --version 2^>nul') do (
for /f "tokens=1,2 delims=." %%j in ("%%i") do (
if %%j GEQ 3 (
if %%k GEQ 8 (
goto check_pip
)
)
)
)
echo Python 3.8 or higher is required. Please install it first.
pause
exit /b
:check_pip
pip --version 2>nul | findstr /r /c:"pip" >nul
if %errorlevel% neq 0 (
echo pip is required. Please install it first.
pause
exit /b
)
goto install1
:install1
cls
echo ========================================================
echo Maigret Installation
echo ========================================================
echo.
echo --------------------------------------------------------
echo If your pip installation is outdated, it could cause
echo cryptography to fail on installation.
echo --------------------------------------------------------
echo Check for and install pip 23.3.2 now?
echo --------------------------------------------------------
choice /c YN
if %errorlevel%==1 goto install2
if %errorlevel%==2 goto install3
:install2
cls
python -m pip install --upgrade pip==23.3.2
if %errorlevel% neq 0 (
echo Failed to update pip to version 23.3.2. Please check your installation.
pause
exit /b
)
goto install3
:install3
cls
echo ========================================================
echo Maigret Installation
echo ========================================================
echo.
echo --------------------------------------------------------
echo Installing Maigret...
python -m pip install maigret
if %errorlevel% neq 0 (
echo Failed to install Maigret. Please check your installation.
pause
exit /b
)
echo.
echo +------------------------------------------------------+
echo Maigret installed successfully.
echo +------------------------------------------------------+
pause
goto after
:after
cls
echo ========================================================
echo Maigret Usage
echo ========================================================
echo.
echo +--------------------------------------------------------+
echo To use Maigret, you can run the following command:
echo.
echo maigret [options] [username]
echo.
echo For example, to search for a username:
echo.
echo maigret example_username
echo.
echo For more options and usage details, refer to the Maigret documentation.
echo.
echo https://github.com/soxoj/maigret/blob/5b3b81b4822f6deb2e9c31eb95039907f25beb5e/README.md
echo +--------------------------------------------------------+
echo.
cmd
pause
exit /b
exit /b
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2026 Soxoj
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+41
View File
@@ -0,0 +1,41 @@
LINT_FILES=maigret wizard.py tests
test:
coverage run --source=./maigret,./maigret/web -m pytest tests
coverage report -m
coverage html
rerun-tests:
pytest --lf -vv
lint:
@echo 'syntax errors or undefined names'
flake8 --count --select=E9,F63,F7,F82 --show-source --statistics ${LINT_FILES}
@echo 'warning'
flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore=E731,W503,E501 ${LINT_FILES}
@echo 'mypy'
mypy --check-untyped-defs ${LINT_FILES}
speed:
time python3 -m maigret --version
python3 -c "import timeit; t = timeit.Timer('import maigret'); print(t.timeit(number = 1000000))"
python3 -X importtime -c "import maigret" 2> maigret-import.log
python3 -m tuna maigret-import.log
format:
@echo 'black'
black --skip-string-normalization ${LINT_FILES}
pull:
git stash
git checkout main
git pull origin main
git stash pop
clean:
rm -rf reports htmcov dist
install:
pip3 install .
+389
View File
@@ -0,0 +1,389 @@
# Maigret
<div align="center">
<div>
<a href="https://pypi.org/project/maigret/">
<img alt="PyPI version badge for Maigret" src="https://img.shields.io/pypi/v/maigret?style=flat-square" />
</a>
<a href="https://pepy.tech/project/maigret">
<img alt="Total downloads" src="https://static.pepy.tech/badge/maigret" />
<img alt="Downloads/month" src="https://static.pepy.tech/badge/maigret/month" />
</a>
</div>
<div>
<a href="https://github.com/soxoj/maigret">
<img alt="View count for Maigret project" src="https://komarev.com/ghpvc/?username=maigret&color=brightgreen&label=views&style=flat-square" />
</a>
<a href="https://github.com/soxoj/maigret">
<img alt="Minimum Python version required: 3.10+" src="https://img.shields.io/badge/Python-3.10%2B-brightgreen?style=flat-square" />
</a>
<a href="https://github.com/soxoj/maigret/blob/main/LICENSE">
<img alt="License badge for Maigret" src="https://img.shields.io/github/license/soxoj/maigret?style=flat-square" />
</a>
</div>
<br>
<div>
<img src="https://raw.githubusercontent.com/soxoj/maigret/main/static/maigret.png" height="300" alt="Maigret logo"/>
</div>
<br>
<div>
<a href="https://codewiki.google/github.com/soxoj/maigret">
<img alt="Ask Code Wiki about Maigret" src="https://img.shields.io/badge/Code_Wiki-ask_about_repo-yellow?logo=googlegemini" />
</a>
<a href="https://deepwiki.com/soxoj/maigret">
<img alt="Ask DeepWiki about Maigret" src="https://img.shields.io/badge/DeepWiki-ask_about_repo-yellow" />
</a>
</div>
<br>
<div>
<b>English</b> · <a href="README.zh-CN.md">简体中文</a>
</div>
<br>
</div>
**Maigret** collects a dossier on a person **by username only**, checking for accounts on a huge number of sites and gathering all the available information from web pages. No API keys required. **[AI profiling (demo)](#ai-analysis)**.
## Sponsors
<p align="center">
<a href="https://www.711proxy.com/?utm_t=1&utm_i=538">
<img src="https://i.imgur.com/s1JHMun.gif" width="250" alt="711Proxy">
</a>
</p>
<p>
<a href="https://www.711proxy.com/?utm_t=1&utm_i=538"><b>711Proxy</b></a> provides reliable residential proxies for web scraping, username lookups, and public data collection. Over <b>100M</b> residential IPs across <b>200+</b> countries • High Success Rates • Fast & Reliable Connections. <br>
<b>Special Offer</b>: Free trial available! Rotating residential proxies from just <b>$0.55/GB</b>. Unlimited residential proxies from <b>$15/hour</b> with no concurrency limits.
</p>
<br>
<p align="center">
<a href="https://9proxy.com/?utm_source=Github&utm_campaign=obscura">
<img src="https://i.imgur.com/FleHdvu.gif" width="250" alt="9Proxy">
</a>
</p>
<p>
<a href="https://9proxy.com/?utm_source=Github&utm_campaign=obscura"><b>9Proxy</b></a> provides residential proxies from just <b>$0.018/IP or $0.68/GB</b>. 20M+ IPs across 90+ countries. Sticky or rotating sessions, managed from desktop or mobile app.
</p>
<br>
<p align="center">
<a href="https://www.rapidproxy.io/?ref=soxoj">
<img src="https://github.com/user-attachments/assets/4ed589d1-37cb-4a40-9273-bff4d6f1a514" width="500" alt="RapidProxy">
</a>
</p>
<p>
<a href="https://www.rapidproxy.io/?ref=soxoj"><b>RapidProxy</b></a> provides high-performance residential proxies for Twitter scraping, Selenium automation, and web data extraction. 90M+ IPs • Smart rotation • Anti-block • Non-expiring traffic. <br>
<b>Special Offer</b>: Try it free — Plans from $0.65/GB. Use code <b>RAPID10</b> for 10% off.
</p>
## Contents
- [In one minute](#in-one-minute)
- [Main features](#main-features)
- [Demo](#demo)
- [Installation](#installation)
- [Usage](#usage)
- [Contributing](#contributing)
- [Commercial Use](#commercial-use)
- [About](#about)
<a id="one-minute"></a>
## In one minute
Ensure you have Python 3.10 or higher.
```bash
pip install maigret
maigret YOUR_USERNAME
```
No install? Try the [community Telegram bot](https://sites.google.com/view/maigret-bot-link) or a [Cloud Shell](#cloud-shells).
Want a web UI? See [how to launch it](#web-interface).
See also: [Quick start](https://maigret.readthedocs.io/en/latest/quick-start.html).
## Main features
- Supports 3,000+ sites ([see full list](https://github.com/soxoj/maigret/blob/main/sites.md)). A default run checks the 500 highest-ranked sites by traffic; pass `-a` to scan everything, or `--tags` to narrow by category/country.
- Embeddable in Python projects — import `maigret` and run searches programmatically (see [library usage](https://maigret.readthedocs.io/en/latest/library-usage.html)).
- [Extracts](https://github.com/soxoj/socid_extractor) all available information about the account owner from profile pages and site APIs, including links to other accounts.
- Performs recursive search using discovered usernames and other IDs.
- Allows filtering by tags (site categories, countries).
- Detects and partially bypasses blocks, censorship, and CAPTCHA.
- Fetches an [auto-updated site database](https://maigret.readthedocs.io/en/latest/settings.html#database-auto-update) from GitHub each run (once per 24 hours), and falls back to the built-in database if offline.
- Works with Tor and I2P websites; able to check domains.
- Ships with a [web interface](#web-interface) for browsing results as a graph and downloading reports in every format from a single page.
- Optional [AI analysis mode](#ai-analysis) (`--ai`) that turns raw findings into a short investigation summary using an OpenAI-compatible API.
For the complete feature list, see the [features documentation](https://maigret.readthedocs.io/en/latest/features.html).
### Used by
Professional OSINT and social-media analysis tools built on Maigret:
<a href="https://github.com/SocialLinks-IO/sociallinks-api"><img height="60" alt="Social Links API" src="https://github.com/user-attachments/assets/789747b2-d7a0-4d4e-8868-ffc4427df660"></a>
<a href="https://sociallinks.io/products/sl-crimewall"><img height="60" alt="Social Links Crimewall" src="https://github.com/user-attachments/assets/0b18f06c-2f38-477b-b946-1be1a632a9d1"></a>
<a href="https://usersearch.ai/"><img height="60" alt="UserSearch" src="https://github.com/user-attachments/assets/66daa213-cf7d-40cf-9267-42f97cf77580"></a>
## Demo
### Video
<a href="https://asciinema.org/a/Ao0y7N0TTxpS0pisoprQJdylZ">
<img src="https://asciinema.org/a/Ao0y7N0TTxpS0pisoprQJdylZ.svg" alt="asciicast" width="600">
</a>
### Reports
[PDF report](https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotographycars.pdf), [HTML report](https://htmlpreview.github.io/?https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotographycars.html)
![HTML report screenshot](https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotography_html_screenshot.png)
![XMind 8 report screenshot](https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotography_xmind_screenshot.png)
[Full console output](https://raw.githubusercontent.com/soxoj/maigret/main/static/recursive_search.md)
## Installation
Already ran the [In one minute](#one-minute) steps? You're set. Below are alternative methods.
Don't want to install anything? Use the [community Telegram bot](https://sites.google.com/view/maigret-bot-link).
### Windows
Download `maigret_standalone.exe` from [Releases](https://github.com/soxoj/maigret/releases). You can launch it two ways:
- **Double-click it** — Maigret will ask for a username, run a default search, and wait at the end so the report links stay visible.
- **Run it from a terminal** — open Command Prompt (press `Win+R`, type `cmd`, hit Enter) or PowerShell to pass extra options:
```cmd
cd %USERPROFILE%\Downloads
maigret_standalone.exe USERNAME
maigret_standalone.exe USERNAME --html :: also save an HTML report
maigret_standalone.exe --help :: list all options
```
Video guide: https://youtu.be/qIgwTZOmMmM.
<a id="cloud-shells"></a>
### Cloud Shells
Run Maigret in the browser via cloud shells or Jupyter notebooks:
<a href="https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/soxoj/maigret&tutorial=cloudshell-tutorial.md"><img src="https://user-images.githubusercontent.com/27065646/92304704-8d146d80-ef80-11ea-8c29-0deaabb1c702.png" alt="Open in Cloud Shell" height="50"></a>
<a href="https://repl.it/github/soxoj/maigret"><img src="https://replit.com/badge/github/soxoj/maigret" alt="Run on Replit" height="50"></a>
<a href="https://colab.research.google.com/gist/soxoj/879b51bc3b2f8b695abb054090645000/maigret-collab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" height="45"></a>
<a href="https://mybinder.org/v2/gist/soxoj/9d65c2f4d3bec5dd25949197ea73cf3a/HEAD"><img src="https://mybinder.org/badge_logo.svg" alt="Open In Binder" height="45"></a>
### Local installation (pip)
```bash
# install from pypi
pip3 install maigret
# usage
maigret username
```
### From source
```bash
# or clone and install manually
git clone https://github.com/soxoj/maigret && cd maigret
# build and install
pip3 install .
# usage
maigret username
```
### Docker
Two image variants are published:
- `soxoj/maigret:latest` — CLI mode (default)
- `soxoj/maigret:web` — auto-launches the [web interface](#web-interface)
```bash
# official image (CLI)
docker pull soxoj/maigret
# CLI usage
docker run -v /mydir:/app/reports soxoj/maigret:latest username --html
# Web UI (open http://localhost:5000)
docker run -p 5000:5000 soxoj/maigret:web
# Web UI on a custom port
docker run -e PORT=8080 -p 8080:8080 soxoj/maigret:web
# manual build
docker build -t maigret . # CLI image (default target)
docker build --target web -t maigret-web . # Web UI image
```
### Troubleshooting
Build errors? See the [troubleshooting guide](https://maigret.readthedocs.io/en/latest/installation.html#troubleshooting).
PDF reports (`--pdf`) are an optional extra — install with `pip install 'maigret[pdf]'`. They need system-level graphics libraries on Linux/macOS; see the [PDF reports section](https://maigret.readthedocs.io/en/latest/installation.html#optional-pdf-reports-maigret-pdf) for per-OS install steps.
## Usage
### Examples
```bash
# make HTML, PDF, and Xmind8 reports
maigret user --html
maigret user --pdf
maigret user --xmind #Output not compatible with xmind 2022+
# machine-readable exports
maigret user --json ndjson # newline-delimited JSON (also: --json simple)
maigret user --csv
maigret user --txt
maigret user --graph # interactive D3 graph (HTML)
maigret user --neo4j # Neo4j Cypher script (graph database)
# search on sites marked with tags photo & dating
maigret user --tags photo,dating
# search on sites marked with tag us
maigret user --tags us
# highlight sites whose page also mentions specific keywords
maigret user --keywords python rust
# keyword-matched sites are shown with "[++]" in bright green
# search for three usernames on all available sites
maigret user1 user2 user3 -a
# AI-assisted investigation summary (needs OPENAI_API_KEY)
maigret user --ai
```
`--neo4j` writes a `*_neo4j.cypher` script of the results graph; import it with `cypher-shell -u neo4j -p <password> < report_user_neo4j.cypher` or paste it into the Neo4j Browser. Re-imports are idempotent. See the [Neo4j export docs](https://maigret.readthedocs.io/en/latest/command-line-options.html#neo4j-export).
Run `maigret --help` for all options. Docs: [CLI options](https://maigret.readthedocs.io/en/latest/command-line-options.html), [more examples](https://maigret.readthedocs.io/en/latest/usage-examples.html). Running into 403s or timeouts? See [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
<a id="web-interface"></a>
### Web interface
Maigret has a built-in web UI with a results graph and downloadable reports.
<details>
<summary>Web Interface Screenshots</summary>
![Web interface: how to start](https://raw.githubusercontent.com/soxoj/maigret/main/static/web_interface_screenshot_start.png)
![Web interface: results](https://raw.githubusercontent.com/soxoj/maigret/main/static/web_interface_screenshot.png)
</details>
```console
maigret --web 5000
```
Open http://127.0.0.1:5000, enter a username, and view results.
### Python library
**Maigret can be embedded in your own Python projects.** The CLI is a thin wrapper around an async function you can call directly — build custom pipelines, feed results into your own tooling, or run it inside a larger OSINT workflow.
See the full [library usage guide](https://maigret.readthedocs.io/en/latest/library-usage.html) for a working example, async patterns, and how to filter sites by tag.
### Useful CLI flags
- `--parse URL` — parse a profile page, extract IDs/usernames, and use them to kick off a recursive search.
- `--permute` — generate likely username variants from two or more inputs (e.g. `john doe``johndoe`, `j.doe`, …) and search for all of them.
- `--self-check [--auto-disable]` — verify `usernameClaimed` / `usernameUnclaimed` pairs against live sites for maintainers auditing the database.
- `--ai` / `--ai-model` — run the [AI analysis](#ai-analysis) over the search results and stream a short investigation summary to the terminal.
<a id="ai-analysis"></a>
### AI analysis
[![asciicast](https://asciinema.org/a/979404.svg)](https://asciinema.org/a/979404)
`--ai` collects the search results, builds an internal Markdown report, and sends it to an OpenAI-compatible chat completion endpoint to produce a short, neutral investigation summary (likely real name, location, occupation, interests, languages, confidence, follow-up leads). Per-site progress is suppressed and the model's output is streamed to stdout.
```bash
export OPENAI_API_KEY=sk-...
maigret user --ai
# pick a different model
maigret user --ai --ai-model gpt-4o-mini
```
The key can also be set as `openai_api_key` in `settings.json`. The endpoint defaults to `https://api.openai.com/v1`, but `openai_api_base_url` in `settings.json` can point to any OpenAI-compatible API (Azure OpenAI, OpenRouter, a local server, …). See the [settings docs](https://maigret.readthedocs.io/en/latest/settings.html) for the full list of options.
### Tor / I2P / proxies
Maigret can route checks through a proxy, Tor, or I2P — useful for `.onion` / `.i2p` sites and for bypassing WAFs that block datacenter IPs.
```bash
# any HTTP/SOCKS proxy
maigret user --proxy socks5://127.0.0.1:1080
# Tor (default gateway socks5://127.0.0.1:9050)
maigret user --tor-proxy socks5://127.0.0.1:9050
# I2P (default gateway http://127.0.0.1:4444)
maigret user --i2p-proxy http://127.0.0.1:4444
```
Start your Tor / I2P daemon before running the command — Maigret does not manage these gateways.
### Cloudflare bypass
> **Experimental.** The Cloudflare webgate is under active development; the configuration schema, CLI behaviour, and the set of routed sites may change without backwards-compatibility guarantees.
A subset of sites in the database require a real browser to solve a JavaScript challenge. Maigret can offload these checks to a local [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) instance:
```bash
docker run -d -p 8191:8191 --name flaresolverr ghcr.io/flaresolverr/flaresolverr:latest
maigret --cloudflare-bypass <username>
```
The bypass is opt-in (`--cloudflare-bypass` or `cloudflare_bypass.enabled` in `settings.json`) and only fires for sites whose `protection` field matches. See the [feature docs](https://maigret.readthedocs.io/en/latest/features.html#cloudflare-bypass) for backend options and configuration.
## Contributing
Add or fix new sites surgically in `data.json` (no `json.load`/`json.dump`), then run `./utils/update_site_data.py` to regenerate `sites.md` and the database metadata, and open a pull request. For more details, see the [CONTRIBUTING guide](https://github.com/soxoj/maigret/blob/main/CONTRIBUTING.md) and [development docs](https://maigret.readthedocs.io/en/latest/development.html). Release history: [CHANGELOG.md](CHANGELOG.md).
## Commercial Use
The open-source Maigret is MIT-licensed and free for commercial use without restriction — but site checks break over time and need active maintenance.
For serious commercial use — with a **daily-updated site database** or a **username-check API** — reach out: 📧 [maigret@soxoj.com](mailto:maigret@soxoj.com)
- Private site database — 5 000+ sites, updated daily (separate from the public open-source database)
- Username check API — integrate Maigret into your product
## About
### Disclaimer
**For educational and lawful purposes only.** You are responsible for complying with all applicable laws (GDPR, CCPA, etc.) in your jurisdiction. The authors bear no responsibility for misuse.
### Feedback
[Open an issue](https://github.com/soxoj/maigret/issues) · [GitHub Discussions](https://github.com/soxoj/maigret/discussions) · [Telegram](https://t.me/soxoj)
### SOWEL classification
OSINT techniques used:
- [SOTL-2.2. Search For Accounts On Other Platforms](https://sowel.soxoj.com/other-platform-accounts)
- [SOTL-6.1. Check Logins Reuse To Find Another Account](https://sowel.soxoj.com/logins-reuse)
- [SOTL-6.2. Check Nicknames Reuse To Find Another Account](https://sowel.soxoj.com/nicknames-reuse)
### License
MIT © [Maigret](https://github.com/soxoj/maigret)
+7
View File
@@ -0,0 +1,7 @@
# WeHub 来源说明
- 原始项目:`soxoj/maigret`
- 原始仓库:https://github.com/soxoj/maigret
- 导入方式:上游默认分支的最新快照
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
+392
View File
@@ -0,0 +1,392 @@
# Maigret
<div align="center">
<div>
<a href="https://pypi.org/project/maigret/">
<img alt="Maigret 的 PyPI 版本" src="https://img.shields.io/pypi/v/maigret?style=flat-square" />
</a>
<a href="https://pepy.tech/project/maigret">
<img alt="Maigret 总下载量" src="https://static.pepy.tech/badge/maigret" />
<img alt="Maigret 月下载量" src="https://static.pepy.tech/badge/maigret/month" />
</a>
</div>
<div>
<a href="https://github.com/soxoj/maigret">
<img alt="Maigret 项目访问量" src="https://komarev.com/ghpvc/?username=maigret&color=brightgreen&label=views&style=flat-square" />
</a>
<a href="https://github.com/soxoj/maigret">
<img alt="所需最低 Python 版本:3.10+" src="https://img.shields.io/badge/Python-3.10%2B-brightgreen?style=flat-square" />
</a>
<a href="https://github.com/soxoj/maigret/blob/main/LICENSE">
<img alt="Maigret 的开源许可证" src="https://img.shields.io/github/license/soxoj/maigret?style=flat-square" />
</a>
</div>
<br>
<div>
<img src="https://raw.githubusercontent.com/soxoj/maigret/main/static/maigret.png" height="300" alt="Maigret logo"/>
</div>
<br>
<div>
<a href="https://codewiki.google/github.com/soxoj/maigret">
<img alt="向 Code Wiki 询问 Maigret" src="https://img.shields.io/badge/Code_Wiki-ask_about_repo-yellow?logo=googlegemini" />
</a>
<a href="https://deepwiki.com/soxoj/maigret">
<img alt="向 DeepWiki 询问 Maigret" src="https://img.shields.io/badge/DeepWiki-ask_about_repo-yellow" />
</a>
</div>
<br>
<div>
<a href="README.md">English</a> · <b>简体中文</b>
</div>
<br>
</div>
**Maigret** 仅凭一个用户名,就能在大量站点上查找其账号,并从网页中收集所有可获取的公开信息,为目标人物生成一份档案。无需任何 API 密钥。**[AI 画像(演示)](#ai-analysis)**。
## 赞助商
<p align="center">
<a href="https://www.711proxy.com/?utm_t=1&utm_i=538">
<img src="https://i.imgur.com/s1JHMun.gif" width="250" alt="711Proxy">
</a>
</p>
<p>
<a href="https://www.711proxy.com/?utm_t=1&utm_i=538"><b>711Proxy</b></a> 提供可靠的住宅代理,适用于网页抓取、用户名查询及公开数据采集。覆盖 <b>200+</b> 个国家超 <b>1 亿</b>住宅 IP · 高成功率 · 稳定快速。<br>
<b>特别优惠</b>:免费试用!轮换住宅代理低至 <b>$0.55/GB</b>。无并发限制的不限量住宅代理低至 <b>$15/小时</b>。
</p>
<br>
<p align="center">
<a href="https://9proxy.com/?utm_source=Github&utm_campaign=obscura">
<img src="https://i.imgur.com/FleHdvu.gif" width="250" alt="9Proxy">
</a>
</p>
<p>
<a href="https://9proxy.com/?utm_source=Github&utm_campaign=obscura"><b>9Proxy</b></a> 提供住宅代理,低至 <b>$0.018/IP 或 $0.68/GB</b>。覆盖 90+ 国家超 2000 万 IP,支持长效或轮换会话,可通过桌面或移动端应用管理。
</p>
<br>
<p align="center">
<a href="https://www.rapidproxy.io/?ref=soxoj">
<img src="https://github.com/user-attachments/assets/4ed589d1-37cb-4a40-9273-bff4d6f1a514" width="500" alt="RapidProxy">
</a>
</p>
<p>
<a href="https://www.rapidproxy.io/?ref=soxoj"><b>RapidProxy</b></a> 提供高性能住宅代理,适用于 Twitter 抓取、Selenium 自动化和网页数据提取。9000万+ IP · 智能轮换 · 反封锁 · 流量永不过期。<br>
<b>特别优惠</b>:免费试用 — 套餐低至 $0.65/GB。使用优惠码 <b>RAPID10</b> 享九折优惠。
</p>
## 目录
- [一分钟上手](#one-minute)
- [核心特性](#main-features)
- [演示](#demo)
- [安装](#installation)
- [使用](#usage)
- [参与贡献](#contributing)
- [商业使用](#commercial-use)
- [关于](#about)
<a id="one-minute"></a>
## 一分钟上手
请先确认本机的 Python 版本不低于 3.10。
```bash
pip install maigret
maigret YOUR_USERNAME
```
不想本地安装?可以试试[社区 Telegram 机器人](https://sites.google.com/view/maigret-bot-link),或者使用[云端 Shell](#cloud-shells)。
想要一个 Web 界面?参见[启动方式](#web-interface)。
延伸阅读:[快速入门](https://maigret.readthedocs.io/en/latest/quick-start.html)。
<a id="main-features"></a>
## 核心特性
- 支持 3000+ 站点(完整列表见 [sites.md](https://github.com/soxoj/maigret/blob/main/sites.md))。默认仅检查访问量排名前 500 的站点;加上 `-a` 可全量扫描,或使用 `--tags` 按分类/国家筛选。
- 可作为 Python 库嵌入到自己的项目中——直接 `import maigret` 即可在代码里发起搜索(参见[库使用文档](https://maigret.readthedocs.io/en/latest/library-usage.html))。
- 通过 [socid_extractor](https://github.com/soxoj/socid_extractor) 从个人主页和站点 API 中[提取](https://github.com/soxoj/socid_extractor)账号所有者的所有可获取信息,包括指向其他账号的链接。
- 基于已发现的用户名和其他 ID,执行递归搜索。
- 支持按标签(站点分类、国家)进行筛选。
- 能够检测并部分绕过封锁、审查和 CAPTCHA。
- 每次运行时(每 24 小时一次)从 GitHub 拉取一份[自动更新的站点数据库](https://maigret.readthedocs.io/en/latest/settings.html#database-auto-update);离线时会回退到内置数据库。
- 可访问 Tor 与 I2P 站点;支持检查域名。
- 自带一个 [Web 界面](#web-interface),可在同一页面将结果以图谱方式浏览,并下载各种格式的报告。
- 可选的 [AI 分析模式](#ai-analysis)(`--ai`),通过 OpenAI 兼容 API 将原始搜索结果整理成一份简短的调查摘要。
完整特性列表请见[特性文档](https://maigret.readthedocs.io/en/latest/features.html)。
### 谁在使用
基于 Maigret 构建的专业 OSINT 与社交媒体分析工具:
<a href="https://github.com/SocialLinks-IO/sociallinks-api"><img height="60" alt="Social Links API" src="https://github.com/user-attachments/assets/789747b2-d7a0-4d4e-8868-ffc4427df660"></a>
<a href="https://sociallinks.io/products/sl-crimewall"><img height="60" alt="Social Links Crimewall" src="https://github.com/user-attachments/assets/0b18f06c-2f38-477b-b946-1be1a632a9d1"></a>
<a href="https://usersearch.ai/"><img height="60" alt="UserSearch" src="https://github.com/user-attachments/assets/66daa213-cf7d-40cf-9267-42f97cf77580"></a>
<a id="demo"></a>
## 演示
### 视频
<a href="https://asciinema.org/a/Ao0y7N0TTxpS0pisoprQJdylZ">
<img src="https://asciinema.org/a/Ao0y7N0TTxpS0pisoprQJdylZ.svg" alt="asciicast" width="600">
</a>
### 报告示例
[PDF 报告](https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotographycars.pdf)、[HTML 报告](https://htmlpreview.github.io/?https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotographycars.html)
![HTML 报告截图](https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotography_html_screenshot.png)
![XMind 8 报告截图](https://raw.githubusercontent.com/soxoj/maigret/main/static/report_alexaimephotography_xmind_screenshot.png)
[完整的命令行输出示例](https://raw.githubusercontent.com/soxoj/maigret/main/static/recursive_search.md)
<a id="installation"></a>
## 安装
如果你已经按[一分钟上手](#one-minute)的步骤跑通了,就无需再装。下面列出几种可选的安装方式。
什么都不想装?直接用[社区 Telegram 机器人](https://sites.google.com/view/maigret-bot-link)。
### Windows
从 [Releases](https://github.com/soxoj/maigret/releases) 下载 `maigret_standalone.exe`。有两种启动方式:
- **双击运行** —— Maigret 会询问用户名、执行一次默认搜索,并在结束时停留,方便你查看报告链接。
- **从终端运行** —— 打开命令提示符(按 `Win+R`,输入 `cmd`,回车)或 PowerShell,以便传入额外参数:
```cmd
cd %USERPROFILE%\Downloads
maigret_standalone.exe USERNAME
maigret_standalone.exe USERNAME --html :: 同时保存 HTML 报告
maigret_standalone.exe --help :: 列出所有选项
```
视频指引:https://youtu.be/qIgwTZOmMmM。
<a id="cloud-shells"></a>
### 云端 Shell
通过云端 Shell 或 Jupyter Notebook 在浏览器里运行 Maigret:
<a href="https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/soxoj/maigret&tutorial=cloudshell-tutorial.md"><img src="https://user-images.githubusercontent.com/27065646/92304704-8d146d80-ef80-11ea-8c29-0deaabb1c702.png" alt="Open in Cloud Shell" height="50"></a>
<a href="https://repl.it/github/soxoj/maigret"><img src="https://replit.com/badge/github/soxoj/maigret" alt="Run on Replit" height="50"></a>
<a href="https://colab.research.google.com/gist/soxoj/879b51bc3b2f8b695abb054090645000/maigret-collab.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab" height="45"></a>
<a href="https://mybinder.org/v2/gist/soxoj/9d65c2f4d3bec5dd25949197ea73cf3a/HEAD"><img src="https://mybinder.org/badge_logo.svg" alt="Open In Binder" height="45"></a>
### 本地安装(pip)
```bash
# 从 PyPI 安装
pip3 install maigret
# 使用
maigret username
```
### 从源码安装
```bash
# 也可以克隆仓库后手动安装
git clone https://github.com/soxoj/maigret && cd maigret
# 构建并安装
pip3 install .
# 使用
maigret username
```
### Docker
官方提供两个镜像变体:
- `soxoj/maigret:latest` —— CLI 模式(默认)
- `soxoj/maigret:web` —— 自动启动 [Web 界面](#web-interface)
```bash
# 拉取官方镜像(CLI)
docker pull soxoj/maigret
# CLI 用法
docker run -v /mydir:/app/reports soxoj/maigret:latest username --html
# Web UI(在 http://localhost:5000 打开)
docker run -p 5000:5000 soxoj/maigret:web
# 自定义 Web UI 端口
docker run -e PORT=8080 -p 8080:8080 soxoj/maigret:web
# 手动构建
docker build -t maigret . # CLI 镜像(默认 target)
docker build --target web -t maigret-web . # Web UI 镜像
```
### 故障排查
构建报错?请见[故障排查指南](https://maigret.readthedocs.io/en/latest/installation.html#troubleshooting)。
PDF 报告(`--pdf`)是可选扩展 —— 通过 `pip install 'maigret[pdf]'` 安装。在 Linux/macOS 上还需要系统级图形库;各操作系统的安装步骤详见 [PDF 报告章节](https://maigret.readthedocs.io/en/latest/installation.html#optional-pdf-reports-maigret-pdf)。
<a id="usage"></a>
## 使用
### 示例
```bash
# 生成 HTML、PDF、XMind 8 报告
maigret user --html
maigret user --pdf
maigret user --xmind # 与 XMind 2022+ 不兼容
# 机器可读的导出格式
maigret user --json ndjson # 行分隔 JSON(也支持 --json simple)
maigret user --csv
maigret user --txt
maigret user --graph # 交互式 D3 图谱(HTML)
maigret user --neo4j # Neo4j Cypher 脚本(图数据库)
# 仅在带有 photo 与 dating 标签的站点上搜索
maigret user --tags photo,dating
# 仅在带有 us 标签的站点上搜索
maigret user --tags us
# 同时在所有站点上搜索三个用户名
maigret user1 user2 user3 -a
# AI 辅助调查摘要(需要 OPENAI_API_KEY)
maigret user --ai
```
`--neo4j` 会生成结果图谱的 `*_neo4j.cypher` 脚本;可用 `cypher-shell -u neo4j -p <password> < report_user_neo4j.cypher` 导入,或粘贴到 Neo4j Browser 中。重复导入是幂等的。详见 [Neo4j 导出文档](https://maigret.readthedocs.io/en/latest/command-line-options.html#neo4j-export)。
完整选项请运行 `maigret --help`。文档:[命令行选项](https://maigret.readthedocs.io/en/latest/command-line-options.html)、[更多示例](https://maigret.readthedocs.io/en/latest/usage-examples.html)。遇到 403 或超时?参见 [TROUBLESHOOTING.md](TROUBLESHOOTING.md)。
<a id="web-interface"></a>
### Web 界面
Maigret 内置一个 Web UI,提供结果图谱视图和报告下载。
<details>
<summary>Web 界面截图</summary>
![Web 界面:启动页](https://raw.githubusercontent.com/soxoj/maigret/main/static/web_interface_screenshot_start.png)
![Web 界面:结果页](https://raw.githubusercontent.com/soxoj/maigret/main/static/web_interface_screenshot.png)
</details>
```console
maigret --web 5000
```
在浏览器中打开 http://127.0.0.1:5000,输入用户名即可查看结果。
### Python 库
**Maigret 可以嵌入到你自己的 Python 项目里使用。** CLI 只是对一个异步函数的薄包装,你完全可以直接调用它——构建自定义流水线、把结果接入自家工具,或将其嵌入更大的 OSINT 工作流。
完整示例(包含异步用法和按标签筛选站点)请参见[库使用指南](https://maigret.readthedocs.io/en/latest/library-usage.html)。
### 常用 CLI 参数
- `--parse URL` —— 解析一个个人主页,从中提取 ID/用户名,并以此为起点发起递归搜索。
- `--permute` —— 基于两个或更多输入生成可能的用户名变体(例如 `john doe``johndoe``j.doe` …)并对其逐一搜索。
- `--self-check [--auto-disable]` —— 维护者用于核对数据库的工具:针对线上站点验证 `usernameClaimed` / `usernameUnclaimed` 配对是否仍然有效。
- `--ai` / `--ai-model` —— 启用 [AI 分析](#ai-analysis),将搜索结果交给 OpenAI 兼容 API,并把简短的调查摘要流式输出到终端。
<a id="ai-analysis"></a>
### AI 分析
[![asciicast](https://asciinema.org/a/979404.svg)](https://asciinema.org/a/979404)
`--ai` 会先收集搜索结果、在内存中构建 Markdown 报告,再将其发送到一个 OpenAI 兼容的 chat completion 接口,生成一份简短、克制的调查摘要(最可能的真实姓名、所在地、职业、兴趣、语言、置信度以及后续线索)。开启该模式后,逐站点的进度输出会被静默,模型的输出会以流式方式打印到 stdout。
```bash
export OPENAI_API_KEY=sk-...
maigret user --ai
# 切换到其它模型
maigret user --ai --ai-model gpt-4o-mini
```
API key 也可以写入 `settings.json``openai_api_key` 字段。接口地址默认为 `https://api.openai.com/v1`,通过在 `settings.json` 中设置 `openai_api_base_url`,可以指向任何 OpenAI 兼容的服务(Azure OpenAI、OpenRouter、本地推理服务等)。完整选项见[配置文档](https://maigret.readthedocs.io/en/latest/settings.html)。
### Tor / I2P / 代理
Maigret 支持通过代理、Tor 或 I2P 转发请求——这对访问 `.onion` / `.i2p` 站点,以及绕过会拦截数据中心 IP 的 WAF 都很有用。
```bash
# 任意 HTTP/SOCKS 代理
maigret user --proxy socks5://127.0.0.1:1080
# Tor(默认网关 socks5://127.0.0.1:9050)
maigret user --tor-proxy socks5://127.0.0.1:9050
# I2P(默认网关 http://127.0.0.1:4444)
maigret user --i2p-proxy http://127.0.0.1:4444
```
请先启动 Tor / I2P 守护进程再运行上述命令——Maigret 不会替你管理这些网关。
### Cloudflare 绕过
> **实验特性。** Cloudflare webgate 仍在积极开发中;配置项、CLI 行为以及命中该机制的站点集合都可能发生变化,不保证向后兼容。
数据库中有一部分站点需要真实浏览器才能通过 JavaScript 挑战。Maigret 可以将这些检查转交给本地运行的 [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr):
```bash
docker run -d -p 8191:8191 --name flaresolverr ghcr.io/flaresolverr/flaresolverr:latest
maigret --cloudflare-bypass <username>
```
该功能为按需启用(命令行加 `--cloudflare-bypass`,或在 `settings.json` 中设置 `cloudflare_bypass.enabled`),并且只对 `protection` 字段匹配的站点生效。后端选项与配置详见[功能文档](https://maigret.readthedocs.io/en/latest/features.html#cloudflare-bypass)。
<a id="contributing"></a>
## 参与贡献
请精确地在 `data.json` 里新增或修复站点(不要使用 `json.load`/`json.dump` 整体读写),然后运行 `./utils/update_site_data.py` 重新生成 `sites.md` 和数据库元数据,再提交 Pull Request。更多细节见 [CONTRIBUTING 指南](https://github.com/soxoj/maigret/blob/main/CONTRIBUTING.md) 和[开发文档](https://maigret.readthedocs.io/en/latest/development.html)。版本历史见 [CHANGELOG.md](CHANGELOG.md)。
<a id="commercial-use"></a>
## 商业使用
开源版本的 Maigret 采用 MIT 许可证,可不受限制地用于商业用途——但站点检查会随时间失效,需要持续维护。
如果你有更严肃的商业需求——希望使用**每日更新的站点数据库**或**用户名查询 API**——欢迎联系:📧 [maigret@soxoj.com](mailto:maigret@soxoj.com)
- 私有站点数据库 —— 5000+ 站点,每日更新(独立于公开开源数据库)
- 用户名查询 API —— 将 Maigret 集成进你的产品
<a id="about"></a>
## 关于
### 免责声明
**仅供教育与合法用途。** 使用者需自行承担遵守所在司法辖区相关法律(GDPR、CCPA 等)的责任。作者不对任何滥用行为负责。
### 反馈
[提交 issue](https://github.com/soxoj/maigret/issues) · [GitHub Discussions](https://github.com/soxoj/maigret/discussions) · [Telegram](https://t.me/soxoj)
### SOWEL 分类
涉及到的 OSINT 技术:
- [SOTL-2.2. Search For Accounts On Other Platforms](https://sowel.soxoj.com/other-platform-accounts)
- [SOTL-6.1. Check Logins Reuse To Find Another Account](https://sowel.soxoj.com/logins-reuse)
- [SOTL-6.2. Check Nicknames Reuse To Find Another Account](https://sowel.soxoj.com/nicknames-reuse)
### 许可证
MIT © [Maigret](https://github.com/soxoj/maigret)
+104
View File
@@ -0,0 +1,104 @@
# Troubleshooting
Common issues when running Maigret and how to fix them. If none of this helps, [open an issue](https://github.com/soxoj/maigret/issues) with the output of `maigret --version` and the exact command you ran.
## "Lots of sites fail / timeout / return 403"
This is by far the most common report. It almost always comes from anti-bot protection (Cloudflare, DDoS-Guard, Akamai, etc.) or a slow network — not from a bug in Maigret.
**Results vary a lot depending on where you run from.** The same command on the same username can produce very different output on:
- **Mobile internet** (4G/5G) — usually the best results. Carrier NAT shares your IP with thousands of real users, so WAFs rarely block it.
- **Home broadband** — generally good, though some ISPs are reputation-flagged.
- **Hosting / cloud / VPS infrastructure** (AWS, GCP, DigitalOcean, Hetzner, etc.) — the worst case. Datacenter IP ranges are blanket-blocked or challenged by most WAFs, so you will see many false negatives and 403s.
If a run looks suspiciously empty, **try a different network before assuming Maigret is broken**: tether from your phone, switch between Wi-Fi and mobile, or move the run off a VPS onto a residential machine. Comparing results across two networks is also the fastest way to tell whether a missing account is genuinely missing or just blocked on the current IP.
Once you have a sense of the baseline, try these tweaks in order:
1. **Raise the timeout.** The default is 30 seconds. On mobile networks or for slow sites, bump it:
```bash
maigret user --timeout 60
```
2. **Retry failed checks.** Transient 5xx / timeouts often clear on a second try:
```bash
maigret user --retries 2
```
3. **Lower parallelism.** Some WAFs rate-limit aggressively. Maigret defaults to 100 concurrent connections (`-n` / `--max-connections`) — dropping this makes you look less like a scanner:
```bash
maigret user -n 20
```
4. **Route through a residential proxy.** Datacenter IPs (AWS, GCP, DigitalOcean) are blanket-blocked by many WAFs. A residential / mobile proxy usually fixes this:
```bash
maigret user --proxy http://user:pass@residential-proxy:port
```
Note: Tor (`--tor-proxy`) rarely helps here — most WAFs block Tor exit nodes just as aggressively as datacenter IPs. Use Tor only when you actually need to reach `.onion` sites (see below).
If specific sites *always* fail regardless of the above, they are likely broken in the database (stale markers, new WAF, site redesign). Report them with `--print-errors` output so a maintainer can look at the check config.
## "No results at all" / "maigret: command not found"
- **`command not found`** — `pip install maigret` put the binary under `~/.local/bin` (Linux/macOS) or `%APPDATA%\Python\Scripts` (Windows). Add that directory to `PATH`, or run `python3 -m maigret user` instead.
- **Empty output** — check that you actually passed a username; `maigret` alone prints help. Also confirm Python 3.10+ with `python3 --version`.
## "SSL / certificate errors"
Usually caused by a corporate MITM proxy or an outdated `certifi` bundle.
```bash
pip install --upgrade certifi
```
If you are behind a corporate proxy, set `HTTPS_PROXY` / `HTTP_PROXY` environment variables and pass `--proxy "$HTTPS_PROXY"` so Maigret uses the same route.
## Running over Tor, I2P, or Tails OS
Two different goals, two different flags:
- **Route only `.onion` / `.i2p` sites through their gateway** (clearweb checks still use your direct connection). Use `--tor-proxy` / `--i2p-proxy`:
```bash
maigret user --tor-proxy socks5://127.0.0.1:9050 # only .onion goes via Tor
maigret user --i2p-proxy http://127.0.0.1:4444 # only .i2p goes via I2P
```
Without these flags, `.onion` / `.i2p` sites are silently skipped.
- **Route the whole run through Tor / a proxy** (e.g. on Tails OS, or to anonymise the scan). Use `--proxy`:
```bash
# system tor daemon (apt install tor, Tails)
maigret user --proxy socks5://127.0.0.1:9050 --timeout 60 --retries 2
# Tor Browser bundle (different SOCKS port!)
maigret user --proxy socks5://127.0.0.1:9150 --timeout 60 --retries 2
```
Most public WAFs block Tor exits, so expect more UNKNOWNs over Tor than on a residential line — this is the cost of anonymity, not a bug. Raising `--timeout` to 60 and adding `--retries 2` materially reduces noise.
On Tails, `torsocks maigret …` / `torify maigret …` do **not** work — Maigret's HTTP client bypasses libc, so the wrapper has no effect. Use `--proxy` instead. To install Maigret over Tor: `torsocks pip install --user maigret`.
Maigret does not launch or manage Tor / I2P daemons — they must already be running.
For the full walkthrough (Tor Browser vs system `tor` ports, Tails persistence, reports paths), see the [Tor, I2P, and proxies](https://maigret.readthedocs.io/en/latest/tor-and-proxies.html) page on readthedocs.
## "The PDF / XMind / HTML report looks wrong"
- **PDF** — requires `weasyprint` and its system dependencies (Pango, Cairo, GDK-PixBuf). On Debian/Ubuntu: `apt install libpango-1.0-0 libpangoft2-1.0-0`. macOS: `brew install pango`.
- **XMind** — the `--xmind` flag generates **XMind 8** files. XMind 2022+ (Zen / XMind 2023) uses a different format and will not open them. Use XMind 8 or convert via `--html`.
- **HTML** looks unstyled — open it through a local file path (`file:///...`), not via a preview pane that strips CSS.
## "The site database is out of date"
Maigret auto-fetches a fresh `data.json` from GitHub once every 24 hours. To force-refresh now:
```bash
maigret user --force-update
```
To run entirely against the local built-in copy (e.g. offline):
```bash
maigret user --no-autoupdate
```
## Still stuck?
- [Open an issue](https://github.com/soxoj/maigret/issues) — include your OS, Python version, Maigret version, and the full command.
- Ask in [GitHub Discussions](https://github.com/soxoj/maigret/discussions) or the [Telegram](https://t.me/soxoj) channel.
+69
View File
@@ -0,0 +1,69 @@
# Maigret
<div align="center">
<img src="https://raw.githubusercontent.com/soxoj/maigret/main/static/maigret.png" height="220" alt="Maigret logo"/>
</div>
**Maigret** collects a dossier on a person **by username only**, checking for accounts on a huge number of sites and gathering all the available information from web pages. No API keys required.
## Installation
Google Cloud Shell does not ship with all the system libraries Maigret needs (`libcairo2-dev`, `pkg-config`). The helper script below installs them and then builds Maigret from the cloned source.
Copy the command and run it in the Cloud Shell terminal:
```bash
./utils/cloudshell_install.sh
```
When the script finishes, verify the install:
```bash
maigret --version
```
## Usage examples
Run a basic search for a username. By default Maigret checks the **500 highest-ranked sites by traffic** — pass `-a` to scan the full 3,000+ database.
```bash
maigret soxoj
```
Search several usernames at once:
```bash
maigret user1 user2 user3
```
Narrow the run to sites related to cryptocurrency via the `crypto` tag (you can also use country tags):
```bash
maigret vitalik.eth --tags crypto
```
Generate reports in HTML, PDF, and XMind 8 formats:
```bash
maigret soxoj --html
maigret soxoj --pdf
maigret soxoj --xmind
```
Download a generated report from Cloud Shell to your local machine:
```bash
cloudshell download reports/report_soxoj.pdf
```
Tune reliability on flaky networks — raise the timeout and retry failed checks:
```bash
maigret soxoj --timeout 60 --retries 2
```
For the full list of options see `maigret --help` or the [CLI documentation](https://maigret.readthedocs.io/en/latest/command-line-options.html).
## Further reading
Full project documentation: [maigret.readthedocs.io](https://maigret.readthedocs.io/)
+40
View File
@@ -0,0 +1,40 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile gettext intl-update html-zh_CN
# Translation pipeline
#
# 1. `make gettext` regenerates .pot files from the English .rst sources.
# 2. `make intl-update LANG=zh_CN` syncs the .po files for that language
# against the new .pot files (new strings get empty msgstrs, removed
# strings get commented out, changed strings get a `fuzzy` marker).
# 3. `make html-zh_CN` builds a local Simplified Chinese HTML preview.
#
# To bootstrap a new language, run `make intl-update LANG=<code>`.
LANG ?= zh_CN
gettext:
$(SPHINXBUILD) -b gettext $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/gettext"
intl-update: gettext
sphinx-intl update -p "$(BUILDDIR)/gettext" -l $(LANG) -d "$(SOURCEDIR)/locale"
html-zh_CN:
$(SPHINXBUILD) -b html -D language=zh_CN $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html_zh_CN"
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+35
View File
@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
+3
View File
@@ -0,0 +1,3 @@
sphinx-copybutton
sphinx_rtd_theme
sphinx-intl
+412
View File
@@ -0,0 +1,412 @@
.. _command-line-options:
Command line options
====================
Usernames
---------
``maigret username1 username2 ...``
You can specify several usernames separated by space. Usernames are
**not** mandatory as there are other operations modes (see below).
Parsing of account pages and online documents
---------------------------------------------
``maigret --parse URL``
Maigret will try to extract information about the document/account owner
(including username and other ids) and will make a search by the
extracted username and ids. See examples in the :ref:`extracting-information-from-pages` section.
Main options
------------
Options are also configurable through settings files, see
:doc:`settings section <settings>`.
``--tags`` - Filter sites for searching by tags: sites categories and
two-letter country codes (**not a language!**). E.g. photo, dating, sport; jp, us, global.
Multiple tags can be associated with one site. **Warning**: tags markup is
not stable now. Read more :doc:`in the separate section <tags>`.
``--exclude-tags`` - Exclude sites with specific tags from the search
(blacklist). E.g. ``--exclude-tags porn,dating`` will skip all sites
tagged with ``porn`` or ``dating``. Can be combined with ``--tags`` to
include certain categories while excluding others. Read more
:doc:`in the separate section <tags>`.
``-n``, ``--max-connections`` - Allowed number of concurrent connections
**(default: 100)**.
``-a``, ``--all-sites`` - Use all sites for scan **(default: top 500)**.
``--top-sites`` - Count of sites for scan ranked by Majestic Million
**(default: top 500)**.
**Mirrors:** After the top *N* sites by Majestic Million rank are chosen (respecting
``--tags``, ``--use-disabled-sites``, etc.), Maigret may add extra sites
whose database field ``source`` names a **parent platform** that itself falls
in the Majestic Million top *N* when ranking **including disabled** sites. For example,
if ``Twitter`` ranks in the first 500 by Majestic Million, a mirror such as ``memory.lol``
(with ``source: Twitter``) is included even though it has no rank and would
otherwise be cut off. The same applies to Instagram-related mirrors (e.g.
Picuki) when ``Instagram`` is in that parent top *N* by rank—even if the
official ``Instagram`` entry is disabled and not scanned by default, its
mirrors can still be pulled in. The final list is the ranked top *N* plus
these mirrors (no fixed upper bound on mirror count).
``--timeout`` - Time (in seconds) to wait for responses from sites
**(default: 30)**. A longer timeout will be more likely to get results
from slow sites. On the other hand, this may cause a long delay to
gather all results. The choice of the right timeout should be carried
out taking into account the bandwidth of the Internet connection.
Network and proxy options
~~~~~~~~~~~~~~~~~~~~~~~~~
``--proxy PROXY_URL`` / ``-p PROXY_URL`` - Route **every** check through
the given HTTP or SOCKS proxy. Example: ``socks5://127.0.0.1:1080``,
``http://user:pass@proxy.example:3128``. This is the flag to use for
routing the whole run through Tor (``--proxy socks5://127.0.0.1:9050``),
a residential proxy, or any corporate gateway. No default.
``--tor-proxy TOR_PROXY_URL`` - Gateway used **only** for ``.onion``
sites in the database **(default: socks5://127.0.0.1:9050)**. Clearweb
sites are unaffected — for them Maigret uses your direct connection or
``--proxy`` if you set one. Without this flag, ``.onion`` sites are
silently skipped.
``--i2p-proxy I2P_PROXY_URL`` - Gateway used **only** for ``.i2p``
sites in the database **(default: http://127.0.0.1:4444)**. Same
"only matching protocol" rule as ``--tor-proxy``.
Maigret does not start the Tor or I2P daemon for you — launch it first.
For a full walkthrough (Tor Browser vs system ``tor`` port numbers,
Tails OS recipe, timeout/retry tuning), see :doc:`tor-and-proxies`.
``--cookies-jar-file`` - File with custom cookies in Netscape format
(aka cookies.txt). You can install an extension to your browser to
download own cookies (`Chrome <https://chrome.google.com/webstore/detail/get-cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>`_, `Firefox <https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/>`_).
``--no-recursion`` - Disable parsing pages for other usernames and
recursive search by them.
``--use-disabled-sites`` - Use disabled sites to search (may cause many
false positives).
``--id-type`` - Specify identifier(s) type (default: username).
Supported types: gaia_id, vk_id, yandex_public_id, ok_id, wikimapia_uid.
Currently, you must add ``-a`` flag to run a scan on sites with custom
id types, sites will be filtered automatically.
``--ignore-ids`` - Do not make search by the specified username or other
ids. Useful for repeated scanning with found known irrelevant usernames.
``--db`` - Load Maigret database from a JSON file or an online, valid,
JSON file. See :ref:`custom-database` below.
``--no-autoupdate`` - Disable the automatic database update check that
runs at startup. The currently cached (or bundled) database is used
as-is.
``--force-update`` - Force a database update check at startup, ignoring
the usual check interval. Implies ``--no-autoupdate`` for the rest of
the run after the explicit update finishes.
``--retries RETRIES`` - Count of attempts to restart temporarily failed
requests.
``--with-domains`` *(experimental)* - Also resolve a small set of
``{username}.<tld>`` patterns through DNS (A-records) in parallel with
the normal HTTP checks. Currently 7 entries in the database use this
path (``.ddns.net``, ``.com``, ``.pro``, ``.me``, ``.biz``, ``.email``,
``.guru``). DNS-only hits can include parking domains and catch-all
wildcards, so treat results as a lead rather than confirmation.
See the :doc:`FAQ entry on DNS domain checks <faq>`.
``--cloudflare-bypass`` *(experimental)* - Route checks for sites tagged
``protection: ["cf_js_challenge"]`` / ``["cf_firewall"]`` / ``["webgate"]``
through a local Chrome-based solver (FlareSolverr by default). The bypass
is opt-in — without this flag (or
``settings.cloudflare_bypass.enabled = true``) those sites are checked
the usual way, which Cloudflare almost always blocks: you get an UNKNOWN
status with a JS-challenge / firewall error rather than a real result.
Configure the backend in ``settings.cloudflare_bypass.modules``.
See :ref:`cloudflare-bypass`. **Experimental** — the flag, schema and
routing rules may change without backwards-compatibility guarantees.
.. _custom-database:
Using a custom sites database
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``--db`` flag accepts three forms:
1. **HTTP(S) URL** — fetched as-is, e.g.
``--db https://example.com/my_db.json``.
2. **Local file path** — absolute (``--db /tmp/private.json``) or
relative to the current working directory
(``--db LLM/maigret_private_db.json``).
3. **Module-relative path** — kept for backwards compatibility, resolved
against the installed ``maigret/`` package directory (e.g. the
default ``resources/data.json``).
Resolution order for local paths: the path is first tried as given
(absolute or cwd-relative); if that file does not exist, Maigret falls
back to the legacy module-relative resolution. If neither location
contains the file, Maigret exits with an error rather than silently
loading the bundled database.
When ``--db`` points to a custom file, automatic database updates are
skipped — the file is used exactly as provided.
On every run Maigret prints the database it actually loaded, for
example::
[+] Using sites database: /path/to/maigret_private_db.json (6 sites)
If loading the requested database fails for any other reason (corrupt
JSON, missing required keys, …), Maigret prints a warning, falls back
to the bundled database, and reports the fallback explicitly::
[-] Falling back to bundled database: /…/maigret/resources/data.json
[+] Using sites database: /…/maigret/resources/data.json (3154 sites)
A typical invocation against a private database, with auto-update
disabled and all sites scanned, looks like::
python3 -m maigret username \
--db LLM/maigret_private_db.json \
--no-autoupdate -a
Reports
-------
``-P``, ``--pdf`` - Generate a PDF report (general report on all
usernames).
``-H``, ``--html`` - Generate an HTML report file (general report on all
usernames).
``-X``, ``--xmind`` - Generate an XMind 8 mindmap (one report per
username).
``-C``, ``--csv`` - Generate a CSV report (one report per username).
``-T``, ``--txt`` - Generate a TXT report (one report per username).
``-J``, ``--json`` - Generate a JSON report of specific type: simple,
ndjson (one report per username). E.g. ``--json ndjson``
``-M``, ``--md`` - Generate a Markdown report (general report on all
usernames). See :ref:`markdown-report` below.
``--neo4j`` - Generate a Neo4j Cypher report: a ``.cypher`` script that
recreates the maigret graph (the same one produced by ``--graph``) in a
Neo4j database, importable with ``cypher-shell`` or the Neo4j Browser
(general report on all usernames). See :ref:`neo4j-export` below.
``--ai`` - Run an AI-powered analysis of the search results using an
OpenAI-compatible chat completion API. The internal Markdown report is
sent to the model, which returns a short investigation summary that is
streamed to the terminal. See :ref:`ai-analysis` below.
``--ai-model`` - Model name to use with ``--ai``. Defaults to
``openai_model`` from settings (``gpt-4o`` out of the box).
``-fo``, ``--folderoutput`` - Results will be saved to this folder,
``results`` by default. Will be created if doesnt exist.
``--web PORT`` - Start the built-in web interface on the given port and
serve results / downloadable reports from a single page. Example:
``maigret --web 5000`` → open ``http://127.0.0.1:5000``. Full
walkthrough with screenshots: :ref:`web-interface`.
Output options
--------------
``-v``, ``--verbose`` - Display extra information and metrics.
*(loglevel=WARNING)*
``-vv``, ``--info`` - Display service information. *(loglevel=INFO)*
``-vvv``, ``--debug``, ``-d`` - Display debugging information and site
responses. *(loglevel=DEBUG)*
``--print-not-found`` - Print sites where the username was not found.
``--print-errors`` - Print errors messages: connection, captcha, site
country ban, etc.
Other operations modes
----------------------
``--version`` - Display version information and dependencies.
``--self-check`` - Do self-checking for sites and database. Each site is
tested by looking up its known-claimed and known-unclaimed usernames and
verifying that the results match expectations. Individual site failures
(network errors, unexpected exceptions, etc.) are caught and logged
without stopping the overall process, so the check always runs to
completion. After checking, Maigret reports a summary of issues found.
If any sites were disabled (see ``--auto-disable``), Maigret asks if you
want to save updates; answering y/Y will rewrite the local database.
``--auto-disable`` - Used with ``--self-check``: automatically disable
sites that fail checks (incorrect detection of claimed/unclaimed
usernames, connection errors, or unexpected exceptions). Without this
flag, ``--self-check`` only **reports** issues without modifying the
database.
``--diagnose`` - Used with ``--self-check``: print detailed diagnosis
information for each failing site, including the check type, the list
of issues found, and recommendations (e.g. suggesting a different
``checkType``).
``--submit URL`` - Do an automatic analysis of the given account URL or
site main page URL to determine the site engine and methods to check
account presence. After checking Maigret asks if you want to add the
site, answering y/Y will rewrite the local database.
.. _markdown-report:
Markdown report (LLM-friendly)
------------------------------
The ``--md`` / ``-M`` flag generates a Markdown report designed for both human reading and analysis by AI assistants (ChatGPT, Claude, etc.).
.. code-block:: console
maigret username --md
The report includes:
- **Summary** with aggregated personal data (all fullnames, locations, bios found across accounts), country tags, website tags, first/last seen timestamps.
- **Per-account sections** with profile URL, site tags, and all extracted fields (username, bio, follower count, linked accounts, etc.).
- **Possible false positives** disclaimer explaining that accounts may belong to different people.
- **Ethical use** notice about applicable data protection laws.
**Using with AI tools:**
The Markdown format is optimized for LLM context windows. You can feed the report directly to an AI assistant for follow-up analysis:
.. code-block:: console
# Generate the report
maigret johndoe --md
# Feed it to an AI tool
cat reports/report_johndoe.md | llm "Analyze this OSINT report and summarize key findings"
The structured Markdown with per-site sections makes it easy for AI tools to extract relationships, cross-reference identities, and identify patterns across accounts.
For a built-in alternative that calls the model for you and prints the
summary directly, see :ref:`ai-analysis` below.
.. _ai-analysis:
AI analysis (built-in)
----------------------
The ``--ai`` flag turns the search results into a short investigation
summary by sending the internal Markdown report to an OpenAI-compatible
chat completion API and streaming the model's reply to the terminal.
.. code-block:: console
export OPENAI_API_KEY=sk-...
maigret username --ai
# use a smaller / cheaper model
maigret username --ai --ai-model gpt-4o-mini
While ``--ai`` is active, per-site progress lines and the short text
report at the end are suppressed so the streamed summary is the main
output. The Markdown report itself is built in memory and is **not**
written to disk by ``--ai`` alone — combine with ``--md`` if you also
want the file on disk.
The summary follows a fixed format with sections for the most likely
real name, location, occupation, interests, languages, main website,
username variants, number of platforms, active years, a confidence
rating, and a short list of follow-up leads. The model is instructed
to rely only on what is supported by the report and to avoid mixing
clearly unrelated profiles into the main identity.
**Configuration.** The API key is resolved from
``settings.openai_api_key`` first, then from the ``OPENAI_API_KEY``
environment variable. The endpoint defaults to
``https://api.openai.com/v1`` and can be redirected to any
OpenAI-compatible service (Azure OpenAI, OpenRouter, a local server,
…) by setting ``openai_api_base_url`` in ``settings.json``. See
:ref:`settings` for the full list of options.
.. note::
``--ai`` makes a network request to the configured chat completion
endpoint and sends the full Markdown report (which contains the
gathered profile data). Use it only with providers and accounts
you trust with that data.
.. _neo4j-export:
Neo4j export
------------
The ``--neo4j`` flag serializes the maigret graph — the same nodes and
relationships behind ``--graph`` — into a ``*_neo4j.cypher`` script you
can load into a `Neo4j <https://neo4j.com/>`_ database for querying and
visual exploration of how identities, accounts, sites, and extracted
data points link together.
.. code-block:: console
maigret username --neo4j
This writes ``reports/report_username_neo4j.cypher``. No extra runtime
dependency is required (it reuses the ``networkx`` graph that ``--graph``
already builds).
**What the script contains.**
- A ``CREATE CONSTRAINT ... IF NOT EXISTS`` ensuring the ``name``
property of every ``:MaigretNode`` is unique.
- One ``MERGE`` per node. Each node carries three properties:
``name`` (the unique key, e.g. ``account: https://github.com/user``),
``type`` (``username``, ``account``, ``fullname``, ``uid``, …) and
``label`` (the human-readable value).
- One ``MERGE`` per edge as a ``[:LINKED_TO]`` relationship.
Because every node and edge is ``MERGE``-d on its unique key, importing
the same report twice is **idempotent** — it updates existing nodes
instead of creating duplicates.
**Importing.**
.. code-block:: console
# via cypher-shell (Neo4j must be running; default Bolt port 7687)
cypher-shell -u neo4j -p <password> < reports/report_username_neo4j.cypher
Alternatively, open the Neo4j Browser at ``http://localhost:7474`` and
paste the script contents into the query editor. Drop the ``-p`` flag if
authentication is disabled, or let ``cypher-shell`` prompt for the
password interactively.
**Exploring the graph.** Once imported, inspect it with Cypher, e.g.:
.. code-block:: cypher
// show the whole graph
MATCH (n:MaigretNode)-[r:LINKED_TO]->(m) RETURN n, r, m;
// accounts linked to a given username
MATCH (u:MaigretNode {type: 'username'})-[:LINKED_TO]->(a:MaigretNode {type: 'account'})
RETURN u.label, a.label;
The ``CREATE CONSTRAINT ... IF NOT EXISTS FOR ... REQUIRE`` syntax
requires Neo4j 4.4+; the ``MERGE`` statements themselves work on any
version.
+48
View File
@@ -0,0 +1,48 @@
# Configuration file for the Sphinx documentation builder.
import os
# -- Project information
project = 'Maigret'
copyright = '2025, soxoj'
author = 'soxoj'
release = '0.6.2'
version = '0.6'
# -- Internationalization
#
# Default to English. Translation projects on Read the Docs set the
# ``READTHEDOCS_LANGUAGE`` env var (e.g. ``zh_CN``); locally the language
# can be overridden via ``sphinx-build -D language=zh_CN``.
language = os.environ.get('READTHEDOCS_LANGUAGE', 'en')
locale_dirs = ['locale/']
gettext_compact = False
gettext_uuid = True
# -- General configuration
extensions = [
'sphinx.ext.duration',
'sphinx.ext.doctest',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.intersphinx',
'sphinx_copybutton'
]
intersphinx_mapping = {
'python': ('https://docs.python.org/3/', None),
'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
}
intersphinx_disabled_domains = ['std']
templates_path = ['_templates']
# -- Options for HTML output
html_theme = 'sphinx_rtd_theme'
# -- Options for EPUB output
epub_show_urls = 'footnote'
+443
View File
@@ -0,0 +1,443 @@
.. _development:
Development
==============
Frequently Asked Questions
--------------------------
1. Where to find the list of supported sites?
The human-readable list of supported sites is available in the `sites.md <https://github.com/soxoj/maigret/blob/main/sites.md>`_ file in the repository.
It's been generated automatically from the main JSON file with the list of supported sites.
The machine-readable JSON file with the list of supported sites is available in the
`data.json <https://github.com/soxoj/maigret/blob/main/maigret/resources/data.json>`_ file in the directory `resources`.
2. Which methods to check the account presence are supported?
The supported methods (``checkType`` values in ``data.json``) are:
- ``message`` - the most reliable method, checks if any string from ``presenceStrs`` is present and none of the strings from ``absenceStrs`` are present in the HTML response
- ``status_code`` - checks that status code of the response is 2XX
- ``response_url`` - check if there is not redirect and the response is 2XX
.. note::
Maigret natively treats specific anti-bot HTTP status codes (like LinkedIn's ``HTTP 999``) as a standard "Not Found/Available" signal instead of throwing an infrastructure Server Error, gracefully preventing false positives.
See the details of check mechanisms in the `checking.py <https://github.com/soxoj/maigret/blob/main/maigret/checking.py#L339>`_ file.
.. note::
Maigret now uses the **Majestic Million** dataset for site popularity sorting instead of the discontinued Alexa Rank API. For backward compatibility with existing configurations and parsers, the ranking field in `data.json` and internal site models remains named ``alexaRank`` and ``alexa_rank``.
**Mirrors and ``--top-sites``:** When you limit scans with ``--top-sites N``, Maigret also includes *mirror* sites (entries whose ``source`` field points at a parent platform such as Twitter or Instagram) if that parent would appear in the Majestic Million top *N* when disabled sites are considered for ranking. See the **Mirrors** paragraph under ``--top-sites`` in :doc:`command-line-options`.
Testing
-------
It is recommended use Python 3.10 for testing.
Install test requirements:
.. code-block:: console
poetry install --with dev
Use the following commands to check Maigret:
.. code-block:: console
# run linter and typing checks
# order of checks:
# - critical syntax errors or undefined names
# - flake checks
# - mypy checks
make lint
# run black formatter
make format
# run testing with coverage html report
# current test coverage is 58%
make test
# open html report
open htmlcov/index.html
# get flamechart of imports to estimate startup time
make speed
Site naming conventions
-----------------------------------------------
Site names are the keys in ``data.json`` and appear in user-facing reports. Follow these rules:
- **Title Case** by default: ``Product Hunt``, ``Hacker News``.
- **Lowercase** only if the brand itself is written that way: ``kofi``, ``note``, ``hi5``.
- **No domain suffix** (``calendly.com````Calendly``), unless the domain is part of the recognized brand name: ``last.fm``, ``VC.ru``, ``Archive.org``.
- **No full UPPERCASE** unless the brand is an acronym: ``VK``, ``CNET``, ``ICQ``, ``IFTTT``.
- **No** ``www.`` **or** ``https://`` **prefix** in the name.
- **Spaces** are allowed when the brand uses them: ``Star Citizen``, ``Google Maps``.
- **{username} templates** in names are acceptable: ``{username}.tilda.ws``.
When in doubt, check how the service refers to itself on its homepage.
How to fix false-positives
-----------------------------------------------
If you want to work with sites database, don't forget to activate statistics update git hook, command for it would look like this: ``git config --local core.hooksPath .githooks/``.
You should make your git commits from your maigret git repo folder, or else the hook wouldn't find the statistics update script.
1. Determine the problematic site.
If you already know which site has a false-positive and want to fix it specifically, go to the next step.
Otherwise, simply run a search with a random username (e.g. `laiuhi3h4gi3u4hgt`) and check the results.
Alternatively, you can use the `community Telegram bot <https://sites.google.com/view/maigret-bot-link>`_.
2. Open the account link in your browser and check:
- If the site is completely gone, remove it from the list
- If the site still works but looks different, update in data.json how we check it
- If the site requires login to view profiles, disable checking it
3. Find the site in the `data.json <https://github.com/soxoj/maigret/blob/main/maigret/resources/data.json>`_ file.
If the ``checkType`` method is not ``message`` and you are going to fix check, update it:
- put ``message`` in ``checkType``
- put in ``absenceStrs`` a keyword that is present in the HTML response for an non-existing account
- put in ``presenceStrs`` a keyword that is present in the HTML response for an existing account
If you have trouble determining the right keywords, you can use automatic detection by passing the account URL with the ``--submit`` option:
.. code-block:: console
maigret --submit https://my.mail.ru/bk/alex
To disable checking, set ``disabled`` to ``true`` or simply run:
.. code-block:: console
maigret --self-check --site My.Mail.ru@bk.ru
To debug the check method using the response HTML, you can run:
.. code-block:: console
maigret soxoj --site My.Mail.ru@bk.ru -d 2> response.txt
There are few options for sites data.json helpful in various cases:
- ``engine`` - a predefined check for the sites of certain type (e.g. forums), see the ``engines`` section in the JSON file
- ``headers`` - a dictionary of additional headers to be sent to the site
- ``requestHeadOnly`` - set to ``true`` if it's enough to make a HEAD request to the site
- ``regexCheck`` - a regex to check if the username is valid, in case of frequent false-positives
- ``requestMethod`` - set the HTTP method to use (e.g., ``POST``). By default, Maigret natively defaults to GET or HEAD.
- ``requestPayload`` - a dictionary with the JSON payload to send for POST requests (e.g., ``{"username": "{username}"}``), extremely useful for parsing GraphQL or modern JSON APIs.
- ``protection`` - a list of protection types detected on the site (see below).
``protection`` (site protection tracking)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``protection`` field records what kind of anti-bot protection a site uses. Maigret reads this field and automatically applies the appropriate bypass mechanism where one exists.
Two categories of tag:
- **Load-bearing.** Maigret changes its HTTP client or headers based on the tag. Currently only ``tls_fingerprint`` (switches to ``curl_cffi`` with Chrome-class TLS).
- **Documentation-only.** Maigret does **not** change behavior based on the tag; it records *why* the site is hard so a future solver can target the right set of sites without re-auditing.
Within the documentation-only tags, there is a further split that dictates whether the site is ``disabled: true``:
- ``ip_reputation`` is the **only** doc-tag that **keeps the site enabled**. It means "works for most users, fails from datacenter/cloud IPs." Disabling would silently hide a working site from anyone with a clean IP. The fix is **external** to Maigret (residential IP or ``--proxy``).
- ``cf_js_challenge``, ``cf_firewall``, ``aws_waf_js_challenge``, ``ddos_guard_challenge``, ``custom_bot_protection``, ``js_challenge`` all pair with ``disabled: true``. They mean "does not work for anyone right now"; the tag identifies the provider so that when a bypass ships, every site with that tag can be re-enabled in one pass.
Supported values:
- ``tls_fingerprint`` *(load-bearing; site stays enabled)* — the site fingerprints the TLS handshake (JA3/JA4) and blocks non-browser clients. Maigret automatically uses ``curl_cffi`` with Chrome browser emulation to bypass this. Requires the ``curl_cffi`` package (included as a dependency). Examples: Instagram, NPM, Codepen, Kickstarter, Letterboxd.
- ``ip_reputation`` *(documentation-only; site stays enabled)* — the site blocks requests from datacenter/cloud IPs regardless of headers or TLS. Cannot be bypassed automatically; run Maigret from a regular internet connection (not a datacenter) or use a proxy (``--proxy``). The site is **not** marked ``disabled`` because it continues to work for users on residential IPs. Examples: Reddit, Patreon, Figma, OnlyFans.
- ``cf_js_challenge`` *(documentation-only; pair with ``disabled: true``)* — Cloudflare Managed Challenge / Turnstile JS challenge. Symptom: HTTP 403 with ``cf-mitigated: challenge`` header; body contains ``challenges.cloudflare.com``, ``_cf_chl_opt``, ``window._cf_chl``, or "Just a moment". Not bypassable via ``curl_cffi`` TLS impersonation (verified across Chrome 123/124/131, Safari 17/18, Firefox 133/135, Edge 101 — all return the same 403 challenge page); a real browser executing the challenge JS is required to obtain the clearance cookie. Sites stay ``disabled: true`` until a CF-challenge solver is integrated. Examples: DMOJ, Elakiri, Fanlore, Bdoutdoors, TheStudentRoom, forum.hr.
- ``cf_firewall`` *(documentation-only; pair with ``disabled: true``)* — Cloudflare firewall rule / bot score block (WAF action=block, **not** action=challenge). Symptom: HTTP 403 served by Cloudflare (``server: cloudflare``, ``cf-ray`` header) **without** JS-challenge markers — body typically shows "Access denied", "Attention Required", or just a bare 1015/1016/1020 error page. Unlike ``ip_reputation``, residential IPs are **not** sufficient to bypass — Cloudflare decides based on a composite of bot score, TLS fingerprint, UA, ASN, and custom site-owner rules, so ``curl_cffi`` Chrome impersonation from a residential line still returns 403. Sites stay ``disabled: true`` until a per-site bypass (cookies, real browser, or residential+clean session) is found. Examples: Fark, Fodors, Huntingnet, Hunttalk.
- ``aws_waf_js_challenge`` *(documentation-only; pair with ``disabled: true``)* — the site is protected by AWS WAF with a JavaScript challenge. Symptom: HTTP 202 with empty body and ``x-amzn-waf-action: challenge`` header (a token-granting challenge that requires executing the CAPTCHA/challenge JS bundle). Neither ``curl_cffi`` TLS impersonation nor User-Agent changes bypass this — a real browser or the official AWS WAF challenge-solver SDK is required. Sites stay ``disabled: true`` until a solver is integrated. Example: Dreamwidth.
- ``ddos_guard_challenge`` *(documentation-only; pair with ``disabled: true``)* — DDoS-Guard (ddos-guard.net) anti-bot page. Symptom: HTTP 403 with ``server: ddos-guard`` header; body contains "DDoS-Guard". DDoS-Guard fingerprints different UAs per source IP, so a single User-Agent override does not work across environments; a JS-capable bypass or DDoS-Guard-aware solver is required. Sites stay ``disabled: true`` until a solver is integrated. Example: ForumHouse.
- ``js_challenge`` *(documentation-only; pair with ``disabled: true``)***fallback** for JavaScript-challenge systems whose provider cannot be identified (custom in-house challenge pages that are not Cloudflare, AWS WAF, or any other recognized vendor). Prefer a provider-specific tag whenever the provider can be pinned down from response headers or body signatures.
- ``custom_bot_protection`` *(documentation-only; pair with ``disabled: true``)***fallback** for non-JS-challenge bot protection served by a custom/in-house system (not Cloudflare, not AWS WAF, not DDoS-Guard). Typical symptom: HTTP 403 from the site's own origin server (``server: nginx``, AWS ELB, etc.) with a branded block page, returned regardless of TLS fingerprint or residential IP. Not generically bypassable; investigate per site (cookies, session, proxy geography). Examples: Hackerearth ("HackerEarth Guardian"), FreelanceJob (nginx-level block).
**Rule: prefer provider-specific protection tags.** When a site is blocked by an identifiable anti-bot vendor, always record the vendor in the tag (``cf_js_challenge``, ``cf_firewall``, ``aws_waf_js_challenge``, ``ddos_guard_challenge``, and future additions such as ``sucuri_challenge``, ``incapsula_challenge``). The generic ``js_challenge`` and ``custom_bot_protection`` tags are reserved for custom/unknown systems. Rationale: bypass solvers are inherently provider-specific (a Cloudflare Turnstile solver does not help with AWS WAF); recording the provider in advance lets us fan out fixes the moment a per-provider solver is added, without re-auditing every disabled site. The same principle applies to other protection categories when the provider is identifiable.
Example:
.. code-block:: json
"Instagram": {
"url": "https://www.instagram.com/{username}/",
"checkType": "message",
"presenseStrs": ["\"routePath\":\"\\/"],
"absenceStrs": ["\"routePath\":null"],
"protection": ["tls_fingerprint"]
}
``urlProbe`` (optional profile probe URL)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default Maigret performs the HTTP request to the same URL as ``url`` (the public profile link pattern).
If you set ``urlProbe`` in ``data.json``, Maigret **fetches** that URL for the presence check (API, GraphQL, JSON endpoint, etc.), while **reports and ``url_user``** still use ``url`` — the human-readable profile page users should open.
Placeholders: ``{username}``, ``{urlMain}``, ``{urlSubpath}`` (same as for ``url``). Example: GitHub uses ``url`` ``https://github.com/{username}`` and ``urlProbe`` ``https://api.github.com/users/{username}``; Picsart uses the web profile ``https://picsart.com/u/{username}`` and probes ``https://api.picsart.com/users/show/{username}.json``.
.. warning::
``url`` must **always** stay a human-openable profile page — it is shown to the
user and printed as the clickable result link. Never put an API / JSON / GraphQL
endpoint in ``url``; that belongs in ``urlProbe``. If the check needs an API
endpoint, keep the browsable profile in ``url`` and add the API URL as
``urlProbe`` (e.g. Weibo: ``url`` ``https://weibo.com/n/{username}``, ``urlProbe``
``https://weibo.com/ajax/profile/info?screen_name={username}``).
Implementation: ``make_site_result`` in `checking.py <https://github.com/soxoj/maigret/blob/main/maigret/checking.py>`__.
Site check fixes using LLM
--------------------------
.. note::
The ``LLM/`` directory at the root of the repository contains detailed instructions for editing site checks (in Markdown format): checklist, full guide to ``checkType`` / ``data.json`` / ``urlProbe``, handling false positives, searching for public JSON APIs, and the proposal log for ``socid_extractor``.
Main files:
- `site-checks-playbook.md <https://github.com/soxoj/maigret/blob/main/LLM/site-checks-playbook.md>`_ — short checklist
- `site-checks-guide.md <https://github.com/soxoj/maigret/blob/main/LLM/site-checks-guide.md>`_ — detailed guide
- `socid_extractor_improvements.log <https://github.com/soxoj/maigret/blob/main/LLM/socid_extractor_improvements.log>`_ — template and entries for identity extractor improvements
These files should be kept up-to-date whenever changes are made to the check logic in the code or in ``data.json``.
.. _activation-mechanism:
Activation mechanism
--------------------
The activation mechanism helps make requests to sites requiring additional authentication like cookies, JWT tokens, or custom headers.
Let's study the Vimeo site check record from the Maigret database:
.. code-block:: json
"Vimeo": {
"tags": [
"us",
"video"
],
"headers": {
"Authorization": "jwt eyJ0..."
},
"activation": {
"url": "https://vimeo.com/_rv/viewer",
"marks": [
"Something strange occurred. Please get in touch with the app's creator."
],
"method": "vimeo"
},
"urlProbe": "https://api.vimeo.com/users/{username}?fields=name...",
"checkType": "status_code",
"alexaRank": 148,
"urlMain": "https://vimeo.com/",
"url": "https://vimeo.com/{username}",
"usernameClaimed": "blue",
"usernameUnclaimed": "noonewouldeverusethis7"
},
The activation method is:
.. code-block:: python
def vimeo(site, logger, cookies={}):
headers = dict(site.headers)
if "Authorization" in headers:
del headers["Authorization"]
import requests
r = requests.get(site.activation["url"], headers=headers)
jwt_token = r.json()["jwt"]
site.headers["Authorization"] = "jwt " + jwt_token
Here's how the activation process works when a JWT token becomes invalid:
1. The site check makes an HTTP request to ``urlProbe`` with the invalid token
2. The response contains an error message specified in the ``activation``/``marks`` field
3. When this error is detected, the ``vimeo`` activation function is triggered
4. The activation function obtains a new JWT token and updates it in the site check record
5. On the next site check (either through retry or a new Maigret run), the valid token is used and the check succeeds
Examples of activation mechanism implementation are available in `activation.py <https://github.com/soxoj/maigret/blob/main/maigret/activation.py>`_ file.
How to publish new version of Maigret
-------------------------------------
**Collaborats rights are requires, write Soxoj to get them**.
For new version publishing you must create a new branch in repository
with a bumped version number and actual changelog first. After it you
must create a release, and GitHub action automatically create a new
PyPi package.
- New branch example: https://github.com/soxoj/maigret/commit/e520418f6a25d7edacde2d73b41a8ae7c80ddf39
- Release example: https://github.com/soxoj/maigret/releases/tag/v0.4.1
1. Make a new branch locally with a new version name. Check the current version number here: https://pypi.org/project/maigret/.
**Increase only patch version (third number)** if there are no breaking changes.
.. code-block:: console
git checkout -b 0.4.0
2. Update Maigret version in four files manually. **All four must be in
sync** — the previous bump missed ``docs/source/conf.py`` and
``snapcraft.yaml`` and they fell behind by a release.
- ``pyproject.toml`` — single line ``version = "X.Y.Z"`` under
``[tool.poetry]``.
- ``maigret/__version__.py`` — single line ``__version__ = 'X.Y.Z'``.
- ``docs/source/conf.py``**two** Sphinx fields. ``release`` is the
full version (``'X.Y.Z'``); ``version`` is the short ``major.minor``
(``'X.Y'``, **without** the patch number). Update **both**.
- ``snapcraft.yaml`` — single line ``version: X.Y.Z`` (no quotes, no
``v`` prefix).
After editing, sanity-check with ``grep -rE '0\.5\.|0\.6\.|<old>'`` to
catch any straggler reference.
3. Create a new empty text section in the beginning of the file `CHANGELOG.md` with a current date:
.. code-block:: console
## [0.4.0] - 2022-01-03
4. Get auto-generate release notes:
- Open https://github.com/soxoj/maigret/releases/new
- Click `Choose a tag`, enter `v0.4.0` (your version)
- Click `Create new tag`
- Press `+ Auto-generate release notes`
- Copy all the text from description text field below
- Paste it to empty text section in `CHANGELOG.txt`
- Remove redundant lines `## What's Changed` and `## New Contributors` section if it exists
- *Close the new release page*
5. Commit all the changes, push, make pull request
.. code-block:: console
git add -p
git commit -m 'Bump to YOUR VERSION'
git push origin head
6. Merge pull request
7. Create new release
- Open https://github.com/soxoj/maigret/releases/new again
- Click `Choose a tag`
- Enter actual version in format `v0.4.0`
- Also enter actual version in the field `Release title`
- Click `Create new tag`
- Press `+ Auto-generate release notes`
- **Press "Publish release" button**
8. That's all, now you can simply wait push to PyPi. You can monitor it in Action page: https://github.com/soxoj/maigret/actions/workflows/python-publish.yml
Documentation updates
---------------------
Documentations is auto-generated and auto-deployed from the ``docs`` directory.
To manually update documentation:
1. Change something in the ``.rst`` files in the ``docs/source`` directory.
2. Install ``python -m pip install -e .`` in the docs directory.
3. Run ``make singlehtml`` in the terminal in the docs directory.
4. Open ``build/singlehtml/index.html`` in your browser to see the result.
5. If you edited any English ``.rst`` text (not just code blocks), refresh the
per-language translation catalogs — see *Translations* below. Skipping this
step lets the non-English builds silently fall back to English on the
changed strings.
6. If everything is ok, commit and push your changes to GitHub.
Translations
------------
The docs are translated via Sphinx's standard gettext workflow. English ``.rst`` files
are the source of truth; translations live as ``.po`` catalogs under
``docs/source/locale/<lang>/LC_MESSAGES/`` (currently only ``zh_CN``).
After editing any English ``.rst`` file, refresh the catalogs so existing
translations stay aligned with the new strings:
.. code-block:: bash
cd docs
make intl-update LANG=zh_CN
This regenerates the ``.pot`` files via ``sphinx-build -b gettext`` and runs
``sphinx-intl update`` to merge them into the per-language ``.po`` files. New
English strings appear with an empty ``msgstr ""``; changed strings get a
``#, fuzzy`` marker that translators should review and re-translate.
Preview a translated build locally:
.. code-block:: bash
make html-zh_CN
open build/html_zh_CN/index.html
CJK escape-spaces gotcha
^^^^^^^^^^^^^^^^^^^^^^^^
reStructuredText inline markup (bold, inline code, hyperlinks) requires
whitespace or punctuation on both sides to close. In English this is free: a
space or full stop always follows. In Chinese / Japanese / Korean translations
the next character is often a CJK letter with no separator, and docutils then
emits warnings like::
<translated>:1: WARNING: Inline strong start-string without end-string.
<translated>:1: WARNING: Inline interpreted text or phrase reference start-string without end-string.
The fix is an explicit RST escape-space — a backslash followed by a space —
between the closing marker and the next CJK character. In the rendered ``.rst``
this is written as ``\<space>``; inside a ``.po`` ``msgstr`` it must be
written as ``\\<space>`` because the ``.po`` parser eats one backslash level.
.. code-block:: po
# WRONG — warning, markup leaks past the bold
msgstr "让**所有**检查请求通过指定代理"
# RIGHT — regular space breaks markup cleanly
msgstr "让 **所有** 检查请求通过指定代理"
# RIGHT — escape-space when no visual space is wanted
msgstr "让\\ **所有**\\ 检查请求通过指定代理"
The same rule applies after inline code before a CJK character, and after a
hyperlink before a CJK opening bracket — always insert ``\\<space>``. After
editing any ``.po`` file, run ``make html-zh_CN``; these warnings only surface
at build time.
To add a new language, run ``make intl-update LANG=<code>`` (e.g. ``ja``,
``de``, ``pt_BR``) — this scaffolds an empty catalog. Read the Docs publishes
each language as a separate project linked under the parent (see the
`Localization guide <https://docs.readthedocs.com/platform/stable/localization.html>`_);
a maintainer needs to create the translation project once in the RTD admin UI,
set its language, and mark it as a translation of the main ``maigret`` project
to enable the language switcher.
Roadmap
-------
.. warning::
This roadmap requires updating to reflect the current project status and future plans.
.. figure:: https://i.imgur.com/kk8cFdR.png
:target: https://i.imgur.com/kk8cFdR.png
:align: center
+175
View File
@@ -0,0 +1,175 @@
.. _faq:
FAQ
===
Short answers to the questions users most often type into the docs
search. For deeper coverage each section links to the relevant page.
Can I search by email address?
------------------------------
**No.** Maigret only takes a username (or one of the
:doc:`supported identifier types <supported-identifier-types>`) as
input — searching by an email or mail address is out of scope.
Looking up a mail address requires different techniques (probing
password-reset flows, registration endpoints) and is the job of a
separate class of tool.
Recommended open-source tools for email lookup:
- `mailcat <https://github.com/sharsil/mailcat>`_
- `holehe <https://github.com/megadose/holehe>`_
- `user-scanner <https://github.com/kaifcodec/user-scanner>`_
Online services:
- `Noimosiny <https://noimosiny.com>`_
- `OSINT Industries <https://osint.industries>`_
- `Epieos <https://epieos.com>`_
Note: if Maigret has already found an account for a username, it often
extracts the linked email from the profile page automatically — see
:ref:`extracting-information-from-pages`.
Can I configure a proxy / SOCKS / Tor / I2P?
--------------------------------------------
**Yes.** Three flags cover three distinct goals:
- ``--proxy URL`` — route **every** check through the given HTTP or
SOCKS proxy (also the right flag for routing the whole run through Tor
with ``socks5://127.0.0.1:9050``, a residential proxy, or a corporate
gateway).
- ``--tor-proxy URL`` — used **only** for ``.onion`` sites in the
database. Clearweb sites still go via your direct connection (or
``--proxy`` if set).
- ``--i2p-proxy URL`` — same idea, only for ``.i2p`` hosts.
The most common confusion is ``--proxy`` vs ``--tor-proxy``: ``--proxy``
is "everything through this gateway", ``--tor-proxy`` is "only onion
sites through Tor".
Full walkthrough (Tor Browser vs system ``tor`` port numbers, Tails OS,
timeout / retry tuning): :doc:`tor-and-proxies`.
If your goal is actually "bypass WAF blocks / fix 403 errors", see the
*Sites fail / timeout / 403* section below — a residential proxy almost
always outperforms Tor or a VPN for that.
Can I use a VPN with Maigret?
-----------------------------
**Yes**, but ``--proxy`` is usually a better choice. A VPN works
transparently at the OS level — Maigret needs no special configuration
to use one. However:
- ``--proxy`` is per-process: it does not affect other apps and does not
leak when toggled.
- ``--proxy`` makes the egress IP visible in logs, which is useful when
diagnosing why a batch of sites returned ``UNKNOWN``.
- ``--proxy`` accepts a different value per run, so you can rotate
between residential and datacenter exits without touching system
network settings.
If a lot of sites are returning 403, the cause is almost certainly that
the VPN exit IP is on a WAF blocklist (Cloudflare, DDoS-Guard, Akamai
all blanket-block common VPN ranges). A residential proxy via
``--proxy`` is the usual fix — see the
`"Lots of sites fail / timeout / return 403" section
<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md#lots-of-sites-fail--timeout--return-403>`_
in TROUBLESHOOTING.md.
Does Maigret check domains via DNS?
-----------------------------------
**Yes, experimentally.** With ``--with-domains`` Maigret resolves a
small set of ``{username}.<tld>`` patterns through DNS (A-records) in
parallel with the normal HTTP checks. The current set is
``.ddns.net``, ``.com``, ``.pro``, ``.me``, ``.biz``, ``.email``,
``.guru`` — 7 entries in the database with ``protocol: dns``.
.. code-block:: console
maigret <username> --with-domains
The flag is marked **experimental**: DNS-only checks can flag parking
domains and catch-all wildcards as if the username were registered, so
treat hits as a lead rather than confirmation.
If your task is wider DNS reconnaissance — subdomain enumeration, WHOIS
history, typo-squatting — Maigret is the wrong tool. Established
alternatives:
- `dnstwist <https://github.com/elceef/dnstwist>`_ — typo-squatting and
look-alike domains.
- `amass <https://github.com/owasp-amass/amass>`_ /
`subfinder <https://github.com/projectdiscovery/subfinder>`_
subdomain enumeration.
- `theHarvester <https://github.com/laramies/theHarvester>`_
email / host / subdomain harvesting by domain.
Is there a Maigret Telegram bot?
--------------------------------
**Yes.** A community-maintained bot lets you run Maigret without
installing anything locally:
- Working instance: `sites.google.com/view/maigret-bot-link
<https://sites.google.com/view/maigret-bot-link>`_ (redirect — the
hosted bot may move between providers).
- Source code: `github.com/soxoj/maigret-tg-bot
<https://github.com/soxoj/maigret-tg-bot>`_.
On the question of *searching Telegram itself*: Maigret checks whether a
``t.me/<user>`` page exists as part of the normal run, but it does not
parse channels, posts, members, or message contents. For Telegram
content OSINT you need a dedicated tool.
Where is the web interface?
---------------------------
.. code-block:: console
maigret --web 5000
Then open http://127.0.0.1:5000. Screenshots and a full walkthrough are
in :ref:`web-interface`.
Sites fail / timeout / return 403 — connection failures
-------------------------------------------------------
This is the most common report and is almost always caused by anti-bot
protection (Cloudflare, DDoS-Guard, Akamai) or a slow link, not by a
bug in Maigret. Quick tweaks, in order:
1. ``--timeout 60`` — the default 30 s is tight for slow networks and
for Tor.
2. ``--retries 2`` — covers transient failures.
3. ``-n 20`` — lower concurrency reduces WAF rate-limiting.
4. ``--proxy http://user:pass@residential-proxy:port`` — datacenter IPs
(AWS, GCP, DigitalOcean) and most VPN ranges are blanket-blocked;
residential / mobile exits usually fix the bulk of 403s.
The full troubleshooting matrix (per-error recipes for 403, timeout,
SSL, captcha, ``UNKNOWN`` floods) lives in
`TROUBLESHOOTING.md
<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_.
How do I generate a PDF report?
-------------------------------
PDF support is an optional extra because it pulls heavy graphics
dependencies:
.. code-block:: console
pip install 'maigret[pdf]'
maigret <username> --pdf
On Linux / macOS you also need system libraries (Pango, Cairo,
GDK-PixBuf). Per-OS install steps are in the
*Optional: PDF reports* section of :doc:`installation`.
For other report formats (``--html``, ``--md``, ``--json``, ``--csv``,
``--txt``, ``--xmind``), see :doc:`command-line-options`.
+377
View File
@@ -0,0 +1,377 @@
.. _features:
Features
========
This is the list of Maigret features.
.. _web-interface:
Web Interface
-------------
You can run Maigret with a web interface, where you can view the graph with results and download reports of all formats on a single page.
.. image:: https://raw.githubusercontent.com/soxoj/maigret/main/static/web_interface_screenshot_start.png
:alt: Web interface: how to start
.. image:: https://raw.githubusercontent.com/soxoj/maigret/main/static/web_interface_screenshot.png
:alt: Web interface: results
Instructions:
1. Run Maigret with the ``--web`` flag and specify the port number.
.. code-block:: console
maigret --web 5000
2. Open http://127.0.0.1:5000 in your browser and enter one or more usernames to make a search.
3. Wait a bit for the search to complete and view the graph with results, the table with all accounts found, and download reports of all formats.
.. _telegram-bot:
Telegram bot
------------
A community-maintained Telegram bot lets you run Maigret without
installing anything locally.
- Working instance: `sites.google.com/view/maigret-bot-link
<https://sites.google.com/view/maigret-bot-link>`_ (redirect — the
hosted bot may move between providers).
- Source code: `github.com/soxoj/maigret-tg-bot
<https://github.com/soxoj/maigret-tg-bot>`_.
Personal info gathering
-----------------------
Maigret does the `parsing of accounts webpages and extraction <https://github.com/soxoj/socid-extractor>`_ of personal info, links to other profiles, etc.
Extracted info displayed as an additional result in CLI output and as tables in HTML and PDF reports.
Also, Maigret use found ids and usernames from links to start a recursive search.
Enabled by default, can be disabled with ``--no extracting``.
.. code-block:: text
$ python3 -m maigret soxoj --timeout 5
[-] Starting a search on top 500 sites from the Maigret database...
[!] You can run search by full list of sites with flag `-a`
[*] Checking username soxoj on:
...
[+] GitHub: https://github.com/soxoj
├─uid: 31013580
├─image: https://avatars.githubusercontent.com/u/31013580?v=4
├─created_at: 2017-08-14T17:03:07Z
├─location: Amsterdam, Netherlands
├─follower_count: 1304
├─following_count: 54
├─fullname: Soxoj
├─public_gists_count: 3
├─public_repos_count: 88
├─twitter_username: sox0j
├─bio: Head of OSINT Center of Excellence in @SocialLinks-IO
├─is_company: Social Links
└─blog_url: soxoj.com
...
Recursive search
----------------
Maigret has the ability to scan account pages for :ref:`common identifiers <supported-identifier-types>` and usernames found in links.
When people include links to their other social media accounts, Maigret can automatically detect and initiate new searches for those profiles.
Any information discovered through this process will be shown in both the command-line interface output and generated reports.
Enabled by default, can be disabled with ``--no-recursion``.
.. code-block:: text
$ python3 -m maigret soxoj --timeout 5
[-] Starting a search on top 500 sites from the Maigret database...
[!] You can run search by full list of sites with flag `-a`
[*] Checking username soxoj on:
...
[+] GitHub: https://github.com/soxoj
├─uid: 31013580
├─image: https://avatars.githubusercontent.com/u/31013580?v=4
├─created_at: 2017-08-14T17:03:07Z
├─location: Amsterdam, Netherlands
├─follower_count: 1304
├─following_count: 54
├─fullname: Soxoj
├─public_gists_count: 3
├─public_repos_count: 88
├─twitter_username: sox0j <===== another username found here
├─bio: Head of OSINT Center of Excellence in @SocialLinks-IO
├─is_company: Social Links
└─blog_url: soxoj.com
...
Searching |████████████████████████████████████████| 500/500 [100%] in 9.1s (54.85/s)
[-] You can see detailed site check errors with a flag `--print-errors`
[*] Checking username sox0j on:
[+] Telegram: https://t.me/sox0j
├─fullname: @Sox0j
...
Username permutations
---------------------
Maigret can generate permutations of usernames. Just pass a few usernames in the CLI and use ``--permute`` flag.
Thanks to `@balestek <https://github.com/balestek>`_ for the idea and implementation.
.. code-block:: text
$ python3 -m maigret --permute hope dream --timeout 5
[-] 12 permutations from hope dream to check...
├─ hopedream
├─ _hopedream
├─ hopedream_
├─ hope_dream
├─ hope-dream
├─ hope.dream
├─ dreamhope
├─ _dreamhope
├─ dreamhope_
├─ dream_hope
├─ dream-hope
└─ dream.hope
[-] Starting a search on top 500 sites from the Maigret database...
[!] You can run search by full list of sites with flag `-a`
[*] Checking username hopedream on:
...
Reports
-------
Maigret currently supports HTML, PDF, TXT, CSV, XMind 8 mindmap, JSON, and
Markdown reports, plus graph exports: an interactive HTML graph (``--graph``)
and a Neo4j Cypher script (``--neo4j``) for loading the results into a graph
database.
HTML/PDF reports contain:
- profile photo
- all the gathered personal info
- additional information about supposed personal data (full name, gender, location), resulting from statistics of all found accounts
Also, there is a short text report in the CLI output after the end of a searching phase.
The ``--neo4j`` flag serialises the same graph that ``--graph`` builds into an
idempotent ``.cypher`` script, importable with ``cypher-shell`` or the Neo4j
Browser. See :ref:`neo4j-export` for the schema and import instructions.
.. warning::
XMind 8 mindmaps are incompatible with XMind 2022!
AI analysis
-----------
Maigret can produce a short, human-readable investigation summary on top
of the raw search results using the ``--ai`` flag. It builds the
internal Markdown report, sends it to an OpenAI-compatible chat
completion endpoint, and streams the model's reply directly to the
terminal.
.. code-block:: console
export OPENAI_API_KEY=sk-...
maigret username --ai
The summary uses a fixed format with the most likely real name,
location, occupation, interests, languages, main website, username
variants, number of platforms, active years, a confidence rating, and a
short list of follow-up leads. While ``--ai`` is active, per-site
progress and the short text report are suppressed so the streamed
summary is the main output.
The endpoint, model, and API key are configured via ``settings.json``
(``openai_api_key``, ``openai_model``, ``openai_api_base_url``) or the
``OPENAI_API_KEY`` environment variable. Any OpenAI-compatible API can
be used (Azure OpenAI, OpenRouter, a local server, …). See
:ref:`ai-analysis` and :ref:`settings` for details.
Tags
----
The Maigret sites database very big (and will be bigger), and it is maybe an overhead to run a search for all the sites.
Also, it is often hard to understand, what sites more interesting for us in the case of a certain person.
Tags markup allows selecting a subset of sites by interests (photo, messaging, finance, etc.) or by country. Tags of found accounts grouped and displayed in the reports.
See full description :doc:`in the Tags Wiki page <tags>`.
Censorship and captcha detection
--------------------------------
Maigret can detect common errors such as censorship stub pages, CloudFlare captcha pages, and others.
If you get more them 3% errors of a certain type in a session, you've got a warning message in the CLI output with recommendations to improve performance and avoid problems.
Retries
-------
Maigret will do retries of the requests with temporary errors got (connection failures, proxy errors, etc.).
One attempt by default, can be changed with option ``--retries N``.
Database self-check
-------------------
Maigret includes a self-check mode (``--self-check``) that validates every site
in the database by looking up its known-claimed and known-unclaimed usernames
and verifying that the detection results match expectations.
The self-check is **error-resilient**: if an individual site check raises an
unexpected exception (e.g. a network error or a parsing failure), the error is
caught, logged, and recorded as an issue — the remaining sites continue to be
checked without interruption. This means the process always runs to completion,
even when checking hundreds of sites with ``-a --self-check``.
Use ``--auto-disable`` together with ``--self-check`` to automatically disable
sites that fail checks. Without it, issues are only reported. Use ``--diagnose``
to print detailed per-site diagnosis including the check type, specific issues,
and recommendations.
.. code-block:: console
# Report-only mode (no changes to the database)
maigret --self-check
# Automatically disable failing sites and save updates
maigret -a --self-check --auto-disable
# Show detailed diagnosis for each failing site
maigret -a --self-check --diagnose
Archives and mirrors checking
-----------------------------
The Maigret database contains not only the original websites, but also mirrors, archives, and aggregators. For example:
- `Picuki <https://www.picuki.com/>`_, Instagram mirror
- (no longer available) `Reddit BigData search <https://camas.github.io/reddit-search/>`_
- (no longer available) `Twitter shadowban <https://shadowban.eu/>`_ checker
It allows getting additional info about the person and checking the existence of the account even if the main site is unavailable (bot protection, captcha, etc.)
.. _cloudflare-bypass:
Cloudflare webgate bypass
-------------------------
.. warning::
**Experimental feature.** The Cloudflare webgate is under active
development. The configuration schema, CLI flag behaviour, and the set
of sites that route through it may change without backwards-compatibility
guarantees. Expect rough edges (CF rate limits, occasional solver
failures) and report issues so they can be ironed out.
Some sites sit behind a full Cloudflare JavaScript challenge or a CF firewall
hard block — these are tagged ``protection: ["cf_js_challenge"]`` or
``protection: ["cf_firewall"]`` in the database and are normally kept disabled
because neither aiohttp nor curl_cffi can solve the JS challenge on their own.
Maigret can offload these checks to a local Chrome-based solver. Two backends
are supported, configured in ``settings.json`` under
``cloudflare_bypass.modules`` (the first reachable module wins; subsequent
ones are tried as a fallback chain):
* **FlareSolverr** (recommended). Runs a real Chrome instance and exposes a
JSON API. The upstream HTTP status, headers and final URL are preserved, so
``checkType: status_code`` and ``checkType: response_url`` keep working
through the bypass.
.. code-block:: console
docker run -d -p 8191:8191 --name flaresolverr ghcr.io/flaresolverr/flaresolverr:latest
* **CloudflareBypassForScraping** (legacy fallback). Returns rendered HTML
only, so the upstream status code is lost — ``checkType: message`` keeps
working but ``status_code`` checks misfire (treated as 200 on success).
Activate the bypass either with the CLI flag::
maigret --cloudflare-bypass <username>
or by setting ``cloudflare_bypass.enabled`` to ``true`` in ``settings.json``.
The web UI (``python -m maigret.web.app``) reads the same setting — there is
no separate toggle in the form, so flipping ``enabled`` is what activates
the bypass for browser-driven runs.
The bypass only fires for sites whose ``protection`` field intersects
``cloudflare_bypass.trigger_protection`` (default
``["cf_js_challenge", "cf_firewall", "webgate"]``); all other sites use the
normal aiohttp / curl_cffi path.
If all configured modules are unreachable, affected sites get an UNKNOWN
status with an actionable error pointing at the first module's URL — the
fix is almost always to start the FlareSolverr container.
FlareSolverr session reuse is automatic: Maigret pins a single
``session: <session_prefix>-<pid>`` per run, so cf_clearance cookies are
shared between checks of the same domain (510× faster on subsequent
requests to that host).
Activation
----------
The activation mechanism helps make requests to sites requiring additional authentication like cookies, JWT tokens, or custom headers.
It works by implementing a custom function that:
1. Makes a specialized HTTP request to a specific website endpoint
2. Processes the response
3. Updates the headers/cookies for that site in the local Maigret database
Since activation only triggers after encountering specific errors, a retry (or another Maigret run) is needed to obtain a valid response with the updated authentication.
The activation mechanism is enabled by default, and cannot be disabled at the moment.
See for more details in Development section :ref:`activation-mechanism`.
.. _extracting-information-from-pages:
Extraction of information from account pages
--------------------------------------------
Maigret can parse URLs and content of web pages by URLs to extract info about account owner and other meta information.
You must specify the URL with the option ``--parse``, it's can be a link to an account or an online document. List of supported sites `see here <https://github.com/soxoj/socid-extractor#sites>`_.
After the end of the parsing phase, Maigret will start the search phase by :doc:`supported identifiers <supported-identifier-types>` found (usernames, ids, etc.).
.. code-block:: console
$ maigret --parse https://docs.google.com/spreadsheets/d/1HtZKMLRXNsZ0HjtBmo0Gi03nUPiJIA4CC4jTYbCAnXw/edit\#gid\=0
Scanning webpage by URL https://docs.google.com/spreadsheets/d/1HtZKMLRXNsZ0HjtBmo0Gi03nUPiJIA4CC4jTYbCAnXw/edit#gid=0...
┣╸org_name: Gooten
┗╸mime_type: application/vnd.google-apps.ritz
Scanning webpage by URL https://clients6.google.com/drive/v2beta/files/1HtZKMLRXNsZ0HjtBmo0Gi03nUPiJIA4CC4jTYbCAnXw?fields=alternateLink%2CcopyRequiresWriterPermission%2CcreatedDate%2Cdescription%2CdriveId%2CfileSize%2CiconLink%2Cid%2Clabels(starred%2C%20trashed)%2ClastViewedByMeDate%2CmodifiedDate%2Cshared%2CteamDriveId%2CuserPermission(id%2Cname%2CemailAddress%2Cdomain%2Crole%2CadditionalRoles%2CphotoLink%2Ctype%2CwithLink)%2Cpermissions(id%2Cname%2CemailAddress%2Cdomain%2Crole%2CadditionalRoles%2CphotoLink%2Ctype%2CwithLink)%2Cparents(id)%2Ccapabilities(canMoveItemWithinDrive%2CcanMoveItemOutOfDrive%2CcanMoveItemOutOfTeamDrive%2CcanAddChildren%2CcanEdit%2CcanDownload%2CcanComment%2CcanMoveChildrenWithinDrive%2CcanRename%2CcanRemoveChildren%2CcanMoveItemIntoTeamDrive)%2Ckind&supportsTeamDrives=true&enforceSingleParent=true&key=AIzaSyC1eQ1xj69IdTMeii5r7brs3R90eck-m7k...
┣╸created_at: 2016-02-16T18:51:52.021Z
┣╸updated_at: 2019-10-23T17:15:47.157Z
┣╸gaia_id: 15696155517366416778
┣╸fullname: Nadia Burgess
┣╸email: nadia@gooten.com
┣╸image: https://lh3.googleusercontent.com/a-/AOh14GheZe1CyNa3NeJInWAl70qkip4oJ7qLsD8vDy6X=s64
┗╸email_username: nadia
.. code-block:: console
$ maigret.py --parse https://steamcommunity.com/profiles/76561199113454789
Scanning webpage by URL https://steamcommunity.com/profiles/76561199113454789...
┣╸steam_id: 76561199113454789
┣╸nickname: Pok
┗╸username: Machine42
Simple API
----------
Maigret can be easily integrated with the use of Python package `maigret <https://pypi.org/project/maigret/>`_.
Example: the community `Telegram bot <https://github.com/soxoj/maigret-tg-bot>`_
+64
View File
@@ -0,0 +1,64 @@
.. _index:
Welcome to the Maigret docs!
============================
**Maigret** is an easy-to-use and powerful OSINT tool for collecting a dossier on a person by a username (alias) only.
This is achieved by checking for accounts on a huge number of sites and gathering all the available information from web pages.
The project's main goal — give to OSINT researchers and pentesters a **universal tool** to get maximum information
about a person of interest by a username and integrate it with other tools in automatization pipelines.
.. warning::
**This tool is intended for educational and lawful purposes only.**
The developers do not endorse or encourage any illegal activities or misuse of this tool.
Regulations regarding the collection and use of personal data vary by country and region,
including but not limited to GDPR in the EU, CCPA in the USA, and similar laws worldwide.
It is your sole responsibility to ensure that your use of this tool complies with all applicable laws
and regulations in your jurisdiction. Any illegal use of this tool is strictly prohibited,
and you are fully accountable for your actions.
The authors and developers of this tool bear no responsibility for any misuse
or unlawful activities conducted by its users.
You may be interested in:
-------------------------
- :doc:`Quick start <quick-start>`
- :doc:`Usage examples <usage-examples>`
- :doc:`FAQ <faq>`
- :doc:`Command line options <command-line-options>`
- :doc:`Features list <features>`
- :doc:`Library usage <library-usage>`
- :doc:`Tor, I2P, and proxies <tor-and-proxies>`
.. toctree::
:hidden:
:caption: Sections
quick-start
installation
usage-examples
faq
command-line-options
features
philosophy
supported-identifier-types
tags
development
.. toctree::
:hidden:
:caption: Advanced usage
library-usage
settings
tor-and-proxies
.. toctree::
:hidden:
:caption: Use cases
use-cases/crypto
use-cases/scientists
+327
View File
@@ -0,0 +1,327 @@
.. _installation:
Installation
============
Maigret can be installed using pip, Docker, or simply can be launched from the cloned repo.
Also, it is available online via the `community Telegram bot <https://sites.google.com/view/maigret-bot-link>`_,
source code of a bot is `available on GitHub <https://github.com/soxoj/maigret-tg-bot>`_.
Windows Standalone EXE-binaries
-------------------------------
A standalone ``maigret_standalone.exe`` for Windows is published in the
`Releases section <https://github.com/soxoj/maigret/releases>`_ of the GitHub
repository. A fresh build is produced automatically after each commit to the
**main** and **dev** branches.
There are two ways to launch the EXE:
* **Double-click it from Explorer.** Maigret will prompt you for a username,
run a default search, and pause at the end so the printed report links
remain on screen until you press Enter.
* **Run it from a terminal** for full control over options:
1. Press ``Win+R``, type ``cmd``, and hit Enter (or use PowerShell).
2. Change to the folder where you saved the file, e.g.
``cd %USERPROFILE%\Downloads``.
3. Run it with at least one username:
.. code-block:: bat
maigret_standalone.exe USERNAME
maigret_standalone.exe USERNAME --html :: also save an HTML report
maigret_standalone.exe USERNAME --pdf :: also save a PDF report
maigret_standalone.exe --help :: list all options
Reports are written next to the EXE in a ``reports\`` subfolder.
Video guide on how to run it: https://youtu.be/qIgwTZOmMmM.
Cloud Shells and Jupyter notebooks
----------------------------------
In case you don't want to install Maigret locally, you can use cloud shells and Jupyter notebooks.
Press one of the buttons below and follow the instructions to launch it in your browser.
.. only:: html
.. image:: https://user-images.githubusercontent.com/27065646/92304704-8d146d80-ef80-11ea-8c29-0deaabb1c702.png
:target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/soxoj/maigret&tutorial=README.md
:alt: Open in Cloud Shell
.. image:: https://replit.com/badge/github/soxoj/maigret
:target: https://repl.it/github/soxoj/maigret
:alt: Run on Replit
:height: 50
.. image:: https://colab.research.google.com/assets/colab-badge.svg
:target: https://colab.research.google.com/gist/soxoj/879b51bc3b2f8b695abb054090645000/maigret-collab.ipynb
:alt: Open In Colab
:height: 45
.. image:: https://mybinder.org/badge_logo.svg
:target: https://mybinder.org/v2/gist/soxoj/9d65c2f4d3bec5dd25949197ea73cf3a/HEAD
:alt: Open In Binder
:height: 45
.. only:: latex
Cloud Shell: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/soxoj/maigret&tutorial=README.md
Replit: https://repl.it/github/soxoj/maigret
Google Colab: https://colab.research.google.com/gist/soxoj/879b51bc3b2f8b695abb054090645000/maigret-collab.ipynb
Binder: https://mybinder.org/v2/gist/soxoj/9d65c2f4d3bec5dd25949197ea73cf3a/HEAD
Local installation from PyPi
----------------------------
Maigret ships with a bundled site database. After installation from PyPI (or any other method), it can **automatically fetch a newer compatible database from GitHub** when you run it—see :ref:`database-auto-update` in :doc:`settings`.
.. note::
Python 3.10 or higher and pip is required, **Python 3.11 is recommended.**
.. code-block:: bash
# install from pypi
pip3 install maigret
# usage
maigret username
PDF report support is shipped as an **optional extra** because it relies on
system-level graphics libraries that pip cannot install for you. If you plan to
use ``--pdf``, install Maigret with the ``pdf`` extra:
.. code-block:: bash
pip3 install 'maigret[pdf]'
See :ref:`pdf-extra` below for the full background on why PDF support is
optional and how to fix the most common build errors.
Development version (GitHub)
----------------------------
.. code-block:: bash
git clone https://github.com/soxoj/maigret && cd maigret
pip3 install .
# OR
pip3 install git+https://github.com/soxoj/maigret.git
# usage
maigret username
# OR use poetry in case you plan to develop Maigret
pip3 install poetry
poetry run maigret
Docker
------
.. code-block:: bash
# official image of the development version, updated from the github repo
docker pull soxoj/maigret
# usage
docker run -v /mydir:/app/reports soxoj/maigret:latest username --html
# manual build
docker build -t maigret .
Troubleshooting
---------------
If you encounter build errors during installation such as ``cannot find ft2build.h``
or errors related to ``reportlab`` / ``_renderPM``, you need to install system-level
dependencies required to compile native extensions.
**Debian/Ubuntu/Kali:**
.. code-block:: bash
sudo apt install -y libfreetype6-dev libjpeg-dev libffi-dev
**Fedora/RHEL/CentOS:**
.. code-block:: bash
sudo dnf install -y freetype-devel libjpeg-devel libffi-devel
**Arch Linux:**
.. code-block:: bash
sudo pacman -S freetype2 libjpeg-turbo libffi
**macOS (Homebrew):**
.. code-block:: bash
brew install freetype
After installing the system dependencies, retry the maigret installation.
If you continue to have issues, consider using Docker instead, which includes all
necessary dependencies.
.. _pdf-extra:
Optional: PDF reports (``maigret[pdf]``)
----------------------------------------
The ``--pdf`` report format is shipped as an optional extra. To enable it:
.. code-block:: bash
pip3 install 'maigret[pdf]'
If PDF support is not installed and you pass ``--pdf``, Maigret prints a
warning and continues without crashing — every other output format
(``--html``, ``--json``, ``--csv``, ``--txt``, ``--xmind``, ``--graph``)
keeps working.
Why is PDF optional?
~~~~~~~~~~~~~~~~~~~~
Maigret renders PDFs by converting an HTML template, and that conversion
pipeline ultimately depends on the ``cairo`` graphics library through a
chain of Python packages roughly shaped like::
maigret[pdf] → xhtml2pdf → svglib → rlPyCairo → pycairo → libcairo2 (system)
The bottom of that chain is a C library — ``libcairo2`` — that has to exist
on the host *before* pip can build the Python bindings. The Python binding
package (``pycairo``) currently ships **only Windows wheels** on PyPI; on
Linux and macOS pip falls back to building from source, and the build fails
the moment ``pkg-config`` cannot find ``cairo``. The error looks like::
../cairo/meson.build:31:12: ERROR: Dependency "cairo" not found (tried pkg-config)
note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed
Pulling this whole chain for every Maigret install just so the much smaller
group of users who actually want PDFs can have them is a poor trade — so
``xhtml2pdf`` is gated behind the ``pdf`` extra.
Two more packages — ``arabic-reshaper`` and ``python-bidi`` — are bundled
into the same extra. Maigret core never imports them; they are only used
by ``xhtml2pdf`` to shape Arabic glyphs and lay out right-to-left text in
PDFs. ``python-bidi`` v0.5+ is also a Rust binding, so on niche platforms
without a published wheel it would otherwise pull in a Cargo build for
users who never asked for PDF support.
Installing the system prerequisites
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Install the cairo headers, ``pkg-config``, and a working C toolchain
*before* running ``pip install 'maigret[pdf]'``.
**Debian / Ubuntu / Linux Mint / Kali:**
.. code-block:: bash
sudo apt update
sudo apt install -y libcairo2-dev pkg-config python3-dev build-essential
pip3 install --upgrade pip setuptools wheel
pip3 install 'maigret[pdf]'
**Fedora / RHEL / CentOS:**
.. code-block:: bash
sudo dnf install -y cairo-devel pkgconfig python3-devel gcc
pip3 install 'maigret[pdf]'
**Arch Linux:**
.. code-block:: bash
sudo pacman -S cairo pkgconf base-devel
pip3 install 'maigret[pdf]'
**Alpine Linux:**
.. code-block:: bash
sudo apk add cairo-dev pkgconf python3-dev build-base
pip3 install 'maigret[pdf]'
**macOS (Homebrew):**
.. code-block:: bash
brew install cairo pkg-config
pip3 install --upgrade pip setuptools wheel
pip3 install 'maigret[pdf]'
**Windows:**
No system packages are needed — ``pycairo`` ships prebuilt wheels for
Windows. Just run:
.. code-block:: bash
pip install 'maigret[pdf]'
**Google Cloud Shell / Colab / Replit / generic CI:**
These environments behave like Debian/Ubuntu — install the same
``libcairo2-dev pkg-config python3-dev build-essential`` triple before
``pip install 'maigret[pdf]'``. If you do not control the base image and
cannot ``apt install``, skip the extra and use ``--html`` reports instead;
HTML reports contain the same data and open in any browser.
``maigret: command not found`` after install
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If pip prints warnings like::
WARNING: The scripts maigret and update_sitesmd are installed in
'/home/<user>/.local/bin' which is not on PATH.
…and ``maigret --version`` then fails with ``command not found``, your
``--user`` install put the entry-point script in a directory the shell does
not search. Add it to ``PATH``:
.. code-block:: bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Or install into a virtual environment, where the entry point lands in the
venv's ``bin/`` automatically:
.. code-block:: bash
python3 -m venv ~/.venvs/maigret
source ~/.venvs/maigret/bin/activate
pip install 'maigret[pdf]' # or just `pip install maigret`
Optional: Cloudflare bypass solver
----------------------------------
.. warning::
**Experimental.** The Cloudflare webgate is under active development;
the configuration schema and CLI behaviour may change without
backwards-compatibility guarantees.
Sites tagged ``cf_js_challenge`` / ``cf_firewall`` need a real browser to pass
their JavaScript challenge. To check those sites you can run a local
`FlareSolverr <https://github.com/FlareSolverr/FlareSolverr>`_ instance —
Maigret will route protected checks to it when ``--cloudflare-bypass`` is set:
.. code-block:: bash
docker run -d -p 8191:8191 --name flaresolverr ghcr.io/flaresolverr/flaresolverr:latest
This is **optional** — Maigret runs without it; only sites whose
``protection`` field intersects ``settings.cloudflare_bypass.trigger_protection``
require the solver. See :ref:`cloudflare-bypass` for details.
+139
View File
@@ -0,0 +1,139 @@
.. _library-usage:
Library usage
=============
Maigret's CLI is a thin wrapper around an async Python API. You can embed Maigret in your own tools, pipelines, and OSINT workflows — no need to shell out.
This page covers the common patterns. For the full argument list of the underlying function, see ``maigret.checking.maigret`` in the source.
Installation
------------
.. code-block:: bash
pip install maigret
Minimal example
---------------
A working end-to-end search against the top 500 sites:
.. code-block:: python
import asyncio
import logging
from maigret import search as maigret_search
from maigret.sites import MaigretDatabase
# Load the bundled site database
db = MaigretDatabase().load_from_path(
"maigret/resources/data.json"
)
# Pick which sites to scan (same filtering the CLI uses)
sites = db.ranked_sites_dict(top=500)
results = asyncio.run(
maigret_search(
username="soxoj",
site_dict=sites,
logger=logging.getLogger("maigret"),
timeout=30,
is_parsing_enabled=True,
)
)
for site_name, result in results.items():
if result["status"].is_found():
print(site_name, result["url_user"])
Key points:
- ``maigret_search`` is an ``async`` function — wrap it with ``asyncio.run(...)`` or ``await`` it from inside your own event loop.
- ``is_parsing_enabled=True`` turns on ``socid_extractor`` so ``result["ids_data"]`` is populated with profile fields (bio, linked accounts, uids, etc.).
- Each entry in the returned dict has a ``"status"`` object with ``is_found()``, plus ``url_user``, ``http_status``, ``rank``, ``ids_data``, and more.
Filtering sites
---------------
``ranked_sites_dict`` accepts the same filters as the CLI:
.. code-block:: python
# All sites tagged as coding, top 200 by rank
sites = db.ranked_sites_dict(top=200, tags=["coding"])
# Exclude NSFW and dating sites
sites = db.ranked_sites_dict(excluded_tags=["nsfw", "dating"])
# Only specific sites by name
sites = db.ranked_sites_dict(names=["GitHub", "Reddit", "VK"])
# Include disabled sites (useful for maintenance / self-check)
sites = db.ranked_sites_dict(disabled=True)
Running inside an existing event loop
-------------------------------------
If your application already runs an asyncio loop (FastAPI, aiohttp server, a Discord bot, etc.), ``await`` ``maigret_search`` directly instead of calling ``asyncio.run``:
.. code-block:: python
async def check_username(username: str) -> dict:
results = await maigret_search(
username=username,
site_dict=sites,
logger=logger,
timeout=30,
)
return {
name: r["url_user"]
for name, r in results.items()
if r["status"].is_found()
}
Routing through a proxy
-----------------------
The same proxy / Tor / I2P flags the CLI exposes are plain keyword arguments:
.. code-block:: python
results = await maigret_search(
username="soxoj",
site_dict=sites,
logger=logger,
proxy="socks5://127.0.0.1:1080",
tor_proxy="socks5://127.0.0.1:9050", # used for .onion sites
i2p_proxy="http://127.0.0.1:4444", # used for .i2p sites
timeout=30,
)
Full function signature
-----------------------
.. code-block:: python
async def maigret(
username: str,
site_dict: Dict[str, MaigretSite],
logger,
query_notify=None,
proxy=None,
tor_proxy=None,
i2p_proxy=None,
timeout=30,
is_parsing_enabled=False,
id_type="username",
debug=False,
forced=False,
max_connections=100,
no_progressbar=False,
cookies=None,
retries=0,
check_domains=False,
) -> Dict[str, Any]
See :doc:`command-line-options` for a description of each option — the semantics match the CLI flags one-to-one.
@@ -0,0 +1,736 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-23 09:20+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/command-line-options.rst:4 f407954fa4604408bcf616f2c4d9e546
msgid "Command line options"
msgstr "命令行选项"
#: ../../source/command-line-options.rst:7 de65474ed32040cdb6f5df32b44ae67b
msgid "Usernames"
msgstr "用户名"
#: ../../source/command-line-options.rst:9 f1f5dd56badc42a1a7f9adac620981d5
msgid "``maigret username1 username2 ...``"
msgstr "``maigret username1 username2 ...``"
#: ../../source/command-line-options.rst:11 2e481c46cdaa43308c4b92f573e94ba3
msgid ""
"You can specify several usernames separated by space. Usernames are "
"**not** mandatory as there are other operations modes (see below)."
msgstr "可以用空格分隔传入多个用户名。\\ **不一定**\\ 必须传入用户名 —— 还有其它运行模式(见下文)。"
#: ../../source/command-line-options.rst:15 bbc3f676aab74709a36b1537a3b1846d
msgid "Parsing of account pages and online documents"
msgstr "解析账号页面与在线文档"
#: ../../source/command-line-options.rst:17 08808b412866468e94f13dcee1f1b196
msgid "``maigret --parse URL``"
msgstr "``maigret --parse URL``"
#: ../../source/command-line-options.rst:19 579f2cee066c4dd085fad1ef0df68324
msgid ""
"Maigret will try to extract information about the document/account owner "
"(including username and other ids) and will make a search by the "
"extracted username and ids. See examples in the :ref:`extracting-"
"information-from-pages` section."
msgstr ""
"Maigret 会尝试抽取该文档/账号所有者的信息(包括用户名和其它 ID),并以抽取到的用户名和 ID 进行搜索。示例见 :ref"
":`extracting-information-from-pages` 一节。"
#: ../../source/command-line-options.rst:24 18e92dec39484389b324a50823b4c0ad
msgid "Main options"
msgstr "主要选项"
#: ../../source/command-line-options.rst:26 6357b34278fc4546a57d7cb38f712a69
msgid ""
"Options are also configurable through settings files, see :doc:`settings "
"section <settings>`."
msgstr "各项选项也可以通过配置文件设置,参见 :doc:`配置一节 <settings>`\\ 。"
#: ../../source/command-line-options.rst:29 44f82332afe24dfb95c0bdf178cb9ba2
msgid ""
"``--tags`` - Filter sites for searching by tags: sites categories and "
"two-letter country codes (**not a language!**). E.g. photo, dating, "
"sport; jp, us, global. Multiple tags can be associated with one site. "
"**Warning**: tags markup is not stable now. Read more :doc:`in the "
"separate section <tags>`."
msgstr ""
"``--tags`` —— 按标签筛选搜索站点:站点分类,或两位国家代码(**不是语言代码!**)。例如 "
"photo、dating、sport;jp、us、global。一个站点可以关联多个标签。\\ **注意**:标签体系目前尚未稳定。详见 "
":doc:`独立章节 <tags>`\\ 。"
#: ../../source/command-line-options.rst:34 ca25af40db7b429f8ad2174805c4594e
msgid ""
"``--exclude-tags`` - Exclude sites with specific tags from the search "
"(blacklist). E.g. ``--exclude-tags porn,dating`` will skip all sites "
"tagged with ``porn`` or ``dating``. Can be combined with ``--tags`` to "
"include certain categories while excluding others. Read more :doc:`in the"
" separate section <tags>`."
msgstr ""
"``--exclude-tags`` —— 从搜索中排除带有特定标签的站点(黑名单)。例如 ``--exclude-tags "
"porn,dating`` 会跳过所有打上 ``porn`` 或 ``dating`` 标签的站点。可与 ``--tags`` "
"组合使用,在包含某些类别的同时排除另一些。详见 :doc:`独立章节 <tags>`\\ 。"
#: ../../source/command-line-options.rst:40 dfdcfacf726340a9bf52b29b42bf3fd9
msgid ""
"``-n``, ``--max-connections`` - Allowed number of concurrent connections "
"**(default: 100)**."
msgstr "``-n``\\ 、\\ ``--max-connections`` —— 允许的并发连接数 **(默认:100)**\\ 。"
#: ../../source/command-line-options.rst:43 dcc9b5cfae384a6eb20182e90a0a1ddf
msgid "``-a``, ``--all-sites`` - Use all sites for scan **(default: top 500)**."
msgstr "``-a``\\ 、\\ ``--all-sites`` —— 扫描全部站点 **(默认仅前 500 名)**\\ 。"
#: ../../source/command-line-options.rst:45 e2223531796c4faea69f91fcb27a04c3
msgid ""
"``--top-sites`` - Count of sites for scan ranked by Majestic Million "
"**(default: top 500)**."
msgstr "``--top-sites`` —— 按 Majestic Million 排名扫描站点的数量 **(默认:前 500 名)**\\ 。"
#: ../../source/command-line-options.rst:48 a3fc16e927d04d7288dcd2fff85165b6
msgid ""
"**Mirrors:** After the top *N* sites by Majestic Million rank are chosen "
"(respecting ``--tags``, ``--use-disabled-sites``, etc.), Maigret may add "
"extra sites whose database field ``source`` names a **parent platform** "
"that itself falls in the Majestic Million top *N* when ranking "
"**including disabled** sites. For example, if ``Twitter`` ranks in the "
"first 500 by Majestic Million, a mirror such as ``memory.lol`` (with "
"``source: Twitter``) is included even though it has no rank and would "
"otherwise be cut off. The same applies to Instagram-related mirrors (e.g."
" Picuki) when ``Instagram`` is in that parent top *N* by rank—even if the"
" official ``Instagram`` entry is disabled and not scanned by default, its"
" mirrors can still be pulled in. The final list is the ranked top *N* "
"plus these mirrors (no fixed upper bound on mirror count)."
msgstr ""
"**镜像站:** 在按 Majestic Million 排名选出前 *N* 个站点(同时考虑 ``--tags``\\ 、\\ ``--use-"
"disabled-sites`` 等)之后,Maigret 还可能额外加入一些站点 —— 它们的数据库字段 ``source`` 指向某个\\ "
"**父平台**,且该父平台(在\\ **包含已禁用站点**\\ 进行排名时)落在 Majestic Million 的前 *N* "
"名内。例如,如果\\ ``Twitter`` 在 Majestic Million 前 500 名内,即便 ``memory.lol`` "
"这样的镜像(``source: Twitter``)本身没有排名、按规则会被剔除,也仍会被加入。Instagram相关镜像(如 "
"Picuki)同理:即便官方的 ``Instagram`` 条目被禁用、默认不被扫描,只要 ``Instagram`` 在父级排名的前 *N* "
"名内,其镜像就仍可能被纳入。最终的站点列表 = 排名前 *N* + 这些镜像(镜像数量没有固定上限)。"
#: ../../source/command-line-options.rst:60 88df9b7417d34d8693e526309d34ab4b
msgid ""
"``--timeout`` - Time (in seconds) to wait for responses from sites "
"**(default: 30)**. A longer timeout will be more likely to get results "
"from slow sites. On the other hand, this may cause a long delay to gather"
" all results. The choice of the right timeout should be carried out "
"taking into account the bandwidth of the Internet connection."
msgstr ""
"``--timeout`` —— 等待站点响应的时长,单位为秒 **(默认:30)**\\ "
"。超时设得越长,越有机会从慢站点拿到结果;另一方面,这也会拉长整体结果汇总的时间。具体取值应结合自身网络带宽来权衡。"
#: ../../source/command-line-options.rst:67 eefa478db1c04aa3850a7fc3d3630447
msgid "Network and proxy options"
msgstr "网络与代理选项"
#: ../../source/command-line-options.rst:69 acd45006cd324eb9ba01dcc6468d5379
msgid ""
"``--proxy PROXY_URL`` / ``-p PROXY_URL`` - Route **every** check through "
"the given HTTP or SOCKS proxy. Example: ``socks5://127.0.0.1:1080``, "
"``http://user:pass@proxy.example:3128``. This is the flag to use for "
"routing the whole run through Tor (``--proxy socks5://127.0.0.1:9050``), "
"a residential proxy, or any corporate gateway. No default."
msgstr ""
"``--proxy PROXY_URL`` / ``-p PROXY_URL`` —— 让\\ **所有**\\ 检查都通过指定的 HTTP 或 "
"SOCKS 代理。示例:``socks5://127.0.0.1:1080``\\ 、\\ "
"``http://user:pass@proxy.example:3128``\\ 。这是把整次运行都走 Tor(``--proxy "
"socks5://127.0.0.1:9050``)、住宅代理或企业网关时应当使用的参数。无默认值。"
#: ../../source/command-line-options.rst:75 cd34c112c42e4fe1b40dddc08407f4a6
msgid ""
"``--tor-proxy TOR_PROXY_URL`` - Gateway used **only** for ``.onion`` "
"sites in the database **(default: socks5://127.0.0.1:9050)**. Clearweb "
"sites are unaffected — for them Maigret uses your direct connection or "
"``--proxy`` if you set one. Without this flag, ``.onion`` sites are "
"silently skipped."
msgstr ""
"``--tor-proxy TOR_PROXY_URL`` —— **仅**\\ 用于数据库中 ``.onion`` 站点的网关 "
"**(默认:socks5://127.0.0.1:9050)**\\ 。明网站点不受影响 —— 对它们,Maigret "
"会使用你本机的直连,或者你设置的 ``--proxy``\\ 。不传该参数时,\\ ``.onion`` 站点会被静默跳过。"
#: ../../source/command-line-options.rst:81 ec07be13427d47e6b701fa3c73d821b8
msgid ""
"``--i2p-proxy I2P_PROXY_URL`` - Gateway used **only** for ``.i2p`` sites "
"in the database **(default: http://127.0.0.1:4444)**. Same \"only "
"matching protocol\" rule as ``--tor-proxy``."
msgstr ""
"``--i2p-proxy I2P_PROXY_URL`` —— **仅**\\ 用于数据库中 ``.i2p`` 站点的网关 "
"**(默认:http://127.0.0.1:4444)**\\ 。规则与 ``--tor-proxy`` 一致 —— 只对匹配的协议生效。"
#: ../../source/command-line-options.rst:85 3883a2b23935446bb4fc52ad1c3ed69a
msgid ""
"Maigret does not start the Tor or I2P daemon for you — launch it first. "
"For a full walkthrough (Tor Browser vs system ``tor`` port numbers, Tails"
" OS recipe, timeout/retry tuning), see :doc:`tor-and-proxies`."
msgstr ""
"Maigret 不会替你启动 Tor 或 I2P 守护进程 —— 请先把它们跑起来。完整指南(包括 Tor 浏览器与系统 ``tor`` "
"端口的差别、Tails 操作系统下的配方、超时/重试调优等)请参见 :doc:`tor-and-proxies`\\ 。"
#: ../../source/command-line-options.rst:89 e320ba07df8c4de6a4fcf19dc81a54a1
msgid ""
"``--cookies-jar-file`` - File with custom cookies in Netscape format (aka"
" cookies.txt). You can install an extension to your browser to download "
"own cookies (`Chrome <https://chrome.google.com/webstore/detail/get-"
"cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>`_, `Firefox "
"<https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/>`_)."
msgstr ""
"``--cookies-jar-file`` —— Netscape 格式(即 cookies.txt)的自定义 cookie "
"文件。你可以在浏览器里安装对应扩展来导出自己的 cookie(`Chrome "
"<https://chrome.google.com/webstore/detail/get-"
"cookiestxt/bgaddhkoddajcdgocldbbfleckgcbcid>`_、\\ `Firefox "
"<https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/>`_)。"
#: ../../source/command-line-options.rst:93 d55a21187e5d4c51beb9b433595cccce
msgid ""
"``--no-recursion`` - Disable parsing pages for other usernames and "
"recursive search by them."
msgstr "``--no-recursion`` —— 禁用页面解析,以及基于发现的其它用户名进行递归搜索。"
#: ../../source/command-line-options.rst:96 61e5e8f7907d4b1dbf239e183fb1f802
msgid ""
"``--use-disabled-sites`` - Use disabled sites to search (may cause many "
"false positives)."
msgstr "``--use-disabled-sites`` —— 把已禁用的站点也纳入搜索范围(可能产生大量误报)。"
#: ../../source/command-line-options.rst:99 20ce94c212df49338e550f7ecf71cc28
msgid ""
"``--id-type`` - Specify identifier(s) type (default: username). Supported"
" types: gaia_id, vk_id, yandex_public_id, ok_id, wikimapia_uid. "
"Currently, you must add ``-a`` flag to run a scan on sites with custom id"
" types, sites will be filtered automatically."
msgstr ""
"``--id-type`` —— "
"指定标识符类型(默认:username)。支持的类型有:gaia_id、vk_id、yandex_public_id、ok_id、wikimapia_uid。目前必须同时加"
" ``-a``,才能扫描支持自定义 ID 类型的站点 —— 站点会被自动筛选。"
#: ../../source/command-line-options.rst:104 ef2bfbd405b1471abfed99b2b82974d7
msgid ""
"``--ignore-ids`` - Do not make search by the specified username or other "
"ids. Useful for repeated scanning with found known irrelevant usernames."
msgstr "``--ignore-ids`` —— 不对指定的用户名或其它 ID 发起搜索。适用于在多次重复扫描中,屏蔽已知与目标无关的用户名。"
#: ../../source/command-line-options.rst:107 8eb8e891848f4c0c940843698b63497b
msgid ""
"``--db`` - Load Maigret database from a JSON file or an online, valid, "
"JSON file. See :ref:`custom-database` below."
msgstr ""
"``--db`` —— 从一个本地 JSON 文件,或一个在线的有效 JSON 文件加载 Maigret 数据库。详见下文 :ref"
":`custom-database`\\ 。"
#: ../../source/command-line-options.rst:110 6413fe29bf874d50acd8025d0a2bdf30
msgid ""
"``--no-autoupdate`` - Disable the automatic database update check that "
"runs at startup. The currently cached (or bundled) database is used as-"
"is."
msgstr "``--no-autoupdate`` —— 禁用启动时的自动数据库更新检查。直接使用当前已缓存(或内置)的数据库,不做任何变动。"
#: ../../source/command-line-options.rst:114 cbfeb6479805402ba3c609e992dd66be
msgid ""
"``--force-update`` - Force a database update check at startup, ignoring "
"the usual check interval. Implies ``--no-autoupdate`` for the rest of the"
" run after the explicit update finishes."
msgstr ""
"``--force-update`` —— "
"在启动时强制执行一次数据库更新检查,忽略常规的检查间隔。强制更新结束后,本次运行剩余阶段相当于隐含开启了 ``--no-"
"autoupdate``\\ 。"
#: ../../source/command-line-options.rst:118 67b357544dd746d88351d5c3ac4d041c
msgid ""
"``--retries RETRIES`` - Count of attempts to restart temporarily failed "
"requests."
msgstr "``--retries RETRIES`` —— 对临时性失败的请求,允许重启重试的次数。"
#: ../../source/command-line-options.rst:121 1bc0e2cbf5f34a589897cd97f32c227a
#, python-brace-format
msgid ""
"``--with-domains`` *(experimental)* - Also resolve a small set of "
"``{username}.<tld>`` patterns through DNS (A-records) in parallel with "
"the normal HTTP checks. Currently 7 entries in the database use this path"
" (``.ddns.net``, ``.com``, ``.pro``, ``.me``, ``.biz``, ``.email``, "
"``.guru``). DNS-only hits can include parking domains and catch-all "
"wildcards, so treat results as a lead rather than confirmation. See the "
":doc:`FAQ entry on DNS domain checks <faq>`."
msgstr ""
"``--with-domains`` *(实验性)* —— 在执行普通 HTTP 检查的同时,通过 DNS"
"(A 记录)再解析一小组 ``{username}.<tld>`` 模式。当前数据库中有 7 条记录走"
"这条路径(``.ddns.net``、\\ ``.com``、\\ ``.pro``、\\ ``.me``、\\ "
"``.biz``、\\ ``.email``、\\ ``.guru``)。仅靠 DNS 的命中可能包括停放域名"
"和通配符记录,所以应当把结果当作线索而非确认结果。参见 :doc:`常见问题中关于"
" DNS 域名检查的条目 <faq>`。"
#: ../../source/command-line-options.rst:129 f25c6830550a44219e0d387848857f72
msgid ""
"``--cloudflare-bypass`` *(experimental)* - Route checks for sites tagged "
"``protection: [\"cf_js_challenge\"]`` / ``[\"cf_firewall\"]`` / "
"``[\"webgate\"]`` through a local Chrome-based solver (FlareSolverr by "
"default). The bypass is opt-in — without this flag (or "
"``settings.cloudflare_bypass.enabled = true``) those sites are checked "
"the usual way, which Cloudflare almost always blocks: you get an UNKNOWN "
"status with a JS-challenge / firewall error rather than a real result. "
"Configure the backend in ``settings.cloudflare_bypass.modules``. See :ref"
":`cloudflare-bypass`. **Experimental** — the flag, schema and routing "
"rules may change without backwards-compatibility guarantees."
msgstr ""
"``--cloudflare-bypass`` *(实验性)* —— 将 ``protection: "
"[\"cf_js_challenge\"]`` / ``[\"cf_firewall\"]`` / ``[\"webgate\"]`` "
"标记的站点交给本地基于 Chrome 的求解器(默认 FlareSolverr)处理。该绕过为按需启用 —— 不传该参数(也未在 "
"``settings.cloudflare_bypass.enabled`` 中设为 "
"``true``)时,这类站点会按常规方式被检查,而几乎肯定被 Cloudflare 拦下,得到的是 UNKNOWN 状态加上 JS "
"挑战/防火墙错误,而不是真实结果。后端可在 ``settings.cloudflare_bypass.modules`` 中配置。详见 :ref"
":`cloudflare-bypass`\\ 。\\ **实验性** —— 参数本身、配置结构以及路由规则均可能发生变化,不保证向后兼容。"
#: ../../source/command-line-options.rst:143 480a4a0211a44ab3884625356c0e27c4
msgid "Using a custom sites database"
msgstr "使用自定义站点数据库"
#: ../../source/command-line-options.rst:145 824c6462cdca48a7a1c792a10f605035
msgid "The ``--db`` flag accepts three forms:"
msgstr "``--db`` 参数支持以下三种形式:"
#: ../../source/command-line-options.rst:147 3c00a1005cd54a3081ebf8cdc37d8c85
msgid ""
"**HTTP(S) URL** — fetched as-is, e.g. ``--db "
"https://example.com/my_db.json``."
msgstr "**HTTP(S) URL** —— 按原样拉取,例如 ``--db https://example.com/my_db.json``\\ 。"
#: ../../source/command-line-options.rst:149 975d5a4ff3154d42b233654ca8df2e18
msgid ""
"**Local file path** — absolute (``--db /tmp/private.json``) or relative "
"to the current working directory (``--db LLM/maigret_private_db.json``)."
msgstr ""
"**本地文件路径** —— 可以是绝对路径(``--db /tmp/private.json``),也可以是相对当前工作目录的路径(``--db "
"LLM/maigret_private_db.json``)。"
#: ../../source/command-line-options.rst:152 abbe569364f343b5a19aad5cb1b638c5
msgid ""
"**Module-relative path** — kept for backwards compatibility, resolved "
"against the installed ``maigret/`` package directory (e.g. the default "
"``resources/data.json``)."
msgstr ""
"**相对模块的路径** —— 出于向后兼容保留,基于已安装的 ``maigret/`` 包目录解析(例如默认的 "
"``resources/data.json``)。"
#: ../../source/command-line-options.rst:156 dca2c80645e54b5e97f4032a2bf5c627
msgid ""
"Resolution order for local paths: the path is first tried as given "
"(absolute or cwd-relative); if that file does not exist, Maigret falls "
"back to the legacy module-relative resolution. If neither location "
"contains the file, Maigret exits with an error rather than silently "
"loading the bundled database."
msgstr ""
"本地路径的解析顺序:先按用户给的形式(绝对路径或基于当前目录的相对路径)尝试;若该文件不存在,再回退到旧的“相对模块路径”解析。如果两处都找不到,Maigret"
" 会报错退出,而不会悄悄加载内置数据库。"
#: ../../source/command-line-options.rst:162 e483264d6cdf49caac506883eed82aec
msgid ""
"When ``--db`` points to a custom file, automatic database updates are "
"skipped — the file is used exactly as provided."
msgstr "当 ``--db`` 指向自定义文件时,会跳过自动更新 —— 直接使用你提供的文件。"
#: ../../source/command-line-options.rst:165 07d9cd1ee7aa41d2851ce5b6afe0444d
msgid "On every run Maigret prints the database it actually loaded, for example::"
msgstr "每次运行时,Maigret 都会打印实际加载的数据库,例如::"
#: ../../source/command-line-options.rst:170 c8bd9320a67f425383801203ac0a643a
msgid ""
"If loading the requested database fails for any other reason (corrupt "
"JSON, missing required keys, …), Maigret prints a warning, falls back to "
"the bundled database, and reports the fallback explicitly::"
msgstr "如果加载请求的数据库因其它原因失败(JSON 损坏、缺少必需字段等),Maigret 会打印警告并显式回退到内置数据库,并在输出中明确告知::"
#: ../../source/command-line-options.rst:177 53da78b4c7ce47ef8f530004cc09caad
msgid ""
"A typical invocation against a private database, with auto-update "
"disabled and all sites scanned, looks like::"
msgstr "一个典型的命令:针对私有数据库、关闭自动更新、扫描全部站点 ——::"
#: ../../source/command-line-options.rst:185 00d2c6ddbefe4172bc0ee88c37e87582
msgid "Reports"
msgstr "报告"
#: ../../source/command-line-options.rst:187 0cad7e0c6a6c43aea7bdc0b91853a1f4
msgid ""
"``-P``, ``--pdf`` - Generate a PDF report (general report on all "
"usernames)."
msgstr "``-P``\\ 、\\ ``--pdf`` —— 生成 PDF 报告(覆盖所有用户名的整体报告)。"
#: ../../source/command-line-options.rst:190 8025b2f344b74156b6642395fea2ddb8
msgid ""
"``-H``, ``--html`` - Generate an HTML report file (general report on all "
"usernames)."
msgstr "``-H``\\ 、\\ ``--html`` —— 生成 HTML 报告文件(覆盖所有用户名的整体报告)。"
#: ../../source/command-line-options.rst:193 fea6b4f5114d4fe98b73734d509805a1
msgid ""
"``-X``, ``--xmind`` - Generate an XMind 8 mindmap (one report per "
"username)."
msgstr "``-X``\\ 、\\ ``--xmind`` —— 生成 XMind 8 思维导图(每个用户名生成一份)。"
#: ../../source/command-line-options.rst:196 54fae6515d004e25b4d634d4047b2c7e
msgid "``-C``, ``--csv`` - Generate a CSV report (one report per username)."
msgstr "``-C``\\ 、\\ ``--csv`` —— 生成 CSV 报告(每个用户名生成一份)。"
#: ../../source/command-line-options.rst:198 49a1c608a75e4efcbeb63fe8c969295a
msgid "``-T``, ``--txt`` - Generate a TXT report (one report per username)."
msgstr "``-T``\\ 、\\ ``--txt`` —— 生成 TXT 报告(每个用户名生成一份)。"
#: ../../source/command-line-options.rst:200 542b9ac68ab24286848241f5504c00d3
msgid ""
"``-J``, ``--json`` - Generate a JSON report of specific type: simple, "
"ndjson (one report per username). E.g. ``--json ndjson``"
msgstr ""
"``-J``\\ 、\\ ``--json`` —— 生成指定类型的 JSON 报告:simple、ndjson(每个用户名生成一份)。例如 "
"``--json ndjson``\\ 。"
#: ../../source/command-line-options.rst:203 de062e12c0e849d6ae0766e3afcb6899
msgid ""
"``-M``, ``--md`` - Generate a Markdown report (general report on all "
"usernames). See :ref:`markdown-report` below."
msgstr ""
"``-M``\\ 、\\ ``--md`` —— 生成 Markdown 报告(覆盖所有用户名的整体报告)。详见下文 :ref"
":`markdown-report`\\ 。"
#: ../../source/command-line-options.rst:206 cbdcc8708d624fbeb7fe053d660ee98d
msgid ""
"``--ai`` - Run an AI-powered analysis of the search results using an "
"OpenAI-compatible chat completion API. The internal Markdown report is "
"sent to the model, which returns a short investigation summary that is "
"streamed to the terminal. See :ref:`ai-analysis` below."
msgstr ""
"``--ai`` —— 通过一个 OpenAI 兼容的 chat completion 接口,对搜索结果做一次 AI 分析。内部 Markdown"
" 报告会被发送给模型,模型返回的简短调查摘要会以流式方式打印到终端。详见下文 :ref:`ai-analysis`\\ 。"
#: ../../source/command-line-options.rst:211 6e6a139b2eaf453fbccf5f91642944fb
msgid ""
"``--ai-model`` - Model name to use with ``--ai``. Defaults to "
"``openai_model`` from settings (``gpt-4o`` out of the box)."
msgstr ""
"``--ai-model`` —— 与 ``--ai`` 搭配使用的模型名。默认取配置中的 ``openai_model``\\ (开箱即用为 "
"``gpt-4o``)。"
#: ../../source/command-line-options.rst:214 7e584a9aa20f4a179a75e9b19f491211
msgid ""
"``-fo``, ``--folderoutput`` - Results will be saved to this folder, "
"``results`` by default. Will be created if doesnt exist."
msgstr "``-fo``\\ 、\\ ``--folderoutput`` —— 结果保存到此目录,默认 ``results``\\ 。如果不存在会自动创建。"
#: ../../source/command-line-options.rst:217 e86fadc1aa274c0cb74a74b49252a4fb
msgid ""
"``--web PORT`` - Start the built-in web interface on the given port and "
"serve results / downloadable reports from a single page. Example: "
"``maigret --web 5000`` → open ``http://127.0.0.1:5000``. Full walkthrough"
" with screenshots: :ref:`web-interface`."
msgstr ""
"``--web PORT`` —— 在指定端口启动内置 Web 界面,在一个页面上提供搜索结果和"
"可下载的报告。例如:``maigret --web 5000`` → 在浏览器中打开 "
"``http://127.0.0.1:5000``。带截图的完整说明见 :ref:`web-interface`。"
#: ../../source/command-line-options.rst:223 2fefea5f68854596860a467f801351d4
msgid "Output options"
msgstr "输出选项"
#: ../../source/command-line-options.rst:225 f1ee27d6d1424578b7eb1c2630a63afa
msgid ""
"``-v``, ``--verbose`` - Display extra information and metrics. "
"*(loglevel=WARNING)*"
msgstr "``-v``\\ 、\\ ``--verbose`` —— 展示更多信息和指标。*(loglevel=WARNING)*"
#: ../../source/command-line-options.rst:228 d82d5a8a8fb74653afd70b97f406edd6
msgid "``-vv``, ``--info`` - Display service information. *(loglevel=INFO)*"
msgstr "``-vv``\\ 、\\ ``--info`` —— 展示运行信息。*(loglevel=INFO)*"
#: ../../source/command-line-options.rst:230 13ddfc9f5fdd4418b3af1a7894f00edf
msgid ""
"``-vvv``, ``--debug``, ``-d`` - Display debugging information and site "
"responses. *(loglevel=DEBUG)*"
msgstr ""
"``-vvv``\\ 、\\ ``--debug``\\ 、\\ ``-d`` —— "
"展示调试信息以及站点响应内容。*(loglevel=DEBUG)*"
#: ../../source/command-line-options.rst:233 d3f6f33a968e4e69aedf63e6f3e4cac9
msgid "``--print-not-found`` - Print sites where the username was not found."
msgstr "``--print-not-found`` —— 打印那些没有找到该用户名的站点。"
#: ../../source/command-line-options.rst:235 74d0e809c3d649ccaac4b8072191e609
msgid ""
"``--print-errors`` - Print errors messages: connection, captcha, site "
"country ban, etc."
msgstr "``--print-errors`` —— 打印错误信息:连接错误、CAPTCHA、按国家封禁等。"
#: ../../source/command-line-options.rst:239 eb1c3d7bc2a74c4584bec4db4e6876ee
msgid "Other operations modes"
msgstr "其它运行模式"
#: ../../source/command-line-options.rst:241 ef8d45d364824f4f82caab5750059f40
msgid "``--version`` - Display version information and dependencies."
msgstr "``--version`` —— 展示版本信息和依赖。"
#: ../../source/command-line-options.rst:243 dda0b9c369994a18af5a95576f384224
msgid ""
"``--self-check`` - Do self-checking for sites and database. Each site is "
"tested by looking up its known-claimed and known-unclaimed usernames and "
"verifying that the results match expectations. Individual site failures "
"(network errors, unexpected exceptions, etc.) are caught and logged "
"without stopping the overall process, so the check always runs to "
"completion. After checking, Maigret reports a summary of issues found. If"
" any sites were disabled (see ``--auto-disable``), Maigret asks if you "
"want to save updates; answering y/Y will rewrite the local database."
msgstr ""
"``--self-check`` —— "
"对站点和数据库执行自检。对每个站点,会分别查询其已知被占用、已知未被占用的用户名,并验证结果是否符合预期。单个站点的失败(网络错误、意外异常等)会被捕获并记录,而不会中断整体流程,因此自检总能跑到结束。检查完毕后,Maigret"
" 会汇总报告发现的问题。如果有站点被禁用了(参见 ``--auto-disable``),Maigret 会询问是否保存改动;输入 y/Y "
"会改写本地数据库。"
#: ../../source/command-line-options.rst:252 d90dd8924f484ed89c26a1498906d376
msgid ""
"``--auto-disable`` - Used with ``--self-check``: automatically disable "
"sites that fail checks (incorrect detection of claimed/unclaimed "
"usernames, connection errors, or unexpected exceptions). Without this "
"flag, ``--self-check`` only **reports** issues without modifying the "
"database."
msgstr ""
"``--auto-disable`` —— 与 ``--self-check`` "
"搭配使用:自动禁用检查失败的站点(claimed/unclaimed 用户名识别不正确、连接错误、意外异常等)。不加该参数时,\\ "
"``--self-check`` 仅\\ **报告**\\ 问题,不会修改数据库。"
#: ../../source/command-line-options.rst:258 4abd8317982f4e42835299b7609a2d4f
msgid ""
"``--diagnose`` - Used with ``--self-check``: print detailed diagnosis "
"information for each failing site, including the check type, the list of "
"issues found, and recommendations (e.g. suggesting a different "
"``checkType``)."
msgstr ""
"``--diagnose`` —— 与 ``--self-check`` "
"搭配使用:打印每个检查失败站点的详细诊断信息,包括检查类型、发现的问题列表,以及修复建议(例如建议改用另一种 ``checkType``)。"
#: ../../source/command-line-options.rst:263 ce42fe61bfb0482dbc89ef2c3911519f
msgid ""
"``--submit URL`` - Do an automatic analysis of the given account URL or "
"site main page URL to determine the site engine and methods to check "
"account presence. After checking Maigret asks if you want to add the "
"site, answering y/Y will rewrite the local database."
msgstr ""
"``--submit URL`` —— 对给定的账号 URL 或站点主页 URL "
"做一次自动分析,判断该站点的引擎以及检测账号是否存在的方式。结束后,Maigret 会询问是否要把该站点加入数据库;输入 y/Y "
"会改写本地数据库。"
#: ../../source/command-line-options.rst:271 4d6f6cbccf7a451ca20856047705749f
msgid "Markdown report (LLM-friendly)"
msgstr "Markdown 报告(对 LLM 友好)"
#: ../../source/command-line-options.rst:273 169c9c5850df41be928a3b97d0053eaf
msgid ""
"The ``--md`` / ``-M`` flag generates a Markdown report designed for both "
"human reading and analysis by AI assistants (ChatGPT, Claude, etc.)."
msgstr ""
"``--md`` / ``-M`` 参数会生成一份 Markdown 报告,既方便人类阅读,也便于AI 助手(ChatGPT、Claude "
"等)分析。"
#: ../../source/command-line-options.rst:279 bdd50a6d6ea0417fbf43fe41de094513
msgid "The report includes:"
msgstr "该报告包含:"
#: ../../source/command-line-options.rst:281 2c1e1915c3274e6a8bc2c0e084819107
msgid ""
"**Summary** with aggregated personal data (all fullnames, locations, bios"
" found across accounts), country tags, website tags, first/last seen "
"timestamps."
msgstr "**摘要**,包括跨账号汇总的个人数据(出现过的所有真实姓名、所在地、个人简介)、国家标签、站点标签、首次/最近一次出现的时间戳。"
#: ../../source/command-line-options.rst:282 e68e7e3fcb6d447f84f4c3db5576d2cb
msgid ""
"**Per-account sections** with profile URL, site tags, and all extracted "
"fields (username, bio, follower count, linked accounts, etc.)."
msgstr "**每个账号一节**,包含主页 URL、站点标签,以及所有抽取出的字段(用户名、简介、关注者数、关联账号等)。"
#: ../../source/command-line-options.rst:283 0399652878474aca95cb31aab128a9a3
msgid ""
"**Possible false positives** disclaimer explaining that accounts may "
"belong to different people."
msgstr "**可能的误报**\\ 声明,提醒不同账号有可能属于不同的人。"
#: ../../source/command-line-options.rst:284 3e8bb2a7b47e48edb3ac863d7dd7971f
msgid "**Ethical use** notice about applicable data protection laws."
msgstr "**伦理使用**\\ 提示,涉及相关数据保护法律。"
#: ../../source/command-line-options.rst:286 7d11f4af4bc64f47acfff800307964dd
msgid "**Using with AI tools:**"
msgstr "**搭配 AI 工具使用:**"
#: ../../source/command-line-options.rst:288 eb246a42d256435095cc135fc0663933
msgid ""
"The Markdown format is optimized for LLM context windows. You can feed "
"the report directly to an AI assistant for follow-up analysis:"
msgstr "该 Markdown 格式针对 LLM 上下文窗口做了优化。你可以直接把报告投喂给 AI 助手,做后续分析:"
#: ../../source/command-line-options.rst:298 dc7df4be38eb4e87904519db90ddcfaa
msgid ""
"The structured Markdown with per-site sections makes it easy for AI tools"
" to extract relationships, cross-reference identities, and identify "
"patterns across accounts."
msgstr "按站点分段的结构化 Markdown,使得 AI 工具更容易抽取关系、对照身份、并在多个账号之间识别模式。"
#: ../../source/command-line-options.rst:300 afe2ae91795446b0a33b0b1280d85508
msgid ""
"For a built-in alternative that calls the model for you and prints the "
"summary directly, see :ref:`ai-analysis` below."
msgstr "如果想要由 Maigret 自己调用模型并直接打印摘要,请参见下文的 :ref:`ai-analysis`\\ 。"
#: ../../source/command-line-options.rst:306 74b535f2764f4fc6a1517b76af1feccc
msgid "AI analysis (built-in)"
msgstr "AI 分析(内置)"
#: ../../source/command-line-options.rst:308 aff26f7a190b42c9972a8dd47884abef
msgid ""
"The ``--ai`` flag turns the search results into a short investigation "
"summary by sending the internal Markdown report to an OpenAI-compatible "
"chat completion API and streaming the model's reply to the terminal."
msgstr ""
"``--ai`` 参数会把搜索结果转化为一份简短的调查摘要 —— 将内部 Markdown 报告发送至一个 OpenAI 兼容的 chat "
"completion 接口,并把模型回复以流式方式输出到终端。"
#: ../../source/command-line-options.rst:320 37cd9626351146ae95977a26d64827ee
msgid ""
"While ``--ai`` is active, per-site progress lines and the short text "
"report at the end are suppressed so the streamed summary is the main "
"output. The Markdown report itself is built in memory and is **not** "
"written to disk by ``--ai`` alone — combine with ``--md`` if you also "
"want the file on disk."
msgstr ""
"``--ai`` 启用期间,逐站点进度行以及末尾的简短文本报告都会被抑制,以便流式摘要成为主要输出。Markdown "
"报告本身只构建于内存中,单独使用 ``--ai`` 时\\ **不会**\\ 落盘 —— 如果同时也想把文件保存到磁盘,请配合 ``--md`` "
"使用。"
#: ../../source/command-line-options.rst:326 e34c3a4ea5364123a895c6ef7006852b
msgid ""
"The summary follows a fixed format with sections for the most likely real"
" name, location, occupation, interests, languages, main website, username"
" variants, number of platforms, active years, a confidence rating, and a "
"short list of follow-up leads. The model is instructed to rely only on "
"what is supported by the report and to avoid mixing clearly unrelated "
"profiles into the main identity."
msgstr "摘要采用固定格式,分别给出最可能的真实姓名、所在地、职业、兴趣、语言、主要使用的网站、用户名变体、平台数量、活跃年份、置信度评级以及一份简短的后续线索清单。模型被明确要求:只能依据报告所支持的内容来作答,避免把明显无关的账号也并入主身份。"
#: ../../source/command-line-options.rst:333 1b0f6bb02ecc49e28da3fc9c59ddebcc
msgid ""
"**Configuration.** The API key is resolved from "
"``settings.openai_api_key`` first, then from the ``OPENAI_API_KEY`` "
"environment variable. The endpoint defaults to "
"``https://api.openai.com/v1`` and can be redirected to any OpenAI-"
"compatible service (Azure OpenAI, OpenRouter, a local server, …) by "
"setting ``openai_api_base_url`` in ``settings.json``. See :ref:`settings`"
" for the full list of options."
msgstr ""
"**配置说明。** API key 的取值顺序:先取 ``settings.openai_api_key``,再回退到 "
"``OPENAI_API_KEY`` 环境变量。接口地址默认为 ``https://api.openai.com/v1``,通过在 "
"``settings.json`` 中设置 ``openai_api_base_url``,可以重定向到任意 OpenAI 兼容服务(Azure "
"OpenAI、OpenRouter、本地推理服务等)。完整选项请见 :ref:`settings`\\ 。"
#: ../../source/command-line-options.rst:343 714f46dbe1fa4747b900ca499b6bf4a7
msgid ""
"``--ai`` makes a network request to the configured chat completion "
"endpoint and sends the full Markdown report (which contains the gathered "
"profile data). Use it only with providers and accounts you trust with "
"that data."
msgstr ""
"``--ai`` 会向所配置的 chat completion 接口发起网络请求,并将完整的 Markdown "
"报告(其中包含已收集到的个人主页数据)一并发送过去。请仅在你信任的服务商和账号上使用该功能。"
#: ../../source/command-line-options.rst:206
msgid ""
"``--neo4j`` - Generate a Neo4j Cypher report: a ``.cypher`` script that "
"recreates the maigret graph (the same one produced by ``--graph``) in a "
"Neo4j database, importable with ``cypher-shell`` or the Neo4j Browser "
"(general report on all usernames). See :ref:`neo4j-export` below."
msgstr "``--neo4j`` —— 生成 Neo4j Cypher 报告:一个 ``.cypher``\\ 脚本,可在 Neo4j 数据库中重建 maigret 图谱(与 ``--graph``\\ 生成的图谱相同),可用 ``cypher-shell``\\ 或 Neo4j Browser 导入(针对所有用户名的汇总报告)。参见下文的 :ref:`neo4j-export`\\ 。"
#: ../../source/command-line-options.rst:356
msgid "Neo4j export"
msgstr "Neo4j 导出"
#: ../../source/command-line-options.rst:358
msgid ""
"The ``--neo4j`` flag serializes the maigret graph — the same nodes and "
"relationships behind ``--graph`` — into a ``*_neo4j.cypher`` script you "
"can load into a `Neo4j <https://neo4j.com/>`_ database for querying and "
"visual exploration of how identities, accounts, sites, and extracted data"
" points link together."
msgstr "``--neo4j``\\ 选项会把 maigret 图谱(与 ``--graph``\\ 背后相同的节点和关系)序列化为一个 ``*_neo4j.cypher``\\ 脚本,你可以将其载入 `Neo4j <https://neo4j.com/>`_\\ 数据库,用于查询并可视化地探索身份、账号、站点与抽取到的数据点之间是如何相互关联的。"
#: ../../source/command-line-options.rst:368
msgid ""
"This writes ``reports/report_username_neo4j.cypher``. No extra runtime "
"dependency is required (it reuses the ``networkx`` graph that ``--graph``"
" already builds)."
msgstr "该命令会写出 ``reports/report_username_neo4j.cypher``\\ 。无需额外的运行时依赖(它复用了 ``--graph``\\ 已经构建的 ``networkx``\\ 图谱)。"
#: ../../source/command-line-options.rst:372
msgid "**What the script contains.**"
msgstr "**脚本包含哪些内容。**"
#: ../../source/command-line-options.rst:374
msgid ""
"A ``CREATE CONSTRAINT ... IF NOT EXISTS`` ensuring the ``name`` property "
"of every ``:MaigretNode`` is unique."
msgstr "一条 ``CREATE CONSTRAINT ... IF NOT EXISTS``\\ 语句,确保每个 ``:MaigretNode``\\ 的 ``name``\\ 属性唯一。"
#: ../../source/command-line-options.rst:376
msgid ""
"One ``MERGE`` per node. Each node carries three properties: ``name`` (the"
" unique key, e.g. ``account: https://github.com/user``), ``type`` "
"(``username``, ``account``, ``fullname``, ``uid``, …) and ``label`` (the "
"human-readable value)."
msgstr "每个节点一条 ``MERGE``\\ 。每个节点带有三个属性:``name`` (唯一键,例如 ``account: https://github.com/user``)、``type`` (``username``\\ 、\\ ``account``\\ 、\\ ``fullname``\\ 、\\ ``uid``\\ 等)以及 ``label`` (便于阅读的值)。"
#: ../../source/command-line-options.rst:380
msgid "One ``MERGE`` per edge as a ``[:LINKED_TO]`` relationship."
msgstr "每条边一条 ``MERGE``\\ ,作为 ``[:LINKED_TO]``\\ 关系。"
#: ../../source/command-line-options.rst:382
msgid ""
"Because every node and edge is ``MERGE``-d on its unique key, importing "
"the same report twice is **idempotent** — it updates existing nodes "
"instead of creating duplicates."
msgstr "由于每个节点和每条边都按其唯一键进行 ``MERGE``\\ ,因此重复导入同一份报告是\\ **幂等的**\\ —— 它会更新已有节点,而不会创建重复项。"
#: ../../source/command-line-options.rst:386
msgid "**Importing.**"
msgstr "**导入。**"
#: ../../source/command-line-options.rst:393
msgid ""
"Alternatively, open the Neo4j Browser at ``http://localhost:7474`` and "
"paste the script contents into the query editor. Drop the ``-p`` flag if "
"authentication is disabled, or let ``cypher-shell`` prompt for the "
"password interactively."
msgstr "或者,在 ``http://localhost:7474``\\ 打开 Neo4j Browser,把脚本内容粘贴到查询编辑器中。如果未启用身份验证,可省略 ``-p``\\ 参数;也可以让 ``cypher-shell``\\ 交互式地提示输入密码。"
#: ../../source/command-line-options.rst:398
msgid "**Exploring the graph.** Once imported, inspect it with Cypher, e.g.:"
msgstr "**探索图谱。** 导入后,可用 Cypher 查询进行检视,例如:"
#: ../../source/command-line-options.rst:409
msgid ""
"The ``CREATE CONSTRAINT ... IF NOT EXISTS FOR ... REQUIRE`` syntax "
"requires Neo4j 4.4+; the ``MERGE`` statements themselves work on any "
"version."
msgstr "``CREATE CONSTRAINT ... IF NOT EXISTS FOR ... REQUIRE``\\ 语法需要 Neo4j 4.4+;而 ``MERGE``\\ 语句本身在任何版本上都可用。"
File diff suppressed because it is too large Load Diff
+376
View File
@@ -0,0 +1,376 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-23 09:37+0200\n"
"PO-Revision-Date: 2026-05-23 09:30+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/faq.rst:4 e40cd7b2142f466fbf5f49c80433cbdb
msgid "FAQ"
msgstr "常见问题"
#: ../../source/faq.rst:6 d5980e032ec54e73a6636bd6d8e88c8d
msgid ""
"Short answers to the questions users most often type into the docs "
"search. For deeper coverage each section links to the relevant page."
msgstr "针对用户在文档搜索中出现频率最高的问题给出简短回答。需要更深入的内容时,每个小节都会链接到对应的详细页面。"
#: ../../source/faq.rst:10 4f7d8da7b4024ada94f6b494212b546c
msgid "Can I search by email address?"
msgstr "可以用邮箱地址进行搜索吗?"
#: ../../source/faq.rst:12 eb6cb9de3d2240c886510c777c23911e
msgid ""
"**No.** Maigret only takes a username (or one of the :doc:`supported "
"identifier types <supported-identifier-types>`) as input — searching by "
"an email or mail address is out of scope. Looking up a mail address "
"requires different techniques (probing password-reset flows, registration"
" endpoints) and is the job of a separate class of tool."
msgstr ""
"**不可以。** Maigret 只接受用户名(或 :doc:`受支持的标识符类型 "
"<supported-identifier-types>` 之一)作为输入 —— 通过 email 或 mail 地址进行"
"搜索不在其能力范围内。查询邮箱地址需要另一类技术(探测密码重置流程、注册"
"端点),属于另一类工具的工作。"
#: ../../source/faq.rst:19 74fe6aa4775b448fad555f57cdaddb0e
msgid "Recommended open-source tools for email lookup:"
msgstr "推荐用于邮箱查询的开源工具:"
#: ../../source/faq.rst:21 3432ec400d1e431182c9299305cc0d87
msgid "`mailcat <https://github.com/sharsil/mailcat>`_"
msgstr "`mailcat <https://github.com/sharsil/mailcat>`_"
#: ../../source/faq.rst:22 c24f3826f52f41d68e1b85f64d5dfea4
msgid "`holehe <https://github.com/megadose/holehe>`_"
msgstr "`holehe <https://github.com/megadose/holehe>`_"
#: ../../source/faq.rst:23 896b565ff8e54e14baf3ffb73978cbaf
msgid "`user-scanner <https://github.com/kaifcodec/user-scanner>`_"
msgstr "`user-scanner <https://github.com/kaifcodec/user-scanner>`_"
#: ../../source/faq.rst:25 cfe0c3f0de4a4369be5ca3cfaec25011
msgid "Online services:"
msgstr "在线服务:"
#: ../../source/faq.rst:27 0e76f0ed0a6841dc88475a7550952678
msgid "`Noimosiny <https://noimosiny.com>`_"
msgstr "`Noimosiny <https://noimosiny.com>`_"
#: ../../source/faq.rst:28 af71ec52140443739608101da59ad807
msgid "`OSINT Industries <https://osint.industries>`_"
msgstr "`OSINT Industries <https://osint.industries>`_"
#: ../../source/faq.rst:29 ae0c6b5a20b2455e8f98469eaa40c68e
msgid "`Epieos <https://epieos.com>`_"
msgstr "`Epieos <https://epieos.com>`_"
#: ../../source/faq.rst:31 b877c79107cc4264a539ee43bfd9118b
msgid ""
"Note: if Maigret has already found an account for a username, it often "
"extracts the linked email from the profile page automatically — see :ref"
":`extracting-information-from-pages`."
msgstr ""
"注意:如果 Maigret 已经通过用户名找到了账号,它往往会自动从资料页中提取出关联的邮箱地址 —— 参见 :ref:`extracting-"
"information-from-pages`。"
#: ../../source/faq.rst:36 eae13968aee54ce6b4b31536c8b403b7
msgid "Can I configure a proxy / SOCKS / Tor / I2P?"
msgstr "可以配置代理 / SOCKS / Tor / I2P 吗?"
#: ../../source/faq.rst:38 6e76c51d1261462e9a2843aada43ec07
msgid "**Yes.** Three flags cover three distinct goals:"
msgstr "**可以。** 三个参数分别对应三种不同目标:"
#: ../../source/faq.rst:40 8c234ba9cedc4f828ca4bd83cbe35a9f
msgid ""
"``--proxy URL`` — route **every** check through the given HTTP or SOCKS "
"proxy (also the right flag for routing the whole run through Tor with "
"``socks5://127.0.0.1:9050``, a residential proxy, or a corporate "
"gateway)."
msgstr ""
"``--proxy URL`` —— 让 **所有** 检查请求都通过指定的 HTTP 或 SOCKS 代理转发(如果想让整次运行都走 "
"Tor,使用 ``socks5://127.0.0.1:9050``;或者用于住宅代理、公司网关,也用这个参数)。"
#: ../../source/faq.rst:44 01aac87b54df48dfaf6b215f45dde233
msgid ""
"``--tor-proxy URL`` — used **only** for ``.onion`` sites in the database."
" Clearweb sites still go via your direct connection (or ``--proxy`` if "
"set)."
msgstr ""
"``--tor-proxy URL`` —— **只** 对数据库中 ``.onion`` 站点生效。明网站点仍走你的直连(或 "
"``--proxy``,如果你设置了的话)。"
#: ../../source/faq.rst:47 7139f8e1fb5f48dbaedae73a219b7964
msgid "``--i2p-proxy URL`` — same idea, only for ``.i2p`` hosts."
msgstr "``--i2p-proxy URL`` —— 同样的思路,只对 ``.i2p`` 域名生效。"
#: ../../source/faq.rst:49 0e042a4962cd42b084483fb10bd5c81c
msgid ""
"The most common confusion is ``--proxy`` vs ``--tor-proxy``: ``--proxy`` "
"is \"everything through this gateway\", ``--tor-proxy`` is \"only onion "
"sites through Tor\"."
msgstr ""
"最常见的混淆是 ``--proxy`` 与 ``--tor-proxy`` 的区别:``--proxy`` 是\"所有流量走这个网关\",而 "
"``--tor-proxy`` 是\"只有 onion 站点走 Tor\"。"
#: ../../source/faq.rst:53 d3b4785e4c9e4600bc6c12dd573d25c6
msgid ""
"Full walkthrough (Tor Browser vs system ``tor`` port numbers, Tails OS, "
"timeout / retry tuning): :doc:`tor-and-proxies`."
msgstr ""
"完整说明(Tor Browser 与系统 ``tor`` 守护进程端口差异、Tails 系统、超时与重试调优)::doc:`tor-and-"
"proxies`。"
#: ../../source/faq.rst:56 f9cf19afe2c443caa0effba4efa1f980
msgid ""
"If your goal is actually \"bypass WAF blocks / fix 403 errors\", see the "
"*Sites fail / timeout / 403* section below — a residential proxy almost "
"always outperforms Tor or a VPN for that."
msgstr ""
"如果你的目标实际上是\"绕过 WAF 拦截 / 修复 403 错误\",请参见下文 *站点失败 / 超时 / 403* 一节 —— "
"对此场景,住宅代理几乎总是优于 Tor 或 VPN。"
#: ../../source/faq.rst:61 d2d6f885aeea4ca088a589df8df26641
msgid "Can I use a VPN with Maigret?"
msgstr "可以配合 VPN 使用 Maigret 吗?"
#: ../../source/faq.rst:63 10108a9dbc4943e087a901f3cb2cefbc
msgid ""
"**Yes**, but ``--proxy`` is usually a better choice. A VPN works "
"transparently at the OS level — Maigret needs no special configuration to"
" use one. However:"
msgstr ""
"**可以**,但通常 ``--proxy`` 是更好的选择。VPN 在操作系统层透明工作 —— Maigret 不需要任何特殊配置就能用上 "
"VPN。不过:"
#: ../../source/faq.rst:67 f2a5a564b66145ed806a4d69d70eb231
msgid ""
"``--proxy`` is per-process: it does not affect other apps and does not "
"leak when toggled."
msgstr "``--proxy`` 是按进程生效的:不会影响其它应用,在切换时也不会泄露。"
#: ../../source/faq.rst:69 f5c5e385abe0443b9b085ec37699e131
msgid ""
"``--proxy`` makes the egress IP visible in logs, which is useful when "
"diagnosing why a batch of sites returned ``UNKNOWN``."
msgstr "``--proxy`` 会让出口 IP 出现在日志里,在排查为什么某批站点返回 ``UNKNOWN`` 时很有用。"
#: ../../source/faq.rst:71 bf3de5bb7878478990cb7f6b6029ac19
msgid ""
"``--proxy`` accepts a different value per run, so you can rotate between "
"residential and datacenter exits without touching system network "
"settings."
msgstr "``--proxy`` 每次运行都可以传入不同的值,因此可以在住宅出口和数据中心出口之间轮换,而不必动系统的网络设置。"
#: ../../source/faq.rst:75 fcfb61d7f06f434e8095a2d92d88f45f
msgid ""
"If a lot of sites are returning 403, the cause is almost certainly that "
"the VPN exit IP is on a WAF blocklist (Cloudflare, DDoS-Guard, Akamai all"
" blanket-block common VPN ranges). A residential proxy via ``--proxy`` is"
" the usual fix — see the `\"Lots of sites fail / timeout / return 403\" "
"section <https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md"
"#lots-of-sites-fail--timeout--return-403>`_ in TROUBLESHOOTING.md."
msgstr ""
"如果很多站点都返回 403,原因几乎可以肯定是 VPN 出口 IP 在某个 WAF 黑名单上(Cloudflare、DDoS-"
"Guard、Akamai 都会对常见 VPN 网段做整体封锁)。通常的修复办法是通过 ``--proxy`` 使用住宅代理 —— 参见 "
"TROUBLESHOOTING.md 中的 `\"Lots of sites fail / timeout / return 403\" 章节 "
"<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md#lots-of-"
"sites-fail--timeout--return-403>`_。"
#: ../../source/faq.rst:84 60bdb99512fc49228e835d0860f91e8a
msgid "Does Maigret check domains via DNS?"
msgstr "Maigret 会通过 DNS 检查域名吗?"
#: ../../source/faq.rst:86 4a265a61c38f498099a8c7b605d73942
#, python-brace-format
msgid ""
"**Yes, experimentally.** With ``--with-domains`` Maigret resolves a small"
" set of ``{username}.<tld>`` patterns through DNS (A-records) in parallel"
" with the normal HTTP checks. The current set is ``.ddns.net``, ``.com``,"
" ``.pro``, ``.me``, ``.biz``, ``.email``, ``.guru`` — 7 entries in the "
"database with ``protocol: dns``."
msgstr ""
"**可以,处于实验阶段。** 加上 ``--with-domains``,Maigret 会在执行普通 HTTP 检查的同时,通过 DNS(A "
"记录)解析一小组 ``{username}.<tld>`` 模式。当前包含的后缀有 ``.ddns.net``、\\ ``.com``、\\ "
"``.pro``、\\ ``.me``、\\ ``.biz``、\\ ``.email``、\\ ``.guru`` —— 数据库中共有 7 条 "
"``protocol: dns`` 的记录。"
#: ../../source/faq.rst:96 20a9b14798984df78d034196fd4ecfe7
msgid ""
"The flag is marked **experimental**: DNS-only checks can flag parking "
"domains and catch-all wildcards as if the username were registered, so "
"treat hits as a lead rather than confirmation."
msgstr "该参数被标记为 **实验性**:仅靠 DNS 的检查可能会把停放域名和通配符记录也识别为\"用户名已注册\",所以命中应当视为线索,而不是确认结果。"
#: ../../source/faq.rst:100 9448f73809274579befe33918ad996bb
msgid ""
"If your task is wider DNS reconnaissance — subdomain enumeration, WHOIS "
"history, typo-squatting — Maigret is the wrong tool. Established "
"alternatives:"
msgstr "如果你的任务是更广义的 DNS 侦察 —— 子域名枚举、WHOIS 历史、相似域名 —— 那么 Maigret 不是合适的工具。常用替代方案如下:"
#: ../../source/faq.rst:104 0ac035429e394a33939ca5a86ff608c4
msgid ""
"`dnstwist <https://github.com/elceef/dnstwist>`_ — typo-squatting and "
"look-alike domains."
msgstr "`dnstwist <https://github.com/elceef/dnstwist>`_ —— 用于发现拼写错误抢注与相似域名。"
#: ../../source/faq.rst:106 e476d2783f9847a7b911a3ea70c941b4
msgid ""
"`amass <https://github.com/owasp-amass/amass>`_ / `subfinder "
"<https://github.com/projectdiscovery/subfinder>`_ — subdomain "
"enumeration."
msgstr ""
"`amass <https://github.com/owasp-amass/amass>`_ / `subfinder "
"<https://github.com/projectdiscovery/subfinder>`_ —— 子域名枚举。"
#: ../../source/faq.rst:109 41f9b96fe0a54d7e87118f27ffa22385
msgid ""
"`theHarvester <https://github.com/laramies/theHarvester>`_ — email / host"
" / subdomain harvesting by domain."
msgstr ""
"`theHarvester <https://github.com/laramies/theHarvester>`_ —— 按域名收集邮箱 / "
"主机 / 子域名信息。"
#: ../../source/faq.rst:113 714faff2c1b344fba86dacbd49bd9805
msgid "Is there a Maigret Telegram bot?"
msgstr "有 Maigret 的 Telegram 机器人吗?"
#: ../../source/faq.rst:115 0d1fc1eac9f847c2806efabc79f3f601
msgid ""
"**Yes.** A community-maintained bot lets you run Maigret without "
"installing anything locally:"
msgstr "**有。** 社区维护的一个 Telegram 机器人让你无需在本地安装就能运行 Maigret:"
#: ../../source/faq.rst:118 21e6de736cc84ffc85818194eacd5f0b
msgid ""
"Working instance: `sites.google.com/view/maigret-bot-link "
"<https://sites.google.com/view/maigret-bot-link>`_ (redirect — the hosted"
" bot may move between providers)."
msgstr ""
"在线实例:`sites.google.com/view/maigret-bot-link "
"<https://sites.google.com/view/maigret-bot-link>`_\\ (跳转链接 —— "
"托管实例可能会在不同服务商之间迁移)。"
#: ../../source/faq.rst:121 2c720ea08a8a49c28db091f66d631991
msgid ""
"Source code: `github.com/soxoj/maigret-tg-bot <https://github.com/soxoj"
"/maigret-tg-bot>`_."
msgstr ""
"源代码:`github.com/soxoj/maigret-tg-bot <https://github.com/soxoj/maigret-"
"tg-bot>`_。"
#: ../../source/faq.rst:124 f90e3de745a5439ca62e76bdcd50721b
msgid ""
"On the question of *searching Telegram itself*: Maigret checks whether a "
"``t.me/<user>`` page exists as part of the normal run, but it does not "
"parse channels, posts, members, or message contents. For Telegram content"
" OSINT you need a dedicated tool."
msgstr ""
"关于*搜索 Telegram 本身*这个问题:Maigret 在常规扫描中会检查 ``t.me/<user>`` "
"页面是否存在,但不会解析频道、帖子、成员或消息内容。如需对 Telegram 内容进行OSINT 调查,需要使用专门的工具。"
#: ../../source/faq.rst:130 ff8d899b55ad4981878d800059744c37
msgid "Where is the web interface?"
msgstr "Web 界面在哪里?"
#: ../../source/faq.rst:136 92c24ac940b246d6b83a1bb9e994bd99
msgid ""
"Then open http://127.0.0.1:5000. Screenshots and a full walkthrough are "
"in :ref:`web-interface`."
msgstr "然后在浏览器中打开 http://127.0.0.1:5000。截图和完整说明见 :ref:`web-interface`。"
#: ../../source/faq.rst:140 5f9121c254c74f99871615b9e560d30f
msgid "Sites fail / timeout / return 403 — connection failures"
msgstr "站点失败 / 超时 / 返回 403 —— 连接失败 (connection failure)"
#: ../../source/faq.rst:142 d58c22a1e28f4bf8b6ce44ae4f23d9b4
msgid ""
"This is the most common report and is almost always caused by anti-bot "
"protection (Cloudflare, DDoS-Guard, Akamai) or a slow link, not by a bug "
"in Maigret. Quick tweaks, in order:"
msgstr ""
"这是被反映得最多的问题,几乎总是由反机器人防护(Cloudflare、DDoS-Guard、Akamai)或缓慢的网络链路引起的,而不是 "
"Maigret 的 bug。可按下面的顺序逐步调整:"
#: ../../source/faq.rst:146 deef74a8e0b742028aa031a6e21b7cd5
msgid ""
"``--timeout 60`` — the default 30 s is tight for slow networks and for "
"Tor."
msgstr "``--timeout 60`` —— 在慢速网络和 Tor 环境下,默认 30 秒往往太紧。"
#: ../../source/faq.rst:148 3040ea861ee64362af875c54e26f2f52
msgid "``--retries 2`` — covers transient failures."
msgstr "``--retries 2`` —— 覆盖偶发性失败。"
#: ../../source/faq.rst:149 9f77e97c68e4455b9b1191914871e81c
msgid "``-n 20`` — lower concurrency reduces WAF rate-limiting."
msgstr "``-n 20`` —— 降低并发可以减少 WAF 的速率限制。"
#: ../../source/faq.rst:150 22857912134d407e975e8c35506c6e51
msgid ""
"``--proxy http://user:pass@residential-proxy:port`` — datacenter IPs "
"(AWS, GCP, DigitalOcean) and most VPN ranges are blanket-blocked; "
"residential / mobile exits usually fix the bulk of 403s."
msgstr ""
"``--proxy http://user:pass@residential-proxy:port`` —— 数据中心 "
"IP(AWS、GCP、DigitalOcean)以及大多数 VPN 网段都被整体封锁;住宅 / 移动出口通常能解决大部分 403。"
#: ../../source/faq.rst:154 a35607cae3e44d2f9a45c559af21994d
msgid ""
"The full troubleshooting matrix (per-error recipes for 403, timeout, SSL,"
" captcha, ``UNKNOWN`` floods) lives in `TROUBLESHOOTING.md "
"<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_."
msgstr ""
"完整的故障排查矩阵(针对 403、超时、SSL、验证码、大量 ``UNKNOWN`` 等各类错误的处理办法)见 "
"`TROUBLESHOOTING.md "
"<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_。"
#: ../../source/faq.rst:160 6cb6194574c84ee989588bd042af2717
msgid "How do I generate a PDF report?"
msgstr "如何生成 PDF 报告?"
#: ../../source/faq.rst:162 c78fb658396542ecb0def972847707e5
msgid ""
"PDF support is an optional extra because it pulls heavy graphics "
"dependencies:"
msgstr "PDF 支持是一个可选附加项,因为它会引入较重的图形库依赖:"
#: ../../source/faq.rst:170 db7268f5192b4a128ba05c0a8521e70b
msgid ""
"On Linux / macOS you also need system libraries (Pango, Cairo, GDK-"
"PixBuf). Per-OS install steps are in the *Optional: PDF reports* section "
"of :doc:`installation`."
msgstr ""
"在 Linux / macOS 上还需要安装系统级图形库(Pango、Cairo、GDK-PixBuf)。各操作系统的安装步骤见 "
":doc:`installation` 中 *Optional: PDF reports* 一节。"
#: ../../source/faq.rst:174 dedfb921abb6432d95ce8379721a615a
msgid ""
"For other report formats (``--html``, ``--md``, ``--json``, ``--csv``, "
"``--txt``, ``--xmind``), see :doc:`command-line-options`."
msgstr ""
"其它报告格式(``--html``、\\ ``--md``、\\ ``--json``、\\ ``--csv``、\\ ``--txt``、\\ "
"``--xmind``)见 :doc:`command-line-options`。"
#~ msgid ""
#~ "`holehe <https://github.com/megadose/holehe>`_ — "
#~ "checks whether an email is registered"
#~ " on ~120 sites by probing their "
#~ "password-reset endpoints."
#~ msgstr ""
#~ "`holehe <https://github.com/megadose/holehe>`_ —— "
#~ "通过探测各站点的密码重置端点,检测某个邮箱是否在约 120 个网站上注册过。"
@@ -0,0 +1,557 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-25 18:02+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/features.rst:4 eca41ada7a9f4545800a2d110503a14a
msgid "Features"
msgstr "特性"
#: ../../source/features.rst:6 b24b658bc3bd4954af7bd8307d12dd42
msgid "This is the list of Maigret features."
msgstr "以下是 Maigret 的特性列表。"
#: ../../source/features.rst:11 8d83920098174365a4969a42ba7ceac6
msgid "Web Interface"
msgstr "Web 界面"
#: ../../source/features.rst:13 5957db1bd94f4d7ab214b92c06d67e73
msgid ""
"You can run Maigret with a web interface, where you can view the graph "
"with results and download reports of all formats on a single page."
msgstr "你可以以 Web 界面的方式运行 Maigret —— 在同一个页面里查看结果图谱、并下载所有格式的报告。"
#: ../../source/features.rst:16 7631566c1cfd402b80ba138921568711
msgid "Web interface: how to start"
msgstr "Web 界面:启动页"
#: ../../source/features.rst:20 deda308a1ecb4354a06c77ae44d74f64
msgid "Web interface: results"
msgstr "Web 界面:结果页"
#: ../../source/features.rst:24 035b97ee389442efa904d58e7675819d
msgid "Instructions:"
msgstr "使用步骤:"
#: ../../source/features.rst:26 7d8d5e3519cd45f8996e3901d59b4745
msgid "Run Maigret with the ``--web`` flag and specify the port number."
msgstr "使用 ``--web`` 参数运行 Maigret,并指定端口号。"
#: ../../source/features.rst:32 f99c4e96a1794fdebbca1eb8de276e7e
msgid ""
"Open http://127.0.0.1:5000 in your browser and enter one or more "
"usernames to make a search."
msgstr "在浏览器中打开 http://127.0.0.1:5000,输入一个或多个用户名以发起搜索。"
#: ../../source/features.rst:34 d7e33c3780724f95bf553821485840b8
msgid ""
"Wait a bit for the search to complete and view the graph with results, "
"the table with all accounts found, and download reports of all formats."
msgstr "稍等片刻搜索完成后,即可查看结果图谱、列出全部已发现账号的表格,并下载所有格式的报告。"
#: ../../source/features.rst:39 20f45c4b369b419e883745ee1d31152f
msgid "Telegram bot"
msgstr "Telegram 机器人"
#: ../../source/features.rst:41 06eb5827613b479583d051a01176b920
msgid ""
"A community-maintained Telegram bot lets you run Maigret without "
"installing anything locally."
msgstr "社区维护的一个 Telegram 机器人让你无需在本地安装就能运行 Maigret。"
#: ../../source/features.rst:44 7465f025cb9141fbb92af0576662eccc
msgid ""
"Working instance: `sites.google.com/view/maigret-bot-link "
"<https://sites.google.com/view/maigret-bot-link>`_ (redirect — the hosted"
" bot may move between providers)."
msgstr ""
"在线实例:`sites.google.com/view/maigret-bot-link "
"<https://sites.google.com/view/maigret-bot-link>`_\\ (跳转链接 —— "
"托管实例可能会在不同服务商之间迁移)。"
#: ../../source/features.rst:47 eb79c93a7e8b4de4a207568ffdd52b35
msgid ""
"Source code: `github.com/soxoj/maigret-tg-bot <https://github.com/soxoj"
"/maigret-tg-bot>`_."
msgstr ""
"源代码:`github.com/soxoj/maigret-tg-bot <https://github.com/soxoj/maigret-"
"tg-bot>`_。"
#: ../../source/features.rst:51 f83b69f1fcba4abeb66a12a711d151f1
msgid "Personal info gathering"
msgstr "个人信息收集"
#: ../../source/features.rst:53 2913acff61f8466e80dc03285b75c54b
msgid ""
"Maigret does the `parsing of accounts webpages and extraction "
"<https://github.com/soxoj/socid-extractor>`_ of personal info, links to "
"other profiles, etc. Extracted info displayed as an additional result in "
"CLI output and as tables in HTML and PDF reports. Also, Maigret use found"
" ids and usernames from links to start a recursive search."
msgstr ""
"Maigret 会\\ `解析账号网页并抽取 <https://github.com/soxoj/socid-extractor>`_ "
"个人信息、指向其它主页的链接等内容。抽取结果会以附加信息的形式出现在 CLI 输出中,并在 HTML 和 PDF "
"报告中以表格呈现。此外,Maigret 还会用从链接中发现的 ID 和用户名,启动递归搜索。"
#: ../../source/features.rst:57 774c604cfa0d48ac83687ae40bfb8098
msgid "Enabled by default, can be disabled with ``--no extracting``."
msgstr "默认启用,可通过 ``--no extracting`` 关闭。"
#: ../../source/features.rst:83 3e586217eafe436fbd566dc44751ae72
msgid "Recursive search"
msgstr "递归搜索"
#: ../../source/features.rst:85 98a5a5561deb463ca15440072947bd04
msgid ""
"Maigret has the ability to scan account pages for :ref:`common "
"identifiers <supported-identifier-types>` and usernames found in links. "
"When people include links to their other social media accounts, Maigret "
"can automatically detect and initiate new searches for those profiles. "
"Any information discovered through this process will be shown in both the"
" command-line interface output and generated reports."
msgstr ""
"Maigret 能在账号页面中扫描 :ref:`常见标识符 <supported-identifier-types>` "
"和链接中出现的用户名。当用户在主页里贴出指向其它社交账号的链接时,Maigret "
"会自动识别并对这些账号发起新的搜索。在此过程中发现的全部信息,都会同时显示在命令行输出和生成的报告中。"
#: ../../source/features.rst:89 305f7071bf074687aaca8511531ea186
msgid "Enabled by default, can be disabled with ``--no-recursion``."
msgstr "默认启用,可通过 ``--no-recursion`` 关闭。"
#: ../../source/features.rst:122 a1ec13a8b3fc4091a9d9aad8fc4536e2
msgid "Username permutations"
msgstr "用户名变体生成"
#: ../../source/features.rst:124 e4c00d35553049a9b73c4203e77df5f2
msgid ""
"Maigret can generate permutations of usernames. Just pass a few usernames"
" in the CLI and use ``--permute`` flag. Thanks to `@balestek "
"<https://github.com/balestek>`_ for the idea and implementation."
msgstr ""
"Maigret 可以生成用户名的各种变体组合。只需在命令行传入几个用户名并加上 ``--permute`` 参数即可。感谢 `@balestek "
"<https://github.com/balestek>`_ 提供的想法和实现。"
#: ../../source/features.rst:149 9dad12744e69420aa749fcf646f5fc8f
msgid "Reports"
msgstr "报告"
#: ../../source/features.rst:151 e70802a82a1f4c7d978558399b52c3ed
msgid ""
"Maigret currently supports HTML, PDF, TXT, XMind 8 mindmap, and JSON "
"reports."
msgstr "Maigret 目前支持 HTML、PDF、TXT、XMind 8 思维导图和 JSON 等格式的报告。"
#: ../../source/features.rst:153 f81c37a3ae254c37a69c1e8fad6a3fa0
msgid "HTML/PDF reports contain:"
msgstr "HTML / PDF 报告包含以下内容:"
#: ../../source/features.rst:155 4c8b2dab41734e20b0266c76890bcd89
msgid "profile photo"
msgstr "头像"
#: ../../source/features.rst:156 c642caf9ffcf4136ae84e93354e19313
msgid "all the gathered personal info"
msgstr "已收集到的全部个人信息"
#: ../../source/features.rst:157 9744f896e29f47a09724faa36f4f126b
msgid ""
"additional information about supposed personal data (full name, gender, "
"location), resulting from statistics of all found accounts"
msgstr "基于所有已发现账号的统计,推测出的额外个人数据(真实姓名、性别、所在地)"
#: ../../source/features.rst:159 cdfdc97558fa4c278e462f6bcf9c2fd1
msgid ""
"Also, there is a short text report in the CLI output after the end of a "
"searching phase."
msgstr "此外,在搜索结束后,CLI 输出中也会附上一份简短的文本报告。"
#: ../../source/features.rst:162 1389dbc9efba49b499252807bb1eb12b
msgid "XMind 8 mindmaps are incompatible with XMind 2022!"
msgstr "XMind 8 思维导图与 XMind 2022 不兼容!"
#: ../../source/features.rst:165 7855ecf44a5a4139ac24cfac44f659ff
msgid "AI analysis"
msgstr "AI 分析"
#: ../../source/features.rst:167 a7bc7ddbf02841a7acf6773a6aec39b5
msgid ""
"Maigret can produce a short, human-readable investigation summary on top "
"of the raw search results using the ``--ai`` flag. It builds the internal"
" Markdown report, sends it to an OpenAI-compatible chat completion "
"endpoint, and streams the model's reply directly to the terminal."
msgstr ""
"通过 ``--ai`` 参数,Maigret 可以基于原始搜索结果生成一份简短、易读的调查摘要。它会先构建一份内部 Markdown "
"报告,将其发送至一个 OpenAI 兼容的 chat completion 接口,并把模型的回复以流式方式直接输出到终端。"
#: ../../source/features.rst:178 47a99e86b6ae4a1588e09ef7fd44ea0c
msgid ""
"The summary uses a fixed format with the most likely real name, location,"
" occupation, interests, languages, main website, username variants, "
"number of platforms, active years, a confidence rating, and a short list "
"of follow-up leads. While ``--ai`` is active, per-site progress and the "
"short text report are suppressed so the streamed summary is the main "
"output."
msgstr ""
"该摘要采用固定格式,包含最可能的真实姓名、所在地、职业、兴趣、语言、主要使用的网站、用户名变体、出现过的平台数量、活跃年份、置信度评级,以及一份简短的后续线索清单。\\"
" ``--ai`` 启用期间,逐站点进度和简短文本报告都会被抑制,以让这份流式摘要成为主要输出。"
#: ../../source/features.rst:185 85733bb1fa11414dba02e6f0627cb59e
msgid ""
"The endpoint, model, and API key are configured via ``settings.json`` "
"(``openai_api_key``, ``openai_model``, ``openai_api_base_url``) or the "
"``OPENAI_API_KEY`` environment variable. Any OpenAI-compatible API can be"
" used (Azure OpenAI, OpenRouter, a local server, …). See :ref:`ai-"
"analysis` and :ref:`settings` for details."
msgstr ""
"接口地址、模型名和 API key 可通过 ``settings.json``\\ (对应字段 ``openai_api_key``\\ 、\\ "
"``openai_model``\\ 、\\ ``openai_api_base_url``)或 ``OPENAI_API_KEY`` "
"环境变量进行配置。可使用任意 OpenAI 兼容接口(Azure OpenAI、OpenRouter、本地推理服务等)。详情参见 :ref"
":`ai-analysis` 与 :ref:`settings`\\ 。"
#: ../../source/features.rst:192 8bba3181c9ec4009badf240909ff5ba2
msgid "Tags"
msgstr "标签"
#: ../../source/features.rst:194 ac119f2a446745e19e1e0fc51f4ca717
msgid ""
"The Maigret sites database very big (and will be bigger), and it is maybe"
" an overhead to run a search for all the sites. Also, it is often hard to"
" understand, what sites more interesting for us in the case of a certain "
"person."
msgstr ""
"Maigret "
"的站点数据库已经很庞大(并且还会继续扩张),对所有站点都跑一遍搜索未必划算。同时,在调查某个具体人物时,也不容易判断哪些站点更值得关注。"
#: ../../source/features.rst:197 c159b2fb30c0405d83ac51d2f7501c02
msgid ""
"Tags markup allows selecting a subset of sites by interests (photo, "
"messaging, finance, etc.) or by country. Tags of found accounts grouped "
"and displayed in the reports."
msgstr "标签体系允许按兴趣(摄影、即时通讯、金融等)或国家挑选站点子集。已发现账号上的标签会被分组,并显示在报告中。"
#: ../../source/features.rst:199 ed4a4e7541064e3687afe3712d64d77d
msgid "See full description :doc:`in the Tags Wiki page <tags>`."
msgstr "完整说明请参见 :doc:`标签页 <tags>`\\ 。"
#: ../../source/features.rst:202 96dc25e63f79489ba87542fdedd8123f
msgid "Censorship and captcha detection"
msgstr "审查和 CAPTCHA 检测"
#: ../../source/features.rst:204 c05b9f998478422b9c480a2191f383dd
#, python-format
msgid ""
"Maigret can detect common errors such as censorship stub pages, "
"CloudFlare captcha pages, and others. If you get more them 3% errors of a"
" certain type in a session, you've got a warning message in the CLI "
"output with recommendations to improve performance and avoid problems."
msgstr ""
"Maigret 能够识别一些常见错误,例如审查占位页、CloudFlare 验证码页等等。如果在一次会话中某类错误超过了 3%,CLI "
"输出里就会打印一条警告,并附上用于改善表现、规避问题的建议。"
#: ../../source/features.rst:208 ad8458e0de4a4b019049b57af0ee2460
msgid "Retries"
msgstr "重试"
#: ../../source/features.rst:210 64853719d00a4542a5a3c4e33e5fda82
msgid ""
"Maigret will do retries of the requests with temporary errors got "
"(connection failures, proxy errors, etc.)."
msgstr "对于遇到临时性错误(连接失败、代理错误等)的请求,Maigret 会进行重试。"
#: ../../source/features.rst:212 712591ee78254a809879ad313f69220f
msgid "One attempt by default, can be changed with option ``--retries N``."
msgstr "默认尝试一次,可通过 ``--retries N`` 参数调整。"
#: ../../source/features.rst:215 e20338540d6847bda34963bb81d8db8f
msgid "Database self-check"
msgstr "数据库自检"
#: ../../source/features.rst:217 37eb44c6abd440708fbbbd72785d0d9d
msgid ""
"Maigret includes a self-check mode (``--self-check``) that validates "
"every site in the database by looking up its known-claimed and known-"
"unclaimed usernames and verifying that the detection results match "
"expectations."
msgstr ""
"Maigret 内置了一个自检模式(``--self-"
"check``),它会针对数据库中每个站点,分别查询已知被占用和已知未被占用的用户名,并验证检测结果是否符合预期。"
#: ../../source/features.rst:221 d1e875423487428381e6bc8fa17867bd
msgid ""
"The self-check is **error-resilient**: if an individual site check raises"
" an unexpected exception (e.g. a network error or a parsing failure), the"
" error is caught, logged, and recorded as an issue — the remaining sites "
"continue to be checked without interruption. This means the process "
"always runs to completion, even when checking hundreds of sites with ``-a"
" --self-check``."
msgstr ""
"自检过程是\\ "
"**容错的**:如果某个站点的检查抛出意外异常(例如网络错误或解析失败),该错误会被捕获并记录为一个问题项,而其余站点会继续被检查,不会被中断。因此,即便使用"
" ``-a --self-check`` 检查上百个站点,该流程也总能顺利跑完。"
#: ../../source/features.rst:227 d75031d71d3941528aa337fbf56cbc8d
msgid ""
"Use ``--auto-disable`` together with ``--self-check`` to automatically "
"disable sites that fail checks. Without it, issues are only reported. Use"
" ``--diagnose`` to print detailed per-site diagnosis including the check "
"type, specific issues, and recommendations."
msgstr ""
"配合 ``--self-check`` 使用 ``--auto-disable`` "
"可以把检查失败的站点自动禁用;不加该参数时,问题项只会被报告出来,而不会真正禁用。使用 ``--diagnose`` "
"则可以打印每个站点的详细诊断信息,包括检查类型、具体问题和修复建议。"
#: ../../source/features.rst:244 97ad227189b34c2b8e80b9055dd0f48e
msgid "Archives and mirrors checking"
msgstr "归档站与镜像站检查"
#: ../../source/features.rst:246 cab6bb0dc9e34379ae002ec46981c079
msgid ""
"The Maigret database contains not only the original websites, but also "
"mirrors, archives, and aggregators. For example:"
msgstr "Maigret 数据库中不仅包含原始网站,还包含镜像、归档和聚合站点。例如:"
#: ../../source/features.rst:248 500d9a939ca14b2a981b1c9435cdcd73
msgid "`Picuki <https://www.picuki.com/>`_, Instagram mirror"
msgstr "`Picuki <https://www.picuki.com/>`_,Instagram 镜像"
#: ../../source/features.rst:249 53f6db7eb7044cf294720e6239282686
msgid ""
"(no longer available) `Reddit BigData search <https://camas.github.io"
"/reddit-search/>`_"
msgstr ""
"(已不再可用)\\ `Reddit BigData search <https://camas.github.io/reddit-"
"search/>`_"
#: ../../source/features.rst:250 60124ff7e1e244d79c62846abb0cabbe
msgid "(no longer available) `Twitter shadowban <https://shadowban.eu/>`_ checker"
msgstr "(已不再可用)\\ `Twitter shadowban <https://shadowban.eu/>`_ 检查器"
#: ../../source/features.rst:252 44ebab596acb41229bbe2ea989503b85
msgid ""
"It allows getting additional info about the person and checking the "
"existence of the account even if the main site is unavailable (bot "
"protection, captcha, etc.)"
msgstr "通过这些站点,可以获取关于目标人物的额外信息;即便主站点不可用(机器人防护、CAPTCHA 等),也仍能验证账号是否存在。"
#: ../../source/features.rst:257 ff0ab7835ec44e148d82397581bb4290
msgid "Cloudflare webgate bypass"
msgstr "Cloudflare webgate 绕过"
#: ../../source/features.rst:261 99737687941844dfae996c262a4b1a85
msgid ""
"**Experimental feature.** The Cloudflare webgate is under active "
"development. The configuration schema, CLI flag behaviour, and the set of"
" sites that route through it may change without backwards-compatibility "
"guarantees. Expect rough edges (CF rate limits, occasional solver "
"failures) and report issues so they can be ironed out."
msgstr ""
"**实验性特性。** Cloudflare webgate 正处于积极开发阶段。配置项、CLI "
"参数行为以及经由其路由的站点集合都可能发生变化,不保证向后兼容。你可能会遇到一些不完善之处(CF 限速、求解器偶尔失败等),欢迎反馈以便逐步打磨。"
#: ../../source/features.rst:267 d67e2b25a2544422861e89866939e627
msgid ""
"Some sites sit behind a full Cloudflare JavaScript challenge or a CF "
"firewall hard block — these are tagged ``protection: "
"[\"cf_js_challenge\"]`` or ``protection: [\"cf_firewall\"]`` in the "
"database and are normally kept disabled because neither aiohttp nor "
"curl_cffi can solve the JS challenge on their own."
msgstr ""
"部分站点位于完整的 Cloudflare JavaScript 挑战或 CF 防火墙硬封锁之后 —— 它们在数据库中被打上 "
"``protection: [\"cf_js_challenge\"]`` 或 ``protection: [\"cf_firewall\"]``"
" 标记,并通常被禁用,因为 aiohttp 和 curl_cffi 都无法自行解出 JS 挑战。"
#: ../../source/features.rst:272 887722f5e6c9401fb789e6ada2e62b2c
msgid ""
"Maigret can offload these checks to a local Chrome-based solver. Two "
"backends are supported, configured in ``settings.json`` under "
"``cloudflare_bypass.modules`` (the first reachable module wins; "
"subsequent ones are tried as a fallback chain):"
msgstr ""
"Maigret 可以把这类检查转交给本地的 Chrome 类求解器。目前支持两种后端,通过 ``settings.json`` 中的 "
"``cloudflare_bypass.modules`` 配置(第一个能连通的模块生效,后续模块作为兜底链依次尝试):"
#: ../../source/features.rst:277 603de15a7c18429a94037f714af3e69f
msgid ""
"**FlareSolverr** (recommended). Runs a real Chrome instance and exposes a"
" JSON API. The upstream HTTP status, headers and final URL are preserved,"
" so ``checkType: status_code`` and ``checkType: response_url`` keep "
"working through the bypass."
msgstr ""
"**FlareSolverr**\\ (推荐)。运行一个真实的 Chrome 实例,并暴露 JSON API。上游真实的 HTTP "
"状态码、响应头和最终 URL 都会被保留,因此 ``checkType: status_code`` 和 ``checkType: "
"response_url`` 在绕过模式下依然能正常工作。"
#: ../../source/features.rst:286 54d61596bbce43e8a82cb1141379a276
msgid ""
"**CloudflareBypassForScraping** (legacy fallback). Returns rendered HTML "
"only, so the upstream status code is lost — ``checkType: message`` keeps "
"working but ``status_code`` checks misfire (treated as 200 on success)."
msgstr ""
"**CloudflareBypassForScraping**\\ (旧版兜底)。只返回渲染后的 HTML,因此上游真实状态码会丢失 —— "
"``checkType: message`` 仍然可用,但 ``status_code`` 类检查会失准(成功时会被视为 200)。"
#: ../../source/features.rst:290 d6f5bd6b3ac043e1a9153827b0492c00
msgid "Activate the bypass either with the CLI flag::"
msgstr "启用绕过有两种方式,一是命令行参数::"
#: ../../source/features.rst:294 8a9237637b414665bc1a30722539d650
msgid ""
"or by setting ``cloudflare_bypass.enabled`` to ``true`` in "
"``settings.json``. The web UI (``python -m maigret.web.app``) reads the "
"same setting — there is no separate toggle in the form, so flipping "
"``enabled`` is what activates the bypass for browser-driven runs."
msgstr ""
"或在 ``settings.json`` 中将 ``cloudflare_bypass.enabled`` 设为 ``true``。"
"Web 界面(``python -m maigret.web.app``)读取的是同一份配置 —— 表单中"
"没有单独的开关,因此切换 ``enabled`` 就是浏览器场景下启用绕过的唯一方式。"
#: ../../source/features.rst:299 8cd7c8ef31e845cfa7bef77caa8b4a47
msgid ""
"The bypass only fires for sites whose ``protection`` field intersects "
"``cloudflare_bypass.trigger_protection`` (default ``[\"cf_js_challenge\","
" \"cf_firewall\", \"webgate\"]``); all other sites use the normal aiohttp"
" / curl_cffi path."
msgstr ""
"该绕过只对 ``protection`` 字段与 ``cloudflare_bypass.trigger_protection``"
"\\ (默认 ``[\"cf_js_challenge\", \"cf_firewall\", \"webgate\"]``)有交集"
"的站点生效;其它站点仍走常规的 aiohttp / curl_cffi 路径。"
#: ../../source/features.rst:304 98d59ab2376c44fc89adf426a612366d
msgid ""
"If all configured modules are unreachable, affected sites get an UNKNOWN "
"status with an actionable error pointing at the first module's URL — the "
"fix is almost always to start the FlareSolverr container."
msgstr ""
"如果所有配置的模块都不可达,受影响的站点会被标记为 UNKNOWN,并附上一条可操作的错误信息,指向首个模块的 URL —— "
"绝大多数情况下,只需把 FlareSolverr 容器跑起来就能解决。"
#: ../../source/features.rst:308 2ee4a6ee33f847ce87472cd442bcd18e
msgid ""
"FlareSolverr session reuse is automatic: Maigret pins a single ``session:"
" <session_prefix>-<pid>`` per run, so cf_clearance cookies are shared "
"between checks of the same domain (510× faster on subsequent requests to"
" that host)."
msgstr ""
"FlareSolverr 的 session 复用是自动的:Maigret 会在每次运行时固定一个 ``session: "
"<session_prefix>-<pid>``,这样对同一域名的多次检查就会共享 cf_clearance cookie(后续对该主机的请求会快"
" 510 倍)。"
#: ../../source/features.rst:314 3f43bfb6c70e48e285a7cc862d7988e6
msgid "Activation"
msgstr "激活机制"
#: ../../source/features.rst:315 f3fbc5aaaadc475cb27e6050ed392c72
msgid ""
"The activation mechanism helps make requests to sites requiring "
"additional authentication like cookies, JWT tokens, or custom headers."
msgstr "激活机制用于向那些需要额外认证(cookie、JWT token、自定义请求头等)的站点发起请求。"
#: ../../source/features.rst:317 8904ac193b06468aa8c2a0eac03387f0
msgid "It works by implementing a custom function that:"
msgstr "其实现方式是通过一个自定义函数,完成以下三步:"
#: ../../source/features.rst:319 9ff5a4ab39284a55aed726ed75aebe6e
msgid "Makes a specialized HTTP request to a specific website endpoint"
msgstr "向某个特定网站端点发起一个专门的 HTTP 请求"
#: ../../source/features.rst:320 c96edc4ff8654074b71ce45af7fa84c1
msgid "Processes the response"
msgstr "处理响应内容"
#: ../../source/features.rst:321 418b7eb1dd824175b3fad35d3ea9d33c
msgid "Updates the headers/cookies for that site in the local Maigret database"
msgstr "在本地 Maigret 数据库中更新该站点对应的请求头/cookie"
#: ../../source/features.rst:323 30f2c88b051546539456ce0c98957a82
msgid ""
"Since activation only triggers after encountering specific errors, a "
"retry (or another Maigret run) is needed to obtain a valid response with "
"the updated authentication."
msgstr "由于激活只在遇到特定错误后才会触发,因此要拿到使用新认证信息的有效响应,还需要再触发一次重试(或重新运行一次 Maigret)。"
#: ../../source/features.rst:325 9da1a6a8d5d848e7a9987a6e864d6da5
msgid ""
"The activation mechanism is enabled by default, and cannot be disabled at"
" the moment."
msgstr "激活机制默认启用,目前无法关闭。"
#: ../../source/features.rst:327 0573ae8856364e819e56931340bc6fb7
msgid "See for more details in Development section :ref:`activation-mechanism`."
msgstr "更多细节请参见开发文档中的 :ref:`activation-mechanism` 一节。"
#: ../../source/features.rst:332 efe6f17c461a44e099b06d44a597b5b2
msgid "Extraction of information from account pages"
msgstr "从账号页面抽取信息"
#: ../../source/features.rst:334 c820edcca1184d4382c8a57eded6cc47
msgid ""
"Maigret can parse URLs and content of web pages by URLs to extract info "
"about account owner and other meta information."
msgstr "Maigret 可以根据给定 URL 解析网页地址及其内容,抽取账号所有者及其它元信息。"
#: ../../source/features.rst:336 540ba0732e5348e0838f7cdafcdee244
msgid ""
"You must specify the URL with the option ``--parse``, it's can be a link "
"to an account or an online document. List of supported sites `see here "
"<https://github.com/soxoj/socid-extractor#sites>`_."
msgstr ""
"需要使用 ``--parse`` 选项指定 URL,可以是某个账号的链接,也可以是在线文档的链接。支持的站点列表请\\ `查看此处 "
"<https://github.com/soxoj/socid-extractor#sites>`_。"
#: ../../source/features.rst:338 7db26f80206f49e4ac99971c8829c8ef
msgid ""
"After the end of the parsing phase, Maigret will start the search phase "
"by :doc:`supported identifiers <supported-identifier-types>` found "
"(usernames, ids, etc.)."
msgstr ""
"解析阶段结束后,Maigret 会根据从中找到的 :doc:`受支持标识符 <supported-identifier-types>`\\ "
"(用户名、ID 等)进入搜索阶段。"
#: ../../source/features.rst:366 a1bd40f9e8a24be2b76cb9fbdb3c03e1
msgid "Simple API"
msgstr "简单 API"
#: ../../source/features.rst:368 c032748ecb4847ecb5590a4fa6629ba0
msgid ""
"Maigret can be easily integrated with the use of Python package `maigret "
"<https://pypi.org/project/maigret/>`_."
msgstr ""
"借助 Python 包 `maigret <https://pypi.org/project/maigret/>`_,可以很方便地把 "
"Maigret 集成到自己的项目中。"
#: ../../source/features.rst:370 115fa7827cea4dc68871f2a516999f7d
msgid ""
"Example: the community `Telegram bot <https://github.com/soxoj/maigret-"
"tg-bot>`_"
msgstr "示例:社区维护的 `Telegram 机器人 <https://github.com/soxoj/maigret-tg-bot>`_"
#: ../../source/features.rst:151
msgid ""
"Maigret currently supports HTML, PDF, TXT, CSV, XMind 8 mindmap, JSON, "
"and Markdown reports, plus graph exports: an interactive HTML graph "
"(``--graph``) and a Neo4j Cypher script (``--neo4j``) for loading the "
"results into a graph database."
msgstr "Maigret 目前支持 HTML、PDF、TXT、CSV、XMind 8 思维导图、JSON 和 Markdown 报告,以及图谱导出:交互式 HTML 图谱(``--graph``)和 Neo4j Cypher 脚本(``--neo4j``,用于将结果载入图数据库)。"
#: ../../source/features.rst:164
msgid ""
"The ``--neo4j`` flag serialises the same graph that ``--graph`` builds "
"into an idempotent ``.cypher`` script, importable with ``cypher-shell`` "
"or the Neo4j Browser. See :ref:`neo4j-export` for the schema and import "
"instructions."
msgstr "``--neo4j``\\ 选项会把 ``--graph``\\ 所构建的同一图谱序列化为一个幂等的 ``.cypher``\\ 脚本,可用 ``cypher-shell``\\ 或 Neo4j Browser 导入。其结构与导入说明详见 :ref:`neo4j-export`\\ 。"
@@ -0,0 +1,115 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-23 09:20+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/index.rst:36
msgid "Sections"
msgstr "目录"
#: ../../source/index.rst:51
msgid "Advanced usage"
msgstr "进阶用法"
#: ../../source/index.rst:59
msgid "Use cases"
msgstr "使用案例"
#: ../../source/index.rst:4 d318259213394f2d875338f2252e31e2
msgid "Welcome to the Maigret docs!"
msgstr "欢迎阅读 Maigret 文档!"
#: ../../source/index.rst:6 e4629e1ffd874446b8f7835dc05fde8a
msgid ""
"**Maigret** is an easy-to-use and powerful OSINT tool for collecting a "
"dossier on a person by a username (alias) only."
msgstr "**Maigret** 是一款简单易用而又强大的 OSINT 工具,仅凭一个用户名(别名)即可为目标人物生成一份档案。"
#: ../../source/index.rst:8 9edc9c82d8554bcba5891fbdb4c09f30
msgid ""
"This is achieved by checking for accounts on a huge number of sites and "
"gathering all the available information from web pages."
msgstr "实现方式是:在大量站点上查找该用户名对应的账号,并从相关网页中收集所有可获取的公开信息。"
#: ../../source/index.rst:10 3b64dbc62d2846a5b272a673960df8a1
msgid ""
"The project's main goal — give to OSINT researchers and pentesters a "
"**universal tool** to get maximum information about a person of interest "
"by a username and integrate it with other tools in automatization "
"pipelines."
msgstr ""
"项目的主要目标 —— 为 OSINT 研究人员和渗透测试人员提供一款\\ "
"**通用工具**,使其能够仅凭用户名获取关于目标人物的最大化信息,并将该工具集成到自动化流水线中与其它工具协同使用。"
#: ../../source/index.rst:14 0bcc4da8c5e24b7492cc983360da8498
msgid ""
"**This tool is intended for educational and lawful purposes only.** The "
"developers do not endorse or encourage any illegal activities or misuse "
"of this tool. Regulations regarding the collection and use of personal "
"data vary by country and region, including but not limited to GDPR in the"
" EU, CCPA in the USA, and similar laws worldwide."
msgstr ""
"**本工具仅供教育及合法用途使用。** "
"开发者不认可、亦不鼓励任何非法行为或对本工具的滥用。关于个人数据收集和使用的法律法规因国家和地区而异,包括但不限于欧盟 GDPR、美国 "
"CCPA,以及世界各地与之类似的法律。"
#: ../../source/index.rst:19 b4878674298248f3b2b7c26e26947dc9
msgid ""
"It is your sole responsibility to ensure that your use of this tool "
"complies with all applicable laws and regulations in your jurisdiction. "
"Any illegal use of this tool is strictly prohibited, and you are fully "
"accountable for your actions."
msgstr "使用者需独立承担相应责任,确保自身对本工具的使用符合所在司法辖区的全部相关法律法规。严禁以任何非法方式使用本工具;由此产生的一切后果概由使用者本人承担。"
#: ../../source/index.rst:23 af1975d9166b40679d670b8cab1643a4
msgid ""
"The authors and developers of this tool bear no responsibility for any "
"misuse or unlawful activities conducted by its users."
msgstr "对于使用者实施的任何滥用或违法行为,本工具的作者与开发者概不负责。"
#: ../../source/index.rst:27 42ec2bdda2f8465cbb8a42b65b6dcf9e
msgid "You may be interested in:"
msgstr "你可能会感兴趣的内容:"
#: ../../source/index.rst:28 af3ee16dfae24fcda671492cd9405c33
msgid ":doc:`Quick start <quick-start>`"
msgstr ":doc:`快速入门 <quick-start>`"
#: ../../source/index.rst:29 61982997ec074ab48a4fc94efc2fec42
msgid ":doc:`Usage examples <usage-examples>`"
msgstr ":doc:`使用示例 <usage-examples>`"
#: ../../source/index.rst:30 5a2e6eec62ad49c297d7edf16fd64a7a
msgid ":doc:`FAQ <faq>`"
msgstr ":doc:`常见问题 <faq>`"
#: ../../source/index.rst:31 847ca51eb7774066a6b316c86f47970e
msgid ":doc:`Command line options <command-line-options>`"
msgstr ":doc:`命令行选项 <command-line-options>`"
#: ../../source/index.rst:32 ddb043bd72714e37b7f7d30f84c43c56
msgid ":doc:`Features list <features>`"
msgstr ":doc:`特性列表 <features>`"
#: ../../source/index.rst:33 9ed0d068b9574bb8b7f30d4acc657795
msgid ":doc:`Library usage <library-usage>`"
msgstr ":doc:`库使用 <library-usage>`"
#: ../../source/index.rst:34 e7ce2f8ada8942c1b0d43ca57f52b24f
msgid ":doc:`Tor, I2P, and proxies <tor-and-proxies>`"
msgstr ":doc:`Tor、I2P 与代理 <tor-and-proxies>`"
@@ -0,0 +1,406 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-23 10:03+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/installation.rst:4 65ec572b79974a2b8617da6557093010
msgid "Installation"
msgstr "安装"
#: ../../source/installation.rst:6 672da475753e42ba98d2da7764d622f2
msgid ""
"Maigret can be installed using pip, Docker, or simply can be launched "
"from the cloned repo. Also, it is available online via the `community "
"Telegram bot <https://sites.google.com/view/maigret-bot-link>`_, source "
"code of a bot is `available on GitHub <https://github.com/soxoj/maigret-"
"tg-bot>`_."
msgstr ""
"Maigret 可以通过 pip 或 Docker 安装,也可以直接从克隆下来的仓库运行。此外,还可以在线使用\\ `社区 Telegram "
"机器人 <https://sites.google.com/view/maigret-bot-link>`_,其源码 `已在 GitHub 上开源"
" <https://github.com/soxoj/maigret-tg-bot>`_。"
#: ../../source/installation.rst:11 09b47ba6acdb4e18af6e1ecc069a8e6c
msgid "Windows Standalone EXE-binaries"
msgstr "Windows 独立 EXE 二进制文件"
#: ../../source/installation.rst:13 3f62cebffe3e42a2a70477cec9f7b968
msgid ""
"A standalone ``maigret_standalone.exe`` for Windows is published in the "
"`Releases section <https://github.com/soxoj/maigret/releases>`_ of the "
"GitHub repository. A fresh build is produced automatically after each "
"commit to the **main** and **dev** branches."
msgstr ""
"用于 Windows 的独立程序 ``maigret_standalone.exe`` 发布在 GitHub 仓库的\\ `Releases 页面"
" <https://github.com/soxoj/maigret/releases>`_。每次合并到 **main** 与 **dev** "
"分支后,都会自动构建一个新版本。"
#: ../../source/installation.rst:18 c7dc8ed554194a0986d61bad0ecc8dbc
msgid "There are two ways to launch the EXE:"
msgstr "启动该 EXE 的方式有两种:"
#: ../../source/installation.rst:20 615cf386dba948059a0081c26abe2124
msgid ""
"**Double-click it from Explorer.** Maigret will prompt you for a "
"username, run a default search, and pause at the end so the printed "
"report links remain on screen until you press Enter."
msgstr ""
"**在资源管理器中双击运行。** Maigret 会让你输入一个用户名,执行一次默认搜索,并在结束时暂停,这样屏幕上打印的报告链接会保留到你按下 "
"Enter 为止。"
#: ../../source/installation.rst:23 691f5aee31f74e6195a90017856889ec
msgid "**Run it from a terminal** for full control over options:"
msgstr "**从终端启动**,以便完整控制各项参数:"
#: ../../source/installation.rst:25 cdf73ace14e74e57badedcbb0595b332
msgid "Press ``Win+R``, type ``cmd``, and hit Enter (or use PowerShell)."
msgstr "按 ``Win+R``,输入 ``cmd`` 后回车(或使用 PowerShell)。"
#: ../../source/installation.rst:26 e201a1c4a85a4fadb1a012c94873b34d
msgid ""
"Change to the folder where you saved the file, e.g. ``cd "
"%USERPROFILE%\\Downloads``."
msgstr "切换到你保存 EXE 文件的目录,例如 ``cd %USERPROFILE%\\Downloads``\\ 。"
#: ../../source/installation.rst:28 7ef431d9114647ceb3c892d61fcca306
msgid "Run it with at least one username:"
msgstr "至少传入一个用户名后再运行:"
#: ../../source/installation.rst:37 0e249c27e4934f0dbb1f826b88fa085f
msgid "Reports are written next to the EXE in a ``reports\\`` subfolder."
msgstr "报告会写入 EXE 同级的 ``reports\\`` 子目录。"
#: ../../source/installation.rst:39 236594a8b8e04daebe4a59f74aa7e8e7
msgid "Video guide on how to run it: https://youtu.be/qIgwTZOmMmM."
msgstr "如何运行的视频指引:https://youtu.be/qIgwTZOmMmM。"
#: ../../source/installation.rst:43 345ab80862ae4393965a1ae62c6f9225
msgid "Cloud Shells and Jupyter notebooks"
msgstr "云端 Shell 与 Jupyter Notebook"
#: ../../source/installation.rst:45 fbe1675fd2dc41e48a3f29c4e008700e
msgid ""
"In case you don't want to install Maigret locally, you can use cloud "
"shells and Jupyter notebooks. Press one of the buttons below and follow "
"the instructions to launch it in your browser."
msgstr "如果你不想在本地安装 Maigret,可以使用云端 Shell 或 Jupyter Notebook。点击下方任一按钮,按提示在浏览器中启动即可。"
#: ../../source/installation.rst:50 a23f0d4b7bd94cd6967787bd4d2c7bed
msgid "Open in Cloud Shell"
msgstr "在 Cloud Shell 中打开"
#: ../../source/installation.rst:54 6e7de23fcf6e4e6a81536f0af747b9df
msgid "Run on Replit"
msgstr "在 Replit 上运行"
#: ../../source/installation.rst:59 087c932a0a914b5baaf8a0522a8bf892
msgid "Open In Colab"
msgstr "在 Colab 中打开"
#: ../../source/installation.rst:64 eda20a4373ec47cf999aeb50e11a0a30
msgid "Open In Binder"
msgstr "在 Binder 中打开"
#: ../../source/installation.rst:71 0dbe84b6c46e41ebac52324d23a53926
msgid ""
"Cloud Shell: "
"https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/soxoj/maigret&tutorial=README.md"
msgstr ""
"Cloud Shell:"
"https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/soxoj/maigret&tutorial=README.md"
#: ../../source/installation.rst:73 dec4f6a14fff41c588d3dfe7307474fa
msgid "Replit: https://repl.it/github/soxoj/maigret"
msgstr "Replit:https://repl.it/github/soxoj/maigret"
#: ../../source/installation.rst:75 64bc9d1b5e4b431c84de9fd7d8e7b56e
msgid ""
"Google Colab: "
"https://colab.research.google.com/gist/soxoj/879b51bc3b2f8b695abb054090645000"
"/maigret-collab.ipynb"
msgstr ""
"Google Colab:"
"https://colab.research.google.com/gist/soxoj/879b51bc3b2f8b695abb054090645000"
"/maigret-collab.ipynb"
#: ../../source/installation.rst:77 b23a024d4da346d38434796ae40cfb1b
msgid ""
"Binder: "
"https://mybinder.org/v2/gist/soxoj/9d65c2f4d3bec5dd25949197ea73cf3a/HEAD"
msgstr ""
"Binder:"
"https://mybinder.org/v2/gist/soxoj/9d65c2f4d3bec5dd25949197ea73cf3a/HEAD"
#: ../../source/installation.rst:80 cf642441c7ad4ce6a077b082c9d12948
msgid "Local installation from PyPi"
msgstr "通过 PyPI 本地安装"
#: ../../source/installation.rst:82 cd52af3ccddc498aaee8a9c7d47d9138
msgid ""
"Maigret ships with a bundled site database. After installation from PyPI "
"(or any other method), it can **automatically fetch a newer compatible "
"database from GitHub** when you run it—see :ref:`database-auto-update` in"
" :doc:`settings`."
msgstr ""
"Maigret 自带一份站点数据库。无论通过 PyPI 还是其它方式安装完毕后,在每次运行时,它都可以\\ **自动从 GitHub "
"拉取兼容的更新版数据库** —— 详见 :doc:`settings` 中的 :ref:`database-auto-update`\\ 。"
#: ../../source/installation.rst:85 23ec41899c0140918a3137e15b14c404
msgid "Python 3.10 or higher and pip is required, **Python 3.11 is recommended.**"
msgstr "需要 Python 3.10 或更高版本以及 pip,\\ **推荐使用 Python 3.11。**"
#: ../../source/installation.rst:95 368a3bbb1b3243fb89e88a73ad2d2319
msgid ""
"PDF report support is shipped as an **optional extra** because it relies "
"on system-level graphics libraries that pip cannot install for you. If "
"you plan to use ``--pdf``, install Maigret with the ``pdf`` extra:"
msgstr ""
"PDF 报告支持作为\\ **可选扩展**\\ 单独提供,因为它依赖一些 pip 无法替你安装的系统级图形库。如果你打算使用 "
"``--pdf``,请带上 ``pdf`` extra 进行安装:"
#: ../../source/installation.rst:103 6f105be5f2b746559320d154598fe441
msgid ""
"See :ref:`pdf-extra` below for the full background on why PDF support is "
"optional and how to fix the most common build errors."
msgstr "关于 PDF 为何是可选项,以及最常见构建错误的修复方法,请见下文 :ref:`pdf-extra`\\ 。"
#: ../../source/installation.rst:107 c40814fb664941eab04c0db77f59fdcc
msgid "Development version (GitHub)"
msgstr "开发版本(GitHub)"
#: ../../source/installation.rst:125 4335f4b3b2bc422b84577aaf71a14404
msgid "Docker"
msgstr "Docker"
#: ../../source/installation.rst:139 88ca87d239464cd9a746d476bb0604e0
msgid "Troubleshooting"
msgstr "故障排查"
#: ../../source/installation.rst:141 5a58ace323ad4422b6bbdb4e8aa76400
msgid ""
"If you encounter build errors during installation such as ``cannot find "
"ft2build.h`` or errors related to ``reportlab`` / ``_renderPM``, you need"
" to install system-level dependencies required to compile native "
"extensions."
msgstr ""
"如果你在安装过程中遇到 ``cannot find ft2build.h``,或与 ``reportlab`` / ``_renderPM`` "
"相关的构建错误,那就需要先安装编译原生扩展所需的系统级依赖。"
#: ../../source/installation.rst:145 04b94becaa584dc08daef25362ee93b5
msgid "**Debian/Ubuntu/Kali:**"
msgstr "**Debian / Ubuntu / Kali:**"
#: ../../source/installation.rst:151 36f58a3216b146719fd706a5c7d02e01
msgid "**Fedora/RHEL/CentOS:**"
msgstr "**Fedora / RHEL / CentOS:**"
#: ../../source/installation.rst:157 ../../source/installation.rst:242
#: 0042f1613fe7432d9e8a54d1ce76c351 8a5a2d81fefe41e99521afdc646122f0
msgid "**Arch Linux:**"
msgstr "**Arch Linux:**"
#: ../../source/installation.rst:163 ../../source/installation.rst:256
#: a9ba82933f014aadabe3f45946cc3bff c13ecc1e9acd40f9ad11eea33f9854f8
msgid "**macOS (Homebrew):**"
msgstr "**macOS(Homebrew):**"
#: ../../source/installation.rst:169 9e67043b0a664a0ca57bb9013de182df
msgid "After installing the system dependencies, retry the maigret installation."
msgstr "安装好上述系统依赖后,再重新安装 maigret。"
#: ../../source/installation.rst:171 e83ac2ad5cd945109734e3fc34a0b5f2
msgid ""
"If you continue to have issues, consider using Docker instead, which "
"includes all necessary dependencies."
msgstr "如果仍有问题,可以考虑改用 Docker —— 其镜像中已包含所有必需的依赖。"
#: ../../source/installation.rst:177 b0bf899a36ed4bf087844c1f8c490df0
msgid "Optional: PDF reports (``maigret[pdf]``)"
msgstr "可选项:PDF 报告(``maigret[pdf]``)"
#: ../../source/installation.rst:179 2915e92406e0446fa1db73ea4c07ccac
msgid "The ``--pdf`` report format is shipped as an optional extra. To enable it:"
msgstr "``--pdf`` 报告格式作为可选 extra 提供,启用方式如下:"
#: ../../source/installation.rst:185 95fedc1010fa4702a97d63b255511f56
msgid ""
"If PDF support is not installed and you pass ``--pdf``, Maigret prints a "
"warning and continues without crashing — every other output format "
"(``--html``, ``--json``, ``--csv``, ``--txt``, ``--xmind``, ``--graph``) "
"keeps working."
msgstr ""
"如果未安装 PDF 支持但传入了 ``--pdf``,Maigret 会打印一条警告并继续运行,而不会崩溃 —— "
"其它输出格式(``--html``\\ 、\\ ``--json``\\ 、\\ ``--csv``\\ 、\\ ``--txt``\\ 、\\ "
"``--xmind``\\ 、\\ ``--graph``)依然正常可用。"
#: ../../source/installation.rst:191 9da2a7b6441d44069013972619981f94
msgid "Why is PDF optional?"
msgstr "为什么 PDF 是可选的?"
#: ../../source/installation.rst:193 b23ac592343143308b7802b858b361f4
msgid ""
"Maigret renders PDFs by converting an HTML template, and that conversion "
"pipeline ultimately depends on the ``cairo`` graphics library through a "
"chain of Python packages roughly shaped like::"
msgstr ""
"Maigret 通过转换 HTML 模板来渲染 PDF,而这条转换流水线最终通过一连串 Python 包依赖 ``cairo`` "
"图形库,大致的依赖链如下::"
#: ../../source/installation.rst:199 7250ee19355c47d7904ee0e80d2ac064
msgid ""
"The bottom of that chain is a C library — ``libcairo2`` — that has to "
"exist on the host *before* pip can build the Python bindings. The Python "
"binding package (``pycairo``) currently ships **only Windows wheels** on "
"PyPI; on Linux and macOS pip falls back to building from source, and the "
"build fails the moment ``pkg-config`` cannot find ``cairo``. The error "
"looks like::"
msgstr ""
"这条依赖链最底层是一个 C 库 —— ``libcairo2`` —— 必须在 pip 构建 Python 绑定*之前*就已存在于主机上。其 "
"Python 绑定包(``pycairo``)目前在 PyPI 上\\ **只发布了 Windows wheel**;在 Linux 和 "
"macOS 上,pip 会回退为从源码构建,只要 ``pkg-config`` 找不到 ``cairo``,构建就会失败,报错形如::"
#: ../../source/installation.rst:209 8519c15dd39d4238bcd3f42d9330e330
msgid ""
"Pulling this whole chain for every Maigret install just so the much "
"smaller group of users who actually want PDFs can have them is a poor "
"trade — so ``xhtml2pdf`` is gated behind the ``pdf`` extra."
msgstr ""
"为了让一小部分确实需要 PDF 的用户能用上,而对每一次 Maigret 安装都强行拉入这一整条依赖链,显然不划算 —— 因此 "
"``xhtml2pdf`` 被收进了 ``pdf`` extra 中。"
#: ../../source/installation.rst:213 3d0e063fb7334859b5f9570b910c6c00
msgid ""
"Two more packages — ``arabic-reshaper`` and ``python-bidi`` — are bundled"
" into the same extra. Maigret core never imports them; they are only used"
" by ``xhtml2pdf`` to shape Arabic glyphs and lay out right-to-left text "
"in PDFs. ``python-bidi`` v0.5+ is also a Rust binding, so on niche "
"platforms without a published wheel it would otherwise pull in a Cargo "
"build for users who never asked for PDF support."
msgstr ""
"另有两个包 —— ``arabic-reshaper`` 和 ``python-bidi`` —— 也被一并收入了同一个 "
"extra。Maigret 核心从不导入它们;它们仅被 ``xhtml2pdf`` 用于在 PDF 中塑造阿拉伯字符并排版从右到左的文字。\\ "
"``python-bidi`` 0.5 及以上版本是 Rust 绑定,在某些没有预编译 wheel 的小众平台上,如果不这样隔离,就会让从未要求 "
"PDF 支持的用户也被迫触发一次 Cargo 构建。"
#: ../../source/installation.rst:221 80f71336475d47cb9873448795ae9c21
msgid "Installing the system prerequisites"
msgstr "安装系统前置依赖"
#: ../../source/installation.rst:223 1e879f0511414a6e942dcd7be741f285
msgid ""
"Install the cairo headers, ``pkg-config``, and a working C toolchain "
"*before* running ``pip install 'maigret[pdf]'``."
msgstr ""
"在执行 ``pip install 'maigret[pdf]'`` *之前*,先安装 cairo 头文件、\\ ``pkg-config`` "
"以及一套可用的 C 工具链。"
#: ../../source/installation.rst:226 4bab90ae63914beebdc87f7bb848fe9b
msgid "**Debian / Ubuntu / Linux Mint / Kali:**"
msgstr "**Debian / Ubuntu / Linux Mint / Kali:**"
#: ../../source/installation.rst:235 edea2b08b8d44a1daafc0317fe8099a2
msgid "**Fedora / RHEL / CentOS:**"
msgstr "**Fedora / RHEL / CentOS:**"
#: ../../source/installation.rst:249 fb81460a3ae44c23bff7dbdc6cdbfabf
msgid "**Alpine Linux:**"
msgstr "**Alpine Linux:**"
#: ../../source/installation.rst:264 abb8643d8c4b4d6bbd3002fee178ebf1
msgid "**Windows:**"
msgstr "**Windows:**"
#: ../../source/installation.rst:266 6bcbcc0664d24c879cda8422cb9fb46f
msgid ""
"No system packages are needed — ``pycairo`` ships prebuilt wheels for "
"Windows. Just run:"
msgstr "不需要安装任何系统包 —— ``pycairo`` 在 Windows 上提供了预编译 wheel。直接执行:"
#: ../../source/installation.rst:273 c9c4b06a92954316b3c9223f3649e8b0
msgid "**Google Cloud Shell / Colab / Replit / generic CI:**"
msgstr "**Google Cloud Shell / Colab / Replit / 通用 CI:**"
#: ../../source/installation.rst:275 81a59c7198c04a218b644c5493ae8dfd
msgid ""
"These environments behave like Debian/Ubuntu — install the same "
"``libcairo2-dev pkg-config python3-dev build-essential`` triple before "
"``pip install 'maigret[pdf]'``. If you do not control the base image and "
"cannot ``apt install``, skip the extra and use ``--html`` reports "
"instead; HTML reports contain the same data and open in any browser."
msgstr ""
"这些环境的表现与 Debian/Ubuntu 类似 —— 在 ``pip install 'maigret[pdf]'`` 之前,安装相同的 "
"``libcairo2-dev pkg-config python3-dev build-essential`` "
"组合即可。如果你无法控制基础镜像、也不能执行 ``apt install``,那就跳过这个 extra,改用 ``--html`` 报告;HTML"
" 报告承载的数据完全一致,任何浏览器都能打开。"
#: ../../source/installation.rst:282 798403d50a8348169c437f2050dce6b9
msgid "``maigret: command not found`` after install"
msgstr "安装后出现 ``maigret: command not found``"
#: ../../source/installation.rst:284 92141864a3804547a8e9b13b9aeaae94
msgid "If pip prints warnings like::"
msgstr "如果 pip 打印出类似下面的警告::"
#: ../../source/installation.rst:289 2c7ce540fd1e43c0a3a32f881a0a6fd5
msgid ""
"…and ``maigret --version`` then fails with ``command not found``, your "
"``--user`` install put the entry-point script in a directory the shell "
"does not search. Add it to ``PATH``:"
msgstr ""
"…而 ``maigret --version`` 又报 ``command not found``,这说明你用 ``--user`` "
"安装时,入口脚本被放进了一个不在 shell 搜索路径里的目录。把它加入 ``PATH``:"
#: ../../source/installation.rst:298 efe742649ce6451ea3cace0789712bae
msgid ""
"Or install into a virtual environment, where the entry point lands in the"
" venv's ``bin/`` automatically:"
msgstr "或者改在虚拟环境中安装 —— 入口脚本会自动出现在该 venv 的 ``bin/`` 下:"
#: ../../source/installation.rst:308 e3c75239517d4e2c842320df68b3f58b
msgid "Optional: Cloudflare bypass solver"
msgstr "可选项:Cloudflare 绕过求解器"
#: ../../source/installation.rst:312 d4efdfc93ad14983b7e21a30881bc04c
msgid ""
"**Experimental.** The Cloudflare webgate is under active development; the"
" configuration schema and CLI behaviour may change without backwards-"
"compatibility guarantees."
msgstr "**实验特性。** Cloudflare webgate 仍在积极开发中;配置项和 CLI 行为都可能发生变化,不保证向后兼容。"
#: ../../source/installation.rst:316 ad0d8e94beec4c2bbfff01354b2294f3
msgid ""
"Sites tagged ``cf_js_challenge`` / ``cf_firewall`` need a real browser to"
" pass their JavaScript challenge. To check those sites you can run a "
"local `FlareSolverr <https://github.com/FlareSolverr/FlareSolverr>`_ "
"instance — Maigret will route protected checks to it when ``--cloudflare-"
"bypass`` is set:"
msgstr ""
"打上 ``cf_js_challenge`` / ``cf_firewall`` 标签的站点需要真实浏览器才能通过 JavaScript "
"挑战。要检查这些站点,你可以在本地运行一个 `FlareSolverr "
"<https://github.com/FlareSolverr/FlareSolverr>`_ 实例 —— 在传入 "
"``--cloudflare-bypass`` 后,Maigret 会把受保护站点的检查请求转交给它:"
#: ../../source/installation.rst:325 09e7149d2d8f4f469db6483203330229
msgid ""
"This is **optional** — Maigret runs without it; only sites whose "
"``protection`` field intersects "
"``settings.cloudflare_bypass.trigger_protection`` require the solver. See"
" :ref:`cloudflare-bypass` for details."
msgstr ""
"该求解器是\\ **可选项** —— 没有它 Maigret 一样能跑;只有那些 ``protection`` 字段与 "
"``settings.cloudflare_bypass.trigger_protection`` 有交集的站点,才会实际依赖求解器。详情参见 "
":ref:`cloudflare-bypass`\\ 。"
@@ -0,0 +1,125 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-22 20:48+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/library-usage.rst:4 0e3b47fe98c44e169eff36fc278d43d3
msgid "Library usage"
msgstr "作为库使用"
#: ../../source/library-usage.rst:6 1795f987c28745229ba9a09a14083804
msgid ""
"Maigret's CLI is a thin wrapper around an async Python API. You can embed"
" Maigret in your own tools, pipelines, and OSINT workflows — no need to "
"shell out."
msgstr ""
"Maigret 的 CLI 只是对一套异步 Python API 的薄包装。你完全可以将 Maigret 嵌入"
"自己的工具、流水线和 OSINT 工作流中,无需通过 shell 调用。"
#: ../../source/library-usage.rst:8 83987665fd604f0eb68aa7ca68be4f49
msgid ""
"This page covers the common patterns. For the full argument list of the "
"underlying function, see ``maigret.checking.maigret`` in the source."
msgstr ""
"本页介绍常见用法。底层函数的完整参数列表请参见源码中的 "
"``maigret.checking.maigret``\ 。"
#: ../../source/library-usage.rst:11 6b066ed2cf054a00b5c3e753d7cf4bd6
msgid "Installation"
msgstr "安装"
#: ../../source/library-usage.rst:18 bc7986312a6745f291fe5e40659f0733
msgid "Minimal example"
msgstr "最小示例"
#: ../../source/library-usage.rst:20 92fe494f90544f2bae74193f83b245eb
msgid "A working end-to-end search against the top 500 sites:"
msgstr "一个可直接运行、面向访问量排名前 500 站点的端到端搜索示例:"
#: ../../source/library-usage.rst:52 aafe2765921648228f18a5290818a94d
msgid "Key points:"
msgstr "关键点:"
#: ../../source/library-usage.rst:54 c9e560325a0b40f9a18b03d2fbd66895
msgid ""
"``maigret_search`` is an ``async`` function — wrap it with "
"``asyncio.run(...)`` or ``await`` it from inside your own event loop."
msgstr ""
"``maigret_search`` 是一个 ``async`` 函数 —— 在脚本中用 "
"``asyncio.run(...)`` 包裹,或在你自己的事件循环中 ``await`` 调用即可。"
#: ../../source/library-usage.rst:55 b0281cf7f39c4d6fa358d5cc4b13d1ae
msgid ""
"``is_parsing_enabled=True`` turns on ``socid_extractor`` so "
"``result[\"ids_data\"]`` is populated with profile fields (bio, linked "
"accounts, uids, etc.)."
msgstr ""
"``is_parsing_enabled=True`` 会启用 ``socid_extractor``,从而把个人主页中"
"的字段(个人简介、关联账号、各类 uid 等)填充到 ``result[\"ids_data\"]``\ 。"
#: ../../source/library-usage.rst:56 886c9269cbf346819fd029eb0c86b1ff
msgid ""
"Each entry in the returned dict has a ``\"status\"`` object with "
"``is_found()``, plus ``url_user``, ``http_status``, ``rank``, "
"``ids_data``, and more."
msgstr ""
"返回字典中的每一项都包含一个带有 ``is_found()`` 方法的 ``\"status\"`` "
"对象,以及 ``url_user``\ 、\ ``http_status``\ 、\ ``rank``\ 、\ ``ids_data`` 等字段。"
#: ../../source/library-usage.rst:59 542863605bc8418f9ddbcc5fd64a036d
msgid "Filtering sites"
msgstr "站点过滤"
#: ../../source/library-usage.rst:61 7fd59c0ee4fb48cb90cc6ec62a86c751
msgid "``ranked_sites_dict`` accepts the same filters as the CLI:"
msgstr "``ranked_sites_dict`` 支持的过滤参数与 CLI 完全一致:"
#: ../../source/library-usage.rst:78 8813c2686bb74458b2dfdffa2a3476af
msgid "Running inside an existing event loop"
msgstr "在已有事件循环中运行"
#: ../../source/library-usage.rst:80 25560e5994a547e5b634d8995c5c3d75
msgid ""
"If your application already runs an asyncio loop (FastAPI, aiohttp "
"server, a Discord bot, etc.), ``await`` ``maigret_search`` directly "
"instead of calling ``asyncio.run``:"
msgstr ""
"如果你的应用本身就在运行一个 asyncio 事件循环(例如 FastAPI、aiohttp "
"服务、Discord 机器人等),请直接 ``await`` ``maigret_search``,不要再调用 "
"``asyncio.run``:"
#: ../../source/library-usage.rst:98 8935761064444371b1ef71f426d3ab29
msgid "Routing through a proxy"
msgstr "通过代理转发"
#: ../../source/library-usage.rst:100 d54f955ba79144bb923dbc88d72d4888
msgid ""
"The same proxy / Tor / I2P flags the CLI exposes are plain keyword "
"arguments:"
msgstr "CLI 中暴露的代理 / Tor / I2P 选项,在库中就是普通的关键字参数:"
#: ../../source/library-usage.rst:115 9c0e258bf007420fa47cf79815ab4152
msgid "Full function signature"
msgstr "完整函数签名"
#: ../../source/library-usage.rst:139 7cd14f1926dd4a5db137982b74294344
msgid ""
"See :doc:`command-line-options` for a description of each option — the "
"semantics match the CLI flags one-to-one."
msgstr ""
"每个选项的含义请参见 :doc:`命令行选项 <command-line-options>` —— 这些参数"
"与 CLI 选项一一对应,语义完全相同。"
@@ -0,0 +1,105 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-22 20:48+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/philosophy.rst:4 48807e936bde4ac1ab63debcc1939e6e
msgid "Philosophy"
msgstr "项目理念"
#: ../../source/philosophy.rst:6 db0dc9d9dfb442bbaa66ecdf816d2f07
msgid ""
"*The Commissioner Jules Maigret is a fictional French police detective, "
"created by Georges Simenon. His investigation method is based on "
"understanding the personality of different people and their "
"interactions.*"
msgstr ""
"*Jules Maigret 探长是由 Georges Simenon 创作的法国虚构警探。他的侦查方法"
"建立在理解不同人物的性格,以及他们之间的相互关系之上。*"
#: ../../source/philosophy.rst:10 73fa31505603406f9316541034d7f8e3
msgid "TL;DR: Username => Dossier"
msgstr "一句话总结:用户名 => 档案"
#: ../../source/philosophy.rst:12 593fd204fbf24c6bb8a92f34dfd1ee1f
msgid ""
"Maigret is designed to gather all the available information about person "
"by his username."
msgstr ""
"Maigret 的设计目标,是仅凭一个用户名,就尽可能收集关于此人的全部可获取信息。"
#: ../../source/philosophy.rst:14 2dcbea0926ac4fc09759ffacdd43a7f2
msgid ""
"What kind of information is this? First, links to person accounts. "
"Secondly, all the machine-extractable pieces of info, such as: other "
"usernames, full name, URLs to people's images, birthday, location "
"(country, city, etc.), gender."
msgstr ""
"具体包括哪些信息?一是指向此人各个账号的链接;二是一切可被机器抽取的字段,"
"例如:其它用户名、真实姓名、头像 URL、生日、所在地(国家、城市等)、性别。"
#: ../../source/philosophy.rst:18 c18a634161624488ad6ea8732fe063da
msgid ""
"All this information forms some dossier, but it also useful for other "
"tools and analytical purposes. Each collected piece of data has a label "
"of a certain format (for example, ``follower_count`` for the number of "
"subscribers or ``created_at`` for account creation time) so that it can "
"be parsed and analyzed by various systems and stored in databases."
msgstr ""
"这些数据汇总起来构成一份档案,同时也方便其它工具进一步处理和分析。每一项"
"数据都带有固定格式的标签(例如关注者数量为 ``follower_count``,账号创建时间"
"为 ``created_at``),以便各类系统对其进行解析、分析,并存入数据库。"
#: ../../source/philosophy.rst:24 7efd25a3f90c4da087311e23478d0ba9
msgid "Origins"
msgstr "项目起源"
#: ../../source/philosophy.rst:26 f74cfcc6c3924f92a8d44aa1d42a1051
msgid ""
"Maigret started from studying what OSINT investigators actually use in "
"practice — and from the realization that many popular tools do not "
"deliver real investigative value. The original research behind this "
"observation is summarized in the article `What's wrong with namecheckers "
"<https://soxoj.medium.com/whats-wrong-with-namecheckers-981e5cba600e>`_. "
"For a broader landscape of username-checking tools, see the curated "
"`OSINT namecheckers list <https://github.com/soxoj/osint-namecheckers-"
"list>`_."
msgstr ""
"Maigret 始于一次对 OSINT 调查人员实际使用工具的研究,以及由此得出的结论:许多流行工具并没有真正带来调查价值。这一观察的原始研究总结在文章 `What's wrong with namecheckers <https://soxoj.medium.com/whats-wrong-with-namecheckers-981e5cba600e>`_ 中。如需更全面地了解用户名检查类工具的全景,请参见整理好的 `OSINT namecheckers list <https://github.com/soxoj/osint-namecheckers-list>`_。"
#: ../../source/philosophy.rst:33 d6e18afa687c4308bc803dcc2f991b0f
msgid "Two ideas grew out of that research:"
msgstr "由此衍生出两个想法:"
#: ../../source/philosophy.rst:35 bb7a5044518943068fff752a527be1eb
msgid ""
"`socid-extractor <https://github.com/soxoj/socid-extractor>`_ — a library"
" focused on pulling structured identity data (user IDs, full names, "
"linked accounts, bios, timestamps, etc.) out of account pages and public "
"API responses, so that finding an account is not the end of the pipeline."
msgstr ""
"`socid-extractor <https://github.com/soxoj/socid-extractor>`_ —— 一个专注于从账号页面和公开 API 响应中抽取结构化身份数据(用户 ID、真实姓名、关联账号、个人简介、时间戳等)的库,让“找到账号”不再是流水线的终点。"
#: ../../source/philosophy.rst:38 d8778348b35e4514998f83f1ba7bb49a
msgid ""
"**Maigret** itself — which started as a fork of `Sherlock "
"<https://github.com/sherlock-project/sherlock>`_ but has long since "
"outgrown the original project in coverage, extraction depth, and check "
"reliability. Today Maigret is used as a component by major OSINT vendors "
"in their commercial products."
msgstr ""
"**Maigret** 本身 —— 最初是 `Sherlock <https://github.com/sherlock-project/sherlock>`_ 的 fork,但在覆盖范围、信息抽取深度和检查可靠性等方面早已远超原项目。如今,主流 OSINT 厂商已把 Maigret 作为组件集成进各自的商业产品中。"
@@ -0,0 +1,48 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-22 20:48+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/quick-start.rst:4 297881c55bde4ddb8f503a1758fd54dc
msgid "Quick start"
msgstr "快速入门"
#: ../../source/quick-start.rst:6 1d4989eaa3cd4181a0d57335e41f649a
msgid ""
"After :doc:`installing Maigret <installation>`, you can begin searching "
"by providing one or more usernames to look up:"
msgstr ""
"在 :doc:`安装 Maigret <installation>` 之后,你可以提供一个或多个待查的用户名"
"来发起搜索:"
#: ../../source/quick-start.rst:8 9c0c37c565874d4090be5edf00885c55
msgid "``maigret username1 username2 ...``"
msgstr "``maigret username1 username2 ...``"
#: ../../source/quick-start.rst:10 69c10fb6163a421fa0ef1fd65f0df2b6
msgid ""
"Maigret will search for accounts with the specified usernames across a "
"vast number of websites. It will provide you with a list of URLs to any "
"discovered accounts, along with relevant information extracted from those"
" profiles."
msgstr ""
"Maigret 会在大量站点上搜索与指定用户名匹配的账号,并向你返回已发现账号的 "
"URL 列表,以及从这些个人主页中提取出的相关信息。"
#: ../../source/quick-start.rst:13 771e3fb4af5c434280dd9b5288fde81b
msgid "Maigret search results screenshot"
msgstr "Maigret 搜索结果截图"
@@ -0,0 +1,442 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-25 18:02+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/settings.rst:4 fe76c47d0aa1445c891f588fa40b146c
msgid "Settings"
msgstr "配置"
#: ../../source/settings.rst:7 53c34f2bdb7d4be498b654113d7d6231
msgid "The settings system is under development and may be subject to change."
msgstr "配置系统仍在开发中,后续可能发生变化。"
#: ../../source/settings.rst:9 4d4fd09541ce4883bfcdc64d646caf16
msgid ""
"Options are also configurable through settings files. See `settings JSON "
"file "
"<https://github.com/soxoj/maigret/blob/main/maigret/resources/settings.json>`_"
" for the list of currently supported options."
msgstr ""
"各项选项也可以通过配置文件进行设置。当前支持的完整选项列表请参见 `settings JSON 文件 "
"<https://github.com/soxoj/maigret/blob/main/maigret/resources/settings.json>`_。"
#: ../../source/settings.rst:13 0c0a9cd3c69b41c1b577466ebc33e36d
msgid ""
"After start Maigret tries to load configuration from the following "
"sources in exactly the same order:"
msgstr "启动后,Maigret 会\\ **按以下顺序**\\ 依次尝试从这些位置加载配置:"
#: ../../source/settings.rst:26 15ad0a7667b246b081ad08074d479d8d
msgid ""
"Missing any of these files is not an error. If the next settings file "
"contains already known option, this option will be rewrited. So it is "
"possible to make custom configuration for different users and "
"directories."
msgstr "上述文件中任何一个不存在都不算错误。如果后一个配置文件包含了已经出现过的选项,该选项就会被覆盖。因此你可以针对不同用户和不同目录设置不同的自定义配置。"
#: ../../source/settings.rst:34 018bbabf2f414554a59579239aa70d06
msgid "Database auto-update"
msgstr "数据库自动更新"
#: ../../source/settings.rst:36 652f6ed1943a4bbf95290f956eb7b28a
msgid ""
"Maigret ships with a bundled site database, but it gets outdated between "
"releases. To keep the database current, Maigret automatically checks for "
"updates on startup."
msgstr "Maigret 自带一份站点数据库,但它会随着新版本发布而逐渐过时。为了保持数据库是最新的,Maigret 在启动时会自动检查更新。"
#: ../../source/settings.rst:38 c9681e2aab35468e9af30b1b684301a0
msgid "**How it works:**"
msgstr "**工作机制:**"
#: ../../source/settings.rst:40 def91bc9f8e84dcebce7e2fa602ae0f5
msgid ""
"On startup, Maigret checks if more than 24 hours have passed since the "
"last update check."
msgstr "启动时,Maigret 会检查距离上次更新检查是否已超过 24 小时。"
#: ../../source/settings.rst:41 dfab33e815164c86a4ebe0e7e78ed6ee
msgid ""
"If so, it fetches a lightweight metadata file (~200 bytes) from GitHub to"
" see if a newer database is available."
msgstr "如果超过了,就从 GitHub 拉取一份轻量元数据文件(约 200 字节),查看是否有更新版本的数据库。"
#: ../../source/settings.rst:42 209b0131bfdd48c2b68e7088c2cb6014
msgid ""
"If a newer, compatible database exists, Maigret downloads it to "
"``~/.maigret/data.json`` and uses it instead of the bundled copy."
msgstr "如果存在更新且兼容的数据库,Maigret 会把它下载到 ``~/.maigret/data.json``,并用它替代内置的版本。"
#: ../../source/settings.rst:43 d8aa5049e4ee40929e746b6f5b4c6be8
msgid ""
"If the download fails or the new database is incompatible with your "
"Maigret version, the bundled database is used as a fallback."
msgstr "如果下载失败,或新数据库与你的 Maigret 版本不兼容,就回退到内置数据库。"
#: ../../source/settings.rst:45 26696d84d93342dfbd89b6ca614f29a5
msgid ""
"The downloaded database has **higher priority** than the bundled one — it"
" replaces, not overlays."
msgstr "下载下来的数据库\\ **优先级高于**\\ 内置数据库 —— 它是替换关系,而不是叠加。"
#: ../../source/settings.rst:47 93ce61948c464569a7efdd07ae269fb2
msgid "**Status messages** are printed only when an action occurs:"
msgstr "**状态消息**\\ 仅在实际发生某种动作时才会打印:"
#: ../../source/settings.rst:56 0845742ea7ed4b44a6194a7a9f8faca2
msgid "**Forcing an update:**"
msgstr "**强制更新:**"
#: ../../source/settings.rst:58 ee8fcb44764949dcb422d9523f203837
msgid ""
"Use the ``--force-update`` flag to check for updates immediately, "
"ignoring the check interval:"
msgstr "使用 ``--force-update`` 参数会忽略检查间隔,立刻检查更新:"
#: ../../source/settings.rst:64 abcac12715f6447283a54b1d21eb3f26
msgid ""
"The update happens at startup, then the search continues normally with "
"the freshly downloaded database."
msgstr "更新只在启动时进行,完成后即用新下载的数据库继续执行正常搜索。"
#: ../../source/settings.rst:66 8dfdb9ddb94742d6916accf6a637b621
msgid "**Disabling auto-update:**"
msgstr "**禁用自动更新:**"
#: ../../source/settings.rst:68 e63059a385e042989dbffc8faa6620be
msgid "Use the ``--no-autoupdate`` flag to skip the update check entirely:"
msgstr "使用 ``--no-autoupdate`` 参数可以完全跳过更新检查:"
#: ../../source/settings.rst:74 4538e0b662e74117ac60feb580ea14cf
msgid "Or set it permanently in ``~/.maigret/settings.json``:"
msgstr "或者在 ``~/.maigret/settings.json`` 中永久设置:"
#: ../../source/settings.rst:82 01fb39cf9f6648cb818421edd235e9bd
msgid ""
"This is recommended for **Docker containers**, **CI pipelines**, and "
"**air-gapped environments**."
msgstr "该选项推荐用于 **Docker 容器**\\ 、\\ **CI 流水线**\\ 以及\\ **离线隔离环境**\\ 。"
#: ../../source/settings.rst:84 4d8f3a1e5313475ab522313771b11096
msgid "**Configuration options** (in ``settings.json``):"
msgstr "**配置项**\\ (位于 ``settings.json``):"
#: ../../source/settings.rst:90 ../../source/settings.rst:242
#: 8ee43c194a614f57b2f10cf306622d9d 9bce6a59ddf14978a8a6b536b266d6cb
msgid "Setting"
msgstr "选项"
#: ../../source/settings.rst:91 ../../source/settings.rst:243
#: 0961df2fea32498a82984fcd2a6cd428 e8446230ddf34848bbbc331cfefb4745
msgid "Default"
msgstr "默认值"
#: ../../source/settings.rst:92 ../../source/settings.rst:183
#: ../../source/settings.rst:244 2dbd3a0f5c00431a908b42aec54f648c
#: 6d42f72550974316980c6bf1806ff29c e84d99a18c264735a2adfa5099d4b0fd
msgid "Description"
msgstr "说明"
#: ../../source/settings.rst:93 ffec75c5cdea4c9aa0c69125f639339a
msgid "``no_autoupdate``"
msgstr "``no_autoupdate``"
#: ../../source/settings.rst:94 16b9c70c0b6a412d9bde98cb2a60e216
msgid "``false``"
msgstr "``false``"
#: ../../source/settings.rst:95 2381445bb42b40fdacaebd43988daa49
msgid "Disable auto-update entirely"
msgstr "完全禁用自动更新"
#: ../../source/settings.rst:96 c6ad3238aab14a3d9070588c11ae8ed4
msgid "``autoupdate_check_interval_hours``"
msgstr "``autoupdate_check_interval_hours``"
#: ../../source/settings.rst:97 6cf231184c7740ffb6e8116d11387e4f
msgid "``24``"
msgstr "``24``"
#: ../../source/settings.rst:98 3c1ea4469c8d425492ec6cccce8aac55
msgid "How often to check for updates (in hours)"
msgstr "检查更新的间隔(单位:小时)"
#: ../../source/settings.rst:99 efb1d0e86cac42c3adf1f3ef4c76c91a
msgid "``db_update_meta_url``"
msgstr "``db_update_meta_url``"
#: ../../source/settings.rst:100 1a0cf0e2a938494689e926cdf7f7ac9e
msgid "GitHub raw URL"
msgstr "GitHub raw URL"
#: ../../source/settings.rst:101 25a72fbd4eb74ff7b5b0b28e8e43addf
msgid "URL of the metadata file (for custom mirrors)"
msgstr "元数据文件的 URL(用于自建镜像)"
#: ../../source/settings.rst:103 4a02e3e5680c477d8a03b2c5a1b5ca8c
msgid ""
"**Using a custom database** with ``--db`` always skips auto-update — you "
"are explicitly choosing your data source."
msgstr "使用 ``--db`` 指定自定义数据库时,\\ **总是会跳过**\\ 自动更新 —— 因为你已经明确选择了数据源。"
#: ../../source/settings.rst:106 f7ea5acf3a354a2681d7a150e277f086
msgid "Cloudflare webgate"
msgstr "Cloudflare webgate"
#: ../../source/settings.rst:110 9a059aba51304c1aa1c47c4e681511e6
msgid ""
"**Experimental.** The ``cloudflare_bypass`` block is under active "
"development; field names, defaults, and the trigger-protection routing "
"rules may change without backwards-compatibility guarantees."
msgstr ""
"**实验特性。** ``cloudflare_bypass`` 这一节仍在积极开发中;字段名、默认值以及 trigger-protection "
"的路由规则都可能发生变化,不保证向后兼容。"
#: ../../source/settings.rst:114 3121d43837664c1f9818c7099732eb09
msgid ""
"The ``cloudflare_bypass`` block in ``settings.json`` configures the "
"optional bypass described in :ref:`cloudflare-bypass`. The same block is "
"honoured by the CLI, the Python API, and the web UI (``python -m "
"maigret.web.app``); set ``enabled: true`` once and every entry point "
"routes cf-protected sites through the solver."
msgstr ""
"``settings.json`` 中的 ``cloudflare_bypass`` 配置块用于配置 "
":ref:`cloudflare-bypass` 所述的可选绕过机制。CLI、Python API 以及 Web 界面"
"(``python -m maigret.web.app``)都会读取同一份配置:只需将 ``enabled`` 设为"
" ``true``,所有入口都会把受 cf 保护的站点转发给求解器。"
#: ../../source/settings.rst:120 a98ec36b3f954c30af38363e9bea6ec3
msgid ""
"**Minimal FlareSolverr setup.** Start the solver in Docker, then drop the"
" following snippet into ``~/.maigret/settings.json`` (or any path listed "
"in :ref:`settings`):"
msgstr ""
"**FlareSolverr 最小化配置。** 先用 Docker 启动求解器,然后把下面这段配置"
"写入 ``~/.maigret/settings.json``\\ (或 :ref:`settings` 中列出的任意路径):"
#: ../../source/settings.rst:145 d8dc102a6ea247d991b6c09f917bc37a
msgid ""
"That is enough — ``session_prefix`` and ``trigger_protection`` fall back "
"to sensible defaults (``\"maigret\"`` and ``[\"cf_js_challenge\", "
"\"cf_firewall\", \"webgate\"]`` respectively). On the next run, Maigret "
"logs ``Cloudflare webgate active: ...`` and routes matching sites through"
" the solver."
msgstr ""
"这样就够了 —— ``session_prefix`` 和 ``trigger_protection`` 会回退到合理的"
"默认值(分别为 ``\"maigret\"`` 和 "
"``[\"cf_js_challenge\", \"cf_firewall\", \"webgate\"]``)。下次运行时,"
"Maigret 会输出 ``Cloudflare webgate active: ...`` 日志,并把命中的站点"
"路由到求解器。"
#: ../../source/settings.rst:151 bfff9d4907664fc3862b5a6a20da88be
msgid "Default value (full schema with every supported field):"
msgstr "默认值(包含全部受支持字段的完整结构):"
#: ../../source/settings.rst:176 46b6a3845ba548aa8823e049281bda6d
msgid "**Fields.**"
msgstr "**字段说明。**"
#: ../../source/settings.rst:182 bad54b56ab2945058697d7054a6055fa
msgid "Field"
msgstr "字段"
#: ../../source/settings.rst:184 610f1da5f28040079ae2bcb10618573c
msgid "``enabled``"
msgstr "``enabled``"
#: ../../source/settings.rst:185 c3149bfa418c4e3b95dde5611ef4717b
msgid ""
"When ``true``, the bypass is active for every run; when ``false`` (the "
"default), it activates only on ``--cloudflare-bypass``."
msgstr ""
"为 ``true`` 时,每次运行都会启用绕过;为 ``false``\\ (默认)时,仅在传入 ``--cloudflare-bypass`` "
"时才启用。"
#: ../../source/settings.rst:187 5c92e71e8ec144be83f8abbca6e37d01
msgid "``trigger_protection``"
msgstr "``trigger_protection``"
#: ../../source/settings.rst:188 38557548995949a5b8121d81ffc51178
msgid ""
"List of ``site.protection`` values that route a check through the "
"webgate. Sites whose protection is empty or doesn't intersect this list "
"use the default (aiohttp / curl_cffi) checker."
msgstr ""
"一个 ``site.protection`` 值的列表;命中其中任何一个时,检查请求都会经由webgate 转发。\\ "
"``protection`` 为空、或与本列表无交集的站点,仍使用默认的检查器(aiohttp / curl_cffi)。"
#: ../../source/settings.rst:191 4b93a27e05b34050837eccffbcb40bed
msgid "``session_prefix``"
msgstr "``session_prefix``"
#: ../../source/settings.rst:192 c54f79f2b5ca4c42805b948cbbb3c3d2
msgid ""
"Prefix for the FlareSolverr ``session`` field. Maigret appends the "
"process PID so concurrent runs don't collide. Reusing a session caches "
"cf_clearance between checks of the same domain."
msgstr ""
"FlareSolverr ``session`` 字段的前缀。Maigret 会在其后追加进程 PID,避免并发运行相互冲突。复用同一个 "
"session 会在对同一域名的多次检查之间缓存 cf_clearance。"
#: ../../source/settings.rst:195 9742c805b326433aac2ad92e1e48a880
msgid "``modules``"
msgstr "``modules``"
#: ../../source/settings.rst:196 8c7a26caf24040fc9afce140b739ba89
msgid ""
"Ordered list of backend modules. The first reachable module handles the "
"check; later ones serve as a fallback chain."
msgstr "后端模块的有序列表。第一个能连通的模块负责处理该检查,后续模块作为兜底链。"
#: ../../source/settings.rst:199 262a0c13a9ae4815962b16733ef6016e
msgid "**Module methods.**"
msgstr "**模块方法。**"
#: ../../source/settings.rst:201 a3145c584a0e453aac0ca51f67ee12db
msgid ""
"``json_api`` — FlareSolverr-compatible POST endpoint at ``url``. "
"Preserves real upstream HTTP status, headers and final URL. Optional "
"``max_timeout_ms`` (default ``60000``) is the per-request budget the "
"solver is allowed to spend on the JS challenge."
msgstr ""
"``json_api`` —— 在 ``url`` 上提供的 FlareSolverr 兼容 POST 接口。会保留上游真实的 HTTP "
"状态码、响应头和最终 URL。可选字段 ``max_timeout_ms``\\ (默认 ``60000``)是允许求解器在 JS "
"挑战上花费的单次请求时间预算。"
#: ../../source/settings.rst:205 fe03ccdb70144c77adc1b7280d9f71ca
#, python-brace-format
msgid ""
"``url_rewrite`` — legacy CloudflareBypassForScraping endpoint. The "
"``url`` must contain a ``{url}`` placeholder; the original probe URL is "
"URL-encoded and substituted in. Returns rendered HTML only — ``checkType:"
" status_code`` and ``response_url`` checks misfire under this method "
"(treated as a synthetic HTTP 200 on success)."
msgstr ""
"``url_rewrite`` —— 旧版 CloudflareBypassForScraping 接口。\\ ``url`` 中必须包含 "
"``{url}`` 占位符;原始探测 URL 会被 URL 编码后代入。该方式仅返回渲染后的 HTML —— 因此在此方法下,\\ "
"``checkType: status_code`` 和 ``response_url`` 两类检查会失准(成功时会被当作一个虚构的 HTTP "
"200)。"
#: ../../source/settings.rst:211 7dd51af2a40f4b64ab35e75a7291ad88
msgid "**Optional ``proxy`` field (``json_api`` only).**"
msgstr "**可选的 ``proxy`` 字段(仅 ``json_api`` 支持)。**"
#: ../../source/settings.rst:213 08bf49a34630456db598e3e585a3e128
msgid ""
"A module may carry a ``proxy`` entry that the solver routes the upstream "
"request through. Useful when a site enforces ``ip_reputation`` rules that"
" block the solver host. Two forms are accepted:"
msgstr ""
"每个模块都可以带一个 ``proxy`` 字段 —— 求解器会通过该代理转发上游请求。在站点启用了会封锁求解器主机的 "
"``ip_reputation`` 规则时,这会非常有用。接受两种写法:"
#: ../../source/settings.rst:227 7e0856096ecb44599b40a1e0efbb7fbb
msgid ""
"Only ``url``/``username``/``password`` are forwarded; other keys are "
"dropped. Cloudflare ``Error 1015 / 1020`` responses indicate the IP is "
"rate-limited or banned — switch the proxy rather than retrying. .. _ai-"
"analysis-settings:"
msgstr ""
"仅 ``url``/``username``/``password`` 会被转发;其它字段会被丢弃。Cloudflare ``Error 1015"
" / 1020`` 响应表示该 IP 被限速或被封 —— 此时请更换代理,而不是反复重试。 .. _ai-analysis-settings:"
#: ../../source/settings.rst:233 c0352f79365140edadb45dfe6e0373d3
msgid "AI analysis"
msgstr "AI 分析"
#: ../../source/settings.rst:235 bf3d82f2b15745cb8a1b4dfed7c9f836
msgid ""
"The ``--ai`` flag (see :ref:`ai-analysis`) talks to an OpenAI-compatible "
"chat completion API. Three settings control how that request is made:"
msgstr ""
"``--ai`` 参数(参见 :ref:`ai-analysis`)会调用一个 OpenAI 兼容的 chat completion "
"接口。以下三个配置项决定该请求的具体行为:"
#: ../../source/settings.rst:245 5fbd903f98e64b349c146792cc7ab2e9
msgid "``openai_api_key``"
msgstr "``openai_api_key``"
#: ../../source/settings.rst:246 c73824b01b8e4cb8aabfed01c94ea2ad
msgid "``\"\"`` (empty)"
msgstr "``\"\"``\\ (空)"
#: ../../source/settings.rst:247 82aaaef9d7cc4665813cdd874130bdb7
msgid ""
"API key. If empty, Maigret falls back to the ``OPENAI_API_KEY`` "
"environment variable."
msgstr "API key。为空时,Maigret 会回退到 ``OPENAI_API_KEY`` 环境变量。"
#: ../../source/settings.rst:249 037412797a4c42519aef357c77d1833f
msgid "``openai_model``"
msgstr "``openai_model``"
#: ../../source/settings.rst:250 2562a1def3be41889c3b05620eb1870b
msgid "``gpt-4o``"
msgstr "``gpt-4o``"
#: ../../source/settings.rst:251 0d3623bf37a84a0399c893f90dae649a
msgid "Default model name. Overridable per-run with ``--ai-model``."
msgstr "默认模型名。可在单次运行中通过 ``--ai-model`` 覆盖。"
#: ../../source/settings.rst:252 f6ae5711a47f42e4a6effae3c90c2560
msgid "``openai_api_base_url``"
msgstr "``openai_api_base_url``"
#: ../../source/settings.rst:253 4aac62c2d35148328d255646e18623d4
msgid "``https://api.openai.com/v1``"
msgstr "``https://api.openai.com/v1``"
#: ../../source/settings.rst:254 6096b4d81ac248a09f6f49f5d8303676
msgid ""
"Base URL of the chat completion API. Point this at any OpenAI-compatible "
"service (Azure OpenAI, OpenRouter, a local server, …) to use it instead "
"of OpenAI directly."
msgstr ""
"chat completion 接口的基础 URL。把它指向任何 OpenAI 兼容的服务(Azure "
"OpenAI、OpenRouter、本地推理服务等),即可用其代替 OpenAI 官方服务。"
#: ../../source/settings.rst:258 186158107d2b4ee1af1a46a7e45f6c1a
msgid "Example ``~/.maigret/settings.json`` snippet using a non-OpenAI endpoint:"
msgstr "下面是一个使用非 OpenAI 接口的 ``~/.maigret/settings.json`` 片段示例:"
#: ../../source/settings.rst:269 2e37565d7dd74e9898cacf648c27476a
msgid ""
"The key resolution order is ``settings.openai_api_key`` → "
"``OPENAI_API_KEY`` environment variable; the first non-empty value wins."
msgstr ""
"API key 的取值顺序为:``settings.openai_api_key`` → ``OPENAI_API_KEY`` "
"环境变量;以第一个非空值为准。"
#: ../../source/settings.rst:274 0bb83aab896446628c567ec9e33644a1
msgid ""
"``--ai`` sends the full internal Markdown report (which contains the "
"gathered profile data) to the configured endpoint. Only use providers and"
" accounts you trust with that data."
msgstr ""
"``--ai`` 会把完整的内部 Markdown "
"报告(其中包含收集到的所有个人主页数据)发送至所配置的接口。请仅在你信任的服务商和账号上使用该功能。"
#~ msgid ""
#~ "The ``cloudflare_bypass`` block in "
#~ "``settings.json`` configures the optional "
#~ "bypass described in :ref:`cloudflare-bypass`."
#~ " Default value:"
#~ msgstr ""
#~ "``settings.json`` 中的 ``cloudflare_bypass`` 配置块用于配置"
#~ " :ref:`cloudflare-bypass` 所述的可选绕过机制。默认值如下:"
@@ -0,0 +1,80 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-22 20:48+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/supported-identifier-types.rst:4
#: 33063447be45418d8712a5f1e6f0f26f
msgid "Supported identifier types"
msgstr "支持的标识符类型"
#: ../../source/supported-identifier-types.rst:6
#: 650e5fb3b81b455ebec318e5e5eaef39
msgid ""
"Maigret can search against not only ordinary usernames, but also through "
"certain common identifiers. There is a list of all currently supported "
"identifiers."
msgstr ""
"Maigret 不仅可以基于普通用户名进行搜索,还可以基于若干常见的标识符进行搜索。"
"以下是当前支持的全部标识符列表。"
#: ../../source/supported-identifier-types.rst:8
#: d89f32e75ed54a29968fa59b9cae2226
msgid ""
"**gaia_id** - Google inner numeric user identifier, in former times was "
"placed in a Google Plus account URL."
msgstr ""
"**gaia_id** —— Google 内部的数字用户标识符,过去会出现在 Google Plus 账号 "
"URL 中。"
#: ../../source/supported-identifier-types.rst:9
#: 8625cfbcf5e14b9b86b1067ea0f11b5f
msgid "**steam_id** - Steam inner numeric user identifier."
msgstr "**steam_id** —— Steam 内部的数字用户标识符。"
#: ../../source/supported-identifier-types.rst:10
#: 3136248733fe40488f23f5bf448de51b
msgid "**wikimapia_uid** - Wikimapia.org inner numeric user identifier."
msgstr "**wikimapia_uid** —— Wikimapia.org 内部的数字用户标识符。"
#: ../../source/supported-identifier-types.rst:11
#: 363bd8c96cef4411811b3c141d60a2a1
msgid "**uidme_uguid** - uID.me inner numeric user identifier."
msgstr "**uidme_uguid** —— uID.me 内部的数字用户标识符。"
#: ../../source/supported-identifier-types.rst:12
#: 10477c76097549c9a59caacd6a9957c4
msgid ""
"**yandex_public_id** - Yandex sites inner letter user identifier. See "
"also: `YaSeeker <https://github.com/HowToFind-bot/YaSeeker>`_."
msgstr ""
"**yandex_public_id** —— Yandex 系站点内部的字母型用户标识符。延伸阅读:`YaSeeker <https://github.com/HowToFind-bot/YaSeeker>`_。"
#: ../../source/supported-identifier-types.rst:13
#: 10b9f7a111604b84b8b03395336afbbb
msgid "**vk_id** - VK.com inner numeric user identifier."
msgstr "**vk_id** —— VK.com 内部的数字用户标识符。"
#: ../../source/supported-identifier-types.rst:14
#: b0a7e5c93dec4af8b78ca3c6a65541f3
msgid "**ok_id** - OK.ru inner numeric user identifier."
msgstr "**ok_id** —— OK.ru 内部的数字用户标识符。"
#: ../../source/supported-identifier-types.rst:15
#: 5eac68e6daa548d4a0559b3c70fc5de8
msgid "**yelp_userid** - Yelp inner user identifier."
msgstr "**yelp_userid** —— Yelp 内部的用户标识符。"
@@ -0,0 +1,160 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-22 20:48+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/tags.rst:4 dfd4dbb048734bd08dd4e7b916240c7a
msgid "Tags"
msgstr "标签"
#: ../../source/tags.rst:6 efaa336b0e2d455d99374fd69e49b69f
msgid ""
"The use of tags allows you to select a subset of the sites from big "
"Maigret DB for search."
msgstr ""
"通过标签,你可以从庞大的 Maigret 数据库中挑选出一个子集进行搜索。"
#: ../../source/tags.rst:9 69aba3e9c9034688a05b6b56dc1ce422
msgid "Tags markup is still not stable."
msgstr "标签体系目前尚未稳定。"
#: ../../source/tags.rst:11 e6166e567f43487eb602492f6230b3bb
msgid "There are several types of tags:"
msgstr "标签分为以下几类:"
#: ../../source/tags.rst:13 3e93c919597347b1b17bbe8b1a6e0099
msgid ""
"**Country codes**: ``us``, ``jp``, ``br``... (`ISO 3166-1 alpha-2 "
"<https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_). A country tag "
"means that having an account on the site implies a connection to that "
"country — either origin or residence. The goal is attribution, not "
"perfect accuracy."
msgstr ""
"**国家代码**:``us``\ 、\ ``jp``\ 、\ ``br`` 等(`ISO 3166-1 alpha-2 <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_)。国家标签的含义是:在该站点拥有账号,意味着此人与该国家存在关联 —— 出生地或居住地。其目标是做出归属判断,而非追求完美准确。"
#: ../../source/tags.rst:15 1bb17b8af4e846d28db19f90fa60ea87
msgid ""
"**Global sites** (GitHub, YouTube, Reddit, Medium, etc.) get **no country"
" tag** — an account there says nothing about where a person is from."
msgstr ""
"**全球性站点**\ (GitHub、YouTube、Reddit、Medium 等)\ **不打国家标签** —— "
"在这些站点上有账号并不能说明此人来自哪里。"
#: ../../source/tags.rst:16 b02bc81d7a424f628bbd10bbbfa538ae
msgid ""
"**Regional/local sites** where an account implies a specific country "
"**must** have a country tag: ``VK`` → ``ru``, ``Naver`` → ``kr``, "
"``Zhihu`` → ``cn``."
msgstr ""
"**区域性/本地站点**,如果拥有账号意味着归属于某个特定国家,\ **必须**\ 打上"
"国家标签:``VK`` → ``ru``,\ ``Naver`` → ``kr``,\ ``Zhihu`` → ``cn``\ 。"
#: ../../source/tags.rst:17 51204b29bfb8444bb66e090d71f0d298
msgid ""
"Multiple country tags are allowed when a service is used predominantly in"
" a few countries (e.g. ``Xing`` → ``de``, ``eu``)."
msgstr ""
"如果某个服务主要在少数几个国家使用,可以打多个国家标签(例如 ``Xing`` → "
"``de``\ 、\ ``eu``)。"
#: ../../source/tags.rst:18 e7b136f5d67744babdf5b2548c053156
msgid ""
"Do **not** assign country tags based on traffic statistics alone — a site"
" popular in India by traffic is not \"Indian\" if it is used globally."
msgstr ""
"**不要**\ 仅凭流量统计就给站点打国家标签 —— 某站点即使在印度流量很高,只要其"
"用户群是全球性的,就不算“印度站点”。"
#: ../../source/tags.rst:20 c8179b6d4a9c46028e5ee6b564e1be42
msgid ""
"**Site engines**. Most of them are forum engines now: ``uCoz``, "
"``vBulletin``, ``XenForo`` et al. Full list of engines stored in the "
"Maigret database."
msgstr ""
"**站点引擎**\ 。目前大多为论坛引擎,例如 ``uCoz``\ 、\ ``vBulletin``\ 、\ ``XenForo`` 等。完整的引擎列表存储于 Maigret 数据库中。"
#: ../../source/tags.rst:22 54452d52c2ec4ca4aa7014e8b2b2fc6a
msgid ""
"**Sites' subject/type and interests of its users**. Full list of "
"\"standard\" tags is `present in the source code "
"<https://github.com/soxoj/maigret/blob/main/maigret/sites.py#L13>`_ only "
"for a moment."
msgstr ""
"**站点的主题/类型,以及用户兴趣**\ 。完整的“标准”标签列表目前只在\ `源码 <https://github.com/soxoj/maigret/blob/main/maigret/sites.py#L13>`_ 中维护。"
#: ../../source/tags.rst:25 34282854dc044d0d985cb3e06dfc2ce0
msgid "Usage"
msgstr "使用方法"
#: ../../source/tags.rst:26 575fe2076f99476185e5b8d26fb5c4e2
msgid ""
"``--tags us,jp`` -- search on US and Japanese sites (actually marked as "
"such in the Maigret database)"
msgstr ""
"``--tags us,jp`` —— 在美国和日本站点上搜索(以 Maigret 数据库中实际打的"
"标签为准)"
#: ../../source/tags.rst:28 ba09e85ba8a24daaaf40ff9543c203d3
msgid "``--tags coding`` -- search on sites related to software development."
msgstr "``--tags coding`` —— 在软件开发相关站点上搜索。"
#: ../../source/tags.rst:30 f4a3ae2c95d44082941910d88b697337
msgid "``--tags ucoz`` -- search on uCoz sites only (mostly CIS countries)"
msgstr "``--tags ucoz`` —— 只在 uCoz 系站点上搜索(主要为独联体国家)"
#: ../../source/tags.rst:33 7a6bb72b86b548b09ee0362827a0f527
msgid "Blacklisting (excluding) tags"
msgstr "按标签排除(黑名单)"
#: ../../source/tags.rst:34 1f51439352e849aa851bf499267cc2d0
msgid ""
"You can exclude sites with certain tags from the search using "
"``--exclude-tags``:"
msgstr "通过 ``--exclude-tags`` 可以从搜索中排除带有指定标签的站点:"
#: ../../source/tags.rst:36 0d4f7bb04e6d4d178c8f18341e61cb0b
msgid ""
"``--exclude-tags porn,dating`` -- skip all sites tagged with ``porn`` or "
"``dating``."
msgstr ""
"``--exclude-tags porn,dating`` —— 跳过所有打上 ``porn`` 或 ``dating`` "
"标签的站点。"
#: ../../source/tags.rst:38 dd07939a21de48dba2d1e8cf2c61ebd4
msgid "``--exclude-tags ru`` -- skip all Russian sites."
msgstr "``--exclude-tags ru`` —— 跳过所有俄罗斯站点。"
#: ../../source/tags.rst:40 491ae6de296e48bdaad0f9f508a6c4ba
msgid ""
"You can combine ``--tags`` and ``--exclude-tags`` to fine-tune your "
"search:"
msgstr "也可以将 ``--tags`` 和 ``--exclude-tags`` 组合使用,对搜索范围进行精细控制:"
#: ../../source/tags.rst:42 2531e53d09c142a7ae52e8867fe34de2
msgid ""
"``--tags forum --exclude-tags ru`` -- search on forum sites, but skip "
"Russian ones."
msgstr "``--tags forum --exclude-tags ru`` —— 在论坛类站点上搜索,但跳过俄罗斯站点。"
#: ../../source/tags.rst:44 10637ee8975e4848816ce7a5883cd645
msgid ""
"In the web interface, the tag cloud supports three states per tag: click "
"once to **include** (green), click again to **exclude** "
"(dark/strikethrough), and click once more to return to **neutral** (red)."
msgstr ""
"在 Web 界面中,标签云对每个标签支持三种状态:第一次点击为\ **包含**\ (绿色),"
"再次点击为\ **排除**\ (深色/带删除线),第三次点击恢复为\ **中性**\ (红色)。"
@@ -0,0 +1,309 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-23 10:03+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/tor-and-proxies.rst:4 8799af869fb8498ca9136204833c12bc
msgid "Tor, I2P, and proxies"
msgstr "Tor、I2P 与代理"
#: ../../source/tor-and-proxies.rst:6 18b3f3e7045a4504be0c719800d9d74c
msgid ""
"Maigret can route checks through an HTTP/SOCKS proxy, the Tor network, or"
" I2P. Three CLI flags cover three distinct goals — knowing which one you "
"need is the most common stumbling block."
msgstr ""
"Maigret 可以将检查请求通过 HTTP/SOCKS 代理、Tor 网络或 I2P 转发。三个 CLI 参数分别对应三种不同目标 —— "
"弄清楚自己到底需要哪一个,是最常见的拦路虎。"
#: ../../source/tor-and-proxies.rst:9 c7e16af249aa4f778513ddced342b2c8
msgid "``--proxy`` vs ``--tor-proxy`` (and ``--i2p-proxy``)"
msgstr "``--proxy`` 与 ``--tor-proxy``\\ (以及 ``--i2p-proxy``)的区别"
#: ../../source/tor-and-proxies.rst:11 a99844d2182148a9bb794847e86d2ba5
msgid ""
"The most-asked question (see `issue #544 "
"<https://github.com/soxoj/maigret/issues/544>`_):"
msgstr "最常被问到的问题(参见 `issue #544 <https://github.com/soxoj/maigret/issues/544>`_):"
#: ../../source/tor-and-proxies.rst:13 fbd98e4eaedf4574908ed7a7f451dc63
msgid ""
"**You want every check to go through Tor** (e.g. you're on Tails OS, or "
"behind a country-level block, or your IP is rate-limited). → Use "
"``--proxy``, pointing at your Tor SOCKS port:"
msgstr ""
"**希望所有检查请求都通过 Tor 发出**\\ (比如你在 Tails 操作系统下、处于国家级封锁之后,或本机 IP 被限速)。→ 使用 "
"``--proxy`` 并指向你的 Tor SOCKS 端口:"
#: ../../source/tor-and-proxies.rst:19 4b55c77cbbe643f3a5db220b5711e698
msgid ""
"**You want to reach ``.onion`` sites in the Maigret database**, while the"
" rest of the run still uses your normal connection. → Use ``--tor-"
"proxy``:"
msgstr "**只想访问 Maigret 数据库中的 ``.onion`` 站点**,其它站点继续走你本机的正常连接。→ 使用 ``--tor-proxy``:"
#: ../../source/tor-and-proxies.rst:25 e2944b6a49024d9b8875fe75ca9e145c
msgid ""
"``--tor-proxy`` is **only** consulted for sites whose ``url`` is a "
"``.onion`` host. For every other site Maigret uses your direct connection"
" (or ``--proxy`` if set). Without ``--tor-proxy``, ``.onion`` sites are "
"silently skipped."
msgstr ""
"``--tor-proxy`` **只**\\ 对 ``url`` 为 ``.onion`` 域名的站点生效。其它站点会走你本机的直连(或者 "
"``--proxy``,如果你设置了的话)。如果不传 ``--tor-proxy``,\\ ``.onion`` 站点会被静默跳过。"
#: ../../source/tor-and-proxies.rst:27 b7021bbaa1e44f3fb63c528ac3dc9de2
msgid ""
"The same split applies to ``--i2p-proxy``: it is consulted only for "
"``.i2p`` hosts, never for clearweb sites."
msgstr "``--i2p-proxy`` 的逻辑相同:只对 ``.i2p`` 域名生效,绝不会用于明网站点。"
#: ../../source/tor-and-proxies.rst:29 3743546b2784445dad8092c822bbe33e
msgid ""
"Defaults: ``--tor-proxy`` defaults to ``socks5://127.0.0.1:9050`` and "
"``--i2p-proxy`` to ``http://127.0.0.1:4444``. ``--proxy`` has no default."
" Maigret does **not** launch ``tor`` or an I2P router for you — start the"
" daemon first."
msgstr ""
"默认值:``--tor-proxy`` 默认为 ``socks5://127.0.0.1:9050``,\\ ``--i2p-proxy`` "
"默认为 ``http://127.0.0.1:4444``\\ 。\\ ``--proxy`` 没有默认值。Maigret **不会**\\ "
"替你启动 ``tor`` 或 I2P 路由器 —— 请先把守护进程跑起来。"
#: ../../source/tor-and-proxies.rst:32 5f21c934dc37477bb494409e9bac0ac0
msgid "Tor Browser vs system ``tor``: port numbers"
msgstr "Tor 浏览器 vs 系统级 ``tor``:端口号差异"
#: ../../source/tor-and-proxies.rst:34 d9ac1b375147453288de6411fa79eb06
msgid "The SOCKS port differs by Tor installation:"
msgstr "不同的 Tor 安装方式监听的 SOCKS 端口不同:"
#: ../../source/tor-and-proxies.rst:36 ad205064c5394cbba1e596552f71f374
msgid ""
"**System ``tor`` daemon** (``apt install tor``, ``brew install tor``, "
"Tails) listens on ``9050``."
msgstr ""
"**系统级 ``tor`` 守护进程**\\ (``apt install tor``\\ 、\\ ``brew install tor``\\ "
"、Tails)监听 ``9050``\\ 。"
#: ../../source/tor-and-proxies.rst:37 9e3a803c6b924430bc61b4462005b015
msgid "**Tor Browser bundle** ships its own ``tor`` listening on ``9150``."
msgstr "**Tor 浏览器套件**\\ 自带的 ``tor`` 监听在 ``9150``\\ 。"
#: ../../source/tor-and-proxies.rst:39 4c82ce8f33a344abb8aaeb852b762a7a
msgid "If a connection refuses, try the other port:"
msgstr "如果连接被拒绝,请换另一个端口试试:"
#: ../../source/tor-and-proxies.rst:50 8fde99f033d0470abd7164e97eab656d
msgid "A note on results over Tor"
msgstr "关于在 Tor 上结果的说明"
#: ../../source/tor-and-proxies.rst:52 99e2940611824ba0a08166be4788f7c7
msgid ""
"Most public WAFs (Cloudflare, DDoS-Guard, AWS WAF, Akamai) block Tor exit"
" nodes by default — usually more aggressively than they block datacenter "
"IPs. A Tor run typically produces **more UNKNOWNs and fewer CLAIMEDs** "
"than the same run from a residential connection. This is not a bug in "
"Maigret; it is the cost of anonymity."
msgstr ""
"大多数公共 WAF(Cloudflare、DDoS-Guard、AWS WAF、Akamai)都会默认封锁 Tor 出口节点,而且通常比封数据中心"
" IP 更激进。因此走 Tor 跑一次,通常会比从居民宽带跑同样一次得到\\ **更多 UNKNOWN、更少 CLAIMED**\\ 。这不是 "
"Maigret 的 bug,而是匿名带来的代价。"
#: ../../source/tor-and-proxies.rst:54 cffac7c98141464eaed495930432725c
msgid "Recommended flags for a Tor run:"
msgstr "走 Tor 时推荐的参数:"
#: ../../source/tor-and-proxies.rst:60 4ddc5f7075c44db6b0b935c06251d907
msgid ""
"``--timeout 60`` — Tor circuits add 13 seconds per request; the default "
"30 s causes spurious timeouts."
msgstr "``--timeout 60`` —— Tor 链路会给每个请求增加 1–3 秒延迟;默认的 30 秒会导致虚假的超时。"
#: ../../source/tor-and-proxies.rst:61 92bd328e957e4868a8700b9439758a47
msgid ""
"``--retries 2`` — retries cover transient circuit failures, which are "
"common on Tor."
msgstr "``--retries 2`` —— 重试可以兜住 Tor 上很常见的短暂链路故障。"
#: ../../source/tor-and-proxies.rst:62 bbec2be7ef214c86af5b12bee9578b22
msgid ""
"Optional ``-n 20`` — lowering concurrency (default 100) reduces the "
"chance of exits rate-limiting you."
msgstr "可选项 ``-n 20`` —— 降低并发度(默认 100),减少被出口节点限速的概率。"
#: ../../source/tor-and-proxies.rst:64 96449803c8404e35a03ec728f0e6021f
msgid ""
"If you mainly need to bypass WAFs (rather than to remain anonymous), a "
"residential proxy will usually outperform Tor by a wide margin. See the "
"**\"Lots of sites fail / timeout / return 403\"** section in "
"`TROUBLESHOOTING.md "
"<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_."
msgstr ""
"如果你的主要目的是绕过 WAF(而不是保持匿名),那么住宅代理在表现上通常会远好于 Tor。具体可参见 `TROUBLESHOOTING.md "
"<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_ 中\\ "
"**\"Lots of sites fail / timeout / return 403\"** 一节。"
#: ../../source/tor-and-proxies.rst:67 b06fbc34282845e795752cdbea4f9002
msgid "Running on Tails OS"
msgstr "在 Tails 操作系统下运行"
#: ../../source/tor-and-proxies.rst:69 9b3d8be6c13a43909bee721b25bf677a
msgid ""
"Tails forces every outbound connection through Tor at the network layer. "
"Maigret needs no special configuration to comply — pointing ``--proxy`` "
"at the Tails Tor daemon is enough:"
msgstr ""
"Tails 会在网络层强制所有出站连接走 Tor。Maigret 不需要任何额外配置就能符合这一约束 —— 把 ``--proxy`` 指向 "
"Tails 的 Tor 守护进程即可:"
#: ../../source/tor-and-proxies.rst:75 b3ea70059964418d89589f95a5eafad2
msgid "Things that are **not** needed:"
msgstr "以下做法\\ **没有必要**:"
#: ../../source/tor-and-proxies.rst:77 6b7359d44b3c4af4a78e88ad527a9034
msgid ""
"``torsocks maigret …`` and ``torify maigret …`` — these wrap libc socket "
"calls, but Maigret's HTTP client (``aiohttp`` / ``curl_cffi``) bypasses "
"libc for network I/O, so the wrapper has no effect. Use ``--proxy`` "
"instead."
msgstr ""
"``torsocks maigret …`` 与 ``torify maigret …`` —— 这类工具是包装 libc 的 socket "
"调用,但 Maigret 的 HTTP 客户端(``aiohttp`` / ``curl_cffi``)在网络 I/O 上绕过了 "
"libc,所以这层包装实际上不会起作用。请改用 ``--proxy``\\ 。"
#: ../../source/tor-and-proxies.rst:78 e8164ca48e07422897e800fb28060e16
msgid ""
"``--tor-proxy`` — on Tails, *everything* must go via Tor (the OS enforces"
" this), so the niche \"only .onion via Tor\" mode that ``--tor-proxy`` "
"provides does not apply."
msgstr ""
"``--tor-proxy`` —— 在 Tails 下,*所有*流量都必须走 Tor(由系统强制),因此 ``--tor-proxy`` "
"提供的“仅 .onion 走 Tor”这个小众模式在这里并不适用。"
#: ../../source/tor-and-proxies.rst:81 250efb2bdd5440559aa8ed695cf7f94a
msgid "Installation over Tor on Tails"
msgstr "在 Tails 上通过 Tor 进行安装"
#: ../../source/tor-and-proxies.rst:83 249743f00a9f4c648532b8dcb67a3849
msgid ""
"``pip`` itself does not know about Tor; on Tails you need ``torsocks`` to"
" wrap it:"
msgstr "``pip`` 自己并不知道 Tor 的存在;在 Tails 上需要用 ``torsocks`` 把它包起来:"
#: ../../source/tor-and-proxies.rst:89 d7028e56fbe64a80ae5bbe1d89c52043
msgid ""
"After install, the binary lands in ``~/.local/bin/maigret``. If "
"``maigret: command not found``, either add ``~/.local/bin`` to ``PATH`` "
"or invoke it as ``python3 -m maigret <username>``."
msgstr ""
"安装完成后,可执行文件位于 ``~/.local/bin/maigret``\\ 。如果遇到 ``maigret: command not "
"found``,要么把 ``~/.local/bin`` 加进 ``PATH``,要么以 ``python3 -m maigret "
"<username>`` 方式调用。"
#: ../../source/tor-and-proxies.rst:92 4fe964b5b15f48859b86b50ca7aaf6df
msgid "Persisting Maigret across Tails sessions"
msgstr "让 Maigret 跨 Tails 会话保留"
#: ../../source/tor-and-proxies.rst:94 e24c907641a94caf8ad08b730021ca4e
msgid ""
"Tails wipes ``~/.local/`` on reboot unless you configure the Persistent "
"Storage to keep it. This is Tails configuration, not Maigret "
"configuration — see the official Tails docs:"
msgstr ""
"除非你配置了 Tails 的“持久化存储”,否则 ``~/.local/`` 会在重启时被清空。这属于 Tails 的配置,与 Maigret "
"无关 —— 请参考官方文档:"
#: ../../source/tor-and-proxies.rst:96 5746e67fcb8e4d1692ee1b0f6d1d6f5d
msgid ""
"`Persistent Storage on Tails "
"<https://tails.boum.org/doc/persistent_storage/>`_"
msgstr "`Tails 的持久化存储 <https://tails.boum.org/doc/persistent_storage/>`_"
#: ../../source/tor-and-proxies.rst:97 2ef228cce1ca4daf851f50adb07a5ad4
msgid ""
"`Configuring Persistent Storage features "
"<https://tails.boum.org/doc/persistent_storage/configure/>`_"
msgstr "`配置持久化存储的具体功能 <https://tails.boum.org/doc/persistent_storage/configure/>`_"
#: ../../source/tor-and-proxies.rst:99 51476935068a4586b6d90a8b3af2be59
msgid ""
"A step-by-step recipe contributed by a user (persisting "
"``~/.local/lib/python3.9`` and ``~/.local/bin`` and patching ``.bashrc``)"
" is in `issue #544 "
"<https://github.com/soxoj/maigret/issues/544#issuecomment-1356469171>`__."
" Treat it as a starting point: the Python version and Tails internals "
"change between Tails releases."
msgstr ""
"`issue #544 "
"<https://github.com/soxoj/maigret/issues/544#issuecomment-1356469171>`__ "
"中有一位用户贡献的逐步操作配方(持久化 ``~/.local/lib/python3.9`` 与 ``~/.local/bin``,并修改 "
"``.bashrc``)。请把它当作起点即可:Tails 的 Python 版本和系统内部结构会随版本变化。"
#: ../../source/tor-and-proxies.rst:102 b9a8f9016be14e60b8f81eb0aa237568
msgid "Reports on Tails — where to save them"
msgstr "Tails 上的报告 —— 应该保存到哪里"
#: ../../source/tor-and-proxies.rst:104 ece3ef6d8a32412eba76e97e7b505d66
msgid ""
"The default ``reports/`` directory lives next to the working directory "
"and is wiped with the amnesiac session. To save reports somewhere "
"persistent, either pass ``-fo``:"
msgstr "默认的 ``reports/`` 目录位于工作目录旁,会随这次健忘式会话一起被清空。要把报告保存到持久化的位置,可以传入 ``-fo``:"
#: ../../source/tor-and-proxies.rst:110 df1fb36e79994387b87c2b3e26243fb7
msgid ""
"or set ``\"reports_path\"`` in your ``settings.json`` to a persistent "
"path. See :doc:`settings`."
msgstr ""
"或者把 ``settings.json`` 中的 ``\"reports_path\"`` 设为一个持久化路径。详见 "
":doc:`settings`\\ 。"
#: ../../source/tor-and-proxies.rst:113 46ae196a8d704cb3ad43e87fb0da7703
msgid "Programmatic equivalents (Python library)"
msgstr "以 Python 库形式使用时的等价写法"
#: ../../source/tor-and-proxies.rst:115 53560bf488684147b1af9098edc5f147
msgid ""
"The same options are available through the Python API. See :doc:`library-"
"usage` — the relevant keyword arguments are ``proxy=``, ``tor_proxy=`` "
"and ``i2p_proxy=``, accepting the same URL formats as the CLI flags."
msgstr ""
"上述全部选项都可以通过 Python API 使用。详见 :doc:`library-usage` —— 对应的关键字参数为 "
"``proxy=``\\ 、\\ ``tor_proxy=`` 和 ``i2p_proxy=``,接受的 URL 格式与 CLI 选项完全一致。"
#: ../../source/tor-and-proxies.rst:118 0442e42c309748caa809a94ffcbd15c4
msgid "See also"
msgstr "延伸阅读"
#: ../../source/tor-and-proxies.rst:120 627d782f87af47159601ea834f7f23b8
msgid ":doc:`command-line-options` — full reference for the three flags."
msgstr ":doc:`command-line-options` —— 上述三个参数的完整说明。"
#: ../../source/tor-and-proxies.rst:121 bad3a48187aa46099c7d7dd83d385521
msgid ""
"`TROUBLESHOOTING.md "
"<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_ — quick"
" recipes for ``.onion`` / I2P sites and for WAF-induced 403s."
msgstr ""
"`TROUBLESHOOTING.md "
"<https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_ —— 针对 "
"``.onion`` / I2P 站点以及 WAF 触发的 403 问题的快速排查方案。"
#: ../../source/tor-and-proxies.rst:122 5e475ebc7e784d9b95950e8a06a9da08
msgid ":doc:`library-usage` — proxy options for embedded use."
msgstr ":doc:`library-usage` —— 嵌入式使用场景下的代理选项。"
@@ -0,0 +1,126 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-22 20:48+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/usage-examples.rst:4 f20bb5db270a45b9a46f9ea9235e8046
msgid "Usage examples"
msgstr "使用示例"
#: ../../source/usage-examples.rst:6 29bd6b2971534bebac4719eb5606ebf6
msgid "You can use Maigret as:"
msgstr "你可以以下列方式使用 Maigret:"
#: ../../source/usage-examples.rst:8 0d13962bb83c496ca9185b4957ce729a
msgid "a command line tool: initial and a default mode"
msgstr "命令行工具:最初的、也是默认的使用方式"
#: ../../source/usage-examples.rst:9 7335113bff4c4ff2811042119bdd7a34
msgid ""
"a `web interface <#web-interface>`_: view the graph with results and "
"download all report formats on a single page"
msgstr ""
"`Web 界面 <#web-interface>`_:在同一个页面里查看结果图谱,并下载所有格式的报告"
#: ../../source/usage-examples.rst:10 2e4d1ff6ac8142a285499de3dbaf4b99
msgid "a library: integrate Maigret into your own project"
msgstr "Python 库:将 Maigret 集成进你自己的项目"
#: ../../source/usage-examples.rst:13 639d7206182f49fe8c0516827db36791
msgid "Use Cases"
msgstr "使用案例"
#: ../../source/usage-examples.rst:16 c3afa53b1cb94d35ac6fef0493b044f0
msgid ""
"Search for accounts with username ``machine42`` on top 500 sites (by "
"default, according to Majestic Million rank) from the Maigret DB."
msgstr ""
"在 Maigret 数据库中、按 Majestic Million 排名前 500 的站点(默认范围)上"
"搜索用户名为 ``machine42`` 的账号。"
#: ../../source/usage-examples.rst:22 d0977ee969484a1eb5c857f3cf45fa65
msgid ""
"Search for accounts with username ``machine42`` on **all sites** from the"
" Maigret DB."
msgstr ""
"在 Maigret 数据库中的\ **所有站点**\ 上搜索用户名为 ``machine42`` 的账号。"
#: ../../source/usage-examples.rst:29 bb49f44d1bd1407c9d512e3923663590
msgid ""
"Maigret will search for accounts on a huge number of sites, and some of "
"them may return false positive results. At the moment, we are working on "
"autorepair mode to deliver the most accurate results."
msgstr ""
"Maigret 会在大量站点上进行搜索,其中部分站点可能给出误报。目前我们正在开发"
"自动修复模式,以提供最准确的结果。"
#: ../../source/usage-examples.rst:33 09e5da08de63493d83adbb25ca2bc415
msgid "If you experience many false positives, you can do the following:"
msgstr "如果你遇到了较多误报,可以尝试以下做法:"
#: ../../source/usage-examples.rst:35 89b8e37d364f487f9f0475e81c771482
msgid "Install the last development version of Maigret from GitHub"
msgstr "从 GitHub 安装 Maigret 的最新开发版本"
#: ../../source/usage-examples.rst:36 495b144f263c4f78803509d7f162cea3
msgid ""
"Run Maigret with ``--self-check --auto-disable`` flag and agree on "
"disabling of problematic sites"
msgstr ""
"使用 ``--self-check --auto-disable`` 参数运行 Maigret,并同意自动禁用"
"有问题的站点"
#: ../../source/usage-examples.rst:38 c94bf3d4bd19499289bef6561bcfaaf3
msgid ""
"Search for accounts with username ``machine42`` and generate HTML and PDF"
" reports."
msgstr ""
"搜索用户名为 ``machine42`` 的账号,并生成 HTML 与 PDF 格式的报告。"
#: ../../source/usage-examples.rst:44 39bc7cdeb1b3406c8b68295ef37cc15b
msgid "or"
msgstr "或:"
#: ../../source/usage-examples.rst:51 e63cc4ef7c46427693eb487bd22efcd7
msgid "Search for accounts with username ``machine42`` on Facebook only."
msgstr "仅在 Facebook 上搜索用户名为 ``machine42`` 的账号。"
#: ../../source/usage-examples.rst:57 52ba0b41f8f54a13b846a976a8fcd201
msgid ""
"Extract information from the Steam page by URL and start a search for "
"accounts with found username ``machine42``."
msgstr ""
"通过 URL 从一个 Steam 页面中抽取信息,并以其中发现的用户名 ``machine42`` "
"为起点发起搜索。"
#: ../../source/usage-examples.rst:63 da17a981cdfe44ada63b1925dca1f676
msgid ""
"Search for accounts with username ``machine42`` only on US and Japanese "
"sites."
msgstr "只在美国和日本的站点上搜索用户名为 ``machine42`` 的账号。"
#: ../../source/usage-examples.rst:69 173e16cb0c8c44bfaf9df2fe96118bc7
msgid ""
"Search for accounts with username ``machine42`` only on sites related to "
"software development."
msgstr "只在软件开发相关的站点上搜索用户名为 ``machine42`` 的账号。"
#: ../../source/usage-examples.rst:75 9e6b3b424d064cde99cf312ec2864121
msgid ""
"Search for accounts with username ``machine42`` on uCoz sites only "
"(mostly CIS countries)."
msgstr "只在 uCoz 系站点(主要为独联体国家)上搜索用户名为 ``machine42`` 的账号。"
@@ -0,0 +1,499 @@
# Simplified Chinese translation of the Maigret documentation.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
#
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-22 20:48+0200\n"
"PO-Revision-Date: 2026-05-22 21:00+0200\n"
"Last-Translator: \n"
"Language: zh_CN\n"
"Language-Team: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/use-cases/crypto.rst:4 6a36cde76cb44081bf4d2fe545c36f32
msgid "Cryptocurrency & Web3 Investigations"
msgstr "加密货币与 Web3 调查"
#: ../../source/use-cases/crypto.rst:6 446f8286401e46219fbc5618c4a874fa
msgid ""
"Blockchain transactions are public, but the people behind wallets are "
"not. Maigret helps bridge this gap by finding Web3 accounts tied to a "
"username, revealing the person behind a pseudonymous crypto persona."
msgstr ""
"链上交易是公开的,但钱包背后的人不是。Maigret 通过依据用户名定位与之关联"
"的 Web3 账号,帮助弥合这道鸿沟,揭示匿名加密身份背后的真实人物。"
#: ../../source/use-cases/crypto.rst:9 3a9d880c2bfe4cfe83b745e3cc738721
msgid "Why it matters"
msgstr "意义所在"
#: ../../source/use-cases/crypto.rst:11 25d4bd14a8d24865a9ebdbe8d9f7690b
msgid ""
"Crypto investigations often start with a wallet address or an ENS name "
"but hit a wall — the blockchain tells you *what* happened, not *who* did "
"it. A username, however, is reused across platforms. If someone trades on"
" OpenSea as ``zachxbt`` and posts on Warpcast as ``zachxbt``, Maigret "
"connects the dots and builds a full profile."
msgstr ""
"加密相关调查通常以钱包地址或 ENS 名为起点,但很快就会撞墙 —— 区块链能告诉"
"你*发生了什么*,却不会告诉你*是谁干的*。然而,用户名往往会跨平台复用。如果"
"某人在 OpenSea 上以 ``zachxbt`` 进行交易,同时又以 ``zachxbt`` 的身份在 "
"Warpcast 上发帖,Maigret 就能把这些点连起来,构建出一份完整画像。"
#: ../../source/use-cases/crypto.rst:13 7b5220423458480d81d6a9744aedc69a
msgid "Common scenarios:"
msgstr "常见场景:"
#: ../../source/use-cases/crypto.rst:15 7d0ffb5af12e4e2bb8925ce75c49224e
msgid ""
"**Scam attribution.** A rug-pull promoter uses the same alias on Fragment"
" (Telegram username marketplace), OpenSea, and a personal blog."
msgstr ""
"**诈骗归因。** 一名 rug-pull 推手在 Fragment(Telegram 用户名交易市场)、"
"OpenSea 以及个人博客上使用同一个别名。"
#: ../../source/use-cases/crypto.rst:16 f2be36892ccb42d5ae3d95415d584de2
msgid ""
"**Sanctions compliance.** Verifying whether a counterparty's online "
"footprint matches known sanctioned individuals."
msgstr ""
"**制裁合规。** 验证交易对手在网络上的活动痕迹是否与已知受制裁人物相吻合。"
#: ../../source/use-cases/crypto.rst:17 72830f7113ca477ba6775d1f04686fd4
msgid ""
"**Due diligence.** Before an OTC deal or DAO vote, checking whether the "
"other party has a consistent online presence or is a freshly created "
"sockpuppet."
msgstr ""
"**尽职调查。** 在 OTC 交易或 DAO 投票之前,核查对方是否拥有一致、长期的"
"在线身份,还是新近创建的马甲账号。"
#: ../../source/use-cases/crypto.rst:18 92a4d86b40e843999c45d84248ba8843
msgid ""
"**Stolen funds tracing.** A stolen NFT appears on OpenSea under a new "
"account — but the username matches a Warpcast profile with real-world "
"links."
msgstr ""
"**被盗资金追踪。** 一枚被盗的 NFT 出现在 OpenSea 上的一个新账号下 —— 但其"
"用户名却与某个 Warpcast 主页相匹配,而后者还带有现实身份的线索。"
#: ../../source/use-cases/crypto.rst:21 88e142c2d4104353b3e29c26dfe7eba3
msgid "Supported sites"
msgstr "支持的站点"
#: ../../source/use-cases/crypto.rst:23 d7675ff36f6844449d615d365b692c50
msgid "Maigret currently checks the following crypto and Web3 platforms:"
msgstr "Maigret 目前可以检查以下加密 / Web3 平台:"
#: ../../source/use-cases/crypto.rst:29 36479686d5bc4b80b6c626b898a3fcda
msgid "Site"
msgstr "站点"
#: ../../source/use-cases/crypto.rst:30 396d36b953a6426b8067653c9caa9389
msgid "What it reveals"
msgstr "能揭示的信息"
#: ../../source/use-cases/crypto.rst:31 401a64d61a774e96aa33dd2a7bb4318a
msgid "Notes"
msgstr "备注"
#: ../../source/use-cases/crypto.rst:32 e0886b1eb4504204be03671fc2779641
msgid "**OpenSea**"
msgstr "**OpenSea**"
#: ../../source/use-cases/crypto.rst:33 018bb624aa814081a0604ba034ad62e4
msgid "NFT collections, trading history, profile bio, linked website"
msgstr "NFT 收藏、交易历史、个人简介、关联网站"
#: ../../source/use-cases/crypto.rst:35 ea3b4a2e47614186b3ddbab9646a19be
msgid "**Rarible**"
msgstr "**Rarible**"
#: ../../source/use-cases/crypto.rst:36 fd0496f01d174dc3bc9accfce493732d
msgid "NFT marketplace profile, collections, listing history"
msgstr "NFT 市场主页、收藏、挂单历史"
#: ../../source/use-cases/crypto.rst:37 328b6ef534234f8a80dd362226fd20e1
msgid "Complements OpenSea for NFT attribution across marketplaces"
msgstr "在跨市场的 NFT 归因中,可作为 OpenSea 的补充"
#: ../../source/use-cases/crypto.rst:38 4261580262c549d097c7fb6882265b12
msgid "**Zora**"
msgstr "**Zora**"
#: ../../source/use-cases/crypto.rst:39 2399fac946c045cab0ef07b24e66a305
msgid "Zora Network profile, minted NFTs, creator activity"
msgstr "Zora Network 主页、已铸造 NFT、创作者活动"
#: ../../source/use-cases/crypto.rst:40 6ff049235f9f4cfe9d7b74fc6540d0fa
msgid "Ethereum L2 creator platform; useful for on-chain art attribution"
msgstr "以太坊 L2 上的创作者平台;适用于链上艺术品归因"
#: ../../source/use-cases/crypto.rst:41 e1018c24556144fdbf2fb840d3daf49c
msgid "**Polymarket**"
msgstr "**Polymarket**"
#: ../../source/use-cases/crypto.rst:42 d4be83a2949445ca8c18a35fa002c8c8
msgid "Prediction-market profile, positions, public portfolio P&L"
msgstr "预测市场主页、持仓、公开投资组合的盈亏(P&L)"
#: ../../source/use-cases/crypto.rst:43 720b43eda58f41d291e12f5b72f1d062
msgid "Useful for political/financial prediction attribution"
msgstr "适用于政治 / 财经类预测的归因分析"
#: ../../source/use-cases/crypto.rst:44 1529266027b742a8b7246a2e63d16d3c
msgid "**Warpcast** (Farcaster)"
msgstr "**Warpcast**\ (Farcaster)"
#: ../../source/use-cases/crypto.rst:45 2ab39c49d51643f2828ba632646663f6
msgid "Decentralized social profile, posts, follower graph, Farcaster ID"
msgstr "去中心化社交主页、帖子、关注者关系图、Farcaster ID"
#: ../../source/use-cases/crypto.rst:46 012aec8cfc5748f7a5333b64f160a23f
msgid ""
"Every Farcaster ID maps to an Ethereum address via the on-chain ID "
"registry"
msgstr "每个 Farcaster ID 都通过链上的 ID 注册合约映射到一个以太坊地址"
#: ../../source/use-cases/crypto.rst:47 150075eb065148a79d79ab23146c587a
msgid "**Fragment**"
msgstr "**Fragment**"
#: ../../source/use-cases/crypto.rst:48 52316f945d114b6c85205c2bb2779043
msgid "Telegram username ownership, TON wallet address, purchase date and price"
msgstr "Telegram 用户名的所有权、TON 钱包地址、购买日期与价格"
#: ../../source/use-cases/crypto.rst:49 19f2fa34a5cf4f70b8952e639a0ba83f
msgid "Valuable for linking Telegram identities to TON wallets"
msgstr "在把 Telegram 身份与 TON 钱包关联起来这件事上非常有价值"
#: ../../source/use-cases/crypto.rst:50 23d1eda868054a15999e548214cfed59
msgid "**Paragraph**"
msgstr "**Paragraph**"
#: ../../source/use-cases/crypto.rst:51 04ff000179da43b594b86e2bad1b2a44
msgid "Web3 blog/newsletter, ETH wallet address, linked Twitter handle"
msgstr "Web3 博客 / Newsletter、ETH 钱包地址、关联的 Twitter handle"
#: ../../source/use-cases/crypto.rst:52 a9355ba0d54e4db6b3c7f295e402d511
msgid "Richest cross-platform data among crypto sites"
msgstr "在加密类站点中,跨平台数据最为丰富"
#: ../../source/use-cases/crypto.rst:53 d6b87473a4c24f91887880bfcb6d60fa
msgid "**Tonometerbot**"
msgstr "**Tonometerbot**"
#: ../../source/use-cases/crypto.rst:54 b9fa8e5d654a417faccb1e06172cfbe0
msgid "TON wallet balance, subscriber count, NFT collection, rankings"
msgstr "TON 钱包余额、订阅数、NFT 收藏、各类排名"
#: ../../source/use-cases/crypto.rst:55 d9a9d072ac734075aad957cf5360dfc1
msgid "TON blockchain analytics"
msgstr "TON 区块链分析"
#: ../../source/use-cases/crypto.rst:56 d28f61e2e9364e148af40ecf2a7a4801
msgid "**Spatial**"
msgstr "**Spatial**"
#: ../../source/use-cases/crypto.rst:57 253757f68abc46c3bcd2c89d46288ea0
msgid ""
"Metaverse profile, linked social accounts (Discord, Twitter, Instagram, "
"LinkedIn, TikTok)"
msgstr "元宇宙主页、关联的社交账号(Discord、Twitter、Instagram、LinkedIn、TikTok)"
#: ../../source/use-cases/crypto.rst:58 097b7928f50a465884c00a9a3a28d6c8
msgid "Rich cross-platform links"
msgstr "跨平台链接丰富"
#: ../../source/use-cases/crypto.rst:59 73cf117adfb841e8a73e4adbab43dc70
msgid "**Revolut.me**"
msgstr "**Revolut.me**"
#: ../../source/use-cases/crypto.rst:60 7ba0f8054e6146c791aa6eb60156ffb4
msgid ""
"Payment handle: first/last name, country code, base currency, supported "
"payment methods"
msgstr "支付 handle:姓名、国家代码、基础币种、支持的支付方式"
#: ../../source/use-cases/crypto.rst:61 1ff17cb8fa324e8d87af5dada107866a
msgid ""
"Not strictly Web3, but widely used by crypto OTC traders for fiat off-"
"ramps; the public API returns structured KYC-adjacent data"
msgstr ""
"严格意义上不算 Web3,但加密 OTC 交易员普遍用它作为法币出口;其公开 API "
"返回的是结构化的、接近 KYC 的数据"
#: ../../source/use-cases/crypto.rst:64 f81736e0bc4248328708dac2646e0eeb
msgid "Real-world example: zachxbt"
msgstr "真实案例:zachxbt"
#: ../../source/use-cases/crypto.rst:66 30ec801d7c774c33b231f9a2e225e093
msgid ""
"`ZachXBT <https://twitter.com/zachxbt>`_ is a well-known on-chain "
"investigator. Let's see what Maigret can find from just the username "
"``zachxbt``:"
msgstr ""
"`ZachXBT <https://twitter.com/zachxbt>`_ 是一位知名的链上调查员。我们来看看仅凭用户名 ``zachxbt``,Maigret 能挖出些什么:"
#: ../../source/use-cases/crypto.rst:72 af592d7feeb54257a3f4d0272a872d7c
msgid ""
"Maigret finds 5 accounts and automatically extracts structured data from "
"each:"
msgstr ""
"Maigret 找到 5 个账号,并自动从每个账号中抽取结构化数据:"
#: ../../source/use-cases/crypto.rst:74 2aa9253cc3ef444190395e9313a23922
msgid ""
"**Fragment** — confirms the Telegram username ``@zachxbt`` is claimed, "
"reveals the TON wallet address (``EQBisZrk...``), purchase price (10 "
"TON), and date (January 2023)."
msgstr ""
"**Fragment** —— 确认 Telegram 用户名 ``@zachxbt`` 已被占用,披露其 TON "
"钱包地址(``EQBisZrk...``)、购买价格(10 TON)以及日期(2023 年 1 月)。"
#: ../../source/use-cases/crypto.rst:76 d4210fa5dce642609d0e804626a428b6
msgid ""
"**Paragraph** — the richest result. Returns the real name used on the "
"platform (``ZachXBT``), bio (``Scam survivor turned 2D investigator``), "
"an Ethereum wallet address (``0x23dBf066...``), and a linked Twitter "
"handle (``zachxbt``). The ``wallet_address`` field is especially valuable"
" — it directly links the pseudonym to an on-chain identity."
msgstr ""
"**Paragraph** —— 信息最丰富的一项。返回该平台上使用的姓名(``ZachXBT``)、个人简介(``Scam survivor turned 2D investigator``)、一个以太坊钱包地址(``0x23dBf066...``)以及关联的 Twitter handle(``zachxbt``)。\ ``wallet_address`` 字段尤其有价值 —— 它直接把这个化名与一个链上身份关联在了一起。"
#: ../../source/use-cases/crypto.rst:78 80ff1d93437b497c9d3fbe95c5ddbcd6
msgid ""
"**Warpcast** — Farcaster profile with a Farcaster ID (``fid: 20931``), "
"profile image, and social graph (33K followers). Every Farcaster ID is "
"tied to an Ethereum address via the on-chain ID registry, so this is "
"another on-chain anchor."
msgstr ""
"**Warpcast** —— Farcaster 主页,包含 Farcaster ID(``fid: 20931``)、头像"
"以及社交图谱(3.3 万关注者)。每个 Farcaster ID 都通过链上的 ID 注册合约"
"绑定到一个以太坊地址,因此这又是一个链上锚点。"
#: ../../source/use-cases/crypto.rst:80 08511f31a12b436589e00f1c5a6681d5
msgid ""
"**OpenSea** — NFT marketplace profile with bio (``On-chain sleuth | 10x "
"rug pull survivor``), avatar (hosted on ``seadn.io`` with an Ethereum "
"address in the URL path), and a link to an external investigations page."
msgstr ""
"**OpenSea** —— NFT 市场主页,包含个人简介(``On-chain sleuth | 10x rug pull survivor``)、头像(托管在 ``seadn.io`` 上,URL 路径中带有一个以太坊地址),以及一个指向外部调查页面的链接。"
#: ../../source/use-cases/crypto.rst:82 80d74c0512d94d86ad70c587e206b9a4
msgid ""
"**Hive Blog** — blockchain-based blog account created in March 2025. Low "
"activity (1 post), but confirms the username is claimed across blockchain"
" ecosystems."
msgstr ""
"**Hive Blog** —— 基于区块链的博客账号,创建于 2025 年 3 月。活跃度较低"
"(仅 1 条帖子),但可以证明该用户名在多个区块链生态中均被占用。"
#: ../../source/use-cases/crypto.rst:84 514341ad3ff4473587b1183df9ce902c
msgid "From a single username, Maigret produces:"
msgstr "仅凭一个用户名,Maigret 就产出了:"
#: ../../source/use-cases/crypto.rst:86 9b7cf6d5440645ddbff26636a94b51b3
msgid ""
"**2 wallet addresses** — one TON (from Fragment), one Ethereum (from "
"Paragraph)"
msgstr ""
"**2 个钱包地址** —— 一个 TON(来自 Fragment),一个以太坊(来自 Paragraph)"
#: ../../source/use-cases/crypto.rst:87 f12a75c8bd554626bb776d265b0d03db
msgid "**1 confirmed Twitter handle** — ``zachxbt`` (from Paragraph)"
msgstr "**1 个已确认的 Twitter handle** —— ``zachxbt``\ (来自 Paragraph)"
#: ../../source/use-cases/crypto.rst:88 ee2b8a4d2d654460b1426150cde12138
msgid "**1 Telegram username** — ``@zachxbt`` (from Fragment)"
msgstr "**1 个 Telegram 用户名** —— ``@zachxbt``\ (来自 Fragment)"
#: ../../source/use-cases/crypto.rst:89 fb02a867fe9041688628fd93a80c7b9c
msgid "**1 external link** — ``investigations.notion.site`` (from OpenSea)"
msgstr "**1 个外部链接** —— ``investigations.notion.site``\ (来自 OpenSea)"
#: ../../source/use-cases/crypto.rst:90 bea84cf7fa664843a3659a79d1ca09c2
msgid "**Social graph data** — 33K Farcaster followers, blog activity timestamps"
msgstr "**社交图谱数据** —— 3.3 万 Farcaster 关注者、博客活动时间戳"
#: ../../source/use-cases/crypto.rst:92 da5d6e12494449358fbedcf8ce518e3f
msgid ""
"This is enough to pivot into blockchain analysis tools (Etherscan, "
"Arkham, Nansen) using the wallet addresses, or into social media analysis"
" using the Twitter handle."
msgstr ""
"凭这些信息,你可以借助钱包地址转向区块链分析工具(Etherscan、Arkham、"
"Nansen)继续深挖,也可以借助 Twitter handle 转入社交媒体分析。"
#: ../../source/use-cases/crypto.rst:95 24f6a25beba34fe3a3ae1a03d000edf1
msgid "Workflow: from username to wallet"
msgstr "工作流:从用户名到钱包"
#: ../../source/use-cases/crypto.rst:97 8ea66b29b70a4897b6592e5ff2cd8979
msgid "**Step 1: Search crypto platforms**"
msgstr "**步骤 1:搜索加密类平台**"
#: ../../source/use-cases/crypto.rst:103 cf0ac1ee52654a4d9e777b70e34fa09f
msgid "Review the results. Pay attention to:"
msgstr "查看结果,重点关注:"
#: ../../source/use-cases/crypto.rst:105 3791046ddb614f74860bc4900ef1e0a9
msgid ""
"**Fragment** — if the username is claimed, you get a TON wallet address "
"directly."
msgstr ""
"**Fragment** —— 如果该用户名已被占用,你就能直接拿到一个 TON 钱包地址。"
#: ../../source/use-cases/crypto.rst:106 8f9b65f0f9074ff3adcb90e07e3ee597
msgid ""
"**Paragraph** — blog profiles often contain an ETH address and a Twitter "
"handle."
msgstr ""
"**Paragraph** —— 博客主页中常常包含一个 ETH 地址和一个 Twitter handle。"
#: ../../source/use-cases/crypto.rst:107 cde260dfc7f144c69db4362f24df1c68
msgid ""
"**Warpcast** — Farcaster IDs map to Ethereum addresses via the on-chain "
"registry."
msgstr ""
"**Warpcast** —— Farcaster ID 通过链上注册合约映射到以太坊地址。"
#: ../../source/use-cases/crypto.rst:108 a3197c64fedd4b8692e27af02ad0c125
msgid "**OpenSea** — avatar URLs sometimes contain wallet addresses in the path."
msgstr "**OpenSea** —— 头像 URL 的路径中,有时会嵌入钱包地址。"
#: ../../source/use-cases/crypto.rst:110 6b73b4fbc38040918cbffda20c3b621b
msgid "**Step 2: Expand with extracted identifiers**"
msgstr "**步骤 2:基于抽取出的标识符进行扩展**"
#: ../../source/use-cases/crypto.rst:112 1165778c3df34d3aa460923e430fa731
msgid ""
"Maigret automatically extracts additional identifiers from found profiles"
" (real names, linked accounts, profile URLs) and recursively searches for"
" them. This is enabled by default. If Maigret finds a linked Twitter "
"handle on a Paragraph profile, it will automatically search for that "
"handle across all sites."
msgstr ""
"Maigret 会自动从已发现的主页中抽取更多标识符(真实姓名、关联账号、主页 "
"URL 等),并对其进行递归搜索。此功能默认启用。如果 Maigret 在某个 "
"Paragraph 主页上发现了一个关联的 Twitter handle,它会自动对该 handle 在"
"所有站点上发起搜索。"
#: ../../source/use-cases/crypto.rst:114 3e2968b0ab304d0d9148dda88cc3dd4e
msgid "**Step 3: Cross-reference with non-crypto platforms**"
msgstr "**步骤 3:与非加密类平台交叉比对**"
#: ../../source/use-cases/crypto.rst:116 943a68e9bbcc48a6b945ce1e40e0cf30
msgid ""
"The real power is connecting crypto personas to mainstream accounts. Drop"
" the tag filter:"
msgstr ""
"真正的威力在于把加密身份与主流账号串联起来。去掉标签过滤即可:"
#: ../../source/use-cases/crypto.rst:122 be3642062cc84d63b2d6f5fc1d15135e
msgid ""
"This checks all 3000+ sites. A match on GitHub, Reddit, or a forum can "
"reveal the person behind the wallet."
msgstr ""
"这会检查全部 3000+ 个站点。如果在 GitHub、Reddit 或某个论坛上匹配上了,"
"就可能揭示钱包背后的真人身份。"
#: ../../source/use-cases/crypto.rst:125 dd079778b09d4c10be312d6dbe665757
msgid "Workflow: from wallet to identity"
msgstr "工作流:从钱包到身份"
#: ../../source/use-cases/crypto.rst:127 41314afd5a5d4372a104b4d4f16f6597
msgid ""
"If you start with a wallet address rather than a username, you can use "
"complementary tools to get a username first:"
msgstr ""
"如果你的起点是钱包地址而不是用户名,可以先借助一些辅助工具,把它转成用户名:"
#: ../../source/use-cases/crypto.rst:129 c3027e423cf944efbd507d19142c4ad1
msgid ""
"**ENS / Unstoppable Domains** — resolve the wallet address to a human-"
"readable name (``vitalik.eth``). Then search that name in Maigret."
msgstr ""
"**ENS / Unstoppable Domains** —— 把钱包地址解析为人类可读的名字"
"(``vitalik.eth``),然后在 Maigret 中搜索该名字。"
#: ../../source/use-cases/crypto.rst:130 be0dc030b5934f40882b4613fa6b2abe
msgid ""
"**Etherscan labels** — check if the address has a public label (exchange,"
" known entity)."
msgstr ""
"**Etherscan 标签** —— 查看该地址是否带有公开标签(交易所、已知实体等)。"
#: ../../source/use-cases/crypto.rst:131 97011ce2b6e245edae02ce7466bdca05
msgid ""
"**Fragment** — search the TON wallet address to find which Telegram "
"usernames it purchased."
msgstr ""
"**Fragment** —— 用 TON 钱包地址进行搜索,查看它曾经购买过哪些 Telegram "
"用户名。"
#: ../../source/use-cases/crypto.rst:132 44d52297818b4d3d844ffe9b7ead8003
msgid ""
"**Arkham Intelligence / Nansen** — blockchain attribution platforms that "
"may tag the address with a known identity."
msgstr ""
"**Arkham Intelligence / Nansen** —— 这些区块链归因平台可能已将该地址打上"
"已知身份的标签。"
#: ../../source/use-cases/crypto.rst:134 34f2071cdf9d49108bb8062e095e8aec
msgid "Once you have a username candidate, feed it to Maigret."
msgstr "一旦得到候选用户名,就把它喂给 Maigret。"
#: ../../source/use-cases/crypto.rst:137 4380ee2c21f5418e8031e1db57083e86
msgid "Tips"
msgstr "技巧"
#: ../../source/use-cases/crypto.rst:139 fbd1e7f1f06e48ef888f18d9bcbd931c
msgid ""
"**Username reuse is the #1 signal.** Crypto-native users often reuse "
"their ENS name (``alice.eth``) or a variation (``alice_eth``, "
"``aliceeth``) across platforms. Try all variations."
msgstr ""
"**用户名复用是最重要的信号。** 加密原生用户经常跨平台复用自己的 ENS 名"
"(``alice.eth``)或其变体(``alice_eth``\ 、\ ``aliceeth``)。务必把所有变体"
"都试一遍。"
#: ../../source/use-cases/crypto.rst:140 8bb6fe7926234043aa8a11d5bf5b21dc
msgid ""
"**Fragment is uniquely valuable** because it directly links Telegram "
"usernames to TON wallet addresses — a rare on-chain / off-chain bridge."
msgstr ""
"**Fragment 的价值无可替代**,因为它直接把 Telegram 用户名和 TON 钱包地址"
"绑在了一起 —— 这是一座少见的“链上 / 链下”桥梁。"
#: ../../source/use-cases/crypto.rst:141 358b7cdcaec046a082ee101df7df939f
msgid ""
"**Warpcast profiles are Ethereum-native.** Every Farcaster account is "
"tied to an Ethereum address via the ID registry contract. If you find a "
"Warpcast profile, you implicitly have a wallet address."
msgstr ""
"**Warpcast 主页本质上是以太坊原生的。** 每个 Farcaster 账号都通过 ID "
"注册合约绑定到一个以太坊地址。找到一个 Warpcast 主页,就相当于隐式拿到了"
"一个钱包地址。"
#: ../../source/use-cases/crypto.rst:142 0d2489ee349b458c8e9ce0314d732a51
msgid ""
"**Paragraph often has the richest data** — wallet address, Twitter "
"handle, bio, and activity timestamps in a single API response."
msgstr ""
"**Paragraph 通常是信息最丰富的一项** —— 钱包地址、Twitter handle、个人简介"
"以及活动时间戳,常常在同一份 API 响应中一并返回。"
#: ../../source/use-cases/crypto.rst:143 c5fa5df259ca4db0b2989c65d5f8074d
msgid ""
"**Use** ``--exclude-tags`` **to skip irrelevant sites** when you're "
"focused on crypto:"
msgstr ""
"在专注于加密调查时,\ **使用** ``--exclude-tags`` **跳过无关站点**:"
@@ -0,0 +1,353 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2025, soxoj
# This file is distributed under the same license as the Maigret package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2026.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Maigret 0.6\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-30 14:25+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: zh_CN\n"
"Language-Team: zh_CN <LL@li.org>\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.18.0\n"
#: ../../source/use-cases/scientists.rst:4 7b5ff03b9c0f472b8c3cd95f60c1f8e1
msgid "Academic & Research Investigations"
msgstr ""
#: ../../source/use-cases/scientists.rst:6 e3ac16b61b1f4945a3bf64e154d5d262
msgid ""
"Academic identity has a unique anchor that social-media identity does "
"not: the **ORCID** (Open Researcher and Contributor ID). Every ORCID is "
"verified, globally unique, and tied to a confirmed email — a single "
"confirmed ORCID match between two platforms means the **same human**, "
"with effectively zero false-positive rate."
msgstr ""
#: ../../source/use-cases/scientists.rst:8 d782c133394947f3aa209a9346a99e3e
msgid ""
"Maigret uses ORCID as a first-class identifier: extract it once from a "
"username-keyed profile (iNaturalist, GitHub bio, lab homepage), then "
"pivot into the academic graph (ORCID, OpenAlex, arXiv, DBLP, Scholia) — a"
" chain of HTTP calls that turns an anonymous handle into a CV with "
"employer, education, publication list, citation count, co-author graph, "
"and topical area."
msgstr ""
#: ../../source/use-cases/scientists.rst:11 867228e9de47484e83d1807fd4e6eb26
msgid "Why it matters"
msgstr ""
#: ../../source/use-cases/scientists.rst:13 f5b624f1dd494a4b9457fbb827dc23d5
msgid ""
"Most OSINT work on academic targets stalls at the same bottleneck: people"
" use one alias on a citizen-science site, a different alias on Twitter, a"
" third in a forum signature, and only their real name on PubMed. "
"Connecting the personas by hand means trawling Google Scholar, "
"ResearchGate, university web pages, and old conference programmes. ORCID "
"short-circuits this entirely — one verified identifier resolves all of "
"them."
msgstr ""
#: ../../source/use-cases/scientists.rst:15 b64f4161731e428199f9b3ea7c14f228
msgid "Common scenarios:"
msgstr ""
#: ../../source/use-cases/scientists.rst:17 5deba3f670f64edc8a1bb0fb9f8c23f2
msgid ""
"**Researcher background check.** Before a collaboration, a preprint "
"review, or a grant decision — verifying that a claimed h-index, "
"employment history, and publication count actually match the public ORCID"
" record."
msgstr ""
#: ../../source/use-cases/scientists.rst:18 cbe573103ee549938e66d27db5682fb2
msgid ""
"**Co-author graph reconstruction.** From a single ORCID, OpenAlex returns"
" every co-author, their institutions, and topical clusters — useful for "
"spotting undisclosed conflicts of interest."
msgstr ""
#: ../../source/use-cases/scientists.rst:19 e8757165fe994f439ff329ebd840d5b3
msgid ""
"**Paper mill / fraud investigation.** When a suspicious paper's authors "
"share an ORCID-claimed identity, the cross-platform footprint (or absence"
" of one) is itself evidence."
msgstr ""
#: ../../source/use-cases/scientists.rst:20 c7da890638cb4e7598e1abbe83deb77c
msgid ""
"**Pseudonym deanonymisation.** A wildlife photographer posts naturalist "
"observations under the handle ``kueda``. iNaturalist's public API returns"
" their ORCID, and one further request reveals their real name, employer "
"(California Academy of Sciences), education (UC Berkeley), and 700+ "
"citable observations."
msgstr ""
#: ../../source/use-cases/scientists.rst:21 90a41c759dbe4c18a168180597059fb6
msgid ""
"**Award / appointment due diligence.** Confirming a Turing-Award claim, a"
" tenure status, or a society fellowship via the DBLP and ORCID activity "
"timelines rather than a CV PDF."
msgstr ""
#: ../../source/use-cases/scientists.rst:24 c4c61174c75945dc9ff5f5c0b88dfff4
msgid "Supported sites"
msgstr ""
#: ../../source/use-cases/scientists.rst:26 89387151cb984ef29a906e1e6010b0b7
msgid ""
"Maigret currently checks the following ORCID-keyed platforms (use ``--id-"
"type orcid`` when starting from a bare ORCID, or rely on the recursive "
"chain when starting from a username):"
msgstr ""
#: ../../source/use-cases/scientists.rst:32 7018dd5767c34188852e8849b4f7963d
msgid "Site"
msgstr ""
#: ../../source/use-cases/scientists.rst:33 90d3927d6b5541ef911b851a8b202352
msgid "What it reveals"
msgstr ""
#: ../../source/use-cases/scientists.rst:34 3300c20b483c4730b27f8acbfa5d5309
msgid "Notes"
msgstr ""
#: ../../source/use-cases/scientists.rst:35 165a217f9d334f37ba8c9ba6bea6b6c3
msgid "**ORCID**"
msgstr ""
#: ../../source/use-cases/scientists.rst:36 ed3a9670c6224bb8ac3e5f47ccf94e26
msgid ""
"Full name, biography, employment, education, researcher URLs (homepages "
"and social), keywords, country, linked external IDs (Scopus, "
"ResearcherID, Loop), publication summary, email-verified status"
msgstr ""
#: ../../source/use-cases/scientists.rst:37 66cbb10c920544feb9857a7d6aa94395
msgid ""
"All disciplines — ORCID is a field-agnostic identifier issued to any "
"researcher."
msgstr ""
#: ../../source/use-cases/scientists.rst:38 b5f4938be8634202aa2ef55a2bab7231
msgid "**OpenAlex**"
msgstr ""
#: ../../source/use-cases/scientists.rst:39 4274c0b3ce134202a5fe26d2fab4539c
msgid ""
"Display name and name alternatives, works count, total citations, "
"h-index, i10-index, last-known institutions (with country), topical "
"areas, raw author-name variants from publications"
msgstr ""
#: ../../source/use-cases/scientists.rst:40 1231967f924f44638ab08e7ab8f51a6d
msgid ""
"All disciplines — indexes works across the entire scholarly literature, "
"from humanities to medicine."
msgstr ""
#: ../../source/use-cases/scientists.rst:41 8dd55c43c3814466924b05161b7bbf44
msgid "**arXiv**"
msgstr ""
#: ../../source/use-cases/scientists.rst:42 ffdbe5ff004544beba1352c016a4fdf6
msgid "Author preprint listing — paper titles, IDs, dates"
msgstr ""
#: ../../source/use-cases/scientists.rst:43 e49114fe565a4e0bbdb180046e7fd2a0
msgid ""
"Strongly biased toward physics, math, CS (and quantitative biology, "
"statistics, EE, quantitative finance, economics)."
msgstr ""
#: ../../source/use-cases/scientists.rst:44 9093d0eb93c8445d8229512dcda2361e
msgid "**DBLP**"
msgstr ""
#: ../../source/use-cases/scientists.rst:45 c36bcf7b35f44e38871046b2ade683b4
msgid ""
"Full name, DBLP person ID, paper count, affiliation, awards (e.g. Turing "
"Award), homepage URLs, links to Google Scholar / ResearchGate / Scopus"
msgstr ""
#: ../../source/use-cases/scientists.rst:46 a99b909aaa494a638e30b89c24406004
msgid ""
"Computer science only — a biologist or pure economist will not be in the "
"index."
msgstr ""
#: ../../source/use-cases/scientists.rst:47 4e012a634eac4833bd2f5c11140a6778
msgid "**Scholia**"
msgstr ""
#: ../../source/use-cases/scientists.rst:48 dc081b477db34543a4f5734800bb6bb7
msgid ""
"Wikidata QID for the author, linked publications, co-author graph, "
"employer/affiliation timeline, topical clusters"
msgstr ""
#: ../../source/use-cases/scientists.rst:49 feb7693e07684c1bbab49d2befc782ec
msgid ""
"All disciplines, but only if the author has been curated in Wikidata — "
"coverage is broadest for prominent figures (award winners, senior "
"faculty, deceased researchers)."
msgstr ""
#: ../../source/use-cases/scientists.rst:52 eab5a8d300404c0eb8fbd7ebbcb80333
msgid "Workflow: from username to ORCID"
msgstr ""
#: ../../source/use-cases/scientists.rst:54 808d5ce53d964749887069bae38c807c
msgid "**Step 1: Find a profile that publishes ORCID**"
msgstr ""
#: ../../source/use-cases/scientists.rst:56 d1234c3240e242a287bc35d4ed4bc7ad
msgid ""
"The most reliable bridges from a username to an ORCID are platforms whose"
" users *want* their academic identity discoverable. In practice:"
msgstr ""
#: ../../source/use-cases/scientists.rst:58 82b0e296494b48c7b85603018e3bbe87
#, python-brace-format
msgid ""
"**iNaturalist** — biologists, naturalists, citizen scientists. Public API"
" ``api.inaturalist.org/v1/users/{username}`` returns the ORCID directly "
"in the ``orcid`` field. Maigret extracts it automatically."
msgstr ""
#: ../../source/use-cases/scientists.rst:59 a1f675cec421482ea6f97a690ea06ed3
#, python-brace-format
msgid ""
"**GitHub** — scientists often paste their ORCID URL into the **bio** or "
"**blog** field on their GitHub profile, or under a *generic* entry in "
"``/users/{u}/social_accounts``. A pattern like "
"``orcid\\.org/(\\d{4}-\\d{4}-\\d{4}-\\d{3}[\\dX])`` matches both "
"``http://orcid.org/...`` and ``orcid.org/...`` variants."
msgstr ""
#: ../../source/use-cases/scientists.rst:60 79c5a098a2ad412e875dda25af551ee2
msgid ""
"**Lab homepage / institutional bio.** Use ``--parse <URL>`` to scrape an "
"arbitrary page for known identifiers — useful when the target's footprint"
" lives at ``faculty.example.edu/~jdoe``."
msgstr ""
#: ../../source/use-cases/scientists.rst:62 5239ba6c999445f8ab55e37b84366969
msgid "**Step 2: Let the recursive search run**"
msgstr ""
#: ../../source/use-cases/scientists.rst:68 2a6e2072e73a4f36ae2d17c296e53ae7
msgid ""
"Recursive search is enabled by default. As soon as Maigret extracts an "
"``orcid`` field from any found profile, it queues an ``--id-type orcid`` "
"second wave automatically. No manual chaining needed."
msgstr ""
#: ../../source/use-cases/scientists.rst:70 80271d7bde6244ae8bae4efa2e8b6932
msgid "**Step 3: Start from a bare ORCID**"
msgstr ""
#: ../../source/use-cases/scientists.rst:72 9e5b19c6bdb646deaeb1e132dfb00b4a
msgid ""
"If you already have the ORCID (e.g. from a paper's author footer, a grant"
" database, or a Wikidata entry):"
msgstr ""
#: ../../source/use-cases/scientists.rst:78 d996f47f9c8b4f4e911e4cd407482945
msgid "This bypasses the username-bridge step entirely."
msgstr ""
#: ../../source/use-cases/scientists.rst:81 dc57bde7ca0043b790d9b2fae391e7ca
msgid "Workflow: from ORCID to mainstream identity"
msgstr ""
#: ../../source/use-cases/scientists.rst:83 cbfc17b9a07a40c391018b99089aa0c5
msgid ""
"The ORCID record itself contains the link back to ordinary social "
"platforms:"
msgstr ""
#: ../../source/use-cases/scientists.rst:85 aecd3e6275544d1ebcc0e05617e1eeb9
msgid ""
"**ORCID ``researcher-urls``** — a list of self-declared external links. "
"Typical entries: lab homepage, Twitter/Bluesky/Mastodon profile, personal"
" blog. These are entered by the researcher and therefore confirmed."
msgstr ""
#: ../../source/use-cases/scientists.rst:86 00baf7aced6e4013a70030dad1e95196
msgid ""
"**ORCID ``external-identifiers``** — IDs from sibling academic systems "
"(Scopus Author ID, ResearcherID/Publons, Loop profile). Each unlocks "
"another extraction pipeline."
msgstr ""
#: ../../source/use-cases/scientists.rst:87 74ffab64a3d041ec83e361347a40fb46
msgid ""
"**OpenAlex ``last_known_institutions``** — current employer with country,"
" ROR ID, and OpenAlex institution ID; useful for pivoting into the "
"institution's directory."
msgstr ""
#: ../../source/use-cases/scientists.rst:88 77fab68f62a544769f76eb08c872deb6
msgid ""
"**DBLP** ``<url>`` **tags** — DBLP records often embed direct links to "
"Google Scholar, ResearchGate, and personal pages."
msgstr ""
#: ../../source/use-cases/scientists.rst:89 e6efabbec6634f1384c1c03caf2990a4
msgid ""
"**Scholia ``wikidata_qid``** — once you have a Wikidata QID, the SPARQL "
"endpoint unlocks the broadest external ID set in any single OSINT system:"
" VIAF, LCCN, GND, Twitter handle, Mastodon handle, IMDb, GitHub, ORCID, "
"and ~200 others."
msgstr ""
#: ../../source/use-cases/scientists.rst:92 8dca4a2181844b9a9e7ef231ddb66d1f
msgid "Tips"
msgstr ""
#: ../../source/use-cases/scientists.rst:94 b2e8e4b9c2f74a4ea9d6a3e7c0d5f1a8
msgid ""
"**Email comes from ORCID** — within the academic chain (ORCID, OpenAlex, "
"arXiv, DBLP, Scholia) it is the only source that returns an email "
"address, and only when the researcher has marked their primary email "
"public. The same response also carries ``history.verified-primary-"
"email``, an ORCID-confirmed flag that means the address was email-"
"validated at registration. Use this as a strong pivot into email-keyed "
"lookups (Holehe, HIBP, Gravatar, etc.)."
msgstr ""
#: ../../source/use-cases/scientists.rst:95 c3f9f5cad3e85b5fb0e7b4f8d1e6f2b9
msgid ""
"**Outside the academic chain, GitLab is the dev platform most likely to "
"leak an email** via its public ``public_email`` field — relevant for "
"scientists who self-host code on a CERN/research-institute GitLab. "
"GitHub's REST API does not expose email by default."
msgstr ""
#: ../../source/use-cases/scientists.rst:96 a465848b418c416c8eccf6f06e35b3d3
msgid ""
"**Compare OpenAlex** ``raw_author_names`` **against the ORCID** ``other-"
"names``. Discrepancies often expose pre-marriage names, transliterations "
"from non-Latin scripts, or co-author misattributions worth flagging."
msgstr ""
#: ../../source/use-cases/scientists.rst:97 44313424c1a14ebfbb94b92ec821e228
#, python-brace-format
msgid ""
"**Anything fed into** ``--id-type orcid`` **must match** "
"``^\\d{4}-\\d{4}-\\d{4}-\\d{3}[\\dX]$``. The trailing character may "
"legitimately be ``X`` (ISO checksum); strip ``https://orcid.org/`` "
"prefixes before passing the bare ID."
msgstr ""
Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

+41
View File
@@ -0,0 +1,41 @@
.. _philosophy:
Philosophy
==========
*The Commissioner Jules Maigret is a fictional French police detective, created by Georges Simenon.
His investigation method is based on understanding the personality of different people and their
interactions.*
TL;DR: Username => Dossier
Maigret is designed to gather all the available information about person by his username.
What kind of information is this? First, links to person accounts. Secondly, all the machine-extractable
pieces of info, such as: other usernames, full name, URLs to people's images, birthday, location (country,
city, etc.), gender.
All this information forms some dossier, but it also useful for other tools and analytical purposes.
Each collected piece of data has a label of a certain format (for example, ``follower_count`` for the number
of subscribers or ``created_at`` for account creation time) so that it can be parsed and analyzed by various
systems and stored in databases.
Origins
-------
Maigret started from studying what OSINT investigators actually use in practice — and from
the realization that many popular tools do not deliver real investigative value. The original
research behind this observation is summarized in the article
`What's wrong with namecheckers <https://soxoj.medium.com/whats-wrong-with-namecheckers-981e5cba600e>`_.
For a broader landscape of username-checking tools, see the curated
`OSINT namecheckers list <https://github.com/soxoj/osint-namecheckers-list>`_.
Two ideas grew out of that research:
- `socid-extractor <https://github.com/soxoj/socid-extractor>`_ — a library focused on pulling
structured identity data (user IDs, full names, linked accounts, bios, timestamps, etc.) out of
account pages and public API responses, so that finding an account is not the end of the pipeline.
- **Maigret** itself — which started as a fork of
`Sherlock <https://github.com/sherlock-project/sherlock>`_ but has long since outgrown the
original project in coverage, extraction depth, and check reliability. Today Maigret is used
as a component by major OSINT vendors in their commercial products.
+15
View File
@@ -0,0 +1,15 @@
.. _quick-start:
Quick start
===========
After :doc:`installing Maigret <installation>`, you can begin searching by providing one or more usernames to look up:
``maigret username1 username2 ...``
Maigret will search for accounts with the specified usernames across a vast number of websites. It will provide you with a list
of URLs to any discovered accounts, along with relevant information extracted from those profiles.
.. image:: maigret_screenshot.png
:alt: Maigret search results screenshot
:align: center
+276
View File
@@ -0,0 +1,276 @@
.. _settings:
Settings
==============
.. warning::
The settings system is under development and may be subject to change.
Options are also configurable through settings files. See
`settings JSON file <https://github.com/soxoj/maigret/blob/main/maigret/resources/settings.json>`_
for the list of currently supported options.
After start Maigret tries to load configuration from the following sources in exactly the same order:
.. code-block:: console
# relative path, based on installed package path
resources/settings.json
# absolute path, configuration file in home directory
~/.maigret/settings.json
# relative path, based on current working directory
settings.json
Missing any of these files is not an error.
If the next settings file contains already known option,
this option will be rewritten. So it is possible to make
custom configuration for different users and directories.
.. _database-auto-update:
Database auto-update
--------------------
Maigret ships with a bundled site database, but it gets outdated between releases. To keep the database current, Maigret automatically checks for updates on startup.
**How it works:**
1. On startup, Maigret checks if more than 24 hours have passed since the last update check.
2. If so, it fetches a lightweight metadata file (~200 bytes) from GitHub to see if a newer database is available.
3. If a newer, compatible database exists, Maigret downloads it to ``~/.maigret/data.json`` and uses it instead of the bundled copy.
4. If the download fails or the new database is incompatible with your Maigret version, the bundled database is used as a fallback.
The downloaded database has **higher priority** than the bundled one — it replaces, not overlays.
**Status messages** are printed only when an action occurs:
.. code-block:: text
[*] DB auto-update: checking for updates...
[+] DB auto-update: database updated successfully (3180 sites)
[*] DB auto-update: database is up to date (3157 sites)
[!] DB auto-update: latest database requires maigret >= 0.6.0, you have 0.5.0
**Forcing an update:**
Use the ``--force-update`` flag to check for updates immediately, ignoring the check interval:
.. code-block:: console
maigret username --force-update
The update happens at startup, then the search continues normally with the freshly downloaded database.
**Disabling auto-update:**
Use the ``--no-autoupdate`` flag to skip the update check entirely:
.. code-block:: console
maigret username --no-autoupdate
Or set it permanently in ``~/.maigret/settings.json``:
.. code-block:: json
{
"no_autoupdate": true
}
This is recommended for **Docker containers**, **CI pipelines**, and **air-gapped environments**.
**Configuration options** (in ``settings.json``):
.. list-table::
:header-rows: 1
:widths: 35 15 50
* - Setting
- Default
- Description
* - ``no_autoupdate``
- ``false``
- Disable auto-update entirely
* - ``autoupdate_check_interval_hours``
- ``24``
- How often to check for updates (in hours)
* - ``db_update_meta_url``
- GitHub raw URL
- URL of the metadata file (for custom mirrors)
**Using a custom database** with ``--db`` always skips auto-update — you are explicitly choosing your data source.
Cloudflare webgate
------------------
.. warning::
**Experimental.** The ``cloudflare_bypass`` block is under active
development; field names, defaults, and the trigger-protection routing
rules may change without backwards-compatibility guarantees.
The ``cloudflare_bypass`` block in ``settings.json`` configures the optional
bypass described in :ref:`cloudflare-bypass`. The same block is honoured by
the CLI, the Python API, and the web UI (``python -m maigret.web.app``); set
``enabled: true`` once and every entry point routes cf-protected sites
through the solver.
**Minimal FlareSolverr setup.** Start the solver in Docker, then drop the
following snippet into ``~/.maigret/settings.json`` (or any path listed in
:ref:`settings`):
.. code-block:: bash
docker run -d -p 8191:8191 --name flaresolverr \
ghcr.io/flaresolverr/flaresolverr:latest
.. code-block:: json
{
"cloudflare_bypass": {
"enabled": true,
"modules": [
{
"name": "flaresolverr",
"method": "json_api",
"url": "http://localhost:8191/v1",
"max_timeout_ms": 60000
}
]
}
}
That is enough — ``session_prefix`` and ``trigger_protection`` fall back to
sensible defaults (``"maigret"`` and
``["cf_js_challenge", "cf_firewall", "webgate"]`` respectively). On the next
run, Maigret logs ``Cloudflare webgate active: ...`` and routes matching
sites through the solver.
Default value (full schema with every supported field):
.. code-block:: json
{
"cloudflare_bypass": {
"enabled": false,
"session_prefix": "maigret",
"trigger_protection": ["cf_js_challenge", "cf_firewall", "webgate"],
"modules": [
{
"name": "flaresolverr",
"method": "json_api",
"url": "http://localhost:8191/v1",
"max_timeout_ms": 60000
},
{
"name": "chrome_webgate",
"method": "url_rewrite",
"url": "http://localhost:8000/html?url={url}&retries=1"
}
]
}
}
**Fields.**
.. list-table::
:header-rows: 1
:widths: 30 70
* - Field
- Description
* - ``enabled``
- When ``true``, the bypass is active for every run; when ``false``
(the default), it activates only on ``--cloudflare-bypass``.
* - ``trigger_protection``
- List of ``site.protection`` values that route a check through the
webgate. Sites whose protection is empty or doesn't intersect this
list use the default (aiohttp / curl_cffi) checker.
* - ``session_prefix``
- Prefix for the FlareSolverr ``session`` field. Maigret appends the
process PID so concurrent runs don't collide. Reusing a session
caches cf_clearance between checks of the same domain.
* - ``modules``
- Ordered list of backend modules. The first reachable module
handles the check; later ones serve as a fallback chain.
**Module methods.**
* ``json_api`` — FlareSolverr-compatible POST endpoint at ``url``.
Preserves real upstream HTTP status, headers and final URL.
Optional ``max_timeout_ms`` (default ``60000``) is the per-request
budget the solver is allowed to spend on the JS challenge.
* ``url_rewrite`` — legacy CloudflareBypassForScraping endpoint. The
``url`` must contain a ``{url}`` placeholder; the original probe URL
is URL-encoded and substituted in. Returns rendered HTML only —
``checkType: status_code`` and ``response_url`` checks misfire under
this method (treated as a synthetic HTTP 200 on success).
**Optional ``proxy`` field (``json_api`` only).**
A module may carry a ``proxy`` entry that the solver routes the upstream
request through. Useful when a site enforces ``ip_reputation`` rules
that block the solver host. Two forms are accepted:
.. code-block:: json
{ "proxy": "socks5://localhost:1080" }
.. code-block:: json
{ "proxy": { "url": "http://gw.example:3128",
"username": "u",
"password": "p" } }
Only ``url``/``username``/``password`` are forwarded; other keys are
dropped. Cloudflare ``Error 1015 / 1020`` responses indicate the IP is
rate-limited or banned — switch the proxy rather than retrying.
.. _ai-analysis-settings:
AI analysis
-----------
The ``--ai`` flag (see :ref:`ai-analysis`) talks to an OpenAI-compatible
chat completion API. Three settings control how that request is made:
.. list-table::
:header-rows: 1
:widths: 35 25 40
* - Setting
- Default
- Description
* - ``openai_api_key``
- ``""`` (empty)
- API key. If empty, Maigret falls back to the ``OPENAI_API_KEY``
environment variable.
* - ``openai_model``
- ``gpt-4o``
- Default model name. Overridable per-run with ``--ai-model``.
* - ``openai_api_base_url``
- ``https://api.openai.com/v1``
- Base URL of the chat completion API. Point this at any
OpenAI-compatible service (Azure OpenAI, OpenRouter, a local
server, …) to use it instead of OpenAI directly.
Example ``~/.maigret/settings.json`` snippet using a non-OpenAI
endpoint:
.. code-block:: json
{
"openai_api_key": "sk-...",
"openai_model": "gpt-4o-mini",
"openai_api_base_url": "https://openrouter.ai/api/v1"
}
The key resolution order is ``settings.openai_api_key````OPENAI_API_KEY``
environment variable; the first non-empty value wins.
.. note::
``--ai`` sends the full internal Markdown report (which contains the
gathered profile data) to the configured endpoint. Only use providers
and accounts you trust with that data.
@@ -0,0 +1,15 @@
.. _supported-identifier-types:
Supported identifier types
==========================
Maigret can search against not only ordinary usernames, but also through certain common identifiers. There is a list of all currently supported identifiers.
- **gaia_id** - Google inner numeric user identifier, in former times was placed in a Google Plus account URL.
- **steam_id** - Steam inner numeric user identifier.
- **wikimapia_uid** - Wikimapia.org inner numeric user identifier.
- **uidme_uguid** - uID.me inner numeric user identifier.
- **yandex_public_id** - Yandex sites inner letter user identifier. See also: `YaSeeker <https://github.com/HowToFind-bot/YaSeeker>`_.
- **vk_id** - VK.com inner numeric user identifier.
- **ok_id** - OK.ru inner numeric user identifier.
- **yelp_userid** - Yelp inner user identifier.
+46
View File
@@ -0,0 +1,46 @@
.. _tags:
Tags
====
The use of tags allows you to select a subset of the sites from big Maigret DB for search.
.. warning::
Tags markup is still not stable.
There are several types of tags:
1. **Country codes**: ``us``, ``jp``, ``br``... (`ISO 3166-1 alpha-2 <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_). A country tag means that having an account on the site implies a connection to that country — either origin or residence. The goal is attribution, not perfect accuracy.
- **Global sites** (GitHub, YouTube, Reddit, Medium, etc.) get **no country tag** — an account there says nothing about where a person is from.
- **Regional/local sites** where an account implies a specific country **must** have a country tag: ``VK````ru``, ``Naver````kr``, ``Zhihu````cn``.
- Multiple country tags are allowed when a service is used predominantly in a few countries (e.g. ``Xing````de``, ``eu``).
- Do **not** assign country tags based on traffic statistics alone — a site popular in India by traffic is not "Indian" if it is used globally.
2. **Site engines**. Most of them are forum engines now: ``uCoz``, ``vBulletin``, ``XenForo`` et al. Full list of engines stored in the Maigret database.
3. **Sites' subject/type and interests of its users**. Full list of "standard" tags is `present in the source code <https://github.com/soxoj/maigret/blob/main/maigret/sites.py#L13>`_ only for a moment.
Usage
-----
``--tags us,jp`` -- search on US and Japanese sites (actually marked as such in the Maigret database)
``--tags coding`` -- search on sites related to software development.
``--tags ucoz`` -- search on uCoz sites only (mostly CIS countries)
Blacklisting (excluding) tags
------------------------------
You can exclude sites with certain tags from the search using ``--exclude-tags``:
``--exclude-tags porn,dating`` -- skip all sites tagged with ``porn`` or ``dating``.
``--exclude-tags ru`` -- skip all Russian sites.
You can combine ``--tags`` and ``--exclude-tags`` to fine-tune your search:
``--tags forum --exclude-tags ru`` -- search on forum sites, but skip Russian ones.
In the web interface, the tag cloud supports three states per tag:
click once to **include** (green), click again to **exclude** (dark/strikethrough),
and click once more to return to **neutral** (red).
+122
View File
@@ -0,0 +1,122 @@
.. _tor-and-proxies:
Tor, I2P, and proxies
=====================
Maigret can route checks through an HTTP/SOCKS proxy, the Tor network, or I2P. Three CLI flags cover three distinct goals — knowing which one you need is the most common stumbling block.
``--proxy`` vs ``--tor-proxy`` (and ``--i2p-proxy``)
----------------------------------------------------
The most-asked question (see `issue #544 <https://github.com/soxoj/maigret/issues/544>`_):
- **You want every check to go through Tor** (e.g. you're on Tails OS, or behind a country-level block, or your IP is rate-limited). → Use ``--proxy``, pointing at your Tor SOCKS port:
.. code-block:: console
maigret <username> --proxy socks5://127.0.0.1:9050
- **You want to reach ``.onion`` sites in the Maigret database**, while the rest of the run still uses your normal connection. → Use ``--tor-proxy``:
.. code-block:: console
maigret <username> --tor-proxy socks5://127.0.0.1:9050
``--tor-proxy`` is **only** consulted for sites whose ``url`` is a ``.onion`` host. For every other site Maigret uses your direct connection (or ``--proxy`` if set). Without ``--tor-proxy``, ``.onion`` sites are silently skipped.
The same split applies to ``--i2p-proxy``: it is consulted only for ``.i2p`` hosts, never for clearweb sites.
Defaults: ``--tor-proxy`` defaults to ``socks5://127.0.0.1:9050`` and ``--i2p-proxy`` to ``http://127.0.0.1:4444``. ``--proxy`` has no default. Maigret does **not** launch ``tor`` or an I2P router for you — start the daemon first.
Tor Browser vs system ``tor``: port numbers
-------------------------------------------
The SOCKS port differs by Tor installation:
- **System ``tor`` daemon** (``apt install tor``, ``brew install tor``, Tails) listens on ``9050``.
- **Tor Browser bundle** ships its own ``tor`` listening on ``9150``.
If a connection refuses, try the other port:
.. code-block:: console
# system tor
maigret <username> --proxy socks5://127.0.0.1:9050
# Tor Browser running in the background
maigret <username> --proxy socks5://127.0.0.1:9150
A note on results over Tor
--------------------------
Most public WAFs (Cloudflare, DDoS-Guard, AWS WAF, Akamai) block Tor exit nodes by default — usually more aggressively than they block datacenter IPs. A Tor run typically produces **more UNKNOWNs and fewer CLAIMEDs** than the same run from a residential connection. This is not a bug in Maigret; it is the cost of anonymity.
Recommended flags for a Tor run:
.. code-block:: console
maigret <username> --proxy socks5://127.0.0.1:9050 --timeout 60 --retries 2
- ``--timeout 60`` — Tor circuits add 13 seconds per request; the default 30 s causes spurious timeouts.
- ``--retries 2`` — retries cover transient circuit failures, which are common on Tor.
- Optional ``-n 20`` — lowering concurrency (default 100) reduces the chance of exits rate-limiting you.
If you mainly need to bypass WAFs (rather than to remain anonymous), a residential proxy will usually outperform Tor by a wide margin. See the **"Lots of sites fail / timeout / return 403"** section in `TROUBLESHOOTING.md <https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_.
Running on Tails OS
-------------------
Tails forces every outbound connection through Tor at the network layer. Maigret needs no special configuration to comply — pointing ``--proxy`` at the Tails Tor daemon is enough:
.. code-block:: console
maigret <username> --proxy socks5://127.0.0.1:9050 --timeout 60
Things that are **not** needed:
- ``torsocks maigret …`` and ``torify maigret …`` — these wrap libc socket calls, but Maigret's HTTP client (``aiohttp`` / ``curl_cffi``) bypasses libc for network I/O, so the wrapper has no effect. Use ``--proxy`` instead.
- ``--tor-proxy`` — on Tails, *everything* must go via Tor (the OS enforces this), so the niche "only .onion via Tor" mode that ``--tor-proxy`` provides does not apply.
Installation over Tor on Tails
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``pip`` itself does not know about Tor; on Tails you need ``torsocks`` to wrap it:
.. code-block:: console
torsocks pip install --user maigret
After install, the binary lands in ``~/.local/bin/maigret``. If ``maigret: command not found``, either add ``~/.local/bin`` to ``PATH`` or invoke it as ``python3 -m maigret <username>``.
Persisting Maigret across Tails sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tails wipes ``~/.local/`` on reboot unless you configure the Persistent Storage to keep it. This is Tails configuration, not Maigret configuration — see the official Tails docs:
- `Persistent Storage on Tails <https://tails.boum.org/doc/persistent_storage/>`_
- `Configuring Persistent Storage features <https://tails.boum.org/doc/persistent_storage/configure/>`_
A step-by-step recipe contributed by a user (persisting ``~/.local/lib/python3.9`` and ``~/.local/bin`` and patching ``.bashrc``) is in `issue #544 <https://github.com/soxoj/maigret/issues/544#issuecomment-1356469171>`__. Treat it as a starting point: the Python version and Tails internals change between Tails releases.
Reports on Tails — where to save them
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default ``reports/`` directory lives next to the working directory and is wiped with the amnesiac session. To save reports somewhere persistent, either pass ``-fo``:
.. code-block:: console
maigret <username> --html -fo "/home/amnesia/Persistent/maigret-reports"
or set ``"reports_path"`` in your ``settings.json`` to a persistent path. See :doc:`settings`.
Programmatic equivalents (Python library)
-----------------------------------------
The same options are available through the Python API. See :doc:`library-usage` — the relevant keyword arguments are ``proxy=``, ``tor_proxy=`` and ``i2p_proxy=``, accepting the same URL formats as the CLI flags.
See also
--------
- :doc:`command-line-options` — full reference for the three flags.
- `TROUBLESHOOTING.md <https://github.com/soxoj/maigret/blob/main/TROUBLESHOOTING.md>`_ — quick recipes for ``.onion`` / I2P sites and for WAF-induced 403s.
- :doc:`library-usage` — proxy options for embedded use.
+80
View File
@@ -0,0 +1,80 @@
.. _usage-examples:
Usage examples
==============
You can use Maigret as:
- a command line tool: initial and a default mode
- a `web interface <#web-interface>`_: view the graph with results and download all report formats on a single page
- a library: integrate Maigret into your own project
Use Cases
---------
1. Search for accounts with username ``machine42`` on top 500 sites (by default, according to Majestic Million rank) from the Maigret DB.
.. code-block:: console
maigret machine42
2. Search for accounts with username ``machine42`` on **all sites** from the Maigret DB.
.. code-block:: console
maigret machine42 -a
.. note::
Maigret will search for accounts on a huge number of sites,
and some of them may return false positive results. At the moment, we are working on autorepair mode to deliver
the most accurate results.
If you experience many false positives, you can do the following:
- Install the last development version of Maigret from GitHub
- Run Maigret with ``--self-check --auto-disable`` flag and agree on disabling of problematic sites
3. Search for accounts with username ``machine42`` and generate HTML and PDF reports.
.. code-block:: console
maigret machine42 -HP
or
.. code-block:: console
maigret machine42 -a --html --pdf
4. Search for accounts with username ``machine42`` on Facebook only.
.. code-block:: console
maigret machine42 --site Facebook
5. Extract information from the Steam page by URL and start a search for accounts with found username ``machine42``.
.. code-block:: console
maigret --parse https://steamcommunity.com/profiles/76561199113454789
6. Search for accounts with username ``machine42`` only on US and Japanese sites.
.. code-block:: console
maigret machine42 --tags us,jp
7. Search for accounts with username ``machine42`` only on sites related to software development.
.. code-block:: console
maigret machine42 --tags coding
8. Search for accounts with username ``machine42`` on uCoz sites only (mostly CIS countries).
.. code-block:: console
maigret machine42 --tags ucoz
+147
View File
@@ -0,0 +1,147 @@
.. _use-case-crypto:
Cryptocurrency & Web3 Investigations
=====================================
Blockchain transactions are public, but the people behind wallets are not. Maigret helps bridge this gap by finding Web3 accounts tied to a username, revealing the person behind a pseudonymous crypto persona.
Why it matters
--------------
Crypto investigations often start with a wallet address or an ENS name but hit a wall — the blockchain tells you *what* happened, not *who* did it. A username, however, is reused across platforms. If someone trades on OpenSea as ``zachxbt`` and posts on Warpcast as ``zachxbt``, Maigret connects the dots and builds a full profile.
Common scenarios:
- **Scam attribution.** A rug-pull promoter uses the same alias on Fragment (Telegram username marketplace), OpenSea, and a personal blog.
- **Sanctions compliance.** Verifying whether a counterparty's online footprint matches known sanctioned individuals.
- **Due diligence.** Before an OTC deal or DAO vote, checking whether the other party has a consistent online presence or is a freshly created sockpuppet.
- **Stolen funds tracing.** A stolen NFT appears on OpenSea under a new account — but the username matches a Warpcast profile with real-world links.
Supported sites
---------------
Maigret currently checks the following crypto and Web3 platforms:
.. list-table::
:header-rows: 1
:widths: 20 40 40
* - Site
- What it reveals
- Notes
* - **OpenSea**
- NFT collections, trading history, profile bio, linked website
-
* - **Rarible**
- NFT marketplace profile, collections, listing history
- Complements OpenSea for NFT attribution across marketplaces
* - **Zora**
- Zora Network profile, minted NFTs, creator activity
- Ethereum L2 creator platform; useful for on-chain art attribution
* - **Polymarket**
- Prediction-market profile, positions, public portfolio P&L
- Useful for political/financial prediction attribution
* - **Warpcast** (Farcaster)
- Decentralized social profile, posts, follower graph, Farcaster ID
- Every Farcaster ID maps to an Ethereum address via the on-chain ID registry
* - **Fragment**
- Telegram username ownership, TON wallet address, purchase date and price
- Valuable for linking Telegram identities to TON wallets
* - **Paragraph**
- Web3 blog/newsletter, ETH wallet address, linked Twitter handle
- Richest cross-platform data among crypto sites
* - **Tonometerbot**
- TON wallet balance, subscriber count, NFT collection, rankings
- TON blockchain analytics
* - **Spatial**
- Metaverse profile, linked social accounts (Discord, Twitter, Instagram, LinkedIn, TikTok)
- Rich cross-platform links
* - **Revolut.me**
- Payment handle: first/last name, country code, base currency, supported payment methods
- Not strictly Web3, but widely used by crypto OTC traders for fiat off-ramps; the public API returns structured KYC-adjacent data
Real-world example: zachxbt
---------------------------
`ZachXBT <https://twitter.com/zachxbt>`_ is a well-known on-chain investigator. Let's see what Maigret can find from just the username ``zachxbt``:
.. code-block:: console
maigret zachxbt --tags crypto
Maigret finds 5 accounts and automatically extracts structured data from each:
**Fragment** — confirms the Telegram username ``@zachxbt`` is claimed, reveals the TON wallet address (``EQBisZrk...``), purchase price (10 TON), and date (January 2023).
**Paragraph** — the richest result. Returns the real name used on the platform (``ZachXBT``), bio (``Scam survivor turned 2D investigator``), an Ethereum wallet address (``0x23dBf066...``), and a linked Twitter handle (``zachxbt``). The ``wallet_address`` field is especially valuable — it directly links the pseudonym to an on-chain identity.
**Warpcast** — Farcaster profile with a Farcaster ID (``fid: 20931``), profile image, and social graph (33K followers). Every Farcaster ID is tied to an Ethereum address via the on-chain ID registry, so this is another on-chain anchor.
**OpenSea** — NFT marketplace profile with bio (``On-chain sleuth | 10x rug pull survivor``), avatar (hosted on ``seadn.io`` with an Ethereum address in the URL path), and a link to an external investigations page.
**Hive Blog** — blockchain-based blog account created in March 2025. Low activity (1 post), but confirms the username is claimed across blockchain ecosystems.
From a single username, Maigret produces:
- **2 wallet addresses** — one TON (from Fragment), one Ethereum (from Paragraph)
- **1 confirmed Twitter handle**``zachxbt`` (from Paragraph)
- **1 Telegram username**``@zachxbt`` (from Fragment)
- **1 external link**``investigations.notion.site`` (from OpenSea)
- **Social graph data** — 33K Farcaster followers, blog activity timestamps
This is enough to pivot into blockchain analysis tools (Etherscan, Arkham, Nansen) using the wallet addresses, or into social media analysis using the Twitter handle.
Workflow: from username to wallet
---------------------------------
**Step 1: Search crypto platforms**
.. code-block:: console
maigret <username> --tags crypto -v
Review the results. Pay attention to:
- **Fragment** — if the username is claimed, you get a TON wallet address directly.
- **Paragraph** — blog profiles often contain an ETH address and a Twitter handle.
- **Warpcast** — Farcaster IDs map to Ethereum addresses via the on-chain registry.
- **OpenSea** — avatar URLs sometimes contain wallet addresses in the path.
**Step 2: Expand with extracted identifiers**
Maigret automatically extracts additional identifiers from found profiles (real names, linked accounts, profile URLs) and recursively searches for them. This is enabled by default. If Maigret finds a linked Twitter handle on a Paragraph profile, it will automatically search for that handle across all sites.
**Step 3: Cross-reference with non-crypto platforms**
The real power is connecting crypto personas to mainstream accounts. Drop the tag filter:
.. code-block:: console
maigret <username> -a
This checks all 3000+ sites. A match on GitHub, Reddit, or a forum can reveal the person behind the wallet.
Workflow: from wallet to identity
---------------------------------
If you start with a wallet address rather than a username, you can use complementary tools to get a username first:
1. **ENS / Unstoppable Domains** — resolve the wallet address to a human-readable name (``vitalik.eth``). Then search that name in Maigret.
2. **Etherscan labels** — check if the address has a public label (exchange, known entity).
3. **Fragment** — search the TON wallet address to find which Telegram usernames it purchased.
4. **Arkham Intelligence / Nansen** — blockchain attribution platforms that may tag the address with a known identity.
Once you have a username candidate, feed it to Maigret.
Tips
----
- **Username reuse is the #1 signal.** Crypto-native users often reuse their ENS name (``alice.eth``) or a variation (``alice_eth``, ``aliceeth``) across platforms. Try all variations.
- **Fragment is uniquely valuable** because it directly links Telegram usernames to TON wallet addresses — a rare on-chain / off-chain bridge.
- **Warpcast profiles are Ethereum-native.** Every Farcaster account is tied to an Ethereum address via the ID registry contract. If you find a Warpcast profile, you implicitly have a wallet address.
- **Paragraph often has the richest data** — wallet address, Twitter handle, bio, and activity timestamps in a single API response.
- **Use** ``--exclude-tags`` **to skip irrelevant sites** when you're focused on crypto:
.. code-block:: console
maigret alice_eth --exclude-tags porn,dating,forum
+97
View File
@@ -0,0 +1,97 @@
.. _use-case-scientists:
Academic & Research Investigations
==================================
Academic identity has a unique anchor that social-media identity does not: the **ORCID** (Open Researcher and Contributor ID). Every ORCID is verified, globally unique, and tied to a confirmed email — a single confirmed ORCID match between two platforms means the **same human**, with effectively zero false-positive rate.
Maigret uses ORCID as a first-class identifier: extract it once from a username-keyed profile (iNaturalist, GitHub bio, lab homepage), then pivot into the academic graph (ORCID, OpenAlex, arXiv, DBLP, Scholia) — a chain of HTTP calls that turns an anonymous handle into a CV with employer, education, publication list, citation count, co-author graph, and topical area.
Why it matters
--------------
Most OSINT work on academic targets stalls at the same bottleneck: people use one alias on a citizen-science site, a different alias on Twitter, a third in a forum signature, and only their real name on PubMed. Connecting the personas by hand means trawling Google Scholar, ResearchGate, university web pages, and old conference programmes. ORCID short-circuits this entirely — one verified identifier resolves all of them.
Common scenarios:
- **Researcher background check.** Before a collaboration, a preprint review, or a grant decision — verifying that a claimed h-index, employment history, and publication count actually match the public ORCID record.
- **Co-author graph reconstruction.** From a single ORCID, OpenAlex returns every co-author, their institutions, and topical clusters — useful for spotting undisclosed conflicts of interest.
- **Paper mill / fraud investigation.** When a suspicious paper's authors share an ORCID-claimed identity, the cross-platform footprint (or absence of one) is itself evidence.
- **Pseudonym deanonymisation.** A wildlife photographer posts naturalist observations under the handle ``kueda``. iNaturalist's public API returns their ORCID, and one further request reveals their real name, employer (California Academy of Sciences), education (UC Berkeley), and 700+ citable observations.
- **Award / appointment due diligence.** Confirming a Turing-Award claim, a tenure status, or a society fellowship via the DBLP and ORCID activity timelines rather than a CV PDF.
Supported sites
---------------
Maigret currently checks the following ORCID-keyed platforms (use ``--id-type orcid`` when starting from a bare ORCID, or rely on the recursive chain when starting from a username):
.. list-table::
:header-rows: 1
:widths: 18 42 40
* - Site
- What it reveals
- Notes
* - **ORCID**
- Full name, biography, employment, education, researcher URLs (homepages and social), keywords, country, linked external IDs (Scopus, ResearcherID, Loop), publication summary, email-verified status
- All disciplines — ORCID is a field-agnostic identifier issued to any researcher.
* - **OpenAlex**
- Display name and name alternatives, works count, total citations, h-index, i10-index, last-known institutions (with country), topical areas, raw author-name variants from publications
- All disciplines — indexes works across the entire scholarly literature, from humanities to medicine.
* - **arXiv**
- Author preprint listing — paper titles, IDs, dates
- Strongly biased toward physics, math, CS (and quantitative biology, statistics, EE, quantitative finance, economics).
* - **DBLP**
- Full name, DBLP person ID, paper count, affiliation, awards (e.g. Turing Award), homepage URLs, links to Google Scholar / ResearchGate / Scopus
- Computer science only — a biologist or pure economist will not be in the index.
* - **Scholia**
- Wikidata QID for the author, linked publications, co-author graph, employer/affiliation timeline, topical clusters
- All disciplines, but only if the author has been curated in Wikidata — coverage is broadest for prominent figures (award winners, senior faculty, deceased researchers).
Workflow: from username to ORCID
--------------------------------
**Step 1: Find a profile that publishes ORCID**
The most reliable bridges from a username to an ORCID are platforms whose users *want* their academic identity discoverable. In practice:
- **iNaturalist** — biologists, naturalists, citizen scientists. Public API ``api.inaturalist.org/v1/users/{username}`` returns the ORCID directly in the ``orcid`` field. Maigret extracts it automatically.
- **GitHub** — scientists often paste their ORCID URL into the **bio** or **blog** field on their GitHub profile, or under a *generic* entry in ``/users/{u}/social_accounts``. A pattern like ``orcid\.org/(\d{4}-\d{4}-\d{4}-\d{3}[\dX])`` matches both ``http://orcid.org/...`` and ``orcid.org/...`` variants.
- **Lab homepage / institutional bio.** Use ``--parse <URL>`` to scrape an arbitrary page for known identifiers — useful when the target's footprint lives at ``faculty.example.edu/~jdoe``.
**Step 2: Let the recursive search run**
.. code-block:: console
maigret <username> --tags science -v
Recursive search is enabled by default. As soon as Maigret extracts an ``orcid`` field from any found profile, it queues an ``--id-type orcid`` second wave automatically. No manual chaining needed.
**Step 3: Start from a bare ORCID**
If you already have the ORCID (e.g. from a paper's author footer, a grant database, or a Wikidata entry):
.. code-block:: console
maigret 0000-0002-9322-3515 --id-type orcid -a
This bypasses the username-bridge step entirely.
Workflow: from ORCID to mainstream identity
-------------------------------------------
The ORCID record itself contains the link back to ordinary social platforms:
1. **ORCID ``researcher-urls``** — a list of self-declared external links. Typical entries: lab homepage, Twitter/Bluesky/Mastodon profile, personal blog. These are entered by the researcher and therefore confirmed.
2. **ORCID ``external-identifiers``** — IDs from sibling academic systems (Scopus Author ID, ResearcherID/Publons, Loop profile). Each unlocks another extraction pipeline.
3. **OpenAlex ``last_known_institutions``** — current employer with country, ROR ID, and OpenAlex institution ID; useful for pivoting into the institution's directory.
4. **DBLP** ``<url>`` **tags** — DBLP records often embed direct links to Google Scholar, ResearchGate, and personal pages.
5. **Scholia ``wikidata_qid``** — once you have a Wikidata QID, the SPARQL endpoint unlocks the broadest external ID set in any single OSINT system: VIAF, LCCN, GND, Twitter handle, Mastodon handle, IMDb, GitHub, ORCID, and ~200 others.
Tips
----
- **Email comes from ORCID** — within the academic chain (ORCID, OpenAlex, arXiv, DBLP, Scholia) it is the only source that returns an email address, and only when the researcher has marked their primary email public. The same response also carries ``history.verified-primary-email``, an ORCID-confirmed flag that means the address was email-validated at registration. Use this as a strong pivot into email-keyed lookups (Holehe, HIBP, Gravatar, etc.).
- **Outside the academic chain, GitLab is the dev platform most likely to leak an email** via its public ``public_email`` field — relevant for scientists who self-host code on a CERN/research-institute GitLab. GitHub's REST API does not expose email by default.
- **Compare OpenAlex** ``raw_author_names`` **against the ORCID** ``other-names``. Discrepancies often expose pre-marriage names, transliterations from non-Latin scripts, or co-author misattributions worth flagging.
- **Anything fed into** ``--id-type orcid`` **must match** ``^\d{4}-\d{4}-\d{4}-\d{3}[\dX]$``. The trailing character may legitimately be ``X`` (ISO checksum); strip ``https://orcid.org/`` prefixes before passing the bare ID.
+24
View File
@@ -0,0 +1,24 @@
"""Maigret"""
__title__ = 'Maigret'
__package__ = 'maigret'
__author__ = 'Soxoj'
__author_email__ = 'soxoj@protonmail.com'
from .__version__ import __version__
try:
from .checking import maigret as search
except ImportError as e:
raise ImportError(
"Missing required dependency while starting Maigret.\n\n"
"If installed from PyPI:\n"
" pip install -U maigret\n\n"
"If running from a cloned repository:\n"
" pip install -e .\n\n"
"Then run Maigret as:\n"
" python -m maigret <username>"
) from e
from .maigret import main as cli
from .sites import MaigretEngine, MaigretSite, MaigretDatabase
from .notify import QueryNotifyPrint as Notifier
+22
View File
@@ -0,0 +1,22 @@
#! /usr/bin/env python3
"""
Maigret entrypoint
"""
import asyncio
import sys
from .maigret import main
if __name__ == "__main__":
# First Ctrl+C is caught inside main() (search loop → falls through to
# report generation with partial results). A *second* Ctrl+C during
# report generation, or any uncaught Ctrl+C before the search loop runs,
# reaches asyncio.run as KeyboardInterrupt — exit with the conventional
# SIGINT code (130) instead of dumping a traceback.
try:
asyncio.run(main())
except KeyboardInterrupt:
print('\nMaigret interrupted.', file=sys.stderr)
sys.exit(130)
+3
View File
@@ -0,0 +1,3 @@
"""Maigret version file"""
__version__ = '0.6.2'
+172
View File
@@ -0,0 +1,172 @@
import json
import re
from http.cookiejar import MozillaCookieJar
from http.cookies import Morsel
from aiohttp import ClientSession, CookieJar
class ParsingActivator:
@staticmethod
async def twitter(site, logger, cookies={}, **kwargs):
headers = dict(site.headers)
headers.pop("x-guest-token", None)
async with ClientSession(trust_env=True) as session:
async with session.post(
site.activation["url"],
headers=headers,
timeout=kwargs.get("timeout"),
) as response:
logger.info(response)
j = await response.json(content_type=None)
guest_token = j[site.activation["src"]]
site.headers[site.activation.get("dst", "x-guest-token")] = guest_token
@staticmethod
async def vimeo(site, logger, cookies={}, **kwargs):
headers = dict(site.headers)
headers.pop("Authorization", None)
async with ClientSession(trust_env=True) as session:
async with session.get(
site.activation["url"],
headers=headers,
timeout=kwargs.get("timeout"),
) as response:
payload = await response.json(content_type=None)
logger.debug(f"Vimeo viewer activation: {json.dumps(payload, indent=4)}")
jwt_token = payload["jwt"]
site.headers["Authorization"] = "jwt " + jwt_token
@staticmethod
async def wikimapia(site, logger, html="", **kwargs):
# Wikimapia gates content behind a per-IP JS cookie challenge: the first
# response is a stub that sets `ngxsession=<token>` via document.cookie and
# refreshes. The token is deterministic per source IP, so we read it straight
# from the challenge body the checker already fetched and merge it into the
# request cookie before the retry (re-fetching would race a fresh challenge).
match = re.search(r'ngxsession=([0-9a-f]+)', html or "")
if not match:
logger.warning(
f"Wikimapia activation: ngxsession token not found for {site.name}"
)
return
token = match.group(1)
existing = site.headers.get("Cookie", "")
parts = [
p.strip()
for p in existing.split(";")
if p.strip() and not p.strip().startswith("ngxsession=")
]
parts.append(f"ngxsession={token}")
site.headers["Cookie"] = "; ".join(parts)
@staticmethod
async def onlyfans(site, logger, url=None, **kwargs):
# Signing rules (static_param / checksum_indexes / checksum_constant / format / app_token)
# live in data.json under OnlyFans.activation and rotate upstream every ~13 weeks.
# If "Please refresh the page" keeps firing after activation, refresh them from:
# https://raw.githubusercontent.com/DATAHOARDERS/dynamic-rules/main/onlyfans.json
import hashlib
import secrets
import time as _time
from urllib.parse import urlparse
act = site.activation
static_param = act["static_param"]
indexes = act["checksum_indexes"]
constant = act["checksum_constant"]
fmt = act["format"]
init_url = act["url"]
user_id = site.headers.get("user-id", "0") or "0"
def _sign(path):
t = str(int(_time.time() * 1000))
msg = "\n".join([static_param, t, path, user_id]).encode()
sha = hashlib.sha1(msg).hexdigest()
cs = sum(ord(sha[i]) for i in indexes) + constant
return t, fmt.format(sha, abs(cs))
if site.headers.get("x-bc", "").strip("0") == "":
site.headers["x-bc"] = secrets.token_hex(20)
if not site.headers.get("cookie"):
init_path = urlparse(init_url).path
t, sg = _sign(init_path)
hdrs = dict(site.headers)
hdrs["time"] = t
hdrs["sign"] = sg
hdrs.pop("cookie", None)
async with ClientSession(trust_env=True) as session:
async with session.get(
init_url,
headers=hdrs,
timeout=kwargs.get("timeout", 15),
) as response:
jar = "; ".join(
f"{k}={getattr(v, 'value', v)}"
for k, v in response.cookies.items()
)
if jar:
site.headers["cookie"] = jar
logger.debug(
f"OnlyFans init: got cookies {list(response.cookies.keys())}"
)
target_path = urlparse(url).path if url else urlparse(init_url).path
t, sg = _sign(target_path)
site.headers["time"] = t
site.headers["sign"] = sg
logger.debug(f"OnlyFans signed {target_path} time={t}")
@staticmethod
async def weibo(site, logger, **kwargs):
# Weibo gates its ajax profile API behind an anonymous "Sina Visitor
# System" cookie. genvisitor2 mints a fresh visitor SUB/SUBP pair and
# returns it in the JSONP body. The previous version stored the
# passport-domain Set-Cookie header (SVB) instead of that SUB/SUBP, so
# the cookie never unlocked weibo.com and every check 403'd.
headers = dict(site.headers)
headers.pop("Cookie", None)
timeout = kwargs.get("timeout")
async with ClientSession(trust_env=True) as session:
async with session.post(
site.activation["url"],
headers=headers,
data={'cb': 'visitor_gray_callback', 'tid': '', 'from': 'weibo'},
timeout=timeout,
) as response:
body = await response.text()
match = re.search(r"\{.*\}", body)
data = json.loads(match.group(0)).get("data", {}) if match else {}
sub, subp = data.get("sub"), data.get("subp")
if sub and subp:
site.headers["Cookie"] = f"SUB={sub}; SUBP={subp}"
logger.debug("Weibo activation: visitor SUB/SUBP acquired")
else:
logger.warning(f"Weibo activation failed: no SUB/SUBP in {body[:120]!r}")
def import_aiohttp_cookies(cookiestxt_filename):
cookies_obj = MozillaCookieJar(cookiestxt_filename)
cookies_obj.load(ignore_discard=True, ignore_expires=True)
cookies = CookieJar()
cookies_list = []
for domain in cookies_obj._cookies.values(): # type: ignore[attr-defined]
for key, cookie in list(domain.values())[0].items():
c: Morsel = Morsel()
c.set(key, cookie.value, cookie.value)
c["domain"] = cookie.domain
c["path"] = cookie.path
cookies_list.append((key, c))
cookies.update_cookies(cookies_list)
return cookies
+162
View File
@@ -0,0 +1,162 @@
"""Maigret AI Analysis Module
Provides AI-powered analysis of search results using OpenAI-compatible APIs.
"""
import asyncio
import json
import os
import sys
import threading
import aiohttp
def load_ai_prompt() -> str:
"""Load the AI system prompt from the resources directory."""
maigret_path = os.path.dirname(os.path.realpath(__file__))
prompt_path = os.path.join(maigret_path, "resources", "ai_prompt.txt")
with open(prompt_path, "r", encoding="utf-8") as f:
return f.read()
def resolve_api_key(settings) -> str | None:
"""Resolve OpenAI API key from settings or environment variable.
Priority: settings.openai_api_key > OPENAI_API_KEY env var.
"""
key = getattr(settings, "openai_api_key", None)
if key:
return key
return os.environ.get("OPENAI_API_KEY")
class _Spinner:
"""Simple animated spinner for terminal output."""
FRAMES = ["", "", "", "", "", "", "", "", "", ""]
def __init__(self, text=""):
self.text = text
self._stop = threading.Event()
self._thread = None
def start(self):
self._thread = threading.Thread(target=self._spin, daemon=True)
self._thread.start()
def _spin(self):
i = 0
while not self._stop.is_set():
frame = self.FRAMES[i % len(self.FRAMES)]
sys.stderr.write(f"\r{frame} {self.text}")
sys.stderr.flush()
i += 1
self._stop.wait(0.08)
def stop(self):
self._stop.set()
if self._thread:
self._thread.join()
sys.stderr.write("\r\033[2K")
sys.stderr.flush()
async def print_streaming(text: str, delay: float = 0.04):
"""Print text word by word with a delay, simulating streaming LLM output."""
words = text.split(" ")
for i, word in enumerate(words):
if i > 0:
sys.stdout.write(" ")
sys.stdout.write(word)
sys.stdout.flush()
await asyncio.sleep(delay)
sys.stdout.write("\n")
sys.stdout.flush()
async def _check_response(resp):
"""Raise descriptive errors for non-success HTTP responses."""
if resp.status == 401:
raise RuntimeError("Invalid OpenAI API key (HTTP 401)")
if resp.status == 429:
raise RuntimeError("OpenAI API rate limit exceeded (HTTP 429)")
if resp.status != 200:
body = await resp.text()
raise RuntimeError(f"OpenAI API error (HTTP {resp.status}): {body[:500]}")
async def _stream_response(resp, spinner, first_token):
"""Stream tokens from resp, display them, and return (first_token, full_analysis)."""
full_response = []
async for line in resp.content:
decoded = line.decode("utf-8").strip()
if not decoded or not decoded.startswith("data: "):
continue
data_str = decoded[len("data: "):]
if data_str == "[DONE]":
break
try:
chunk = json.loads(data_str)
except json.JSONDecodeError:
continue
delta = chunk.get("choices", [{}])[0].get("delta", {})
content = delta.get("content", "")
if not content:
continue
if first_token:
spinner.stop()
print()
first_token = False
sys.stdout.write(content)
sys.stdout.flush()
full_response.append(content)
return first_token, "".join(full_response)
async def get_ai_analysis(
api_key: str,
markdown_report: str,
model: str = "gpt-4o",
api_base_url: str = "https://api.openai.com/v1",
) -> str:
"""Send the markdown report to an OpenAI-compatible API and return the analysis.
Uses streaming to display tokens as they arrive.
Raises on HTTP errors with descriptive messages.
"""
system_prompt = load_ai_prompt()
url = f"{api_base_url.rstrip('/')}/chat/completions"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"model": model,
"stream": True,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": markdown_report},
],
}
spinner = _Spinner("Analysing the data with AI...")
spinner.start()
first_token = True
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload, headers=headers) as resp:
await _check_response(resp)
first_token, analysis = await _stream_response(resp, spinner, first_token)
except Exception:
spinner.stop()
raise
if first_token:
# No tokens received — stop spinner anyway
spinner.stop()
print()
return analysis
+1600
View File
File diff suppressed because it is too large Load Diff
+335
View File
@@ -0,0 +1,335 @@
"""
Database auto-update logic for maigret.
Checks a lightweight meta file to determine if a newer site database is available,
downloads it if compatible, and caches it locally in ~/.maigret/.
"""
import hashlib
import json
import logging
import os
import os.path as path
import tempfile
from datetime import datetime, timezone
from typing import Optional
import requests
from colorama import Fore, Style
from .__version__ import __version__
logger = logging.getLogger("maigret")
_use_color = True
def _print(prefix: str, color: str, msg: str) -> None:
text = f"[{prefix}] {msg}"
print(Style.BRIGHT + color + text + Style.RESET_ALL if _use_color else text)
def _print_info(msg: str) -> None:
_print("*", Fore.GREEN, msg)
def _print_success(msg: str) -> None:
_print("+", Fore.GREEN, msg)
def _print_warning(msg: str) -> None:
_print("!", Fore.YELLOW, msg)
DEFAULT_META_URL = (
"https://raw.githubusercontent.com/soxoj/maigret/main/maigret/resources/db_meta.json"
)
DEFAULT_CHECK_INTERVAL_HOURS = 24
MAIGRET_HOME = path.expanduser("~/.maigret")
CACHED_DB_PATH = path.join(MAIGRET_HOME, "data.json")
STATE_PATH = path.join(MAIGRET_HOME, "autoupdate_state.json")
BUNDLED_DB_PATH = path.join(path.dirname(path.realpath(__file__)), "resources", "data.json")
def _parse_version(version_str: str) -> tuple:
"""Parse a version string like '0.5.0' into a comparable tuple (0, 5, 0)."""
try:
return tuple(int(x) for x in version_str.strip().split("."))
except (ValueError, AttributeError):
return (0, 0, 0)
def _ensure_maigret_home() -> None:
os.makedirs(MAIGRET_HOME, exist_ok=True)
def _load_state() -> dict:
try:
with open(STATE_PATH, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError, OSError):
return {}
def _save_state(state: dict) -> None:
_ensure_maigret_home()
tmp_path = STATE_PATH + ".tmp"
try:
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(state, f, indent=2, ensure_ascii=False)
os.replace(tmp_path, STATE_PATH)
except OSError:
try:
os.unlink(tmp_path)
except OSError:
pass
def _needs_check(state: dict, interval_hours: int) -> bool:
last_check = state.get("last_check_at")
if not last_check:
return True
try:
last_dt = datetime.fromisoformat(last_check.replace("Z", "+00:00"))
elapsed = (datetime.now(timezone.utc) - last_dt).total_seconds() / 3600
return elapsed >= interval_hours
except (ValueError, TypeError):
return True
def _fetch_meta(meta_url: str, timeout: int = 10) -> Optional[dict]:
try:
response = requests.get(meta_url, timeout=timeout)
if response.status_code == 200:
return response.json()
except Exception:
pass
return None
def _is_version_compatible(meta: dict) -> bool:
min_ver = meta.get("min_maigret_version", "0.0.0")
return _parse_version(__version__) >= _parse_version(min_ver)
def _is_update_available(meta: dict, state: dict) -> bool:
if not path.isfile(CACHED_DB_PATH):
return True
remote_date = meta.get("updated_at", "")
cached_date = state.get("last_meta", {}).get("updated_at", "")
return remote_date > cached_date
def _download_and_verify(data_url: str, expected_sha256: str, timeout: int = 60) -> Optional[str]:
_ensure_maigret_home()
tmp_fd, tmp_path = tempfile.mkstemp(dir=MAIGRET_HOME, suffix=".json")
try:
response = requests.get(data_url, timeout=timeout)
if response.status_code != 200:
return None
content = response.content
actual_sha256 = hashlib.sha256(content).hexdigest()
if actual_sha256 != expected_sha256:
_print_warning("DB auto-update: SHA-256 mismatch, download rejected")
return None
# Validate JSON structure
data = json.loads(content)
if not all(k in data for k in ("sites", "engines", "tags")):
_print_warning("DB auto-update: invalid database structure")
return None
os.write(tmp_fd, content)
os.close(tmp_fd)
tmp_fd = None
os.replace(tmp_path, CACHED_DB_PATH)
return CACHED_DB_PATH
except Exception:
return None
finally:
if tmp_fd is not None:
os.close(tmp_fd)
try:
os.unlink(tmp_path)
except OSError:
pass
def _best_local() -> str:
"""Return cached DB if it exists and is valid, otherwise bundled."""
if path.isfile(CACHED_DB_PATH):
try:
with open(CACHED_DB_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
if "sites" in data:
return CACHED_DB_PATH
except (json.JSONDecodeError, OSError):
pass
return BUNDLED_DB_PATH
def _now_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def resolve_db_path(
db_file_arg: str,
no_autoupdate: bool = False,
meta_url: str = DEFAULT_META_URL,
check_interval_hours: int = DEFAULT_CHECK_INTERVAL_HOURS,
color: bool = True,
) -> str:
"""
Determine which database file to use, potentially downloading an update.
Returns the path to the database file that should be loaded.
"""
global _use_color
_use_color = color
default_db_name = "resources/data.json"
# User specified a custom DB — skip auto-update
is_url = db_file_arg.startswith("http://") or db_file_arg.startswith("https://")
is_default = db_file_arg == default_db_name
if is_url:
return db_file_arg
if not is_default:
# Try the path as-is (absolute or relative to cwd) first.
if path.isfile(db_file_arg):
return path.abspath(db_file_arg)
# Fall back to legacy behavior: resolve relative to the maigret module dir.
module_relative = path.join(path.dirname(path.realpath(__file__)), db_file_arg)
if module_relative != db_file_arg and path.isfile(module_relative):
return module_relative
if module_relative != db_file_arg:
raise FileNotFoundError(
f"Custom database file not found: {db_file_arg!r} "
f"(also tried {module_relative!r})"
)
raise FileNotFoundError(f"Custom database file not found: {db_file_arg!r}")
# Auto-update disabled
if no_autoupdate:
return _best_local()
# Check interval
_ensure_maigret_home()
state = _load_state()
if not _needs_check(state, check_interval_hours):
return _best_local()
# Time to check
_print_info("DB auto-update: checking for updates...")
meta = _fetch_meta(meta_url)
if meta is None:
_print_warning("DB auto-update: could not reach update server, using local database")
state["last_check_at"] = _now_iso()
_save_state(state)
return _best_local()
# Version compatibility
if not _is_version_compatible(meta):
min_ver = meta.get("min_maigret_version", "?")
_print_warning(
f"DB auto-update: latest database requires maigret >= {min_ver}, "
f"you have {__version__}. Please upgrade with: pip install -U maigret"
)
state["last_check_at"] = _now_iso()
_save_state(state)
return _best_local()
# Check if update available
if not _is_update_available(meta, state):
sites_count = meta.get("sites_count", "?")
_print_info(f"DB auto-update: database is up to date ({sites_count} sites)")
state["last_check_at"] = _now_iso()
state["last_meta"] = meta
_save_state(state)
return _best_local()
# Download update
new_count = meta.get("sites_count", "?")
old_count = state.get("last_meta", {}).get("sites_count")
if old_count:
_print_info(f"DB auto-update: downloading updated database ({new_count} sites, was {old_count})...")
else:
_print_info(f"DB auto-update: downloading database ({new_count} sites)...")
data_url = meta.get("data_url", "")
expected_sha = meta.get("data_sha256", "")
result = _download_and_verify(data_url, expected_sha)
if result is None:
_print_warning("DB auto-update: download failed, using local database")
state["last_check_at"] = _now_iso()
_save_state(state)
return _best_local()
_print_success(f"DB auto-update: database updated successfully ({new_count} sites)")
state["last_check_at"] = _now_iso()
state["last_meta"] = meta
state["cached_db_sha256"] = expected_sha
_save_state(state)
return CACHED_DB_PATH
def force_update(
meta_url: str = DEFAULT_META_URL,
color: bool = True,
) -> bool:
"""
Force check for database updates and download if available.
Returns True if database was updated, False otherwise.
"""
global _use_color
_use_color = color
_ensure_maigret_home()
_print_info("DB update: checking for updates...")
meta = _fetch_meta(meta_url)
if meta is None:
_print_warning("DB update: could not reach update server")
return False
if not _is_version_compatible(meta):
min_ver = meta.get("min_maigret_version", "?")
_print_warning(
f"DB update: latest database requires maigret >= {min_ver}, "
f"you have {__version__}. Please upgrade with: pip install -U maigret"
)
return False
state = _load_state()
new_count = meta.get("sites_count", "?")
old_count = state.get("last_meta", {}).get("sites_count")
if not _is_update_available(meta, state):
_print_info(f"DB update: database is already up to date ({new_count} sites)")
state["last_check_at"] = _now_iso()
state["last_meta"] = meta
_save_state(state)
return False
if old_count:
_print_info(f"DB update: downloading updated database ({new_count} sites, was {old_count})...")
else:
_print_info(f"DB update: downloading database ({new_count} sites)...")
data_url = meta.get("data_url", "")
expected_sha = meta.get("data_sha256", "")
result = _download_and_verify(data_url, expected_sha)
if result is None:
_print_warning("DB update: download failed")
return False
_print_success(f"DB update: database updated successfully ({new_count} sites)")
state["last_check_at"] = _now_iso()
state["last_meta"] = meta
state["cached_db_sha256"] = expected_sha
_save_state(state)
return True
+36
View File
@@ -0,0 +1,36 @@
from typing import Dict, Optional
from maigret import errors
from maigret.errors import CheckError
def detect_error_page(
html_text: str,
status_code: int,
fail_flags: Optional[Dict[str, str]] = None,
ignore_403: bool = False,
) -> Optional[CheckError]:
"""Classify a response as an error condition.
Three signals, checked in order:
1. Site-specific failure markers (``fail_flags`` substring match).
2. Generic provider / bot-protection wording (``errors.detect``).
3. HTTP status — 403 (unless suppressed), >=500 server. HTTP 999
(LinkedIn anti-bot) is a valid "not-found", not an error.
"""
if fail_flags and html_text:
for flag, msg in fail_flags.items():
if flag in html_text:
return CheckError("Site-specific", msg)
err = errors.detect(html_text)
if err:
return err
if status_code == 403 and not ignore_403:
return CheckError("Access denied", f"403 status code, {errors.PROXY_RECOMMENDATION}")
if status_code == 999:
return None
if status_code >= 500:
return CheckError("Server", f"{status_code} status code")
return None
+237
View File
@@ -0,0 +1,237 @@
from typing import Dict, List, Any, Tuple
from .result import MaigretCheckResult, SiteResult
# error got as a result of completed search query
class CheckError:
_type = 'Unknown'
_desc = ''
def __init__(self, typename, desc=''):
self._type = typename
self._desc = desc
def __str__(self):
if not self._desc:
return f'{self._type} error'
return f'{self._type} error: {self._desc}'
@property
def type(self):
return self._type
@property
def desc(self):
return self._desc
COMMON_ERRORS = {
'<title>Attention Required! | Cloudflare</title>': CheckError(
'Captcha', 'Cloudflare'
),
# Prefix (no closing tag) so it matches both '<title>Just a moment</title>'
# and Cloudflare's real title '<title>Just a moment...</title>' (with the
# trailing ellipsis). The exact-match form missed the ellipsis variant, so
# lightweight CF pages without the /cdn-cgi/challenge-platform script went
# undetected and produced false CLAIMED on message-check sites (e.g. RealMeye).
'<title>Just a moment': CheckError(
'Bot protection', 'Cloudflare challenge page'
),
'Please stand by, while we are checking your browser': CheckError(
'Bot protection', 'Cloudflare'
),
'<span data-translate="checking_browser">Checking your browser before accessing</span>': CheckError(
'Bot protection', 'Cloudflare'
),
'This website is using a security service to protect itself from online attacks.': CheckError(
'Access denied', 'Cloudflare'
),
'<title>Доступ ограничен</title>': CheckError('Censorship', 'Rostelecom'),
'document.getElementById(\'validate_form_submit\').disabled=true': CheckError(
'Captcha', 'Mail.ru'
),
'Verifying your browser, please wait...<br>DDoS Protection by</font> Blazingfast.io': CheckError(
'Bot protection', 'Blazingfast'
),
'404</h1><p class="error-card__description">Мы&nbsp;не&nbsp;нашли страницу': CheckError(
'Resolving', 'MegaFon 404 page'
),
'Доступ к информационному ресурсу ограничен на основании Федерального закона': CheckError(
'Censorship', 'MGTS'
),
'Incapsula incident ID': CheckError('Bot protection', 'Incapsula'),
'<title>Client Challenge</title>': CheckError('Bot protection', 'Anti-bot challenge'),
'<title>DDoS-Guard</title>': CheckError('Bot protection', 'DDoS-Guard'),
'Сайт заблокирован хостинг-провайдером': CheckError(
'Site-specific', 'Site is disabled (Beget)'
),
'Generated by cloudfront (CloudFront)': CheckError('Request blocked', 'Cloudflare'),
'/cdn-cgi/challenge-platform/h/b/orchestrate/chl_page': CheckError(
'Just a moment: bot redirect challenge', 'Cloudflare'
),
}
PROXY_RECOMMENDATION = (
"it's recommended to use --cloudflare-bypass or proxy, "
"e.g. https://vaultproxies.net/maigret"
)
ERRORS_TYPES = {
'Captcha': 'Try to switch to another IP address or to use service cookies',
'Bot protection': 'Try to switch to another IP address',
'Access denied': PROXY_RECOMMENDATION,
'Censorship': 'Switch to another internet service provider',
'Request timeout': 'Try to increase timeout or to switch to another internet service provider',
'Connecting failure': 'Check your internet connection; if only a subset of sites fails, try `-n 10` to lower parallelism',
'Connecting failure (DNS)': (
'DNS resolution failed for most sites — Maigret\'s async DNS resolver (aiodns) could not contact a server. '
'First, try `--dns-resolver threaded` to fall back to the system DNS resolver (often fixes this on Windows / VPN / corporate networks). '
'If that does not help, check your internet connection, VPN, or firewall, and consider a public resolver (1.1.1.1 or 8.8.8.8)'
),
'Webgate unavailable': (
'cloudflare_bypass is enabled but every configured solver is unreachable. '
'Verify the URLs under `cloudflare_bypass.modules` in settings.json, and start at least one solver — '
'most commonly FlareSolverr (`docker run -d -p 8191:8191 ghcr.io/flaresolverr/flaresolverr:latest`). '
'Or set `cloudflare_bypass.enabled` to false in settings.json (and drop `--cloudflare-bypass`) to skip CF-protected sites'
),
}
ERRORS_REASONS = {
'Login required': 'Add authorization cookies through `--cookies-jar-file` (see cookies.txt)',
}
TEMPORARY_ERRORS_TYPES = [
'Request timeout',
'Unknown',
'Request failed',
'Connecting failure',
'Connecting failure (DNS)',
'Webgate unavailable',
'HTTP',
'Proxy',
'Interrupted',
'Connection lost',
]
THRESHOLD = 3 # percent — default threshold above which an error type is "important"
# Per-error-type threshold overrides. The default 3% catches systemic issues
# (Captcha, Bot protection) quickly, but for some classes a low percentage is
# expected noise that does NOT mean the user has a fixable problem:
#
# - "Connecting failure (DNS)": a few sites in the database have stale or
# dead DNS records (sites that shut down). Firing the alarm at 3% means
# 3 dead domains in a 100-site batch produce a Windows/VPN troubleshooting
# suggestion that is wrong for nearly every user. Wait for ≥10% before
# nagging — at that rate it's clearly the user's resolver, not data rot.
ERROR_THRESHOLDS: Dict[str, float] = {
'Connecting failure (DNS)': 10,
}
def threshold_for(err_type: str) -> float:
return ERROR_THRESHOLDS.get(err_type, THRESHOLD)
def is_important(err_data):
return err_data['perc'] >= threshold_for(err_data['err'])
def is_permanent(err_type):
return err_type not in TEMPORARY_ERRORS_TYPES
def detect(text):
for flag, err in COMMON_ERRORS.items():
if flag in text:
return err
return None
def solution_of(err_type) -> str:
return ERRORS_TYPES.get(err_type, '')
def extract_and_group(search_res: Dict[str, SiteResult]) -> List[Dict[str, Any]]:
errors_counts: Dict[str, int] = {}
for r in search_res.values():
if r and isinstance(r, dict) and r.get('status'):
if not isinstance(r['status'], MaigretCheckResult):
continue
err = r['status'].error
if not err:
continue
errors_counts[err.type] = errors_counts.get(err.type, 0) + 1
counts = []
for err, count in sorted(errors_counts.items(), key=lambda x: x[1], reverse=True):
counts.append(
{
'err': err,
'count': count,
# Keep the RAW percentage. is_important() compares this value
# against the threshold, so rounding it here can push a
# sub-threshold rate up to the cutoff: e.g. 8/267 = 2.996%,
# but round(2.996, 2) == 3.0, which would trip the 3%
# "important" threshold for a rate that is below it. Rounding
# is display-only (see notify_about_errors).
'perc': count / len(search_res) * 100,
}
)
return counts
def notify_about_errors(
search_results: Dict[str, SiteResult], query_notify, show_statistics=False
) -> List[Tuple]:
"""
Prepare error notifications in search results, to be displayed by the
notify object. Each notification is a tuple:
- ``(text, symbol)`` — plain message rendered fully bold/bright
- ``(text, symbol, advice)`` — header (``text``) is bold, ``advice`` is
appended in normal weight so the actionable explanation does not
visually overwhelm the count line. Consumer (``notify.warning``)
uses ``*tuple`` unpacking; the extra arg is optional there.
Example::
[
("Too many errors of type \"Connecting failure (DNS)\" (94.0%)",
"!", "DNS resolution failed for ..."),
("Verbose error statistics:", "-"),
]
"""
results = []
errs = extract_and_group(search_results)
was_errs_displayed = False
for e in errs:
if not is_important(e):
continue
text = f'Too many errors of type "{e["err"]}" ({round(e["perc"],2)}%)'
solution = solution_of(e['err'])
if solution:
# Pass advice separately so notify.warning can render it in
# normal weight while keeping the count line bold.
results.append((text, '!', solution.capitalize()))
else:
results.append((text, '!'))
was_errs_displayed = True
if show_statistics:
results.append(('Verbose error statistics:', '-'))
for e in errs:
text = f'{e["err"]}: {round(e["perc"],2)}%'
results.append((text, '!'))
if was_errs_displayed:
results.append(
('You can see detailed site check errors with a flag `--print-errors`', '-')
)
return results
+75
View File
@@ -0,0 +1,75 @@
import asyncio
import time
from typing import Any, Iterable, Callable
class AsyncioQueueGeneratorExecutor:
def __init__(self, *args, **kwargs):
self.workers_count = kwargs.get('in_parallel', 10)
self.queue: asyncio.Queue = asyncio.Queue()
self.timeout = kwargs.get('timeout')
self.logger = kwargs['logger']
self._results: asyncio.Queue = asyncio.Queue()
self._stop_signal = object()
async def worker(self):
"""Process tasks from the queue and put results into the results queue."""
while True:
task = await self.queue.get()
if task is self._stop_signal:
self.queue.task_done()
break
try:
f, args, kwargs = task
query_future = f(*args, **kwargs)
query_task = asyncio.create_task(query_future)
try:
result = await asyncio.wait_for(query_task, timeout=self.timeout)
except asyncio.TimeoutError:
result = kwargs.get('default')
await self._results.put(result)
except Exception as e:
self.logger.error(f"Error in worker: {e}", exc_info=True)
finally:
self.queue.task_done()
async def run(self, queries: Iterable[Callable[..., Any]]):
"""Run workers to process queries in parallel."""
start_time = time.time()
# Add tasks to the queue
for t in queries:
await self.queue.put(t)
# Create workers
workers = [
asyncio.create_task(self.worker()) for _ in range(self.workers_count)
]
# Add stop signals
for _ in range(self.workers_count):
await self.queue.put(self._stop_signal)
try:
while any(w.done() is False for w in workers) or not self._results.empty():
try:
result = await asyncio.wait_for(self._results.get(), timeout=1)
yield result
except asyncio.TimeoutError:
pass
finally:
# If the consumer cancelled us (Ctrl+C → search_task.cancel()),
# the workers are independent asyncio.Tasks that keep draining
# the queue and blocking the finally — for ~timeout per item,
# which is forever from the user's perspective. Cancel them
# explicitly so this finally returns promptly. Swallow their
# CancelledError via return_exceptions=True so it doesn't
# re-raise here and mask the original cancellation.
for w in workers:
if not w.done():
w.cancel()
await asyncio.gather(*workers, return_exceptions=True)
self.execution_time = time.time() - start_time
self.logger.debug(f"Spent time: {self.execution_time}")
+1112
View File
File diff suppressed because it is too large Load Diff
+294
View File
@@ -0,0 +1,294 @@
"""Console and query notification helpers.
This module defines objects for notifying the caller about the results of queries.
"""
import sys
from colorama import Fore, Style, init
from .result import MaigretCheckStatus, KeywordMatchStatus
from .utils import get_dict_ascii_tree
class QueryNotifyPrint:
"""Query Notify Print Object.
Query notify class that prints results.
"""
def __init__(
self,
result=None,
verbose=False,
print_found_only=False,
skip_check_errors=False,
color=True,
silent=False,
):
"""Create Query Notify Print Object.
Contains information about a specific method of notifying the results
of a query.
Keyword Arguments:
self -- This object.
result -- Object of type QueryResult() containing
results for this query.
verbose -- Boolean indicating whether to give verbose output.
print_found_only -- Boolean indicating whether to only print found sites.
color -- Boolean indicating whether to color terminal output
Return Value:
Nothing.
"""
# Colorama module's initialization.
init(autoreset=True)
self.result = result
self.verbose = verbose
self.print_found_only = print_found_only
self.skip_check_errors = skip_check_errors
self.color = color
self.silent = silent
return
def make_colored_terminal_notify(
self, status, text, status_color, text_color, appendix
):
text = [
f"{Style.BRIGHT}{Fore.WHITE}[{status_color}{status}{Fore.WHITE}]"
+ f"{text_color} {text}: {Style.RESET_ALL}"
+ f"{appendix}"
]
return "".join(text)
def make_simple_terminal_notify(
self, status, text, status_color, text_color, appendix
):
return f"[{status}] {text}: {appendix}"
def make_terminal_notify(self, *args):
if self.color:
return self.make_colored_terminal_notify(*args)
else:
return self.make_simple_terminal_notify(*args)
def start(self, message=None, id_type="username"):
"""Notify Start.
Will print the title to the standard output.
Keyword Arguments:
self -- This object.
message -- String containing username that the series
of queries are about.
Return Value:
Nothing.
"""
if self.silent:
return
title = f"Checking {id_type}"
if self.color:
print(
Style.BRIGHT
+ Fore.GREEN
+ "["
+ Fore.YELLOW
+ "*"
+ Fore.GREEN
+ f"] {title}"
+ Fore.WHITE
+ f" {message}"
+ Fore.GREEN
+ " on:"
)
else:
print(f"[*] {title} {message} on:")
def finish(self, message=None):
# Hook called at the end of a run. Currently a no-op; kept on the
# surface because the search loop calls it (checking.py:finish()).
return
def _colored_print(self, fore_color, msg):
if self.color:
print(Style.BRIGHT + fore_color + msg)
else:
print(msg)
def success(self, message, symbol="+"):
msg = f"[{symbol}] {message}"
self._colored_print(Fore.GREEN, msg)
def warning(self, message, symbol="-", advice=None):
"""Print a warning. When ``advice`` is supplied it is appended after
the headline in *normal* weight (same colour), so the actionable
text reads as guidance rather than as part of the alarm itself."""
msg = f"[{symbol}] {message}"
if advice and self.color:
# Bold + yellow for the count line; turn off bold for the advice
# but keep the yellow until the line is reset at the end.
print(
Style.BRIGHT + Fore.YELLOW + msg
+ Style.NORMAL + ". " + advice
+ Style.RESET_ALL
)
elif advice:
# No-colour mode: dot separator is enough to distinguish the
# parts, no ANSI codes leak into the output.
print(f"{msg}. {advice}")
else:
self._colored_print(Fore.YELLOW, msg)
def info(self, message, symbol="*"):
msg = f"[{symbol}] {message}"
self._colored_print(Fore.BLUE, msg)
def update(self, result, is_similar=False):
"""Notify Update.
Will print the query result to the standard output.
Keyword Arguments:
self -- This object.
result -- Object of type QueryResult() containing
results for this query.
Return Value:
Nothing.
"""
if self.silent:
return
notify = None
self.result = result
ids_data_text = ""
if self.result.ids_data:
ids_data_text = get_dict_ascii_tree(self.result.ids_data.items(), " ")
# Output to the terminal is desired.
if result.status == MaigretCheckStatus.CLAIMED:
# Check if this is a keyword match
if (result.keyword_match_status == KeywordMatchStatus.KEYWORD_FOUND and
result.keywords):
# Keyword-context match: site contains username + at least one keyword
color = Fore.LIGHTGREEN_EX
status = "++"
notify = self.make_terminal_notify(
status,
result.site_name,
color,
color,
result.site_url_user + ids_data_text,
)
else:
# Normal claimed site
color = Fore.BLUE if is_similar else Fore.GREEN
status = "?" if is_similar else "+"
notify = self.make_terminal_notify(
status,
result.site_name,
color,
color,
result.site_url_user + ids_data_text,
)
elif result.status == MaigretCheckStatus.AVAILABLE:
if not self.print_found_only:
notify = self.make_terminal_notify(
"-",
result.site_name,
Fore.RED,
Fore.YELLOW,
"Not found!" + ids_data_text,
)
elif result.status == MaigretCheckStatus.UNKNOWN:
if not self.skip_check_errors:
notify = self.make_terminal_notify(
"?",
result.site_name,
Fore.RED,
Fore.RED,
str(self.result.error) + ids_data_text,
)
elif result.status == MaigretCheckStatus.ILLEGAL:
if not self.print_found_only:
text = "Illegal Username Format For This Site!"
notify = self.make_terminal_notify(
"-",
result.site_name,
Fore.RED,
Fore.YELLOW,
text + ids_data_text,
)
else:
# It should be impossible to ever get here...
raise ValueError(
f"Unknown Query Status '{str(result.status)}' for "
f"site '{self.result.site_name}'"
)
if notify:
sys.stdout.write("\x1b[1K\r")
print(notify)
return notify
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
result = str(self.result)
return result
PATREON_URL = "https://www.patreon.com/soxoj"
INTRO_TEXT = "MAIGRET - collect a dossier by username from 3000+ sites"
def _format_intro(use_color: bool) -> str:
if not use_color:
return f"[+] {INTRO_TEXT}"
tag = f"{Style.BRIGHT}{Fore.WHITE}[{Fore.GREEN}+{Fore.WHITE}]{Style.RESET_ALL}"
text = f"{Style.BRIGHT}{Fore.GREEN}{INTRO_TEXT}{Style.RESET_ALL}"
return f"{tag} {text}"
def print_intro_banner(no_color: bool = False, silent: bool = False) -> None:
"""Print the Maigret intro tagline. Skipped only in silent (--ai) mode."""
if silent:
return
print(_format_intro(use_color=not no_color))
def _format_donate_banner(use_color: bool) -> str:
title = "Support Maigret — sites database & development"
link_label = "Donate on Patreon:"
if not use_color:
return f"[♥] {title}\n[♥] {link_label} {PATREON_URL}"
tag = f"{Style.BRIGHT}{Fore.WHITE}[{Fore.RED}{Fore.WHITE}]{Style.RESET_ALL}"
title_c = f"{Style.BRIGHT}{Fore.WHITE}{title}{Style.RESET_ALL}"
label_c = f"{Style.BRIGHT}{Fore.WHITE}{link_label}{Style.RESET_ALL}"
url_c = f"{Style.BRIGHT}{Fore.RED}{PATREON_URL}{Style.RESET_ALL}"
return f"{tag} {title_c}\n{tag} {label_c} {url_c}"
def print_donate_banner(no_color: bool = False, silent: bool = False) -> None:
"""Print a colored donation banner. Skipped only in silent (--ai) mode."""
if silent:
return
print(_format_donate_banner(use_color=not no_color))
+26
View File
@@ -0,0 +1,26 @@
# License MIT. by balestek https://github.com/balestek
from itertools import permutations
class Permute:
def __init__(self, elements: dict):
self.separators = ["", "_", "-", "."]
self.elements = elements
def gather(self, method: str = "strict" or "all") -> dict:
permutations_dict = {}
for i in range(1, len(self.elements) + 1):
for subset in permutations(self.elements, i):
if i == 1:
if method == "all":
permutations_dict[subset[0]] = self.elements[subset[0]]
permutations_dict["_" + subset[0]] = self.elements[subset[0]]
permutations_dict[subset[0] + "_"] = self.elements[subset[0]]
else:
for separator in self.separators:
perm = separator.join(subset)
permutations_dict[perm] = self.elements[subset[0]]
if separator == "":
permutations_dict["_" + perm] = self.elements[subset[0]]
permutations_dict[perm + "_"] = self.elements[subset[0]]
return permutations_dict
+817
View File
@@ -0,0 +1,817 @@
import ast
import csv
import io
import json
import logging
import os
from datetime import datetime
from typing import Dict, Any
import xmind # type: ignore[import-untyped]
from dateutil.tz import gettz
from dateutil.parser import parse as parse_datetime_str
from jinja2 import Template
from .checking import SUPPORTED_IDS
from .result import MaigretCheckStatus
from .sites import MaigretDatabase
from .utils import is_country_tag, CaseConverter, enrich_link_str
ADDITIONAL_TZINFO = {"CDT": gettz("America/Chicago")}
SUPPORTED_JSON_REPORT_FORMATS = [
"simple",
"ndjson",
]
"""
UTILS
"""
def filter_supposed_data(data):
allowed_fields = ["fullname", "gender", "location", "age"]
def _first(v):
if isinstance(v, (list, tuple)):
return v[0] if v else ""
return v
return {
CaseConverter.snake_to_title(k): _first(v)
for k, v in data.items()
if k in allowed_fields
}
def sort_report_by_data_points(results):
return dict(
sorted(
results.items(),
key=lambda x: len(
(x[1].get('status') and x[1]['status'].ids_data or {}).keys()
),
reverse=True,
)
)
"""
REPORTS SAVING
"""
def save_csv_report(filename: str, username: str, results: dict):
with open(filename, "w", newline="", encoding="utf-8") as f:
generate_csv_report(username, results, f)
def save_txt_report(filename: str, username: str, results: dict):
with open(filename, "w", encoding="utf-8") as f:
generate_txt_report(username, results, f)
def save_html_report(filename: str, context: dict):
template, _ = generate_report_template(is_pdf=False)
filled_template = template.render(**context)
with open(filename, "w", encoding="utf-8") as f:
f.write(filled_template)
PDF_EXTRA_HINT = (
"PDF reports require the optional 'pdf' extra. "
"Install it with: pip install 'maigret[pdf]'"
)
def save_pdf_report(filename: str, context: dict):
# Imported lazily so that users without the optional 'pdf' extra
# can still import maigret.report and use other report formats.
try:
from xhtml2pdf import pisa # type: ignore[import-untyped]
except ImportError as e:
raise RuntimeError(PDF_EXTRA_HINT) from e
template, css = generate_report_template(is_pdf=True)
filled_template = template.render(**context)
with open(filename, "w+b") as f:
pisa.pisaDocument(io.StringIO(filled_template), dest=f, default_css=css)
def save_json_report(filename: str, username: str, results: dict, report_type: str):
with open(filename, "w", encoding="utf-8") as f:
generate_json_report(username, results, f, report_type=report_type)
class MaigretGraph:
other_params: dict = {'size': 10, 'group': 3}
site_params: dict = {'size': 15, 'group': 2}
username_params: dict = {'size': 20, 'group': 1}
def __init__(self, graph):
self.G = graph
def add_node(self, key, value, color=None):
node_name = f'{key}: {value}'
params = dict(self.other_params)
if key in SUPPORTED_IDS:
params = dict(self.username_params)
elif value.startswith('http'):
params = dict(self.site_params)
params['title'] = node_name
if color:
params['color'] = color
self.G.add_node(node_name, **params)
return node_name
def link(self, node1_name, node2_name):
self.G.add_edge(node1_name, node2_name, weight=2)
def _build_maigret_graph(username_results: list, db: MaigretDatabase):
import networkx as nx
G: Any = nx.Graph()
graph = MaigretGraph(G)
base_site_nodes = {}
site_account_nodes = {}
processed_values: Dict[str, Any] = {} # Track processed values to avoid duplicates
for username, id_type, results in username_results:
# Add username node, using normalized version directly if different
norm_username = username.lower()
username_node_name = graph.add_node(id_type, norm_username)
for website_name, dictionary in results.items():
if dictionary.get("is_similar"):
continue
status = dictionary.get("status")
if not status or status.status != MaigretCheckStatus.CLAIMED:
continue
# base site node
site_base_url = website_name
if site_base_url not in base_site_nodes:
base_site_nodes[site_base_url] = graph.add_node(
'site', site_base_url, color='#28a745'
) # Green color
site_base_node_name = base_site_nodes[site_base_url]
# account node
account_url = dictionary.get('url_user', f'{site_base_url}/{norm_username}')
account_node_id = f"{site_base_url}: {account_url}"
if account_node_id not in site_account_nodes:
site_account_nodes[account_node_id] = graph.add_node(
'account', account_url
)
account_node_name = site_account_nodes[account_node_id]
# link username → account → site
graph.link(username_node_name, account_node_name)
graph.link(account_node_name, site_base_node_name)
def process_ids(parent_node, ids):
for k, v in ids.items():
if (
k.endswith('_count')
or k.startswith('is_')
or k.endswith('_at')
or k in 'image'
):
continue
# Normalize value if string
norm_v = v.lower() if isinstance(v, str) else v
value_key = f"{k}:{norm_v}"
if value_key in processed_values:
ids_data_name = processed_values[value_key]
else:
v_data = v
if isinstance(v, str) and v.startswith('['):
try:
v_data = ast.literal_eval(v)
except Exception as e:
logging.error(e)
continue
if isinstance(v_data, list):
list_node_name = graph.add_node(k, site_base_url)
processed_values[value_key] = list_node_name
for vv in v_data:
data_node_name = graph.add_node(vv, site_base_url)
graph.link(list_node_name, data_node_name)
add_ids = {
a: b for b, a in db.extract_ids_from_url(vv).items()
}
if add_ids:
process_ids(data_node_name, add_ids)
ids_data_name = list_node_name
else:
ids_data_name = graph.add_node(k, norm_v)
processed_values[value_key] = ids_data_name
if 'username' in k or k in SUPPORTED_IDS:
new_username_key = f"username:{norm_v}"
if new_username_key not in processed_values:
new_username_node_name = graph.add_node(
'username', norm_v
)
processed_values[new_username_key] = (
new_username_node_name
)
graph.link(ids_data_name, new_username_node_name)
add_ids = {
k: v for v, k in db.extract_ids_from_url(v).items()
}
if add_ids:
process_ids(ids_data_name, add_ids)
graph.link(parent_node, ids_data_name)
if status.ids_data:
process_ids(account_node_name, status.ids_data)
# Remove overly long nodes
nodes_to_remove = [node for node in G.nodes if len(str(node)) > 100]
G.remove_nodes_from(nodes_to_remove)
# Remove site nodes with only one connection
single_degree_sites = [
n for n, deg in G.degree() if n.startswith("site:") and deg <= 1
]
G.remove_nodes_from(single_degree_sites)
return G
def save_graph_report(filename: str, username_results: list, db: MaigretDatabase):
G = _build_maigret_graph(username_results, db)
# Generate interactive visualization
from pyvis.network import Network # type: ignore[import-untyped]
nt = Network(notebook=True, height="100vh", width="100%")
nt.from_nx(G)
nt.show(filename)
def _graph_to_cypher(G) -> str:
"""Serialize a maigret networkx graph as an idempotent Cypher script.
Nodes are MERGEd on their unique ``name`` so re-importing the same report
does not create duplicate nodes; edges become ``[:LINKED_TO]`` relationships.
"""
def cstr(value) -> str:
# Cypher single-quoted string literal, with escaping.
s = (
str(value)
.replace('\\', '\\\\')
.replace("'", "\\'")
.replace('\n', '\\n')
.replace('\r', '\\r')
.replace('\t', '\\t')
)
return "'" + s + "'"
lines = [
"// maigret Neo4j export. Import: cypher-shell -u neo4j -p <password> < <file>.cypher",
"CREATE CONSTRAINT maigret_node_name IF NOT EXISTS",
"FOR (n:MaigretNode) REQUIRE n.name IS UNIQUE;",
"",
]
for node in G.nodes():
node_type, _, label = str(node).partition(':')
lines.append(
"MERGE (n:MaigretNode {name: %s}) SET n.type = %s, n.label = %s;"
% (cstr(node), cstr(node_type.strip()), cstr(label.strip()))
)
lines.append("")
for node1, node2 in G.edges():
lines.append(
"MATCH (a:MaigretNode {name: %s}), (b:MaigretNode {name: %s}) "
"MERGE (a)-[:LINKED_TO]->(b);" % (cstr(node1), cstr(node2))
)
lines.append("")
return "\n".join(lines)
def save_neo4j_report(filename: str, username_results: list, db: MaigretDatabase):
"""Save a Neo4j Cypher report of the maigret graph (see ``_graph_to_cypher``).
Load the resulting file with ``cypher-shell -u neo4j -p <password> < file``
or paste it into the Neo4j Browser.
"""
G = _build_maigret_graph(username_results, db)
with open(filename, 'w', encoding='utf-8') as f:
f.write(_graph_to_cypher(G))
def get_plaintext_report(context: dict) -> str:
output = (context['brief'] + " ").replace('. ', '.\n')
interests = list(map(lambda x: x[0], context.get('interests_tuple_list', [])))
countries = list(map(lambda x: x[0], context.get('countries_tuple_list', [])))
if countries:
output += f'Countries: {", ".join(countries)}\n'
if interests:
output += f'Interests (tags): {", ".join(interests)}\n'
return output.strip()
def _md_format_value(value) -> str:
"""Format a value for Markdown output, detecting links."""
if isinstance(value, list):
return ", ".join(str(v) for v in value)
s = str(value)
if s.startswith("http://") or s.startswith("https://"):
return f"[{s}]({s})"
return s
def generate_markdown_report(context: dict, run_info: dict = None) -> str:
username = context.get("username", "unknown")
generated_at = context.get("generated_at", "")
brief = context.get("brief", "")
countries = context.get("countries_tuple_list", [])
interests = context.get("interests_tuple_list", [])
first_seen = context.get("first_seen")
results = context.get("results", [])
# Collect ALL values for key fields across all accounts
all_fields: Dict[str, list] = {}
last_seen = None
for _, _, data in results:
for _, v in data.items():
if not v.get("found") or v.get("is_similar"):
continue
ids_data = v.get("ids_data", {})
# Map multiple source fields to unified output fields
field_sources = {
"fullname": ("fullname", "name"),
"location": ("location", "country", "city", "country_code", "locale", "region"),
"gender": ("gender",),
"bio": ("bio", "about", "description"),
}
for out_field, source_keys in field_sources.items():
for src in source_keys:
val = ids_data.get(src)
if val:
all_fields.setdefault(out_field, [])
val_str = str(val)
if val_str not in all_fields[out_field]:
all_fields[out_field].append(val_str)
# Track last_seen
for ts_field in ("last_online", "latest_activity_at", "updated_at"):
ts = ids_data.get(ts_field)
if ts and (last_seen is None or str(ts) > str(last_seen)):
last_seen = ts
lines = []
lines.append(f"# Report by searching on username \"{username}\"\n")
# Generated line with run info
gen_line = f"Generated at {generated_at} by [Maigret](https://github.com/soxoj/maigret)"
if run_info:
parts = []
if run_info.get("sites_count"):
parts.append(f"{run_info['sites_count']} sites checked")
if run_info.get("flags"):
parts.append(f"flags: `{run_info['flags']}`")
if parts:
gen_line += f" ({', '.join(parts)})"
lines.append(f"{gen_line}\n")
# Summary
lines.append("## Summary\n")
lines.append(f"{brief}\n")
if all_fields:
lines.append("**Information extracted from accounts:**\n")
for field, values in all_fields.items():
title = CaseConverter.snake_to_title(field)
lines.append(f"- {title}: {'; '.join(values)}")
lines.append("")
if countries:
geo = ", ".join(f"{code} (x{count})" for code, count in countries)
lines.append(f"**Country tags:** {geo}\n")
if interests:
tags = ", ".join(f"{tag} (x{count})" for tag, count in interests)
lines.append(f"**Website tags:** {tags}\n")
if first_seen:
lines.append(f"**First seen:** {first_seen}")
if last_seen:
lines.append(f"**Last seen:** {last_seen}")
if first_seen or last_seen:
lines.append("")
# Accounts found
lines.append("## Accounts found\n")
for u, id_type, data in results:
for site_name, v in data.items():
if not v.get("found") or v.get("is_similar"):
continue
lines.append(f"### {site_name}\n")
lines.append(f"- **URL:** [{v.get('url_user', '')}]({v.get('url_user', '')})")
tags = v.get("status") and v["status"].tags or []
if tags:
lines.append(f"- **Tags:** {', '.join(tags)}")
lines.append("")
ids_data = v.get("ids_data", {})
if ids_data:
for field, value in ids_data.items():
if field == "image":
continue
title = CaseConverter.snake_to_title(field)
lines.append(f"- {title}: {_md_format_value(value)}")
lines.append("")
# Possible false positives
lines.append("## Possible false positives\n")
lines.append(
f"This report was generated by searching for accounts matching the username `{username}`. "
f"Accounts listed above may belong to different people who happen to use the same "
f"or similar username. Results without extracted personal information could contain "
f"some false positive findings. Always verify findings before drawing conclusions.\n"
)
# Ethical use
lines.append("## Ethical use\n")
lines.append(
"This report is a result of a technical collection of publicly available information "
"from online accounts and does not constitute personal data processing. If you intend "
"to use this data for personal data processing or collection purposes, ensure your use "
"complies with applicable laws and regulations in your jurisdiction (such as GDPR, "
"CCPA, and similar).\n"
)
return "\n".join(lines)
def save_markdown_report(filename: str, context: dict, run_info: dict = None):
content = generate_markdown_report(context, run_info)
with open(filename, "w", encoding="utf-8") as f:
f.write(content)
"""
REPORTS GENERATING
"""
def generate_report_template(is_pdf: bool):
"""
HTML/PDF template generation
"""
def get_resource_content(filename):
return open(os.path.join(maigret_path, "resources", filename)).read()
maigret_path = os.path.dirname(os.path.realpath(__file__))
if is_pdf:
template_content = get_resource_content("simple_report_pdf.tpl")
css_content = get_resource_content("simple_report_pdf.css")
else:
template_content = get_resource_content("simple_report.tpl")
css_content = None
# autoescape: report data comes from scanned profiles and must be escaped
# to avoid XSS in the generated report.
template = Template(template_content, autoescape=True)
template.globals["title"] = CaseConverter.snake_to_title # type: ignore
template.globals["detect_link"] = enrich_link_str # type: ignore
return template, css_content
def generate_report_context(username_results: list):
brief_text = []
usernames = {}
extended_info_count = 0
tags: Dict[str, int] = {}
supposed_data: Dict[str, Any] = {}
first_seen = None
# moved here to speed up the launch of Maigret
import pycountry
for username, id_type, results in username_results:
found_accounts = 0
new_ids = []
usernames[username] = {"type": id_type}
for website_name in results:
dictionary = results[website_name]
if dictionary.get("is_similar"):
continue
status = dictionary.get("status")
if not status: # FIXME: currently in case of timeout
continue
if status.ids_data:
dictionary["ids_data"] = status.ids_data
extended_info_count += 1
# detect first seen
created_at = status.ids_data.get("created_at")
if created_at:
if first_seen is None:
first_seen = created_at
else:
try:
known_time = parse_datetime_str(
first_seen, tzinfos=ADDITIONAL_TZINFO
)
new_time = parse_datetime_str(
created_at, tzinfos=ADDITIONAL_TZINFO
)
if new_time < known_time:
first_seen = created_at
except Exception as e:
logging.debug(
"Problems with converting datetime %s/%s: %s",
first_seen,
created_at,
str(e),
exc_info=True,
)
for k, v in status.ids_data.items():
# suppose target data
field = "fullname" if k == "name" else k
if field not in supposed_data:
supposed_data[field] = []
supposed_data[field].append(v)
# suppose country
if k in ["country", "locale"]:
try:
if is_country_tag(v):
country = pycountry.countries.get(alpha_2=v)
tag = country.alpha_2.lower() # type: ignore[union-attr]
else:
tag = pycountry.countries.search_fuzzy(v)[
0
].alpha_2.lower() # type: ignore[attr-defined]
tags[tag] = tags.get(tag, 0) + 1
except Exception as e:
logging.debug(
"Pycountry exception: %s", str(e), exc_info=True
)
new_usernames = dictionary.get("ids_usernames")
if new_usernames:
for u, utype in new_usernames.items():
if u not in usernames:
new_ids.append((u, utype))
usernames[u] = {"type": utype}
if status.status == MaigretCheckStatus.CLAIMED:
found_accounts += 1
dictionary["found"] = True
else:
continue
# ignore non-exact search results
if status.tags:
for t in status.tags:
tags[t] = tags.get(t, 0) + 1
brief_text.append(
f"Search by {id_type} {username} returned {found_accounts} accounts."
)
if new_ids:
ids_list = []
for u, t in new_ids:
ids_list.append(f"{u} ({t})" if t != "username" else u)
brief_text.append("Found target's other IDs: " + ", ".join(ids_list) + ".")
brief_text.append(f"Extended info extracted from {extended_info_count} accounts.")
brief = " ".join(brief_text).strip()
tuple_sort = lambda d: sorted(d, key=lambda x: x[1], reverse=True)
if "global" in tags:
# remove tag 'global' useless for country detection
del tags["global"]
first_username = username_results[0][0]
countries_lists = list(filter(lambda x: is_country_tag(x[0]), tags.items()))
interests_list = list(filter(lambda x: not is_country_tag(x[0]), tags.items()))
filtered_supposed_data = filter_supposed_data(supposed_data)
return {
"username": first_username,
"brief": brief,
"results": username_results,
"first_seen": first_seen,
"interests_tuple_list": tuple_sort(interests_list),
"countries_tuple_list": tuple_sort(countries_lists),
"supposed_data": filtered_supposed_data,
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
def generate_csv_report(username: str, results: dict, csvfile):
writer = csv.writer(csvfile)
writer.writerow(
[
"username",
"name",
"url_main",
"url_user",
"exists",
"http_status",
"error_reason",
]
)
for site in results:
status = 'Unknown'
result_status = results[site].get("status")
error_reason = _get_result_error_reason(result_status)
if result_status:
status = str(result_status.status)
writer.writerow(
[
username,
site,
results[site].get("url_main", ""),
results[site].get("url_user", ""),
status,
results[site].get("http_status", 0),
error_reason,
]
)
def _get_result_error_reason(result_status):
if not result_status:
return 'Unknown'
if result_status.error:
context = getattr(result_status.error, 'context', None)
if context:
return str(context)
return str(result_status.error)
if result_status.context:
return str(result_status.context)
if result_status.status == MaigretCheckStatus.UNKNOWN:
return 'Unknown'
return ''
def generate_txt_report(username: str, results: dict, file):
exists_counter = 0
for website_name in results:
dictionary = results[website_name]
if (
dictionary.get("status")
and dictionary["status"].status == MaigretCheckStatus.CLAIMED
):
exists_counter += 1
file.write(dictionary["url_user"] + "\n")
file.write(f"Total Websites Username Detected On : {exists_counter}")
def generate_json_report(username: str, results: dict, file, report_type):
is_report_per_line = report_type.startswith("ndjson")
all_json = {}
for sitename in results:
site_result = results[sitename]
if not site_result.get("status"):
continue
if site_result["status"].status != MaigretCheckStatus.CLAIMED:
continue
data = dict(site_result)
data["status"] = data["status"].json()
data["site"] = data["site"].json
for field in ["future", "checker"]:
if field in data:
del data[field]
if is_report_per_line:
data["sitename"] = sitename
file.write(json.dumps(data) + "\n")
else:
all_json[sitename] = data
if not is_report_per_line:
file.write(json.dumps(all_json))
"""
XMIND 8 Functions
"""
def save_xmind_report(filename, username, results):
if os.path.exists(filename):
os.remove(filename)
workbook = xmind.load(filename)
sheet = workbook.getPrimarySheet()
design_xmind_sheet(sheet, username, results)
xmind.save(workbook, path=filename)
def add_xmind_subtopic(userlink, k, v, supposed_data):
currentsublabel = userlink.addSubTopic()
field = "fullname" if k == "name" else k
if field not in supposed_data:
supposed_data[field] = []
supposed_data[field].append(v)
currentsublabel.setTitle("%s: %s" % (k, v))
def design_xmind_sheet(sheet, username, results):
alltags: Dict[str, Any] = {}
supposed_data: Dict[str, Any] = {}
sheet.setTitle("%s Analysis" % (username))
root_topic1 = sheet.getRootTopic()
root_topic1.setTitle("%s" % (username))
undefinedsection = root_topic1.addSubTopic()
undefinedsection.setTitle("Undefined")
alltags["undefined"] = undefinedsection
error_section = None
for website_name in results:
dictionary = results[website_name]
result_status = dictionary.get("status")
if not result_status or result_status.status != MaigretCheckStatus.CLAIMED:
error_reason = _get_result_error_reason(result_status)
if not error_reason:
continue
if error_section is None:
error_section = root_topic1.addSubTopic()
error_section.setTitle("Errors")
userlink = error_section.addSubTopic()
userlink.setTitle(f"{website_name}: {error_reason}")
if dictionary.get("url_user"):
userlink.addLabel(dictionary["url_user"])
continue
stripped_tags = list(map(lambda x: x.strip(), result_status.tags))
normalized_tags = list(
filter(lambda x: x and not is_country_tag(x), stripped_tags)
)
category = None
for tag in normalized_tags:
if tag in alltags.keys():
continue
tagsection = root_topic1.addSubTopic()
tagsection.setTitle(tag)
alltags[tag] = tagsection
category = tag
section = alltags[category] if category else undefinedsection
userlink = section.addSubTopic()
userlink.addLabel(result_status.site_url_user)
ids_data = result_status.ids_data or {}
for k, v in ids_data.items():
# suppose target data
if isinstance(v, list):
for currentval in v:
add_xmind_subtopic(userlink, k, currentval, supposed_data)
else:
add_xmind_subtopic(userlink, k, v, supposed_data)
# add supposed data
filtered_supposed_data = filter_supposed_data(supposed_data)
if len(filtered_supposed_data) > 0:
undefinedsection = root_topic1.addSubTopic()
undefinedsection.setTitle("SUPPOSED DATA")
for k, v in filtered_supposed_data.items():
currentsublabel = undefinedsection.addSubTopic()
currentsublabel.setTitle("%s: %s" % (k, v))
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
{
"version": 1,
"updated_at": "2026-07-11T14:02:40Z",
"sites_count": 3187,
"min_maigret_version": "0.5.0",
"data_sha256": "4eeed3b475a1ff4dce558bbca926d50adcb6b8e5372770f330e25baa4a252df0",
"data_url": "https://raw.githubusercontent.com/soxoj/maigret/main/maigret/resources/data.json"
}
+110
View File
@@ -0,0 +1,110 @@
<html>
<head>
<meta charset="utf-8" />
</head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
<title>{{ username }} -- Maigret username search report</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<style>
.table td, .table th {
padding: .4rem;
}
@media print {
.pagebreak { page-break-before: always; }
}
</style>
<body>
<div class="container">
<div class="row-mb">
<div class="col-12 card-body" style="padding-bottom: 0.5rem;">
<h4 class="mb-0">
<a class="blog-header-logo text-dark" href="#">Username search report for {{ username }}</a>
</h4>
<small class="text-muted">Generated by <a href="https://github.com/soxoj/maigret">Maigret</a> at {{ generated_at }}</small>
</div>
</div>
<div class="row-mb">
<div class="col-md">
<div class="card flex-md-row mb-4 box-shadow h-md-250">
<div class="card-body d-flex flex-column align-items-start">
<h5>Supposed personal data</h5>
{% for k, v in supposed_data.items() %}
<span>
{{ k }}: {{ v }}
</span>
{% endfor %}
{% if countries_tuple_list %}
<span>
Geo: {% for k, v in countries_tuple_list %}{{ k }} <span class="text-muted">({{ v }})</span>{{ ", " if not loop.last }}{% endfor %}
</span>
{% endif %}{% if interests_tuple_list %}
<span>
Interests: {% for k, v in interests_tuple_list %}{{ k }} <span class="text-muted">({{ v }})</span>{{ ", " if not loop.last }}{% endfor %}
</span>
{% endif %}{% if first_seen %}
<span>
First seen: {{ first_seen }}
</span>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row-mb">
<div class="col-md">
<div class="card flex-md-row mb-4 box-shadow h-md-250">
<div class="card-body d-flex flex-column align-items-start">
<h5>Brief</h5>
<span>
{{ brief }}
</span>
</div>
</div>
</div>
</div>
{% for u, t, data in results %}
{% for k, v in data.items() %}
{% if v.found and not v.is_similar %}
<div class="row-mb">
<div class="col-md">
<div class="card flex-md-row mb-4 box-shadow h-md-250">
<img class="card-img-right flex-auto d-md-block" alt="Photo" style="width: 200px; height: 200px; object-fit: scale-down;" src="{{ v.status and v.status.ids_data and v.status.ids_data.image or 'https://i.imgur.com/040fmbw.png' }}" data-holder-rendered="true">
<div class="card-body d-flex flex-column align-items-start" style="padding-top: 0;">
<h3 class="mb-0" style="padding-top: 1rem;">
<a class="text-dark" href="{{ v.url_main }}" target="_blank">{{ k }}</a>
</h3>
{% if v.status.tags %}
<div class="mb-1 text-muted">Tags: {{ v.status.tags | join(', ') }}</div>
{% endif %}
<p class="card-text">
<a href="{{ v.url_user }}" target="_blank">{{ v.url_user }}</a>
<span class="text-muted small">(<a href="https://web.archive.org/web/*/{{ v.url_user }}" target="_blank">web.archive.org</a>, <a href="https://archive.is/newest/{{ v.url_user }}" target="_blank">archive.is</a>)</span>
</p>
{% if v.ids_data %}
<table class="table table-striped">
<tbody>
{% for k1, v1 in v.ids_data.items() %}
{% if k1 != 'image' %}
<tr>
<th>{{ title(k1) }}</th>
<td>{% if v1 is iterable and (v1 is not string and v1 is not mapping) %}{{ v1 | join(', ') }}{% else %}{{ detect_link(v1) }}{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% endif %}
</p>
</div>
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</html>
+45
View File
@@ -0,0 +1,45 @@
h2 {
font-size: 30px;
width: 100%;
display:block;
}
h3 {
font-size: 25px;
width: 100%;
display:block;
}
h4 {
font-size: 20px;
width: 100%;
display:block;
}
p {
margin: 0 0 5px;
display: block;
}
table {
margin-bottom: 10px;
width:100%;
}
th {
font-weight: bold;
}
th,td,caption {
padding: 4px 10px 4px 5px;
}
table tr:nth-child(even) td,
table tr.even td {
background-color: #e5ecf9;
}
div {
border-bottom-color: #3e3e3e;
border-bottom-width: 1px;
border-bottom-style: solid;
}
.invalid-button {
position: absolute;
left: 10px;
}
+116
View File
@@ -0,0 +1,116 @@
<html>
<head>
<meta charset="utf-8" />
</head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
<title>{{ username }} -- Maigret username search report</title>
<body>
<div class="container">
<div class="row-mb">
<div class="col-12 card-body" style="padding-bottom: 0.5rem; width:100%">
<h2 class="mb-0">
Username search report for {{ username }}
</h2>
<small>Generated by <a href="https://github.com/soxoj/maigret">Maigret</a> at {{ generated_at }}</small>
</div>
</div>
<br/><br/>
<div>
<div>
<div>
<div>
<h3>Supposed personal data</h3>
{% for k, v in supposed_data.items() %}
<p>
{{ k }}: {{ v }}
</p>
{% endfor %}
{% if countries_tuple_list %}
<p>
Geo: {% for k, v in countries_tuple_list %}{{ k }} <span class="text-muted">({{ v }})</span>{{ ", " if not loop.last }}{% endfor %}
</p>
{% endif %}{% if interests_tuple_list %}
<p>
Interests: {% for k, v in interests_tuple_list %}{{ k }} <span class="text-muted">({{ v }})</span>{{ ", " if not loop.last }}{% endfor %}
</p>
{% endif %}{% if first_seen %}
<p>
First seen: {{ first_seen }}
</p>
{% endif %}
</div>
</div>
</div>
</div>
<br/>
<div>
<div>
<div>
<div>
<h3>Brief</h3>
<p>
{{ brief }}
</p>
</div>
</div>
</div>
</div>
{% for u, t, data in results %}
{% for k, v in data.items() %}
{% if v.found and not v.is_similar %}
<split></split>
<hr>
<br/>
<div class="sitebox" style="margin-top: 20px;" >
<div>
<div>
<table>
<tr>
<td valign="top">
<div class="textbox" style="padding-top: 10px;" >
<h3>
<a class="text-dark" href="{{ v.url_main }}" target="_blank">{{ k }}</a>
</h3>
{% if v.status.tags %}
<div class="mb-1 text-muted">Tags: {{ v.status.tags | join(', ') }}</div>
{% endif %}
<p class="card-text">
<a href="{{ v.url_user }}" target="_blank">{{ v.url_user }}</a>
<span class="text-muted small">(<a href="https://web.archive.org/web/*/{{ v.url_user }}" target="_blank">web.archive.org</a>, <a href="https://archive.is/newest/{{ v.url_user }}" target="_blank">archive.is</a>)</span>
</p>
</div>
{% if v.ids_data %}
<div style="clear:both;"></div>
<div style="width:100%">
<br/>
<h4>Details</h4>
<table class="table table-striped;" style="margin-top:5px;">
<tbody>
{% for k1, v1 in v.ids_data.items() %}
{% if k1 != 'image' %}
<tr>
<th style="width:200px;">{{ title(k1) }}</th>
<td>{% if v1 is iterable and (v1 is not string and v1 is not mapping) %}{{ v1 | join(', ') }}{% else %}{{ detect_link(v1) }}{% endif %}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</td>
<td style="width:201px; position: relative;" valign="top">
<img alt="Photo" style="width: 200px; height: 200px; object-fit: scale-down;" src="{{ v.status.ids_data.image or 'https://i.imgur.com/040fmbw.png' }}" data-holder-rendered="true">
</td>
</tr>
</table>
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
</body>
</html>
+190
View File
@@ -0,0 +1,190 @@
"""Maigret Result Module
This module defines various objects for recording the results of queries.
"""
import asyncio
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypedDict
if TYPE_CHECKING:
from aiohttp import CookieJar
from .sites import MaigretSite
class KeywordMatchStatus(Enum):
"""Keyword Match Status Enumeration.
Describes the status of keyword matching for a given site.
"""
NO_KEYWORDS = "No Keywords"
KEYWORD_FOUND = "Keyword Found"
KEYWORDS_NOT_FOUND = "Keywords Not Found"
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
return self.value
class MaigretCheckStatus(Enum):
"""Query Status Enumeration.
Describes status of query about a given username.
"""
CLAIMED = "Claimed" # Username Detected
AVAILABLE = "Available" # Username Not Detected
UNKNOWN = "Unknown" # Error Occurred While Trying To Detect Username
ILLEGAL = "Illegal" # Username Not Allowable For This Site
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
return self.value
class MaigretCheckResult:
"""
Describes result of checking a given username on a given site
"""
def __init__(
self,
username,
site_name,
site_url_user,
status,
ids_data=None,
query_time=None,
context=None,
error=None,
tags=[],
keywords=None,
keyword_match_status=None
):
"""
Keyword Arguments:
self -- This object.
username -- String indicating username that query result
was about.
site_name -- String which identifies site.
site_url_user -- String containing URL for username on site.
NOTE: The site may or may not exist: this
just indicates what the name would
be, if it existed.
status -- Enumeration of type QueryStatus() indicating
the status of the query.
query_time -- Time (in seconds) required to perform query.
Default of None.
context -- String indicating any additional context
about the query. For example, if there was
an error, this might indicate the type of
error that occurred.
Default of None.
ids_data -- Extracted from website page info about other
usernames and inner ids.
keywords -- List of keywords to search for in page content.
Default of None.
keyword_match_status -- Enumeration of type KeywordMatchStatus()
indicating keyword matching status.
Default of None.
Return Value:
Nothing.
"""
self.username = username
self.site_name = site_name
self.site_url_user = site_url_user
self.status = status
self.query_time = query_time
self.context = context
self.ids_data = ids_data
self.tags = tags
self.error = error
self.keywords = keywords or []
self.keyword_match_status = keyword_match_status or KeywordMatchStatus.NO_KEYWORDS
def json(self):
return {
"username": self.username,
"site_name": self.site_name,
"url": self.site_url_user,
"status": str(self.status),
"ids": self.ids_data or {},
"tags": self.tags,
"keywords": self.keywords,
"keyword_match_status": str(self.keyword_match_status)
}
def is_found(self):
return self.status == MaigretCheckStatus.CLAIMED
def __repr__(self):
return f"<{self.__str__()}>"
def __str__(self):
"""Convert Object To String.
Keyword Arguments:
self -- This object.
Return Value:
Nicely formatted string to get information about this object.
"""
status = str(self.status)
if self.context is not None:
# There is extra context information available about the results.
# Append it to the normal response text.
status += f" ({self.context})"
return status
class SiteResult(TypedDict, total=False):
"""Per-site result dict, keyed by site name in the top-level results dict.
Populated across three phases — make_site_result, process_site_result,
then report generation — so every field is optional from the type
system's point of view.
"""
# make_site_result
site: "MaigretSite"
username: str
keywords: List[str]
parsing_enabled: bool
url_main: str
cookies: Optional["CookieJar"]
url_user: str
url_probe: str
future: asyncio.Future
checker: Any # CheckerBase lives in checking.py; importing back here is circular
# process_site_result
status: MaigretCheckResult
http_status: Any # int on success, "" on error branches — mixed by design
is_similar: bool
rank: Optional[int]
response_text: str
# extract_ids_data
ids_usernames: Dict[str, str]
ids_links: List[str]
# added during report generation
found: bool
ids_data: Dict[str, Any]
sitename: str # only set for per-line JSON export
+92
View File
@@ -0,0 +1,92 @@
import os
import os.path as path
import json
from typing import List
SETTINGS_FILES_PATHS = [
path.join(path.dirname(path.realpath(__file__)), "resources/settings.json"),
path.expanduser('~/.maigret/settings.json'),
path.join(os.getcwd(), 'settings.json'),
]
class Settings:
# main maigret setting
retries_count: int
sites_db_path: str
timeout: int
max_connections: int
recursive_search: bool
info_extracting: bool
cookie_jar_file: str
ignore_ids_list: List
reports_path: str
proxy_url: str
tor_proxy_url: str
i2p_proxy_url: str
domain_search: bool
scan_all_sites: bool
top_sites_count: int
scan_disabled_sites: bool
scan_sites_list: List
self_check_enabled: bool
print_not_found: bool
print_check_errors: bool
colored_print: bool
show_progressbar: bool
report_sorting: str
json_report_type: str
txt_report: bool
csv_report: bool
xmind_report: bool
pdf_report: bool
html_report: bool
graph_report: bool
neo4j_report: bool
md_report: bool
web_interface_port: int
no_autoupdate: bool
db_update_meta_url: str
autoupdate_check_interval_hours: int
cloudflare_bypass: dict
# submit mode settings
presence_strings: list
supposed_usernames: list
def __init__(self):
pass
def load(self, paths=None):
was_inited = False
if not paths:
paths = SETTINGS_FILES_PATHS
for filename in paths:
data = {}
try:
with open(filename, "r", encoding="utf-8") as file:
data = json.load(file)
except FileNotFoundError:
# treast as a normal situation
pass
except Exception as error:
return False, ValueError(
f"Problem with parsing json contents of "
f"settings file '{filename}': {str(error)}."
)
self.__dict__.update(data)
if data:
was_inited = True
return (
was_inited,
f'None of the default settings files found: {", ".join(paths)}',
)
@property
def json(self):
return self.__dict__
+730
View File
@@ -0,0 +1,730 @@
# ****************************** -*-
"""Maigret Sites Information"""
import copy
import json
import logging
import sys
from typing import Optional, List, Dict, Any, Tuple
from .utils import CaseConverter, URLMatcher, is_country_tag
logger = logging.getLogger(__name__)
class MaigretEngine:
site: Dict[str, Any] = {}
def __init__(self, name, data):
self.name = name
self.__dict__.update(data)
@property
def json(self):
return self.__dict__
class MaigretSite:
# Fields that should not be serialized when converting site to JSON
NOT_SERIALIZABLE_FIELDS = [
"name",
"engineData",
"requestFuture",
"detectedEngine",
"engineObj",
"stats",
"urlRegexp",
]
# Username known to exist on the site
username_claimed = ""
# Username known to not exist on the site
username_unclaimed = ""
# Additional URL path component, e.g. /forum in https://example.com/forum/users/{username}
url_subpath = ""
# Main site URL (the main page)
url_main = ""
# Full URL pattern for username page, e.g. https://example.com/forum/users/{username}
url = ""
# Whether site is disabled. Not used by Maigret without --use-disabled argument
disabled = False
# Whether a positive result indicates accounts with similar usernames rather than exact matches
similar_search = False
# Whether to ignore 403 status codes
ignore403 = False
# Site category tags
tags: List[str] = []
# Type of identifier (username, gaia_id etc); see SUPPORTED_IDS in checking.py
type = "username"
# Custom HTTP headers
headers: Dict[str, str] = {}
# Error message substrings
errors: Dict[str, str] = {}
# Site activation requirements
activation: Dict[str, Any] = {}
# Regular expression for username validation
regex_check = None
# URL to probe site status
url_probe = None
# Type of check to perform
check_type = ""
# HTTP request method (GET, POST, HEAD, etc.)
request_method = ""
# HTTP request payload (for POST, PUT, etc.)
request_payload: Dict[str, Any] = {}
# Whether to only send HEAD requests (GET by default)
request_head_only = ""
# GET parameters to include in requests
get_params: Dict[str, Any] = {}
# Substrings in HTML response that indicate profile exists
presense_strs: List[str] = []
# Substrings in HTML response that indicate profile doesn't exist
absence_strs: List[str] = []
# Site statistics
stats: Dict[str, Any] = {}
# Site engine name
engine = None
# Engine-specific configuration
engine_data: Dict[str, Any] = {}
# Engine instance
engine_obj: Optional["MaigretEngine"] = None
# Future for async requests
request_future = None
# Alexa traffic rank
alexa_rank = None
# Source (in case a site is a mirror of another site)
source: Optional[str] = None
# URL protocol (http/https)
protocol = ''
# Protection types detected on this site (e.g. ["tls_fingerprint", "ddos_guard"])
protection: List[str] = []
def __init__(self, name, information):
self.name = name
self.url_subpath = ""
for k, v in information.items():
self.__dict__[CaseConverter.camel_to_snake(k)] = v
if (self.alexa_rank is None) or (self.alexa_rank == 0):
# We do not know the popularity, so make site go to bottom of list.
self.alexa_rank = sys.maxsize
self.update_detectors()
def __str__(self):
return f"{self.name} ({self.url_main})"
def __is_equal_by_url_or_name(self, url_or_name_str: str):
lower_url_or_name_str = url_or_name_str.lower()
lower_url = self.url.lower()
lower_name = self.name.lower()
lower_url_main = self.url_main.lower()
return (
lower_name == lower_url_or_name_str
or (lower_url_main and lower_url_main == lower_url_or_name_str)
or (lower_url_main and lower_url_main in lower_url_or_name_str)
or (lower_url_main and lower_url_or_name_str in lower_url_main)
or (lower_url and lower_url_or_name_str in lower_url)
)
def __eq__(self, other):
if isinstance(other, MaigretSite):
# Compare only relevant attributes, not internal state like request_future
attrs_to_compare = [
'name',
'url_main',
'url_subpath',
'type',
'headers',
'errors',
'activation',
'regex_check',
'url_probe',
'check_type',
'request_method',
'request_payload',
'request_head_only',
'get_params',
'presense_strs',
'absence_strs',
'stats',
'engine',
'engine_data',
'alexa_rank',
'source',
'protocol',
]
return all(
getattr(self, attr) == getattr(other, attr) for attr in attrs_to_compare
)
elif isinstance(other, str):
# Compare only by name (exactly) or url_main (partial similarity)
return self.__is_equal_by_url_or_name(other)
return False
def update_detectors(self):
if "url" in self.__dict__:
url = self.url
for group in ["urlMain", "urlSubpath"]:
if group in url:
url = url.replace(
"{" + group + "}",
self.__dict__[CaseConverter.camel_to_snake(group)],
)
self.url_regexp = URLMatcher.make_profile_url_regexp(
url, self.regex_check or ""
)
def detect_username(self, url: str) -> Optional[str]:
if self.url_regexp:
match_groups = self.url_regexp.match(url)
if match_groups:
username = next(
(
group.rstrip("/")
for group in reversed(match_groups.groups())
if isinstance(group, str) and group
),
None,
)
return username
return None
def extract_id_from_url(self, url: str) -> Optional[Tuple[str, str]]:
"""
Extracts username from url.
It's outdated, detects only a format of https://example.com/{username}
"""
if not self.url_regexp:
return None
match_groups = self.url_regexp.match(url)
if not match_groups:
return None
_id = next(
(
group.rstrip("/")
for group in reversed(match_groups.groups())
if isinstance(group, str) and group
),
None,
)
if _id is None:
return None
_type = self.type
return _id, _type
@property
def pretty_name(self):
if self.source:
return f"{self.name} [{self.source}]"
return self.name
@property
def json(self):
result = {}
for k, v in self.__dict__.items():
# convert to camelCase
field = CaseConverter.snake_to_camel(k)
# strip empty elements
if v in (False, "", [], {}, None, sys.maxsize, "username"):
continue
if field in self.NOT_SERIALIZABLE_FIELDS:
continue
result[field] = v
return result
@property
def errors_dict(self) -> dict:
errors: Dict[str, str] = {}
if self.engine_obj:
errors.update(self.engine_obj.site.get('errors', {}))
errors.update(self.errors)
return errors
def get_url_template(self) -> str:
url = URLMatcher.extract_main_part(self.url)
if url.startswith("{username}"):
url = "SUBDOMAIN"
elif url == "":
url = f"{self.url} ({self.engine or 'no engine'})"
else:
parts = url.split("/")
url = "/" + "/".join(parts[1:])
return url
def update(self, updates: "dict") -> "MaigretSite":
self.__dict__.update(updates)
self.update_detectors()
return self
def update_from_engine(self, engine: MaigretEngine) -> "MaigretSite":
engine_data = engine.site
for k, v in engine_data.items():
field = CaseConverter.camel_to_snake(k)
if isinstance(v, dict):
# update dicts like errors
target = self.__dict__.get(field, {})
for item_key, item_value in v.items():
if item_key in target and target[item_key] != item_value:
logger.warning(
"Engine %s overrides %s.%s for site %s",
engine.name,
field,
item_key,
self.name,
)
target.update(v)
elif isinstance(v, list):
self.__dict__[field] = self.__dict__.get(field, []) + v
else:
self.__dict__[field] = v
self.engine_obj = engine
self.update_detectors()
return self
def strip_engine_data(self) -> "MaigretSite":
if not self.engine_obj:
return self
self.request_future = None
self.url_regexp = None
self_copy = copy.deepcopy(self)
engine_data = self_copy.engine_obj and self_copy.engine_obj.site or {}
site_data_keys = list(self_copy.__dict__.keys())
for k in engine_data.keys():
field = CaseConverter.camel_to_snake(k)
is_exists = field in site_data_keys
# remove dict keys
if isinstance(engine_data[k], dict) and is_exists:
for f in engine_data[k].keys():
if f in self_copy.__dict__[field]:
del self_copy.__dict__[field][f]
continue
# remove list items
if isinstance(engine_data[k], list) and is_exists:
for f in engine_data[k]:
if f in self_copy.__dict__[field]:
self_copy.__dict__[field].remove(f)
continue
if is_exists:
del self_copy.__dict__[field]
return self_copy
class MaigretDatabase:
def __init__(self):
self._tags: list = []
self._sites: list = []
self._engines: list = []
@property
def sites(self):
return self._sites
@property
def sites_dict(self):
return {site.name: site for site in self._sites}
def has_site(self, site: MaigretSite):
for s in self._sites:
if site == s:
return True
return False
def __contains__(self, site):
return self.has_site(site)
def ranked_sites_dict(
self,
reverse=False,
top=sys.maxsize,
tags=[],
excluded_tags=[],
names=[],
disabled=True,
id_type="username",
):
"""
Ranking and filtering of the sites list
When ``top`` is limited (not "all sites"), **mirrors** may be appended after
the Alexa-ranked slice. A mirror is any filtered site with a non-empty
``source`` field equal to the name of a site that appears in the first
``top`` positions of a **parent ranking** that includes disabled sites.
Thus mirrors such as third-party viewers (e.g. for Twitter or Instagram)
are still scanned when their parent platform ranks highly, even if the
official site is disabled and omitted from the main list.
Args:
reverse (bool, optional): Reverse the sorting order. Defaults to False.
top (int, optional): Maximum number of sites to return. Defaults to sys.maxsize.
tags (list, optional): List of tags to filter sites by (whitelist). Defaults to empty list.
excluded_tags (list, optional): List of tags to exclude sites by (blacklist). Defaults to empty list.
names (list, optional): List of site names (or urls, see MaigretSite.__eq__) to filter by. Defaults to empty list.
disabled (bool, optional): Whether to include disabled sites. Defaults to True.
id_type (str, optional): Type of identifier to filter by. Defaults to "username".
Returns:
dict: Dictionary of filtered and ranked sites (base top slice plus mirrors),
with site names as keys and MaigretSite objects as values
"""
normalized_names = list(map(str.lower, names))
normalized_tags = list(map(str.lower, tags))
normalized_excluded_tags = list(map(str.lower, excluded_tags))
is_name_ok = lambda x: x.name.lower() in normalized_names
is_source_ok = lambda x: x.source and x.source.lower() in normalized_names
is_engine_ok = (
lambda x: isinstance(x.engine, str) and x.engine.lower() in normalized_tags
)
is_tags_ok = lambda x: set(map(str.lower, x.tags)).intersection(
set(normalized_tags)
)
is_protocol_in_tags = lambda x: x.protocol and x.protocol in normalized_tags
is_disabled_needed = lambda x: not x.disabled or (
"disabled" in tags or disabled
)
is_id_type_ok = lambda x: x.type == id_type
is_excluded_by_tag = lambda x: set(map(str.lower, x.tags)).intersection(
set(normalized_excluded_tags)
)
is_excluded_by_engine = lambda x: (
isinstance(x.engine, str) and x.engine.lower() in normalized_excluded_tags
)
is_excluded_by_protocol = lambda x: (
x.protocol and x.protocol in normalized_excluded_tags
)
is_not_excluded = lambda x: not excluded_tags or not (
is_excluded_by_tag(x)
or is_excluded_by_engine(x)
or is_excluded_by_protocol(x)
)
filter_tags_engines_fun = (
lambda x: not tags
or is_engine_ok(x)
or is_tags_ok(x)
or is_protocol_in_tags(x)
)
filter_names_fun = lambda x: not names or is_name_ok(x) or is_source_ok(x)
filter_fun = (
lambda x: filter_tags_engines_fun(x)
and is_not_excluded(x)
and filter_names_fun(x)
and is_disabled_needed(x)
and is_id_type_ok(x)
)
filtered_list = [s for s in self.sites if filter_fun(s)]
sorted_list = sorted(
filtered_list, key=lambda x: x.alexa_rank, reverse=reverse
)[:top]
# Mirrors: sites whose `source` matches a parent platform that ranks in the
# top `top` by Alexa when disabled entries are included in the ranking pool
# (so e.g. Instagram can be a parent for Picuki even if Instagram is disabled).
if top < sys.maxsize and sorted_list:
filter_fun_ranking_parents = (
lambda x: filter_tags_engines_fun(x)
and is_not_excluded(x)
and filter_names_fun(x)
and is_id_type_ok(x)
)
ranking_pool = [s for s in self.sites if filter_fun_ranking_parents(s)]
sorted_parents = sorted(
ranking_pool, key=lambda x: x.alexa_rank, reverse=reverse
)[:top]
parent_names_lower = {s.name.lower() for s in sorted_parents}
base_names = {s.name for s in sorted_list}
def is_mirror(s) -> bool:
if not s.source or s.name in base_names:
return False
return s.source.lower() in parent_names_lower
mirrors = [s for s in filtered_list if is_mirror(s)]
mirrors.sort(key=lambda x: (x.alexa_rank, x.name))
sorted_list = list(sorted_list) + mirrors
return {site.name: site for site in sorted_list}
@property
def engines(self):
return self._engines
@property
def engines_dict(self):
return {engine.name: engine for engine in self._engines}
def update_site(self, site: MaigretSite) -> "MaigretDatabase":
for i, s in enumerate(self._sites):
if s.name == site.name:
self._sites[i] = site
return self
self._sites.append(site)
return self
def save_to_file(self, filename: str) -> "MaigretDatabase":
if '://' in filename:
return self
db_data = {
"sites": {site.name: site.strip_engine_data().json for site in self._sites},
"engines": {engine.name: engine.json for engine in self._engines},
"tags": self._tags,
}
json_data = json.dumps(db_data, indent=4, ensure_ascii=False)
with open(filename, "w", encoding="utf-8") as f:
f.write(json_data)
return self
def load_from_json(self, json_data: dict) -> "MaigretDatabase":
# Add all of site information from the json file to internal site list.
site_data = json_data.get("sites", {})
engines_data = json_data.get("engines", {})
tags = json_data.get("tags", [])
self._tags += tags
for engine_name in engines_data:
self._engines.append(MaigretEngine(engine_name, engines_data[engine_name]))
for site_name in site_data:
try:
maigret_site = MaigretSite(site_name, site_data[site_name])
engine = site_data[site_name].get("engine")
if engine:
maigret_site.update_from_engine(self.engines_dict[engine])
self._sites.append(maigret_site)
except KeyError as error:
raise ValueError(
f"Problem parsing json content for site {site_name}: "
f"Missing attribute {str(error)}."
)
return self
def load_from_str(self, db_str: "str") -> "MaigretDatabase":
try:
data = json.loads(db_str)
except Exception as error:
raise ValueError(
f"Problem parsing json contents from str"
f"'{db_str[:50]}'...: {str(error)}."
)
return self.load_from_json(data)
def load_from_path(self, path: str) -> "MaigretDatabase":
if '://' in path:
return self.load_from_http(path)
else:
return self.load_from_file(path)
def load_from_http(self, url: str) -> "MaigretDatabase":
is_url_valid = url.startswith("http://") or url.startswith("https://")
if not is_url_valid:
raise FileNotFoundError(f"Invalid data file URL '{url}'.")
import requests
try:
response = requests.get(url=url)
except Exception as error:
raise FileNotFoundError(
f"Problem while attempting to access "
f"data file URL '{url}': "
f"{str(error)}"
)
if response.status_code == 200:
try:
data = response.json()
except Exception as error:
raise ValueError(
f"Problem parsing json contents at " f"'{url}': {str(error)}."
)
else:
raise FileNotFoundError(
f"Bad response while accessing " f"data file URL '{url}'."
)
return self.load_from_json(data)
def load_from_file(self, filename: "str") -> "MaigretDatabase":
try:
with open(filename, "r", encoding="utf-8") as file:
try:
data = json.load(file)
except Exception as error:
raise ValueError(
f"Problem parsing json contents from "
f"file '{filename}': {str(error)}."
)
except FileNotFoundError as error:
raise FileNotFoundError(
f"Problem while attempting to access " f"data file '{filename}'."
) from error
return self.load_from_json(data)
def get_scan_stats(self, sites_dict):
sites = sites_dict or self.sites_dict
found_flags: Dict[str, int] = {}
for _, s in sites.items():
if "presense_flag" in s.stats:
flag = s.stats["presense_flag"]
found_flags[flag] = found_flags.get(flag, 0) + 1
return found_flags
def extract_ids_from_url(self, url: str) -> dict:
results = {}
for s in self._sites:
result = s.extract_id_from_url(url)
if not result:
continue
_id, _type = result
results[_id] = _type
return results
def get_db_stats(self, is_markdown=False):
# Initialize counters
sites_dict = self.sites_dict
urls: Dict[str, int] = {}
tags: Dict[str, int] = {}
engine_total: Dict[str, int] = {}
engine_enabled: Dict[str, int] = {}
disabled_count = 0
message_checks_one_factor = 0
status_checks = 0
# Collect statistics
for site in sites_dict.values():
# Count disabled sites
if site.disabled:
disabled_count += 1
# Count URL types
url_type = site.get_url_template()
urls[url_type] = urls.get(url_type, 0) + 1
# Count check types for enabled sites
if not site.disabled:
if site.check_type == 'message':
if not (site.absence_strs and site.presense_strs):
message_checks_one_factor += 1
elif site.check_type == 'status_code':
status_checks += 1
# Count engines
if site.engine:
engine_total[site.engine] = engine_total.get(site.engine, 0) + 1
if not site.disabled:
engine_enabled[site.engine] = engine_enabled.get(site.engine, 0) + 1
# Count tags
if not site.tags:
tags["NO_TAGS"] = tags.get("NO_TAGS", 0) + 1
for tag in filter(lambda x: not is_country_tag(x), site.tags):
tags[tag] = tags.get(tag, 0) + 1
# Calculate percentages
total_count = len(sites_dict)
enabled_count = total_count - disabled_count
enabled_perc = round(100 * enabled_count / total_count, 2)
checks_perc = round(100 * message_checks_one_factor / enabled_count, 2)
status_checks_perc = round(100 * status_checks / enabled_count, 2)
# Sites with probing and activation (kinda special cases, let's watch them)
site_with_probing = []
site_with_activation = []
for site in sites_dict.values():
def get_site_label(site):
return f"{site.name}{' (disabled)' if site.disabled else ''}"
if site.url_probe:
site_with_probing.append(get_site_label(site))
if site.activation:
site_with_activation.append(get_site_label(site))
# Format output
separator = "\n\n"
output = [
f"Enabled/total sites: {enabled_count}/{total_count} = {enabled_perc}%",
f"Incomplete message checks: {message_checks_one_factor}/{enabled_count} = {checks_perc}% (false positive risks)",
f"Status code checks: {status_checks}/{enabled_count} = {status_checks_perc}% (false positive risks)",
f"False positive risk (total): {checks_perc + status_checks_perc:.2f}%",
f"Sites with probing: {', '.join(sorted(site_with_probing))}",
f"Sites with activation: {', '.join(sorted(site_with_activation))}",
self._format_top_items("profile URLs", urls, 20, is_markdown),
self._format_engine_stats(engine_total, engine_enabled, is_markdown),
self._format_top_items("tags", tags, 20, is_markdown, self._tags),
]
return separator.join(output)
def _format_engine_stats(self, engine_total, engine_enabled, is_markdown):
"""Format per-engine enabled/total counts, sorted by total descending."""
output = "Sites by engine:\n"
for engine, total in sorted(
engine_total.items(), key=lambda x: x[1], reverse=True
):
enabled = engine_enabled.get(engine, 0)
perc = round(100 * enabled / total, 1) if total else 0.0
if is_markdown:
output += f"- `{engine}`: {enabled}/{total} ({perc}%)\n"
else:
output += f"{enabled}/{total} ({perc}%)\t{engine}\n"
return output
def _format_top_items(
self, title, items_dict, limit, is_markdown, valid_items=None
):
"""Helper method to format top items lists"""
output = f"Top {limit} {title}:\n"
for item, count in sorted(items_dict.items(), key=lambda x: x[1], reverse=True)[
:limit
]:
if count == 1:
break
mark = (
" (non-standard)"
if valid_items is not None and item not in valid_items
else ""
)
output += (
f"- ({count})\t`{item}`{mark}\n"
if is_markdown
else f"{count}\t{item}{mark}\n"
)
return output
+624
View File
@@ -0,0 +1,624 @@
import asyncio
import json
import re
import os
import logging
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import urlparse
from aiohttp import ClientSession, TCPConnector
from colorama import Fore, Style
from .activation import import_aiohttp_cookies
from .result import MaigretCheckResult
from .settings import Settings
from .sites import MaigretDatabase, MaigretEngine, MaigretSite
from .utils import get_random_user_agent
from .checking import site_self_check
from .utils import get_match_ratio, generate_random_username
class Submitter:
HEADERS = {
"User-Agent": get_random_user_agent(),
}
SEPARATORS = "\"'\n"
RATIO = 0.6
TOP_FEATURES = 5
URL_RE = re.compile(r"https?://(www\.)?")
def __init__(self, db: MaigretDatabase, settings: Settings, logger, args):
self.settings = settings
self.args = args
self.db = db
self.logger = logger
from aiohttp_socks import ProxyConnector
proxy = self.args.proxy
cookie_jar = None
if args.cookie_file:
if not os.path.exists(args.cookie_file):
logger.error(f"Cookie file {args.cookie_file} does not exist!")
else:
cookie_jar = import_aiohttp_cookies(args.cookie_file)
ssl_context = __import__('ssl').create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = __import__('ssl').CERT_NONE
connector = ProxyConnector.from_url(proxy) if proxy else TCPConnector(ssl=ssl_context)
self.session = ClientSession(
connector=connector, trust_env=True, cookie_jar=cookie_jar
)
async def close(self):
await self.session.close()
async def site_self_check(self, site, semaphore, silent=False):
# Call the general function from the checking.py
changes = await site_self_check(
site=site,
logger=self.logger,
semaphore=semaphore,
db=self.db,
silent=silent,
proxy=self.args.proxy,
cookies=self.args.cookie_file,
# Don't skip errors in submit mode - we need check both false positives/true negatives
skip_errors=False,
cloudflare_bypass=getattr(self, 'cloudflare_bypass', None),
)
return changes
def generate_additional_fields_dialog(self, engine: MaigretEngine, dialog):
fields = {}
if 'urlSubpath' in engine.site.get('url', ''):
msg = (
'Detected engine suppose additional URL subpath using (/forum/, /blog/, etc). '
'Enter in manually if it exists: '
)
subpath = input(msg).strip('/')
if subpath:
fields['urlSubpath'] = f'/{subpath}'
return fields
async def detect_known_engine(
self, url_exists, url_mainpage, session, follow_redirects, headers
) -> Tuple[List[MaigretSite], str]:
session = session or self.session
resp_text, _ = await self.get_html_response_to_compare(
url_exists, session, follow_redirects, headers
)
for engine in self.db.engines:
strs_to_check = engine.__dict__.get("presenseStrs")
if strs_to_check and resp_text:
all_strs_in_response = True
for s in strs_to_check:
if s not in resp_text:
all_strs_in_response = False
sites = []
if all_strs_in_response:
engine_name = engine.__dict__.get("name")
print(f"Detected engine {engine_name} for site {url_mainpage}")
usernames_to_check = self.settings.supposed_usernames
supposed_username = self.extract_username_dialog(url_exists)
if supposed_username:
usernames_to_check = [supposed_username] + usernames_to_check
add_fields = self.generate_additional_fields_dialog(
engine, url_exists
)
for u in usernames_to_check:
site_data = {
"urlMain": url_mainpage,
"name": url_mainpage.split("//")[1].split("/")[0],
"engine": engine_name,
"usernameClaimed": u,
"usernameUnclaimed": "noonewouldeverusethis7",
**add_fields,
}
self.logger.info(site_data)
maigret_site = MaigretSite(
url_mainpage.split("/")[-1], site_data
)
maigret_site.update_from_engine(
self.db.engines_dict[engine_name]
)
sites.append(maigret_site)
return sites, resp_text
return [], resp_text
@staticmethod
def extract_username_dialog(url):
url_parts = url.rstrip("/").split("/")
supposed_username = url_parts[-1].strip('@')
entered_username = input(
f"{Fore.GREEN}[?] Is \"{supposed_username}\" a valid username? If not, write it manually: {Style.RESET_ALL}"
)
return entered_username if entered_username else supposed_username
# TODO: replace with checking.py/SimpleAiohttpChecker call
@staticmethod
async def get_html_response_to_compare(
url: str, session: Optional[ClientSession] = None, redirects=False, headers: Optional[Dict] = None
):
assert session is not None, "session must not be None"
async with session.get(
url, allow_redirects=redirects, headers=headers
) as response:
# Try different encodings or fallback to 'ignore' errors
try:
html_response = await response.text(encoding='utf-8')
except UnicodeDecodeError:
try:
html_response = await response.text(encoding='latin1')
except UnicodeDecodeError:
html_response = await response.text(errors='ignore')
return html_response, response.status
async def check_features_manually(
self,
username: str,
url_exists: str,
cookie_filename="", # TODO: use cookies
session: Optional[ClientSession] = None,
follow_redirects=False,
headers: Optional[dict] = None,
) -> Tuple[Optional[List[str]], Optional[List[str]], str, str]:
random_username = generate_random_username()
url_of_non_existing_account = url_exists.lower().replace(
username.lower(), random_username
)
try:
session = session or self.session
first_html_response, first_status = await self.get_html_response_to_compare(
url_exists, session, follow_redirects, headers
)
second_html_response, second_status = (
await self.get_html_response_to_compare(
url_of_non_existing_account, session, follow_redirects, headers
)
)
await session.close()
except Exception as e:
self.logger.error(
f"Error while getting HTTP response for username {username}: {e}",
exc_info=True,
)
return None, None, str(e), random_username
self.logger.info(f"URL with existing account: {url_exists}")
self.logger.info(
f"HTTP response status for URL with existing account: {first_status}"
)
self.logger.info(
f"HTTP response length URL with existing account: {len(first_html_response)}"
)
self.logger.debug(first_html_response)
self.logger.info(f"URL with existing account: {url_of_non_existing_account}")
self.logger.info(
f"HTTP response status for URL with non-existing account: {second_status}"
)
self.logger.info(
f"HTTP response length URL with non-existing account: {len(second_html_response)}"
)
self.logger.debug(second_html_response)
# TODO: filter by errors, move to dialog function
if (
"/cdn-cgi/challenge-platform" in first_html_response
or "\t\t\t\tnow: " in first_html_response
or "Sorry, you have been blocked" in first_html_response
):
self.logger.info("Cloudflare detected, skipping")
return None, None, "Cloudflare detected, skipping", random_username
tokens_a = set(re.split(f'[{self.SEPARATORS}]', first_html_response))
tokens_b = set(re.split(f'[{self.SEPARATORS}]', second_html_response))
a_minus_b: List[str] = [x.strip('\\') for x in tokens_a.difference(tokens_b)]
b_minus_a: List[str] = [x.strip('\\') for x in tokens_b.difference(tokens_a)]
# Filter out strings containing usernames
a_minus_b = [s for s in a_minus_b if username.lower() not in s.lower()]
b_minus_a = [s for s in b_minus_a if random_username.lower() not in s.lower()]
def filter_tokens(token: str, html_response: str) -> bool:
is_in_html = token in html_response
is_long_str = len(token) >= 50
is_number = re.match(r'^\d\.?\d+$', token) or re.match(r':^\d+$', token)
is_whitelisted_number = token in ['200', '404', '403']
return not (
is_in_html or is_long_str or (is_number and not is_whitelisted_number)
)
a_minus_b = list(
filter(lambda t: filter_tokens(t, second_html_response), a_minus_b)
)
b_minus_a = list(
filter(lambda t: filter_tokens(t, first_html_response), b_minus_a)
)
if len(a_minus_b) == len(b_minus_a) == 0:
return (
None,
None,
"HTTP responses for pages with existing and non-existing accounts are the same",
random_username,
)
match_fun = get_match_ratio(self.settings.presence_strings)
presence_list = sorted(a_minus_b, key=match_fun, reverse=True)[
: self.TOP_FEATURES
]
absence_list = sorted(b_minus_a, key=match_fun, reverse=True)[
: self.TOP_FEATURES
]
self.logger.info(f"Detected presence features: {presence_list}")
self.logger.info(f"Detected absence features: {absence_list}")
return presence_list, absence_list, "Found", random_username
async def add_site(self, site):
sem = asyncio.Semaphore(1)
print(
f"{Fore.BLUE}{Style.BRIGHT}[*] Adding site {site.name}, let's check it...{Style.RESET_ALL}"
)
result = await self.site_self_check(site, sem)
if result["disabled"]:
print(f"Checks failed for {site.name}, please, verify them manually.")
return {
"valid": False,
"reason": "checks_failed",
}
while True:
print("\nAvailable fields to edit:")
editable_fields = {
'1': 'name',
'2': 'tags',
'3': 'url',
'4': 'url_main',
'5': 'username_claimed',
'6': 'username_unclaimed',
'7': 'presense_strs',
'8': 'absence_strs',
}
for num, field in editable_fields.items():
current_value = getattr(site, field)
print(f"{num}. {field} (current: {current_value})")
print("0. finish editing")
print("10. reject and block domain")
print("11. invalid params, remove")
choice = input("\nSelect field number to edit (0-8): ").strip()
if choice == '0':
break
if choice == '10':
return {
"valid": False,
"reason": "manual block",
}
if choice == '11':
return {
"valid": False,
"reason": "remove",
}
if choice in editable_fields:
field = editable_fields[choice]
current_value = getattr(site, field)
new_value = input(
f"Enter new value for {field} (current: {current_value}): "
).strip()
if field in ['tags', 'presense_strs', 'absence_strs']:
new_value = list(map(str.strip, new_value.split(','))) # type: ignore[assignment]
if new_value:
setattr(site, field, new_value)
print(f"Updated {field} to: {new_value}")
self.logger.info(site.json)
self.db.update_site(site)
return {
"valid": True,
}
async def dialog(self, url_exists, cookie_file):
"""
An implementation of the submit mode:
- User provides a URL of a existing social media account
- Maigret tries to detect the site engine and understand how to check
for account presence with HTTP responses analysis
- If detection succeeds, Maigret generates a new site entry/replace old one in the database
"""
old_site = None
additional_options_enabled = self.logger.level in (
logging.DEBUG,
logging.WARNING,
)
domain_raw = self.URL_RE.sub("", url_exists).strip().strip("/")
domain_raw = domain_raw.split("/")[0]
self.logger.info('Domain is %s', domain_raw)
# check for existence
domain_re = re.compile(
r'://(www\.)?' + re.escape(domain_raw) + r'(/|$)'
)
matched_sites = list(
filter(
lambda x: domain_re.search(x.url_main + x.url), self.db.sites
)
)
if matched_sites:
# TODO: update the existing site
print(
f"{Fore.YELLOW}[!] Sites with domain \"{domain_raw}\" already exists in the Maigret database!{Style.RESET_ALL}"
)
site_status = lambda s: "(disabled)" if s.disabled else ""
url_block = lambda s: f"\n\t{s.url_main}\n\t{s.url}"
print(
"\n".join(
[
f"{site.name} {site_status(site)}{url_block(site)}"
for site in matched_sites
]
)
)
if (
input(
f"{Fore.GREEN}[?] Do you want to continue? [yN] {Style.RESET_ALL}"
).lower()
in "n"
):
return False
site_names = [site.name for site in matched_sites]
site_name = (
input(
f"{Fore.GREEN}[?] Which site do you want to update in case of success? 1st by default. [{', '.join(site_names)}] {Style.RESET_ALL}"
)
or matched_sites[0].name
)
old_site = next(
(site for site in matched_sites if site.name == site_name), None
)
if old_site is None:
print(
f'{Fore.RED}[!] Site "{site_name}" not found in the matched list. Proceeding without updating an existing site.{Style.RESET_ALL}'
)
else:
print(
f'{Fore.GREEN}[+] We will update site "{old_site.name}" in case of success.{Style.RESET_ALL}'
)
# Check if the site check is ordinary or not
if old_site and (old_site.url_probe or old_site.activation):
skip = input(
f"{Fore.RED}[!] The site check depends on activation / probing mechanism! Consider to update it manually. Continue? [yN]{Style.RESET_ALL}"
)
if skip.lower() in ['n', '']:
return False
# TODO: urlProbe support
# TODO: activation support
parsed = urlparse(url_exists)
url_mainpage = f"{parsed.scheme}://{parsed.netloc}"
# headers update
custom_headers = dict(self.HEADERS)
while additional_options_enabled:
header_key = input(
f'{Fore.GREEN}[?] Specify custom header if you need or just press Enter to skip. Header name: {Style.RESET_ALL}'
)
if not header_key:
break
header_value = input(f'{Fore.GREEN}[?] Header value: {Style.RESET_ALL}')
custom_headers[header_key.strip()] = header_value.strip()
# redirects settings update
redirects = False
if additional_options_enabled:
redirects = (
'y'
in input(
f'{Fore.GREEN}[?] Should we do redirects automatically? [yN] {Style.RESET_ALL}'
).lower()
)
print('Detecting site engine, please wait...')
sites: List[MaigretSite] = []
text = None
try:
sites, text = await self.detect_known_engine(
url_exists,
url_exists,
session=None,
follow_redirects=redirects,
headers=custom_headers,
)
except KeyboardInterrupt:
print('Engine detect process is interrupted.')
if not sites:
print("Unable to detect site engine, lets generate checking features")
supposed_username = self.extract_username_dialog(url_exists)
self.logger.info(f"Supposed username: {supposed_username}")
# TODO: pass status_codes
# check it here and suggest to enable / auto-enable redirects
presence_list, absence_list, status, non_exist_username = (
await self.check_features_manually(
username=supposed_username,
url_exists=url_exists,
cookie_filename=cookie_file,
follow_redirects=redirects,
headers=custom_headers,
)
)
if status == "Found":
site_data = {
"absenceStrs": absence_list,
"presenseStrs": presence_list,
"url": url_exists.replace(supposed_username, '{username}'),
"urlMain": url_mainpage,
"usernameClaimed": supposed_username,
"usernameUnclaimed": non_exist_username,
"headers": custom_headers,
"checkType": "message",
}
self.logger.info(json.dumps(site_data, indent=4))
if custom_headers != self.HEADERS:
site_data['headers'] = custom_headers
site = MaigretSite(url_mainpage.split("/")[-1], site_data)
sites.append(site)
else:
print(
f"{Fore.RED}[!] The check for site failed! Reason: {status}{Style.RESET_ALL}"
)
return False
self.logger.debug(sites[0].__dict__)
sem = asyncio.Semaphore(1)
print(f"{Fore.GREEN}[*] Checking, please wait...{Style.RESET_ALL}")
found = False
chosen_site = None
for s in sites:
chosen_site = s
result = await self.site_self_check(s, sem)
if not result["disabled"]:
found = True
break
assert chosen_site is not None, "No sites to check"
if not found:
print(
f"{Fore.RED}[!] The check for site '{chosen_site.name}' failed!{Style.RESET_ALL}"
)
print(
"Try to run this mode again and increase features count or choose others."
)
self.logger.debug(json.dumps(chosen_site.json))
return False
else:
if (
input(
f"{Fore.GREEN}[?] Site {chosen_site.name} successfully checked. Do you want to save it in the Maigret DB? [Yn] {Style.RESET_ALL}"
)
.lower()
.strip("y")
):
return False
if self.args.verbose:
self.logger.info(
"Verbose mode is enabled, additional settings are available"
)
source = input(
f"{Fore.GREEN}[?] Name the source site if it is mirror: {Style.RESET_ALL}"
)
if source:
chosen_site.source = source
default_site_name = old_site.name if old_site else chosen_site.name
new_name = (
input(
f"{Fore.GREEN}[?] Change site name if you want [{default_site_name}]: {Style.RESET_ALL}"
)
or default_site_name
)
if new_name != default_site_name:
self.logger.info(f"New site name is {new_name}")
chosen_site.name = new_name
default_tags_str = ""
if old_site:
default_tags_str = f' [{", ".join(old_site.tags)}]'
new_tags = input(
f"{Fore.GREEN}[?] Site tags{default_tags_str}: {Style.RESET_ALL}"
)
if new_tags:
chosen_site.tags = list(map(str.strip, new_tags.split(',')))
else:
chosen_site.tags = []
self.logger.info(f"Site tags are: {', '.join(chosen_site.tags)}")
self.logger.info(chosen_site.json)
stripped_site = chosen_site.strip_engine_data()
self.logger.info(stripped_site.json)
if old_site:
# Update old site with new values and log changes
fields_to_check = {
'url': 'URL',
'url_main': 'Main URL',
'username_claimed': 'Username claimed',
'username_unclaimed': 'Username unclaimed',
'check_type': 'Check type',
'presense_strs': 'Presence strings',
'absence_strs': 'Absence strings',
'tags': 'Tags',
'source': 'Source',
'headers': 'Headers',
}
for field, display_name in fields_to_check.items():
old_value = getattr(old_site, field)
new_value = getattr(stripped_site, field)
if field == 'tags' and not new_tags:
continue
if str(old_value) != str(new_value):
print(
f"{Fore.YELLOW}[*] '{display_name}' updated: {Fore.RED}{old_value} {Fore.YELLOW}to {Fore.GREEN}{new_value}{Style.RESET_ALL}"
)
old_site.__dict__[field] = new_value
# update the site
final_site = old_site if old_site else stripped_site
self.db.update_site(final_site)
# save the db in file
if self.args.db_file != self.settings.sites_db_path:
print(
f"{Fore.GREEN}[+] Maigret DB is saved to {self.args.db}.{Style.RESET_ALL}"
)
self.db.save_to_file(self.args.db)
return True
+196
View File
@@ -0,0 +1,196 @@
# coding: utf8
import ast
import difflib
import re
import random
import string
from typing import Any
from markupsafe import Markup, escape
DEFAULT_USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
]
class CaseConverter:
@staticmethod
def camel_to_snake(camelcased_string: str) -> str:
return re.sub(r"(?<!^)(?=[A-Z])", "_", camelcased_string).lower()
@staticmethod
def snake_to_camel(snakecased_string: str) -> str:
formatted = "".join(word.title() for word in snakecased_string.split("_"))
result = formatted[0].lower() + formatted[1:]
return result
@staticmethod
def snake_to_title(snakecased_string: str) -> str:
words = snakecased_string.split("_")
words[0] = words[0].title()
return " ".join(words)
def is_country_tag(tag: str) -> bool:
"""detect if tag represent a country"""
return bool(re.match("^([a-zA-Z]){2}$", tag)) or tag == "global"
def enrich_link_str(link: str) -> str:
link = link.strip()
if link.startswith("www.") or (link.startswith("http") and "//" in link):
# escape the link to avoid XSS; Markup keeps the <a> tag itself intact
# under the template's autoescaping.
safe = escape(link)
return Markup(f'<a class="auto-link" href="{safe}">{safe}</a>')
return link
class URLMatcher:
_HTTP_URL_RE_STR = r"^https?://(www\.|m\.)?(.+)$"
HTTP_URL_RE = re.compile(_HTTP_URL_RE_STR)
UNSAFE_SYMBOLS = ".?"
@classmethod
def extract_main_part(self, url: str) -> str:
match = self.HTTP_URL_RE.search(url)
if match and match.group(2):
return match.group(2).rstrip("/")
return ""
@classmethod
def make_profile_url_regexp(self, url: str, username_regexp: str = ""):
url_main_part = self.extract_main_part(url)
for c in self.UNSAFE_SYMBOLS:
url_main_part = url_main_part.replace(c, f"\\{c}")
prepared_username_regexp = (username_regexp or ".+?").lstrip('^').rstrip('$')
url_regexp = url_main_part.replace(
"{username}", f"({prepared_username_regexp})"
)
regexp_str = self._HTTP_URL_RE_STR.replace("(.+)", url_regexp)
return re.compile(regexp_str, re.IGNORECASE)
def ascii_data_display(data: str) -> Any:
try:
return ast.literal_eval(data)
except (ValueError, SyntaxError):
return data
def get_dict_ascii_tree(items, prepend="", new_line=True):
new_result = b'\xe2\x94\x9c'.decode()
h_line = b'\xe2\x94\x80'.decode()
last_result = b'\xe2\x94\x94'.decode()
skip_result = b'\xe2\x94\x82'.decode()
text = ""
for num, item in enumerate(items):
box_symbol = (
new_result + h_line if num != len(items) - 1 else last_result + h_line
)
if isinstance(item, tuple):
field_name, field_value = item
if field_value.startswith("['"):
is_last_item = num == len(items) - 1
prepend_symbols = " " * 3 if is_last_item else f" {skip_result} "
data = ascii_data_display(field_value)
field_value = get_dict_ascii_tree(data, prepend_symbols)
text += f"\n{prepend}{box_symbol}{field_name}: {field_value}"
else:
text += f"\n{prepend}{box_symbol} {item}"
if not new_line:
text = text[1:]
return text
def get_random_user_agent():
return random.choice(DEFAULT_USER_AGENTS)
def get_match_ratio(base_strs: list):
def get_match_inner(s: str):
return round(
max(
[
difflib.SequenceMatcher(a=s.lower(), b=s2.lower()).ratio()
for s2 in base_strs
]
),
2,
)
return get_match_inner
def generate_random_username():
return ''.join(random.choices(string.ascii_lowercase, k=10))
def is_plausible_username(value: Any) -> bool:
"""Reject obviously non-username strings extracted from sites' identity data.
Extractor schemes occasionally populate fields named like ``*_username``
with URLs (e.g. ``instagram_username`` -> ``https://instagram.com/X``) or
emails (e.g. ``your_username`` -> ``user@example.com``). Feeding such a
value back into a site URL template produces broken requests on every
subsequent site, which manifests as a cascade of false errors and the
"wrong username" symptom in #1403.
"""
if not isinstance(value, str):
return False
s = value.strip()
if not s:
return False
if "://" in s or s.startswith(("http://", "https://", "www.", "//")):
return False
if "/" in s:
return False
if any(c.isspace() for c in s):
return False
if "@" in s and "." in s:
return False
return True
def extract_usernames(info, logger):
"""Extract plausible usernames from socid_extractor results.
Supports single username fields (e.g. ``profile_username``) and
serialized username lists (e.g. ``other_usernames``). Invalid values
such as URLs or emails are rejected via ``is_plausible_username``.
"""
results = []
for key, value in info.items():
if "username" in key and "usernames" not in key:
if is_plausible_username(value):
results.append(value)
else:
logger.debug(
f"Rejected non-username value extracted "
f"under key {key!r}: {value!r}"
)
elif "usernames" in key:
try:
parsed = ast.literal_eval(value)
if isinstance(parsed, list):
for item in parsed:
if is_plausible_username(item):
results.append(item)
else:
logger.debug(
f"Rejected non-username item "
f"from list under key {key!r}: {item!r}"
)
except Exception as e:
logger.warning(e)
return results
+378
View File
@@ -0,0 +1,378 @@
from flask import (
Flask,
render_template,
request,
send_from_directory,
Response,
flash,
redirect,
url_for,
)
from werkzeug.exceptions import NotFound
import logging
import os
import asyncio
from datetime import datetime
from threading import Thread
from typing import Any, Dict
import maigret
import maigret.settings
from maigret.checking import build_cloudflare_bypass_config
from maigret.sites import MaigretDatabase
from maigret.report import generate_report_context
app = Flask(__name__)
# Use environment variable for secret key, generate random one if not set
app.secret_key = os.getenv('FLASK_SECRET_KEY', os.urandom(24).hex())
# add background job tracking
background_jobs: Dict[str, Any] = {}
job_results = {}
# Configuration
app.config["MAIGRET_DB_FILE"] = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'resources', 'data.json')
app.config["COOKIES_FILE"] = "cookies.txt"
app.config["UPLOAD_FOLDER"] = 'uploads'
app.config["REPORTS_FOLDER"] = os.path.abspath('/tmp/maigret_reports')
def setup_logger(log_level, name):
logger = logging.getLogger(name)
logger.setLevel(log_level)
return logger
async def maigret_search(username, options):
logger = setup_logger(logging.WARNING, 'maigret')
try:
settings = maigret.settings.Settings()
settings.load()
cf_bypass_config = build_cloudflare_bypass_config(settings)
if cf_bypass_config:
modules_summary = ", ".join(
f"{m.get('name', m.get('method'))}({m.get('url')})"
for m in cf_bypass_config["modules"]
)
logger.info(
f"Cloudflare webgate active: triggers={cf_bypass_config['trigger_protection']}, "
f"modules=[{modules_summary}]"
)
db = MaigretDatabase().load_from_path(app.config["MAIGRET_DB_FILE"])
top_sites = int(options.get('top_sites') or 500)
if options.get('all_sites'):
top_sites = 999999999 # effectively all
tags = options.get('tags', [])
excluded_tags = options.get('excluded_tags', [])
site_list = options.get('site_list', [])
logger.info(f"Filtering sites by tags: {tags}, excluded: {excluded_tags}")
sites = db.ranked_sites_dict(
top=top_sites,
tags=tags,
excluded_tags=excluded_tags,
names=site_list,
disabled=False,
id_type='username',
)
logger.info(f"Found {len(sites)} sites matching the tag criteria")
results = await maigret.search(
username=username,
site_dict=sites,
timeout=int(options.get('timeout', 30)),
logger=logger,
id_type='username',
cookies=app.config["COOKIES_FILE"] if options.get('use_cookies') else None,
is_parsing_enabled=(not options.get('disable_extracting', False)),
recursive_search_enabled=(
not options.get('disable_recursive_search', False)
),
check_domains=options.get('with_domains', False),
proxy=options.get('proxy', None),
tor_proxy=options.get('tor_proxy', None),
i2p_proxy=options.get('i2p_proxy', None),
cloudflare_bypass=cf_bypass_config,
)
return results
except Exception as e:
logger.error(f"Error during search: {str(e)}")
raise
async def search_multiple_usernames(usernames, options):
results = []
for username in usernames:
try:
search_results = await maigret_search(username.strip(), options)
results.append((username.strip(), 'username', search_results))
except Exception as e:
logging.error(f"Error searching username {username}: {str(e)}")
return results
def sanitize_username_for_path(username: str) -> str:
"""Remove path separators and dangerous components from username for safe file path usage."""
# Replace path separators and null bytes
sanitized = username.replace('/', '_').replace('\\', '_').replace('\0', '_')
# Remove . and .. components
sanitized = sanitized.strip('.')
# If empty after sanitization, use a fallback
return sanitized or '_'
def process_search_task(usernames, options, timestamp):
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
general_results = loop.run_until_complete(
search_multiple_usernames(usernames, options)
)
os.makedirs(app.config["REPORTS_FOLDER"], exist_ok=True)
session_folder = os.path.join(
app.config["REPORTS_FOLDER"], f"search_{timestamp}"
)
os.makedirs(session_folder, exist_ok=True)
graph_path = os.path.join(session_folder, "combined_graph.html")
maigret.report.save_graph_report(
graph_path,
general_results,
MaigretDatabase().load_from_path(app.config["MAIGRET_DB_FILE"]),
)
individual_reports = []
for username, id_type, results in general_results:
safe_username = sanitize_username_for_path(username)
report_base = os.path.join(session_folder, f"report_{safe_username}")
csv_path = f"{report_base}.csv"
json_path = f"{report_base}.json"
pdf_path = f"{report_base}.pdf"
html_path = f"{report_base}.html"
context = generate_report_context(general_results)
maigret.report.save_csv_report(csv_path, username, results)
maigret.report.save_json_report(
json_path, username, results, report_type='ndjson'
)
maigret.report.save_pdf_report(pdf_path, context)
maigret.report.save_html_report(html_path, context)
claimed_profiles = []
for site_name, site_data in results.items():
if (
site_data.get('status')
and site_data['status'].status
== maigret.result.MaigretCheckStatus.CLAIMED
):
claimed_profiles.append(
{
'site_name': site_name,
'url': site_data.get('url_user', ''),
'tags': (
site_data.get('status').tags
if site_data.get('status')
else []
),
}
)
individual_reports.append(
{
'username': username,
'csv_file': os.path.join(
f"search_{timestamp}", f"report_{safe_username}.csv"
),
'json_file': os.path.join(
f"search_{timestamp}", f"report_{safe_username}.json"
),
'pdf_file': os.path.join(
f"search_{timestamp}", f"report_{safe_username}.pdf"
),
'html_file': os.path.join(
f"search_{timestamp}", f"report_{safe_username}.html"
),
'claimed_profiles': claimed_profiles,
}
)
# save results and mark job as complete using timestamp as key
job_results[timestamp] = {
'status': 'completed',
'session_folder': f"search_{timestamp}",
'graph_file': os.path.join(f"search_{timestamp}", "combined_graph.html"),
'usernames': usernames,
'individual_reports': individual_reports,
}
except Exception as e:
logging.error(f"Error in search task for timestamp {timestamp}: {str(e)}")
job_results[timestamp] = {'status': 'failed', 'error': str(e)}
finally:
background_jobs[timestamp]['completed'] = True
@app.route('/')
def index():
# load site data for autocomplete
db = MaigretDatabase().load_from_path(app.config["MAIGRET_DB_FILE"])
site_options = []
for site in db.sites:
# add main site name
site_options.append(site.name)
# add URL if different from name
if site.url_main and site.url_main not in site_options:
site_options.append(site.url_main)
# sort and deduplicate
site_options = sorted(set(site_options))
return render_template('index.html', site_options=site_options)
# Modified search route
@app.route('/search', methods=['POST'])
def search():
usernames_input = request.form.get('usernames', '').strip()
if not usernames_input:
flash('At least one username is required', 'danger')
return redirect(url_for('index'))
usernames = [
u.strip() for u in usernames_input.replace(',', ' ').split() if u.strip()
]
# Create timestamp for this search session
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Get selected tags - ensure it's a list
selected_tags = request.form.getlist('tags')
excluded_tags = request.form.getlist('excluded_tags')
logging.info(f"Selected tags: {selected_tags}, Excluded tags: {excluded_tags}")
options = {
'top_sites': request.form.get('top_sites') or '500',
'timeout': request.form.get('timeout') or '30',
'use_cookies': 'use_cookies' in request.form,
'all_sites': 'all_sites' in request.form,
'disable_recursive_search': 'disable_recursive_search' in request.form,
'disable_extracting': 'disable_extracting' in request.form,
'with_domains': 'with_domains' in request.form,
'proxy': request.form.get('proxy', None) or None,
'tor_proxy': request.form.get('tor_proxy', None) or None,
'i2p_proxy': request.form.get('i2p_proxy', None) or None,
'permute': 'permute' in request.form,
'tags': selected_tags, # Pass selected tags as a list
'excluded_tags': excluded_tags, # Pass excluded tags as a list
'site_list': [
s.strip() for s in request.form.get('site', '').split(',') if s.strip()
],
}
logging.info(
f"Starting search for usernames: {usernames} with tags: {selected_tags}, excluded: {excluded_tags}"
)
# Start background job
background_jobs[timestamp] = {
'completed': False,
'thread': Thread(
target=process_search_task, args=(usernames, options, timestamp)
),
}
background_jobs[timestamp]['thread'].start() # type: ignore[union-attr]
return redirect(url_for('status', timestamp=timestamp))
@app.route('/status/<timestamp>')
def status(timestamp):
logging.info(f"Status check for timestamp: {timestamp}")
# Validate timestamp
if timestamp not in background_jobs:
flash('Invalid search session.', 'danger')
logging.error(f"Invalid search session: {timestamp}")
return redirect(url_for('index'))
# Check if job is completed
if background_jobs[timestamp]['completed']:
result = job_results.get(timestamp)
if not result:
flash('No results found for this search session.', 'warning')
logging.error(f"No results found for completed session: {timestamp}")
return redirect(url_for('index'))
if result['status'] == 'completed':
# Note: use the session_folder from the results to redirect
return redirect(url_for('results', session_id=result['session_folder']))
else:
error_msg = result.get('error', 'Unknown error occurred.')
flash(f'Search failed: {error_msg}', 'danger')
logging.error(f"Search failed for session {timestamp}: {error_msg}")
return redirect(url_for('index'))
# If job is still running, show a status page
return render_template('status.html', timestamp=timestamp)
@app.route('/results/<session_id>')
def results(session_id):
# Find completed results that match this session_folder
result_data = next(
(
r
for r in job_results.values()
if r.get('status') == 'completed' and r['session_folder'] == session_id
),
None,
)
if not result_data:
flash('No results found for this session ID.', 'danger')
logging.error(f"Results for session {session_id} not found in job_results.")
return redirect(url_for('index'))
return render_template(
'results.html',
usernames=result_data['usernames'],
graph_file=result_data['graph_file'],
individual_reports=result_data['individual_reports'],
timestamp=session_id.replace('search_', ''),
)
@app.route('/reports/<path:filename>')
def download_report(filename):
reports_root = app.config["REPORTS_FOLDER"]
os.makedirs(reports_root, exist_ok=True)
try:
return send_from_directory(reports_root, filename)
except NotFound:
return "File not found", 404
except Exception as e:
logging.error(f"Error serving file {filename}: {str(e)}")
return "File not found", 404
if __name__ == '__main__':
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)
debug_mode = os.getenv('FLASK_DEBUG', 'False').lower() in ['true', '1', 't']
# Host configuration: secure by default
# Use 127.0.0.1 for local development, 0.0.0.0 only if explicitly set
host = os.getenv('FLASK_HOST', '127.0.0.1')
port = int(os.getenv('FLASK_PORT', '5000'))
app.run(host=host, port=port, debug=debug_mode)
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

+118
View File
@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maigret Web Interface</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.main-container {
flex: 1;
padding-top: 2rem;
}
.form-container {
max-width: auto;
margin: auto;
padding-bottom: 2rem;
}
[data-bs-theme="dark"] {
--bs-body-bg: #212529;
--bs-body-color: #dee2e6;
}
.header {
padding: 1rem 0;
margin-bottom: 2rem;
border-bottom: 1px solid var(--bs-border-color);
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.logo-container {
display: flex;
align-items: center;
gap: 1rem;
}
.logo {
height: 40px;
width: auto;
}
.footer {
margin-top: auto;
padding: 1rem 0;
text-align: center;
border-top: 1px solid var(--bs-border-color);
font-size: 0.9rem;
}
.footer a {
color: inherit;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="header">
<div class="container">
<div class="header-content">
<div class="logo-container">
<img src="{{ url_for('static', filename='maigret.png') }}" alt="Maigret Logo" class="logo">
<h1 class="h4 mb-0">Maigret Web Interface</h1>
</div>
<button class="btn btn-outline-secondary" id="theme-toggle">
Toggle Dark/Light Mode
</button>
</div>
</div>
</div>
<div class="main-container">
<div class="container">
{% block content %}{% endblock %}
</div>
</div>
<footer class="footer">
<div class="container">
<p class="mb-0">
Powered by <a href="https://github.com/soxoj/maigret" target="_blank">Maigret</a> |
Licensed under <a href="https://github.com/soxoj/maigret/blob/main/LICENSE" target="_blank">MIT
License</a>
</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById('theme-toggle').addEventListener('click', function () {
const html = document.documentElement;
if (html.getAttribute('data-bs-theme') === 'dark') {
html.setAttribute('data-bs-theme', 'light');
} else {
html.setAttribute('data-bs-theme', 'dark');
}
});
</script>
</body>
</html>
+520
View File
@@ -0,0 +1,520 @@
{% extends "base.html" %}
{% block content %}
<style>
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 15px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
}
.tag {
display: inline-block;
padding: 5px 10px;
border-radius: 15px;
background-color: #dc3545;
color: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
user-select: none;
}
.tag.selected {
background-color: #28a745;
}
.tag.excluded {
background-color: #343a40;
text-decoration: line-through;
}
.tag:hover {
transform: translateY(-2px);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.hidden-select {
display: none !important;
}
.site-input-container {
position: relative;
}
.site-input {
width: 100%;
}
.selected-sites {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 10px 0;
}
.selected-site {
background-color: #214e7b;
padding: 2px 8px;
border-radius: 12px;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 5px;
}
.remove-site {
cursor: pointer;
color: #dc3545;
font-weight: bold;
}
.section-header {
cursor: pointer;
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
margin-bottom: 0.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-content {
padding: 1rem;
display: none;
}
.section-content.show {
display: block;
}
.chevron::after {
content: '▼';
transition: transform 0.2s;
}
.chevron.collapsed::after {
transform: rotate(-90deg);
}
.main-search-section {
background: rgba(255, 255, 255, 0.03);
padding: 2rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.search-button {
width: 100%;
padding: 1rem;
font-size: 1.2rem;
margin-top: 2rem;
}
</style>
<div class="form-container">
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
<form method="POST" action="{{ url_for('search') }}" class="mb-4">
<!-- Main Search Section -->
<div class="main-search-section">
<div class="mb-4">
<label for="usernames" class="form-label h5">Usernames to Search</label>
<textarea class="form-control" id="usernames" name="usernames" rows="3" required
placeholder="Enter one or more usernames (separated by spaces or commas)..."></textarea>
</div>
<div class="row align-items-center">
<div class="col-md-6">
<label for="top_sites" class="form-label">Number of Sites</label>
<input type="number" class="form-control" id="top_sites" name="top_sites" min="1" max="10000"
placeholder="Default: 500">
</div>
<div class="col-md-6">
<label for="timeout" class="form-label">Timeout (seconds)</label>
<input type="number" class="form-control" id="timeout" name="timeout" min="1"
placeholder="Default: 30">
</div>
<div class="col-12 mt-3">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="all_sites" name="all_sites"
onchange="document.getElementById('top_sites').disabled = this.checked;">
<label class="form-check-label" for="all_sites">Search All Sites</label>
</div>
</div>
</div>
</div>
<!-- Filters Section -->
<div class="mb-4">
<div class="section-header" onclick="toggleSection('filters')">
<h5 class="mb-0">Filters</h5>
<span class="chevron"></span>
</div>
<div id="filters" class="section-content">
<div class="mb-3 site-input-container">
<label for="site" class="form-label">Specify Sites (Optional)</label>
<input type="text" class="form-control site-input" id="siteInput"
placeholder="Type to search for sites..." list="siteOptions">
<input type="hidden" id="site" name="site">
<datalist id="siteOptions">
{% for site in site_options %}
<option value="{{ site }}">
{% endfor %}
</datalist>
<div class="selected-sites" id="selectedSites"></div>
</div>
<div class="mb-3">
<label class="form-label">Tags (click to cycle: include → exclude → neutral)</label>
<div class="mb-2">
<small class="text-muted">
<span style="display:inline-block;width:12px;height:12px;background:#28a745;border-radius:50%;"></span> Included (whitelist)
&nbsp;&nbsp;
<span style="display:inline-block;width:12px;height:12px;background:#343a40;border-radius:50%;"></span> Excluded (blacklist)
&nbsp;&nbsp;
<span style="display:inline-block;width:12px;height:12px;background:#dc3545;border-radius:50%;"></span> Neutral
</small>
</div>
<div class="tag-cloud" id="tagCloud"></div>
<select multiple class="hidden-select" id="tags" name="tags">
<option value="gaming">Gaming</option>
<option value="coding">Coding</option>
<option value="photo">Photo</option>
<option value="music">Music</option>
<option value="blog">Blog</option>
<option value="finance">Finance</option>
<option value="freelance">Freelance</option>
<option value="dating">Dating</option>
<option value="tech">Tech</option>
<option value="forum">Forum</option>
<option value="porn">Porn</option>
<option value="erotic">Erotic</option>
<option value="webcam">Webcam</option>
<option value="video">Video</option>
<option value="movies">Movies</option>
<option value="hacking">Hacking</option>
<option value="art">Art</option>
<option value="discussion">Discussion</option>
<option value="sharing">Sharing</option>
<option value="writing">Writing</option>
<option value="wiki">Wiki</option>
<option value="business">Business</option>
<option value="shopping">Shopping</option>
<option value="sport">Sport</option>
<option value="books">Books</option>
<option value="news">News</option>
<option value="documents">Documents</option>
<option value="travel">Travel</option>
<option value="maps">Maps</option>
<option value="hobby">Hobby</option>
<option value="apps">Apps</option>
<option value="classified">Classified</option>
<option value="career">Career</option>
<option value="geosocial">Geosocial</option>
<option value="streaming">Streaming</option>
<option value="education">Education</option>
<option value="networking">Networking</option>
<option value="torrent">Torrent</option>
<option value="science">Science</option>
<option value="medicine">Medicine</option>
<option value="reading">Reading</option>
<option value="stock">Stock</option>
<option value="messaging">Messaging</option>
<option value="trading">Trading</option>
<option value="links">Links</option>
<option value="fashion">Fashion</option>
<option value="tasks">Tasks</option>
<option value="military">Military</option>
<option value="auto">Auto</option>
<option value="gambling">Gambling</option>
<option value="cybercriminal">Cybercriminal</option>
<option value="review">Review</option>
<option value="bookmarks">Bookmarks</option>
<option value="design">Design</option>
<option value="tor">Tor</option>
<option value="i2p">I2P</option>
<option value="q&a">Q&A</option>
<option value="crypto">Crypto</option>
<option value="ai">AI</option>
<!-- Country tags -->
<option value="ae" data-group="country">AE - United Arab Emirates</option>
<option value="ao" data-group="country">AO - Angola</option>
<option value="ar" data-group="country">AR - Argentina</option>
<option value="at" data-group="country">AT - Austria</option>
<option value="au" data-group="country">AU - Australia</option>
<option value="az" data-group="country">AZ - Azerbaijan</option>
<option value="bd" data-group="country">BD - Bangladesh</option>
<option value="be" data-group="country">BE - Belgium</option>
<option value="bg" data-group="country">BG - Bulgaria</option>
<option value="br" data-group="country">BR - Brazil</option>
<option value="by" data-group="country">BY - Belarus</option>
<option value="ca" data-group="country">CA - Canada</option>
<option value="ch" data-group="country">CH - Switzerland</option>
<option value="cl" data-group="country">CL - Chile</option>
<option value="cn" data-group="country">CN - China</option>
<option value="co" data-group="country">CO - Colombia</option>
<option value="cr" data-group="country">CR - Costa Rica</option>
<option value="cz" data-group="country">CZ - Czechia</option>
<option value="de" data-group="country">DE - Germany</option>
<option value="dk" data-group="country">DK - Denmark</option>
<option value="dz" data-group="country">DZ - Algeria</option>
<option value="ee" data-group="country">EE - Estonia</option>
<option value="eg" data-group="country">EG - Egypt</option>
<option value="es" data-group="country">ES - Spain</option>
<option value="eu" data-group="country">EU - European Union</option>
<option value="fi" data-group="country">FI - Finland</option>
<option value="fr" data-group="country">FR - France</option>
<option value="gb" data-group="country">GB - United Kingdom</option>
<option value="global" data-group="country">🌍 Global</option>
<option value="gr" data-group="country">GR - Greece</option>
<option value="hk" data-group="country">HK - Hong Kong</option>
<option value="hr" data-group="country">HR - Croatia</option>
<option value="hu" data-group="country">HU - Hungary</option>
<option value="id" data-group="country">ID - Indonesia</option>
<option value="ie" data-group="country">IE - Ireland</option>
<option value="il" data-group="country">IL - Israel</option>
<option value="in" data-group="country">IN - India</option>
<option value="ir" data-group="country">IR - Iran</option>
<option value="it" data-group="country">IT - Italy</option>
<option value="jp" data-group="country">JP - Japan</option>
<option value="kg" data-group="country">KG - Kyrgyzstan</option>
<option value="kr" data-group="country">KR - Korea</option>
<option value="kz" data-group="country">KZ - Kazakhstan</option>
<option value="la" data-group="country">LA - Laos</option>
<option value="lk" data-group="country">LK - Sri Lanka</option>
<option value="lt" data-group="country">LT - Lithuania</option>
<option value="ma" data-group="country">MA - Morocco</option>
<option value="md" data-group="country">MD - Moldova</option>
<option value="mg" data-group="country">MG - Madagascar</option>
<option value="mk" data-group="country">MK - North Macedonia</option>
<option value="mx" data-group="country">MX - Mexico</option>
<option value="ng" data-group="country">NG - Nigeria</option>
<option value="nl" data-group="country">NL - Netherlands</option>
<option value="no" data-group="country">NO - Norway</option>
<option value="ph" data-group="country">PH - Philippines</option>
<option value="pk" data-group="country">PK - Pakistan</option>
<option value="pl" data-group="country">PL - Poland</option>
<option value="pt" data-group="country">PT - Portugal</option>
<option value="re" data-group="country">RE - Réunion</option>
<option value="ro" data-group="country">RO - Romania</option>
<option value="rs" data-group="country">RS - Serbia</option>
<option value="ru" data-group="country">RU - Russia</option>
<option value="sa" data-group="country">SA - Saudi Arabia</option>
<option value="sd" data-group="country">SD - Sudan</option>
<option value="se" data-group="country">SE - Sweden</option>
<option value="sg" data-group="country">SG - Singapore</option>
<option value="sk" data-group="country">SK - Slovakia</option>
<option value="sv" data-group="country">SV - El Salvador</option>
<option value="th" data-group="country">TH - Thailand</option>
<option value="tn" data-group="country">TN - Tunisia</option>
<option value="tr" data-group="country">TR - Türkiye</option>
<option value="tw" data-group="country">TW - Taiwan</option>
<option value="ua" data-group="country">UA - Ukraine</option>
<option value="uk" data-group="country">UK - United Kingdom</option>
<option value="us" data-group="country">US - United States</option>
<option value="uz" data-group="country">UZ - Uzbekistan</option>
<option value="ve" data-group="country">VE - Venezuela</option>
<option value="vi" data-group="country">VI - Virgin Islands</option>
<option value="vn" data-group="country">VN - Viet Nam</option>
<option value="za" data-group="country">ZA - South Africa</option>
</select>
<select multiple class="hidden-select" id="excludedTags" name="excluded_tags">
</select>
</div>
</div>
</div>
<!-- Advanced Options Section -->
<div class="mb-4">
<div class="section-header" onclick="toggleSection('advanced')">
<h5 class="mb-0">Advanced Options</h5>
<span class="chevron"></span>
</div>
<div id="advanced" class="section-content">
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="permute" name="permute">
<label class="form-check-label" for="permute">Enable Username Permutations</label>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="disable_recursive_search"
name="disable_recursive_search">
<label class="form-check-label" for="disable_recursive_search">Disable Recursive Search</label>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="disable_extracting" name="disable_extracting">
<label class="form-check-label" for="disable_extracting">Disable Information Extraction</label>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="with_domains" name="with_domains">
<label class="form-check-label" for="with_domains">Check Domains</label>
</div>
<div class="mb-3">
<label for="proxy" class="form-label">Proxy URL</label>
<input type="text" class="form-control" id="proxy" name="proxy"
placeholder="e.g., 127.0.0.1:1080">
</div>
<div class="mb-3">
<label for="tor_proxy" class="form-label">TOR Proxy URL</label>
<input type="text" class="form-control" id="tor_proxy" name="tor_proxy"
placeholder="Default: 127.0.0.1:9050">
</div>
<div class="mb-3">
<label for="i2p_proxy" class="form-label">I2P Proxy URL</label>
<input type="text" class="form-control" id="i2p_proxy" name="i2p_proxy"
placeholder="Default: 127.0.0.1:4444">
</div>
</div>
</div>
<button type="submit" class="btn search-button" style="background-color: rgb(249, 207, 0); color: black;">
Start Search
</button>
</form>
</div>
<script>
function toggleSection(sectionId) {
const content = document.getElementById(sectionId);
const header = content.previousElementSibling;
content.classList.toggle('show');
header.querySelector('.chevron').classList.toggle('collapsed');
}
document.addEventListener('DOMContentLoaded', function () {
// Tag cloud functionality with include/exclude (whitelist/blacklist) support
const tagCloud = document.getElementById('tagCloud');
const hiddenSelect = document.getElementById('tags');
const excludedSelect = document.getElementById('excludedTags');
const allTags = Array.from(hiddenSelect.options).map(opt => ({
value: opt.value,
label: opt.text,
group: opt.dataset.group || 'category'
}));
function updateTagSelects() {
// Clear and repopulate hidden selects based on tag states
Array.from(hiddenSelect.options).forEach(opt => opt.selected = false);
// Clear excluded select
excludedSelect.innerHTML = '';
document.querySelectorAll('#tagCloud .tag').forEach(tagEl => {
const val = tagEl.dataset.value;
if (tagEl.classList.contains('selected')) {
const option = Array.from(hiddenSelect.options).find(opt => opt.value === val);
if (option) option.selected = true;
} else if (tagEl.classList.contains('excluded')) {
const opt = document.createElement('option');
opt.value = val;
opt.selected = true;
excludedSelect.appendChild(opt);
}
});
}
let lastGroup = '';
allTags.forEach(tag => {
if (tag.group !== lastGroup && tag.group === 'country') {
const separator = document.createElement('div');
separator.style.cssText = 'width:100%;margin:8px 0 4px;padding:4px 0;border-top:1px solid rgba(0,0,0,0.15);font-size:13px;color:#666;';
separator.textContent = 'Countries';
tagCloud.appendChild(separator);
}
lastGroup = tag.group;
const tagElement = document.createElement('span');
tagElement.className = 'tag';
tagElement.textContent = tag.label;
tagElement.dataset.value = tag.value;
// Single click cycles: neutral -> included -> excluded -> neutral
tagElement.addEventListener('click', function (e) {
e.preventDefault();
if (this.classList.contains('selected')) {
// included -> excluded
this.classList.remove('selected');
this.classList.add('excluded');
} else if (this.classList.contains('excluded')) {
// excluded -> neutral
this.classList.remove('excluded');
} else {
// neutral -> included
this.classList.add('selected');
}
updateTagSelects();
});
tagCloud.appendChild(tagElement);
});
// Site selection functionality
const siteInput = document.getElementById('siteInput');
const hiddenInput = document.getElementById('site');
const selectedSitesContainer = document.getElementById('selectedSites');
let selectedSites = new Set();
function updateHiddenInput() {
hiddenInput.value = Array.from(selectedSites).join(',');
}
function addSite(site) {
if (site && !selectedSites.has(site)) {
selectedSites.add(site);
updateHiddenInput();
const siteElement = document.createElement('span');
siteElement.className = 'selected-site';
siteElement.innerHTML = `${site}<span class="remove-site" data-site="${site}">&times;</span>`;
selectedSitesContainer.appendChild(siteElement);
}
}
function removeSite(site) {
selectedSites.delete(site);
updateHiddenInput();
const siteElements = selectedSitesContainer.querySelectorAll('.selected-site');
siteElements.forEach(el => {
if (el.querySelector('.remove-site').dataset.site === site) {
el.remove();
}
});
}
siteInput.addEventListener('change', function (e) {
const value = this.value.trim();
if (value) {
addSite(value);
this.value = '';
}
});
selectedSitesContainer.addEventListener('click', function (e) {
if (e.target.classList.contains('remove-site')) {
removeSite(e.target.dataset.site);
}
});
siteInput.addEventListener('paste', function (e) {
e.preventDefault();
const paste = (e.clipboardData || window.clipboardData).getData('text');
const sites = paste.split(',').map(site => site.trim()).filter(site => site);
sites.forEach(addSite);
});
const form = document.querySelector('form');
form.addEventListener('submit', function (e) {
const selectedTags = Array.from(tagCloud.querySelectorAll('.tag.selected'));
Array.from(hiddenSelect.options).forEach(opt => {
opt.selected = selectedTags.some(tag => tag.dataset.value === opt.value);
});
updateHiddenInput();
});
});
</script>
{% endblock %}
+156
View File
@@ -0,0 +1,156 @@
{% extends "base.html" %}
{% block content %}
<style>
.tag-badge {
background-color: #214e7b;
padding: 2px 8px;
border-radius: 12px;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 5px;
margin: 2px;
color: white;
}
.profile-list {
list-style: none;
padding: 0;
}
.profile-item {
margin-bottom: 10px;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.profile-link {
display: flex;
align-items: center;
gap: 8px;
}
.favicon {
width: 16px;
height: 16px;
}
.tag-container {
display: flex;
flex-wrap: wrap;
gap: 5px;
justify-content: flex-end;
}
.report-container {
margin-bottom: 1rem;
}
.report-header {
cursor: pointer;
padding: 1rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
margin-bottom: 0.5rem;
}
.report-content {
display: none;
}
.report-content.show {
display: block;
}
.chevron::after {
content: '▼';
margin-left: 8px;
transition: transform 0.2s;
}
.chevron.collapsed::after {
transform: rotate(-90deg);
}
</style>
<div class="form-container">
<h1 class="mb-4">Search Results</h1>
<!-- Flash messages -->
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<p>The search has completed. <a href="{{ url_for('index')}}">Back to start.</a></p>
{% if graph_file %}
<h3>Combined Graph</h3>
<iframe src="{{ url_for('download_report', filename=graph_file) }}" style="width:100%; height:600px; border:none;"></iframe>
{% endif %}
<hr>
{% if individual_reports %}
<h3>Individual Reports</h3>
<div class="reports-list">
{% for report in individual_reports %}
<div class="report-container">
<div class="report-header" onclick="toggleReport(this)" data-target="report-{{ loop.index }}">
<h5 class="mb-0 d-flex align-items-center">
<span>{{ report.username }}</span>
<span class="chevron"></span>
</h5>
</div>
<div id="report-{{ loop.index }}" class="report-content">
<p>
<a href="{{ url_for('download_report', filename=report.csv_file) }}">CSV Report</a> |
<a href="{{ url_for('download_report', filename=report.json_file) }}">JSON Report</a> |
<a href="{{ url_for('download_report', filename=report.pdf_file) }}">PDF Report</a> |
<a href="{{ url_for('download_report', filename=report.html_file) }}">HTML Report</a>
</p>
{% if report.claimed_profiles %}
<strong>Claimed Profiles:</strong>
<ul class="profile-list">
{% for profile in report.claimed_profiles %}
<li class="profile-item">
<div class="profile-link">
<img class="favicon" src="https://www.google.com/s2/favicons?domain={{ profile.url }}" onerror="this.style.display='none'" alt="">
<a href="{{ profile.url }}" target="_blank">{{ profile.site_name }}</a>
</div>
{% if profile.tags %}
<div class="tag-container">
{% for tag in profile.tags %}
<span class="tag-badge">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No claimed profiles found.</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<p>No individual reports available.</p>
{% endif %}
</div>
<script>
function toggleReport(header) {
const reportId = header.getAttribute('data-target');
const content = document.getElementById(reportId);
content.classList.toggle('show');
header.querySelector('.chevron').classList.toggle('collapsed');
}
</script>
{% endblock %}
+16
View File
@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4 text-center">
<h2>Search in progress...</h2>
<p>Your request is being processed in the background. This page will automatically redirect once the results are ready.</p>
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<script>
// Auto-refresh the page every 5 seconds to check completion
setTimeout(function() {
window.location.reload();
}, 5000);
</script>
</div>
{% endblock %}
Generated
+3568
View File
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env python3
import asyncio
import platform
import sys
import maigret
DOUBLE_CLICK_INTRO = """\
Maigret runs from the command line. You can:
- call it from cmd/PowerShell: maigret_standalone.exe USERNAME [options]
- or enter a username below for a default search.
Full options: maigret_standalone.exe --help
Documentation: https://maigret.readthedocs.io/
"""
def _launched_by_double_click() -> bool:
# On Windows, Explorer spawns a fresh console just for our process, so
# GetConsoleProcessList returns 1. When launched from an existing cmd /
# PowerShell session the count is >= 2 (the shell is attached too).
if platform.system() != "Windows":
return False
if len(sys.argv) > 1:
return False
try:
import ctypes
buf = (ctypes.c_uint * 4)()
count = ctypes.windll.kernel32.GetConsoleProcessList(buf, 4)
return count <= 1
except Exception:
return False
def _pause() -> None:
try:
input("\nPress Enter to exit...")
except EOFError:
pass
def _prompt_for_username() -> str:
try:
return input("Username to search: ").strip()
except (EOFError, KeyboardInterrupt):
return ""
def main() -> None:
if not _launched_by_double_click():
asyncio.run(maigret.cli())
return
print(DOUBLE_CLICK_INTRO)
username = _prompt_for_username()
if not username:
print(
"\nNo username entered. Re-run with one, e.g.\n"
" maigret_standalone.exe alice"
)
_pause()
return
# Inject the username so maigret's argparse sees it like a normal CLI run.
sys.argv = [sys.argv[0], username]
try:
asyncio.run(maigret.cli())
finally:
_pause()
if __name__ == "__main__":
main()
+55
View File
@@ -0,0 +1,55 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import collect_all
datas = []
binaries = []
hiddenimports = []
full_import_modules = ['maigret', 'socid_extractor', 'arabic_reshaper', 'pyvis', 'reportlab.graphics.barcode']
for module in full_import_modules:
tmp_ret = collect_all(module)
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
hiddenimports += ['PySocks', 'beautifulsoup4', 'python-dateutil',
'future-annotations', 'six', 'python-bidi',
'typing-extensions', 'attrs', 'torrequest']
block_cipher = None
a = Analysis(['maigret_standalone.py'],
pathex=[],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='maigret_standalone',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None )
+5
View File
@@ -0,0 +1,5 @@
maigret @ https://github.com/soxoj/maigret/archive/refs/heads/main.zip
pefile==2023.2.7 # do not bump while pyinstaller is 6.11.1, there is a conflict
psutil==7.2.2
pyinstaller==6.21.0
pywin32-ctypes==0.2.3
+102
View File
@@ -0,0 +1,102 @@
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "maigret"
version = "0.6.2"
description = "🕵️‍♂️ Collect a dossier on a person by username from thousands of sites."
authors = ["Soxoj <soxoj@protonmail.com>"]
readme = "README.md"
license = "MIT License"
homepage = "https://pypi.org/project/maigret"
documentation = "https://maigret.readthedocs.io"
repository = "https://github.com/soxoj/maigret"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Intended Audience :: Information Technology",
"Operating System :: OS Independent",
"License :: OSI Approved :: MIT License",
"Natural Language :: English"
]
[tool.poetry.urls]
"Bug Tracker" = "https://github.com/soxoj/maigret/issues"
"Funding" = "https://www.patreon.com/soxoj"
[tool.poetry.dependencies]
# poetry install
# Install only production dependencies:
# poetry install --without dev
# Install with dev dependencies:
# poetry install --with dev
python = "^3.10"
aiodns = ">=3,<5"
aiohttp = "^3.12.14"
aiohttp-socks = ">=0.10.1,<0.12.0"
arabic-reshaper = {version = "^3.0.0", optional = true}
certifi = ">=2025.6.15,<2027.0.0"
colorama = "^0.4.6"
html5lib = "^1.1"
Jinja2 = "^3.1.6"
lxml = ">=6.0.2,<7.0"
MarkupSafe = "^3.0.2"
pycountry = ">=24.6.1,<27.0.0"
PySocks = "^1.7.1"
python-bidi = {version = "^0.6.3", optional = true}
requests = "^2.32.4"
socid-extractor = ">=0.0.27,<0.1.1"
soupsieve = "^2.6"
alive_progress = "^3.2.0"
typing-extensions = "^4.14.1"
xhtml2pdf = {version = "^0.2.11", optional = true}
XMind = "^1.2.0"
yarl = "^1.20.1"
networkx = "^2.6.3"
pyvis = "^0.3.2"
reportlab = "^4.4.3"
flask = {extras = ["async"], version = "^3.1.1"}
asgiref = "^3.9.1"
platformdirs = "^4.3.8"
curl-cffi = ">=0.14,<1.0"
[tool.poetry.extras]
# Install PDF support with: pip install 'maigret[pdf]'
# Skipped by default because the underlying `pycairo` has no Linux/macOS
# wheels on PyPI and requires system libcairo + pkg-config to build.
# arabic-reshaper and python-bidi are pulled in too — they're only used
# by xhtml2pdf (RTL text shaping in PDFs), nothing in maigret core touches
# them, and python-bidi v0.5+ is a Rust binding that can need cargo on
# niche platforms.
pdf = ["xhtml2pdf", "arabic-reshaper", "python-bidi"]
[tool.poetry.group.dev.dependencies]
# How to add a new dev dependency: poetry add black --group dev
# Install dev dependencies with: poetry install --with dev
flake8 = "^7.1.1"
pytest = ">=8.3.4,<10.0.0"
pytest-asyncio = "^1.0.0"
pytest-cov = ">=6,<8"
pytest-httpserver = "^1.0.0"
pytest-rerunfailures = ">=15.1,<17.0"
reportlab = "^4.4.3"
xhtml2pdf = "^0.2.11"
arabic-reshaper = "^3.0.0"
python-bidi = "^0.6.3"
mypy = ">=1.14.1,<3.0.0"
tuna = "^0.5.11"
coverage = "^7.9.2"
black = ">=25.1,<27.0"
[tool.poetry.scripts]
# Run with: poetry run maigret <username>
maigret = "maigret.maigret:run"
update_sitesmd = "utils.update_site_data:main"
+7
View File
@@ -0,0 +1,7 @@
# pytest.ini
[pytest]
filterwarnings =
error
ignore::UserWarning
ignore:codecs.open\(\) is deprecated:DeprecationWarning:xmind.core.saver
asyncio_mode=auto

Some files were not shown because too many files have changed in this diff Show More