Observed since 21 Oct, 11:42 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Minimal emoji captcha middleware for aiogram v3
Minimal captcha middleware for aiogram v3.
Behavior:
aiogram>=3.0.0pip install aiocaptcha
Create a bot file like Source/main.py and run it.
export BOT_TOKEN="YOUR_TELEGRAM_BOT_TOKEN"
python Source/main.py
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)
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:
SqliteCaptchaStorageuses a single connection with a lock and is fine for typical small bots.
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.
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)
callback_prefix — prefix for callback data (default: aiocaptcha)pool — tuple of emojischoices — how many buttons are shownttl_seconds — challenge expiration timeprompt_text — message template. Use {target}passed_text — message after passingwrong_alert — alert text on wrong clickdp.update.middleware(...)) that blocks all updates from non-verified users;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",
)
user_id.Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 5 Jan 2026 | 1 |
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 11:42 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:6e8b2d789d9287cb254ac41164f3c8d0cac7cca3c4d9cd94a3930314b1c94eee