Не работает фильтр для состояний в aiogram
В aiogram квиз-боте я пытаюсь создать кастомный фильтр, который возвращает True, если пользователь находится в одном из нескольких состояний. Ответы от пользователя я получаю с помощью inline клавиатуры и результаты каждого вопроса записываю в FSM (так ведь можно делать?). Вот код хендлеров, в которых начинаются проблемы:
@dp.callback_query_handler(text="choice")
async def choice_continent(callback: types.CallbackQuery):
await callback.message.edit_text("Выберите континент, по столицам странам которого "
"будет проводиться тест ?")
await callback.message.edit_reply_markup(reply_markup=choice_menu)
await Test.first()
@dp.callback_query_handler(text_contains="choice_continent", state=Test.start)
async def start_test(callback: types.CallbackQuery, state: FSMContext):
continent = callback.data.split(":")[-1]
countries = data[continent]
await callback.message.edit_text(f"Выбран континент: <b>{continent}</b> ⛰")
await callback.message.edit_reply_markup(reply_markup=start_menu)
await state.update_data(continent=continent, countries=countries)
await Test.next()
@dp.callback_query_handler(text="back", state=Test.Q1)
async def back_to_menu(callback: types.CallbackQuery, state: FSMContext):
await choice_continent(callback)
@dp.callback_query_handler(IsInStatesGroup(), text="start_question")
async def start_question(callback: types.CallbackQuery, state: FSMContext):
data_test = await state.get_data()
countries = data_test["countries"]
country = str(random.choice(list(countries.keys())))
await callback.message.edit_text(f"<b>Выберите столицу страны: {country}.</b>")
await callback.message.edit_reply_markup(reply_markup=await create_question_menu(country, countries))
await state.update_data(country=country, countries=countries)
await Test.next()
Мне нужно из choice_continent переходить в start_test, при нажатии на кнопку отмены переходить в back_to_menu, а из start_test в start_question. Вот код inline-клавиатур:
choice_callback_data = CallbackData("choice_continent", "continent")
question_callback_data = CallbackData("question", "type", "capital")
continents = list(data.keys())
main_menu = InlineKeyboardMarkup()
main_menu.insert(InlineKeyboardButton(text="Выбрать континент ?", callback_data="choice"))
choice_menu = InlineKeyboardMarkup()
for name in continents:
choice_menu.insert(InlineKeyboardButton(text=name, callback_data=choice_callback_data.new(
continent=name
)))
start_menu = InlineKeyboardMarkup()
start_menu.insert(InlineKeyboardButton(text="Начать тест ✏", callback_data="start_question"))
start_menu.insert(InlineKeyboardButton(text="Назад ⬅", callback_data="back"))
next_menu = InlineKeyboardMarkup()
next_menu.insert(InlineKeyboardButton(text="Дальше ➡", callback_data="next_question"))
Вот класс с группой состояний:
class Test(StatesGroup):
start = State()
Q1 = State()
Q2 = State()
Q3 = State()
Q4 = State()
Q5 = State()
Q6 = State()
Q7 = State()
Q8 = State()
Q9 = State()
Q10 = State()
Вот класс кастомного фильтр:
class IsInStatesGroup(BoundFilter):
async def check(self, callback: types.CallbackQuery, state: FSMContext):
state_name = await state.get_state()
return state_name in Test.all_states_names[1:]
Так я его подключаю:
if __name__ == "filters":
dp.filters_factory.bind(IsInStatesGroup)
Если что, я в loader файл при создании диспатчера передал объект MemoryStorage, а при импорте в app файле придерживался правильного порядка: filters, middlewares, handlers. Когда я просто в хендлере делаю фильтр state=Test.Q1 - всё работает. Что делать?
Ответы (2 шт):
Смотри. Я не сильно уверен, но сделал то что точно работает.
class FSM_TEST(StatesGroup):
Q1 = State()
Q2 = State()
class Filter_(BoundFilter):
async def check(self, message: types.Message):
state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
cur_state = await state.get_state()
return cur_state in FSM_TEST.all_states_names
@dp.message_handler()
async def start(message: types.Message):
await message.answer("Приве1т.")
await FSM_TEST.Q1.set()
@dp.message_handler(Filter_(), state="*")
async def start(message: types.Message):
await message.answer("Приве2т.")
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
dp.filters_factory.bind(Filter_)
Я не смог получить обьект state в фильтре, пришлось получать его через экземпляр диспатчера. Ты только поменяй с месседжа на колбек. Я думаю что он у тебя не забиндился, где бы он у тебя это не делал.
К слову, я не совсем понимаю зачем ради такой мелочи делать целый кастомный фильтр.
Для aiogram>3 я создал такой фильтр
from typing import Type
from aiogram.filters import Filter
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import StatesGroup
from aiogram.types import Message
class StateFilter(Filter):
"""Фильтр на группу состояний"""
def __init__(self, states_group: Type[StatesGroup]):
self.states_group = states_group
async def __call__(self, message: Message, state: FSMContext) -> bool:
current_state = await state.get_state()
return current_state in self.states_group._get_all_states_names()
Применение:
class TEST(StatesGroup):
Q1 = State()
Q2 = State()
@router.message(StateFilter(TEST))
...