В данной статье планирую поделиться с вами своей наработкой, которая позволяет создавать меню и кнопки вашего Telegram бота на основе данных хранящихся в БД.
Реализовывать все это будем на Python и нам потребуются библиотеки:
sqlite3 - для работы с БД (устанавливать не нужно, поставляется в коробке с Питоном)
pyTelegramBotAPI - для создания Telegram бота (предварительно необходимо установить)
Представьте, что мы имеем файл базы данных "database.db" с таблицей, которая называется "create_menu". Эта таблица хранит следующую информацию:
type_menu | order_num | btn_name | btn_callback |
buy | 1 | 🍏Яблоки | apple |
buy | 2 | 🍋Лимоны | lemon |
buy | 3 | 🍌Бананы | banana |
buy | 4 | 🔙Назад | back |
help | 1 | 🔙Назад | back |
main | 1 | 💸Купить | buy |
main | 2 | 💰Продать | sell |
main | 3 | 🆘Помощь | help |
sell | 1 | 🍅Томаты | tomato |
sell | 2 | 🥥Кокосы | coconut |
sell | 3 | 🥭Манго | mango |
sell | 4 | 🔙Назад | back |
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" функция вернёт словарь: {'💸Купить': 'buy', '💰Продать': 'sell', '🆘Помощь': 'help'})
Ну и давайте напишем единственный метод в классе, который будет вызываться программистом. Назовем его 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
Надеюсь данный материал был полезен для вас!
Спасибо за внимание!
