Skip to content
Open
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
7 changes: 7 additions & 0 deletions astrbot/core/star/command_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ def _set_filter_fragment(
setattr(filter_ref, attr, fragment)
if hasattr(filter_ref, "_cmpl_cmd_names"):
filter_ref._cmpl_cmd_names = None
if isinstance(filter_ref, CommandGroupFilter):
# 指令组名称变更后,同步刷新子指令持有的父级前缀快照与缓存,
# 否则子指令仍只能通过旧前缀触发(#9366)。
filter_ref.sync_sub_filters_parent_names()


def _set_filter_aliases(
Expand All @@ -495,6 +499,9 @@ def _set_filter_aliases(
setattr(filter_ref, "alias", set(aliases))
if hasattr(filter_ref, "_cmpl_cmd_names"):
filter_ref._cmpl_cmd_names = None
if isinstance(filter_ref, CommandGroupFilter):
# 指令组别名变更同样会影响子指令的完整指令名,需要一并同步(#9366)。
filter_ref.sync_sub_filters_parent_names()


def _is_command_in_use(
Expand Down
16 changes: 16 additions & 0 deletions astrbot/core/star/filter/command_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ def add_sub_command_filter(
) -> None:
self.sub_command_filters.append(sub_command_filter)

def sync_sub_filters_parent_names(self) -> None:
"""同步子指令的父级指令名快照与缓存。

子指令 CommandFilter 的 parent_command_names 是注册时的快照,
当指令组的名称或别名发生变更(如 WebUI 重命名)后,需要调用此方法
让所有子指令重新使用新的完整前缀,否则子指令仍只能通过旧前缀触发。
"""
parent_names = self.get_complete_command_names()
for sub_filter in self.sub_command_filters:
if isinstance(sub_filter, CommandGroupFilter):
sub_filter._cmpl_cmd_names = None
sub_filter.sync_sub_filters_parent_names()
else:
sub_filter.parent_command_names = list(parent_names)
sub_filter._cmpl_cmd_names = None

def add_custom_filter(self, custom_filter: CustomFilter) -> None:
self.custom_filter_list.append(custom_filter)

Expand Down
116 changes: 116 additions & 0 deletions tests/test_command_group_rename_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""指令组重命名后子指令前缀同步的测试(#9366)。

模拟 WebUI 中对指令组的重命名 / 别名修改流程,验证子指令(含嵌套指令组)
能够正确匹配新前缀,且不再匹配旧前缀。
"""

from __future__ import annotations

from astrbot.core.star.command_management import (
_set_filter_aliases,
_set_filter_fragment,
)
from astrbot.core.star.filter.command import CommandFilter
from astrbot.core.star.filter.command_group import CommandGroupFilter


def _make_group_with_sub() -> tuple[CommandGroupFilter, CommandFilter]:
"""模拟 register_command 的注册流程:子指令持有父级完整指令名的快照。"""
group = CommandGroupFilter("example_cmd_group")
sub = CommandFilter(
"sub_cmd",
parent_command_names=group.get_complete_command_names(),
)
group.add_sub_command_filter(sub)
return group, sub


def test_rename_group_updates_sub_command_prefix():
group, sub = _make_group_with_sub()

# 预热缓存,模拟重命名前指令已被触发过
assert sub.equals("example_cmd_group sub_cmd")

_set_filter_fragment(group, "renamed_group")

assert set(group.get_complete_command_names()) == {"renamed_group"}
assert set(sub.get_complete_command_names()) == {"renamed_group sub_cmd"}
assert sub.equals("renamed_group sub_cmd")
assert not sub.equals("example_cmd_group sub_cmd")


def test_rename_group_updates_nested_group_and_leaf():
root = CommandGroupFilter("g1")
nested = CommandGroupFilter("g2", parent_group=root)
root.add_sub_command_filter(nested)
leaf = CommandFilter(
"leaf",
parent_command_names=nested.get_complete_command_names(),
)
nested.add_sub_command_filter(leaf)

# 预热缓存
assert leaf.equals("g1 g2 leaf")
assert nested.equals("g1 g2")

_set_filter_fragment(root, "root")

assert set(nested.get_complete_command_names()) == {"root g2"}
assert set(leaf.get_complete_command_names()) == {"root g2 leaf"}
assert leaf.equals("root g2 leaf")
assert not leaf.equals("g1 g2 leaf")


def test_rename_nested_group_only_affects_its_subtree():
root = CommandGroupFilter("g1")
nested = CommandGroupFilter("g2", parent_group=root)
root.add_sub_command_filter(nested)
direct_sub = CommandFilter(
"direct",
parent_command_names=root.get_complete_command_names(),
)
root.add_sub_command_filter(direct_sub)
leaf = CommandFilter(
"leaf",
parent_command_names=nested.get_complete_command_names(),
)
nested.add_sub_command_filter(leaf)

_set_filter_fragment(nested, "sub2")

assert set(leaf.get_complete_command_names()) == {"g1 sub2 leaf"}
# 兄弟指令不受影响
assert set(direct_sub.get_complete_command_names()) == {"g1 direct"}


def test_group_alias_change_updates_sub_command_prefix():
group, sub = _make_group_with_sub()
assert sub.equals("example_cmd_group sub_cmd")

_set_filter_aliases(group, ["ecg"])

assert set(sub.get_complete_command_names()) == {
"example_cmd_group sub_cmd",
"ecg sub_cmd",
}
assert sub.equals("ecg sub_cmd")


def test_rename_group_with_sub_command_alias():
group = CommandGroupFilter("example_cmd_group")
sub = CommandFilter(
"sub_cmd",
alias={"sc"},
parent_command_names=group.get_complete_command_names(),
)
group.add_sub_command_filter(sub)
assert sub.equals("example_cmd_group sc")

_set_filter_fragment(group, "renamed_group")

assert set(sub.get_complete_command_names()) == {
"renamed_group sub_cmd",
"renamed_group sc",
}
assert sub.equals("renamed_group sc")
assert not sub.equals("example_cmd_group sc")
Loading