From fe198f31cfbc56628dbfa211e04b5250dbdd25c5 Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Tue, 24 Feb 2026 00:58:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(core):=20=E4=BC=98=E5=8C=96=20File=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E5=B9=B6?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=20OneBot=20=E9=A9=B1=E5=8A=A8=E5=B1=82?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原因 (Necessity): 1. 内核一致性:AstrBot 内核的 Record 和 Video 组件均具备识别 `file:///` 协议头的逻辑,但 File 组件此前缺失此功能,导致行为不统一。 2. OneBot 协议合规:OneBot 11 标准要求本地文件路径必须使用 `file:///` 协议头。此前驱动层未对裸路径进行自动转换,导致发送本地文件时常触发 retcode 1200 (识别URL失败) 错误。 3. 容器环境适配:在 Docker 等路径隔离环境下,裸路径更容易因驱动或协议端的解析歧义而失效。 更改 (Changes): - [astrbot/core/message/components.py]: - 在 File.get_file() 中增加对 `file:///` 前缀的识别与剥离逻辑,使其与 Record/Video 组件行为对齐。 - [astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py]: - 在发送文件前增加自动修正逻辑:若路径为绝对路径且未包含协议头,驱动层将自动补全 `file:///` 前缀。 - 对 http、base64 及已有协议头,确保不干扰原有的正常传输逻辑。 影响 (Impact): - 以完全兼容的方式增强了文件发送的鲁棒性。 - 解决了插件在发送日志等本地生成的压缩包时,因路径格式不规范导致的发送失败问题。 --- astrbot/core/message/components.py | 13 ++++++++++--- .../sources/aiocqhttp/aiocqhttp_message_event.py | 11 +++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index a9bb091224..18a5298996 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -720,13 +720,20 @@ async def get_file(self, allow_return_url: bool = False) -> str: if allow_return_url and self.url: return self.url - if self.file_ and os.path.exists(self.file_): - return os.path.abspath(self.file_) + if self.file_: + path = self.file_ + if path.startswith("file:///"): + path = path[8:] + if os.path.exists(path): + return os.path.abspath(path) if self.url: await self._download_file() if self.file_: - return os.path.abspath(self.file_) + path = self.file_ + if path.startswith("file:///"): + path = path[8:] + return os.path.abspath(path) return "" diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py index 99ea727315..13cd9df786 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py @@ -1,4 +1,5 @@ import asyncio +import os import re from collections.abc import AsyncGenerator @@ -45,6 +46,16 @@ async def _from_segment_to_dict(segment: BaseMessageComponent) -> dict: if isinstance(segment, File): # For File segments, we need to handle the file differently d = await segment.to_dict() + file_val = d.get("data", {}).get("file", "") + if ( + file_val + and os.path.isabs(file_val) + and not file_val.startswith(("http", "base64", "file://")) + ): + if file_val.startswith("/"): + d["data"]["file"] = f"file://{file_val}" + else: + d["data"]["file"] = f"file:///{file_val}" return d if isinstance(segment, Video): d = await segment.to_dict() From a5f119bb00c02ff2a1044bc0bb3d26a30b339044 Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Tue, 24 Feb 2026 01:10:05 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor(core):=20=E6=A0=B9=E6=8D=AE=20cr?= =?UTF-8?q?=20=E5=BB=BA=E8=AE=AE=EF=BC=8C=E8=A7=84=E8=8C=83=E5=8C=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6=20URI=20=E7=94=9F=E6=88=90=E4=B8=8E=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96=E8=B7=A8?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原因 (Necessity): 1. 修复原生路径与 URI 转换在 Windows 下的不对称问题。 2. 规范化 file: 协议头处理,确保符合 RFC 标准并能在 Linux/Windows 间稳健切换。 3. 增强协议判定准确度,防止对普通绝对路径的误处理。 更改 (Changes): - [astrbot/core/platform/sources/aiocqhttp]: - 弃用手动拼接,改用 `pathlib.Path.as_uri()` 生成标准 URI。 - 将协议检测逻辑从前缀匹配优化为包含性检测 ("://")。 - [astrbot/core/message/components]: - 重构 `File.get_file` 解析逻辑,支持对称处理 2/3 斜杠格式。 - 针对 Windows 环境增加了对 `file:///C:/` 格式的自动修正,避免 `os.path` 识别失效。 - [data/plugins/astrbot_plugin_logplus]: - 在直接 API 调用中同步应用 URI 规范化处理。 影响 (Impact): - 解决 Docker 环境中因路径不规范导致的 "识别URL失败" 报错。 - 提升了本体框架在 Windows 系统下的文件操作鲁棒性。 --- astrbot/core/message/components.py | 26 ++++++++++++++++--- .../aiocqhttp/aiocqhttp_message_event.py | 22 +++++++++------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index 18a5298996..15265c38d1 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -722,8 +722,19 @@ async def get_file(self, allow_return_url: bool = False) -> str: if self.file_: path = self.file_ - if path.startswith("file:///"): - path = path[8:] + if path.startswith("file://"): + # 处理 file:// (2 slashes) 或 file:/// (3 slashes) + # pathlib.as_uri() 通常生成 file:/// + path = path[7:] + # 兼容 Windows: file:///C:/path -> /C:/path -> C:/path + if ( + os.name == "nt" + and len(path) > 2 + and path[0] == "/" + and path[2] == ":" + ): + path = path[1:] + if os.path.exists(path): return os.path.abspath(path) @@ -731,8 +742,15 @@ async def get_file(self, allow_return_url: bool = False) -> str: await self._download_file() if self.file_: path = self.file_ - if path.startswith("file:///"): - path = path[8:] + if path.startswith("file://"): + path = path[7:] + if ( + os.name == "nt" + and len(path) > 2 + and path[0] == "/" + and path[2] == ":" + ): + path = path[1:] return os.path.abspath(path) return "" diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py index 13cd9df786..7e42a0fd86 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py @@ -1,5 +1,4 @@ import asyncio -import os import re from collections.abc import AsyncGenerator @@ -47,15 +46,18 @@ async def _from_segment_to_dict(segment: BaseMessageComponent) -> dict: # For File segments, we need to handle the file differently d = await segment.to_dict() file_val = d.get("data", {}).get("file", "") - if ( - file_val - and os.path.isabs(file_val) - and not file_val.startswith(("http", "base64", "file://")) - ): - if file_val.startswith("/"): - d["data"]["file"] = f"file://{file_val}" - else: - d["data"]["file"] = f"file:///{file_val}" + if file_val: + import pathlib + + try: + # 使用 pathlib 处理路径,能更好地处理 Windows/Linux 差异 + path_obj = pathlib.Path(file_val) + # 如果是绝对路径且不包含协议头 (://),则转换为标准的 file: URI + if path_obj.is_absolute() and "://" not in file_val: + d["data"]["file"] = path_obj.as_uri() + except Exception: + # 如果不是合法路径(例如已经是特定的特殊字符串),则跳过转换 + pass return d if isinstance(segment, Video): d = await segment.to_dict()