from math import ceil from typing import Final from aiogram import Router, F from aiogram.fsm.context import FSMContext from aiogram.types import ( Message, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, SwitchInlineQueryChosenChat, CopyTextButton ) from aiogram.exceptions import TelegramBadRequest from aiogram.utils.markdown import hide_link from bot.core import storage from bot.utils import pagination_btn router: Router = Router(name="posts_manager_router") PAGE_SIZE: Final[int] = 5 async def send_posts_list( message: Message = None, callback_query: CallbackQuery = None, page: int = 0 ) -> None: """Отправляет список постов пользователя с пагинацией.""" user_id = message.from_user.id if message else callback_query.from_user.id posts = storage.load_user_posts(user_id) if not posts: msg = "Нет сохранённых постов." if message: await message.answer(msg) else: await callback_query.answer(msg, show_alert=True) return post_ids = list(posts.keys()) total = len(post_ids) pages = ceil(total / PAGE_SIZE) page = max(0, min(page, pages - 1)) start = page * PAGE_SIZE end = start + PAGE_SIZE current_ids = post_ids[start:end] rows: list[list[InlineKeyboardButton]] = [] for pid in current_ids: post = posts[pid] priv = "🔒" if post.get("private") else "🔓" btn = InlineKeyboardButton( text=f"{priv} Пост {pid}", callback_data=f"view_post_{pid}" ) rows.append([btn]) # Пагинация nav_buttons = pagination_btn( action="open_post_list", page=page, total_posts=total, bt_page=PAGE_SIZE ) if nav_buttons: rows.append(nav_buttons) # Кнопка закрытия rows.append([InlineKeyboardButton(text="Закрыть❌", callback_data="cancel_list")]) keyboard = InlineKeyboardMarkup(inline_keyboard=rows) header: str = "Список ваших постов:" try: if callback_query: await callback_query.message.edit_text(header, reply_markup=keyboard) else: await message.answer(header, reply_markup=keyboard) except TelegramBadRequest: if callback_query: await callback_query.message.delete() await callback_query.message.answer(header, reply_markup=keyboard) else: await message.answer(header, reply_markup=keyboard) # --- Хендлеры списка --- @router.message(F.text.lower() == "посмотреть список📋") async def cmd_list(message: Message, state: FSMContext): # Сбрасываем состояние перед показом списка await state.clear() await send_posts_list(message=message) @router.callback_query(F.data == "open_post_list") async def cb_open_list(cq: CallbackQuery, state: FSMContext): await state.clear() await send_posts_list(callback_query=cq) await cq.answer() @router.callback_query(F.data.startswith("open_post_list_page_")) async def cb_paginate(cq: CallbackQuery, state: FSMContext): try: page = int(cq.data.rsplit("_", 1)[-1]) except ValueError: await cq.answer("Некорректная страница", show_alert=True) return await state.clear() await send_posts_list(callback_query=cq, page=page) await cq.answer() @router.callback_query(F.data == "cancel_list") async def cb_cancel(cq: CallbackQuery): await cq.message.delete() await cq.answer() @router.callback_query(F.data.startswith("view_post_")) async def view_post_callback(cq: CallbackQuery): """Просмотр отдельного поста""" pid = cq.data.replace("view_post_", "") uid = cq.from_user.id posts = storage.load_user_posts(uid) if pid not in posts: await cq.answer("Пост не найден", show_alert=True) return post = posts[pid] text = post.get("text", "") img = post.get("image", "") if img.startswith("http"): text = f"{hide_link(img)}{text}" rows: list[list[InlineKeyboardButton]] = [] for row in post.get("buttons", []): btns: list[InlineKeyboardButton] = [] for b in row: if "copy_text" in b: btns.append( InlineKeyboardButton( text=b["text"], copy_text=CopyTextButton(text=b["copy_text"]) ) ) elif "switch_inline_query" in b: btns.append( InlineKeyboardButton( text=b["text"], switch_inline_query=b["switch_inline_query"] ) ) elif "switch_inline_query_current_chat" in b: btns.append( InlineKeyboardButton( text=b["text"], switch_inline_query_current_chat=b["switch_inline_query_current_chat"] ) ) elif "switch_inline_query_chosen_chat" in b: raw = b["switch_inline_query_chosen_chat"] cfg = raw if isinstance(raw, dict) else { "query": raw, "allow_user_chats": True } btns.append( InlineKeyboardButton( text=b["text"], switch_inline_query_chosen_chat=SwitchInlineQueryChosenChat(**cfg) ) ) elif "url" in b: url = b["url"] if url.lower().endswith("void"): btns.append( InlineKeyboardButton(text=b["text"], callback_data="void") ) else: btns.append( InlineKeyboardButton(text=b["text"], url=url) ) elif "callback_data" in b: btns.append( InlineKeyboardButton(text=b["text"], callback_data=b["callback_data"]) ) if btns: rows.append(btns) # Удалить / назад rows.append([ InlineKeyboardButton(text="Удалить❌", callback_data=f"delete_post_{pid}"), InlineKeyboardButton(text="Назад◀️", callback_data="open_post_list") ]) rows.append( [InlineKeyboardButton(text="Отправить↪️", switch_inline_query=f"{pid}")]) keyboard = InlineKeyboardMarkup(inline_keyboard=rows) await cq.message.answer(text=text, reply_markup=keyboard) await cq.message.delete() await cq.answer() @router.callback_query(lambda c: c.data and c.data.startswith("delete_post_")) async def delete_post_callback(cq: CallbackQuery, state: FSMContext): """Удаление поста.""" pid = cq.data.replace("delete_post_", "") uid = cq.from_user.id if storage.delete_user_post(uid, pid): await cq.answer(f"Пост {pid} удалён") await state.clear() await send_posts_list(callback_query=cq) else: await cq.answer(text="Не удалось удалить пост", show_alert=True)