fix: prefer record url for audio downloads#7726
fix: prefer record url for audio downloads#7726bugkeep wants to merge 1 commit intoAstrBotDevs:masterfrom
Conversation
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Consider refactoring the common
url = self.url or self.fileresolution and subsequent checks inconvert_to_file_pathandconvert_to_base64into a shared helper to avoid duplication and keep behavior consistent if the logic evolves. - Both
_download_audio_urland the conversion methods raise bareExceptionwith generic messages; using a more specific exception type (or preserving the underlying download error) would make failure modes easier to distinguish and debug.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider refactoring the common `url = self.url or self.file` resolution and subsequent checks in `convert_to_file_path` and `convert_to_base64` into a shared helper to avoid duplication and keep behavior consistent if the logic evolves.
- Both `_download_audio_url` and the conversion methods raise bare `Exception` with generic messages; using a more specific exception type (or preserving the underlying download error) would make failure modes easier to distinguish and debug.
## Individual Comments
### Comment 1
<location path="tests/unit/test_message_record_component.py" line_range="37-46" />
<code_context>
+
+
+@pytest.mark.asyncio
+async def test_record_convert_to_base64_prefers_url_when_file_is_name(
+ monkeypatch,
+ tmp_path,
+):
+ audio_bytes = b"audio-content"
+ audio_url = "http://napcat.local/nt_data/Ptt/2026-04/Ori/voice.amr"
+
+ async def fake_download_file(url: str, path: str, *_, **__) -> None:
+ assert url == audio_url
+ Path(path).write_bytes(audio_bytes)
+
+ monkeypatch.setattr(components, "download_file", fake_download_file)
+ monkeypatch.setattr(components, "get_astrbot_temp_path", lambda: str(tmp_path))
+
+ record = Record(file="voice.amr", url=audio_url)
+
+ assert await record.convert_to_base64() == base64.b64encode(audio_bytes).decode()
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding coverage for `base64://` URLs to ensure the new URL preference applies there as well
Since `convert_to_base64` now prefers `url` over `file` (including for `base64://` URLs), adding tests like `Record(file="voice.amr", url="base64://...")` for both `convert_to_file_path` and `convert_to_base64` would verify that `url` is used for base64 sources and that audio base64 handling (extension and round-tripping) works as intended.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| async def test_record_convert_to_base64_prefers_url_when_file_is_name( | ||
| monkeypatch, | ||
| tmp_path, | ||
| ): | ||
| audio_bytes = b"audio-content" | ||
| audio_url = "http://napcat.local/nt_data/Ptt/2026-04/Ori/voice.amr" | ||
|
|
||
| async def fake_download_file(url: str, path: str, *_, **__) -> None: | ||
| assert url == audio_url | ||
| Path(path).write_bytes(audio_bytes) |
There was a problem hiding this comment.
suggestion (testing): Consider adding coverage for base64:// URLs to ensure the new URL preference applies there as well
Since convert_to_base64 now prefers url over file (including for base64:// URLs), adding tests like Record(file="voice.amr", url="base64://...") for both convert_to_file_path and convert_to_base64 would verify that url is used for base64 sources and that audio base64 handling (extension and round-tripping) works as intended.
There was a problem hiding this comment.
Code Review
This pull request refactors the Record component to improve audio handling by introducing helper methods for downloading files and determining suffixes. It updates convert_to_file_path and convert_to_base64 to prioritize the url field over file and adds corresponding unit tests. Review feedback suggests refactoring the duplicated URL selection logic into a shared property and using more specific exception types, such as ValueError and RuntimeError, to improve maintainability and error handling.
| if os.path.exists(self.file): | ||
| return os.path.abspath(self.file) | ||
| raise Exception(f"not a valid file: {self.file}") | ||
| url = self.url or self.file |
There was a problem hiding this comment.
The logic to prefer self.url over self.file is duplicated in both convert_to_file_path and convert_to_base64. Refactor this into a shared helper function or property to avoid code duplication. Additionally, ensure this new attachment handling functionality is accompanied by corresponding unit tests.
@property
def _source_url(self) -> str | None:
return self.url or self.file
async def convert_to_file_path(self) -> str:
"""将这个语音统一转换为本地文件路径。这个方法避免了手动判断语音数据类型,直接返回语音数据的本地路径(如果是网络 URL, 则会自动进行下载)。
Returns:
str: 语音的本地路径,以绝对路径表示。
"""
url = self._source_urlReferences
- When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.
- New functionality, such as handling attachments, should be accompanied by corresponding unit tests.
| raise Exception(f"not a valid file: {self.file}") | ||
| url = self.url or self.file | ||
| if not url: | ||
| raise Exception(f"not a valid file: {url}") |
There was a problem hiding this comment.
| await download_file(url, str(file_path)) | ||
| if file_path.exists(): | ||
| return str(file_path.resolve()) | ||
| raise Exception(f"download failed: {url}") |
There was a problem hiding this comment.
The download_file utility already raises an exception if the download fails (e.g., non-200 status or network error). This explicit check is largely redundant, but if kept, it should raise a more specific exception like RuntimeError or IOError instead of a generic Exception.
| raise Exception(f"download failed: {url}") | |
| raise RuntimeError(f"download failed: {url}") |
| url = self.url or self.file | ||
| if not url: | ||
| raise Exception(f"not a valid file: {url}") |
There was a problem hiding this comment.
As suggested for convert_to_file_path, using a property for the source URL and a more specific exception type would improve maintainability and consistency by avoiding logic duplication.
| url = self.url or self.file | |
| if not url: | |
| raise Exception(f"not a valid file: {url}") | |
| url = self._source_url | |
| if not url: | |
| raise ValueError("No valid file or URL provided") |
References
- When implementing similar functionality for different cases (e.g., direct vs. quoted attachments), refactor the logic into a shared helper function to avoid code duplication.
Summary
Record.urloverRecord.filewhen resolving audio records, matching the existing Image fallback behavior.fileis only a filename buturlpoints to the downloadable voice file.Fixes #7686
Testing
uv run pytest tests/unit/test_message_record_component.py -quv run pytest tests/unit -quv run ruff format .uv run ruff check .Summary by Sourcery
Prefer record URLs over file names for resolving audio records and handle remote audio downloads via the generic file downloader.
Bug Fixes:
Enhancements:
Tests: