Finite state machine example

finite_state_machine_example.py
  1import logging
  2
  3import aiogram.utils.markdown as md
  4from aiogram import Bot, Dispatcher, types
  5from aiogram.contrib.fsm_storage.memory import MemoryStorage
  6from aiogram.dispatcher import FSMContext
  7from aiogram.dispatcher.filters import Text
  8from aiogram.dispatcher.filters.state import State, StatesGroup
  9from aiogram.types import ParseMode
 10from aiogram.utils import executor
 11
 12logging.basicConfig(level=logging.INFO)
 13
 14API_TOKEN = 'BOT TOKEN HERE'
 15
 16
 17bot = Bot(token=API_TOKEN)
 18
 19# For example use simple MemoryStorage for Dispatcher.
 20storage = MemoryStorage()
 21dp = Dispatcher(bot, storage=storage)
 22
 23
 24# States
 25class Form(StatesGroup):
 26    name = State()  # Will be represented in storage as 'Form:name'
 27    age = State()  # Will be represented in storage as 'Form:age'
 28    gender = State()  # Will be represented in storage as 'Form:gender'
 29
 30
 31@dp.message_handler(commands='start')
 32async def cmd_start(message: types.Message):
 33    """
 34    Conversation's entry point
 35    """
 36    # Set state
 37    await Form.name.set()
 38
 39    await message.reply("Hi there! What's your name?")
 40
 41
 42# You can use state '*' if you need to handle all states
 43@dp.message_handler(state='*', commands='cancel')
 44@dp.message_handler(Text(equals='cancel', ignore_case=True), state='*')
 45async def cancel_handler(message: types.Message, state: FSMContext):
 46    """
 47    Allow user to cancel any action
 48    """
 49    current_state = await state.get_state()
 50    if current_state is None:
 51        return
 52
 53    logging.info('Cancelling state %r', current_state)
 54    # Cancel state and inform user about it
 55    await state.finish()
 56    # And remove keyboard (just in case)
 57    await message.reply('Cancelled.', reply_markup=types.ReplyKeyboardRemove())
 58
 59
 60@dp.message_handler(state=Form.name)
 61async def process_name(message: types.Message, state: FSMContext):
 62    """
 63    Process user name
 64    """
 65    async with state.proxy() as data:
 66        data['name'] = message.text
 67
 68    await Form.next()
 69    await message.reply("How old are you?")
 70
 71
 72# Check age. Age gotta be digit
 73@dp.message_handler(lambda message: not message.text.isdigit(), state=Form.age)
 74async def process_age_invalid(message: types.Message):
 75    """
 76    If age is invalid
 77    """
 78    return await message.reply("Age gotta be a number.\nHow old are you? (digits only)")
 79
 80
 81@dp.message_handler(lambda message: message.text.isdigit(), state=Form.age)
 82async def process_age(message: types.Message, state: FSMContext):
 83    # Update state and data
 84    await Form.next()
 85    await state.update_data(age=int(message.text))
 86
 87    # Configure ReplyKeyboardMarkup
 88    markup = types.ReplyKeyboardMarkup(resize_keyboard=True, selective=True)
 89    markup.add("Male", "Female")
 90    markup.add("Other")
 91
 92    await message.reply("What is your gender?", reply_markup=markup)
 93
 94
 95@dp.message_handler(lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender)
 96async def process_gender_invalid(message: types.Message):
 97    """
 98    In this example gender has to be one of: Male, Female, Other.
 99    """
100    return await message.reply("Bad gender name. Choose your gender from the keyboard.")
101
102
103@dp.message_handler(state=Form.gender)
104async def process_gender(message: types.Message, state: FSMContext):
105    async with state.proxy() as data:
106        data['gender'] = message.text
107
108        # Remove keyboard
109        markup = types.ReplyKeyboardRemove()
110
111        # And send message
112        await bot.send_message(
113            message.chat.id,
114            md.text(
115                md.text('Hi! Nice to meet you,', md.bold(data['name'])),
116                md.text('Age:', md.code(data['age'])),
117                md.text('Gender:', data['gender']),
118                sep='\n',
119            ),
120            reply_markup=markup,
121            parse_mode=ParseMode.MARKDOWN,
122        )
123
124    # Finish conversation
125    await state.finish()
126
127
128if __name__ == '__main__':
129    executor.start_polling(dp, skip_updates=True)