优化robot_menu交互:支持@问答式功能清单
变更项: 1. 新增@语义入口:群内@机器人并包含“功能清单、怎么用、帮助”等关键词时自动回复菜单。 2. 菜单输出改为用户友好格式:按功能展示名称、触发方式和一句话说明,提升可读性与可操作性。 3. 保留原指令入口(菜单/功能菜单),并统一走新的直观菜单文案。 4. 增加对Feature描述的解析逻辑,自动提取方括号中的触发命令作为使用方法。
This commit is contained in:
@@ -20,6 +20,16 @@ class RobotMenuPlugin(MessagePluginInterface):
|
|||||||
# 功能权限常量
|
# 功能权限常量
|
||||||
FEATURE_KEY = "ROBOT_MENU"
|
FEATURE_KEY = "ROBOT_MENU"
|
||||||
FEATURE_DESCRIPTION = "📋 功能菜单 [菜单 - 显示功能菜单 | 菜单 状态 - 显示功能状态]"
|
FEATURE_DESCRIPTION = "📋 功能菜单 [菜单 - 显示功能菜单 | 菜单 状态 - 显示功能状态]"
|
||||||
|
AT_QUERY_KEYWORDS = (
|
||||||
|
"功能清单",
|
||||||
|
"功能菜单",
|
||||||
|
"功能列表",
|
||||||
|
"菜单",
|
||||||
|
"怎么用",
|
||||||
|
"如何用",
|
||||||
|
"帮助",
|
||||||
|
"help",
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@@ -92,9 +102,56 @@ class RobotMenuPlugin(MessagePluginInterface):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
content = str(message.get("content", "")).strip()
|
content = str(message.get("content", "")).strip()
|
||||||
command = content.split(" ")[0]
|
command = content.split(" ")[0] if content else ""
|
||||||
|
roomid = str(message.get("roomid", "") or "")
|
||||||
|
is_at = bool(message.get("is_at", False))
|
||||||
|
|
||||||
return command in self._commands
|
# 指令入口
|
||||||
|
if command in self._commands:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# @语义入口:只在群聊 + @机器人 + 明确菜单意图时触发
|
||||||
|
if roomid and is_at and self._is_at_menu_query(content):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize_text(content: str) -> str:
|
||||||
|
text = str(content or "").lower().strip()
|
||||||
|
text = re.sub(r"\s+", "", text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _is_at_menu_query(self, content: str) -> bool:
|
||||||
|
text = self._normalize_text(content)
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
return any(self._normalize_text(keyword) in text for keyword in self.AT_QUERY_KEYWORDS)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_usage_from_description(description: str) -> str:
|
||||||
|
"""
|
||||||
|
从 Feature.description 中提取 [ ... ] 内的触发方式
|
||||||
|
例如: '📋 功能菜单 [菜单 - 显示功能菜单]' -> '菜单'
|
||||||
|
"""
|
||||||
|
desc = str(description or "")
|
||||||
|
match = re.search(r"\[(.*?)\]", desc)
|
||||||
|
if not match:
|
||||||
|
return "请发送“菜单”查看"
|
||||||
|
inner = match.group(1).strip()
|
||||||
|
# 取第一段触发方式,避免太长
|
||||||
|
first = inner.split("|")[0].strip()
|
||||||
|
if "-" in first:
|
||||||
|
first = first.split("-", 1)[0].strip()
|
||||||
|
return first or "请发送“菜单”查看"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_brief_from_description(description: str) -> str:
|
||||||
|
desc = str(description or "")
|
||||||
|
# 去掉开头 emoji 和 [..] 指令块,只保留一句用途
|
||||||
|
desc = re.sub(r"^[^\u4e00-\u9fa5A-Za-z0-9]+", "", desc)
|
||||||
|
desc = re.sub(r"\[.*?\]", "", desc).strip()
|
||||||
|
return desc or "暂无说明"
|
||||||
|
|
||||||
def display_menu_status(self, group_id: str) -> str:
|
def display_menu_status(self, group_id: str) -> str:
|
||||||
"""显示所有功能列表及其在指定群组中的当前状态,带emoji"""
|
"""显示所有功能列表及其在指定群组中的当前状态,带emoji"""
|
||||||
@@ -106,43 +163,64 @@ class RobotMenuPlugin(MessagePluginInterface):
|
|||||||
menu.append(f"{status_emoji} {status_str}-{feature.value}-{feature.description}")
|
menu.append(f"{status_emoji} {status_str}-{feature.value}-{feature.description}")
|
||||||
return "\n".join(menu)
|
return "\n".join(menu)
|
||||||
|
|
||||||
def get_enabled_features(self, group_id: str) -> str:
|
def _get_enabled_feature_items(self, group_id: str) -> List[Dict[str, str]]:
|
||||||
"""获取某个群已启用的功能列表及其描述,并返回格式化的字符串
|
"""获取本群已启用且可对用户展示的功能条目"""
|
||||||
只返回描述中包含指令(方括号[])的功能
|
items: List[Dict[str, str]] = []
|
||||||
|
|
||||||
Args:
|
|
||||||
group_id: 群ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: 格式化的已启用功能列表字符串
|
|
||||||
"""
|
|
||||||
enabled_features = []
|
|
||||||
|
|
||||||
# 检查群是否在列表中
|
# 检查群是否在列表中
|
||||||
if group_id not in GroupBotManager.local_cache["group_list"]:
|
if group_id not in GroupBotManager.local_cache["group_list"]:
|
||||||
return "该群未启用机器人功能"
|
return items
|
||||||
|
|
||||||
# 遍历所有功能,检查哪些已启用且包含指令
|
# 遍历所有功能,检查哪些已启用且包含指令
|
||||||
for feature in Feature:
|
for feature in Feature:
|
||||||
status = GroupBotManager.get_group_permission(group_id, feature)
|
status = GroupBotManager.get_group_permission(group_id, feature)
|
||||||
# 只添加已启用且描述中包含方括号的功能
|
# 只添加已启用且描述中包含方括号的功能
|
||||||
if status == PermissionStatus.ENABLED and "[" in feature.description and "]" in feature.description:
|
if status == PermissionStatus.ENABLED and "[" in feature.description and "]" in feature.description:
|
||||||
enabled_features.append({
|
items.append({
|
||||||
"id": feature.value,
|
"id": feature.value,
|
||||||
"name": feature.name,
|
"name": feature.name,
|
||||||
"description": feature.description
|
"description": feature.description
|
||||||
})
|
})
|
||||||
|
return items
|
||||||
|
|
||||||
# 如果没有启用任何带指令的功能
|
def get_enabled_features(self, group_id: str) -> str:
|
||||||
|
"""兼容旧接口:返回简化文本菜单"""
|
||||||
|
enabled_features = self._get_enabled_feature_items(group_id)
|
||||||
|
if group_id not in GroupBotManager.local_cache["group_list"]:
|
||||||
|
return "该群未启用机器人功能"
|
||||||
if not enabled_features:
|
if not enabled_features:
|
||||||
return "该群未启用任何带指令的功能"
|
return "该群未启用任何带指令的功能"
|
||||||
|
result = "群功能菜单:\n"
|
||||||
# 构建格式化的字符串
|
|
||||||
result = f"不支持@交互,请通过指令触发\n 群功能菜单:\n"
|
|
||||||
for feature in enabled_features:
|
for feature in enabled_features:
|
||||||
result += f"{feature['id']}.{feature['description']}\n"
|
usage = self._extract_usage_from_description(feature["description"])
|
||||||
|
brief = self._extract_brief_from_description(feature["description"])
|
||||||
|
result += f"{feature['id']}. {feature['name']} | 触发:{usage} | {brief}\n"
|
||||||
|
return result.strip()
|
||||||
|
|
||||||
return result
|
def build_user_friendly_menu(self, group_id: str) -> str:
|
||||||
|
"""构建给普通群成员看的直观功能菜单"""
|
||||||
|
if group_id not in GroupBotManager.local_cache["group_list"]:
|
||||||
|
return "当前群未开通机器人功能,请联系管理员开启。"
|
||||||
|
|
||||||
|
enabled_features = self._get_enabled_feature_items(group_id)
|
||||||
|
if not enabled_features:
|
||||||
|
return "当前群暂无可用功能。"
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"本群已开通功能:",
|
||||||
|
"直接复制“触发”里的命令发送即可。",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
for idx, feature in enumerate(enabled_features, start=1):
|
||||||
|
usage = self._extract_usage_from_description(feature["description"])
|
||||||
|
brief = self._extract_brief_from_description(feature["description"])
|
||||||
|
lines.append(f"{idx}. {feature['name']}")
|
||||||
|
lines.append(f"触发:{usage}")
|
||||||
|
lines.append(f"说明:{brief}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
lines.append("快捷入口:")
|
||||||
|
lines.append("发送“菜单”可再次查看;发送“菜单 状态”可看全部开关状态。")
|
||||||
|
return "\n".join(lines).strip()
|
||||||
|
|
||||||
def get_group_list(self) -> str:
|
def get_group_list(self) -> str:
|
||||||
"""返回所有启用了群机器人的群组清单"""
|
"""返回所有启用了群机器人的群组清单"""
|
||||||
@@ -171,6 +249,7 @@ class RobotMenuPlugin(MessagePluginInterface):
|
|||||||
self.LOG.debug(f"插件执行: {self.name}:{content}")
|
self.LOG.debug(f"插件执行: {self.name}:{content}")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
|
is_at = bool(message.get("is_at", False))
|
||||||
command = content.split(" ")[0]
|
command = content.split(" ")[0]
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
bot: WechatAPIClient = message.get("bot")
|
bot: WechatAPIClient = message.get("bot")
|
||||||
@@ -187,15 +266,24 @@ class RobotMenuPlugin(MessagePluginInterface):
|
|||||||
return False, "没有权限"
|
return False, "没有权限"
|
||||||
|
|
||||||
target = roomid if roomid else sender
|
target = roomid if roomid else sender
|
||||||
|
is_at_menu_query = roomid and is_at and self._is_at_menu_query(content)
|
||||||
|
|
||||||
|
# @语义菜单入口:用户@机器人直接问“功能清单/怎么用”
|
||||||
|
if is_at_menu_query and command not in self._commands:
|
||||||
|
menu_text = self.build_user_friendly_menu(roomid)
|
||||||
|
client_msg_id, create_time, new_msg_id = await bot.send_text_message(target, menu_text, sender)
|
||||||
|
revoke.add_message_to_revoke(target, client_msg_id, create_time, new_msg_id, 120)
|
||||||
|
return True, "at菜单问答"
|
||||||
|
|
||||||
# 检查命令格式
|
# 检查命令格式
|
||||||
parts = content.split()
|
parts = content.split()
|
||||||
if len(parts) == 1:
|
if len(parts) == 1:
|
||||||
# 显示功能菜单
|
# 显示功能菜单
|
||||||
menu_text = self.get_enabled_features(roomid if roomid else sender)
|
group_for_menu = roomid if roomid else sender
|
||||||
|
menu_text = self.build_user_friendly_menu(group_for_menu)
|
||||||
|
|
||||||
client_msg_id, create_time, new_msg_id = await bot.send_text_message(target, menu_text, sender)
|
client_msg_id, create_time, new_msg_id = await bot.send_text_message(target, menu_text, sender)
|
||||||
revoke.add_message_to_revoke(target, client_msg_id, create_time, new_msg_id, 90)
|
revoke.add_message_to_revoke(target, client_msg_id, create_time, new_msg_id, 120)
|
||||||
return True, "显示功能菜单"
|
return True, "显示功能菜单"
|
||||||
|
|
||||||
# 提取命令名
|
# 提取命令名
|
||||||
|
|||||||
Reference in New Issue
Block a user