基础命令
菜单:查看完整功能菜单(状态 + 详细指令)菜单 状态:查看所有功能当前启用状态菜单 群列表:查看已启用群机器人的群组清单
管理员命令
菜单 启用 序号/菜单 关闭 序号菜单 启用 功能键/菜单 关闭 功能键菜单 管理员 添加 wxid/昵称、菜单 管理员 删除 wxid/昵称、菜单 管理员 列表
import asyncio
import html
import os
import re
import time
from pathlib import Path
from typing import Any, Optional, Tuple
from loguru import logger as default_logger
from utils.markdown_to_image import convert_md_str_to_image, html_to_image
from utils.revoke.message_auto_revoke import MessageAutoRevoke
from utils.robot_cmd.robot_command import Feature, GroupBotManager, PermissionStatus
from wechat_ipad import WechatAPIClient
class RobotMenuRenderTool:
"""机器人菜单渲染工具。
设计目标:
1. 将“菜单排版 + 图片渲染 + 发送策略”从主插件拆出,降低 main.py 维护成本;
2. 统一菜单文本/图片输出行为,保证菜单与状态页展示一致;
3. 对外只暴露清晰方法,主插件只负责“取业务数据 + 调用工具”。
"""
def __init__(
self,
output_mode: Any,
image_fallback_to_text: bool,
image_render_timeout_seconds: int,
image_render_retries: int,
log=default_logger,
):
# 输出模式:支持 text / image,非法值自动归一化为 text。
self.output_mode = self.normalize_output_mode(output_mode)
# 图片失败时是否回退文本。
self.image_fallback_to_text = bool(image_fallback_to_text)
# 渲染超时与重试参数,统一集中在工具层处理。
self.image_render_timeout_seconds = int(image_render_timeout_seconds)
self.image_render_retries = int(image_render_retries)
# 注入日志对象,便于主插件统一控制日志风格与输出目标。
self.log = log or default_logger
@staticmethod
def normalize_output_mode(raw_mode: Any) -> str:
"""将配置中的输出模式标准化为 `text` 或 `image`。"""
mode = str(raw_mode or "").strip().lower()
if mode in {"image", "img", "picture", "pic"}:
return "image"
return "text"
@staticmethod
def _split_feature_description(feature_desc: str) -> Tuple[str, str]:
"""拆分功能描述为“功能说明”和“触发指令提示”。
约定:
1. 描述中 `[]` 内的文本作为“触发说明”;
2. 无 `[]` 时返回“无”,保证渲染模板字段完整。
"""
desc = str(feature_desc or "").strip()
match = re.match(r"^(.*?)(?:\[(.*?)\])?$", desc)
if not match:
return desc or "未命名功能", "无"
title = (match.group(1) or "").strip() or "未命名功能"
cmd_hint = (match.group(2) or "").strip() or "无"
return title, cmd_hint
def build_feature_status_markdown(self, group_id: str) -> str:
"""构建菜单 Markdown(作为图片渲染兜底或文本调试来源)。"""
lines = [
"# 机器人功能菜单",
"",
f"- 目标:`{group_id}`",
f"- 生成时间:`{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}`",
"",
"| 序号 | 功能键 | 状态 | 功能说明 | 指令/触发方式 |",
"| --- | --- | --- | --- | --- |",
]
for feature in Feature:
status = GroupBotManager.get_group_permission(group_id, feature)
status_text = "启用 ✅" if status == PermissionStatus.ENABLED else "关闭 ❌"
title, cmd_hint = self._split_feature_description(feature.description)
lines.append(
f"| {feature.value} | `{feature.name}` | {status_text} | {title} | `{cmd_hint}` |"
)
return "\n".join(lines)
def _get_feature_command_examples(self, feature: Feature) -> str:
"""返回单个功能在菜单卡片中的详细指令文案。"""
_, cmd_hint = self._split_feature_description(feature.description)
manage_hint = (
f"管理:菜单 启用 {feature.value} | 菜单 关闭 {feature.value} | "
f"菜单 启用 {feature.name} | 菜单 关闭 {feature.name}"
)
if cmd_hint == "无":
return f"触发:自动/定时触发(无直接聊天指令) {command_examples}
{manage_hint}"
return f"触发:{html.escape(cmd_hint)}
{manage_hint}"
def build_feature_status_html(self, group_id: str) -> str:
"""构建菜单 HTML(自定义样式,不复用 md2image 默认样式)。"""
feature_cards = []
for feature in Feature:
status = GroupBotManager.get_group_permission(group_id, feature)
enabled = status == PermissionStatus.ENABLED
status_class = "badge-on" if enabled else "badge-off"
status_text = "已启用" if enabled else "已关闭"
title, _ = self._split_feature_description(feature.description)
command_examples = self._get_feature_command_examples(feature)
feature_cards.append(
f"""
目标群/会话:{html.escape(group_id)}
生成时间:{now_text}
菜单:查看完整功能菜单(状态 + 详细指令)菜单 状态:查看所有功能当前启用状态菜单 群列表:查看已启用群机器人的群组清单菜单 启用 序号 / 菜单 关闭 序号菜单 启用 功能键 / 菜单 关闭 功能键菜单 管理员 添加 wxid/昵称、菜单 管理员 删除 wxid/昵称、菜单 管理员 列表