Files
AstrBot/src-new/astrbot_sdk/api/event/astr_message_event.py
whatevertogo 9c5ecf0a13 feat: Enhance plugin capability support and legacy compatibility
- Introduced LoadedCapability class to manage plugin capabilities.
- Updated load_plugin function to discover and load capabilities alongside handlers.
- Enhanced Peer class to handle remote provided capabilities during initialization.
- Added tests for capability registration and invocation in legacy plugins.
- Improved MessageChain and message component handling in legacy plugins.
- Added comprehensive tests for legacy plugin integration and compatibility.
- Updated protocol messages to include provided capabilities in initialization.
- Enhanced top-level module imports to include capability-related functions.
2026-03-13 06:16:57 +08:00

344 lines
11 KiB
Python

"""旧版 ``AstrMessageEvent`` 的兼容包装。"""
from __future__ import annotations
from typing import Any, Literal
from pydantic import BaseModel, Field
from ...events import MessageEvent
from ..message.chain import MessageChain
from ..message.components import BaseMessageComponent
from ..platform.platform_metadata import PlatformMetadata
from .astrbot_message import AstrBotMessage, Group, MessageMember
from .event_result import MessageEventResult
from .message_session import MessageSession
from .message_type import MessageType
def _coerce_message_type(
message_type: MessageType | str | None,
*,
has_group: bool = False,
has_user: bool = False,
) -> MessageType:
if isinstance(message_type, MessageType):
return message_type
if isinstance(message_type, str):
try:
return MessageType(message_type)
except ValueError:
pass
if has_group:
return MessageType.GROUP_MESSAGE
if has_user:
return MessageType.FRIEND_MESSAGE
return MessageType.OTHER_MESSAGE
class AstrMessageEventModel(BaseModel):
message_str: str
message_obj: AstrBotMessage
platform_meta: PlatformMetadata | None = None
session_id: str
role: Literal["admin", "member"] = "member"
is_wake: bool = False
is_at_or_wake_command: bool = False
extras: dict[str, Any] = Field(default_factory=dict)
result: MessageEventResult | None = None
has_send_oper: bool = False
call_llm: bool = False
plugins_name: list[str] = Field(default_factory=list)
@classmethod
def from_event(cls, event: "AstrMessageEvent") -> "AstrMessageEventModel":
return cls(
message_str=event.get_message_str(),
message_obj=event.message_obj,
platform_meta=event.platform_meta,
session_id=event.session_id,
role=event.role,
is_wake=event.is_wake,
is_at_or_wake_command=event.is_at_or_wake_command,
extras=dict(event.get_extra()),
result=event.get_result(),
has_send_oper=event.has_send_oper,
call_llm=event.call_llm,
plugins_name=list(event._plugins_name),
)
def to_event(self) -> "AstrMessageEvent":
return AstrMessageEvent(
text=self.message_str,
user_id=self.message_obj.sender.user_id,
group_id=self.message_obj.group_id,
platform=self.platform_meta.id if self.platform_meta else None,
session_id=self.session_id,
raw=self.message_obj.raw_message,
message_obj=self.message_obj,
platform_meta=self.platform_meta,
role=self.role,
is_wake=self.is_wake,
is_at_or_wake_command=self.is_at_or_wake_command,
extras=self.extras,
result=self.result,
has_send_oper=self.has_send_oper,
call_llm=self.call_llm,
plugins_name=self.plugins_name,
)
class AstrMessageEvent(MessageEvent):
def __init__(
self,
*,
text: str = "",
user_id: str | None = None,
group_id: str | None = None,
platform: str | None = None,
session_id: str | None = None,
raw: dict[str, Any] | None = None,
context=None,
reply_handler=None,
message_obj: AstrBotMessage | None = None,
platform_meta: PlatformMetadata | None = None,
role: Literal["admin", "member"] = "member",
is_wake: bool = False,
is_at_or_wake_command: bool = False,
extras: dict[str, Any] | None = None,
result: MessageEventResult | None = None,
has_send_oper: bool = False,
call_llm: bool = False,
plugins_name: list[str] | None = None,
) -> None:
super().__init__(
text=text,
user_id=user_id,
group_id=group_id,
platform=platform,
session_id=session_id,
raw=raw,
context=context,
reply_handler=reply_handler,
)
self.message_obj = message_obj or self._build_message_obj()
self.platform_meta = platform_meta
self.role = role
self.is_wake = is_wake
self.is_at_or_wake_command = is_at_or_wake_command
self._extras = dict(extras or {})
self._result = result
self.has_send_oper = has_send_oper
self.call_llm = call_llm
self._plugins_name = list(plugins_name or [])
self.session = MessageSession(
platform_name=self.get_platform_id(),
message_type=self.get_message_type(),
session_id=self.session_id,
)
self.unified_msg_origin = str(self.session)
self.platform = self.platform_meta or self.platform
@classmethod
def from_payload(
cls,
payload: dict[str, Any],
*,
context=None,
reply_handler=None,
) -> "AstrMessageEvent":
return cls(
text=str(payload.get("text", payload.get("message_str", ""))),
user_id=payload.get("user_id"),
group_id=payload.get("group_id"),
platform=payload.get("platform"),
session_id=payload.get("session_id"),
raw=payload,
context=context,
reply_handler=reply_handler,
)
@classmethod
def from_message_event(cls, event: MessageEvent) -> "AstrMessageEvent":
if isinstance(event, cls):
return event
return cls(
text=event.text,
user_id=event.user_id,
group_id=event.group_id,
platform=event.platform,
session_id=event.session_id,
raw=event.raw,
context=getattr(event, "_context", None),
reply_handler=getattr(event, "_reply_handler", None),
)
def _build_message_obj(self) -> AstrBotMessage:
sender_payload = (
self.raw.get("sender") if isinstance(self.raw, dict) else {}
) or {}
sender = MessageMember(
user_id=str(sender_payload.get("user_id") or self.user_id or ""),
nickname=sender_payload.get("nickname"),
)
group = None
group_payload = (
self.raw.get("group") if isinstance(self.raw, dict) else None
) or None
if isinstance(group_payload, dict):
group = Group(
group_id=str(group_payload.get("group_id") or self.group_id or ""),
group_name=group_payload.get("group_name"),
group_avatar=group_payload.get("group_avatar"),
group_owner=group_payload.get("group_owner"),
group_admins=group_payload.get("group_admins"),
members=group_payload.get("members"),
)
elif self.group_id:
group = Group(group_id=self.group_id)
message_components = self.raw.get("message")
if not isinstance(message_components, list):
message_components = []
message_type = _coerce_message_type(
self.raw.get("message_type"),
has_group=bool(group),
has_user=bool(self.user_id),
)
return AstrBotMessage(
type=message_type,
self_id=str(self.raw.get("self_id", "")),
session_id=self.session_id,
message_id=str(self.raw.get("message_id", "")),
sender=sender,
message=message_components,
message_str=self.text,
raw_message=self.raw,
group=group,
)
def get_platform_name(self) -> str:
if self.platform_meta is not None:
return self.platform_meta.name
return str(self.raw.get("platform_name") or self.raw.get("platform") or "")
def get_platform_id(self) -> str:
if self.platform_meta is not None:
return self.platform_meta.id
return str(self.raw.get("platform_id") or self.raw.get("platform") or "")
def get_message_str(self) -> str:
return self.text
def get_messages(self) -> list[BaseMessageComponent]:
return list(self.message_obj.message)
def get_message_type(self) -> MessageType:
return self.message_obj.type
def get_session_id(self) -> str:
return self.session_id
def get_group_id(self) -> str:
return self.message_obj.group_id
def get_self_id(self) -> str:
return self.message_obj.self_id
def get_sender_id(self) -> str:
return self.message_obj.sender.user_id
def get_sender_name(self) -> str | None:
return self.message_obj.sender.nickname
def set_extra(self, key: str, value: Any) -> None:
self._extras[key] = value
def get_extra(self, key: str | None = None, default: Any = None) -> Any:
if key is None:
return self._extras
return self._extras.get(key, default)
def clear_extra(self) -> None:
self._extras.clear()
def is_private_chat(self) -> bool:
return self.get_message_type() == MessageType.FRIEND_MESSAGE
def is_wake_up(self) -> bool:
return self.is_wake
def is_admin(self) -> bool:
return self.role == "admin"
def set_result(self, result: MessageEventResult | str) -> None:
if isinstance(result, str):
result = MessageEventResult().message(result)
self._result = result
def stop_event(self) -> None:
if self._result is None:
self._result = MessageEventResult().stop_event()
return
self._result.stop_event()
def continue_event(self) -> None:
if self._result is None:
self._result = MessageEventResult().continue_event()
return
self._result.continue_event()
def is_stopped(self) -> bool:
if self._result is None:
return False
return self._result.is_stopped()
def should_call_llm(self, call_llm: bool) -> None:
self.call_llm = call_llm
def get_result(self) -> MessageEventResult | None:
return self._result
def clear_result(self) -> None:
self._result = None
def make_result(self) -> MessageEventResult:
return MessageEventResult()
def plain_result(self, text: str) -> MessageEventResult:
return MessageEventResult().message(text)
def image_result(self, url_or_path: str) -> MessageEventResult:
result = MessageEventResult()
if url_or_path.startswith("http"):
return result.url_image(url_or_path)
return result.file_image(url_or_path)
def chain_result(self, chain: list[BaseMessageComponent]) -> MessageEventResult:
result = MessageEventResult()
result.chain = chain
return result
async def send(self, message: MessageChain) -> None:
self.has_send_oper = True
runtime_context = getattr(self, "_context", None)
if runtime_context is not None and not message.is_plain_text_only():
await runtime_context.platform.send_chain(
self.session_ref or self.session_id,
message.to_payload(),
)
return
await self.reply(message.get_plain_text())
async def react(self, emoji: str) -> None:
self.has_send_oper = True
await self.reply(emoji)
async def get_group(self, group_id: str | None = None, **kwargs) -> Group | None:
if self.message_obj.group is None:
return None
if group_id is None or self.message_obj.group.group_id == group_id:
return self.message_obj.group
return None