fix: handle Telegram Voice_messages_forbidden with typed exception and text caption#5204
Conversation
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些整体层面的反馈:
_send_voice_with_fallback中的"Voice_messages_forbidden"字面量现在是一个对行为至关重要的判别条件;建议将其(以及可能的错误类型)提取为具名常量或辅助函数,这样可以让与 Telegram API 错误字符串之间的耦合关系更加清晰,也更方便在上游文案变更时进行修改。_send_voice_with_fallback中对BadRequest的处理假定e.message始终是包含 API 错误的字符串;你可能需要通过str(e)或getattr(e, "message", "")进行防御性标准化,以避免在该属性缺失或结构不同的边缘情况下出错。_send_voice_with_fallback里关于use_media_action的分支是重复的(send_voice/send_document都有带 media-action 和不带 media-action 的分支);可以考虑将公共的调用模式抽取到一个小的内部辅助函数中,以减少重复代码,并降低这些分支随着时间推移产生差异的风险。
面向 AI 代理的提示词
请根据以下代码审查意见进行修改:
## 整体评论
- `_send_voice_with_fallback` 中的 `"Voice_messages_forbidden"` 字面量现在是一个对行为至关重要的判别条件;建议将其(以及可能的错误类型)提取为具名常量或辅助函数,这样可以让与 Telegram API 错误字符串之间的耦合关系更加清晰,也更方便在上游文案变更时进行修改。
- `_send_voice_with_fallback` 中对 `BadRequest` 的处理假定 `e.message` 始终是包含 API 错误的字符串;你可能需要通过 `str(e)` 或 `getattr(e, "message", "")` 进行防御性标准化,以避免在该属性缺失或结构不同的边缘情况下出错。
- `_send_voice_with_fallback` 里关于 `use_media_action` 的分支是重复的(`send_voice`/`send_document` 都有带 media-action 和不带 media-action 的分支);可以考虑将公共的调用模式抽取到一个小的内部辅助函数中,以减少重复代码,并降低这些分支随着时间推移产生差异的风险。
## 单条评论
### 评论 1
<location> `astrbot/core/platform/sources/telegram/tg_event.py:178-153` </location>
<code_context>
+ **cast(Any, payload),
+ )
+ else:
+ await client.send_document(
+ document=path,
+ caption=caption,
+ **cast(Any, payload),
+ )
+
</code_context>
<issue_to_address>
**issue (bug_risk):** 在回退路径中同时传入 `caption` 和 `**payload`,存在重复 `caption` 关键字参数的风险。
在回退分支中,你调用的是 `send_document(document=path, caption=caption, **payload)`。如果 `payload` 中包含 `caption` 键,就会因为同一个参数传入多次而抛出 `TypeError`。可以在这个调用前从 `payload` 中移除 `caption`,或者只通过 `payload` 传入 `caption`,并去掉单独的 `caption` 参数。对 `_send_media_with_action` 路径同样需要考虑这一点,因为它也会同时把 `caption` 和 `**payload` 传给 `client.send_document`。
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据这些反馈改进后续的审查。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- The
"Voice_messages_forbidden"literal in_send_voice_with_fallbackis now a behavior-critical discriminator; consider extracting it (and possibly the error type) into a named constant or helper to make the coupling to the Telegram API error string more explicit and easier to change if the upstream wording shifts. - The
BadRequesthandling in_send_voice_with_fallbackassumese.messageis always a string containing the API error; you may want to defensively normalize viastr(e)orgetattr(e, "message", "")to avoid edge cases where the attribute is missing or structured differently. - The duplicated branching for
use_media_actionin_send_voice_with_fallback(send_voice/send_documentboth having media-action and non-media-action variants) could be simplified by factoring the common call pattern into a small internal helper to reduce repetition and the chance of the branches diverging over time.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `"Voice_messages_forbidden"` literal in `_send_voice_with_fallback` is now a behavior-critical discriminator; consider extracting it (and possibly the error type) into a named constant or helper to make the coupling to the Telegram API error string more explicit and easier to change if the upstream wording shifts.
- The `BadRequest` handling in `_send_voice_with_fallback` assumes `e.message` is always a string containing the API error; you may want to defensively normalize via `str(e)` or `getattr(e, "message", "")` to avoid edge cases where the attribute is missing or structured differently.
- The duplicated branching for `use_media_action` in `_send_voice_with_fallback` (`send_voice`/`send_document` both having media-action and non-media-action variants) could be simplified by factoring the common call pattern into a small internal helper to reduce repetition and the chance of the branches diverging over time.
## Individual Comments
### Comment 1
<location> `astrbot/core/platform/sources/telegram/tg_event.py:178-153` </location>
<code_context>
+ **cast(Any, payload),
+ )
+ else:
+ await client.send_document(
+ document=path,
+ caption=caption,
+ **cast(Any, payload),
+ )
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Passing both `caption` and `**payload` risks a duplicate `caption` kwarg in the fallback path.
In the fallback branch you call `send_document(document=path, caption=caption, **payload)`. If `payload` ever includes a `caption` key, this will raise a `TypeError` for multiple values. Either remove `caption` from `payload` before this call, or pass `caption` only via `payload` and drop the separate `caption` argument. The same consideration applies to the `_send_media_with_action` path that forwards both `caption` and `**payload` to `client.send_document`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| user_name=user_name, | ||
| message_thread_id=message_thread_id, | ||
| voice=path, | ||
| **cast(Any, payload), |
There was a problem hiding this comment.
issue (bug_risk): 在回退路径中同时传入 caption 和 **payload,存在重复 caption 关键字参数的风险。
在回退分支中,你调用的是 send_document(document=path, caption=caption, **payload)。如果 payload 中包含 caption 键,就会因为同一个参数传入多次而抛出 TypeError。可以在这个调用前从 payload 中移除 caption,或者只通过 payload 传入 caption,并去掉单独的 caption 参数。对 _send_media_with_action 路径同样需要考虑这一点,因为它也会同时把 caption 和 **payload 传给 client.send_document。
Original comment in English
issue (bug_risk): Passing both caption and **payload risks a duplicate caption kwarg in the fallback path.
In the fallback branch you call send_document(document=path, caption=caption, **payload). If payload ever includes a caption key, this will raise a TypeError for multiple values. Either remove caption from payload before this call, or pass caption only via payload and drop the separate caption argument. The same consideration applies to the _send_media_with_action path that forwards both caption and **payload to client.send_document.
…r with typed BadRequest exception - Add _send_voice_with_fallback helper to deduplicate voice forbidden handling - Catch telegram.error.BadRequest instead of bare Exception with string matching - Add text field to Record component to preserve TTS source text - Store original text in Record during TTS conversion for use as document caption - Skip _send_chat_action when chat_id is empty to avoid unnecessary warnings
bbaaf5c to
7a8f77c
Compare
…r with typed BadRequest exception (AstrBotDevs#5204) - Add _send_voice_with_fallback helper to deduplicate voice forbidden handling - Catch telegram.error.BadRequest instead of bare Exception with string matching - Add text field to Record component to preserve TTS source text - Store original text in Record during TTS conversion for use as document caption - Skip _send_chat_action when chat_id is empty to avoid unnecessary warnings
The duplicated Voice_messages_forbidden handling in send_with_client and send_streaming was fragile (bare Exception + string matching) and hard to maintain. This PR consolidates it into a single helper with proper typed exception handling.
Modifications / 改动点
�strbot/core/platform/sources/telegram/tg_event.py: Extracted _send_voice_with_fallback helper to deduplicate voice forbidden fallback logic. Catches elegram.error.BadRequest instead of bare Exception. Added empty chat_id guard in _send_chat_action.
�strbot/core/message/components.py: Added optional ext field to Record to preserve TTS source text.
�strbot/core/pipeline/result_decorate/stage.py: Store original plain text into Record.text during TTS conversion, so it can be used as document caption on fallback.
This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
Tested with a Telegram user whose privacy settings forbid voice messages.

Checklist / 检查清单
Verification StepsandScreenshotshave been provided above.requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations inrequirements.txtandpyproject.toml.