Conversation
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py" line_range="51-64" />
<code_context>
if isinstance(segment, File):
# For File segments, we need to handle the file differently
+ try:
+ file_path = pathlib.Path(await segment.get_file())
+ if file_path.is_file():
+ file_data = base64.b64encode(file_path.read_bytes()).decode()
+ return {
+ "type": "file",
</code_context>
<issue_to_address>
**issue (broader_impact):** URL-backed `File` segments are downloaded into AstrBot's local filesystem and serialized as `base64://` instead of preserving their original URL. A failed URL download also escapes the `OSError` fallback because `get_file()` can raise non-`OSError` exceptions, so the existing URL serialization is not retained.
**Triggers:** When a `File` is constructed with an HTTP(S) URL rather than a local path.
**Suggested fix:** Only attempt the local-file base64 path when the component has a local `file_` source; otherwise serialize the URL through the existing `to_dict()` path, and handle download failures consistently.
```suggestion
if segment.file_:
try:
file_path = pathlib.Path(await segment.get_file())
if file_path.is_file():
file_data = base64.b64encode(file_path.read_bytes()).decode()
return {
"type": "file",
"data": {
"name": segment.name or file_path.name,
"file": f"base64://{file_data}",
},
}
except Exception:
# Fall back to a file reference if the contents are unavailable.
pass
```
</issue_to_address>
### Comment 2
<location path="astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py" line_range="54" />
<code_context>
+ try:
+ file_path = pathlib.Path(await segment.get_file())
+ if file_path.is_file():
+ file_data = base64.b64encode(file_path.read_bytes()).decode()
+ return {
+ "type": "file",
</code_context>
<issue_to_address>
**issue (performance):** `read_bytes()` performs a complete synchronous filesystem read inside the async message serialization coroutine, blocking the event loop for the entire read and base64 conversion. Sending a large local file therefore stalls unrelated bot tasks and message handling.
**Triggers:** When a local file is large or the filesystem is slow, such as a mounted volume in a container.
**Suggested fix:** Read and encode the file using an async file API or run the blocking read/base64 work via `asyncio.to_thread`.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 2 findings to address first, and this changes file handling from sending a reference to transmitting the local file contents in the outbound message. If the path or destination handling is wrong, file data can be exposed or an incorrect message can be sent, and reverting cannot retract what was already delivered.
Blocking findings: astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py:64, astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py:54
| try: | ||
| file_path = pathlib.Path(await segment.get_file()) | ||
| if file_path.is_file(): | ||
| file_data = base64.b64encode(file_path.read_bytes()).decode() | ||
| return { | ||
| "type": "file", | ||
| "data": { | ||
| "name": segment.name or file_path.name, | ||
| "file": f"base64://{file_data}", | ||
| }, | ||
| } | ||
| except OSError: | ||
| # Fall back to a file reference if the contents are unavailable. | ||
| pass |
There was a problem hiding this comment.
issue (broader_impact): URL-backed File segments are downloaded into AstrBot's local filesystem and serialized as base64:// instead of preserving their original URL. A failed URL download also escapes the OSError fallback because get_file() can raise non-OSError exceptions, so the existing URL serialization is not retained.
Triggers: When a File is constructed with an HTTP(S) URL rather than a local path.
Suggested fix: Only attempt the local-file base64 path when the component has a local file_ source; otherwise serialize the URL through the existing to_dict() path, and handle download failures consistently.
| try: | |
| file_path = pathlib.Path(await segment.get_file()) | |
| if file_path.is_file(): | |
| file_data = base64.b64encode(file_path.read_bytes()).decode() | |
| return { | |
| "type": "file", | |
| "data": { | |
| "name": segment.name or file_path.name, | |
| "file": f"base64://{file_data}", | |
| }, | |
| } | |
| except OSError: | |
| # Fall back to a file reference if the contents are unavailable. | |
| pass | |
| if segment.file_: | |
| try: | |
| file_path = pathlib.Path(await segment.get_file()) | |
| if file_path.is_file(): | |
| file_data = base64.b64encode(file_path.read_bytes()).decode() | |
| return { | |
| "type": "file", | |
| "data": { | |
| "name": segment.name or file_path.name, | |
| "file": f"base64://{file_data}", | |
| }, | |
| } | |
| except Exception: | |
| # Fall back to a file reference if the contents are unavailable. | |
| pass |
| try: | ||
| file_path = pathlib.Path(await segment.get_file()) | ||
| if file_path.is_file(): | ||
| file_data = base64.b64encode(file_path.read_bytes()).decode() |
There was a problem hiding this comment.
issue (performance): read_bytes() performs a complete synchronous filesystem read inside the async message serialization coroutine, blocking the event loop for the entire read and base64 conversion. Sending a large local file therefore stalls unrelated bot tasks and message handling.
Triggers: When a local file is large or the filesystem is slow, such as a mounted volume in a container.
Suggested fix: Read and encode the file using an async file API or run the blocking read/base64 work via asyncio.to_thread.
Motivation
Fixes #9626.
When AstrBot runs in a container, aiocqhttp/OneBot protocol adapters such as NapCat may not share the AstrBot filesystem.
ImageandRecordsegments are already encoded in-process, but localFilesegments were converted tofile://URIs. The protocol side then tried to open a path from a different container and failed withENOENT.Changes
Filecontents as a OneBotbase64://payload, matching the existing image/audio behavior.Testing
file://payload.uv run pytest tests/unit/test_aiocqhttp_file.py -q(3 passed).uv run pytest tests/unit/test_aiocqhttp_group_info.py tests/unit/test_aiocqhttp_poke.py tests/unit/test_aiocqhttp_reply.py tests/unit/test_aiocqhttp_file.py -q(18 passed).git diff --check.Summary by Sourcery
Ensure local OneBot file segments remain transferable when protocol adapters run in separate containers.
Bug Fixes:
Enhancements:
Tests: