Skip to main content

AioCaptcha (aiogram middleware)

Minimal captcha middleware for aiogram v3.

Behavior:

  • Any user interaction with the bot is blocked until the user passes the captcha.
  • Captcha is an inline keyboard with emojis. The bot asks the user to click the required emoji.
  • Successful users can be stored persistently (SQLite) so they never need to pass captcha again.

Requirements

  • Python 3.10+ (recommended)
  • aiogram>=3.0.0

Install

pip install aiocaptcha

Quick start

Create a bot file like Source/main.py and run it.

1) Set the token

export BOT_TOKEN="YOUR_TELEGRAM_BOT_TOKEN"

2) Run

python Source/main.py

Usage

Minimal (in-memory)

Users will be considered “verified” only until the bot restarts.

from aiogram import Bot, Dispatcher
from aiocaptcha import CaptchaManager

bot = Bot(token="...")
dp = Dispatcher()

captcha = CaptchaManager()
captcha.setup(dp)

# ... register your handlers
await dp.start_polling(bot)
Persistent (SQLite)

Users who passed captcha will be stored in captcha.sqlite3 and won’t be asked again after restarts.

from aiogram import Bot, Dispatcher
from aiocaptcha import CaptchaManager, SqliteCaptchaStorage

bot = Bot(token="...")
dp = Dispatcher()

storage = SqliteCaptchaStorage("captcha.sqlite3")
captcha = CaptchaManager(storage=storage)
captcha.setup(dp)

await dp.start_polling(bot)

Note: SqliteCaptchaStorage uses a single connection with a lock and is fine for typical small bots.

Custom storage (your own)

You can provide your own storage to persist “verified” users.

The required interface is:

from typing import Protocol


class CaptchaStorage(Protocol):
  def is_verified(self, user_id: int) -> bool: ...

  def mark_verified(self, user_id: int) -> None: ...

  def reset(self, user_id: int) -> None: ...

Example: a simple file-backed JSON storage (minimal example):

import json
from pathlib import Path
from typing import Set

from aiocaptcha import CaptchaManager, CaptchaOptions


class JsonCaptchaStorage:
  def __init__(self, path: str = "verified.json") -> None:
    self.path = Path(path)
    self._verified: Set[int] = set()
    self._load()

  def _load(self) -> None:
    if not self.path.exists():
      return
    data = json.loads(self.path.read_text("utf-8"))
    self._verified = set(int(x) for x in data.get("verified", []))

  def _save(self) -> None:
    self.path.write_text(
      json.dumps({"verified": sorted(self._verified)}, ensure_ascii=False),
      "utf-8",
    )

  def is_verified(self, user_id: int) -> bool:
    return user_id in self._verified

  def mark_verified(self, user_id: int) -> None:
    self._verified.add(user_id)
    self._save()

  def reset(self, user_id: int) -> None:
    self._verified.discard(user_id)
    self._save()


opts = CaptchaOptions()
storage = JsonCaptchaStorage("verified.json")
captcha = CaptchaManager(options=opts, storage=storage)

For production/multi-instance setups, a Redis-based storage is usually a better fit.

Configuration

CaptchaOptions

Use CaptchaOptions to customize messages and emoji pool:

from aiocaptcha import CaptchaManager, CaptchaOptions

opts = CaptchaOptions(
    prompt_text="Please verify you're human by clicking {target}",
    passed_text="Captcha passed successfully! ✅",
    wrong_alert="Incorrect choice. Please try again.",
    pool=("🐶", "🐱", "🐭", "🐹", "🐰", "🦊", "🐻", "🐼"),
    choices=4,
    ttl_seconds=120,
)

captcha = CaptchaManager(options=opts)

Available options

  • callback_prefix — prefix for callback data (default: aiocaptcha)
  • pool — tuple of emojis
  • choices — how many buttons are shown
  • ttl_seconds — challenge expiration time
  • prompt_text — message template. Use {target}
  • passed_text — message after passing
  • wrong_alert — alert text on wrong click

How it works

Implementation notes
  • The library attaches:
    • an update-level middleware (dp.update.middleware(...)) that blocks all updates from non-verified users;
    • a small router that handles captcha callback queries.
  • When a non-verified user sends any message/clicks any button, the bot sends (or edits) a captcha message.
  • On correct click the user becomes verified; on wrong click the captcha regenerates.

Logging

Events and setup

The library logs events using Python logging under the logger name aiocaptcha:

  • captcha_passed user_id=...
  • captcha_failed user_id=... answer=... expected=...
  • captcha_sent user_id=... chat_id=... message_id=...

Enable logging in your app:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)

Notes / limitations

Notes
  • Captcha state and verification are keyed by user_id.
  • In groups, a captcha message is sent to the chat where the interaction happened.
  • If you run multiple bot instances, use a shared storage (SQLite on shared disk, or implement your own storage like Redis).

Metadata

Release files for aiocaptcha 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiocaptcha 0.1.0
File Size Uploaded Provenance
aiocaptcha-0.1.0.tar.gz 9.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for aiocaptcha 0.1.0
File Interpreter ABI Platform
aiocaptcha-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 18.1 kB

Release files / aiocaptcha-0.1.0.tar.gz

Download URL aiocaptcha-0.1.0.tar.gz
Size 9.3 kB
Tags Source
SHA-256 checksum
How to use checksums
b5290c8cdd366578223db20d17f27228aefeff235248b0127c0406abc8ca44d9
BLAKE2b-256 checksum
How to use checksums
4e19a91d748ad222a6e39a8e3669e8fc6111ba0cdc8b6e66e2da4fe541053227
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.11

Release files / aiocaptcha-0.1.0-py3-none-any.whl

Download URL aiocaptcha-0.1.0-py3-none-any.whl
Size 8.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6770d67e438c01862e0a99756fa91ce12d6d6c9cffb8909931d91ac74f758900
BLAKE2b-256 checksum
How to use checksums
43e1f72a932ce01debd5f90d869611027caa40df3059fab66137eb9aa9e0658c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.11

Security for aiocaptcha 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page