优化菜单生图为紧凑使用手册并移除状态与管理员展示

This commit is contained in:
liuwei
2026-04-20 11:50:03 +08:00
parent 64e7e82712
commit 08810c98c3

View File

@@ -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"触发:自动/定时触发(无直接聊天指令)<br>{manage_hint}"
return f"触发:{html.escape(cmd_hint)}<br>{manage_hint}"
tokens = self._extract_command_tokens(cmd_hint)
if not tokens:
return "自动/定时触发,无需发送命令"
rows = [f"<span class='line-main'>指令:<code>{html.escape(tokens[0])}</code></span>"]
if len(tokens) > 1:
alias_text = " / ".join([f"<code>{html.escape(t)}</code>" for t in tokens[1:4]])
rows.append(f"<span class='line-sub'>别名:{alias_text}</span>")
return "<br>".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"""
<section class="feature-card">
<div class="feature-top">
<div class="feature-index">#{feature.value}</div>
<div class="feature-index">{feature.value}</div>
<div class="feature-meta">
<h3>{html.escape(title)}</h3>
<p class="feature-key">功能键:<code>{html.escape(feature.name)}</code></p>
</div>
<span class="feature-badge {status_class}">{status_text}</span>
</div>
<div class="feature-body">
<p>{command_examples}</p>
@@ -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;
}}
</style>
</head>
@@ -240,23 +268,14 @@ class RobotMenuRenderTool:
</header>
<main class="content">
<section class="block">
<h2>基础命令</h2>
<h2>快速上手</h2>
<ul>
<li><code>菜单</code>:查看完整功能菜单(状态 + 详细指令</li>
<li><code>菜单 状态</code>:查看所有功能当前启用状态</li>
<li><code>菜单 群列表</code>:查看已启用群机器人的群组清单</li>
<li><code>菜单</code>:查看可用功能和指令</li>
<li>以下内容仅面向普通用户,聚焦“怎么用”</li>
</ul>
</section>
<section class="block">
<h2>管理员命令</h2>
<ul>
<li><code>菜单 启用 序号</code> / <code>菜单 关闭 序号</code></li>
<li><code>菜单 启用 功能键</code> / <code>菜单 关闭 功能键</code></li>
<li><code>菜单 管理员 添加 wxid/昵称</code>、<code>菜单 管理员 删除 wxid/昵称</code>、<code>菜单 管理员 列表</code></li>
</ul>
</section>
<section class="block">
<h2>功能明细</h2>
<h2>功能与指令</h2>
<div class="feature-list">
{''.join(feature_cards)}
</div>