Spaces:
Runtime error
Runtime error
File size: 10,804 Bytes
9a1712b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | """
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() |