ForcePilot/backend/package/yuxi/channels/adapters/telegram/inline_buttons.py
Kris 71fec609bb feat(telegram-adapter): 实现完整的Telegram适配器基础代码
新增了Telegram适配器的全套基础模块,包括:
1.  核心适配器入口与会话工具
2.  账号管理、认证与配置系统
3.  连接相关的轮询、Webhook、更新偏移管理
4.  话题路由、管理与缓存系统
5.  消息反抖动、超时配置与工具类
6.  响应式UI与命令交互系统
7.  反应表情与通知系统
8.  审批与安全审计模块
9.  健康检查与状态监控
10. 贴纸缓存与视觉工具
11. 流式响应与协作功能
12. 群组迁移与目标归一化处理
2026-05-12 00:49:52 +08:00

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