Ответ
Для организации последовательных диалогов (опросов, анкет) в Telegram-ботах на Python используется механизм машины состояний (Finite State Machine, FSM).
Он позволяет "запоминать", на каком шаге диалога находится пользователь, и направлять его следующее сообщение в нужный обработчик. В популярной библиотеке aiogram это реализуется через FSMContext.
Принцип работы:
- Определение состояний: Создается класс, наследуемый от
StatesGroup, где каждое состояние — это шаг в диалоге. - Установка состояния: При старте диалога бот устанавливает пользователю начальное состояние (например,
await Form.question1.set()). - Обработка по состоянию: Создаются хендлеры, которые срабатывают только для пользователей в определенном состоянии.
- Сохранение данных и переход: Внутри хендлера ответ пользователя сохраняется во временное хранилище (
state.proxy()) и бот переводит пользователя в следующее состояние (await Form.next()). - Завершение: После последнего ответа состояние сбрасывается (
await state.finish()).
Пример на aiogram:
from aiogram import Bot, Dispatcher, types
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.contrib.fsm_storage.memory import MemoryStorage
# 1. Определяем состояния
class UserPoll(StatesGroup):
name = State() # Состояние для ожидания имени
age = State() # Состояние для ожидания возраста
city = State() # Состояние для ожидания города
# Хендлер для начала опроса
async def start_poll(message: types.Message):
await UserPoll.name.set() # 2. Устанавливаем первое состояние
await message.answer("Как вас зовут?")
# 3. Хендлер, который ловит ответ, когда пользователь в состоянии 'name'
async def process_name(message: types.Message, state: FSMContext):
async with state.proxy() as data: # 4. Сохраняем данные
data['name'] = message.text
await UserPoll.next() # Переходим к следующему состоянию (age)
await message.answer("Сколько вам лет?")
# Хендлер для состояния 'age'
async def process_age(message: types.Message, state: FSMContext):
async with state.proxy() as data:
data['age'] = message.text
await UserPoll.next() # Переходим к следующему состоянию (city)
await message.answer("Из какого вы города?")
# Хендлер для последнего состояния 'city'
async def process_city(message: types.Message, state: FSMContext):
async with state.proxy() as data:
data['city'] = message.text
# Выводим собранные данные
await message.answer(f"Спасибо! Ваши данные: Имя - {data['name']}, Возраст - {data['age']}, Город - {data['city']}")
await state.finish() # 5. Завершаем диалог и сбрасываем состояние
# Регистрация хендлеров
def register_handlers(dp: Dispatcher):
dp.register_message_handler(start_poll, commands=['poll'], state=None)
dp.register_message_handler(process_name, state=UserPoll.name)
dp.register_message_handler(process_age, state=UserPoll.age)
dp.register_message_handler(process_city, state=UserPoll.city)
Важно: По умолчанию aiogram использует хранилище в памяти (MemoryStorage), которое сбрасывается при перезапуске бота. Для продакшена следует использовать внешние хранилища, такие как RedisStorage или MongoStorage, чтобы состояния пользователей не терялись.
Ответ 18+ 🔞
А, вот эта тема про диалоги в ботах! Ну, блядь, классика же, каждый второй разработчик на этом спотыкается, как пьяный о бордюр. Слушай, сейчас разжую, как бабушка манную кашу.
Так вот, смотри. Чтобы бот не был тупым, как пробка, и мог вести нормальный диалог — типа «спросил имя, спросил возраст, запомнил всё это дело» — нужна какая-то память. А то он тебе на втором вопросе уже забудет, что ты на первый ответил, и будет как тот Герасим из «Муму» — мычит одно и то же.
Для этого в aiogram есть такая штука — машина состояний, или FSM. Это не «Фонд Свободных Макдаков», а Finite State Machine. Суть в том, что бот запоминает, на каком шаге диалога ты сейчас находишься. Представь себе анкету в военкомате: пока не заполнишь первую страницу, на вторую не пустят. Вот тут так же.
Как это работает, ёпта:
- Придумываем шаги. Создаёшь класс, где каждый шаг диалога — это отдельное «состояние». Как очередь в поликлинике: «запись», «кабинет врача», «аптека».
- Ставим в очередь. Когда пользователь начинает диалог, бот ему говорит: «Ты теперь в состоянии „ждать имени“». И ставит его в эту виртуальную очередь.
- Ловим по шагам. Пишем обработчики, которые срабатывают только если пользователь находится в конкретном состоянии. Не перепутает имя с возрастом.
- Сохраняем и двигаем дальше. Получил ответ — сохранил его в карман (во временное хранилище) и перевёл бедолагу на следующий шаг.
- Выпускаем с миром. Когда все вопросы кончились — собрал все ответы из кармана, отдал результат и сбросил состояние. Человек свободен!
Смотри, как это в коде выглядит, блядь:
from aiogram import Bot, Dispatcher, types
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.contrib.fsm_storage.memory import MemoryStorage
# 1. Рисуем нашу анкету — какие шаги будут.
class UserPoll(StatesGroup):
name = State() # Шаг первый: ждём, когда пользователь имя назовёт
age = State() # Шаг второй: тыкаем его, чтобы возраст сказал
city = State() # Шаг третий: допытываемся про город
# Хендлер, который запускает весь этот цирк
async def start_poll(message: types.Message):
await UserPoll.name.set() # 2. Ставим юзера на первую ступеньку
await message.answer("Ну-ка, быстро своё имя сказал!")
# 3. Этот хендлер ждёт, пока юзер будет в состоянии 'name' и ответит
async def process_name(message: types.Message, state: FSMContext):
async with state.proxy() as data: # 4. Достаём карман (прокси) для данных
data['name'] = message.text # Пихаем имя в карман
await UserPoll.next() # Команда "Шаг вперёд!" Переводим на этап 'age'
await message.answer("Окей. А лет сколько отроду?")
# Ловим на втором шаге
async def process_age(message: types.Message, state: FSMContext):
async with state.proxy() as data:
data['age'] = message.text # Суём возраст в тот же карман
await UserPoll.next() # И снова "Шаг вперёд!" Теперь состояние 'city'
await message.answer("Ясненько. И из какого ты города, красавец?")
# Финальный аккорд
async def process_city(message: types.Message, state: FSMContext):
async with state.proxy() as data:
data['city'] = message.text # Последнюю инфу в карман
# Теперь всё вываливаем обратно пользователю
await message.answer(f"Вот и славно! Записал: Зовут - {data['name']}, Возраст - {data['age']}, Город - {data['city']}. Можешь идти.")
await state.finish() # 5. Всё, диалог окончен! Состояние обнуляем.
# Ну и не забываем всё это богатство зарегистрировать
def register_handlers(dp: Dispatcher):
dp.register_message_handler(start_poll, commands=['poll'], state=None)
dp.register_message_handler(process_name, state=UserPoll.name)
dp.register_message_handler(process_age, state=UserPoll.age)
dp.register_message_handler(process_city, state=UserPoll.city)
Вот тут важный момент, блядь, как гвоздь в ботинке! По умолчанию aiogram использует MemoryStorage — это как память у золотой рыбки, всё в оперативке. Перезапустил бота — и все твои полузаполненные анкеты накрылись медным тазом, пользователи охуеют. Для чего-то серьёзного надо ставить нормальное хранилище — RedisStorage или MongoStorage. Чтобы состояния переживали перезапуски и не терялись, как носки в стирке.