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
8 changes: 7 additions & 1 deletion astrbot/core/star/command_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ async def list_command_conflicts() -> list[dict[str, Any]]:
# Internal helpers ----------------------------------------------------------


def _is_plugin_activated(desc: CommandDescriptor) -> bool:
plugin_meta = star_map.get(desc.module_path)
return bool(plugin_meta.activated) if plugin_meta else True


def _collect_descriptors(include_sub_commands: bool) -> list[CommandDescriptor]:
"""收集指令,按需包含子指令。"""
descriptors: list[CommandDescriptor] = []
Expand Down Expand Up @@ -465,7 +470,7 @@ def _group_conflicts(
) -> dict[str, list[CommandDescriptor]]:
conflicts: dict[str, list[CommandDescriptor]] = defaultdict(list)
for desc in descriptors:
if desc.effective_command and desc.enabled:
if desc.effective_command and desc.enabled and _is_plugin_activated(desc):
conflicts[desc.effective_command].append(desc)
return {k: v for k, v in conflicts.items() if len(v) > 1}

Expand Down Expand Up @@ -531,6 +536,7 @@ def _descriptor_to_dict(desc: CommandDescriptor) -> dict[str, Any]:
"aliases": desc.aliases,
"permission": desc.permission,
"enabled": desc.enabled,
"plugin_activated": _is_plugin_activated(desc),
"is_group": desc.is_group,
"has_conflict": desc.has_conflict,
"reserved": desc.reserved,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,13 @@ const getPermissionLabel = (permission: string): string => {
}
};

const isPluginInactive = (cmd: CommandItem) => !cmd.plugin_activated;

// 获取状态信息
const getStatusInfo = (cmd: CommandItem): StatusInfo => {
if (isPluginInactive(cmd)) {
return { text: tm('status.pluginDisabled'), color: 'default', variant: 'outlined' };
}
if (cmd.has_conflict) {
return { text: tm('status.conflict'), color: 'warning', variant: 'flat' };
}
Expand All @@ -88,6 +93,9 @@ const getRowProps = ({ item }: { item: CommandItem }) => {
if (item.is_group) {
classes.push('group-row');
}
if (isPluginInactive(item)) {
classes.push('plugin-inactive-row');
}
return classes.length > 0 ? { class: classes.join(' ') } : {};
};
</script>
Expand Down Expand Up @@ -154,6 +162,7 @@ const getRowProps = ({ item }: { item: CommandItem }) => {
:color="getPermissionColor(item.permission)"
size="small"
class="font-weight-medium cursor-pointer"
:disabled="isPluginInactive(item)"
link
>
{{ getPermissionLabel(item.permission) }}
Expand Down Expand Up @@ -198,25 +207,27 @@ const getRowProps = ({ item }: { item: CommandItem }) => {
icon
size="small"
color="success"
:disabled="isPluginInactive(item)"
@click="emit('toggle-command', item)"
>
<v-icon size="22">mdi-play</v-icon>
<v-tooltip activator="parent" location="top">{{ tm('tooltips.enable') }}</v-tooltip>
<v-tooltip activator="parent" location="top">{{ isPluginInactive(item) ? tm('tooltips.pluginInactive') : tm('tooltips.enable') }}</v-tooltip>
</v-btn>
<v-btn
v-else
icon
size="small"
color="error"
:disabled="isPluginInactive(item)"
@click="emit('toggle-command', item)"
>
<v-icon size="22">mdi-pause</v-icon>
<v-tooltip activator="parent" location="top">{{ tm('tooltips.disable') }}</v-tooltip>
<v-tooltip activator="parent" location="top">{{ isPluginInactive(item) ? tm('tooltips.pluginInactive') : tm('tooltips.disable') }}</v-tooltip>
</v-btn>

<v-btn icon size="small" color="warning" @click="emit('rename', item)">
<v-btn icon size="small" color="warning" :disabled="isPluginInactive(item)" @click="emit('rename', item)">
<v-icon size="22">mdi-pencil</v-icon>
<v-tooltip activator="parent" location="top">{{ tm('tooltips.rename') }}</v-tooltip>
<v-tooltip activator="parent" location="top">{{ isPluginInactive(item) ? tm('tooltips.pluginInactive') : tm('tooltips.rename') }}</v-tooltip>
</v-btn>

<v-btn icon size="small" @click="emit('view-details', item)">
Expand Down Expand Up @@ -285,5 +296,32 @@ code.sub-command-code {
.cursor-pointer {
cursor: pointer;
}

.v-data-table .plugin-inactive-row,
.v-data-table .plugin-inactive-row td,
.v-data-table .plugin-inactive-row .v-data-table__td {
background-color: rgba(var(--v-theme-on-surface), 0.06) !important;
color: rgba(var(--v-theme-on-surface), 0.72) !important;
}

.v-data-table .plugin-inactive-row:hover,
.v-data-table .plugin-inactive-row:hover td,
.v-data-table .plugin-inactive-row:hover .v-data-table__td {
background-color: rgba(var(--v-theme-on-surface), 0.09) !important;
}

.v-data-table .plugin-inactive-row .v-chip,
.v-data-table .plugin-inactive-row code,
.v-data-table .plugin-inactive-row .text-body-2,
.v-data-table .plugin-inactive-row .text-subtitle-1 {
color: rgba(var(--v-theme-on-surface), 0.72) !important;
filter: grayscale(1);
opacity: 1;
}

.v-data-table .plugin-inactive-row .v-btn-group .v-btn:disabled,
.v-data-table .plugin-inactive-row .v-chip.cursor-pointer.v-chip--disabled {
cursor: not-allowed !important;
}
</style>

1 change: 1 addition & 0 deletions dashboard/src/components/extension/componentPanel/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface CommandItem {
aliases: string[];
permission: PermissionType;
enabled: boolean;
plugin_activated: boolean;
is_group: boolean;
has_conflict: boolean;
reserved: boolean;
Expand Down
4 changes: 3 additions & 1 deletion dashboard/src/i18n/locales/en-US/features/command.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"status": {
"enabled": "Enabled",
"disabled": "Disabled",
"pluginDisabled": "Plugin off",
"conflict": "Conflict"
},
"permission": {
Expand All @@ -39,7 +40,8 @@
"enable": "Enable command",
"disable": "Disable command",
"rename": "Rename command",
"viewDetails": "View details"
"viewDetails": "View details",
"pluginInactive": "Enable the plugin first"
},
"dialogs": {
"rename": {
Expand Down
4 changes: 3 additions & 1 deletion dashboard/src/i18n/locales/ru-RU/features/command.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"status": {
"enabled": "Активна",
"disabled": "Отключена",
"pluginDisabled": "Плагин отключён",
"conflict": "Конфликт"
},
"permission": {
Expand All @@ -39,7 +40,8 @@
"enable": "Включить",
"disable": "Выключить",
"rename": "Переименовать",
"viewDetails": "Подробности"
"viewDetails": "Подробности",
"pluginInactive": "Сначала включите плагин"
},
"dialogs": {
"rename": {
Expand Down
4 changes: 3 additions & 1 deletion dashboard/src/i18n/locales/zh-CN/features/command.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"status": {
"enabled": "已启用",
"disabled": "已禁用",
"pluginDisabled": "未启用",
"conflict": "有冲突"
},
"permission": {
Expand All @@ -39,7 +40,8 @@
"enable": "启用指令",
"disable": "禁用指令",
"rename": "重命名指令",
"viewDetails": "查看详情"
"viewDetails": "查看详情",
"pluginInactive": "请先启用所属插件"
},
"dialogs": {
"rename": {
Expand Down
70 changes: 70 additions & 0 deletions tests/unit/test_command_plugin_activation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Commands from disabled plugins should not look enabled in the dashboard."""

from types import SimpleNamespace

from astrbot.core.star.command_management import (
CommandDescriptor,
_descriptor_to_dict,
_group_conflicts,
_is_plugin_activated,
star_map,
)


def _descriptor(module_path: str, *, enabled: bool = True) -> CommandDescriptor:
return CommandDescriptor(
handler=SimpleNamespace(), # type: ignore[arg-type]
module_path=module_path,
enabled=enabled,
effective_command="demo",
)


def test_plugin_activation_is_serialized_without_mutating_enabled():
original = dict(star_map)
try:
star_map.clear()
star_map["data.plugins.foo.main"] = SimpleNamespace(activated=True)
star_map["data.plugins.bar.main"] = SimpleNamespace(activated=False)

active = _descriptor("data.plugins.foo.main", enabled=True)
inactive = _descriptor("data.plugins.bar.main", enabled=True)
unknown = _descriptor("data.plugins.missing.main", enabled=True)

assert _is_plugin_activated(active) is True
assert _is_plugin_activated(inactive) is False
assert _is_plugin_activated(unknown) is True
assert active.enabled is True
assert inactive.enabled is True
assert unknown.enabled is True

active_dict = _descriptor_to_dict(active)
inactive_dict = _descriptor_to_dict(inactive)
unknown_dict = _descriptor_to_dict(unknown)

assert active_dict["enabled"] is True
assert inactive_dict["enabled"] is True
assert unknown_dict["enabled"] is True
assert active_dict["plugin_activated"] is True
assert inactive_dict["plugin_activated"] is False
assert unknown_dict["plugin_activated"] is True
finally:
star_map.clear()
star_map.update(original)


def test_inactive_plugin_commands_are_excluded_from_conflicts():
original = dict(star_map)
try:
star_map.clear()
star_map["data.plugins.foo.main"] = SimpleNamespace(activated=True)
star_map["data.plugins.bar.main"] = SimpleNamespace(activated=False)

live = _descriptor("data.plugins.foo.main")
off = _descriptor("data.plugins.bar.main")
conflicts = _group_conflicts([live, off])

assert conflicts == {}
finally:
star_map.clear()
star_map.update(original)