Как настроить автоматическую остановку конечного автомата (FSM) через определенный промежуток времени?

Как реализовать <b>автоматическую остановку FSM (Finite State Machine) через заданный промежуток времени</b>, если пользователь не отправил сообщение? 

Например, пользователь инициирует команду /register, где ему нужно ввести свое имя. Если пользователь изменит свое решение и не отправит имя, забыв нажать кнопку отмены, я хочу, чтобы бот автоматически завершал текущее состояние через, скажем, 10 минут без получения сообщения от пользователя и уведомлял его о том, что время вышло. 

Мне нужно это для экономии ресурсов бота, и я не понимаю, как это можно реализовать. Буду признателен за любой пример кода, если вы уже сталкивались с подобной задачей, или за советы по ее реализации.
  • 2 ноября 2024 г. 17:12
Ответы на вопрос 1
Чтобы реализовать автоматическую остановку конечного автомата (FSM) на основе временного лимита, вам нужно создать таймер, который будет отсчитывать время, после истечения которого FSM покинет текущее состояние и уведомит пользователя о том, что время вышло. 

Приведу пример реализации на Python с использованием библиотеки `aiogram`, которая часто используется для создания Telegram-ботов.

### Пример кода

```python
import asyncio
from aiogram import Bot, Dispatcher, types
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext, State, StatesGroup
from aiogram.utils import executor

# Создаем бота и диспетчер
API_TOKEN = 'YOUR_API_TOKEN'
bot = Bot(token=API_TOKEN)
storage = MemoryStorage()
dp = Dispatcher(bot, storage=storage)

class Form(StatesGroup):
    waiting_for_name = State()  # Ожидание имени пользователя

async def timeout_task(chat_id, state):
    await asyncio.sleep(600)  # Задержка 10 минут
    # Проверим, находится ли пользователь все еще в этом состоянии
    if state == Form.waiting_for_name:
        await bot.send_message(chat_id, "Время вышло! Вы не ввели имя.")
        await state.finish()  # Завершаем состояние

@dp.message_handler(commands=['register'])
async def cmd_register(message: types.Message):
    await Form.waiting_for_name.set()
    await message.reply("Пожалуйста, введите ваше имя (или напишите /cancel для отмены).")
    
    # Запускаем таймер
    asyncio.create_task(timeout_task(message.chat.id, Form.waiting_for_name))

@dp.message_handler(state=Form.waiting_for_name)
async def process_name(message: types.Message, state: FSMContext):
    # Получаем и сохраняем имя пользователя
    user_name = message.text
    await state.update_data(name=user_name)
    
    await message.reply(f"Спасибо, {user_name}, вы зарегистрированы!")
    await state.finish()  # Завершаем состояние

@dp.message_handler(commands=['cancel'], state=Form.waiting_for_name)
async def cancel_registration(message: types.Message, state: FSMContext):
    await state.finish()  # Завершаем состояние
    await message.reply("Регистрация отменена.")

if __name__ == '__main__':
    executor.start_polling(dp, skip_updates=True)
```

### Объяснение

1. **Импортируем необходимые модули** и создаем экземпляры `Bot` и `Dispatcher`.

2. **Создаем состояния для FSM**. В нашем примере есть одно состояние `waiting_for_name`.

3. **Обработчик команды `/register`**:
    - Устанавливает состояние ожидания ввода имени.
    - Отправляет пользователю сообщение с просьбой ввести имя.
    - Запускает асинхронную задачу `timeout_task`, которая будет ожидать 10 минут (600 секунд) и проверит, все еще ли пользователь в состоянии ожидания имени.

4. **Обработчик для ввода имени**:
    - Если пользователь вводит имя, оно сохраняется, и пользователю отправляется подтверждение.
    - FSM завершается.

5. **Обработчик команды `/cancel`**:
    - Позволяет пользователю отменить процесс регистрации и завершить состояние FSM.

### Заметки
- Важно убедиться, что вы используете асинхронные функции и подходящие методы для работы с состояниями.
- Убедитесь также, что вы корректно обрабатываете сценарии, когда пользователь может завершить состояние до истечения времени.
- При использовании таймера, вам нужно убедиться, что на одном чате не запустится несколько таймеров (в зависимости от логики вашего бота). 

Этот код служит базовым примером, и вы можете адаптировать его под свои нужды, добавляя необходимые проверки и обработку ошибок.
Похожие вопросы