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
15 changes: 15 additions & 0 deletions astrbot/core/core_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ def __init__(self, log_broker: LogBroker, db: BaseDatabase) -> None:
del os.environ["no_proxy"]
logger.debug("HTTP proxy cleared")

async def _load_priority_overrides(self) -> None:
overrides = await sp.global_get("handler_priority_overrides", {})
if not isinstance(overrides, dict) or not overrides:
return

priority_map = {
k: v.get("priority", 0) for k, v in overrides.items() if isinstance(v, dict)
}
if not priority_map:
return

star_handlers_registry.load_priority_overrides(priority_map)

async def initialize(self) -> None:
"""初始化 AstrBot 核心生命周期管理类.

Expand Down Expand Up @@ -157,6 +170,8 @@ async def initialize(self) -> None:
# 扫描、注册插件、实例化插件类
await self.plugin_manager.reload()

await self._load_priority_overrides()

# 根据配置实例化各个 Provider
await self.provider_manager.initialize()

Expand Down
3 changes: 3 additions & 0 deletions astrbot/core/star/command_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class CommandDescriptor:
is_group: bool = False
is_sub_command: bool = False
reserved: bool = False
priority: int = 0
config: CommandConfig | None = None
has_conflict: bool = False
sub_commands: list[CommandDescriptor] = field(default_factory=list)
Expand Down Expand Up @@ -288,6 +289,7 @@ def _build_descriptor(handler: StarHandlerMetadata) -> CommandDescriptor | None:
is_group=isinstance(filter_ref, CommandGroupFilter),
is_sub_command=is_sub_command,
reserved=plugin_meta.reserved if plugin_meta else False,
priority=star_handlers_registry._get_effective_priority(handler),
)
return descriptor

Expand Down Expand Up @@ -487,6 +489,7 @@ def _descriptor_to_dict(desc: CommandDescriptor) -> dict[str, Any]:
"is_group": desc.is_group,
"has_conflict": desc.has_conflict,
"reserved": desc.reserved,
"priority": desc.priority,
}
# 如果是指令组,包含子指令列表
if desc.is_group and desc.sub_commands:
Expand Down
15 changes: 14 additions & 1 deletion astrbot/core/star/star_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ class StarHandlerRegistry(Generic[T]):
def __init__(self):
self.star_handlers_map: dict[str, StarHandlerMetadata] = {}
self._handlers: list[StarHandlerMetadata] = []
self._priority_overrides: dict[str, int] = {}

def load_priority_overrides(self, overrides: dict[str, int]):
self._priority_overrides = overrides
self._handlers.sort(key=lambda h: -self._get_effective_priority(h))

def get_original_priority(self, handler: StarHandlerMetadata) -> int:
return handler.extras_configs.get("priority", 0)

def _get_effective_priority(self, handler: StarHandlerMetadata) -> int:
if handler.handler_full_name in self._priority_overrides:
return self._priority_overrides[handler.handler_full_name]
return handler.extras_configs.get("priority", 0)

def append(self, handler: StarHandlerMetadata):
"""添加一个 Handler,并保持按优先级有序"""
Expand All @@ -23,7 +36,7 @@ def append(self, handler: StarHandlerMetadata):

self.star_handlers_map[handler.handler_full_name] = handler
self._handlers.append(handler)
self._handlers.sort(key=lambda h: -h.extras_configs["priority"])
self._handlers.sort(key=lambda h: -self._get_effective_priority(h))

def _print_handlers(self):
for handler in self._handlers:
Expand Down
219 changes: 218 additions & 1 deletion astrbot/dashboard/routes/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
from astrbot.core.star.filter.command_group import CommandGroupFilter
from astrbot.core.star.filter.permission import PermissionTypeFilter
from astrbot.core.star.filter.regex import RegexFilter
from astrbot.core.star.star_handler import EventType, star_handlers_registry
from astrbot.core.star.star import star_registry
from astrbot.core.star.star_handler import (
EventType,
StarHandlerMetadata,
star_handlers_registry,
)
from astrbot.core.star.star_manager import PluginManager

from .route import Response, Route, RouteContext
Expand Down Expand Up @@ -58,6 +63,11 @@ def __init__(
"/plugin/changelog": ("GET", self.get_plugin_changelog),
"/plugin/source/get": ("GET", self.get_custom_source),
"/plugin/source/save": ("POST", self.save_custom_source),
"/plugin/priority": [
("GET", self.get_priority),
("POST", self.update_priority),
],
"/plugin/priority/reset": ("POST", self.reset_priority),
}
self.core_lifecycle = core_lifecycle
self.plugin_manager = plugin_manager
Expand Down Expand Up @@ -383,6 +393,213 @@ async def get_plugin_handlers_info(self, handler_full_names: list[str]):

return handlers

def _build_filters_summary(self, handler: StarHandlerMetadata) -> str:
if handler.event_type != EventType.AdapterMessageEvent:
return "auto"

parts: list[str] = []
for f in handler.event_filters:
if isinstance(f, CommandFilter):
base = ""
if getattr(f, "parent_command_names", None):
base = f.parent_command_names[0]
cmd = f"{base} {f.command_name}".strip()
parts.append(f"command: {cmd}")
elif isinstance(f, CommandGroupFilter):
try:
cmd = f.get_complete_command_names()[0].strip()
except Exception:
cmd = "command_group"
parts.append(f"command_group: {cmd}")
elif isinstance(f, RegexFilter):
parts.append(f"regex: {f.regex_str}")
elif isinstance(f, PermissionTypeFilter):
parts.append("permission")

return ", ".join(parts) if parts else "event_listener"

def _build_priority_map(self, stored: dict) -> dict[str, int]:
"""Build a priority map from stored override data."""
return {
k: int(v.get("priority", 0))
for k, v in stored.items()
if isinstance(v, dict)
}

async def _load_priority_overrides(self) -> tuple[dict, dict[str, int]]:
"""Load priority overrides from storage and return (stored_data, priority_map)."""
stored = await sp.global_get("handler_priority_overrides", {})
stored = stored if isinstance(stored, dict) else {}
return stored, self._build_priority_map(stored)

async def _save_and_apply_priority_overrides(self, stored: dict) -> None:
"""Save priority overrides to storage and apply to registry."""
await sp.global_put("handler_priority_overrides", stored)
star_handlers_registry.load_priority_overrides(self._build_priority_map(stored))

async def get_priority(self):
try:
_, priority_map = await self._load_priority_overrides()
star_handlers_registry.load_priority_overrides(priority_map)

plugins = []
for star in star_registry:
if not star.module_path:
continue

handlers_payload = []
handler_effective_priorities: list[int] = []
for handler_full_name in star.star_handler_full_names:
handler = star_handlers_registry.get_handler_by_full_name(
handler_full_name
)
if handler is None:
continue

effective_priority = int(
priority_map.get(
handler.handler_full_name,
handler.extras_configs.get("priority", 0) or 0,
)
or 0
)
original_priority = int(
handler.extras_configs.get("priority", 0) or 0
)
is_overridden = handler.handler_full_name in priority_map

handlers_payload.append(
{
"handler_full_name": handler.handler_full_name,
"handler_name": handler.handler_name,
"event_type": handler.event_type.name,
"priority": effective_priority,
"original_priority": original_priority,
"is_overridden": is_overridden,
"enabled": handler.enabled,
"description": handler.desc or "无描述",
"filters_summary": self._build_filters_summary(handler),
}
)
handler_effective_priorities.append(effective_priority)

plugins.append(
{
"name": star.name,
"display_name": star.display_name,
"module_path": star.module_path,
"activated": star.activated,
"reserved": star.reserved,
"handlers": handlers_payload,
"effective_priority": max(handler_effective_priorities)
if handler_effective_priorities
else 0,
}
)

return Response().ok({"plugins": plugins}).__dict__
except Exception as e:
logger.error(f"/api/plugin/priority: {traceback.format_exc()}")
return Response().error(str(e)).__dict__

async def update_priority(self):
if DEMO_MODE:
return (
Response()
.error("You are not permitted to do this operation in demo mode")
.__dict__
)

try:
data = await request.get_json()
updates = data.get("updates", []) if isinstance(data, dict) else []
if not isinstance(updates, list) or not updates:
return (
Response().error("updates fields must be a non-empty list").__dict__
)

stored, priority_map = await self._load_priority_overrides()

for item in updates:
if not isinstance(item, dict):
continue
handler_full_name = item.get("handler_full_name")
if not isinstance(handler_full_name, str) or not handler_full_name:
continue
if "priority" not in item:
continue
try:
new_priority = int(item["priority"])
except (TypeError, ValueError):
return (
Response()
.error(f"invalid priority for handler: {handler_full_name}")
.__dict__
)

handler = star_handlers_registry.get_handler_by_full_name(
handler_full_name
)
if handler is None:
return (
Response()
.error(f"handler not found: {handler_full_name}")
.__dict__
)

original_priority = handler.extras_configs.get("priority", 0)
stored[handler_full_name] = {
"priority": new_priority,
"original_priority": original_priority,
}
priority_map[handler_full_name] = new_priority

await self._save_and_apply_priority_overrides(stored)

return Response().ok(None, "优先级更新成功").__dict__
except Exception as e:
logger.error(f"/api/plugin/priority: {traceback.format_exc()}")
return Response().error(str(e)).__dict__

async def reset_priority(self):
if DEMO_MODE:
return (
Response()
.error("You are not permitted to do this operation in demo mode")
.__dict__
)

try:
data = await request.get_json()
if not isinstance(data, dict):
return Response().error("invalid request body").__dict__

reset_all = data.get("reset_all", False) is True
handler_full_names = data.get("handler_full_names", [])

stored, _ = await self._load_priority_overrides()
if reset_all:
stored = {}
else:
if not isinstance(handler_full_names, list) or not handler_full_names:
return (
Response()
.error(
"handler_full_names must be a non-empty list or reset_all=true"
)
.__dict__
)
for h in handler_full_names:
if isinstance(h, str):
stored.pop(h, None)

await self._save_and_apply_priority_overrides(stored)

return Response().ok(None, "重置成功").__dict__
except Exception as e:
logger.error(f"/api/plugin/priority/reset: {traceback.format_exc()}")
return Response().error(str(e)).__dict__

async def install_plugin(self):
if DEMO_MODE:
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const emit = defineEmits<{
const commandHeaders = computed(() => [
{ title: tm('table.headers.command'), key: 'effective_command', minWidth: '100px' },
{ title: tm('table.headers.type'), key: 'type', sortable: false, width: '100px' },
{ title: tm('table.headers.priority'), key: 'priority', sortable: true, width: '80px' },
{ title: tm('table.headers.plugin'), key: 'plugin', width: '140px' },
{ title: tm('table.headers.description'), key: 'description', sortable: false },
{ title: tm('table.headers.permission'), key: 'permission', sortable: false, width: '100px' },
Expand Down Expand Up @@ -135,6 +136,10 @@ const getRowProps = ({ item }: { item: CommandItem }) => {
</v-chip>
</template>

<template v-slot:item.priority="{ item }">
<div class="text-body-2">{{ item.priority ?? 0 }}</div>
</template>

<template v-slot:item.plugin="{ item }">
<div class="text-body-2">{{ item.plugin_display_name || item.plugin }}</div>
</template>
Expand Down
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 @@ -22,6 +22,7 @@ export interface CommandItem {
is_group: boolean;
has_conflict: boolean;
reserved: boolean;
priority?: number;
sub_commands: CommandItem[];
}

Expand Down
1 change: 1 addition & 0 deletions dashboard/src/i18n/locales/en-US/features/command.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"headers": {
"command": "Command",
"type": "Type",
"priority": "Priority",
"plugin": "Plugin",
"description": "Description",
"permission": "Permission",
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/i18n/locales/zh-CN/features/command.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"headers": {
"command": "指令",
"type": "类型",
"priority": "优先级",
"plugin": "所属插件",
"description": "描述",
"permission": "权限",
Expand Down