Статья Автор: Деникина Н.В., Деникин А.В.

Finite State Machine (FSM) — Конечный автомат. Организация опроса в Telegram-боте

FSM (Finite State Machine) — это математическая модель, используемая в программировании для управления последовательностью действий, где система может находиться только в одном конкретном состоянии в каждый момент времени. В контексте Telegram-ботов FSM помогает организовать диалог с пользователем, разделяя его на логические этапы (состояния).
 
 

Основные компоненты FSM

Компонент Описание
Состояния (States) Этапы диалога (например, "выбор жанра", "указание года").
Переходы (Transitions) Правила перехода между состояниями (например, после ответа о жанре → запрос года).
События (Events) Действия пользователя (текст, команды), которые запускают переходы.
Данные (Data) Временное хранилище ответов (например, context.user_data).
 

Как FSM работает в Telegram-боте?

Пример: опрос для рекомендации фильмов

  1. Старт: Бот спрашивает жанр → состояние GENRE.

  2. Пользователь отвечает → бот переходит в состояние YEAR и спрашивает год.

  3. Пользователь указывает год → бот переходит в состояние RATING и запрашивает рейтинг.

  4. После всех ответов → бот анализирует данные и выдаёт результат.


Зачем использовать FSM?

  1. Управление сложными диалогами
    Без FSM код превращается в хаотичные if-else, которые трудно поддерживать.
    Пример плохого подхода:

    if "жанр" in query:
        await ask_year()
    elif "год" in query:
        await ask_rating()
  2. Сохранение контекста
    Данные (жанр, год) хранятся в context.user_data между этапами.

  3. Гибкость
    Можно добавить новые состояния (например, выбор страны или актёра).
     

 

Реализация FSM в python-telegram-bot

1. Определение состояний

Каждому этапу присваивается уникальный числовой идентификатор:

from telegram.ext import ConversationHandler

# Этапы опроса
GENRE, YEAR, RATING = range(3)

2. Обработчики для каждого состояния

Каждая функция возвращает следующее состояние или ConversationHandler.END для завершения.

async def ask_genre(update: Update, context: CallbackContext) -> int:
    await update.message.reply_text("Укажите жанр:")
    return YEAR  # Переход к запросу года

async def ask_year(update: Update, context: CallbackContext) -> int:
    context.user_data['genre'] = update.message.text  # Сохраняем жанр
    await update.message.reply_text("Укажите год:")
    return RATING  # Переход к запросу рейтинга
 

3. Настройка ConversationHandler

Связывает состояния и обработчики:

conv_handler = ConversationHandler(
    entry_points=[CommandHandler('start', start)],  # Точка входа
    states={
        GENRE: [MessageHandler(filters.TEXT, ask_genre)],
        YEAR: [MessageHandler(filters.TEXT, ask_year)],
        RATING: [MessageHandler(filters.TEXT, ask_rating)]
    },
    fallbacks=[CommandHandler('cancel', cancel)]  # Отмена опроса
)
 

Пример: Полный код бота с FSM

from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ConversationHandler, CallbackContext

# Состояния
GENRE, YEAR = range(2)

async def start(update: Update, context: CallbackContext) -> int:
    await update.message.reply_text("🎬 Назовите жанр фильма:")
    return GENRE

async def ask_genre(update: Update, context: CallbackContext) -> int:
    context.user_data['genre'] = update.message.text
    await update.message.reply_text("📅 Теперь укажите год:")
    return YEAR

async def ask_year(update: Update, context: CallbackContext) -> int:
    context.user_data['year'] = update.message.text
    genre = context.user_data['genre']
    year = context.user_data['year']
    await update.message.reply_text(f"✅ Вы выбрали: {genre} ({year})")
    return ConversationHandler.END  # Завершаем диалог

async def cancel(update: Update, context: CallbackContext) -> int:
    await update.message.reply_text("Опрос отменён.")
    return ConversationHandler.END

def main():
    application = Application.builder().token("TOKEN").build()
    conv_handler = ConversationHandler(
        entry_points=[CommandHandler('start', start)],
        states={
            GENRE: [MessageHandler(filters.TEXT, ask_genre)],
            YEAR: [MessageHandler(filters.TEXT, ask_year)]
        },
        fallbacks=[CommandHandler('cancel', cancel)]
    )
    application.add_handler(conv_handler)
    application.run_polling()

if __name__ == "__main__":
    main()
Пропустить Навигационные Ссылки.
Чтобы оставить комментарий нужна авторизация
Печать