Пагинация в telegram-боте для чайников

от автора

Если погуглить, то решений пагинации не так то и много.

Есть библиотека telegram_bot_pagination, но там пагинация выглядит следующим образом:

фото 1 пагинация в библиотеке "telegram_bot_pagination"

фото 1 пагинация в библиотеке «telegram_bot_pagination»

Внизу кнопки, каждая из которых отвечает за определенную страницу

а хотелось бы что-то такое:

фото 2 Пример пагинации из этой статьи

фото 2 Пример пагинации из этой статьи

Внизу кнопки перелистывания страниц

фото 3

фото 3

Есть похожее решение, только там скрываются кнопки Вправо и Влево на последней и Первой страницах соответственно.

Но смысл? Пусть на первой странице кнопка Влево перелистывает на последнюю страницу, а кнопка Вправо на последней странице — на первую. Да и зачем юзеру кнопка Скрыть?


Для начала создадим бота

фото 4

фото 4

Находим БотаФазера, пишем ему /newbot — команду для создания бота. Отвечаем на его вопросы: Название бота; Username бота. Забираем от него токен.

В коде импортируем telebot — botAPI, types из telebot‘а для создания кнопок и sqlite3 для работы с Базой данных

import telebot from telebot import types import sqlite3

Далее создаем экземпляр класса TeleBot для дальнейшей работы. Проще говоря, создаем бота:

bot = telebot.TeleBot("TOKEN", parse_mode="MARKDOWN") 

parse_mode — это способ форматирования — MarkDown, MarkDownV2 или HTML

Добавим обработчик команды /start

Для проверки, сделаем так, чтобы бот приветствовал нас при запуске бота.

Для этого воспользуемся методом reply_to()
Для запуска воспользуемся методом polling() c параметром none_stop в значении True чтобы бот не выключался при ошибке.

# команда /start @bot.message_handler(commands=['start']) def start(message):     bot.reply_to(message, "приветствую тебя!") bot.polling(none_stop=True)

Проверяем…

фото 5

фото 5

Работает! Продолжаем.

Теперь добавим под сообщение кнопки

Воспользуемся нашими types‘ами: InlineKeyboardMarkup(), types.InlineKeyboardButton() и методом add()

buttons = types.InlineKeyboardMarkup() button  = types.InlineKeyboardButton("Button", callback_data="Button") buttons.add(button)

Через callback_data потом будем отлавливать клик

Теперь присоединим кнопки к сообщению с помощью параметра reply_markup.

bot.reply_to(message, "Приветствую тебя!", reply_markup=buttons)

Запускаем, проверяем..

фото 6

фото 6

Работает! Теперь надо отследить нажатие на кнопку.

Для этого надо создать обработчик

@bot.callback_query_handler(func=lambda c: True) def callback(c):   pass

наш callback_data приходит в c.data .Проверим. Если в нем «button» то отправим сообщение через bot.send_message()

@bot.callback_query_handler(func=lambda c: True) def callback(c): if c.data == 'Button':   bot.send_message(c.message.chat.id, "Вы нажали на кнопку!")

Опять перезапускаем, проверяем…

фото 7

фото 7

Рабооотает!

Теперь создадим кнопки управления

С начала наметим наши кнопки:
Пусть будет так:

фото 8

фото 8

Сверху кнопки перелистывания, а внизу кнопки действия с сообщением, если требуется (например, купить товар, если это магазин)

left_button  = types.InlineKeyboardButton("←",      callback_data="None") page_button  = types.InlineKeyboardButton("1/4",    callback_data="None")  right_button = types.InlineKeyboardButton("→",      callback_data="None") buy_button   = types.InlineKeyboardButton("КУПИТЬ", callback_data="None") buttons.add(left_button, page_button, right_button) buttons.add(buy_button)

Окей, кнопки есть. Теперь займемся содержимым

Получение данных из Базы Данных

для начала создадим колонку page)

Создаем базу данных

Для управления базой данных я использую SQLiteStudio

фото 9

фото 9

В программе добавляем БД

фото 10

фото 10

ыбираем/создаем файл db.db в папке с ботом

фото 11

фото 11

Ниже пишем для удобства название которое будет отображаться в программе

жмем ОК

фото 12

фото 12

Слева появится название то которое вы указали во втором поле выше.
Жмете на > и внизу выбираете Tables чтобы просмотреть таблицы

фото 13

фото 13

и видим … ничего не видим. БД то пустая!

фото 14

фото 14

Жмем на Creat Table

фото 15

фото 15

После чего появится такая вкладка.
В поле 1 задаем имя таблицы — users

Далее создаем колонку (2)

фото 16

фото 16

В открывшемся окне в поле Column name (1) пишем название столбца — id

Далее задаем тип данных (2) — INTEGER — целое число (в телеграме все id это цифры)

Задаём Primary Key (4) — это значит что данный столбец будет содержать только уникальные значения

Задаем not NULL — (5) — не ничего — это значит что эта колонка обязательно должна быть заполнена.

Жмем ОК

Прекрасно! Мы создали колонку id! Далее создадим еще одну колонку.

Создаем колонку (фото 15 2).

  • Название (фото 16 1): page

  • Тип данных (фото 16 2): INTEGER

  • Значение по умолчанию: 1

    • Устанавливаем галочку возле Dafault (фото 16 6)

    • Жмем Configure (фото 16 1)

    • В открывшемся окне пишем 1

    • Жмем Apply

  • Жмем ОК

Отлично! Теперь сохраним. Жмем на кнопку Commit structure changes (фото 15 3) или жмем сочетание клавиш Ctrl + C. Появится новое окно с кодом. Не пугайтесь, так надо, жмем ОК

Таблица users есть, теперь нужна таблица с информацией которую будем пагинировать

Жмем на Creat Table (фото 14).Задаем имя (фото 15 1). В моем случае это store — магазин. Создаем 4 колонки (фото 15 2).

  • Название (фото 16 1): page

  • Тип данных (фото 16 2): INTEGER

  • Задаем Primary Key (16 4)

  • и not NULL (фото 16 5)

  • Название (фото 16 1): title

  • Тип данных (фото 16 2): TEXT

  • Название (фото 16 1): description

  • Тип данных (фото 16 2): TEXT

  • Название (фото 16 1): photo_path

  • Тип данных (фото 16 2): TEXT

Не забываем сохранить (фото 15 3)

Должно получится 2 таблицы

фото 17 Таблица users

фото 17 Таблица users
фото 18 Таблица store. Структура

фото 18 Таблица store. Структура

Итак, у нас есть БД. Теперь для теста создадим пару записей в store.

фото 19

фото 19

Жмем на вкладку Data (1), Далее на кнопку Insert rows (2), ниже появится строчка. Кликаем по синим ячейкам и заполняем их.

page — 1, 2, 3…
Title — тестирую название 1.
description — тестирую описание 2 
photo_path — путь или URL к картинке.
Не забываем сохранить! Кнопка Commit (3)

У меня получилось так:

фото 20 Таблица store. Данные

фото 20 Таблица store. Данные

Итак, БД есть, Данные есть, Теперь подключимся к БД в пайтоне!

Будем использовать библиотеку sqlite3

connect = sqlite3.connect("db.db") # не забудьте поставить свое название БД, cursor  = connect.cursor()         # если оно у вас не такое!

с помощью cursor‘а будем обращаться к БД.

Для начала выведем первую страницу по команде /start

page_query = cursor.execute("SELECT `title`, `description`, `photo_path` FROM `store` WHERE `page` = 1;") title, description, photo_path = page_query.fetchone()

в переменных title, description и photo_path хранится соответственная информация из БД на странице 1 (!).

Теперь составим шаблон сообщения

# Название: *{title}* # Описание: *{description}*  msg = f"Название: *{title}*\nОписание: *{description}*"

Отправим фото с описанием с помощью bot.send_photo(), и присобачим кнопки

bot.send_photo(message.chat.id, photo=photo_path, caption=msg, reply_markup=buttons)

итак, весь наш код сейчас выглядит так:

import telebot from telebot import types import sqlite3  bot = telebot.TeleBot("TOKEN", parse_mode="MARKDOWN")   # команда /start @bot.message_handler(commands=['start']) def start(message):      buttons      = types.InlineKeyboardMarkup()     left_button  = types.InlineKeyboardButton("←",      callback_data="None")     page_button  = types.InlineKeyboardButton("1/4",    callback_data="None")      right_button = types.InlineKeyboardButton("→",      callback_data="None")     buy_button   = types.InlineKeyboardButton("КУПИТЬ", callback_data="None")     buttons.add(left_button, page_button, right_button)     buttons.add(buy_button)      connect = sqlite3.connect("db.db")     cursor = connect.cursor()      page_query = cursor.execute("SELECT `title`, `description`, `photo_path` FROM `store` WHERE `page` = '1';")     title, description, photo_path = page_query.fetchone()      msg = f"Название: *{title}*\nОписание: *{description}*"          bot.send_photo(message.chat.id, photo=photo_path, caption=msg)    @bot.callback_query_handler(func=lambda c: True) def callback(c):     if c.data == 'None':         bot.send_message(c.message.chat.id, "Вы нажали на кнопку!")    bot.polling(none_stop=True)

Запускаем, проверяем …

фото 21

фото 21

все работет, с БД все ок. Теперь присобачим кнопки
к bot.send_photo() на 27 строке добавим аргумент reply_markup с значением buttons:

bot.send_photo(message.chat.id, photo=photo_path, caption=msg, reply_markup=buttons)

проверяем..

фото 22

фото 22

Кнопки есть!

Сейчас раскажу саму суть работы кнопок.
При открытии какой-нибуть страницы в БД идет номер этой страницы (колонка page).
А в колбэк кнопок мы просто будем засовывать страницу на которую надо перейти.
То есть Делаем функцию которая показывает страницу (у нас она уже есть — start). Добавляем к ней атрибут page — номер страницы которую надо вывести. В колбэк кнопки «→» записываем номер данной страницы + 1. А в колбэк кнопки «←» номер страницы — 1.
При нажатии кнопки обработчик будет забирать номер страницы которую нужно вывести и вызывает функцию показа страницы (start) с параметром из колбэка.
Так-же в start надо добавить атрибут previous_message — в нем будет передаваться предидущее сообщение чтобы его удалить.

Итак. Сейчас создадим переменные right и left для колбэка.
НО! Помним, что кнопка Влево на первой странице кнопка должна перелистывать на последнюю страницу, а кнопка Вправо на последней странице — на первую.

По этому надо узнать количество страниц. Узнаем их по количеству строк в таблице store:

pages_count_query = cursor.execute(f"SELECT COUNT(*) FROM `store`") pages_count = int(pages_count_query.fetchone()[0])

Теперь создадим это переменные

left  = page-1 if page != 1 else pages_count right = page+1 if page != pages_count else 1

Теперь добавим значения в кнопки. Колбєк будет типа команды: to {страница}

left_button  = types.InlineKeyboardButton("←", callback_data=f'to {left}') page_button  = types.InlineKeyboardButton(f"{str(page)}/{str(pages_count)}", callback_data='_')  right_button = types.InlineKeyboardButton("→", callback_data=f'to {right}') buy_button   = types.InlineKeyboardButton("КУПИТЬ", callback_data='buy')

Теперь изменим нашу функцию, добавим аргументы page и previous_message
По-умолчаню page = 1

def start(message, page=1, previous_message=None):   # code   pass

Еще надо понимать, что сообщения могут быть как и с фотографией, так и без. При чём фотография может быть как и на компьютере, так и в интернете (ссылка).

Надо под это подстроится

try:      try: photo = open(photo_path, 'rb')     except: photo = photo_path     msg  = f"\[*{title}*]\nОписание: "     msg += f"*{description}*\n" if description != None else '_нет_\n'     bot.send_photo(message.chat.id, photo=photo, caption=msg, reply_markup=buttons) except:      msg  = f"\[*{title}*]\nОписание: "     msg += f"*{description}*\n" if description != None else '_нет_\n'     bot.send_message(message.chat.id, msg, reply_markup=buttons)

и на последок удаление сообщения:

try: bot.delete_message(message.chat.id, previous_message.id) except: pass

АГГГА! Чуть не забыл! Надо же еще доставать информацию из БД определенной страницы. Исправим.

product_query = cursor.execute(f"SELECT `title`, `description`, `photo_path` FROM `store` WHERE `page` = ?;", (page,)) title, description, photo_path = product_query.fetchone()

Вот так нормально. Переходим к обработчику.

Итак. В обработчике все просто. Вспоминаем план — «При нажатии кнопки обработчик будет забирать номер страницы которую нужно вывести и вызывает функцию показа страницы (start) с параметром из колбэка. «

То есть, проверяем, если есть в колбэке «to» то забираем то что после to и вызываем функцию старт с нужными параметрами.

# Обработчик callback @bot.callback_query_handler(func=lambda c: True) def callback(c):     if 'to' in c.data:          page = c.data.split(' ')[1]         start(c.message, page=page, previous_message=c.message)

Вот и пагинация готова.

Вот результат:

Вот полный код:

# Импорты import telebot import sqlite3 from telebot import types   # Создаем экземпляр бота (создаем бота) bot = telebot.TeleBot("ТОКЕН", parse_mode="MARKDOWN")   # команда /start     @bot.message_handler(commands=['start']) def start(message, page=1, previous_message=None):       connect = sqlite3.connect("habr_db.db")     cursor = connect.cursor()          pages_count_query = cursor.execute(f"SELECT COUNT(*) FROM `store`")     pages_count = int(pages_count_query.fetchone()[0])       product_query = cursor.execute(f"SELECT `title`, `description`, `photo_path` FROM `store` WHERE `page` = ?;", (page,))     title, description, photo_path = product_query.fetchone()       cursor.execute(f"UPDATE `users` SET `page` = ? WHERE `id` = ?;", (page,message.chat.id))     connect.commit()      buttons = types.InlineKeyboardMarkup()      left  = page-1 if page != 1 else pages_count     right = page+1 if page != pages_count else 1       left_button  = types.InlineKeyboardButton("←", callback_data=f'to {left}')     page_button  = types.InlineKeyboardButton(f"{str(page)}/{str(pages_count)}", callback_data='_')      right_button = types.InlineKeyboardButton("→", callback_data=f'to {right}')     buy_button   = types.InlineKeyboardButton("КУПИТЬ", callback_data='buy')     buttons.add(left_button, page_button, right_button)     buttons.add(buy_button)       try:          try: photo = open(photo_path, 'rb')         except: photo = photo_path         msg  = f"Название: *{title}*\nОписание: "         msg += f"*{description}*\n" if description != None else '_нет_\n'          bot.send_photo(message.chat.id, photo=photo, caption=msg, reply_markup=buttons)     except:          msg  = f"Название: *{title}*\nОписание: "         msg += f"*{description}*\n" if description != None else '_нет_\n'          bot.send_message(message.chat.id, msg, reply_markup=buttons)      try: bot.delete_message(message.chat.id, previous_message.id)     except: pass   # Обработчик callback @bot.callback_query_handler(func=lambda c: True) def callback(c):     if 'to' in c.data:          page = int(c.data.split(' ')[1])         start(c.message, page=page, previous_message=c.message)    # Запуск бота bot.polling(none_stop=True)   

Так-же хотел бы поблагодарить seeklay1337 за небольшую помощь в написании статьи

Если нашли ошибку — ctrl + Enter


ссылка на оригинал статьи https://habr.com/ru/articles/721516/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *