diff --git a/bot.py b/bot.py
index 6f0f9e0..4c1de30 100644
--- a/bot.py
+++ b/bot.py
@@ -188,21 +188,52 @@ async def on_media_during_keywords(msg: Message):
# ── /list ────────────────────────────────────────────────────────────────────
+LIST_PAGE_SIZE = 30
+
+
+async def list_page(page: int) -> tuple[str, InlineKeyboardMarkup | None]:
+ total = await db.count_media()
+ if total == 0:
+ return "No memes saved yet.", None
+
+ total_pages = (total + LIST_PAGE_SIZE - 1) // LIST_PAGE_SIZE
+ page = max(0, min(page, total_pages - 1))
+ items = await db.list_media(page, LIST_PAGE_SIZE)
+ lines = [f"{item['id']} [{item['media_type']}] — {item['keywords']}" for item in items]
+ text = f"Page {page + 1} of {total_pages}\n" + "\n".join(lines)
+
+ buttons = []
+ if page > 0:
+ buttons.append(InlineKeyboardButton(text="‹ Previous", callback_data=f"list:{page - 1}"))
+ if page < total_pages - 1:
+ buttons.append(InlineKeyboardButton(text="Next ›", callback_data=f"list:{page + 1}"))
+ keyboard = InlineKeyboardMarkup(inline_keyboard=[buttons]) if buttons else None
+ return text, keyboard
+
+
@dp.message(Command("list"))
async def cmd_list(msg: Message):
if not is_allowed(msg.from_user.id):
return
- items = await db.list_media()
- if not items:
- await msg.answer("No memes saved yet.")
+ text, keyboard = await list_page(0)
+ await msg.answer(text, parse_mode="HTML", reply_markup=keyboard)
+
+
+@dp.callback_query(F.data.startswith("list:"))
+async def on_list_page(callback: CallbackQuery):
+ if not is_allowed(callback.from_user.id):
+ await callback.answer("Not allowed.", show_alert=True)
return
- lines = []
- for item in items[:30]:
- lines.append(f"{item['id']} [{item['media_type']}] — {item['keywords']}")
- text = "\n".join(lines)
- if len(items) > 30:
- text += f"\n…and {len(items) - 30} more."
- await msg.answer(text, parse_mode="HTML")
+
+ try:
+ page = int(callback.data.split(":", 1)[1])
+ except ValueError:
+ await callback.answer("Invalid page.", show_alert=True)
+ return
+
+ text, keyboard = await list_page(page)
+ await callback.message.edit_text(text, parse_mode="HTML", reply_markup=keyboard)
+ await callback.answer()
# ── /delete ──────────────────────────────────────────────────────────────────
diff --git a/db.py b/db.py
index 05d3101..4d0b297 100644
--- a/db.py
+++ b/db.py
@@ -59,8 +59,29 @@ async def search_media(query: str) -> list[dict]:
return [dict(r) for r in rows]
-async def list_media() -> list[dict]:
- return await search_media("")
+async def list_media(page: int, page_size: int) -> list[dict]:
+ """Return one page of saved media, newest first."""
+ offset = page * page_size
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ cursor = await db.execute(
+ """
+ SELECT id, file_id, file_unique_id, media_type, keywords
+ FROM media
+ ORDER BY added_at DESC, id DESC
+ LIMIT ? OFFSET ?
+ """,
+ (page_size, offset),
+ )
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
+
+
+async def count_media() -> int:
+ async with aiosqlite.connect(DB_PATH) as db:
+ cursor = await db.execute("SELECT COUNT(*) FROM media")
+ row = await cursor.fetchone()
+ return row[0]
async def get_media_by_id(media_id: int) -> dict | None: