Если погуглить, то решений пагинации не так то и много.
Есть библиотека telegram_bot_pagination
, но там пагинация выглядит следующим образом:
![фото 1
пагинация в библиотеке "telegram_bot_pagination" фото 1
пагинация в библиотеке "telegram_bot_pagination"](https://habrastorage.org/getpro/habr/upload_files/09a/cdd/830/09acdd830e4b69ec5ff6e6c9d70645e9.png)
Внизу кнопки, каждая из которых отвечает за определенную страницу
а хотелось бы что-то такое:
![фото 2
Пример пагинации из этой статьи фото 2
Пример пагинации из этой статьи](https://habrastorage.org/getpro/habr/upload_files/0d4/1c2/6fa/0d41c26faca20820e1d8a14ff8e63188.png)
Внизу кнопки перелистывания страниц
![фото 3 фото 3](https://habrastorage.org/getpro/habr/upload_files/794/191/7fa/7941917fab3dbc8a593df1e9e4de03d3.gif)
Есть похожее решение, только там скрываются кнопки Вправо
и Влево
на последней и Первой страницах соответственно.
Но смысл? Пусть на первой странице кнопка Влево
перелистывает на последнюю страницу, а кнопка Вправо
на последней странице - на первую. Да и зачем юзеру кнопка Скрыть
?
Для начала создадим бота
![фото 4 фото 4](https://habrastorage.org/getpro/habr/upload_files/921/3f0/e7d/9213f0e7dc12e0fa01fa7f1bb51ff25b.png)
Находим БотаФазера, пишем ему /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](https://habrastorage.org/getpro/habr/upload_files/e66/c57/27a/e66c5727a6ce72e545e1194961c0c79e.png)
Работает! Продолжаем.
Теперь добавим под сообщение кнопки
Воспользуемся нашими 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](https://habrastorage.org/getpro/habr/upload_files/a3d/af7/465/a3daf74655e4a51e52dfb00e2012834d.png)
Работает! Теперь надо отследить нажатие на кнопку.
Для этого надо создать обработчик
@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](https://habrastorage.org/getpro/habr/upload_files/c41/fef/40c/c41fef40cbef48899e898e7def6ee4df.png)
Рабооотает!
Теперь создадим кнопки управления
С начала наметим наши кнопки:
Пусть будет так:
![фото 8 фото 8](https://habrastorage.org/getpro/habr/upload_files/ee1/149/d37/ee1149d37808e99b08cdc5f6155187b2.png)
Сверху кнопки перелистывания, а внизу кнопки действия с сообщением, если требуется (например, купить товар, если это магазин)
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](https://habrastorage.org/getpro/habr/upload_files/801/fa7/a55/801fa7a559183e2130e7ce3cdc51d5f9.png)
В программе добавляем БД
![фото 10 фото 10](https://habrastorage.org/getpro/habr/upload_files/f3e/2a0/ad5/f3e2a0ad5abfdd820ede996cb9cb1225.png)
ыбираем/создаем файл db.db в папке с ботом
![фото 11 фото 11](https://habrastorage.org/getpro/habr/upload_files/a95/2c2/7c5/a952c27c5710bba124bdd58cd0f7c975.png)
Ниже пишем для удобства название которое будет отображаться в программе
жмем ОК
![фото 12 фото 12](https://habrastorage.org/getpro/habr/upload_files/e4f/f1a/215/e4ff1a215327390adb9e92296ef3f95d.png)
Слева появится название то которое вы указали во втором поле выше.
Жмете на >
и внизу выбираете Tables
чтобы просмотреть таблицы
![фото 13 фото 13](https://habrastorage.org/getpro/habr/upload_files/e75/55d/17d/e7555d17d63376cfb4a724653696bd94.png)
и видим ... ничего не видим. БД то пустая!
![фото 14 фото 14](https://habrastorage.org/getpro/habr/upload_files/c5d/bac/a19/c5dbaca1940e547d7c7f7d4cc8bdd92d.png)
Жмем на Creat Table
![фото 15 фото 15](https://habrastorage.org/getpro/habr/upload_files/9f3/d36/626/9f3d36626f2a21fdb4166792633604af.png)
После чего появится такая вкладка.
В поле 1 задаем имя таблицы - users
Далее создаем колонку (2)
![фото 16
фото 16](https://habrastorage.org/getpro/habr/upload_files/6e3/8f9/8eb/6e38f98eba3b21dbf5c75e8e8a7484d0.png)
В открывшемся окне в поле 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](https://habrastorage.org/getpro/habr/upload_files/335/de8/bc3/335de8bc3632c1d98a02f06c81771b48.png)
![фото 18
Таблица store. Структура фото 18
Таблица store. Структура](https://habrastorage.org/getpro/habr/upload_files/5b1/ad3/e0d/5b1ad3e0d9d8e6ee64e05383f1e0224c.png)
Итак, у нас есть БД. Теперь для теста создадим пару записей в store
.
![фото 19 фото 19](https://habrastorage.org/getpro/habr/upload_files/996/476/88e/99647688ea18949ca5963f4765958413.png)
Жмем на вкладку Data (1), Далее на кнопку Insert rows (2), ниже появится строчка. Кликаем по синим ячейкам и заполняем их.
page — 1, 2, 3...
Title — тестирую название 1.
description — тестирую описание 2
photo_path — путь или URL к картинке.
Не забываем сохранить! Кнопка Commit
(3)
У меня получилось так:
![фото 20
Таблица store. Данные фото 20
Таблица store. Данные](https://habrastorage.org/getpro/habr/upload_files/bf1/822/06b/bf182206bb539685fc251ea5b6c65ac5.png)
Итак, БД есть, Данные есть, Теперь подключимся к БД в пайтоне!
Будем использовать библиотеку 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](https://habrastorage.org/getpro/habr/upload_files/180/690/b6a/180690b6aa872a4cb98a6ee3298539ff.png)
все работет, с БД все ок. Теперь присобачим кнопки
к bot.send_photo() на 27 строке добавим аргумент reply_markup с значением buttons:
bot.send_photo(message.chat.id, photo=photo_path, caption=msg, reply_markup=buttons)
проверяем..
![фото 22 фото 22](https://habrastorage.org/getpro/habr/upload_files/fb3/6a1/a9b/fb36a1a9ba0696ef9d54068f22b87675.png)
Кнопки есть!
Сейчас раскажу саму суть работы кнопок.
При открытии какой-нибуть страницы в БД идет номер этой страницы (колонка 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` = {page} WHERE `users`.`id` = {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 за небольшую помощь в написании статьи
Если нашли ошибку - добро пожаловать в комментарии!
Комментарии (10)
alkosenk0
00.00.0000 00:00+2Все супер! Но что если не удалять сообщение, а его изменять? API телеграм позволяет исправлять сообщение по message_id. Кажется, что так было бы плавнее, без лишних мерцаний на экране. Да и если вдруг пропадет интернет и пройдет только первый запрос с удалением, то второго можно и не дождаться :)
RimMirK Автор
00.00.0000 00:00если вдруг пропадет интернет и пройдет только первый запрос с удалением, то второго можно и не дождаться
Лично у меня подобного не случалось, да и с начала я сообщение отправляю, а потом уже удаляю старое
novichikhin
00.00.0000 00:00+1Можно использовать фреймворк aiogram и библиотеку aiogram-dialog для реализации пагинации, упрощая себе жизнь.
RimMirK Автор
00.00.0000 00:00На некоторых хостигах почему-то блокируют айограм. + айограм более сложнее, на мой взгялд
rSedoy
00.00.0000 00:00И что это за такие хостинги? А по коду, не делай больше такой except без конкретики, да еще и с pass
RimMirK Автор
00.00.0000 00:00если не ошибаюсь, pythonAnyWhere.
не делай больше такой except без конкретики, да еще и с pass
А как лучше делать? Посоветуйте. Типа отлавливать ошибку удаления, а дальше что вместо
pass
?rSedoy
00.00.0000 00:00Вроде про это уже кучу раз было рассказано, ловить нужно конкретные исключения (пример, с открытием файла, там ты наверно FileNotFoundError ожидаешь?), а не все подряд, ну а если нужно всё таки ловить все (это не твой случай), то не замалчивать их, логируй их, да даже хотя бы print
WhiteApfel
00.00.0000 00:00+1А в колбэк кнопок мы просто будем засовывать страницу на которую надо перейти
Говно идея. Лучше записывать номер текущей страницы, а в логике кнопок уже добавлять или убавлять. Будет потом меньше гемора с тем, что страницы могут добавиться или пропасть. И, соответственно, искать не id=index+1, а первый элемент min(id>index)
red-cat-fat
Просто и со вкусом и подробно)
Для новичков самое то, одобряю