Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions ncatbot/core/registry/_command_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,17 @@ def get_param_spec(func: Any) -> _ParamSpec:
}


def format_usage(names: tuple, spec: _ParamSpec) -> str:
def format_usage(names: tuple, spec: _ParamSpec, prefix: str = "") -> str:
"""生成命令用法说明字符串。

示例: ``用法: /ban <target: @用户> [duration: 整数 = 60]``

Args:
names: 命令名元组
spec: 参数规格
prefix: 命令前缀(可选)
"""
cmd = names[0]
cmd = f"{prefix}{names[0]}"
parts = [f"用法: {cmd}"]
for param in spec.params:
anno = param.annotation
Expand All @@ -388,12 +393,20 @@ def format_usage(names: tuple, spec: _ParamSpec) -> str:
# ======================= Usage 回复 =======================


async def reply_usage(ctx: Any, names: tuple, spec: _ParamSpec) -> None:
async def reply_usage(
ctx: Any, names: tuple, spec: _ParamSpec, prefix: str = ""
) -> None:
"""尝试通过平台 API 回复命令用法说明。

静默处理: api 不可用或调用失败时仅记录 WARNING。

Args:
ctx: HookContext
names: 命令名元组
spec: 参数规格
prefix: 命令前缀(可选)
"""
usage = format_usage(names, spec)
usage = format_usage(names, spec, prefix)
api = getattr(ctx, "api", None)
if api is None:
return
Expand Down
16 changes: 14 additions & 2 deletions ncatbot/core/registry/command_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class CommandHook(Hook):
匹配规则:
- 统一前缀匹配 — 对消息预处理后首段 PlainText 分词,
首 token 匹配命令名即触发(无论 handler 有无额外参数)
- 支持自定义命令前缀(如 "/"、"!" 等)

参数绑定规则:
- 消息段预处理: 首个 PlainText 移到最前, 解决 Reply 开头的消息
Expand All @@ -55,12 +56,14 @@ def __init__(
*names: str,
ignore_case: bool = False,
priority: int = 95,
prefix: Optional[str] = None,
):
if not names:
raise ValueError("CommandHook 至少需要一个命令名")
self.names = names
self.ignore_case = ignore_case
self.priority = priority
self.prefix = prefix
self._sig_cache: Dict[int, Optional[_ParamSpec]] = {}

async def execute(self, ctx: HookContext) -> HookAction:
Expand All @@ -83,7 +86,16 @@ async def execute(self, ctx: HookContext) -> HookAction:
if not text:
return HookAction.SKIP

# 3) 统一前缀匹配: tokenize 后首 token 匹配命令名
# 3) 检查前缀(如果配置了前缀)
if self.prefix is not None:
if not text.startswith(self.prefix):
return HookAction.SKIP
# 去除前缀
text = text[len(self.prefix) :].strip()
if not text:
return HookAction.SKIP

# 4) 统一前缀匹配: tokenize 后首 token 匹配命令名
matched_name = match_command_prefix(text, self.names, self.ignore_case)
if matched_name is None:
return HookAction.SKIP
Expand Down Expand Up @@ -119,7 +131,7 @@ async def execute(self, ctx: HookContext) -> HookAction:
matched_name,
func.__name__,
)
await reply_usage(ctx, self.names, spec)
await reply_usage(ctx, self.names, spec, self.prefix or "")
return HookAction.SKIP

ctx.kwargs.update(kwargs)
Expand Down
39 changes: 36 additions & 3 deletions ncatbot/core/registry/registrar.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,28 @@ def on_command(
priority: int = 0,
ignore_case: bool = False,
platform: Optional[str] = None,
prefix: Optional[str] = None,
**metadata: Any,
) -> Callable:
"""注册命令 handler (群+私聊)

匹配 message.text,支持注解式参数绑定。

Args:
*names: 命令名(支持多个别名)
priority: 优先级,越大越先执行
ignore_case: 是否忽略大小写
platform: 平台过滤,None 接收所有平台
prefix: 命令前缀,如 "/"、"!" 等
**metadata: 附加元信息
"""

def decorator(func: Callable) -> Callable:
if not hasattr(func, "__hooks__"):
func.__hooks__ = []
func.__hooks__.append(CommandHook(*names, ignore_case=ignore_case))
func.__hooks__.append(
CommandHook(*names, ignore_case=ignore_case, prefix=prefix)
)
return self.on("message", priority=priority, platform=platform, **metadata)(
func
)
Expand All @@ -229,18 +240,29 @@ def on_group_command(
priority: int = 0,
ignore_case: bool = False,
platform: Optional[str] = None,
prefix: Optional[str] = None,
**metadata: Any,
) -> Callable:
"""注册群命令 handler

组合 MessageTypeFilter("group") + CommandHook 匹配 + 参数绑定。

Args:
*names: 命令名(支持多个别名)
priority: 优先级,越大越先执行
ignore_case: 是否忽略大小写
platform: 平台过滤,None 接收所有平台
prefix: 命令前缀,如 "/"、"!" 等
**metadata: 附加元信息
"""

def decorator(func: Callable) -> Callable:
if not hasattr(func, "__hooks__"):
func.__hooks__ = []
func.__hooks__.append(MessageTypeFilter("group"))
func.__hooks__.append(CommandHook(*names, ignore_case=ignore_case))
func.__hooks__.append(
CommandHook(*names, ignore_case=ignore_case, prefix=prefix)
)
return self.on("message", priority=priority, platform=platform, **metadata)(
func
)
Expand All @@ -253,18 +275,29 @@ def on_private_command(
priority: int = 0,
ignore_case: bool = False,
platform: Optional[str] = None,
prefix: Optional[str] = None,
**metadata: Any,
) -> Callable:
"""注册私聊命令 handler

组合 MessageTypeFilter("private") + CommandHook 匹配 + 参数绑定。

Args:
*names: 命令名(支持多个别名)
priority: 优先级,越大越先执行
ignore_case: 是否忽略大小写
platform: 平台过滤,None 接收所有平台
prefix: 命令前缀,如 "/"、"!" 等
**metadata: 附加元信息
"""

def decorator(func: Callable) -> Callable:
if not hasattr(func, "__hooks__"):
func.__hooks__ = []
func.__hooks__.append(MessageTypeFilter("private"))
func.__hooks__.append(CommandHook(*names, ignore_case=ignore_case))
func.__hooks__.append(
CommandHook(*names, ignore_case=ignore_case, prefix=prefix)
)
return self.on("message", priority=priority, platform=platform, **metadata)(
func
)
Expand Down
Loading