🚦 Finite State Machine (FSM)
Введение
В PBModular встроена система конечных автоматов состояний (FSM), которая позволяет создавать обработчики для многоэтапного взаимодействия с пользователем, например, формы регистрации, викторины или последовательного ввода команд. Она работает путем отслеживания "состояния" каждого пользователя в основной базе данных бота.
Шаг 1: Определение машины состояний и состояний
- Создайте класс, который наследуется от
base.states.StateMachine
. - Внутри этого класса определите свои состояния, создав атрибуты класса, которые являются экземплярами
base.states.State
.
Пример:
# В файле main.py или отдельном файле states.py
from base.states import StateMachine, State
class RegistrationFSM(StateMachine):
waiting_for_name = State()
waiting_for_age = State()
# Состояние по умолчанию - None (состояние не установлено)
Шаг 2: Регистрация машины состояний
В вашем основном классе BaseModule
переопределите свойство state_machine
, чтобы вернуть ваш класс FSM.
# В классе вашего BaseModule (например, main.py)
from .my_fsm import RegistrationFSM # Предполагается, что вы определили его в файле my_fsm.py
class MyModule(BaseModule):
# ... другие свойства ...
@property
def state_machine(self) -> type[StateMachine]:
"""Регистрирует FSM для этого модуля."""
return RegistrationFSM
Шаг 3: Создание обработчиков, специфичных для конкретного состояния
Используйте параметр fsm_state
в декораторах обработчиков (@command
, @message
и т.д.), чтобы указать, на какое состояние (состояния) должен реагировать обработчик.
- Для обработки команды, которая начинает процесс, вы можете опустить
fsm_state
или установитьfsm_state=None
, так как пользователи будут находиться в состоянии по умолчанию (None). - Для обработки последующих сообщений установите
fsm_state
в соответствующий объектState
из вашего класса FSM.
Шаг 4: Управление состоянием в обработчиках
Когда вы регистрируете StateMachine
, ваши обработчики могут принимать дополнительный четвертый аргумент, обычно называемый sm_controller
. Этот объект представляет собой экземпляр вашего класса StateMachine
, привязанный к текущему пользователю, и предоставляет методы для управления его состоянием и данными.
sm_controller.get_state() -> Optional[str]
: Получение имени текущего состояния пользователя.sm_controller.set_state(State, data: Optional[dict] = None)
: Устанавливает состояние пользователя. Вы также можете хранить словарь данных вместе с ним.sm_controller.get_data() -> dict
: Получение сохраненных данных пользователя.sm_controller.update_data(**kwargs)
: Обновление словаря сохраненных данных пользователя.sm_controller.clear(keep_data: bool = False)
: Сбрасывает состояние пользователя на значение по умолчанию (None
). По умолчанию это также очищает данные пользователя.
Полный пример:
# main.py
from base.module import BaseModule, command, message
from base.states import StateMachine, State
from pyrogram.types import Message
# 1. Определите FSM
class RegistrationFSM(StateMachine):
waiting_for_name = State()
waiting_for_age = State()
class FSMModule(BaseModule):
# 2. Зарегистрируйте FSM
@property
def state_machine(self) -> type[StateMachine]:
return RegistrationFSM
# 3. Создайте обработчики
@command("register") # fsm_state=None по умолчанию
async def start_registration(self, message: Message, sm_controller: RegistrationFSM):
"""Запускает процесс регистрации пользователя."""
await message.reply("Отлично! Как тебя зовут?")
# 4. Установите следующее состояние
await sm_controller.set_state(sm_controller.waiting_for_name)
@message(fsm_state=RegistrationFSM.waiting_for_name)
async def handle_name(self, message: Message, sm_controller: RegistrationFSM):
"""Обрабатывает ввод имени пользователя."""
name = message.text
await message.reply(f"Спасибо, {имя}! Сколько тебе лет?")
# Обновление данных и установка следующего состояния
await sm_controller.update_data(name=name)
await sm_controller.set_state(sm_controller.waiting_for_age)
@message(fsm_state=RegistrationFSM.waiting_for_age)
async def handle_age(self, message: Message, sm_controller: RegistrationFSM):
"""Обработка ввода возраста пользователя."""
if not message.text.isdigit():
await message.reply("Пожалуйста, введите действительное число для вашего возраста").
return # Держите пользователя в том же состоянии
age = int(message.text)
# Получите все данные и завершите работу
user_data = await sm_controller.get_data()
name = user_data.get("name")
await message.reply(
f"Registration complete!\n"
f"Name: {name}\n"
f"Age: {age}"
)
# Очистите состояние и данные
await sm_controller.clear()
@command("cancel")
async def cancel_process(self, message: Message, sm_controller: RegistrationFSM):
"""Отменяет любой текущий процесс FSM."""
current_state = await sm_controller.get_state()
if current_state:
await sm_controller.clear()
await message.reply("Процесс отменен").
else:
await message.reply("Нечего отменять").