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
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:
@@ -0,0 +1,8 @@
|
||||
.git/
|
||||
.vscode/
|
||||
static/
|
||||
tests/
|
||||
*.txt
|
||||
!/requirements.txt
|
||||
venv/
|
||||
|
||||
Executable
+10
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: soxoj
|
||||
github: soxoj
|
||||
buy_me_a_coffee: soxoj
|
||||
@@ -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, ...
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 .
|
||||
@@ -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)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
[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>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</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
|
||||
|
||||
[](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)
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`soxoj/maigret`
|
||||
- 原始仓库:https://github.com/soxoj/maigret
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
+392
@@ -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)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
[完整的命令行输出示例](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>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</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 分析
|
||||
|
||||
[](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)
|
||||
@@ -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.
|
||||
@@ -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/)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
sphinx-copybutton
|
||||
sphinx_rtd_theme
|
||||
sphinx-intl
|
||||
@@ -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 doesn’t 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.
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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`.
|
||||
@@ -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 (5–10× 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>`_
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 doesn’t 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
@@ -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 (5–10× faster on subsequent requests to"
|
||||
" that host)."
|
||||
msgstr ""
|
||||
"FlareSolverr 的 session 复用是自动的:Maigret 会在每次运行时固定一个 ``session: "
|
||||
"<session_prefix>-<pid>``,这样对同一域名的多次检查就会共享 cf_clearance cookie(后续对该主机的请求会快"
|
||||
" 5–10 倍)。"
|
||||
|
||||
#: ../../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 1–3 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 |
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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 1–3 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Maigret version file"""
|
||||
|
||||
__version__ = '0.6.2'
|
||||
@@ -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 ~1–3 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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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">Мы не нашли страницу': 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
|
||||
@@ -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}")
|
||||
Executable
+1112
File diff suppressed because it is too large
Load Diff
@@ -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))
|
||||
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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__
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
<span style="display:inline-block;width:12px;height:12px;background:#343a40;border-radius:50%;"></span> Excluded (blacklist)
|
||||
|
||||
<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}">×</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 %}
|
||||
@@ -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 %}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Executable
+77
@@ -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()
|
||||
@@ -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 )
|
||||
@@ -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
@@ -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"
|
||||
@@ -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
Reference in New Issue
Block a user