States

Warning!

This example requires a version of pyTelegramBotAPI that is equal to or higher than version 4.4.0. However, states are present from 4.1.1, but in version 4.4.0, there were significant changes related to states. More you can see in this Pull Request.

Getting input from a user

Do you remember input() in python? This function was used to get input from terminal. So, you may have a question, how do I get input text/photo/whatever in the bot?

Answer: by using states or register_next_step_handler

However, register_next_step_handler is quite an old thing and uncomfortable to use. Instead, we have an implementation of something similar to FSM.

States

The state is unique data. This unique data is not unique for each user: this data is common for getting common input.

Example

First of all, let's perform all imports:

from telebot.handler_backends import State, StatesGroup

State and StatesGroup are classes. To create a state, you need to create a SubClass of StatesGroup, and create some variables there, and those variables should be instances of State class:

class MyStates(StatesGroup):
    name = State()
    surname = State()
    age = State()
    # Each variable will return ClassName:variable_name

Now, you are ready to set_state.

Storages

Beginning from version 4.4.0, pyTelegramBotAPI supports storages for states. Before, we have supported pickle storage and memory storage, but in version 4.4.0, storages have been rewritten to support both group and private chat.

You can use either Redis, either Pickle or memory storages.

RedisStateStorage will store state and data in redis.

PickleStateStorage will store state and data in pickle file.

MemoryStateStorage will store state and data in dictionary.

Breaking change

In version 4.4.0 behaviour of states has changed, as well as its structure. If you were using PickleStorage, I recommend you to switch to new version and to change structure of storage by using instance of PickleStateStorage().convert_old_to_new().

Before initializing TeleBot, you will need to create an instance of one storages by importing them from telebot.storage:

from telebot.storage import <StorageName>
from telebot import custom_filters # import some filters
from telebot import TeleBot
# Storage names:
# RedisStateStorage, PickleStateStorage, MemoryStateStorage
# create instance of your storage:
state_storage = <StorageName>()
# If you are using pickle!:
state_storage.convert_old_to_new()
# now, pass storage:
bot = TeleBot('TOKEN', state_storage=state_storage)

Next thing I recommend to do, is registering state filter:

bot.add_custom_filter(custom_filters.StateFilter(bot)) # state filter
bot.add_custom_filter(custom_filters.IsDigitFilter()) # this filter 
# cheks whether message.text is digit or not

These filters are custom, you can read more on custom filters in the previous chapter.

So, we have everything ready for creating handlers like in example:

@bot.message_handler(commands=['start'])
def start_ex(message):
    """
    Start command. Here we are starting state
    """
    bot.set_state(message.from_user.id, MyStates.name, message.chat.id)
    bot.send_message(message.chat.id, 'Hi, write me a name')
 

# Any state
@bot.message_handler(state="*", commands='cancel')
def any_state(message):
    """
    Cancel state
    """
    bot.send_message(message.chat.id, "Your state was cancelled.")
    bot.delete_state(message.from_user.id, message.chat.id)

@bot.message_handler(state=MyStates.name)
def name_get(message):
    """
    State 1. Will process when user's state is MyStates.name.
    """
    bot.send_message(message.chat.id, f'Now write me a surname')
    bot.set_state(message.from_user.id, MyStates.surname, message.chat.id)
    with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
        data['name'] = message.text
 
 
@bot.message_handler(state=MyStates.surname)
def ask_age(message):
    """
    State 2. Will process when user's state is MyStates.surname.
    """
    bot.send_message(message.chat.id, "What is your age?")
    bot.set_state(message.from_user.id, MyStates.age, message.chat.id)
    with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
        data['surname'] = message.text
 
# result
@bot.message_handler(state=MyStates.age, is_digit=True)
def ready_for_answer(message):
    """
    State 3. Will process when user's state is MyStates.age.
    """
    with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
        bot.send_message(message.chat.id, "Ready, take a look:\n<b>Name: {name}\nSurname: {surname}\nAge: {age}</b>".format(name=data['name'], surname=data['surname'], age=message.text), parse_mode="html")
    bot.delete_state(message.from_user.id, message.chat.id)

#incorrect number
@bot.message_handler(state=MyStates.age, is_digit=False)
def age_incorrect(message):
    """
    Wrong response for MyStates.age
    """
    bot.send_message(message.chat.id, 'Looks like you are submitting a string in the field age. Please enter a number')

This was sample code for states. Now, let's understand methods for states.

Methods for States

TeleBot class contains some methods to manage states. Here is a list of all methods:

You can use functions from TeleBot class to manage states data, state itself.

Last updated