0

I am developing a telegram bot with FSM. It is should've chat-bot for ordering pizza. The conversation should be like this:

  • What kind of pizza do you want? Big or small?
  • Great
  • How will you pay?
  • In cash
  • Do you want a big pizza, cash payment?
  • Yes
  • Thank you for the order

I've come with this code:

bot.py

import telebot
import config
import FSM

from telebot import types

bot = telebot.TeleBot(config.TOKEN)
fsm = FSM.TelegramBot()

@bot.message_handler(commands=['start'])
def welcome(message):

    #keyboard
    markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
    item1 = types.KeyboardButton("Заказать пиццу")
    item2 = types.KeyboardButton("Посмотреть меню")

    markup.add(item1, item2)

    bot.send_message(message.chat.id,
                     "Добро пожаловать, {0.first_name}!\nЯ - <b>{1.first_name}</b> бот, я помогу вам "
                     "сделать заказ.".format(
                         message.from_user, bot.get_me()),
                     parse_mode='html', reply_markup=markup)


@bot.message_handler(content_types=['text'])
@bot.message_handler(func=lambda message: fsm.state == 'asleep')
def order_pizza(message):
    bot.send_message(message.chat.id, 'Какую вы хотите пиццу? Большую или маленькую?')
    fsm.asked_for_payment_method(message.text)




@bot.message_handler(content_types=['text'])
@bot.message_handler(func=lambda message: fsm.state == 'size_selected')
def choose_size(message):
    bot.send_message(message.chat.id, 'Как вы будете платить?')
    fsm.asked_for_payment_method(message.text)

@bot.message_handler(content_types=['text'])
@bot.message_handler(func=lambda message: fsm.state == 'payment_selected')
def choose_size(message):
    bot.send_message(message.chat.id, 'Какой вкус вы хотите?')
    fsm.asked_for_flavour(message.text)
@bot.message_handler(content_types=['text'])
@bot.message_handler(func=lambda message: fsm.state == 'flavour_selected')
def choose_size(message):
    markup = types.InlineKeyboardMarkup(row_width=2)
    item1 = types.InlineKeyboardButton("Да", callback_data='yes')
    item2 = types.InlineKeyboardButton("Нет ", callback_data='no')
    markup.add(item1, item2)

    bot.send_message(message.chat.id, f'Вы хотите {fsm.size} пиццу {fsm.flavour}, оплата - {fsm.pay_method} ?', reply_markup=markup)





@bot.callback_query_handler(func=lambda call: True)
def callback_inline(call):
    try:
        if call.message:
            if call.data == 'yes':
                bot.send_message(call.message.chat.id, 'Спасибо за заказ')
                fsm.confirmed()
                # show alert
                bot.answer_callback_query(callback_query_id=call.id, show_alert=True,
                                          text="Заказ оформлен")
            elif call.data == 'no':
                markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
                item1 = types.KeyboardButton("Заказать пиццу")
                item2 = types.KeyboardButton("Посмотреть меню")

                markup.add(item1, item2)

                bot.send_message(call.message.chat.id, 'Бывает ', reply_markup=markup)
                fsm.confirmed()

            # remove inline buttons
            bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text=f'Вы хотите {fsm.size} пицца {fsm.flavour}, оплата - {fsm.pay_method} ?',
                                  reply_markup=None)



    except Exception as e:
        print(repr(e))


# RUN
bot.polling(none_stop=True)

And this code for Finite State Machine. I've used pytransitions / transitions

FSM.py

from transitions import Machine, State


class telegram_bot_state(object):

    states = ['asleep',
              'size_selected',
              'payment_selected',
              'flavour_selected']

    transitions = [
        {'trigger': 'asked_size', 'source': 'asleep', 'dest': 'size_selected', 'after': 'update_size'},
        {'trigger': 'asked_for_payment_method', 'source': 'size_selected', 'dest': 'payment_selected', 'after': 'update_payment'},
        {'trigger': 'asked_for_flavour', 'source': 'payment_selected', 'dest': 'flavour_selected','after': 'update_flavour'},
        {'trigger': 'confirmed', 'source': 'flavour_selected', 'dest': 'asleep'}, ]

    def __init__(self):
        self.size = ''
        self.pay_method = ''
        self.flavour = ''
        self.machine = Machine(model=self, states=TelegramBot.states, transitions=TelegramBot.transitions, initial='asleep')

    def update_size(self, msg):
        self.size = msg
        print(f'размер задан: {self.size}')


    def update_payment(self, msg):
        self.pay_method = msg
        print(f'способ оплаты задан: {self.pay_method}')


    def update_flavour(self, msg):
        self.flavour = msg
        print(f'Вкус пиццы задан: {self.flavour}')

But the problem is it just runs through everything and doesn't wait for the user's answer. Just send all the questins.

p.s. I'm sorry in advance if wrote something wrong this is my first question.

GermanGerken
  • 11
  • 1
  • 4
  • Welcome to SO, your provided code is quite complex. Could you try to reduce its complexity and get rid of unrelated dependencies (probably Telegram) to create a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example)? – aleneum Dec 13 '21 at 14:09

2 Answers2

0

For sequence use method bot.register_next_step_handler_by_chat_id and create functions with your name of this step. I'm hope this answer is helpful

0

This question is dated but has been viewed quite often. As of September 2022, I'd say that there is no need for transitions when you write a Telegram bot with pyTelegramBotAPI as it contains means for state-based logic and data storage.

There is a custom state example which illustrates how states can be used. I adapted this example for your use case:

import telebot # telebot

from telebot import custom_filters
from telebot.handler_backends import State, StatesGroup #States

# States storage
from telebot.storage import StateMemoryStorage


# Now, you can pass storage to bot.
state_storage = StateMemoryStorage() # you can init here another storage

bot = telebot.TeleBot("<YOUR TOKEN HERE>", state_storage=state_storage)


# States group.
class MyStates(StatesGroup):
    # Just name variables differently
    size = State() # creating instances of State class is enough from now
    payment = State()
    confirmation = State()


@bot.message_handler(commands=['order'])
def order(message):
    bot.set_state(message.from_user.id, MyStates.size, message.chat.id)
    bot.send_message(message.chat.id, 'What size of pizza do you want? Large or small?')
 

@bot.message_handler(state="*", commands=['cancel'])
def cancel(message):
    bot.send_message(message.chat.id, "Your order was cancelled.")
    bot.delete_state(message.from_user.id, message.chat.id)


@bot.message_handler(state=MyStates.size)
def get_size(message):
    inp = message.text.lower()
    if inp not in ["small", "large"]:
        bot.send_message(message.chat.id, 'Please enter "large" or "small".')
        return
    bot.send_message(message.chat.id, 'How will you pay? Cash or paypal?')
    bot.set_state(message.from_user.id, MyStates.payment, message.chat.id)
    with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
        data['size'] = inp
 
 
@bot.message_handler(state=MyStates.payment)
def get_payment(message):
    inp = message.text.lower()
    if inp not in ["cash", "paypal"]:
        bot.send_message(message.chat.id, 'Please enter "cash" or "paypal".')
        return    
    with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
        msg = ("Your order details\n---------\n"
               f"Size: {data['size']}\n"
               f"Payment: {inp}\n---------\n"
               f"Is that correct [yes/no]?")
    bot.send_message(message.chat.id, msg)
    bot.set_state(message.from_user.id, MyStates.confirmation, message.chat.id)

 
# result
@bot.message_handler(state=MyStates.confirmation)
def confirm_order(message):
    inp = message.text.lower()  
    if inp == "yes":
        bot.send_message(message.chat.id, "Great. The order is on its way.")
        bot.delete_state(message.from_user.id, message.chat.id)
        return
    elif inp == "no":
        bot.send_message(message.chat.id, "Okay. Let's start again.")
        order(message)
        return
    bot.send_message(message.chat.id, 'Please enter "yes" or "no".')


bot.add_custom_filter(custom_filters.StateFilter(bot))
bot.infinity_polling(skip_pending=True)
aleneum
  • 2,083
  • 12
  • 29