Как реализовать кнопку "назад" в телеграмм боте?
Не пойму где ошибка при реализации кнопки "назад" в телеграмм боте? При выборе любой кнопки действие идет вперед, а кнопка назад "игнорируется". Часть кода прилагаю.
import telebot
from config import TELEGRAM_BOT_TOKEN
from telebot import types
bot = telebot.TeleBot(TELEGRAM_BOT_TOKEN)
users ={}
@bot.message_handler(commands=['start'])
def menu(message: telebot.types.Message):
chat_id = message.chat.id
keyboard1 = types.ReplyKeyboardMarkup(resize_keyboard=True)
button_glazing = telebot.types.KeyboardButton(text="Остекление окон", )
button_balcony = telebot.types.KeyboardButton(text="Остекление балкона/лоджии")
button_contacts = telebot.types.KeyboardButton(text="? Контакты")
button_portfolio = telebot.types.KeyboardButton(text="? Портфолио")
keyboard1.add(button_glazing, button_balcony, button_contacts, button_portfolio)
bot.send_message(chat_id,
'? Здравствуйте!Мы рады помочь вам с остеклением. Пожалуйста, выберите, что вас интересует:',
reply_markup=keyboard1)
@bot.message_handler(content_types=['text'])
def glazing(message):
if message.text == 'Остекление окон':
chat_id = message.chat.id
keyboard2 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_glazing_kvartira = telebot.types.KeyboardButton(text="Квартира")
button_glazing_dom = telebot.types.KeyboardButton(text="Дом")
button_glazing_office = telebot.types.KeyboardButton(text="Офис")
button_glazing_other = telebot.types.KeyboardButton(text="Другое помещение")
bot.register_next_step_handler(message, glazing_type)
button_glazing_back = telebot.types.KeyboardButton(text="Назад")
keyboard2.add(button_glazing_kvartira, button_glazing_dom, button_glazing_office, button_glazing_other, button_glazing_back )
bot.send_message(chat_id, 'Куда хотите установить окна?', reply_markup=keyboard2)
@bot.message_handler(content_types=['text'])
def glazing_type(message):
chat_id = message.chat.id
keyboard3 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_glazing_kv_window = telebot.types.KeyboardButton(text="Квартирное окно")
button_glazing_balcony_block = telebot.types.KeyboardButton(text="Балконный блок")
button_glazing_enter_block = telebot.types.KeyboardButton(text="Входная группа")
button_glazing_back = telebot.types.KeyboardButton(text="Назад")
keyboard3.add(button_glazing_kv_window, button_glazing_balcony_block, button_glazing_enter_block, button_glazing_back)
bot.send_message(chat_id, 'Какой тип изделия?', reply_markup=keyboard3)
if message.text == 'Квартира' or "Дом" or "Офис" or "Другое помещение":
bot.register_next_step_handler(message, glazing_profile)
elif message.text == 'Назад':
menu(message)
#
def glazing_profile(message):
chat_id = message.chat.id
keyboard4 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_glazing_rehau = telebot.types.KeyboardButton(text="Rehau")
button_glazing_kbe = telebot.types.KeyboardButton(text="KBE")
button_glazing_brusbox = telebot.types.KeyboardButton(text="Brusbox")
button_glazing_consultation = telebot.types.KeyboardButton(text="Нужна консультация")
button_glazing_profile_back = telebot.types.KeyboardButton(text="Назад")
keyboard4.add(button_glazing_rehau, button_glazing_kbe, button_glazing_brusbox,button_glazing_consultation, button_glazing_profile_back)
bot.send_message(chat_id, 'Выберите профиль', reply_markup=keyboard4)
bot.register_next_step_handler(message, glazing_size)
def glazing_size(message):
chat_id = message.chat.id
keyboard5 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_glazing_size_yes = telebot.types.KeyboardButton(text="Да, могу")
button_glazing_size_no = telebot.types.KeyboardButton(text="Нет, нужен замерщик")
button_glazing_size_back = telebot.types.KeyboardButton(text="Назад")
keyboard5.add(button_glazing_size_yes, button_glazing_size_no, button_glazing_size_back )
bot.send_message(chat_id, ' Можете указать размеры изделий?', reply_markup=keyboard5)
bot.register_next_step_handler(message, adress)
@bot.message_handler(
func=lambda message: message.text == 'Остекление балкона/лоджии')
def balcony(message):
chat_id = message.chat.id
keyboard6 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_balcony_hot = telebot.types.KeyboardButton(text="Тёплое остекление")
button_balcony_cold = telebot.types.KeyboardButton(text="Холодное остекление")
button_balcony_slidors = telebot.types.KeyboardButton(text="Slidors")
button_balcony_back = telebot.types.KeyboardButton(text="Назад")
keyboard6.add(button_balcony_hot, button_balcony_cold, button_balcony_slidors,button_balcony_back )
bot.send_message(chat_id, 'Выберите тип остекления:', reply_markup=keyboard6)
bot.register_next_step_handler(message, balcony_form)
def balcony_form(message):
chat_id = message.chat.id
keyboard7 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_form_1 = telebot.types.KeyboardButton(text="Прямая лоджия")
button_form_2 = telebot.types.KeyboardButton(text="Лодочка")
button_form_3 = telebot.types.KeyboardButton(text="Сапожек")
button_form_4 = telebot.types.KeyboardButton(text="П-образный")
button_form_5 = telebot.types.KeyboardButton(text="Другое")
button_form_back = telebot.types.KeyboardButton(text="Назад")
keyboard7.add(button_form_1, button_form_2, button_form_3, button_form_4, button_form_5, button_form_back )
bot.send_message(chat_id, 'Какая у вас форма балкона?', reply_markup=keyboard7)
bot.register_next_step_handler(message, balcony_type)
def balcony_type(message):
chat_id = message.chat.id
keyboard8 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_type_1 = telebot.types.KeyboardButton(text="Да")
button_type_2 = telebot.types.KeyboardButton(text="Нет")
button_type_3 = telebot.types.KeyboardButton(text="Другое")
button_type_back = telebot.types.KeyboardButton(text="Назад")
keyboard8.add(button_type_1, button_type_2, button_type_3, button_type_back )
bot.send_message(chat_id, 'Нужна ли обшивка?', reply_markup=keyboard8)
bot.register_next_step_handler(message, balcony_insulation)
def balcony_insulation(message):
chat_id = message.chat.id
keyboard9 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_insulation_type_1 = telebot.types.KeyboardButton(text="Да")
button_insulation_type_2 = telebot.types.KeyboardButton(text="Нет")
button_insulation_type_3 = telebot.types.KeyboardButton(text="Другое")
button_insulation_back = telebot.types.KeyboardButton(text="Назад")
keyboard9.add(button_insulation_type_1, button_insulation_type_2, button_insulation_type_3, button_insulation_back)
bot.send_message(chat_id, 'Утепление требуется?', reply_markup=keyboard9)
bot.register_next_step_handler(message, balcony_size)
def balcony_size(message):
chat_id = message.chat.id
keyboard10 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_balcony_size_1 = telebot.types.KeyboardButton(text="Да, могу")
button_balcony_size_2 = telebot.types.KeyboardButton(text="Нет, нужен замерщик")
button_balcony_size_back = telebot.types.KeyboardButton(text="Назад")
keyboard10.add(button_balcony_size_1, button_balcony_size_2, button_balcony_size_back)
bot.send_message(chat_id, 'Указать размеры?', reply_markup=keyboard10)
bot.register_next_step_handler(message, adress)
@bot.message_handler(
func=lambda message: message.text == '? Контакты')
def contacts(message):
chat_id = message.chat.id
keyboard11 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_gcontacts_back = telebot.types.KeyboardButton(text="? Главное меню")
keyboard11.add(button_gcontacts_back )
bot.send_message(chat_id, '? Наши контакты:'
'ул. Центральная, д. 10'
'? +7 (900) 000-00-00'
'✉ [email protected]'
'? WhatsApp: https://wa.me/79000000000'
'? Telegram: https://t.me/okna_manager', reply_markup=keyboard11)
bot.register_next_step_handler(message, glazing_type)
@bot.message_handler(
func=lambda message: message.text == '? Портфолио')
def portfolio(message):
chat_id = message.chat.id
keyboard12 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_gcontacts_back = telebot.types.KeyboardButton(text="? Главное меню")
keyboard12.add(button_gcontacts_back )
bot.send_message(chat_id, 'Отправка портфолио'
, reply_markup=keyboard12)
bot.register_next_step_handler(message, glazing_type)
def adress(message):
chat_id = message.chat.id
keyboard13 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_gcontacts_back = telebot.types.KeyboardButton(text="? Назад")
keyboard13.add(button_gcontacts_back )
bot.send_message(chat_id, 'Укажите ваш город/район'
, reply_markup=keyboard13)
bot.register_next_step_handler(message, phone_number)
def phone_number(message):
chat_id = message.chat.id
keyboard14 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_gcontacts_back = telebot.types.KeyboardButton(text="? Назад")
keyboard14.add(button_gcontacts_back )
bot.send_message(chat_id, 'Укажите ваш номер телефона'
, reply_markup=keyboard14)
bot.register_next_step_handler(message, enter_username)
def enter_username(message):
chat_id = message.chat.id
keyboard15 = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True)
button_gcontacts_back = telebot.types.KeyboardButton(text="? Назад")
keyboard15.add(button_gcontacts_back )
bot.send_message(chat_id, 'Укажите ваше имя'
, reply_markup=keyboard15)
bot.register_next_step_handler(message, glazing_type)
bot.polling()
Ответы (2 шт):
Главная проблема - условие.
if message.text == 'Квартира' or "Дом" or "Офис" or "Другое помещение":
В Python 'Дом' - это всегда True, так что ветка elif message.text == 'Назад' ни разу не сработает. Пиши так:
if message.text in ('Квартира', 'Дом', 'Офис', 'Другое помещение'): bot.register_next_step_handler(message, glazing_profile) elif message.text == 'Назад': menu(message)
У тебя несколько @bot.message_handler(content_types=['text']) без фильтров.
Все они цепляются за любое сообщение, порядок запуска неочевиден. исправь либо через func=‑фильтры, либо через finite‑state machine (pyTelegramBotAPI имеет telebot.states). Иначе 'Назад' ловит не тот хендлер и едет дальше.
Регистрируй следующий шаг после проверки 'Назад'. Сначала спросил - ждёшь ответ - разбираешь, что пришло. Не регистрируй хендлер заранее внутри меню, а то перескакиваешь уровни.
Простейший скелет:
def show_menu(chat_id):
kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
kb.add('Окна', '? Контакты')
bot.send_message(chat_id, 'Меню', reply_markup=kb)
bot.register_next_step_handler_by_chat_id(chat_id, route_main)
def route_main(msg):
if msg.text == 'Окна':
show_glazing(msg.chat.id)
else:
show_menu(msg.chat.id)
def show_glazing(chat_id):
kb = types.ReplyKeyboardMarkup(resize_keyboard=True)
kb.add('Квартира', 'Дом', 'Назад')
bot.send_message(chat_id, 'Куда ставим?', reply_markup=kb)
bot.register_next_step_handler_by_chat_id(chat_id, route_glazing)
def route_glazing(msg):
if msg.text == 'Назад':
show_menu(msg.chat.id)
else:
# дальше по цепочке
pass
Работает тупо, но честно: каждое меню своё, 'Назад' просто цепляет предыдущую функцию.
У вас на данный момент проблема заключается в том, что при переходах между шагами register_next_step_handler - намертво приколачивает следующий ввод\нажатие только к одному конкретному обработчику.
Что из этого следует?
Когда нажимаем "Назад", бот не понимает, что это команда BACK, потому что текущий обработчик назначенный register_next_step_handler ожидает сообщение, а не кнопку "Назад".
Кнопка "Назад" просто становится текстом, который вы никак специально не обрабатываете.
У вас НЕТ общей логики или памяти(стека состояния) о том, где пользователь был - история переходов ни как не хранится.
И вот получается, что кнопка "Назад" не работает, потому что бот всегда смотрит только вперёд, и ни шагу назад. А сама кнопка исключительно для красоты.
В общем, что бы это как-то исправить - нужна централизованная система, которая хранит состояние пользователя и умеет возвращать его назад, а не цепочка вызовов по шагам.
Конечно можно реализовать всё и цепочкой, как вам любезно предложили в другом ответе, но тогда при добавлении каждого нового блока нужно будет ВСЁ переделывать. Что очень быстро сведёт расширяемость бота на ноль, а его поддержка превратится в лютую головную боль.
Одним словам, нужно менять подход и искать более масштабируемую систему, а не строить логику работы на register_next_step_handler. В данном случае - это путь в никуда.
Небольшой пример для вас:
from typing import TypedDict, NotRequired, Callable, Dict
import telebot
from telebot import types
bot = telebot.TeleBot("TOKEN")
BACK = "? Назад"
MAIN_MENU = "main_menu"
def send_summary(user_id: int):
session = get_session(user_id)
data = session.data
if not data:
bot.send_message(user_id, "Пока нет собранных данных.")
return
text = "? Ваша заявка:\n"
for key, value in data.items():
text += f"• {key}: {value}\n"
bot.send_message(user_id, text)
session.reset()
class State(TypedDict):
text: str
transitions: NotRequired[Dict[str, str]]
next: NotRequired[str]
buttons: NotRequired[list[str]]
handler: NotRequired[Callable[[int], None]]
input: NotRequired[bool]
EMPTY_STATE: State = {"text": "Ошибка состояния", "next": MAIN_MENU}
fsm: Dict[str, State] = {
"main_menu": {
"text": "? Здравствуйте! Мы рады помочь вам с остеклением. Что вас интересует?",
"buttons": [
"Остекление окон",
"Остекление балкона/лоджии",
"? Контакты",
"? Портфолио",
],
"transitions": {
"Остекление окон": "glazing_place",
"Остекление балкона/лоджии": "balcony_type",
"? Контакты": "contacts",
"? Портфолио": "portfolio",
},
},
"glazing_place": {
"text": "Куда хотите установить окна?",
"buttons": ["Квартира", "Дом", "Офис", "Другое помещение"],
"transitions": {
"Квартира": "glazing_item",
"Дом": "glazing_item",
"Офис": "glazing_item",
"Другое помещение": "glazing_item",
},
},
"glazing_item": {
"text": "Какой тип изделия?",
"buttons": ["Квартирное окно", "Балконный блок", "Входная группа"],
"transitions": {
"Квартирное окно": "glazing_profile",
"Балконный блок": "glazing_profile",
"Входная группа": "glazing_profile",
},
},
"glazing_profile": {
"text": "Выберите профиль:",
"buttons": ["Rehau", "KBE", "Brusbox", "Нужна консультация"],
"transitions": {
"Rehau": "glazing_size",
"KBE": "glazing_size",
"Brusbox": "glazing_size",
"Нужна консультация": "glazing_size",
},
},
"glazing_size": {
"text": "Можете указать размеры изделий?",
"buttons": ["Да, могу", "Нет, нужен замерщик"],
"transitions": {"Да, могу": "adress", "Нет, нужен замерщик": "adress"},
},
"balcony_type": {
"text": "Выберите тип остекления:",
"buttons": ["Тёплое остекление", "Холодное остекление", "Slidors"],
"transitions": {
"Тёплое остекление": "balcony_form",
"Холодное остекление": "balcony_form",
"Slidors": "balcony_form",
},
},
"balcony_form": {
"text": "Какая у вас форма балкона?",
"buttons": ["Прямая лоджия", "Лодочка", "Сапожек", "П-образный", "Другое"],
"transitions": {
"Прямая лоджия": "balcony_sheathing",
"Лодочка": "balcony_sheathing",
"Сапожек": "balcony_sheathing",
"П-образный": "balcony_sheathing",
"Другое": "balcony_sheathing",
},
},
"balcony_sheathing": {
"text": "Нужна ли обшивка?",
"buttons": ["Да", "Нет", "Другое"],
"transitions": {
"Да": "balcony_insulation",
"Нет": "balcony_insulation",
"Другое": "balcony_insulation",
},
},
"balcony_insulation": {
"text": "Утепление требуется?",
"buttons": ["Да", "Нет", "Другое"],
"transitions": {
"Да": "balcony_size",
"Нет": "balcony_size",
"Другое": "balcony_size",
},
},
"balcony_size": {
"text": "Указать размеры?",
"buttons": ["Да, могу", "Нет, нужен замерщик"],
"transitions": {
"Да, могу": "adress",
"Нет, нужен замерщик": "adress",
},
},
"contacts": {
"text": (
"? Наши контакты:\n"
"ул. Центральная, д. 10\n"
"? +7 (900) 000-00-00\n"
"✉ [email protected]\n"
"? [WhatsApp](https://wa.me/79000000000)\n"
"? [Telegram](https://t.me/okna_manager)"
),
},
"portfolio": {
"text": "? Портфолио: [пример](https://ru.stackoverflow.com/users/562052/amgarak)",
},
"adress": {
"text": "Укажите ваш город/район:",
"next": "phone",
"input": True,
},
"phone": {
"text": "Укажите ваш номер телефона:",
"next": "name",
"input": True,
},
"name": {
"text": "Укажите ваше имя:",
"next": "summary",
"input": True,
},
"summary": {
"text": "Спасибо за информацию! Вот ваша заявка:",
"handler": send_summary,
},
}
class UserSession:
def __init__(self):
self.state_stack = [MAIN_MENU]
self.data = {}
def push_state(self, state: str):
self.state_stack.append(state)
def pop_state(self):
if len(self.state_stack) > 1:
removed_state = self.state_stack.pop()
if removed_state in self.data:
del self.data[removed_state]
def current_state(self) -> str:
return self.state_stack[-1]
def reset(self):
self.state_stack = [MAIN_MENU]
self.data = {}
sessions: Dict[int, UserSession] = {}
def get_session(user_id: int) -> UserSession:
return sessions.setdefault(user_id, UserSession())
def send_state(user_id: int):
session = get_session(user_id)
state_name = session.current_state()
state = fsm.get(state_name, EMPTY_STATE)
buttons = state.get("buttons", [])
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
for btn in buttons:
markup.add(types.KeyboardButton(btn))
if state_name != MAIN_MENU:
markup.add(types.KeyboardButton(BACK))
bot.send_message(
user_id, state.get("text", "..."), reply_markup=markup, parse_mode="Markdown"
)
handler = state.get("handler")
if callable(handler):
handler(user_id)
def is_text_input(message):
session = get_session(message.chat.id)
state = fsm.get(session.current_state(), EMPTY_STATE)
return state.get("input") is True
def is_menu_choice(message):
text = message.text
if not text:
return False
if text.startswith("/"):
return False
if is_text_input(message):
return False
if text == BACK:
return False
return True
@bot.message_handler(commands=["start"])
def handle_start(message):
session = get_session(message.from_user.id)
session.reset()
send_state(message.from_user.id)
@bot.message_handler(func=lambda message: message.text == BACK)
def handle_back(message):
session = get_session(message.chat.id)
session.pop_state()
send_state(message.chat.id)
@bot.message_handler(func=is_menu_choice)
def handle_menu_choice(message):
session = get_session(message.chat.id)
text = message.text.strip()
state_name = session.current_state()
state = fsm.get(state_name, EMPTY_STATE)
next_state = state.get("transitions", {}).get(text)
if next_state:
session.data[state_name] = text
session.push_state(next_state)
send_state(message.chat.id)
else:
bot.send_message(
message.chat.id, "Не понял. Пожалуйста, выберите один из вариантов."
)
@bot.message_handler(func=is_text_input)
def handle_text_states(message):
session = get_session(message.chat.id)
state_name = session.current_state()
state = fsm.get(state_name, EMPTY_STATE)
session.data[state_name] = message.text.strip()
next_state = state.get("next", MAIN_MENU)
session.push_state(next_state)
send_state(message.chat.id)
if __name__ == "__main__":
bot.polling(none_stop=True)
По итогу:
Для каждого пользователя есть свой стек состояний state_stack, который хранит последовательность его шагов по меню.
При переходе вперёд - новое состояние кладётся в стек push_state.
При нажатии "Назад" - последнее состояние удаляется из стека pop_state, возвращая пользователя на предыдущий шаг.
Сама структура целиком лежит в словаре fsm - по сути весь диалог с ботом описан в одном месте:
Текст, кнопки, куда нужно перейти по каждому выбору, нужно ли вводить текст.
Благодаря этому можно непринуждённо менять\удалять или добавлять шаги, не ломая себе мозг и остальной код.
И самое главное, при таком подходе мы получаем централизованное управление нашими обработчиками. Нам не нужно плодить 100500 message_handler, громоздить управление через десятки вложенных if, простраивать сложные цепочки через register_next_step_handler, а просто достаточно описать структуру в словаре и радоваться жизни.