telegram-Bot / keyboards /inline.py
Ahmad3g's picture
telegram Commit 1
9a1712b
"""
keyboards/inline.py β€” All inline keyboard builders for the bot UI.
Design principles:
- Folders use a 2-column grid layout.
- Every nested menu has Back and Home buttons.
- Pagination is included for large lists (> ITEMS_PER_PAGE).
- Callback data uses a structured format: "action:param1:param2"
"""
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import config
def _back_home_row(back_callback: str, home_callback: str = "home") -> list[InlineKeyboardButton]:
"""Returns a standard [πŸ”™ Back] [🏠 Home] button row."""
return [
InlineKeyboardButton(text="πŸ”™ Back", callback_data=back_callback),
InlineKeyboardButton(text="🏠 Home", callback_data=home_callback),
]
# ─────────────────────────── MAIN MENUS ───────────────────────────
def main_menu_user() -> InlineKeyboardMarkup:
"""The main menu for regular users."""
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="πŸ“š Browse Knowledge Base", callback_data="browse:root:0"))
builder.row(InlineKeyboardButton(text="ℹ️ About", callback_data="about"))
return builder.as_markup()
def main_menu_admin(view_as_user: bool = False) -> InlineKeyboardMarkup:
"""
Admin main menu. Includes a toggle to preview as a regular user.
When view_as_user=True, management buttons are hidden.
"""
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="πŸ“š Browse Knowledge Base", callback_data="browse:root:0"))
if not view_as_user:
# Show full admin controls
builder.row(InlineKeyboardButton(text="πŸ“ Manage Folders", callback_data="manage:folders:0"))
builder.row(InlineKeyboardButton(text="πŸ“Š Admin Dashboard", callback_data="admin:dashboard"))
builder.row(InlineKeyboardButton(text="πŸ‘οΈ View as User", callback_data="toggle:user_view"))
else:
# Preview mode β€” only show the return button
builder.row(InlineKeyboardButton(text="πŸ‘οΈ Return to Admin View", callback_data="toggle:admin_view"))
return builder.as_markup()
def main_menu_owner(view_as_user: bool = False) -> InlineKeyboardMarkup:
"""Owner main menu β€” extends admin menu with owner-only controls."""
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="πŸ“š Browse Knowledge Base", callback_data="browse:root:0"))
if not view_as_user:
builder.row(InlineKeyboardButton(text="πŸ“ Manage Folders", callback_data="manage:folders:0"))
builder.row(
InlineKeyboardButton(text="πŸ“Š Admin Dashboard", callback_data="admin:dashboard"),
InlineKeyboardButton(text="πŸ‘‘ Owner Dashboard", callback_data="owner:dashboard"),
)
builder.row(
InlineKeyboardButton(text="πŸ‘₯ Manage Admins", callback_data="owner:admins"),
InlineKeyboardButton(text="πŸ‘οΈ View as User", callback_data="toggle:user_view"),
)
else:
builder.row(InlineKeyboardButton(text="πŸ‘οΈ Return to Admin View", callback_data="toggle:admin_view"))
return builder.as_markup()
# ─────────────────────────── FOLDER BROWSING ───────────────────────────
def folder_list_keyboard(
folders: list,
items: list,
parent_id: int | None,
page: int,
is_admin: bool = False,
is_owner: bool = False,
) -> InlineKeyboardMarkup:
"""
Builds a paginated keyboard for browsing folders and items inside a folder.
Folders appear in a 2-column grid, items appear as single rows below.
"""
builder = InlineKeyboardBuilder()
per_page = config.ITEMS_PER_PAGE
total_content = folders + items # Combined for pagination
total_pages = max(1, (len(total_content) + per_page - 1) // per_page)
page_content = total_content[page * per_page: (page + 1) * per_page]
# Separate paginated content back into folders and items
page_folders = [c for c in page_content if isinstance(c, type(folders[0])) if folders] if folders else []
page_items = [c for c in page_content if hasattr(c, 'content_type')]
# Folders in 2-column grid
folder_buttons = [
InlineKeyboardButton(
text=f"{f.emoji} {f.name}",
callback_data=f"browse:folder:{f.id}:0"
)
for f in page_folders
]
# Add folder buttons in pairs (2 per row)
for i in range(0, len(folder_buttons), 2):
row = folder_buttons[i:i+2]
builder.row(*row)
# Items in single rows
content_type_emoji = {
"photo": "πŸ–ΌοΈ", "video": "🎬", "document": "πŸ“„",
"audio": "🎡", "link": "πŸ”—", "text": "πŸ“"
}
for item in page_items:
emoji = content_type_emoji.get(item.content_type, "πŸ“„")
builder.row(InlineKeyboardButton(
text=f"{emoji} {item.title}",
callback_data=f"view:item:{item.id}"
))
# Admin/Owner action buttons (if not in user-view mode)
if is_admin or is_owner:
folder_param = parent_id if parent_id else "root"
builder.row(
InlineKeyboardButton(text="βž• Add New Item", callback_data=f"add:start:{folder_param}"),
InlineKeyboardButton(text="✏️ Edit Folder", callback_data=f"edit:folder:{parent_id}" if parent_id else "home"),
)
if parent_id: # Can only delete non-root folders
builder.row(InlineKeyboardButton(
text="πŸ—‘οΈ Delete Folder",
callback_data=f"delete:folder:{parent_id}"
))
# Pagination row
nav_buttons = []
if page > 0:
nav_buttons.append(InlineKeyboardButton(text="⬅️ Prev", callback_data=f"browse:folder:{parent_id or 'root'}:{page-1}"))
if page < total_pages - 1:
nav_buttons.append(InlineKeyboardButton(text="➑️ Next", callback_data=f"browse:folder:{parent_id or 'root'}:{page+1}"))
if nav_buttons:
builder.row(*nav_buttons)
# Back and Home navigation
if parent_id is not None:
builder.row(*_back_home_row(back_callback="browse:root:0"))
else:
builder.row(InlineKeyboardButton(text="🏠 Home", callback_data="home"))
return builder.as_markup()
def item_view_keyboard(item_id: int, folder_id: int, is_admin: bool = False) -> InlineKeyboardMarkup:
"""Keyboard shown when a user views a specific item."""
builder = InlineKeyboardBuilder()
if is_admin:
builder.row(
InlineKeyboardButton(text="✏️ Edit", callback_data=f"edit:item:{item_id}"),
InlineKeyboardButton(text="πŸ—‘οΈ Delete", callback_data=f"delete:item:{item_id}"),
)
builder.row(*_back_home_row(back_callback=f"browse:folder:{folder_id}:0"))
return builder.as_markup()
# ─────────────────────────── ADMIN MANAGEMENT ───────────────────────────
def add_item_type_keyboard(folder_id: int | str) -> InlineKeyboardMarkup:
"""Asks whether to add a Folder or Content item."""
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="πŸ“ New Folder", callback_data=f"add:folder:{folder_id}"),
InlineKeyboardButton(text="πŸ“„ New Content", callback_data=f"add:content:{folder_id}"),
)
builder.row(InlineKeyboardButton(text="πŸ”™ Cancel", callback_data="home"))
return builder.as_markup()
def content_type_keyboard(folder_id: int | str) -> InlineKeyboardMarkup:
"""Lets admin choose the type of content to add."""
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="πŸ–ΌοΈ Photo", callback_data=f"ctype:photo:{folder_id}"),
InlineKeyboardButton(text="🎬 Video", callback_data=f"ctype:video:{folder_id}"),
)
builder.row(
InlineKeyboardButton(text="πŸ“„ Document", callback_data=f"ctype:document:{folder_id}"),
InlineKeyboardButton(text="🎡 Audio", callback_data=f"ctype:audio:{folder_id}"),
)
builder.row(
InlineKeyboardButton(text="πŸ”— Link", callback_data=f"ctype:link:{folder_id}"),
InlineKeyboardButton(text="πŸ“ Text", callback_data=f"ctype:text:{folder_id}"),
)
builder.row(InlineKeyboardButton(text="πŸ”™ Cancel", callback_data="home"))
return builder.as_markup()
def confirm_delete_keyboard(entity_type: str, entity_id: int) -> InlineKeyboardMarkup:
"""Confirmation dialog before deleting a folder or item."""
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="βœ… Yes, Delete",
callback_data=f"confirm_delete:{entity_type}:{entity_id}"
),
InlineKeyboardButton(text="❌ Cancel", callback_data="home"),
)
return builder.as_markup()
def admin_list_keyboard(admins: list, page: int = 0) -> InlineKeyboardMarkup:
"""Lists all admins with a remove button for each."""
builder = InlineKeyboardBuilder()
per_page = 5
total_pages = max(1, (len(admins) + per_page - 1) // per_page)
page_admins = admins[page * per_page: (page + 1) * per_page]
for admin in page_admins:
name = admin.full_name or admin.username or f"ID:{admin.user_id}"
builder.row(InlineKeyboardButton(
text=f"πŸ‘€ {name}",
callback_data=f"owner:admin_info:{admin.user_id}"
))
# Pagination
nav = []
if page > 0:
nav.append(InlineKeyboardButton(text="⬅️", callback_data=f"owner:admins_page:{page-1}"))
if page < total_pages - 1:
nav.append(InlineKeyboardButton(text="➑️", callback_data=f"owner:admins_page:{page+1}"))
if nav:
builder.row(*nav)
builder.row(InlineKeyboardButton(text="βž• Add Admin", callback_data="owner:add_admin"))
builder.row(InlineKeyboardButton(text="🏠 Home", callback_data="home"))
return builder.as_markup()
def admin_info_keyboard(admin_user_id: int) -> InlineKeyboardMarkup:
"""Shows options for a specific admin."""
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(
text="πŸ—‘οΈ Remove Admin",
callback_data=f"owner:remove_admin:{admin_user_id}"
))
builder.row(*_back_home_row(back_callback="owner:admins"))
return builder.as_markup()
def back_home_keyboard(back_callback: str = "home") -> InlineKeyboardMarkup:
"""Simple back + home keyboard for generic use."""
builder = InlineKeyboardBuilder()
builder.row(*_back_home_row(back_callback=back_callback))
return builder.as_markup()