From ec2c0ba146cac5aabdbd61e03d5bb0e45fb5af06 Mon Sep 17 00:00:00 2001 From: mamahoos Date: Sat, 8 Aug 2026 12:51:44 +0330 Subject: [PATCH] release: 4.1.0 docs and version bump Document DI data_key, Redis-oriented storage setup, and real bot examples. --- README.md | 102 +++++++++++++++++++++++++++++++++++++++---------- pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 58a7859..afd6d3d 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,17 @@ [![PyPI](https://img.shields.io/pypi/v/aiogram-input.svg)](https://pypi.org/project/aiogram-input/) [![Test](https://github.com/mamahoos/aiogram-input/actions/workflows/test.yml/badge.svg)](https://github.com/mamahoos/aiogram-input/actions/workflows/test.yml) -Wait for the next Telegram message inside an aiogram handler — without building an FSM for every short prompt. +Wait for the next Telegram message inside an aiogram handler — without an FSM for every short prompt. ## Why -Aiogram FSM is great for multi-step flows. It is heavy for “ask once, wait, continue.” +You ask for a phone number, a confirmation, or a one-time code. A full FSM for that is noise. You want: -`aiogram-input` gives you that one awaitable wait: register once on the Dispatcher, call `input.wait(...)` from any handler, get a `Message` or `None` on timeout. Unrelated updates still reach FSM and other handlers. +```text +send question → await reply → continue +``` + +Register once on the Dispatcher. Await `input.wait(...)`. Get a `Message`, or `None` on timeout. FSM and other handlers still get unrelated updates. ## Install @@ -17,37 +21,95 @@ Aiogram FSM is great for multi-step flows. It is heavy for “ask once, wait, co pip install -U aiogram-input ``` -Requires Python 3.10+ and aiogram 3. +Python 3.10+, aiogram 3. -## Usage +## Setup ```python -from aiogram import Bot, Dispatcher, F +from aiogram import Dispatcher +from aiogram_input import MemoryInputStorage, setup_input + +dp = Dispatcher() + +# Local / single process +setup_input(dp, storage=MemoryInputStorage()) + +# Production (multi-worker): same API, Redis-backed storage +# setup_input(dp, storage=RedisInputStorage(redis)) + +# If `input` already means something else in your handlers: +# setup_input(dp, data_key="aiogram_input") +``` + +`InputWaiter` is injected into handlers (DI, like `FSMContext`). Storage is swappable via `InputStorage` (**Memory** today, **Redis** when you scale). The DI key is configurable (`data_key`, default `"input"`). + +## Examples + +### DI — one setup, every router + +**Pain:** waiter constructed on `dp`, again on `admin_router`, again in another file. State splits. + +```python +from aiogram import Router from aiogram.filters import Command from aiogram.types import Message -from aiogram_input import InputWaiter, setup_input +from aiogram_input import InputWaiter -dp = Dispatcher() -setup_input(dp) # once +admin = Router() +support = Router() -@dp.message(Command("name")) -async def ask_name(message: Message, input: InputWaiter): - await message.answer("What is your name?") - reply = await input.wait( +@admin.message(Command("ban")) +async def ban_user(message: Message, input: InputWaiter): + await message.answer("Send the user id to ban:") + reply = await input.wait(message.chat.id, timeout=60) + if reply is None: + return await message.answer("Timed out.") + await message.answer(f"Banned `{reply.text}`", parse_mode="Markdown") + +@support.message(Command("ticket")) +async def open_ticket(message: Message, input: InputWaiter): + await message.answer("Describe the issue:") + reply = await input.wait(message.chat.id, timeout=120) + ... + +dp.include_router(admin) +dp.include_router(support) +``` + +### Magic filters — wait for a sticker, not chat noise + +**Pain:** you ask for a sticker pack preview. People spam text. In groups, someone else replies first. + +```python +from aiogram import F +from aiogram.filters import Command +from aiogram.types import Message +from aiogram_input import InputWaiter + +@dp.message(Command("sticker_id")) +async def sticker_id(message: Message, input: InputWaiter): + await message.answer("Send a sticker — text will be ignored.") + sticker = await input.wait( message.chat.id, - timeout=30, - filter=F.from_user.id == message.from_user.id, + timeout=45, + filter=( + F.sticker + & (F.from_user.id == message.from_user.id) + ), ) - if reply is None: + if sticker is None: return await message.answer("Timed out.") - await message.answer(f"Hi, {reply.text}") + await message.answer( + f"file_id:\n`{sticker.sticker.file_id}`", + parse_mode="Markdown", + ) ``` -`setup_input` injects `InputWaiter` into handlers (like `FSMContext`). Optional: `setup_input(dp, storage=MemoryInputStorage())` for a custom `InputStorage`. +Only a sticker from the same user resolves the wait. Texts, photos, and other users’ stickers keep flowing to the rest of your bot. -## 3.x → 4.0 +## 3.x → 4.x -`InputManager` is gone. Use `setup_input(dp)` + `input.wait(...)` instead of `InputManager(router).input(...)`. +`InputManager` is removed. Use `setup_input(dp)` + `input.wait(...)`. ## License diff --git a/pyproject.toml b/pyproject.toml index 2519839..cf7aecf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "aiogram-input" -version = "4.0.0" +version = "4.1.0" description = "Await user replies in aiogram bots with Dispatcher-scoped setup and pluggable storage." readme = "README.md" license = "MIT" diff --git a/uv.lock b/uv.lock index 18c846e..13b027c 100644 --- a/uv.lock +++ b/uv.lock @@ -30,7 +30,7 @@ wheels = [ [[package]] name = "aiogram-input" -version = "4.0.0" +version = "4.1.0" source = { editable = "." } dependencies = [ { name = "aiogram" },