Filter on state in handler does not work correctly when updating state in middleware #1411
-
version:aiogram==3.3.0 Problem:Incorrect operation is associated with the use of custom states (StatesGroup) and updating state in outer_middleware and further filtering of these states in the handler. How to see the problem:send any text messages to the bot twice. Explanation:the first time the bot does not intercept the update due to the filter, although the state is updated to "registered". but the next time it is called, everything works. Code:import asyncio
from typing import Any, Awaitable, Callable, Dict
from aiogram import Bot, Dispatcher, types
from aiogram.enums import ParseMode
from aiogram.filters import CommandStart
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import Message, Update
from aiogram.utils.markdown import hbold
class MyForm(StatesGroup):
registered = State()
TOKEN = "BOT_TOKEN"
dp = Dispatcher()
@dp.update.outer_middleware()
async def check_middleware(
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
event: Update,
data: Dict[str, Any],
) -> Any:
state: FSMContext = data.get("state")
await state.set_state(MyForm.registered)
print(f"State from middleware: {await state.get_state()}")
return await handler(event, data)
@dp.message(CommandStart())
async def command_start_handler(message: Message) -> None:
await message.answer(f"Hello, {hbold(message.from_user.full_name)}!")
@dp.message(MyForm.registered)
async def message_handler(message: types.Message, state: FSMContext) -> None:
current_state = await state.get_state()
print(f"Current state: {current_state}")
await message.send_copy(chat_id=message.chat.id)
async def main() -> None:
bot = Bot(TOKEN, parse_mode=ParseMode.HTML)
await bot.delete_webhook(drop_pending_updates=True)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main()) |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
Expected behavior. You should not change any user state in the middleware or send any messages from it. State filter has no any state loading mechanism in due to performance optimizations. FSM Middleware loads user state to the event context and then FSM filter check user state only from the context. Seems like you want to separate functionality for anonymous and registered users, so, you should detect that user is registered or no and put this information into the context and then check it by filters, for example (pseudocode): dispatcher = Dispatcher()
dispatcher.include_routers(
anonymous_router, # Should be imported
registered_router, # Should be imported
...
)
@dp.update.outer_middleware()
async def check_middleware(
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
event: Update,
data: Dict[str, Any],
) -> Any:
... # Load user from DB or None if not registered
data["user"] = user
return await handler(event, data) Another module with router for anonymous users anonymous_router = Router()
anonymous_router.message.filter(MagicData(~F.user))
@anonymous_router.message()
async def ... Another module with router for registered users registered_router = Router()
registered_router.message.filter(MagicData(F.user))
@registered_router.message()
async def ... |
Beta Was this translation helpful? Give feedback.
Expected behavior.
You should not change any user state in the middleware or send any messages from it.
State filter has no any state loading mechanism in due to performance optimizations. FSM Middleware loads user state to the event context and then FSM filter check user state only from the context.
Seems like you want to separate functionality for anonymous and registered users, so, you should detect that user is registered or no and put this information into the context and then check it by filters, for example (pseudocode):