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

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

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

Есть похожее решение, только там скрываются кнопки Вправо и Влево на последней и Первой страницах соответственно.
Но смысл? Пусть на первой странице кнопка Влево перелистывает на последнюю страницу, а кнопка Вправо на последней странице - на первую. Да и зачем юзеру кнопка Скрыть?
Для начала создадим бота

Находим БотаФазера, пишем ему /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)Проверяем...

Работает! Продолжаем.
Теперь добавим под сообщение кнопки
Воспользуемся нашими 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)Запускаем, проверяем..

Работает! Теперь надо отследить нажатие на кнопку.
Для этого надо создать обработчик
@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, "Вы нажали на кнопку!")Опять перезапускаем, проверяем...

Рабооотает!
Теперь создадим кнопки управления
С начала наметим наши кнопки:
Пусть будет так:

Сверху кнопки перелистывания, а внизу кнопки действия с сообщением, если требуется (например, купить товар, если это магазин)
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

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

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

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

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

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

Жмем на Creat Table

После чего появится такая вкладка.
В поле 1 задаем имя таблицы - users
Далее создаем колонку (2)

В открывшемся окне в поле 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 таблицы


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

Жмем на вкладку Data (1), Далее на кнопку Insert rows (2), ниже появится строчка. Кликаем по синим ячейкам и заполняем их.
page — 1, 2, 3...
Title — тестирую название 1.
description — тестирую описание 2
photo_path — путь или URL к картинке.
Не забываем сохранить! Кнопка Commit (3)
У меня получилось так:

Итак, БД есть, Данные есть, Теперь подключимся к БД в пайтоне!
Будем использовать библиотеку 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)Запускаем, проверяем ...

все работет, с БД все ок. Теперь присобачим кнопки
к bot.send_photo() на 27 строке добавим аргумент reply_markup с значением buttons:
bot.send_photo(message.chat.id, photo=photo_path, caption=msg, reply_markup=buttons)проверяем..

Кнопки есть!
Сейчас раскажу саму суть работы кнопок.
При открытии какой-нибуть страницы в Б�� идет номер этой страницы (колонка 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
