Создание меню/кнопок в pyTelegramBotAPI на основе SQL запроса
В данной статье планирую поделиться с вами своей наработкой, которая позволяет создавать меню и кнопки вашего Telegram бота на основе данных хранящихся в БД.
Реализовывать все это будем на Python и нам потребуются библиотеки:
- sqlite3 — для работы с БД (устанавливать не нужно, поставляется в коробке с Питоном)
- pyTelegramBotAPI — для создания Telegram бота (предварительно необходимо установить)
Представьте, что мы имеем файл базы данных «database.db» с таблицей, которая называется «create_menu«. Эта таблица хранит следующую информацию:
btn_callback
type_menu — название меню, к которому будет относиться данная кнопка
order_num — порядковый номер кнопки в меню (сверху вниз)
btn_name — текст, который будет отображаться на кнопке
btn_callback — данные, которые будут возвращены при нажатии на кнопку. Их будет отлавливать обработчик событий.
Создадим класс CreateMenu и сразу инициализируем в нём путь к нашему файлу базы данных.
import os import sqlite3 from telebot import types class CreateMenu: def __init__(self): '''Конструктор класса. Определяет файл базы данных''' self.__db_path = os.getcwd() self.db_name = os.path.join(self.__db_path, 'database.db')
По умолчанию считаем, что файл БД находиться в одном каталоге с файлом программы, поэтому строим путь к нему используя os.getcwd() и os.path.join().
Теперь сделаем внутренний метод __connect() который будет отвечать за подключение к нашей БД с использованием библиотеки sqlite3.
def __connect(self): '''Функция подключения к базе данных''' connect = sqlite3.connect(self.db_name) return connect
Так же сделаем внутренний метод __select_button() который будет бегать в БД с SQL запросом и возвращать нам словарь (dict), где ключ (key) = название кнопки и значение (value) = callback data этой кнопки. Что бы этот метод не тащил абы чего, он будет принимать аргумент type_menu. Это позволит нам набирать кнопки под конкретное меню.
def __select_button(self, type_menu: str) -> dict: '''В качестве аргумента принимает "тип меню". Возвращает словарь где ключ = текст кнопки, значение = callbackdata кнопки''' with self.__connect() as connect: cursor = connect.cursor() sql = """SELECT btn_name, btn_callback FROM create_menu WHERE type_menu = (?) ORDER BY order_num""" select_db = cursor.execute(sql, (type_menu,)) result = dict() for btn_name, btn_callback in select_db.fetchall(): result[btn_name] = btn_callback return result
Думаю стоит объяснить, что за магия тут твориться.-
- Подключаемся к БД (используя ранее заготовленный __connect())
- Выполняем SQL запрос, который дословно можно перевести : «Покажи мне «название кнопки» и «её callback» из таблицы create_menu где тип меню равен type_menu и отсортируй все это по order_num в порядке возрастания.
- Создаем словарик в который будем записывать результат SQL запроса.
- Пробегаемся в цикле по результату SQL запроса и добавляем новые записи в словарь.
- Возвращаем из функции словарь. Например если мы передадим в качестве аргумента «main» функция вернёт словарь: )
Ну и давайте напишем единственный метод в классе, который будет вызываться программистом. Назовем его create_menu() и как вы догадались он будет создавать меню.
def create_menu(self, type_menu: str) -> types.InlineKeyboardMarkup: '''Создаём меню для TG бота''' markup = types.InlineKeyboardMarkup() btn_list = self.__select_button(type_menu) for element in btn_list.items(): btn = types.InlineKeyboardButton(text= element[0], callback_data= element[1]) markup.add(btn) return markup
Метод в качестве аргумента принимает тип меню (type_menu), которое мы хотим создать. Далее он с этим аргументам дёргает выше рассмотренный внутренний метод __select_button() и получает в свое распоряжение словарик из которого будет лепить меню.
По классике жанра, пробегает словарик в цикле и добавляет в меню (markup) кнопки, где текст кнопки — ключ словаря, а её обратный данные — значение из словаря.
Итоговый код выглядит следующим образом:
import os import sqlite3 from telebot import types class CreateMenu: def __init__(self): '''Конструктор класса. Определяет файл базы данных''' self.__db_path = os.getcwd() self.db_name = os.path.join(self.__db_path, 'database.db') def __connect(self): '''Функция подключения к базе данных''' connect = sqlite3.connect(self.db_name) return connect def __select_button(self, type_menu: str) -> dict: '''В качестве аргумента принимает "тип меню". Возвращает словарь где ключ = текст кнопки, значение = callbackdata кнопки''' with self.__connect() as connect: cursor = connect.cursor() sql = """SELECT btn_name, btn_callback FROM create_menu WHERE type_menu = (?) ORDER BY order_num""" select_db = cursor.execute(sql, (type_menu,)) result = dict() for btn_name, btn_callback in select_db.fetchall(): result[btn_name] = btn_callback return result def create_menu(self, type_menu: str) -> types.InlineKeyboardMarkup: '''Создаём меню для TG бота''' markup = types.InlineKeyboardMarkup() btn_list = self.__select_button(type_menu) for element in btn_list.items(): btn = types.InlineKeyboardButton(text= element[0], callback_data= element[1]) markup.add(btn) return markup
Теперь мы можем импортировать написанный нами класс в свой проект. Создать экземпляр класса CreateMenu и использовать его метод create_menu() для создания разного рода меню.
Примечание класс CreateMenu описывался мной в файле с именем db.py
import telebot from db import CreateMenu bot = telebot.TeleBot("ТОКЕН ВАШЕГО БОТА") cm = CreateMenu() @bot.message_handler(commands=["start"]) def start(message): bot.send_message(message.chat.id, "Добро пожаловать", reply_markup= cm.create_menu('main')) @bot.callback_query_handler(func=lambda call: True) def callback_inline(call): if call.data == 'buy': bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text="Что покупаем?", reply_markup= cm.create_menu('buy')) if call.data == 'sell': bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text="Что продаём?", reply_markup= cm.create_menu('sell')) if call.data == 'back': bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text="Добро пожаловать", reply_markup= cm.create_menu('main')) if call.data == 'help': bot.edit_message_text(chat_id=call.message.chat.id, message_id=call.message.message_id, text="Ничем не могу помочь тебе", reply_markup= cm.create_menu('help')) if __name__ == "__main__": bot.polling(none_stop=True)
Исходник проекта и файл базы данных из примера, вы найдёте у меня на GitHub
Надеюсь данный материал был полезен для вас!
Как сделать многоуровневое меню телеграм бота ( inline — callback) на python?
Как сделать, что бы при нажатии на кнопку — открывалось новое меню из нескольких кнопок, с возможностью вернутся к главному меню?
Надо добавить в bot.send_message необязательный параметр reply_markup=key (т.е. помимо отправления текста бот создаст меню), предварительно указав необходимую информацию.
Вот пример на скорую руку (изменение 3 кнопки):
@bot.message_handler(commands=["start"]) def inline(message): key = types.InlineKeyboardMarkup() but_1 = types.InlineKeyboardButton(text="NumberOne", callback_data="NumberOne") but_2 = types.InlineKeyboardButton(text="NumberTwo", callback_data="NumberTwo") but_3 = types.InlineKeyboardButton(text="NumberTree", callback_data="NumberTree") key.add(but_1, but_2, but_3) bot.send_message(message.chat.id, "ВЫБЕРИТЕ КНОПКУ", reply_markup=key) @bot.callback_query_handler(func=lambda c:True) def inline(c): if c.data == 'NumberOne': bot.send_message(c.message.chat.id, 'Это кнопка 1') if c.data == 'NumberTwo': bot.send_message(c.message.chat.id, 'Это кнопка 2') if c.data == 'NumberTree': key = types.InlineKeyboardMarkup() but_1 = types.InlineKeyboardButton(text="NumberOne", callback_data="NumberOne") but_2 = types.InlineKeyboardButton(text="NumberTwo", callback_data="NumberTwo") but_3 = types.InlineKeyboardButton(text="NumberTree", callback_data="NumberTree") key.add(but_1, but_2, but_3) bot.send_message(c.message.chat.id, 'Это кнопка 3', reply_markup=key)