diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index 8e04077bb..7f73e5d91 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -311,10 +311,24 @@ class Image(BaseMessageComponent): class Reply(BaseMessageComponent): type: ComponentType = "Reply" id: T.Union[str, int] - text: T.Optional[str] = "" - qq: T.Optional[int] = 0 + """所引用的消息 ID""" + chain: T.Optional[T.List["BaseMessageComponent"]] = [] + """引用的消息段列表""" + sender_id: T.Optional[int] | T.Optional[str] = 0 + """引用的消息发送者 ID""" + sender_nickname: T.Optional[str] = "" + """引用的消息发送者昵称""" time: T.Optional[int] = 0 + """引用的消息发送时间""" + message_str: T.Optional[str] = "" + """解析后的纯文本消息字符串""" + + text: T.Optional[str] = "" + """deprecated""" + qq: T.Optional[int] = 0 + """deprecated""" seq: T.Optional[int] = 0 + """deprecated""" def __init__(self, **_): super().__init__(**_) diff --git a/astrbot/core/platform/astr_message_event.py b/astrbot/core/platform/astr_message_event.py index 72e4414b6..fceb63ce7 100644 --- a/astrbot/core/platform/astr_message_event.py +++ b/astrbot/core/platform/astr_message_event.py @@ -14,6 +14,7 @@ from astrbot.core.message.components import ( At, AtAll, Forward, + Reply, ) from astrbot.core.utils.metrics import Metric from astrbot.core.provider.entites import ProviderRequest @@ -101,8 +102,15 @@ class AstrMessageEvent(abc.ABC): elif isinstance(i, Forward): # 转发消息 outline += "[转发消息]" + elif isinstance(i, Reply): + # 引用回复 + if i.message_str: + outline += f"[引用消息({i.sender_nickname}: {i.message_str})]" + else: + outline += "[引用消息]" else: outline += f"[{i.type}]" + outline += " " return outline def get_message_outline(self) -> str: diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 0a2e8d430..0d11e3c0b 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -160,8 +160,14 @@ class AiocqhttpAdapter(Platform): return abm - async def _convert_handle_message_event(self, event: Event) -> AstrBotMessage: - """OneBot V11 消息类事件""" + async def _convert_handle_message_event( + self, event: Event, get_reply=True + ) -> AstrBotMessage: + """OneBot V11 消息类事件 + + @param event: 事件对象 + @param get_reply: 是否获取回复消息。这个参数是为了防止多个回复嵌套。 + """ abm = AstrBotMessage() abm.self_id = str(event.self_id) abm.sender = MessageMember( @@ -240,6 +246,36 @@ class AiocqhttpAdapter(Platform): except BaseException as e: logger.error(f"获取文件失败: {e},此消息段将被忽略。") + elif t == "reply": + if not get_reply: + a = ComponentTypes[t](**m["data"]) # noqa: F405 + abm.message.append(a) + else: + try: + reply_event_data = await self.bot.call_action( + action="get_msg", + message_id=int(m["data"]["id"]), + ) + abm_reply = await self._convert_handle_message_event( + Event.from_payload(reply_event_data), get_reply=False + ) + + reply_seg = Reply( + id=abm_reply.message_id, + chain=abm_reply.message, + sender_id=abm_reply.sender.user_id, + sender_nickname=abm_reply.sender.nickname, + time=abm_reply.timestamp, + message_str=abm_reply.message_str, + text=abm_reply.message_str, # for compatibility + qq=abm_reply.sender.user_id, # for compatibility + ) + + abm.message.append(reply_seg) + except BaseException as e: + logger.error(f"获取引用消息失败: {e}。") + a = ComponentTypes[t](**m["data"]) # noqa: F405 + abm.message.append(a) else: a = ComponentTypes[t](**m["data"]) # noqa: F405 abm.message.append(a) diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index b7d7a6e7e..3dde1802b 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -17,6 +17,7 @@ from astrbot.api.message_components import ( File as AstrBotFile, Video, At, + Reply, ) from astrbot.core.platform.astr_message_event import MessageSesion from astrbot.api.platform import register_platform_adapter @@ -68,7 +69,7 @@ class TelegramPlatformAdapter(Platform): ) message_handler = TelegramMessageHandler( filters=filters.ALL, # receive all messages - callback=self.convert_message, + callback=self.message_handler, ) self.application.add_handler(message_handler) self.client = self.application.bot @@ -104,33 +105,64 @@ class TelegramPlatformAdapter(Platform): chat_id=update.effective_chat.id, text=self.config["start_message"] ) + async def message_handler(self, update: Update, context: ContextTypes.DEFAULT_TYPE): + logger.debug(f"Telegram message: {update.message}") + abm = await self.convert_message(update, context) + await self.handle_msg(abm) + async def convert_message( - self, update: Update, context: ContextTypes.DEFAULT_TYPE + self, update: Update, context: ContextTypes.DEFAULT_TYPE, get_reply=True ) -> AstrBotMessage: + """转换 Telegram 的消息对象为 AstrBotMessage 对象。 + + @param update: Telegram 的 Update 对象。 + @param context: Telegram 的 Context 对象。 + @param get_reply: 是否获取回复消息。这个参数是为了防止多个回复嵌套。 + """ message = AstrBotMessage() # 获得是群聊还是私聊 - if update.effective_chat.type == ChatType.PRIVATE: + if update.message.chat.type == ChatType.PRIVATE: message.type = MessageType.FRIEND_MESSAGE else: message.type = MessageType.GROUP_MESSAGE - message.group_id = str(update.effective_chat.id) + message.group_id = str(update.message.chat.id) if update.message.message_thread_id: # Topic Group message.group_id += "#" + str(update.message.message_thread_id) message.message_id = str(update.message.message_id) - message.session_id = str(update.effective_chat.id) + message.session_id = str(update.message.chat.id) message.sender = MessageMember( - str(update.effective_user.id), update.effective_user.username + str(update.message.from_user.id), update.message.from_user.username ) message.self_id = str(context.bot.username) message.raw_message = update message.message_str = "" message.message = [] - logger.debug(f"Telegram message: {update.message}") + if update.message.reply_to_message: + # 获取回复消息 + reply_update = Update( + update_id=1, + message=update.message.reply_to_message, + ) + reply_abm = await self.convert_message(reply_update, context, False) + + message.message.append( + Reply( + id=reply_abm.message_id, + chain=reply_abm.message, + sender_id=reply_abm.sender.user_id, + sender_nickname=reply_abm.sender.nickname, + time=reply_abm.timestamp, + message_str=reply_abm.message_str, + text=reply_abm.message_str, + qq=reply_abm.sender.user_id, + ) + ) if update.message.text: + # 处理文本消息 plain_text = update.message.text if update.message.entities: @@ -178,7 +210,7 @@ class TelegramPlatformAdapter(Platform): Video(file=file.file_path, path=file.file_path), ] - await self.handle_msg(message) + return message async def handle_msg(self, message: AstrBotMessage): message_event = TelegramPlatformEvent( diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index 42b88cae2..cdb1b3d6d 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -4,6 +4,7 @@ from typing import Dict, List, Awaitable from dataclasses import dataclass from astrbot import logger + @dataclass class FuncTool: """ diff --git a/astrbot/core/star/register/star_handler.py b/astrbot/core/star/register/star_handler.py index 86620cae6..77d7fb482 100644 --- a/astrbot/core/star/register/star_handler.py +++ b/astrbot/core/star/register/star_handler.py @@ -15,7 +15,6 @@ from ..filter.regex import RegexFilter from typing import Awaitable from astrbot.core.provider.func_tool_manager import SUPPORTED_TYPES from astrbot.core.provider.register import llm_tools -from astrbot.core import logger def get_handler_full_name(awaitable: Awaitable) -> str: diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 9fa27798b..347bc13ef 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -485,7 +485,9 @@ class PluginManager: for handler in star_handlers_registry.get_handlers_by_module_name( plugin_module_path ): - logger.info(f"移除了插件 {plugin_name} 的处理函数 {handler.handler_name} ({len(star_handlers_registry)})") + logger.info( + f"移除了插件 {plugin_name} 的处理函数 {handler.handler_name} ({len(star_handlers_registry)})" + ) star_handlers_registry.remove(handler) keys_to_delete = [ k diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index c8ed2564d..463f24950 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -17,7 +17,7 @@ from astrbot.core.star.filter.permission import PermissionTypeFilter from astrbot.core.config.default import VERSION from .long_term_memory import LongTermMemory from astrbot.core import logger -from astrbot.api.message_components import Plain, Image +from astrbot.api.message_components import Plain, Image, Reply from typing import Union @@ -1088,12 +1088,17 @@ UID: {user_id} 此 ID 可用于设置管理员。 @filter.on_llm_request() async def decorate_llm_req(self, event: AstrMessageEvent, req: ProviderRequest): - """在请求 LLM 前注入人格信息、Identifier、时间等 System Prompt""" - logger.debug(req.conversation) - + """在请求 LLM 前注入人格信息、Identifier、时间、回复内容等 System Prompt""" if self.prompt_prefix: req.prompt = self.prompt_prefix + req.prompt + # 解析引用内容 + quote = None + for comp in event.message_obj.message: + if isinstance(comp, Reply): + quote = comp + break + if self.identifier: user_id = event.message_obj.sender.user_id user_nickname = event.message_obj.sender.nickname @@ -1129,6 +1134,13 @@ UID: {user_id} 此 ID 可用于设置管理员。 if begin_dialogs := persona["_begin_dialogs_processed"]: req.contexts[:0] = begin_dialogs + if quote and quote.message_str: + if quote.sender_nickname: + sender_info = f"(Sent by {quote.sender_nickname})" + else: + sender_info = "" + req.system_prompt += f"\nUser is quoting the message{sender_info}: {quote.message_str}, please consider the context." + if self.ltm: try: await self.ltm.on_req_llm(event, req)