168 lines
5.0 KiB
Python
168 lines
5.0 KiB
Python
|
|
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
|