From 7c1e8ce48c0290a2238939e5607bed5ce1648974 Mon Sep 17 00:00:00 2001 From: BigFace123 <30950532+BigFace123@users.noreply.github.com> Date: Sat, 12 Apr 2025 10:17:42 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E6=B7=BB=E5=8A=A0gewechat=E8=A2=ABat?= =?UTF-8?q?=E4=BA=BAwxid=E8=8E=B7=E5=8F=96=EF=BC=8CAstrBotMessage=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0be=5Fat=5Fwxid=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/astrbot_message.py | 2 ++ astrbot/core/platform/sources/gewechat/client.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/astrbot/core/platform/astrbot_message.py b/astrbot/core/platform/astrbot_message.py index e7bd4bd9c..54ed51e80 100644 --- a/astrbot/core/platform/astrbot_message.py +++ b/astrbot/core/platform/astrbot_message.py @@ -62,6 +62,8 @@ class AstrBotMessage: raw_message: object timestamp: int # 消息时间戳 + be_at_wxid: List[str] # gewechat用的群组内at信息,用于机器人获取被at的wxid + def __init__(self) -> None: self.timestamp = int(time.time()) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index ccecc0c71..fb1f13663 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -152,9 +152,13 @@ class SimpleGewechatClient: # at # content = content.split('\u2005')[1] content = re.sub(r"@[^\u2005]*\u2005", "", content) + abm.be_at_wxid = re.findall(r')',d["MsgSource"]) + abm.group_id = from_user_name # at msg_source = d["MsgSource"] + + if ( f"" in msg_source or f"" in msg_source From afa1aa5d932e631eb5dfaa950cec0abd16697c68 Mon Sep 17 00:00:00 2001 From: XiGuang <1046532637@qq.com> Date: Fri, 18 Apr 2025 21:07:39 +0800 Subject: [PATCH 02/29] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9C=9F=E5=AE=9E=E5=A7=93=E5=90=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=B9=E4=B8=BA=E4=BB=8E?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=E4=B8=AD=E6=8F=90=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/sources/gewechat/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index ccecc0c71..9fe0d0999 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -197,7 +197,8 @@ class SimpleGewechatClient: else: user_real_name = self.userrealnames[abm.group_id][user_id] else: - user_real_name = d.get("PushContent", "unknown : ").split(" : ")[0] + info = (await self.get_user_or_group_info(user_id))["data"][0] + user_real_name = info["nickName"] abm.sender = MessageMember(user_id, user_real_name) abm.raw_message = d From d916fda04c42968a084cc56639872ae975c0892f Mon Sep 17 00:00:00 2001 From: XiGuang <1046532637@qq.com> Date: Sat, 19 Apr 2025 12:10:51 +0800 Subject: [PATCH 03/29] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=BC=95=E7=94=A8=E5=B5=8C=E5=A5=97=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/sources/gewechat/client.py | 3 +++ .../platform/sources/gewechat/xml_data_parser.py | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 9fe0d0999..5045b71f8 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -252,6 +252,9 @@ class SimpleGewechatClient: abm_data = data_parser.parse_mutil_49() if abm_data: abm.message.append(abm_data) + abm.message_str = abm_data.message_str + abm.message.append(Plain(abm_data.message_str)) + abm.message[-2].message_str = abm.message[-2].sender_str case 51: # 帐号消息同步? logger.info("消息类型(51):帐号消息同步?") case 10000: # 被踢出群聊/更换群主/修改群名称 diff --git a/astrbot/core/platform/sources/gewechat/xml_data_parser.py b/astrbot/core/platform/sources/gewechat/xml_data_parser.py index 476c37644..ae05860c0 100644 --- a/astrbot/core/platform/sources/gewechat/xml_data_parser.py +++ b/astrbot/core/platform/sources/gewechat/xml_data_parser.py @@ -57,7 +57,20 @@ class GeweDataParser: if displayname is not None: replied_nickname = displayname.text if refermsg_content is not None: - replied_content = refermsg_content.text + # 处理引用嵌套,包括嵌套公众号消息 + if refermsg_content.text.startswith("") or refermsg_content.text.startswith(" Date: Sat, 19 Apr 2025 16:15:47 +0800 Subject: [PATCH 04/29] =?UTF-8?q?bug=20fix:=20=E6=9B=B4=E6=96=B0=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E5=B5=8C=E5=A5=97=E6=B6=88=E6=81=AF=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF=E6=8C=81=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/sources/gewechat/xml_data_parser.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/xml_data_parser.py b/astrbot/core/platform/sources/gewechat/xml_data_parser.py index ae05860c0..a7a0452a0 100644 --- a/astrbot/core/platform/sources/gewechat/xml_data_parser.py +++ b/astrbot/core/platform/sources/gewechat/xml_data_parser.py @@ -62,9 +62,14 @@ class GeweDataParser: try: logger.debug("gewechat: Reference message is nested") refer_root = eT.fromstring(refermsg_content.text) - refermsg_content_title = refer_root.find("appmsg").find("title") - logger.debug(f"gewechat: Reference message nesting: {refermsg_content_title.text}") - replied_content = refermsg_content_title.text + img=refer_root.find("img") + if img is not None: + replied_content = "[图片]" + else: + app_msg=refer_root.find("appmsg") + refermsg_content_title = app_msg.find("title") + logger.debug(f"gewechat: Reference message nesting: {refermsg_content_title.text}") + replied_content = refermsg_content_title.text except Exception as e: logger.error(f"gewechat: nested failed, {e}") # 处理异常情况 From 98427345cf7bf268520e28908fb1ec0bb7d9bad0 Mon Sep 17 00:00:00 2001 From: kkjz <253839323@sohu.com> Date: Sun, 20 Apr 2025 12:04:02 +0800 Subject: [PATCH 05/29] =?UTF-8?q?bug:=20=E4=BF=AE=E5=A4=8Daiocqhttp?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E4=BD=BF=E7=94=A8=E6=8C=87=E4=BB=A4=E7=BB=84?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E5=A6=82=E6=9E=9C=E4=BD=BF=E7=94=A8=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E4=B8=AD=E6=90=BA=E5=B8=A6=E7=BD=91=E5=9D=80=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E8=AF=86=E5=88=AB=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiocqhttp/aiocqhttp_platform_adapter.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 198946b1a..b06f537d4 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -3,6 +3,7 @@ import time import asyncio import logging import uuid +import re from typing import Awaitable, Any from aiocqhttp import CQHttp, Event from astrbot.api.platform import ( @@ -159,6 +160,14 @@ class AiocqhttpAdapter(Platform): return abm + def _is_url_or_ip(self,text: str) -> bool: + """ + 判断一个字符串是否为网址(http/https 开头)或 IP 地址。 + """ + url_pattern = r"^(?:http|https)://.+$" + ip_pattern = r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" + return bool(re.match(url_pattern, text) or re.match(ip_pattern, text)) + async def _convert_handle_message_event( self, event: Event, get_reply=True ) -> AstrBotMessage: @@ -202,13 +211,30 @@ class AiocqhttpAdapter(Platform): return # 按消息段类型类型适配 - for m in event.message: + for idx, m in enumerate(event.message): t = m["type"] a = None if t == "text": - message_str += m["data"]["text"].strip() - a = ComponentTypes[t](**m["data"]) # noqa: F405 - abm.message.append(a) + should_append_new = True # 是否需要正常处理的标签 + # 合并相邻文本段的情况 + if idx > 0 and event.message[idx - 1]["type"] == "text": + prev_text = event.message[idx - 1]["data"]["text"] + curr_text = m["data"]["text"].strip() + + # 处理网址或IP地址的特殊情况,处理两端文本中间为url时的情况 + if (prev_text.endswith(" ") and self._is_url_or_ip(curr_text)) or \ + (idx > 1 and m["data"]["text"].startswith(" ") and self._is_url_or_ip(prev_text.strip())): + # 合并到前一个文本段 + message_str += " " + curr_text + event.message[idx - 1]["data"]["text"] = message_str + a = ComponentTypes[t](**event.message[idx - 1]["data"]) + abm.message[-1] = a + should_append_new = False + # 如果不需要合并,添加为新的消息段 + if should_append_new: + message_str += m["data"]["text"].strip() + a = ComponentTypes[t](**m["data"]) + abm.message.append(a) elif t == "file": if m["data"].get("url") and m["data"].get("url").startswith("http"): From 0607b95df6d1cae61fbdc48fdd25ea10d2526393 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 20 Apr 2025 15:40:51 +0800 Subject: [PATCH 06/29] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/sources/gewechat/client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 5045b71f8..e246e6a2c 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -197,8 +197,12 @@ class SimpleGewechatClient: else: user_real_name = self.userrealnames[abm.group_id][user_id] else: - info = (await self.get_user_or_group_info(user_id))["data"][0] - user_real_name = info["nickName"] + try: + info = (await self.get_user_or_group_info(user_id))["data"][0] + user_real_name = info["nickName"] + except Exception as e: + logger.debug(f"获取用户 {user_id} 昵称失败: {e}") + user_real_name = user_id abm.sender = MessageMember(user_id, user_real_name) abm.raw_message = d From d921b0f6bd332d82b1403448e2c5112f7e8b39f6 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 20 Apr 2025 16:00:59 +0800 Subject: [PATCH 07/29] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20gewechat=20=E7=9A=84=E5=BC=95=E7=94=A8=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/message/components.py | 12 +++--- .../core/platform/sources/gewechat/client.py | 12 +++--- .../sources/gewechat/xml_data_parser.py | 42 ++++++++++++------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index 781dfafca..5020d6b26 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -407,17 +407,15 @@ class Reply(BaseMessageComponent): id: T.Union[str, int] """所引用的消息 ID""" chain: T.Optional[T.List["BaseMessageComponent"]] = [] - """引用的消息段列表""" + """被引用的消息段列表""" sender_id: T.Optional[int] | T.Optional[str] = 0 - """引用的消息发送者 ID""" + """被引用的消息对应的发送者的 ID""" sender_nickname: T.Optional[str] = "" - """引用的消息发送者昵称""" + """被引用的消息对应的发送者的昵称""" time: T.Optional[int] = 0 - """引用的消息发送时间""" + """被引用的消息发送时间""" message_str: T.Optional[str] = "" - """解析后的纯文本消息字符串""" - sender_str: T.Optional[str] = "" - """被引用的消息纯文本""" + """被引用的消息解析后的纯文本消息字符串""" text: T.Optional[str] = "" """deprecated""" diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index e246e6a2c..e5e596b49 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -253,12 +253,12 @@ class SimpleGewechatClient: logger.info("消息类型(48):地理位置") case 49: # 公众号/文件/小程序/引用/转账/红包/视频号/群聊邀请 data_parser = GeweDataParser(content, abm.group_id == "") - abm_data = data_parser.parse_mutil_49() - if abm_data: - abm.message.append(abm_data) - abm.message_str = abm_data.message_str - abm.message.append(Plain(abm_data.message_str)) - abm.message[-2].message_str = abm.message[-2].sender_str + segments = data_parser.parse_mutil_49() + if segments: + abm.message.extend(segments) + for seg in segments: + if isinstance(seg, Plain): + abm.message_str += seg.text case 51: # 帐号消息同步? logger.info("消息类型(51):帐号消息同步?") case 10000: # 被踢出群聊/更换群主/修改群名称 diff --git a/astrbot/core/platform/sources/gewechat/xml_data_parser.py b/astrbot/core/platform/sources/gewechat/xml_data_parser.py index a7a0452a0..1af4a051a 100644 --- a/astrbot/core/platform/sources/gewechat/xml_data_parser.py +++ b/astrbot/core/platform/sources/gewechat/xml_data_parser.py @@ -1,6 +1,11 @@ from defusedxml import ElementTree as eT from astrbot.api import logger -from astrbot.api.message_components import WechatEmoji as Emoji, Reply, Plain +from astrbot.api.message_components import ( + WechatEmoji as Emoji, + Reply, + Plain, + BaseMessageComponent, +) class GeweDataParser: @@ -11,7 +16,7 @@ class GeweDataParser: def _format_to_xml(self): return eT.fromstring(self.data) - def parse_mutil_49(self): + def parse_mutil_49(self) -> list[BaseMessageComponent] | None: appmsg_type = self._format_to_xml().find(".//appmsg/type") if appmsg_type is None: return @@ -34,13 +39,18 @@ class GeweDataParser: except Exception as e: logger.error(f"gewechat: parse_emoji failed, {e}") - def parse_reply(self) -> Reply | None: + def parse_reply(self) -> list[Reply, Plain] | None: + """解析引用消息 + + Returns: + list[Reply, Plain]: 一个包含两个元素的列表。Reply 消息对象和引用者说的文本内容。微信平台下引用消息时只能发送文本消息。 + """ try: replied_id = -1 replied_uid = 0 replied_nickname = "" - replied_content = "" - content = "" + replied_content = "" # 被引用者说的内容 + content = "" # 引用者说的内容 root = self._format_to_xml() refermsg = root.find(".//refermsg") @@ -58,17 +68,21 @@ class GeweDataParser: replied_nickname = displayname.text if refermsg_content is not None: # 处理引用嵌套,包括嵌套公众号消息 - if refermsg_content.text.startswith("") or refermsg_content.text.startswith("" + ) or refermsg_content.text.startswith(" Date: Sun, 20 Apr 2025 18:09:04 +0800 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8groupby?= =?UTF-8?q?=E6=9D=A5=E5=90=88=E5=B9=B6aiocqhttp=E8=BF=9E=E7=BB=AD=E7=9A=84?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiocqhttp/aiocqhttp_platform_adapter.py | 156 ++++++++---------- 1 file changed, 68 insertions(+), 88 deletions(-) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index b06f537d4..b7beff156 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -3,7 +3,7 @@ import time import asyncio import logging import uuid -import re +import itertools from typing import Awaitable, Any from aiocqhttp import CQHttp, Event from astrbot.api.platform import ( @@ -160,14 +160,6 @@ class AiocqhttpAdapter(Platform): return abm - def _is_url_or_ip(self,text: str) -> bool: - """ - 判断一个字符串是否为网址(http/https 开头)或 IP 地址。 - """ - url_pattern = r"^(?:http|https)://.+$" - ip_pattern = r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" - return bool(re.match(url_pattern, text) or re.match(ip_pattern, text)) - async def _convert_handle_message_event( self, event: Event, get_reply=True ) -> AstrBotMessage: @@ -211,99 +203,87 @@ class AiocqhttpAdapter(Platform): return # 按消息段类型类型适配 - for idx, m in enumerate(event.message): - t = m["type"] + for t, m_group in itertools.groupby(event.message, key=lambda x: x["type"]): a = None if t == "text": - should_append_new = True # 是否需要正常处理的标签 # 合并相邻文本段的情况 - if idx > 0 and event.message[idx - 1]["type"] == "text": - prev_text = event.message[idx - 1]["data"]["text"] - curr_text = m["data"]["text"].strip() - - # 处理网址或IP地址的特殊情况,处理两端文本中间为url时的情况 - if (prev_text.endswith(" ") and self._is_url_or_ip(curr_text)) or \ - (idx > 1 and m["data"]["text"].startswith(" ") and self._is_url_or_ip(prev_text.strip())): - # 合并到前一个文本段 - message_str += " " + curr_text - event.message[idx - 1]["data"]["text"] = message_str - a = ComponentTypes[t](**event.message[idx - 1]["data"]) - abm.message[-1] = a - should_append_new = False - # 如果不需要合并,添加为新的消息段 - if should_append_new: - message_str += m["data"]["text"].strip() - a = ComponentTypes[t](**m["data"]) - abm.message.append(a) + for m in m_group: + message_str += m["data"]["text"] + message_str = message_str.strip() + a = ComponentTypes[t](**m["data"]) + abm.message.append(a) elif t == "file": - if m["data"].get("url") and m["data"].get("url").startswith("http"): - # Lagrange - logger.info("guessing lagrange") + for m in m_group: + if m["data"].get("url") and m["data"].get("url").startswith("http"): + # Lagrange + logger.info("guessing lagrange") - file_name = m["data"].get("file_name", "file") - path = os.path.join("data/temp", file_name) - await download_file(m["data"]["url"], path) + file_name = m["data"].get("file_name", "file") + path = os.path.join("data/temp", file_name) + await download_file(m["data"]["url"], path) - m["data"] = {"file": path, "name": file_name} - a = ComponentTypes[t](**m["data"]) # noqa: F405 - abm.message.append(a) - - else: - try: - # Napcat, LLBot - ret = await self.bot.call_action( - action="get_file", - file_id=event.message[0]["data"]["file_id"], - ) - if not ret.get("file", None): - raise ValueError(f"无法解析文件响应: {ret}") - if not os.path.exists(ret["file"]): - raise FileNotFoundError( - f"文件不存在或者权限问题: {ret['file']}。如果您使用 Docker 部署了 AstrBot 或者消息协议端(Napcat等),请先映射路径。如果路径在 /root 目录下,请用 sudo 打开 AstrBot" - ) - - m["data"] = {"file": ret["file"], "name": ret["file_name"]} + m["data"] = {"file": path, "name": file_name} a = ComponentTypes[t](**m["data"]) # noqa: F405 abm.message.append(a) - except ActionFailed as e: - logger.error(f"获取文件失败: {e},此消息段将被忽略。") - except BaseException as e: - logger.error(f"获取文件失败: {e},此消息段将被忽略。") + + else: + try: + # Napcat, LLBot + ret = await self.bot.call_action( + action="get_file", + file_id=event.message[0]["data"]["file_id"], + ) + if not ret.get("file", None): + raise ValueError(f"无法解析文件响应: {ret}") + if not os.path.exists(ret["file"]): + raise FileNotFoundError( + f"文件不存在或者权限问题: {ret['file']}。如果您使用 Docker 部署了 AstrBot 或者消息协议端(Napcat等),请先映射路径。如果路径在 /root 目录下,请用 sudo 打开 AstrBot" + ) + + m["data"] = {"file": ret["file"], "name": ret["file_name"]} + a = ComponentTypes[t](**m["data"]) # noqa: F405 + abm.message.append(a) + except ActionFailed as e: + logger.error(f"获取文件失败: {e},此消息段将被忽略。") + 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}。") + for m in m_group: + 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) + for m in m_group: + a = ComponentTypes[t](**m["data"]) # noqa: F405 + abm.message.append(a) abm.timestamp = int(time.time()) abm.message_str = message_str From 0ae61d586594849371137344972a09cb0ecd1031 Mon Sep 17 00:00:00 2001 From: kkjz <253839323@sohu.com> Date: Sun, 20 Apr 2025 22:11:24 +0800 Subject: [PATCH 09/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=94=9F?= =?UTF-8?q?=E6=88=90text=E7=9A=84Plain=E6=97=B6=E6=96=87=E6=9C=AC=E4=B8=BA?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=90=8E=E7=9A=84=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index b7beff156..5eb1c09d1 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -210,7 +210,7 @@ class AiocqhttpAdapter(Platform): for m in m_group: message_str += m["data"]["text"] message_str = message_str.strip() - a = ComponentTypes[t](**m["data"]) + a = ComponentTypes[t](text = message_str) # noqa: F405 abm.message.append(a) elif t == "file": From 0e1eb3daf69bb240c1b2ebd2f87cbe7a13a21a02 Mon Sep 17 00:00:00 2001 From: kkjz <253839323@sohu.com> Date: Mon, 21 Apr 2025 20:56:18 +0800 Subject: [PATCH 10/29] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8join=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E4=BC=98=E5=8C=96=E7=9B=B8=E9=82=BB=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E6=AE=B5=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sources/aiocqhttp/aiocqhttp_platform_adapter.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 5eb1c09d1..0dd7912e2 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -206,11 +206,9 @@ class AiocqhttpAdapter(Platform): for t, m_group in itertools.groupby(event.message, key=lambda x: x["type"]): a = None if t == "text": - # 合并相邻文本段的情况 - for m in m_group: - message_str += m["data"]["text"] - message_str = message_str.strip() - a = ComponentTypes[t](text = message_str) # noqa: F405 + # 合并相邻文本段 + message_str = "".join(m["data"]["text"] for m in m_group).strip() + a = ComponentTypes[t](text=message_str) # noqa: F405 abm.message.append(a) elif t == "file": From ab227a08d06b78c8464c0ba878df218883ea2a98 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Tue, 22 Apr 2025 11:50:47 +0800 Subject: [PATCH 11/29] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dopenai=20source?= =?UTF-8?q?=E4=B8=ADe=E7=9A=84=E4=BD=9C=E7=94=A8=E5=9F=9F=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/openai_source.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index f4e02b5f5..5399fbc32 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -362,7 +362,7 @@ class ProviderOpenAIOfficial(Provider): available_api_keys = self.api_keys.copy() chosen_key = random.choice(available_api_keys) - e = None + last_exception = None retry_cnt = 0 for retry_cnt in range(max_retries): try: @@ -376,6 +376,7 @@ class ProviderOpenAIOfficial(Provider): payloads["messages"] = new_contexts context_query = new_contexts except Exception as e: + last_exception = e ( success, chosen_key, @@ -398,7 +399,9 @@ class ProviderOpenAIOfficial(Provider): if retry_cnt == max_retries - 1: logger.error(f"API 调用失败,重试 {max_retries} 次仍然失败。") - raise e + if last_exception is None: + raise Exception("未知错误") + raise last_exception return llm_response async def text_chat_stream( @@ -428,7 +431,7 @@ class ProviderOpenAIOfficial(Provider): available_api_keys = self.api_keys.copy() chosen_key = random.choice(available_api_keys) - e = None + last_exception = None retry_cnt = 0 for retry_cnt in range(max_retries): try: @@ -443,6 +446,7 @@ class ProviderOpenAIOfficial(Provider): payloads["messages"] = new_contexts context_query = new_contexts except Exception as e: + last_exception = e ( success, chosen_key, @@ -465,7 +469,9 @@ class ProviderOpenAIOfficial(Provider): if retry_cnt == max_retries - 1: logger.error(f"API 调用失败,重试 {max_retries} 次仍然失败。") - raise e + if last_exception is None: + raise Exception("未知错误") + raise last_exception async def _remove_image_from_context(self, contexts: List): """ @@ -505,7 +511,10 @@ class ProviderOpenAIOfficial(Provider): async def assemble_context(self, text: str, image_urls: List[str] = None) -> dict: """组装成符合 OpenAI 格式的 role 为 user 的消息段""" if image_urls: - user_content = {"role": "user", "content": [{"type": "text", "text": text if text else "[图片]"}]} + user_content = { + "role": "user", + "content": [{"type": "text", "text": text if text else "[图片]"}], + } for image_url in image_urls: if image_url.startswith("http"): image_path = await download_image_by_url(image_url) From 8c19b7d16377f3121770d945d09c311f5e75ec0f Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Tue, 22 Apr 2025 17:52:25 +0800 Subject: [PATCH 12/29] chore: clean code,format --- astrbot/core/provider/func_tool_manager.py | 40 ++++++++++++++++------ 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index d7b0ca4b1..c406dc804 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -3,7 +3,6 @@ import json import textwrap import os import asyncio -import copy import logging from typing import Dict, List, Awaitable, Literal, Any @@ -360,7 +359,7 @@ class FuncCall: self.func_list.append(func_tool) logger.info(f"已连接 MCP 服务 {name}, Tools: {tool_names}") - return True + return except Exception as e: import traceback @@ -369,7 +368,7 @@ class FuncCall: # 发生错误时确保客户端被清理 if name in self.mcp_client_dict: await self._terminate_mcp_client(name) - return False + return async def _terminate_mcp_client(self, name: str) -> None: """关闭并清理MCP客户端""" @@ -441,11 +440,19 @@ class FuncCall: """ # Gemini API 支持的数据类型和格式 - supported_types = {"string", "number", "integer", "boolean", "array", "object", "null"} + supported_types = { + "string", + "number", + "integer", + "boolean", + "array", + "object", + "null", + } supported_formats = { "string": {"enum", "date-time"}, "integer": {"int32", "int64"}, - "number": {"float", "double"} + "number": {"float", "double"}, } def convert_schema(schema: dict) -> dict: @@ -454,15 +461,25 @@ class FuncCall: if "type" in schema and schema["type"] in supported_types: result["type"] = schema["type"] - if ("format" in schema and - schema["format"] in supported_formats.get(result["type"], set())): + if "format" in schema and schema["format"] in supported_formats.get( + result["type"], set() + ): result["format"] = schema["format"] else: # 暂时指定默认为null result["type"] = "null" - support_fields = {"title", "description", "enum", "minimum", "maximum", - "maxItems", "minItems", "nullable", "required"} + support_fields = { + "title", + "description", + "enum", + "minimum", + "maximum", + "maxItems", + "minItems", + "nullable", + "required", + } result.update({k: schema[k] for k in support_fields if k in schema}) if "properties" in schema: @@ -487,9 +504,10 @@ class FuncCall: { "name": f.name, "description": f.description, - **({"parameters": convert_schema(f.parameters)}) + **({"parameters": convert_schema(f.parameters)}), } - for f in self.func_list if f.active + for f in self.func_list + if f.active ] declarations = {} From 45da9837ec4bc54ba23371db366aa3066e5f1dae Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:12:03 +0800 Subject: [PATCH 13/29] docs: Create FUNDING.yml --- .github/FUNDING.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..7ccea82e4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: https://opencollective.com/astrbot +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: ['https://afdian.com/a/astrbot_team'] From ea64afbaa7196a06d0ce4e4368d6dbd830d36687 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:12:40 +0800 Subject: [PATCH 14/29] docs: Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7ccea82e4..ae5829fcc 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -2,7 +2,7 @@ github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username -open_collective: https://opencollective.com/astrbot +open_collective: astrbot ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry From 8d7273924fd4c6980e07fc46fdf671301900a5fb Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Tue, 22 Apr 2025 22:12:03 +0800 Subject: [PATCH 15/29] =?UTF-8?q?fix:=20Gemini=E4=BF=9D=E8=AF=81=E5=81=B6?= =?UTF-8?q?=E6=95=B0=E7=B4=A2=E5=BC=95=E4=B8=BA=E7=94=A8=E6=88=B7=E6=B6=88?= =?UTF-8?q?=E6=81=AF=EF=BC=8C=E5=A5=87=E6=95=B0=E7=B4=A2=E5=BC=95=E4=B8=BA?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/gemini_source.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index ef2305069..038d57751 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -260,6 +260,20 @@ class ProviderGoogleGenAI(Provider): ) ) + # 保证偶数索引为用户消息,奇数索引为模型消息 + content_num = len(gemini_contents) + for i in range(content_num): + expected_type = types.UserContent if i % 2 == 0 else types.ModelContent + if not isinstance(gemini_contents[i], expected_type): + for j in range(i + 1, content_num): + if isinstance(gemini_contents[j], expected_type): + logger.debug(f"交换索引 {i} 与 {j}") + gemini_contents[i], gemini_contents[j] = ( + gemini_contents[j], + gemini_contents[i], + ) + break + return gemini_contents @staticmethod From f07c54d47caf0b4f2caf4b256cc197dd1a79c390 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 00:48:25 +0800 Subject: [PATCH 16/29] =?UTF-8?q?style:=20=E5=87=8F=E5=B0=91=E4=B8=80?= =?UTF-8?q?=E5=B1=82=20intent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com> --- .../core/provider/sources/gemini_source.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 038d57751..8600d3e9f 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -264,15 +264,16 @@ class ProviderGoogleGenAI(Provider): content_num = len(gemini_contents) for i in range(content_num): expected_type = types.UserContent if i % 2 == 0 else types.ModelContent - if not isinstance(gemini_contents[i], expected_type): - for j in range(i + 1, content_num): - if isinstance(gemini_contents[j], expected_type): - logger.debug(f"交换索引 {i} 与 {j}") - gemini_contents[i], gemini_contents[j] = ( - gemini_contents[j], - gemini_contents[i], - ) - break + if isinstance(gemini_contents[i], expected_type): + continue + for j in range(i + 1, content_num): + if isinstance(gemini_contents[j], expected_type): + logger.debug(f"交换索引 {i} 与 {j}") + gemini_contents[i], gemini_contents[j] = ( + gemini_contents[j], + gemini_contents[i], + ) + break return gemini_contents From c8f567347bafe91fb2e882f28a9b1add2a980680 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 11:52:22 +0800 Subject: [PATCH 17/29] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E9=87=8D?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E9=80=BB=E8=BE=91=E4=B8=BA=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E8=BF=9E=E7=BB=AD=E7=9B=B8=E5=90=8C=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/gemini_source.py | 97 +++++++++---------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index 8600d3e9f..d8d4c789c 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -182,11 +182,11 @@ class ProviderGoogleGenAI(Provider): def _prepare_conversation(self, payloads: Dict) -> List[types.Content]: """准备 Gemini SDK 的 Content 列表""" - def create_text_part(text: str) -> types.UserContent: + def create_text_part(text: str) -> types.Part: content_a = text if text else " " if not text: logger.warning("文本内容为空,已添加空格占位") - return types.UserContent(parts=[types.Part.from_text(text=content_a)]) + return types.Part.from_text(text=content_a) def process_image_url(image_url_dict: dict) -> types.Part: url = image_url_dict["url"] @@ -205,75 +205,66 @@ class ProviderGoogleGenAI(Provider): role, content = message["role"], message.get("content") if role == "user": - if isinstance(content, str): - gemini_contents.append(create_text_part(content)) - elif isinstance(content, list): + if isinstance(content, list): parts = [ types.Part.from_text(text=item["text"] or " ") if item["type"] == "text" else process_image_url(item["image_url"]) for item in content ] + else: + parts = [create_text_part(content)] + + if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): + gemini_contents[-1].parts.extend(parts) + else: gemini_contents.append(types.UserContent(parts=parts)) elif role == "assistant": if content: - gemini_contents.append( - types.ModelContent(parts=[types.Part.from_text(text=content)]) - ) - elif "tool_calls" in message and not native_tool_enabled: - gemini_contents.extend( - [ - types.ModelContent( - parts=[ - types.Part.from_function_call( - name=tool["function"]["name"], - args=json.loads(tool["function"]["arguments"]), - ) - ] - ) - for tool in message["tool_calls"] - ] - ) + parts = [types.Part.from_text(text=content)] + if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.ModelContent(parts=parts)) + elif not native_tool_enabled and "tool_calls" in message : + parts = [ + types.Part.from_function_call( + name=tool["function"]["name"], + args=json.loads(tool["function"]["arguments"]), + ) + for tool in message["tool_calls"] + ] + if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.ModelContent(parts=parts)) else: logger.warning("assistant 角色的消息内容为空,已添加空格占位") - if native_tool_enabled: + if native_tool_enabled and "tool_calls" in message: logger.warning( "检测到启用Gemini原生工具,且上下文中存在函数调用,建议使用 /reset 重置上下文" ) - gemini_contents.append( - types.ModelContent(parts=[types.Part.from_text(text=" ")]) - ) + parts = [types.Part.from_text(text=" ")] + if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.ModelContent(parts=parts)) elif role == "tool" and not native_tool_enabled: - gemini_contents.append( - types.UserContent( - parts=[ - types.Part.from_function_response( - name=message["tool_call_id"], - response={ - "name": message["tool_call_id"], - "content": message["content"], - }, - ) - ] + parts = [ + types.Part.from_function_response( + name=message["tool_call_id"], + response={ + "name": message["tool_call_id"], + "content": message["content"], + }, ) - ) - - # 保证偶数索引为用户消息,奇数索引为模型消息 - content_num = len(gemini_contents) - for i in range(content_num): - expected_type = types.UserContent if i % 2 == 0 else types.ModelContent - if isinstance(gemini_contents[i], expected_type): - continue - for j in range(i + 1, content_num): - if isinstance(gemini_contents[j], expected_type): - logger.debug(f"交换索引 {i} 与 {j}") - gemini_contents[i], gemini_contents[j] = ( - gemini_contents[j], - gemini_contents[i], - ) - break + ] + if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): + gemini_contents[-1].parts.extend(parts) + else: + gemini_contents.append(types.UserContent(parts=parts)) return gemini_contents From 97a6a1fdc29a1228dc95479ba7800e8d7d2ee30e Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 12:20:18 +0800 Subject: [PATCH 18/29] =?UTF-8?q?feat:=20=E4=BF=9D=E8=AF=81=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E6=9D=A1=E6=B6=88=E6=81=AF=E4=B8=8D=E4=B8=BAmodel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/sources/gemini_source.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index d8d4c789c..fe9cc4346 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -266,6 +266,9 @@ class ProviderGoogleGenAI(Provider): else: gemini_contents.append(types.UserContent(parts=parts)) + if gemini_contents and isinstance(gemini_contents[0], types.ModelContent): + gemini_contents.pop() + return gemini_contents @staticmethod From a23d5be056a96da724fdcbbf4b0c059400677fd2 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 12:49:27 +0800 Subject: [PATCH 19/29] =?UTF-8?q?refactor:=20=E5=87=8F=E5=B0=91=E5=B5=8C?= =?UTF-8?q?=E5=A5=97=E6=9D=A1=E4=BB=B6=E5=92=8C=E9=87=8D=E5=A4=8D=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/gemini_source.py | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py index fe9cc4346..a175a3d68 100644 --- a/astrbot/core/provider/sources/gemini_source.py +++ b/astrbot/core/provider/sources/gemini_source.py @@ -194,6 +194,12 @@ class ProviderGoogleGenAI(Provider): image_bytes = base64.b64decode(url.split(",", 1)[1]) return types.Part.from_bytes(data=image_bytes, mime_type=mime_type) + def append_or_extend(contents: list[types.Content], part: list[types.Part], content_cls: type[types.Content]) -> None: + if contents and isinstance(contents[-1], content_cls): + contents[-1].parts.extend(part) + else: + contents.append(content_cls(parts=part)) + gemini_contents: List[types.Content] = [] native_tool_enabled = any( [ @@ -214,19 +220,12 @@ class ProviderGoogleGenAI(Provider): ] else: parts = [create_text_part(content)] - - if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.UserContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.UserContent) elif role == "assistant": if content: parts = [types.Part.from_text(text=content)] - if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.ModelContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.ModelContent) elif not native_tool_enabled and "tool_calls" in message : parts = [ types.Part.from_function_call( @@ -235,10 +234,7 @@ class ProviderGoogleGenAI(Provider): ) for tool in message["tool_calls"] ] - if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.ModelContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.ModelContent) else: logger.warning("assistant 角色的消息内容为空,已添加空格占位") if native_tool_enabled and "tool_calls" in message: @@ -246,10 +242,7 @@ class ProviderGoogleGenAI(Provider): "检测到启用Gemini原生工具,且上下文中存在函数调用,建议使用 /reset 重置上下文" ) parts = [types.Part.from_text(text=" ")] - if gemini_contents and isinstance(gemini_contents[-1], types.ModelContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.ModelContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.ModelContent) elif role == "tool" and not native_tool_enabled: parts = [ @@ -261,10 +254,7 @@ class ProviderGoogleGenAI(Provider): }, ) ] - if gemini_contents and isinstance(gemini_contents[-1], types.UserContent): - gemini_contents[-1].parts.extend(parts) - else: - gemini_contents.append(types.UserContent(parts=parts)) + append_or_extend(gemini_contents, parts, types.UserContent) if gemini_contents and isinstance(gemini_contents[0], types.ModelContent): gemini_contents.pop() From 6ee9010645f6b488946e0f013747d70d2a6d2493 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 15:53:18 +0800 Subject: [PATCH 20/29] =?UTF-8?q?feat:=20=E5=85=81=E8=AE=B8=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=87=AA=E5=AE=9A=E4=B9=89telegram=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=99=A8=E6=8C=87=E4=BB=A4=E6=B3=A8=E5=86=8C=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E5=91=BD=E4=BB=A4=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 18 ++++++++++ .../platform/sources/telegram/tg_adapter.py | 36 ++++++++++++------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index b2f4d80fd..cffcef3b3 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -186,6 +186,9 @@ CONFIG_METADATA_2 = { "start_message": "Hello, I'm AstrBot!", "telegram_api_base_url": "https://api.telegram.org/bot", "telegram_file_base_url": "https://api.telegram.org/file/bot", + "telegram_command_register": True, + "telegram_command_auto_refresh": True, + "telegram_command_register_interval": 300 }, }, "items": { @@ -194,6 +197,21 @@ CONFIG_METADATA_2 = { "type": "string", "hint": "如果你的网络环境为中国大陆,请在 `其他配置` 处设置代理或更改 api_base。", }, + "telegram_command_register": { + "description": "Telegram 命令注册", + "type": "bool", + "hint": "启用后,AstrBot 将会自动注册 Telegram 命令。", + }, + "telegram_command_auto_refresh": { + "description": "Telegram 命令自动刷新", + "type": "bool", + "hint": "启用后,AstrBot 将会在运行时自动刷新 Telegram 命令。(单独设置此项无效)", + }, + "telegram_command_register_interval": { + "description": "Telegram 命令自动刷新间隔", + "type": "int", + "hint": "Telegram 命令自动刷新间隔,单位为秒。", + }, "id": { "description": "ID", "type": "string", diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index ebf662127..d227f1f68 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -58,6 +58,10 @@ class TelegramPlatformAdapter(Platform): self.base_url = base_url + self.enable_command_register = self.config.get("telegram_command_register", True) + self.enable_command_refresh = self.config.get("telegram_command_auto_refresh", True) + self.last_command_hash = None + self.application = ( ApplicationBuilder() .token(self.config["telegram_token"]) @@ -95,17 +99,19 @@ class TelegramPlatformAdapter(Platform): async def run(self): await self.application.initialize() await self.application.start() - await self.register_commands() - # TODO 使用更优雅的方式重新注册命令 - self.scheduler.add_job( - self.register_commands, - "interval", - minutes=5, - id="telegram_command_register", - misfire_grace_time=60, - ) - self.scheduler.start() + if self.enable_command_register: + await self.register_commands() + + if self.enable_command_refresh and self.enable_command_register: + self.scheduler.add_job( + self.register_commands, + "interval", + seconds=self.config.get("telegram_command_register_interval", 300), + id="telegram_command_register", + misfire_grace_time=60, + ) + self.scheduler.start() queue = self.application.updater.start_polling() logger.info("Telegram Platform Adapter is running.") @@ -114,10 +120,14 @@ class TelegramPlatformAdapter(Platform): async def register_commands(self): """收集所有注册的指令并注册到 Telegram""" try: - await self.client.delete_my_commands() commands = self.collect_commands() if commands: + current_hash = hash(tuple((cmd.command, cmd.description) for cmd in commands)) + if current_hash == self.last_command_hash: + return + self.last_command_hash = current_hash + await self.client.delete_my_commands() await self.client.set_my_commands(commands) except Exception as e: @@ -342,7 +352,9 @@ class TelegramPlatformAdapter(Platform): self.scheduler.shutdown() await self.application.stop() - await self.client.delete_my_commands() + + if self.enable_command_register: + await self.client.delete_my_commands() # 保险起见先判断是否存在updater对象 if self.application.updater is not None: From be7c3fd00e387fea0e74dcd9898b8d31f4a72f7f Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:31:59 +0800 Subject: [PATCH 21/29] docs: update PR template --- .github/PULL_REQUEST_TEMPLATE.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6b226d48a..98e6a3ada 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@ -修复了 #XYZ +解决了 #XYZ ### Motivation @@ -10,5 +10,10 @@ ### Check -- [ ] 我的 Commit Message 符合良好的[规范](https://www.conventionalcommits.org/en/v1.0.0/#summary) -- [ ] 我新增/修复/优化的功能经过良好的测试 + + + +- [ ] 😊 我的 Commit Message 符合良好的[规范](https://www.conventionalcommits.org/en/v1.0.0/#summary) +- [ ] 👀 我的更改经过良好的测试 +- [ ] 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 `requirements.txt` 和 `pyproject.toml` 文件相应位置。 +- [ ] 😮 我的更改没有引入恶意代码 From e2c26c292ddd27df72d96df40d5de2df60e0daf0 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 23 Apr 2025 19:55:15 +0800 Subject: [PATCH 22/29] =?UTF-8?q?feat:=20=E5=A4=84=E7=90=86MCP=E8=BF=94?= =?UTF-8?q?=E5=9B=9EImageContent=E3=80=81EmbeddedResource=E7=9A=84?= =?UTF-8?q?=E6=83=85=E5=86=B5=EF=BC=8C=E6=8F=90=E4=BE=9B=E7=AE=80=E5=8D=95?= =?UTF-8?q?fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process_stage/method/llm_request.py | 93 ++++++++++++++++--- 1 file changed, 80 insertions(+), 13 deletions(-) diff --git a/astrbot/core/pipeline/process_stage/method/llm_request.py b/astrbot/core/pipeline/process_stage/method/llm_request.py index fd70275d8..28745f2c5 100644 --- a/astrbot/core/pipeline/process_stage/method/llm_request.py +++ b/astrbot/core/pipeline/process_stage/method/llm_request.py @@ -26,6 +26,13 @@ from astrbot.core.provider.entities import ( ) from astrbot.core.star.star_handler import star_handlers_registry, EventType from astrbot.core.star.star import star_map +from mcp.types import ( + TextContent, + ImageContent, + EmbeddedResource, + TextResourceContents, + BlobResourceContents, +) class LLMRequestSubStage(Stage): @@ -66,9 +73,9 @@ class LLMRequestSubStage(Stage): if event.get_extra("provider_request"): req = event.get_extra("provider_request") - assert isinstance( - req, ProviderRequest - ), "provider_request 必须是 ProviderRequest 类型。" + assert isinstance(req, ProviderRequest), ( + "provider_request 必须是 ProviderRequest 类型。" + ) if req.conversation: all_contexts = json.loads(req.conversation.history) @@ -149,7 +156,14 @@ class LLMRequestSubStage(Stage): -(self.max_context_length - self.dequeue_context_length + 1) * 2 : ] # 找到第一个role 为 user 的索引,确保上下文格式正确 - index = next((i for i, item in enumerate(req.contexts) if item.get("role") == "user"), None) + index = next( + ( + i + for i, item in enumerate(req.contexts) + if item.get("role") == "user" + ), + None, + ) if index is not None and index > 0: req.contexts = req.contexts[index:] @@ -265,6 +279,12 @@ class LLMRequestSubStage(Stage): event.set_extra("tool_call_result", None) yield + # 暂时直接发出去 + if img_b64 := event.get_extra("tool_call_img_respond"): + await event.send(MessageChain(chain=[Image.fromBase64(img_b64)])) + event.set_extra("tool_call_img_respond", None) + yield + async def _handle_llm_response( self, event: AstrMessageEvent, @@ -375,21 +395,68 @@ class LLMRequestSubStage(Stage): client = req.func_tool.mcp_client_dict[func_tool.mcp_server_name] res = await client.session.call_tool(func_tool.name, func_tool_args) if res: - # TODO content的类型可能包括list[TextContent | ImageContent | EmbeddedResource],这里只处理了TextContent。 - tool_call_result.append( - ToolCallMessageSegment( - role="tool", - tool_call_id=func_tool_id, - content=res.content[0].text, + # TODO 仅对ImageContent | EmbeddedResource进行了简单的Fallback + if isinstance(res.content[0], TextContent): + tool_call_result.append( + ToolCallMessageSegment( + role="tool", + tool_call_id=func_tool_id, + content=res.content[0].text, + ) ) - ) + elif isinstance(res.content[0], ImageContent): + tool_call_result.append( + ToolCallMessageSegment( + role="tool", + tool_call_id=func_tool_id, + content="返回了图片(已直接发送给用户)", + ) + ) + event.set_extra( + "tool_call_img_respond", + res.content[0].data, + ) + elif isinstance(res.content[0], EmbeddedResource): + resource = res.content[0].resource + if isinstance(resource, TextResourceContents): + tool_call_result.append( + ToolCallMessageSegment( + role="tool", + tool_call_id=func_tool_id, + content=resource.text, + ) + ) + elif ( + isinstance(resource, BlobResourceContents) + and resource.mimeType + and resource.mimeType.startswith("image/") + ): + tool_call_result.append( + ToolCallMessageSegment( + role="tool", + tool_call_id=func_tool_id, + content="返回了图片(已直接发送给用户)", + ) + ) + event.set_extra( + "tool_call_img_respond", + res.content[0].data, + ) + else: + tool_call_result.append( + ToolCallMessageSegment( + role="tool", + tool_call_id=func_tool_id, + content="返回的数据类型不受支持", + ) + ) else: # 获取处理器,过滤掉平台不兼容的处理器 platform_id = event.get_platform_id() star_md = star_map.get(func_tool.handler_module_path) if ( - star_md and - platform_id in star_md.supported_platforms + star_md + and platform_id in star_md.supported_platforms and not star_md.supported_platforms[platform_id] ): logger.debug( From 45cb1432022ad78a3625d6cd5873a2a057a38022 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Fri, 25 Apr 2025 00:46:40 +0800 Subject: [PATCH 23/29] =?UTF-8?q?perf:=20=E5=AE=9E=E7=8E=B0=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=BE=AE=E4=BF=A1=E7=BE=A4=E8=81=8A=E4=B8=8B=E5=AF=B9?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E4=BA=BA=E7=9A=84=20At?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/astrbot_message.py | 2 -- .../core/platform/sources/gewechat/client.py | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/astrbot/core/platform/astrbot_message.py b/astrbot/core/platform/astrbot_message.py index 54ed51e80..e7bd4bd9c 100644 --- a/astrbot/core/platform/astrbot_message.py +++ b/astrbot/core/platform/astrbot_message.py @@ -62,8 +62,6 @@ class AstrBotMessage: raw_message: object timestamp: int # 消息时间戳 - be_at_wxid: List[str] # gewechat用的群组内at信息,用于机器人获取被at的wxid - def __init__(self) -> None: self.timestamp = int(time.time()) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index fb1f13663..f23e1b309 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -143,22 +143,25 @@ class SimpleGewechatClient: content = d["Content"]["string"] # 消息内容 at_me = False + at_wxids = [] if "@chatroom" in from_user_name: abm.type = MessageType.GROUP_MESSAGE _t = content.split(":\n") user_id = _t[0] content = _t[1] + # at + msg_source = d["MsgSource"] if "\u2005" in content: # at # content = content.split('\u2005')[1] content = re.sub(r"@[^\u2005]*\u2005", "", content) - abm.be_at_wxid = re.findall(r')',d["MsgSource"]) - - abm.group_id = from_user_name - # at - msg_source = d["MsgSource"] + at_wxids = re.findall( + r")", + msg_source, + ) + + abm.group_id = from_user_name - if ( f"" in msg_source or f"" in msg_source @@ -176,8 +179,6 @@ class SimpleGewechatClient: return None abm.message = [] - if at_me: - abm.message.insert(0, At(qq=abm.self_id)) # 解析用户真实名字 user_real_name = "unknown" @@ -203,6 +204,13 @@ class SimpleGewechatClient: else: user_real_name = d.get("PushContent", "unknown : ").split(" : ")[0] + if at_me: + abm.message.insert(0, At(qq=abm.self_id, name=self.nickname)) + for wxid in at_wxids: + # 群聊里 At 其他人的列表 + _username = self.userrealnames.get(abm.group_id, {}).get(wxid, wxid) + abm.message.append(At(qq=wxid, name=_username)) + abm.sender = MessageMember(user_id, user_real_name) abm.raw_message = d abm.message_str = "" From 7abb4087b352a868e8ce511441eedfc24c078b03 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Sat, 26 Apr 2025 19:50:30 +0800 Subject: [PATCH 24/29] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7c7d12825..6efd84637 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,8 @@ pre-commit install ## ✨ Demo +👉 点击展开多张 Demo 截图 👈 + @@ -173,6 +175,9 @@ _✨ WebUI ✨_ + + + ## ❤️ Special Thanks 特别感谢所有 Contributors 和插件开发者对 AstrBot 的贡献 ❤️ From a01617b45cde735dee2914be00ca10fdb090ea14 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 26 Apr 2025 21:00:25 +0800 Subject: [PATCH 25/29] =?UTF-8?q?fix:=20OneBot=20v11=20request=20=E7=B1=BB?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=20=E8=A1=A5=E5=85=A8=20session=5Fid=20?= =?UTF-8?q?=E7=9A=84=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sources/aiocqhttp/aiocqhttp_platform_adapter.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 0dd7912e2..fb152484e 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -120,6 +120,12 @@ class AiocqhttpAdapter(Platform): abm.type = MessageType.FRIEND_MESSAGE if self.unique_session and abm.type == MessageType.GROUP_MESSAGE: abm.session_id = str(abm.sender.user_id) + "_" + str(event.group_id) + else: + abm.session_id = ( + str(event.group_id) + if abm.type == MessageType.GROUP_MESSAGE + else abm.sender.user_id + ) abm.message_str = "" abm.message = [] abm.timestamp = int(time.time()) From cf8f0603cac3b9c118de4e24fcb9e8d2ddc74b6e Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sat, 26 Apr 2025 22:57:23 +0800 Subject: [PATCH 26/29] =?UTF-8?q?=F0=9F=90=9B=20fix:=20gewechat=20?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=E5=BC=BA=E5=88=B6=E5=BF=BD=E7=95=A5=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E6=B6=88=E6=81=AF=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes: #1388 --- astrbot/core/platform/sources/gewechat/client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index f23e1b309..360dddff5 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -174,9 +174,10 @@ class SimpleGewechatClient: user_id = from_user_name # 检查消息是否由自己发送,若是则忽略 - if user_id == abm.self_id: - logger.info("忽略自己发送的消息") - return None + # 已经有可配置项专门配置是否需要响应自己的消息,因此这里注释掉。 + # if user_id == abm.self_id: + # logger.info("忽略自己发送的消息") + # return None abm.message = [] From fdf55221e6c25fbf5127841c0f863fcfb222349c Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Mon, 28 Apr 2025 22:14:51 +0800 Subject: [PATCH 27/29] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0aiocqhttp?= =?UTF-8?q?=E5=AF=B9Token=E8=AE=BE=E7=BD=AE=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 8 +++++++- .../sources/aiocqhttp/aiocqhttp_platform_adapter.py | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index cffcef3b3..664a11307 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -140,6 +140,7 @@ CONFIG_METADATA_2 = { "enable": False, "ws_reverse_host": "0.0.0.0", "ws_reverse_port": 6199, + "ws_reverse_token": "", }, "gewechat(微信)": { "id": "gwchat", @@ -188,7 +189,7 @@ CONFIG_METADATA_2 = { "telegram_file_base_url": "https://api.telegram.org/file/bot", "telegram_command_register": True, "telegram_command_auto_refresh": True, - "telegram_command_register_interval": 300 + "telegram_command_register_interval": 300, }, }, "items": { @@ -258,6 +259,11 @@ CONFIG_METADATA_2 = { "type": "int", "hint": "aiocqhttp 适配器的反向 Websocket 端口。", }, + "ws_reverse_token": { + "description": "反向 Websocket Token", + "type": "string", + "hint": "aiocqhttp 适配器的反向 Websocket Token。未设置则不启用 Token 验证。", + }, "lark_bot_name": { "description": "飞书机器人的名字", "type": "string", diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index fb152484e..9cf6b5bab 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -46,7 +46,10 @@ class AiocqhttpAdapter(Platform): ) self.bot = CQHttp( - use_ws_reverse=True, import_name="aiocqhttp", api_timeout_sec=180 + use_ws_reverse=True, + import_name="aiocqhttp", + api_timeout_sec=180, + access_token=platform_config["ws_reverse_token"], ) @self.bot.on_request() From 82026370ec220a80d598bbd6de8125280beebde3 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Tue, 29 Apr 2025 11:16:45 +0800 Subject: [PATCH 28/29] =?UTF-8?q?=E2=9C=A8feat:=20=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=9F=BA=E4=BA=8E=20Star=20=E5=92=8C=20updat?= =?UTF-8?q?ed=5Fat=20=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/src/stores/common.js | 2 ++ dashboard/src/views/ExtensionMarketplace.vue | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/dashboard/src/stores/common.js b/dashboard/src/stores/common.js index 49f55f5db..410fcc7ce 100644 --- a/dashboard/src/stores/common.js +++ b/dashboard/src/stores/common.js @@ -145,6 +145,8 @@ export const useCommonStore = defineStore({ "tags": res.data.data[key]?.tags ? res.data.data[key].tags : [], "logo": res.data.data[key]?.logo ? res.data.data[key].logo : "", "pinned": res.data.data[key]?.pinned ? res.data.data[key].pinned : false, + "stars": res.data.data[key]?.stars ? res.data.data[key].stars : 0, + "updated_at": res.data.data[key]?.updated_at ? res.data.data[key].updated_at : "", }) } this.pluginMarketData = data; diff --git a/dashboard/src/views/ExtensionMarketplace.vue b/dashboard/src/views/ExtensionMarketplace.vue index 2e270d473..64914deed 100644 --- a/dashboard/src/views/ExtensionMarketplace.vue +++ b/dashboard/src/views/ExtensionMarketplace.vue @@ -99,15 +99,13 @@ import 'highlight.js/styles/github.css'; - - - - + {{ item.stars }} + + + {{ new Date(item.updated_at).toLocaleString() }} + + 无 {{ tag @@ -283,6 +281,7 @@ export default { { title: '描述', key: 'desc', maxWidth: '250px' }, { title: '作者', key: 'author', maxWidth: '70px' }, { title: 'Star数', key: 'stars', maxWidth: '100px' }, + { title: '最近更新', key: 'updated_at', maxWidth: '100px' }, { title: '标签', key: 'tags', maxWidth: '100px' }, { title: '操作', key: 'actions', sortable: false } ], From 4d5332fe25e4e27aa7acf86f904d5bf006c2e2dd Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 30 Apr 2025 22:39:54 +0800 Subject: [PATCH 29/29] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=E6=97=A7?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E4=B8=8D=E5=AD=98=E5=9C=A8ws=5Freverse=5Ftok?= =?UTF-8?q?en=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 9cf6b5bab..a1822c5e9 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -49,7 +49,9 @@ class AiocqhttpAdapter(Platform): use_ws_reverse=True, import_name="aiocqhttp", api_timeout_sec=180, - access_token=platform_config["ws_reverse_token"], + access_token=platform_config.get( + "ws_reverse_token" + ), # 以防旧版本配置不存在 ) @self.bot.on_request()