Как управлять состоянием пользователя в Telegram-боте без использования FSM

Ответ

Управлять состоянием пользователя без FSM (Finite State Machine) можно, сохраняя его вручную в каком-либо хранилище. Это подходит для простых сценариев, где не требуется сложная логика переходов между состояниями.

Способы хранения состояния:

  1. Словарь в памяти (In-memory Dictionary): Подходит только для отладки. Данные теряются при перезапуске бота.
  2. Кэш (Redis): Быстрый и эффективный способ. Состояние хранится в виде пар user_id:state с возможностью установки времени жизни (TTL).
  3. База данных (SQLite, PostgreSQL): Надёжный способ для хранения долгоживущих состояний и связанных с ними данных.

Пример на aiogram с хранением в словаре:

from aiogram import Bot, Dispatcher, types

# ВАЖНО: Этот способ не подходит для продакшена, т.к. данные теряются при перезапуске
user_states = {}

bot = Bot(token="YOUR_API_TOKEN")
dp = Dispatcher(bot)

# Шаг 1: Запускаем диалог и устанавливаем состояние
@dp.message_handler(commands=['register'])
async def start_registration(message: types.Message):
    user_id = message.from_user.id
    user_states[user_id] = "waiting_for_name"
    await message.answer("Введите ваше имя:")

# Шаг 2: Обрабатываем сообщение в зависимости от текущего состояния
@dp.message_handler()
async def process_message(message: types.Message):
    user_id = message.from_user.id
    current_state = user_states.get(user_id)

    if current_state == "waiting_for_name":
        name = message.text
        await message.answer(f"Отлично, {name}! Теперь введите ваш возраст.")
        # Переводим пользователя в следующее состояние
        user_states[user_id] = "waiting_for_age"

    elif current_state == "waiting_for_age":
        age = message.text
        await message.answer(f"Регистрация завершена! Ваш возраст: {age}")
        # Очищаем состояние после завершения диалога
        del user_states[user_id]

Преимущества:

  • Простота реализации для коротких диалогов.
  • Полный контроль над логикой хранения.

Недостатки:

  • Код быстро усложняется с ростом числа состояний.
  • Требуется вручную реализовывать логику переходов и очистки состояний.

Для сложных ботов использование встроенных механизмов FSM (aiogram.fsm) является предпочтительным.

Ответ 18+ 🔞

А, ну это ж классика, блядь! Опять про эти ваши состояния пользователей без FSM. Ну слушай, сейчас разжуем, как это бывает в жизни, а не в этих ваших академических справочниках.

Представь себе, что ты — царь и бог своего маленького ботика. И тебе надо запомнить, на каком этапе диалога находится каждый юзверь. FSM — это как нанять строгого менеджера, который будет следить за всеми переходами по правилам. А без него — ты сам этот менеджер, сука, и всё на твоей совести.

Куда можно пихать эти состояния, чтобы не потерять?

  1. В память, в обычный словарик. Это как записывать на салфетке в баре. Перезапустил бота — и всё, пиздец, салфетку выкинули. Только для теста, когда по-быстрому надо проверить идею.
  2. В Redis. О, это уже серьёзно! Быстро, как удар током, и можно сделать так, чтобы запись сама сдохла через время (TTL). Идеально для временных диалогов.
  3. В нормальную базу данных (SQLite, Postgres). Это уже на века, блядь. Если твой диалог — это многоэтапный квест, а данные надо хранить дольше, чем память золотой рыбки.

Смотри, как это выглядит в коде на aiogram, если ты решил быть мазохистом и всё делать руками:

from aiogram import Bot, Dispatcher, types

# ВАЖНО: Это пиздец как ненадёжно для продакшена! Перезапустил — и все твои пользователи как будто только что с Марса прилетели.
user_states = {}  # Вот наш "волшебный" словарик, где всё и хранится

bot = Bot(token="YOUR_API_TOKEN")
dp = Dispatcher(bot)

# Шаг 1: Юзер тыкает /register, а мы ему — развод
@dp.message_handler(commands=['register'])
async def start_registration(message: types.Message):
    user_id = message.from_user.id
    user_states[user_id] = "waiting_for_name"  # Записали: "Вася, ждём имя, не пизди"
    await message.answer("Введите ваше имя:")

# Шаг 2: А тут мы ловим все сообщения и смотрим — а не наш ли это клиент?
@dp.message_handler()
async def process_message(message: types.Message):
    user_id = message.from_user.id
    current_state = user_states.get(user_id)  # Достаём из кармана записку: на каком он этапе?

    if current_state == "waiting_for_name":
        name = message.text
        await message.answer(f"Отлично, {name}! Теперь введите ваш возраст.")
        # Переключаем рычажок в голове пользователя на следующий этап
        user_states[user_id] = "waiting_for_age"

    elif current_state == "waiting_for_age":
        age = message.text
        await message.answer(f"Регистрация завершена! Ваш возраст: {age}")
        # Всё, отжёг, стираем лапшу с ушей. Состояние можно удалять.
        del user_states[user_id]

И что мы имеем в сухом остатке, ёпта?

Плюсы (их мало, но они есть):

  • Для простого диалога из двух-трёх шагов — легко и быстро.
  • Ты как бог контролируешь каждый чих. Хочешь — в кэш пиши, хочешь — в базу.

Минусы (их дохуя и они злые):

  • Как только сценариев станет больше двух, твой код превратится в спагетти, которые даже макароны-монстр не распутает.
  • Всю логику переходов, очистки и проверок ты пишешь сам. Забыл почистить состояние — получил пользователя, который вечно ждёт возраста. Пиздец.

Так что, если твой бот — это не просто одноразовая игрушка, а что-то посерьёзнее, лучше всё-таки использовать встроенные FSM (aiogram.fsm). Это как ехать на велосипеде с мотором, а не толкать его ногами в гору. Но если хочешь почувствовать себя настоящим страдальцем — welcome to the club, делай всё руками.