新增了Telegram适配器的全套基础模块,包括: 1. 核心适配器入口与会话工具 2. 账号管理、认证与配置系统 3. 连接相关的轮询、Webhook、更新偏移管理 4. 话题路由、管理与缓存系统 5. 消息反抖动、超时配置与工具类 6. 响应式UI与命令交互系统 7. 反应表情与通知系统 8. 审批与安全审计模块 9. 健康检查与状态监控 10. 贴纸缓存与视觉工具 11. 流式响应与协作功能 12. 群组迁移与目标归一化处理
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
|