Не работает фильтр для состояний в 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 шт):

Автор решения: oleksandrigo

Смотри. Я не сильно уверен, но сделал то что точно работает.

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 в фильтре, пришлось получать его через экземпляр диспатчера. Ты только поменяй с месседжа на колбек. Я думаю что он у тебя не забиндился, где бы он у тебя это не делал.
К слову, я не совсем понимаю зачем ради такой мелочи делать целый кастомный фильтр.

→ Ссылка
Автор решения: Olg

Для 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))
...
→ Ссылка