Skip to content

Comments

fix(core): 优化 File 组件处理逻辑并增强 OneBot 驱动层路径兼容性#5391

Merged
Soulter merged 2 commits intoAstrBotDevs:masterfrom
SXP-Simon:fix/core-file-handle
Feb 24, 2026
Merged

fix(core): 优化 File 组件处理逻辑并增强 OneBot 驱动层路径兼容性#5391
Soulter merged 2 commits intoAstrBotDevs:masterfrom
SXP-Simon:fix/core-file-handle

Conversation

@SXP-Simon
Copy link
Contributor

@SXP-Simon SXP-Simon commented Feb 23, 2026

原因 (Necessity):

  1. 内核一致性:AstrBot 内核的 Record 和 Video 组件均具备识别 file:/// 协议头的逻辑,但 File 组件此前缺失此功能,导致行为不统一。
  2. OneBot 协议合规:OneBot 11 标准要求本地文件路径必须使用 file:/// 协议头。此前驱动层未对裸路径进行自动转换,导致发送本地文件时常触发 retcode 1200 (识别URL失败) 错误。
  3. 容器环境适配:在 Docker 等路径隔离环境下,裸路径更容易因驱动或协议端的解析歧义而失效。

Modifications / 改动点

更改 (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):

  • 以完全兼容的方式增强了文件发送的鲁棒性。
  • 解决了插件在发送日志等本地生成的压缩包时,因路径格式不规范导致的发送失败问题。可参考插件 Issue
  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

在核心 File 组件和 OneBot(aiocqhttp)驱动之间统一文件路径处理方式,以提升本地文件发送的可靠性和协议兼容性。

Bug 修复:

  • 修复 File 组件在解析带有前缀 file:/// URI scheme 的本地文件时无法正确定位到磁盘路径的问题。
  • 修复 OneBot 驱动在发送使用裸绝对路径的本地文件时会失败的问题,现在会将这些路径规范化为协议要求的 file:// URI。

功能增强:

  • File.get_file 中标准化文件路径规范化逻辑,使其在处理 file:/// URI 时的行为与其他媒体组件保持一致。
Original summary in English

Summary by Sourcery

Align file path handling across core File components and the OneBot (aiocqhttp) driver to improve local file sending robustness and protocol compatibility.

Bug Fixes:

  • Fix File component resolution of local files that include a leading file:/// URI scheme so they are correctly located on disk.
  • Fix OneBot driver failures when sending local files with bare absolute paths by normalizing them to file:// URIs as required by the protocol.

Enhancements:

  • Standardize file path normalization in File.get_file to behave consistently with other media components when dealing with file:/// URIs.

原因 (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):
- 以完全兼容的方式增强了文件发送的鲁棒性。
- 解决了插件在发送日志等本地生成的压缩包时,因路径格式不规范导致的发送失败问题。
@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Feb 23, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @SXP-Simon, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在优化 AstrBot 框架中文件处理的逻辑和 OneBot 驱动层的路径兼容性。通过统一 File 组件对 file:/// 协议头的识别,并为 OneBot 驱动层自动补全本地文件路径的协议头,解决了内核行为不一致、OneBot 协议合规性问题以及容器环境下裸路径解析歧义等痛点。这些改进显著增强了文件发送的鲁棒性,并解决了因路径格式不规范导致的发送失败问题。

Highlights

  • 文件路径协议头处理: 在 astrbot/core/message/components.pyFile.get_file() 方法中,增加了对 file:/// 协议头的识别与剥离逻辑,使其与 RecordVideo 组件的行为保持一致,确保内核处理文件路径的统一性。
  • OneBot 驱动层路径兼容性增强: 在 aiocqhttp 驱动层发送文件前,增加了自动修正逻辑。如果文件路径是绝对路径且未包含 httpbase64file:// 等协议头,系统将自动补全 file:/// 前缀,以符合 OneBot 11 标准,解决发送本地文件时可能出现的错误。
Changelog
  • astrbot/core/message/components.py
    • 更新了 get_file 方法,使其能够识别并正确处理以 file:/// 开头的路径,剥离协议头以获取实际文件系统路径。
  • astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py
    • 引入了 os 模块以支持路径判断。
    • _from_segment_to_dict 方法中,为 File 类型消息增加了自动修正逻辑,检测裸露的绝对路径并为其添加 file:/// 前缀。
Activity
  • SXP-Simon 创建了此拉取请求,旨在优化文件处理逻辑和增强 OneBot 驱动层路径兼容性。
  • 拉取请求描述详细阐述了更改的动机(内核一致性、OneBot 协议合规、容器环境适配)、具体的改动点和预期的影响。
  • 作者已确认此更改不是一个破坏性变更,并提供了测试验证的承诺。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added area:core The bug / feature is about astrbot's core, backend area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. labels Feb 23, 2026
@dosubot
Copy link

dosubot bot commented Feb 23, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:

  • file: URI 的生成和解析方式存在不一致:_from_segment_to_dict 可能生成以 file://(两个斜杠)开头的值,但 File.get_file 只会去掉 file:/// 前缀(三个斜杠),这会导致在两个斜杠的情况下 os.path.exists 调用失败——这两者应该统一成一种规范格式,或者在处理时保持对称。
  • _from_segment_to_dict 中的 Windows 路径情况(例如 C:\foo)会通过 os.path.isabs 检查,进入 else 分支,然后变成 file:///C:\foo,但没有规范化分隔符,也没有使用正确的 file: URI 转换方式,这可能会带来问题;建议使用 pathlib.Path.as_uri(),或者在构造 file: URI 时显式地把反斜杠转换为正斜杠。
  • 协议判断 not file_val.startswith(("http", "base64", "file://")) 过于宽松(例如,它会把任何以 http*base64* 开头的前缀都当作有协议);如果你只打算跳过真正的 URL scheme,建议检查是否包含 "://",或者使用更具体的前缀,如 "http://""https://""base64://",以避免误判。
面向 AI 代理的提示词
Please address the comments from this code review:

## Overall Comments
- There’s an inconsistency between how `file:` URIs are generated and parsed: `_from_segment_to_dict` may produce values starting with `file://` (two slashes), but `File.get_file` only strips the `file:///` prefix (three slashes), which will cause `os.path.exists` to fail for the two-slash case—these should be unified to a single canonical format or handled symmetrically.
- The Windows path case in `_from_segment_to_dict` (e.g., `C:\foo`) will pass `os.path.isabs`, fall into the `else` branch, and become `file:///C:\foo` without normalizing separators or using a proper `file:` URI conversion, which can be problematic; consider using `pathlib.Path.as_uri()` or explicitly normalizing backslashes to forward slashes when constructing `file:` URIs.
- The protocol guard `not file_val.startswith(("http", "base64", "file://"))` is quite loose (e.g., it treats any `http*` or `base64*` prefix as having a protocol); if you only intend to skip entries with real URL schemes, consider checking for `"://"` or more specific prefixes like `"http://"`, `"https://"`, and `"base64://"` to avoid misclassification.

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py" line_range="49-58" />
<code_context>
         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):
</code_context>
<issue_to_address>
**issue (bug_risk):** The absolute-path to `file://` conversion may behave unexpectedly on Windows (drive letters, backslashes, and UNC paths).

On Windows, `os.path.isabs(file_val)` will treat `C:\path\file` and `\\server\share` as absolute, but the current logic produces invalid or nonstandard URIs (e.g. `file:///C:\path\file`, `file://///server\share`). If Windows is in scope, add explicit handling for drive-letter and UNC paths (normalizing to `file:///C:/path` and `file://server/share`). Otherwise, consider limiting this conversion to POSIX-style paths or clearly documenting that only POSIX paths are supported.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会基于这些反馈改进后续的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • There’s an inconsistency between how file: URIs are generated and parsed: _from_segment_to_dict may produce values starting with file:// (two slashes), but File.get_file only strips the file:/// prefix (three slashes), which will cause os.path.exists to fail for the two-slash case—these should be unified to a single canonical format or handled symmetrically.
  • The Windows path case in _from_segment_to_dict (e.g., C:\foo) will pass os.path.isabs, fall into the else branch, and become file:///C:\foo without normalizing separators or using a proper file: URI conversion, which can be problematic; consider using pathlib.Path.as_uri() or explicitly normalizing backslashes to forward slashes when constructing file: URIs.
  • The protocol guard not file_val.startswith(("http", "base64", "file://")) is quite loose (e.g., it treats any http* or base64* prefix as having a protocol); if you only intend to skip entries with real URL schemes, consider checking for "://" or more specific prefixes like "http://", "https://", and "base64://" to avoid misclassification.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There’s an inconsistency between how `file:` URIs are generated and parsed: `_from_segment_to_dict` may produce values starting with `file://` (two slashes), but `File.get_file` only strips the `file:///` prefix (three slashes), which will cause `os.path.exists` to fail for the two-slash case—these should be unified to a single canonical format or handled symmetrically.
- The Windows path case in `_from_segment_to_dict` (e.g., `C:\foo`) will pass `os.path.isabs`, fall into the `else` branch, and become `file:///C:\foo` without normalizing separators or using a proper `file:` URI conversion, which can be problematic; consider using `pathlib.Path.as_uri()` or explicitly normalizing backslashes to forward slashes when constructing `file:` URIs.
- The protocol guard `not file_val.startswith(("http", "base64", "file://"))` is quite loose (e.g., it treats any `http*` or `base64*` prefix as having a protocol); if you only intend to skip entries with real URL schemes, consider checking for `"://"` or more specific prefixes like `"http://"`, `"https://"`, and `"base64://"` to avoid misclassification.

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py" line_range="49-58" />
<code_context>
         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):
</code_context>
<issue_to_address>
**issue (bug_risk):** The absolute-path to `file://` conversion may behave unexpectedly on Windows (drive letters, backslashes, and UNC paths).

On Windows, `os.path.isabs(file_val)` will treat `C:\path\file` and `\\server\share` as absolute, but the current logic produces invalid or nonstandard URIs (e.g. `file:///C:\path\file`, `file://///server\share`). If Windows is in scope, add explicit handling for drive-letter and UNC paths (normalizing to `file:///C:/path` and `file://server/share`). Otherwise, consider limiting this conversion to POSIX-style paths or clearly documenting that only POSIX paths are supported.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

此拉取请求有效地解决了 File 组件处理 file:/// 前缀的不一致性,并增强了 OneBot 驱动层对本地文件路径的兼容性。astrbot/core/message/components.py 中的 get_file 方法现在能正确剥离 file:/// 前缀,使其行为与 RecordVideo 组件保持一致。aiocqhttp_message_event.py 文件引入了在发送前自动为绝对本地文件路径添加 file:/// 前缀的逻辑,这对于 OneBot 11 标准合规性至关重要,并提高了容器环境中的鲁棒性。

Comment on lines 55 to 58
if file_val.startswith("/"):
d["data"]["file"] = f"file://{file_val}"
else:
d["data"]["file"] = f"file:///{file_val}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

aiocqhttp_message_event.py 中为绝对路径添加 file:// 前缀的逻辑需要修正,以完全符合 OneBot 11 标准。OneBot 11 标准要求本地文件路径使用 file:/// 协议头,其中路径部分应为绝对路径。

当前的实现中:

  • 如果 file_val 是 Unix-like 绝对路径(例如 /home/user/file.txt),if file_val.startswith("/") 分支会将其转换为 file:////home/user/file.txt(四个斜杠)。这与 file:/// 标准不符。
  • 如果 file_val 是 Windows 绝对路径(例如 C:\Users\user\file.txt),else 分支会将其转换为 file:///C:\Users\user\file.txt(三个斜杠),这符合标准。

为了确保所有绝对本地路径都以 file:/// 格式正确生成,无论操作系统如何,都应该先移除路径中可能存在的起始斜杠,然后再统一添加 file:/// 前缀。

例如:

  • /home/user/file.txt 应变为 file:///home/user/file.txt
  • C:\Users\user\file.txt 应变为 file:///C:\Users\user\file.txt
                normalized_path = file_val.lstrip("/")
                d["data"]["file"] = f"file:///{normalized_path}"

原因 (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 系统下的文件操作鲁棒性。
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Feb 23, 2026
@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 24, 2026
@Soulter Soulter merged commit 29dbd08 into AstrBotDevs:master Feb 24, 2026
6 checks passed
astrbot-doc-agent bot pushed a commit to AstrBotDevs/AstrBot-docs that referenced this pull request Feb 24, 2026
@astrbot-doc-agent
Copy link

Generated docs update PR (pending manual review):
AstrBotDevs/AstrBot-docs#145
Trigger: PR merged


AI change summary:

  • 更新 dev/star/guides/send-message.md(中/英)。
  • 在 File 组件部分新增注意事项:明确 file 参数支持本地绝对路径、file:/// URI 及 HTTP(S) URL。
  • 补充 aiocqhttp 适配器自动转换路径格式以适配 OneBot 11 协议的说明。
  • 提及使用 pathlib.Path.as_uri() 确保跨平台兼容性。
  • i18n:中英文文档均已同步更新。

Experimental bot notice:

  • This output is generated by AstrBot-Doc-Agent for review only.
  • It does not represent the final documentation form.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants