diff --git a/plugins/robot_menu/menu_render_tool.py b/plugins/robot_menu/menu_render_tool.py index bc1ae34..f814d05 100644 --- a/plugins/robot_menu/menu_render_tool.py +++ b/plugins/robot_menu/menu_render_tool.py @@ -10,7 +10,7 @@ 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 utils.robot_cmd.robot_command import Feature from wechat_ipad import WechatAPIClient @@ -65,57 +65,91 @@ class RobotMenuRenderTool: cmd_hint = (match.group(2) or "").strip() or "无" return title, cmd_hint + @staticmethod + def _extract_command_tokens(cmd_hint: str) -> list[str]: + """从描述里的指令片段提取命令 token 列表。 + + 规则: + 1. 按 `|`、`,`、`,`、`/`、`、` 切分; + 2. 去重并保序; + 3. 清理多余空白,便于后续生成“如何使用”示例。 + """ + raw = str(cmd_hint or "").strip() + if not raw or raw == "无": + return [] + parts = re.split(r"[|,,/、]+", raw) + tokens: list[str] = [] + for part in parts: + token = re.sub(r"\s+", " ", str(part or "").strip()) + if token and token not in tokens: + tokens.append(token) + return tokens + + def _build_feature_usage_lines(self, feature: Feature) -> list[str]: + """构建单个功能的“怎么用”文案(纯用户视角,不含管理员控制语句)。""" + title, cmd_hint = self._split_feature_description(feature.description) + tokens = self._extract_command_tokens(cmd_hint) + lines = [f"{feature.value}. {title}"] + if not tokens: + lines.append(" 用法:自动/定时触发,无需手动指令") + return lines + + # 第一行给出最短触发命令,后续补充可选别名,信息更丰富但仍保持紧凑。 + lines.append(f" 指令:{tokens[0]}") + if len(tokens) > 1: + lines.append(f" 别名:{' / '.join(tokens[1:4])}") + # 给一个通用可替换参数提示,帮助用户快速落地命令输入。 + if any(k in tokens[0] for k in ["@", "积分", "关键词", "日期", "功能名"]): + lines.append(" 提示:按指令中的占位词替换为实际内容后发送") + return lines + def build_feature_status_markdown(self, group_id: str) -> str: - """构建菜单 Markdown(作为图片渲染兜底或文本调试来源)。""" + """构建紧凑菜单 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}` |" - ) + lines.extend(self._build_feature_usage_lines(feature)) + lines.append("") 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"触发:自动/定时触发(无直接聊天指令)
{manage_hint}" - return f"触发:{html.escape(cmd_hint)}
{manage_hint}" + tokens = self._extract_command_tokens(cmd_hint) + if not tokens: + return "自动/定时触发,无需发送命令" + + rows = [f"指令:{html.escape(tokens[0])}"] + if len(tokens) > 1: + alias_text = " / ".join([f"{html.escape(t)}" for t in tokens[1:4]]) + rows.append(f"别名:{alias_text}") + return "
".join(rows) 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"""
-
#{feature.value}
+
{feature.value}

{html.escape(title)}

功能键:{html.escape(feature.name)}

- {status_text}

{command_examples}

@@ -146,88 +180,82 @@ class RobotMenuRenderTool: * {{ box-sizing: border-box; }} body {{ margin: 0; - padding: 24px; + padding: 14px; font-family: "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", sans-serif; background: linear-gradient(180deg, #eef4fa 0%, #f8fbff 100%); color: var(--text); }} .page {{ - width: 820px; + width: 780px; margin: 0 auto; background: var(--card); border: 1px solid var(--line); - border-radius: 18px; + border-radius: 14px; overflow: hidden; - box-shadow: 0 12px 28px rgba(20, 47, 76, 0.10); + box-shadow: 0 8px 18px rgba(20, 47, 76, 0.10); }} .hero {{ - padding: 22px 24px 18px 24px; + padding: 14px 16px 12px 16px; background: linear-gradient(135deg, #0d6efd 0%, #0aa2c0 100%); color: #fff; }} - .hero h1 {{ margin: 0 0 8px 0; font-size: 28px; }} - .hero p {{ margin: 4px 0; font-size: 14px; opacity: .96; }} - .content {{ padding: 20px 22px 24px 22px; }} + .hero h1 {{ margin: 0 0 4px 0; font-size: 22px; }} + .hero p {{ margin: 2px 0; font-size: 12px; opacity: .96; }} + .content {{ padding: 12px 12px 14px 12px; }} .block {{ border: 1px solid var(--line); - border-radius: 12px; - padding: 14px 16px; - margin-bottom: 14px; + border-radius: 10px; + padding: 10px 12px; + margin-bottom: 10px; background: #fff; }} - .block h2 {{ margin: 0 0 8px 0; font-size: 18px; color: #12395f; }} + .block h2 {{ margin: 0 0 6px 0; font-size: 15px; color: #12395f; }} .block ul {{ margin: 0; padding-left: 18px; color: var(--muted); - line-height: 1.7; - font-size: 14px; + line-height: 1.5; + font-size: 12px; }} - .feature-list {{ display: grid; grid-template-columns: 1fr; gap: 12px; }} + .feature-list {{ display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }} .feature-card {{ border: 1px solid var(--line); - border-radius: 12px; - padding: 12px 14px; + border-radius: 10px; + padding: 8px 10px; background: #fff; }} - .feature-top {{ display: flex; align-items: center; gap: 10px; }} + .feature-top {{ display: flex; align-items: center; gap: 8px; }} .feature-index {{ - min-width: 48px; + min-width: 28px; text-align: center; font-weight: 700; color: #114a84; background: var(--brand-soft); border: 1px solid #cfe2ff; - border-radius: 8px; - padding: 6px 8px; + border-radius: 6px; + padding: 2px 4px; + font-size: 11px; }} .feature-meta {{ flex: 1; }} - .feature-meta h3 {{ margin: 0 0 2px 0; font-size: 16px; }} - .feature-key {{ margin: 0; font-size: 13px; color: var(--muted); }} - .feature-badge {{ - padding: 5px 10px; - border-radius: 999px; - font-size: 12px; - font-weight: 700; - border: 1px solid transparent; - }} - .badge-on {{ color: var(--ok); background: var(--ok-soft); border-color: #bee7cf; }} - .badge-off {{ color: var(--off); background: var(--off-soft); border-color: #f3c7c7; }} + .feature-meta h3 {{ margin: 0 0 1px 0; font-size: 13px; }} + .feature-key {{ margin: 0; font-size: 11px; color: var(--muted); }} .feature-body {{ - margin-top: 10px; - padding: 10px 12px; - border-radius: 8px; + margin-top: 6px; + padding: 6px 8px; + border-radius: 7px; background: #f8fbff; border: 1px dashed #d7e6f5; }} - .feature-body p {{ margin: 0; font-size: 13px; color: #3d5870; line-height: 1.75; }} + .feature-body p {{ margin: 0; font-size: 11px; color: #3d5870; line-height: 1.5; }} + .line-main {{ color: #1f3f62; }} + .line-sub {{ color: #5a738a; }} code {{ background: #eef6ff; border: 1px solid #d2e6ff; color: #12539a; - padding: 1px 6px; - border-radius: 6px; - font-size: 12px; + padding: 0 5px; + border-radius: 5px; + font-size: 11px; }} @@ -240,23 +268,14 @@ class RobotMenuRenderTool:
-

基础命令

+

快速上手

    -
  • 菜单:查看完整功能菜单(状态 + 详细指令)
  • -
  • 菜单 状态:查看所有功能当前启用状态
  • -
  • 菜单 群列表:查看已启用群机器人的群组清单
  • +
  • 菜单:查看可用功能和指令
  • +
  • 以下内容仅面向普通用户,聚焦“怎么用”
-

管理员命令

-
    -
  • 菜单 启用 序号 / 菜单 关闭 序号
  • -
  • 菜单 启用 功能键 / 菜单 关闭 功能键
  • -
  • 菜单 管理员 添加 wxid/昵称菜单 管理员 删除 wxid/昵称菜单 管理员 列表
  • -
-
-
-

功能明细

+

功能与指令

{''.join(feature_cards)}