from __future__ import annotations from typing import Any, Literal from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup ButtonStyle = Literal["default", "primary", "danger"] ButtonScope = Literal["off", "dm", "group", "all", "allowlist"] def _style_to_flag_url(style: ButtonStyle) -> str | None: if style == "primary": return "tg://btn_primary" if style == "danger": return "tg://btn_danger" return None def build_inline_keyboard( buttons: list[dict[str, Any]] | None, scope: ButtonScope = "all", chat_type: str = "direct", allowlist_chats: set[str] | None = None, chat_id: str = "", ) -> InlineKeyboardMarkup | None: if not buttons: return None match scope: case "off": return None case "dm": if chat_type != "direct": return None case "group": if chat_type == "direct": return None case "allowlist": allowlist_chats = allowlist_chats or set() if chat_id not in allowlist_chats: return None rows = [] for row in buttons: if isinstance(row, list): row_buttons = [] for btn in row: if isinstance(btn, dict): ib = InlineKeyboardButton( text=btn.get("text", ""), callback_data=btn.get("callback_data", ""), url=btn.get("url"), ) row_buttons.append(ib) if row_buttons: rows.append(row_buttons) elif isinstance(row, dict): btn = row ib = InlineKeyboardButton( text=btn.get("text", ""), callback_data=btn.get("callback_data", ""), url=btn.get("url"), ) rows.append([ib]) return InlineKeyboardMarkup(rows) if rows else None async def send_inline_buttons( bot: Bot, chat_id: str, text: str, buttons: list[dict[str, Any]] | None = None, scope: ButtonScope = "all", chat_type: str = "direct", allowlist_chats: set[str] | None = None, **kwargs, ) -> Any: payload = {"chat_id": chat_id, "text": text} payload.update(kwargs) markup = build_inline_keyboard( buttons, scope=scope, chat_type=chat_type, allowlist_chats=allowlist_chats, chat_id=chat_id, ) if markup: payload["reply_markup"] = markup return await bot.send_message(**payload) def build_model_selector_buttons(models: list[str], current_model: str = "") -> list[dict[str, Any]]: buttons = [] for model in models: prefix = "\u2705 " if model == current_model else "" buttons.append( { "text": f"{prefix}{model}", "callback_data": f"model:{model}", } ) return buttons def build_exec_approval_buttons(approval_id: str) -> list[dict[str, Any]]: return [ [ {"text": "\u2705 \u6279\u51c6", "callback_data": f"exec:approve:{approval_id}"}, {"text": "\u274c \u62d2\u7edd", "callback_data": f"exec:reject:{approval_id}"}, ] ] def build_provider_browse_keyboard( providers: list[str], current_provider: str = "", prefix: str = "provider", ) -> list[list[dict[str, Any]]]: rows = [] for provider in providers: label = f"\u2705 {provider}" if provider == current_provider else provider rows.append([{"text": label, "callback_data": f"{prefix}:{provider}"}]) return rows def build_paginated_command_keyboard( commands: list[dict[str, Any]], page: int = 0, page_size: int = 8, prefix: str = "cmd", ) -> list[list[dict[str, Any]]]: total_pages = (len(commands) + page_size - 1) // page_size if page_size > 0 else 1 start = page * page_size end = min(start + page_size, len(commands)) page_commands = commands[start:end] rows = [] row_count = max(2, min(4, len(page_commands))) cmds_per_row = (len(page_commands) + row_count - 1) // row_count for i in range(row_count): row = [] for j in range(cmds_per_row): idx = i * cmds_per_row + j if idx < len(page_commands): cmd = page_commands[idx] row.append( { "text": cmd.get("label", cmd.get("command", "")), "callback_data": f"{prefix}:{cmd.get('command', '')}", } ) if row: rows.append(row) if total_pages > 1: nav = [] if page > 0: nav.append({"text": "\u25c0 \u4e0a\u4e00\u9875", "callback_data": f"{prefix}:page:{page - 1}"}) nav.append({"text": f"{page + 1}/{total_pages}", "callback_data": f"{prefix}:page:info"}) if page < total_pages - 1: nav.append({"text": "\u4e0b\u4e00\u9875 \u25b6", "callback_data": f"{prefix}:page:{page + 1}"}) rows.append(nav) return rows