From d98cf16a4cc6d06c7f4b18a3c0d3b500ea2e3cf8 Mon Sep 17 00:00:00 2001 From: anka <1350989414@qq.com> Date: Sun, 7 Sep 2025 17:04:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=A0=B9=E6=8D=AEqq?= =?UTF-8?q?=E5=8F=B7/=E7=BE=A4=E5=8F=B7=E4=B8=BB=E5=8A=A8=E5=8F=91?= =?UTF-8?q?=E9=80=81=E6=B6=88=E6=81=AF=E7=9A=84=E5=B0=81=E8=A3=85,=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=8B=E4=BB=B6=E6=9E=84=E9=80=A0=20(#2629?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 增加根据qq号/群号主动发送消息的封装, 增加事件构造 * fix: 增加不支持平台提示, 修正文档字符串 * chore: lint --------- Co-authored-by: Soulter <905617992@qq.com> --- astrbot/core/star/star_tools.py | 137 +++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 21 deletions(-) diff --git a/astrbot/core/star/star_tools.py b/astrbot/core/star/star_tools.py index 40fa3e519..42ed168ff 100644 --- a/astrbot/core/star/star_tools.py +++ b/astrbot/core/star/star_tools.py @@ -1,15 +1,37 @@ +""" +插件开发工具集 +封装了许多常用的操作,方便插件开发者使用 + +说明: + +主动发送消息: send_message(session, message_chain) + 根据 session (unified_msg_origin) 主动发送消息, 前提是需要提前获得或构造 session + +根据id直接主动发送消息: send_message_by_id(type, id, message_chain, platform="aiocqhttp") + 根据 id (例如 qq 号, 群号等) 直接, 主动地发送消息 + +以上两种方式需要构造消息链, 也就是消息组件的列表 + +构造事件: + +首先需要构造一个 AstrBotMessage 对象, 使用 create_message 方法 +然后使用 create_event 方法提交事件到指定平台 +""" + import inspect import os +import uuid from pathlib import Path from typing import Union, Awaitable, List, Optional, ClassVar from astrbot.core.message.components import BaseMessageComponent from astrbot.core.message.message_event_result import MessageChain -from astrbot.api.platform import MessageMember, AstrBotMessage +from astrbot.api.platform import MessageMember, AstrBotMessage, MessageType from astrbot.core.platform.astr_message_event import MessageSesion from astrbot.core.star.context import Context from astrbot.core.star.star import star_map from astrbot.core.utils.astrbot_path import get_astrbot_data_path - +from astrbot.core.platform.sources.aiocqhttp.aiocqhttp_message_event import AiocqhttpMessageEvent +from astrbot.core.platform.sources.aiocqhttp.aiocqhttp_platform_adapter import AiocqhttpAdapter class StarTools: """ @@ -49,42 +71,76 @@ class StarTools: Note: qq_official(QQ官方API平台)不支持此方法 """ + if cls._context is None: + raise ValueError("StarTools not initialized") return await cls._context.send_message(session, message_chain) + @classmethod + async def send_message_by_id( + cls, type: str, id: str, message_chain: MessageChain, platform: str = "aiocqhttp" + ): + """ + 根据 id(例如qq号, 群号等) 直接, 主动地发送消息 + + Args: + type (str): 消息类型, 可选: PrivateMessage, GroupMessage + id (str): 目标ID, 例如QQ号, 群号等 + message_chain (MessageChain): 消息链 + platform (str): 可选的平台名称,默认平台(aiocqhttp), 目前只支持 aiocqhttp + """ + if cls._context is None: + raise ValueError("StarTools not initialized") + platforms = cls._context.platform_manager.get_insts() + if platform == "aiocqhttp": + adapter = next((p for p in platforms if isinstance(p, AiocqhttpAdapter)), None) + if adapter is None: + raise ValueError("未找到适配器: AiocqhttpAdapter") + await AiocqhttpMessageEvent.send_message( + bot=adapter.bot, + message_chain=message_chain, + is_group=(type == "GroupMessage"), + session_id=id, + ) + else: + raise ValueError(f"不支持的平台: {platform}") + @classmethod async def create_message( cls, type: str, self_id: str, session_id: str, - message_id: str, sender: MessageMember, message: List[BaseMessageComponent], message_str: str, - raw_message: object, - group_id: str = "", - ): + message_id: str = "", + raw_message: object = None, + group_id: str = "" + ) -> AstrBotMessage: """ 创建一个AstrBot消息对象 Args: - type (str): 消息类型 + type (str): 消息类型, 例如 "GroupMessage" "FriendMessage" "OtherMessage" self_id (str): 机器人自身ID session_id (str): 会话ID(通常为用户ID)(QQ号, 群号等) - message_id (str): 消息ID - sender (MessageMember): 发送者信息 - message (List[BaseMessageComponent]): 消息组件列表 - message_str (str): 消息字符串 - raw_message (object): 原始消息对象 + sender (MessageMember): 发送者信息, 例如 MessageMember(user_id="123456", nickname="昵称") + message (List[BaseMessageComponent]): 消息组件列表, 也就是消息链, 这个不会发给 llm, 但是会经过其他处理 + message_str (str): 消息字符串, 也就是纯文本消息, 也就是发送给 llm 的消息, 与消息链一致 + + message_id (str): 消息ID, 构造消息时可以随意填写也可不填 + raw_message (object): 原始消息对象, 可以随意填写也可不填 group_id (str, optional): 群组ID, 如果为私聊则为空. Defaults to "". Returns: AstrBotMessage: 创建的消息对象 """ abm = AstrBotMessage() - abm.type = type + abm.type = MessageType(type) abm.self_id = self_id abm.session_id = session_id + if message_id == "": + message_id = uuid.uuid4().hex abm.message_id = message_id abm.sender = sender abm.message = message @@ -93,13 +149,38 @@ class StarTools: abm.group_id = group_id return abm - # todo: 添加构造事件的方法 - # async def create_event( - # self, platform: str, umo: str, sender_id: str, session_id: str - # ): - # platform = self._context.get_platform(platform) + @classmethod + async def create_event( + cls, abm: AstrBotMessage, platform: str = "aiocqhttp", is_wake: bool = True - # todo: 添加找到对应平台并提交对应事件的方法 + ) -> None: + """ + 创建并提交事件到指定平台 + 当有需要创建一个事件, 触发某些处理流程时, 使用该方法 + + Args: + abm (AstrBotMessage): 要提交的消息对象, 请先使用 create_message 创建 + platform (str): 可选的平台名称,默认平台(aiocqhttp), 目前只支持 aiocqhttp + is_wake (bool): 是否标记为唤醒事件, 默认为 True, 只有唤醒事件才会被 llm 响应 + """ + if cls._context is None: + raise ValueError("StarTools not initialized") + platforms = cls._context.platform_manager.get_insts() + if platform == "aiocqhttp": + adapter = next((p for p in platforms if isinstance(p, AiocqhttpAdapter)), None) + if adapter is None: + raise ValueError("未找到适配器: AiocqhttpAdapter") + event = AiocqhttpMessageEvent( + message_str=abm.message_str, + message_obj=abm, + platform_meta=adapter.metadata, + session_id=abm.session_id, + bot=adapter.bot, + ) + event.is_wake = is_wake + adapter.commit_event(event) + else: + raise ValueError(f"不支持的平台: {platform}") @classmethod def activate_llm_tool(cls, name: str) -> bool: @@ -110,6 +191,8 @@ class StarTools: Args: name (str): 工具名称 """ + if cls._context is None: + raise ValueError("StarTools not initialized") return cls._context.activate_llm_tool(name) @classmethod @@ -120,6 +203,8 @@ class StarTools: Args: name (str): 工具名称 """ + if cls._context is None: + raise ValueError("StarTools not initialized") return cls._context.deactivate_llm_tool(name) @classmethod @@ -135,6 +220,8 @@ class StarTools: desc (str): 工具描述 func_obj (Awaitable): 函数对象,必须是异步函数 """ + if cls._context is None: + raise ValueError("StarTools not initialized") cls._context.register_llm_tool(name, func_args, desc, func_obj) @classmethod @@ -146,6 +233,8 @@ class StarTools: Args: name (str): 工具名称 """ + if cls._context is None: + raise ValueError("StarTools not initialized") cls._context.unregister_llm_tool(name) @classmethod @@ -169,8 +258,11 @@ class StarTools: - 创建目录失败(权限不足或其他IO错误) """ if not plugin_name: - frame = inspect.currentframe().f_back - module = inspect.getmodule(frame) + frame = inspect.currentframe() + module = None + if frame: + frame = frame.f_back + module = inspect.getmodule(frame) if not module: raise RuntimeError("无法获取调用者模块信息") @@ -182,6 +274,9 @@ class StarTools: plugin_name = metadata.name + if not plugin_name: + raise ValueError("无法获取插件名称") + data_dir = Path(os.path.join(get_astrbot_data_path(), "plugin_data", plugin_name)) try: