feat: add configurable message format for push notifications - #99
feat: add configurable message format for push notifications#99FFFold wants to merge 18 commits into
Conversation
…h messages 新增 message_format 配置项,支持三种发送格式: - merged_forward(默认):保持当前合并转发行为 - direct:直接发送消息组件,不走合并转发 - image:使用 AstrBot t2i 服务渲染为图片发送 配置层级与 send_mode 同级:_conf_schema.json 全局默认 → 订阅/用户/会话级可覆盖。 OneBot 平台支持所有三种格式,其他平台 merged_forward 自动回退为 direct。 新增文件: - V3_add_message_format.py: 数据库迁移 - entry_card.html: t2i 渲染模板 - test_onebot_sender_message_format.py: OneBot 格式测试 - test_notification_dispatcher_message_format.py: dispatcher 解析测试
…try paths and tests
for more information, see https://pre-commit.ci
📝 WalkthroughWalkthrough新增 Changes消息格式功能
Estimated code review effort: 5 (Critical) | ~90+ minutes Sequence Diagram(s)sequenceDiagram
participant Config as Dashboard/配置
participant Dispatcher as NotificationDispatcher
participant Sender as MessageSender
participant T2I as t2i服务
Config->>Dispatcher: 保存并解析 message_format
Dispatcher->>Sender: 传递 SendRequest.message_format
Sender->>T2I: 图片模式渲染 entry_card.html
T2I-->>Sender: 返回卡片图片
Sender-->>Dispatcher: 发送结果
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
审查者指南实现了通知的可配置消息格式(merged forward、direct、image),并将该选项贯穿到配置/默认值、领域实体、持久化和迁移、分发流水线、消息发送方(包括 OneBot)、Web API/UI、AI XML 推送工具、模板化 t2i 渲染,以及测试/文档中。 带有 t2i 渲染和回退机制的 IMAGE
|
| 变更 | 详情 | 文件 |
|---|---|---|
在配置、领域、持久化和 API 中,将 message_format 添加为一等可配置选项。 |
|
src/shared/constants.pysrc/infrastructure/config/models/plugin_config_models.pysrc/infrastructure/config/models/runtime_settings.pysrc/infrastructure/config/settings_builder.pysrc/domain/entities/user.pysrc/domain/entities/subscription.pysrc/infrastructure/persistence/models.pysrc/infrastructure/persistence/migrations/V3_add_message_format.pysrc/infrastructure/persistence/user_repository_impl.pysrc/infrastructure/persistence/subscription_repository_impl.pysrc/application/commands/get_user_settings_cmd.pysrc/application/commands/set_user_settings_cmd.pysrc/application/dto/user_dto.pysrc/application/dto/subscription_export_record.pysrc/interfaces/handlers/config.pysrc/interfaces/web_api.pysrc/infrastructure/persistence/__tests__/*docs/usage/commands.mdpages/dashboard/**/* |
在通知分发流水线中传播解析出的 message_format,包括代理 XML 推送和重试。 |
|
src/application/services/notification_dispatcher.pysrc/application/services/agent_xml_push_service.pysrc/application/llmtools/xml_push.pytests/unit/application/test_notification_dispatcher_message_format.pytests/unit/application/test_llmtools.pydocs/project/application.mddocs/project/architecture.mddocs/project/dispatch.mddocs/project/domain-model.mddocs/project/platforms.mddocs/usage/ai-tools.mddocs/usage/configuration.md |
实现 message_format 感知的发送行为,包括直接单链发送以及带优雅回退的 t2i 图片渲染。 |
|
src/infrastructure/messaging/senders/base_sender.pysrc/infrastructure/messaging/senders/onebot_sender.pysrc/infrastructure/messaging/senders/provider.pysrc/application/ports/message_sender.pysrc/infrastructure/messaging/senders/types.pytemplates/entry_card.htmltests/unit/infrastructure/test_onebot_sender_message_format.py |
将 message_format 暴露给 AI 工具,并确保文档/测试保持一致。 |
|
src/application/llmtools/xml_push.pytests/unit/application/test_llmtools.pydocs/usage/ai-tools.mddocs/project/application.mddocs/project/architecture.mddocs/project/platforms.mddocs/project/dispatch.mddocs/project/domain-model.mddocs/usage/configuration.md |
与关联 Issue 的对照评估
| Issue | 目标 | 是否已满足 | 说明 |
|---|---|---|---|
| #98 | 引入可配置的 message_format/forward_mode 选项,与 send_mode 并行(默认为 merged_forward),可在全局、用户级和订阅级设置,并被持久化、通过 API/CLI/UI 暴露,以及在分发推送时使用。 |
✅ | |
| #98 | 实现 direct 消息格式,尤其是在 OneBot/QQ 上,将 RSS 条目以普通消息链直接发送(不使用 merged-forward/Nodes 容器),同时在链内部仍使用现有内容选择(send_mode)。 |
✅ | |
| #98 | 实现 image 消息格式,通过 AstrBot 的 t2i 服务(基于 HTML 模板)将条目内容渲染为图片,与链接和媒体附件一并发送,并在 t2i 渲染或模板加载失败时回退到 direct 发送。 |
✅ |
可能相关的 Issue
- Feature: 提供更丰富的发送模式 #98:该 PR 在 OneBot 上完整实现了该 Issue 请求的
message_format选项,包括merged_forward、direct和image模式。
提示和命令
与 Sourcery 交互
- 触发新审查: 在 Pull Request 中评论
@sourcery-ai review。 - 继续讨论: 直接回复 Sourcery 的审查评论。
- 从审查评论生成 GitHub issue: 通过回复审查评论,让 Sourcery 从该评论创建 issue。你也可以回复审查评论
@sourcery-ai issue来从中创建 issue。 - 生成 Pull Request 标题: 在 PR 标题中任意位置写入
@sourcery-ai即可随时生成标题。你也可以在 PR 中评论@sourcery-ai title来(重新)生成标题。 - 生成 Pull Request 摘要: 在 PR 正文中任意位置写入
@sourcery-ai summary,即可在该位置生成 PR 摘要。你也可以在 PR 中评论@sourcery-ai summary来随时(重新)生成摘要。 - 生成审查者指南: 在 Pull Request 中评论
@sourcery-ai guide来(重新)生成审查者指南。 - 解决所有 Sourcery 评论: 在 Pull Request 中评论
@sourcery-ai resolve来标记解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,这会很有用。 - 忽略所有 Sourcery 审查: 在 Pull Request 中评论
@sourcery-ai dismiss来忽略所有现有 Sourcery 审查。尤其适用于想从头开始新的审查的场景——别忘了再评论一次@sourcery-ai review以触发新审查!
自定义你的体验
访问你的 dashboard 可以:
- 启用或禁用审查功能,例如 Sourcery 生成的 PR 摘要、审查者指南等。
- 更改审查语言。
- 添加、删除或编辑自定义审查说明。
- 调整其他审查设置。
获取帮助
Original review guide in English
Reviewer's Guide
Implements configurable message formats for notifications (merged forward, direct, image) and wires this option through config/defaults, domain entities, persistence and migrations, dispatch pipeline, message senders (including OneBot), Web API/UI, AI XML push tooling, templated t2i rendering, and tests/docs.
Sequence diagram for IMAGE message_format sending with t2i rendering and fallback
sequenceDiagram
participant ND as NotificationDispatcher
participant SP as MessageSenderProvider
participant BS as BaseSender
participant HR as html_renderer
ND->>SP: send_to_session(..., message_format=MESSAGE_FORMAT_IMAGE)
SP->>BS: send_to_user(request, context.message_format)
BS->>BS: _send_as_image(request, prepared_media, context)
BS->>HR: render_custom_template(tmpl_str, tmpl_data, options)
alt render succeeds
HR-->>BS: rendered
BS->>BS: _build_components(...)
BS->>BS: _send_chain(session_id, chain)
else render fails or empty
HR-->>BS: [exception or empty]
BS->>BS: _send_direct(request, prepared_media, context)
BS->>BS: _components_to_single_chain(components, request.message)
BS->>BS: _send_chain(session_id, chain)
end
BS-->>SP: SendResult
SP-->>ND: SendResult
Flow diagram for resolving effective message_format in dispatch pipeline
flowchart TD
subgraph ConfigAndDomain
SD[SubscriptionDefaults.message_format]
U[User.message_format]
S[Subscription.message_format]
end
ND[NotificationDispatcher]
RMF[_resolve_message_format]
DFLT[_message_format_from_subscription_defaults]
PREP[PreparedSubscriptionDispatch.effective_message_format]
MC[MessageContext.message_format]
SD --> DFLT
DFLT --> ND
ND --> RMF
S --> RMF
U --> RMF
RMF --> PREP
PREP --> MC
File-Level Changes
| Change | Details | Files |
|---|---|---|
| Add message_format as a first-class configurable option across config, domain, persistence, and APIs. |
|
src/shared/constants.pysrc/infrastructure/config/models/plugin_config_models.pysrc/infrastructure/config/models/runtime_settings.pysrc/infrastructure/config/settings_builder.pysrc/domain/entities/user.pysrc/domain/entities/subscription.pysrc/infrastructure/persistence/models.pysrc/infrastructure/persistence/migrations/V3_add_message_format.pysrc/infrastructure/persistence/user_repository_impl.pysrc/infrastructure/persistence/subscription_repository_impl.pysrc/application/commands/get_user_settings_cmd.pysrc/application/commands/set_user_settings_cmd.pysrc/application/dto/user_dto.pysrc/application/dto/subscription_export_record.pysrc/interfaces/handlers/config.pysrc/interfaces/web_api.pysrc/infrastructure/persistence/__tests__/*docs/usage/commands.mdpages/dashboard/**/* |
| Propagate resolved message_format through the notification dispatch pipeline, including agent XML pushes and retries. |
|
src/application/services/notification_dispatcher.pysrc/application/services/agent_xml_push_service.pysrc/application/llmtools/xml_push.pytests/unit/application/test_notification_dispatcher_message_format.pytests/unit/application/test_llmtools.pydocs/project/application.mddocs/project/architecture.mddocs/project/dispatch.mddocs/project/domain-model.mddocs/project/platforms.mddocs/usage/ai-tools.mddocs/usage/configuration.md |
| Implement message_format-aware sending behavior, including direct single-chain sends and t2i image rendering with graceful fallback. |
|
src/infrastructure/messaging/senders/base_sender.pysrc/infrastructure/messaging/senders/onebot_sender.pysrc/infrastructure/messaging/senders/provider.pysrc/application/ports/message_sender.pysrc/infrastructure/messaging/senders/types.pytemplates/entry_card.htmltests/unit/infrastructure/test_onebot_sender_message_format.py |
| Expose message_format to AI tooling and ensure docs/tests remain consistent. |
|
src/application/llmtools/xml_push.pytests/unit/application/test_llmtools.pydocs/usage/ai-tools.mddocs/project/application.mddocs/project/architecture.mddocs/project/platforms.mddocs/project/dispatch.mddocs/project/domain-model.mddocs/usage/configuration.md |
Assessment against linked issues
| Issue | Objective | Addressed | Explanation |
|---|---|---|---|
| #98 | Introduce a configurable message_format/forward_mode option alongside send_mode (defaulting to merged_forward) that can be set globally, per user, and per subscription, and is persisted, exposed via APIs/CLI/UI, and used when dispatching pushes. |
✅ | |
| #98 | Implement a direct message format where, especially on OneBot/QQ, RSS entries are sent as plain message chains directly (without merged-forward/Nodes container), while still using existing content selection (send_mode) inside the chain. |
✅ | |
| #98 | Implement an image message format that renders entry content into an image via AstrBot’s t2i service (HTML template-based), sends it along with links and media attachments, and falls back to direct sending if t2i rendering or template loading fails. |
✅ |
Possibly linked issues
- Feature: 提供更丰富的发送模式 #98: PR fully implements the issue’s requested message_format option for OneBot, including merged_forward, direct, and image modes.
Tips and commands
Interacting with Sourcery
- Trigger a new review: Comment
@sourcery-ai reviewon the pull request. - Continue discussions: Reply directly to Sourcery's review comments.
- Generate a GitHub issue from a review comment: Ask Sourcery to create an
issue from a review comment by replying to it. You can also reply to a
review comment with@sourcery-ai issueto create an issue from it. - Generate a pull request title: Write
@sourcery-aianywhere in the pull
request title to generate a title at any time. You can also comment
@sourcery-ai titleon the pull request to (re-)generate the title at any time. - Generate a pull request summary: Write
@sourcery-ai summaryanywhere in
the pull request body to generate a PR summary at any time exactly where you
want it. You can also comment@sourcery-ai summaryon the pull request to
(re-)generate the summary at any time. - Generate reviewer's guide: Comment
@sourcery-ai guideon the pull
request to (re-)generate the reviewer's guide at any time. - Resolve all Sourcery comments: Comment
@sourcery-ai resolveon the
pull request to resolve all Sourcery comments. Useful if you've already
addressed all the comments and don't want to see them anymore. - Dismiss all Sourcery reviews: Comment
@sourcery-ai dismisson the pull
request to dismiss all existing Sourcery reviews. Especially useful if you
want to start fresh with a new review - don't forget to comment
@sourcery-ai reviewto trigger a new review!
Customizing Your Experience
Access your dashboard to:
- Enable or disable review features such as the Sourcery-generated pull request
summary, the reviewer's guide, and others. - Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.
Getting Help
- Contact our support team for questions or feedback.
- Visit our documentation for detailed guides and information.
- Keep in touch with the Sourcery team by following us on X/Twitter, LinkedIn or GitHub.
There was a problem hiding this comment.
Hey - 我发现了 5 个问题,并且给了一些整体性的反馈:
_send_as_image中对entry_card.html的查找是从__file__向上走固定层级的父目录,这在以后重构时会非常脆弱;建议使用importlib.resources或者一个集中管理模板路径的工具函数来定位模板,而不是硬编码相对路径的层级深度。message_format的字符串/整数映射逻辑在多个地方重复出现(例如MESSAGE_FORMAT_STRING_MAP、GlobalConfig._MESSAGE_FORMAT_MAP、agent_xml_push_service里的_resolve_push_options);把这些逻辑抽到共享的 helper 中,或者复用枚举/常量映射,可以降低映射关系逐渐不一致的风险。
给 AI Agent 的提示
Please address the comments from this code review:
## Overall Comments
- `_send_as_image` 中对 `entry_card.html` 的查找是从 `__file__` 向上走固定层级的父目录,这在以后重构时会非常脆弱;建议使用 `importlib.resources` 或者一个集中管理模板路径的工具函数来定位模板,而不是硬编码相对路径的层级深度。
- `message_format` 的字符串/整数映射逻辑在多个地方重复出现(例如 `MESSAGE_FORMAT_STRING_MAP`、`GlobalConfig._MESSAGE_FORMAT_MAP`、`agent_xml_push_service` 里的 `_resolve_push_options`);把这些逻辑抽到共享的 helper 中,或者复用枚举/常量映射,可以降低映射关系逐渐不一致的风险。
## Individual Comments
### Comment 1
<location path="src/infrastructure/messaging/senders/base_sender.py" line_range="1484-1493" />
<code_context>
+ entry_title = getattr(context, "entry_title", "")
+ entry_link = getattr(context, "entry_link", "")
+
+ media_previews = []
+ if prepared_media:
+ for pm in prepared_media:
+ if pm.media_type == "image" and pm.original_url:
+ media_previews.append(
+ {
+ "type": "image",
</code_context>
<issue_to_address>
**suggestion:** 模板已经支持视频媒体,但 `_send_as_image` 只收集图片用于预览。
由于 `_send_as_image` 只会为 `pm.media_type == "image"` 的条目添加预览,模板里 `item.type == 'video'` 的分支实际上从未被使用。可以考虑要么扩展这里的逻辑,把视频也纳入(例如以 `type: "video"` 追加到 `media_previews`),要么移除模板中与视频相关的路径,避免产生死代码。
```suggestion
media_previews = []
if prepared_media:
for pm in prepared_media:
if pm.original_url and pm.media_type in ("image", "video"):
media_previews.append(
{
"type": pm.media_type,
"url": pm.original_url,
}
)
```
</issue_to_address>
### Comment 2
<location path="src/application/services/agent_xml_push_service.py" line_range="206-215" />
<code_context>
allowed={SEND_MODE_LINK_ONLY, SEND_MODE_AUTO, SEND_MODE_DIRECT},
fallback=SEND_MODE_AUTO,
)
+ message_format_value = _parse_mapped_int(
+ message_format,
+ mapping={
+ "merged_forward": 0,
+ "merged-forward": 0,
+ "merged": 0,
+ "合并转发": 0,
+ "direct": 1,
+ "直发": 1,
+ "image": 2,
+ "图片": 2,
+ "t2i": 2,
+ },
+ allowed={0, 1, 2},
+ fallback=0,
+ )
style_value = _parse_mapped_int(
</code_context>
<issue_to_address>
**suggestion:** message format 的映射中直接硬编码了数值,而不是复用已有的共享常量。
这里的映射应该使用 `shared/constants.py` 中的 `MessageFormat` / `MESSAGE_FORMAT_*` 常量,而不是裸写 `0/1/2`。请导入并引用 `MESSAGE_FORMAT_MERGED_FORWARD`、`MESSAGE_FORMAT_DIRECT` 和 `MESSAGE_FORMAT_IMAGE`,在 `mapping` 和 `allowed` 中都使用这些常量,以避免枚举变化时出现偏差,并使其与全局约定保持一致。
建议实现如下:
```python
message_format_value = _parse_mapped_int(
message_format,
mapping={
"merged_forward": MESSAGE_FORMAT_MERGED_FORWARD,
"merged-forward": MESSAGE_FORMAT_MERGED_FORWARD,
"merged": MESSAGE_FORMAT_MERGED_FORWARD,
"合并转发": MESSAGE_FORMAT_MERGED_FORWARD,
"direct": MESSAGE_FORMAT_DIRECT,
"直发": MESSAGE_FORMAT_DIRECT,
"image": MESSAGE_FORMAT_IMAGE,
"图片": MESSAGE_FORMAT_IMAGE,
"t2i": MESSAGE_FORMAT_IMAGE,
},
allowed={MESSAGE_FORMAT_MERGED_FORWARD, MESSAGE_FORMAT_DIRECT, MESSAGE_FORMAT_IMAGE},
fallback=MESSAGE_FORMAT_MERGED_FORWARD,
)
```
为了完整实现该建议,还需要:
1. 确保在 `src/application/services/agent_xml_push_service.py` 顶部导入了 `MESSAGE_FORMAT_MERGED_FORWARD`、`MESSAGE_FORMAT_DIRECT` 和 `MESSAGE_FORMAT_IMAGE`,例如:
- `from shared.constants import MESSAGE_FORMAT_MERGED_FORWARD, MESSAGE_FORMAT_DIRECT, MESSAGE_FORMAT_IMAGE`
2. 如果该文件已经从 `shared/constants.py` 导入了其他与 message format 相关的常量,请将这三个常量添加到已有的导入语句中,而不是单独新增一行,以保持当前代码风格一致。
</issue_to_address>
### Comment 3
<location path="src/application/services/notification_dispatcher.py" line_range="1251-1256" />
<code_context>
entry_link=entry_link,
platform_name=target.platform_name or "",
send_mode=self._normalize_send_mode_value(send_mode),
+ message_format=message_format or MESSAGE_FORMAT_DEFAULT,
style=style,
sender_strategy=sender_strategy,
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 依赖 `or` 来处理 `message_format` 默认值,未来在默认值变为非 0 时可能会带来问题。
目前由于 `MESSAGE_FORMAT_DEFAULT` 是 `0`,这种写法对 `None` 和当前已有的合法值来说是可行的。但如果默认值未来变为非 0,那么这里会把上游有意传入的 `0` 给“吃掉”。改为 `message_format if message_format is not None else MESSAGE_FORMAT_DEFAULT` 可以更清晰地表达意图,并避免以后对 “0” 与“默认值” 之间含义的混淆。
```suggestion
entry_link=entry_link,
platform_name=target.platform_name or "",
send_mode=self._normalize_send_mode_value(send_mode),
message_format=message_format if message_format is not None else MESSAGE_FORMAT_DEFAULT,
style=style,
sender_strategy=sender_strategy,
```
</issue_to_address>
### Comment 4
<location path="tests/unit/infrastructure/test_onebot_sender_message_format.py" line_range="1-10" />
<code_context>
+"""V3 迁移:在 rsshub_user 和 rsshub_sub 表中添加 message_format 列"""
+
+from __future__ import annotations
+
+from ...utils import get_logger
</code_context>
<issue_to_address>
**suggestion (testing):** 加强 DIRECT 模式的测试,断言实际的 chain 内容以及默认文本回退逻辑。
当前测试只验证了确实发送了一个 chain,但没有检查它的具体内容。考虑到 `_components_to_single_chain` 中的逻辑比较复杂(裁剪、media 与尾部的处理、`default_text` 等),建议断言生成的 chain 恰好由一个内容为 `"Hello"` 且没有额外字段的 Plain 节点组成(例如通过 `_Plain` shim 检查 `sent_chain[0][1]`)。另外也可以补充一个用例:`message=""` 但组件列表包含媒体内容,以验证在存在媒体时不会注入 `default_text`。
</issue_to_address>
### Comment 5
<location path="docs/project/domain-model.md" line_range="15" />
<code_context>
| 配置继承标记 | `-100` | 订阅继承用户配置,用户继承全局默认 | 不恢复 `use_sub_config` / `use_user_config`。 |
| 用户状态 | `1` / `-1` | `USER_STATE_USER` / `USER_STATE_BANNED` | 旧非负状态统一视为普通用户。 |
| `send_mode` | `-1` / `0` / `1` | 仅链接 / 自动 / 直接发送 | 旧 `1=Telegraph` 归一化为 `0`,旧 `2=直接消息` 归一化为 `1`。 |
+| `message_format` | `0` / `1` / `2` | 合并转发 / 直发 / 图片 | OneBot 支持全部三种格式;其他平台 `0` 自动回落为 `1`。 |
| `style` | `0` / `1` / `2` | 自动或平台经典 / RSSRT / original | 旧 `flowerss=1` 迁移为 `0`,不恢复 flowerss UI 文案。 |
| 显示类字段 | 整数状态 | `display_author`、`display_via`、`display_title`、`display_entry_tags`、`display_media` | 需要支持继承,不能简化为 `true/false`。 |
</code_context>
<issue_to_address>
**nitpick (typo):** 建议与前文 `message_format` 的说明用词保持一致(“回退” vs “回落”)。
在 `docs/usage/commands.md` 中写的是 “其他平台选 `0` 自动回退为 `1`”,而这里写的是 “自动回落为 `1`”。两处描述的是同一个行为,建议统一用词(例如都用“回退”或都用“回落”),以避免产生歧义。
```suggestion
| `message_format` | `0` / `1` / `2` | 合并转发 / 直发 / 图片 | OneBot 支持全部三种格式;其他平台 `0` 自动回退为 `1`。 |
```
</issue_to_address>帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进之后的 review。
Original comment in English
Hey - I've found 5 issues, and left some high level feedback:
- The
entry_card.htmllookup in_send_as_imagewalks up a fixed number of parents from__file__, which is brittle to future refactors; consider usingimportlib.resourcesor a central helper to locate templates instead of hardcoding the relative path depth. message_formatstring/int mapping logic is duplicated in several places (e.g.,MESSAGE_FORMAT_STRING_MAP,GlobalConfig._MESSAGE_FORMAT_MAP,_resolve_push_optionsinagent_xml_push_service); consolidating these into shared helpers or reusing the enum/constant maps would reduce the risk of the mappings drifting out of sync.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `entry_card.html` lookup in `_send_as_image` walks up a fixed number of parents from `__file__`, which is brittle to future refactors; consider using `importlib.resources` or a central helper to locate templates instead of hardcoding the relative path depth.
- `message_format` string/int mapping logic is duplicated in several places (e.g., `MESSAGE_FORMAT_STRING_MAP`, `GlobalConfig._MESSAGE_FORMAT_MAP`, `_resolve_push_options` in `agent_xml_push_service`); consolidating these into shared helpers or reusing the enum/constant maps would reduce the risk of the mappings drifting out of sync.
## Individual Comments
### Comment 1
<location path="src/infrastructure/messaging/senders/base_sender.py" line_range="1484-1493" />
<code_context>
+ entry_title = getattr(context, "entry_title", "")
+ entry_link = getattr(context, "entry_link", "")
+
+ media_previews = []
+ if prepared_media:
+ for pm in prepared_media:
+ if pm.media_type == "image" and pm.original_url:
+ media_previews.append(
+ {
+ "type": "image",
</code_context>
<issue_to_address>
**suggestion:** Template supports video media but `_send_as_image` only collects images for previews.
Since `_send_as_image` only adds entries where `pm.media_type == "image"`, the template’s `item.type == 'video'` branch is never used. Either extend this logic to include video items (e.g. append them to `media_previews` with `type: "video"`) or remove the video-specific template path to avoid dead code.
```suggestion
media_previews = []
if prepared_media:
for pm in prepared_media:
if pm.original_url and pm.media_type in ("image", "video"):
media_previews.append(
{
"type": pm.media_type,
"url": pm.original_url,
}
)
```
</issue_to_address>
### Comment 2
<location path="src/application/services/agent_xml_push_service.py" line_range="206-215" />
<code_context>
allowed={SEND_MODE_LINK_ONLY, SEND_MODE_AUTO, SEND_MODE_DIRECT},
fallback=SEND_MODE_AUTO,
)
+ message_format_value = _parse_mapped_int(
+ message_format,
+ mapping={
+ "merged_forward": 0,
+ "merged-forward": 0,
+ "merged": 0,
+ "合并转发": 0,
+ "direct": 1,
+ "直发": 1,
+ "image": 2,
+ "图片": 2,
+ "t2i": 2,
+ },
+ allowed={0, 1, 2},
+ fallback=0,
+ )
style_value = _parse_mapped_int(
</code_context>
<issue_to_address>
**suggestion:** Message format mapping hard-codes numeric values instead of reusing shared constants.
This mapping should use the `MessageFormat` / `MESSAGE_FORMAT_*` constants from `shared/constants.py` instead of raw values `0/1/2`. Import and reference `MESSAGE_FORMAT_MERGED_FORWARD`, `MESSAGE_FORMAT_DIRECT`, and `MESSAGE_FORMAT_IMAGE` for both `mapping` and `allowed` to avoid drift if the enum changes and to keep this aligned with the global contract.
Suggested implementation:
```python
message_format_value = _parse_mapped_int(
message_format,
mapping={
"merged_forward": MESSAGE_FORMAT_MERGED_FORWARD,
"merged-forward": MESSAGE_FORMAT_MERGED_FORWARD,
"merged": MESSAGE_FORMAT_MERGED_FORWARD,
"合并转发": MESSAGE_FORMAT_MERGED_FORWARD,
"direct": MESSAGE_FORMAT_DIRECT,
"直发": MESSAGE_FORMAT_DIRECT,
"image": MESSAGE_FORMAT_IMAGE,
"图片": MESSAGE_FORMAT_IMAGE,
"t2i": MESSAGE_FORMAT_IMAGE,
},
allowed={MESSAGE_FORMAT_MERGED_FORWARD, MESSAGE_FORMAT_DIRECT, MESSAGE_FORMAT_IMAGE},
fallback=MESSAGE_FORMAT_MERGED_FORWARD,
)
```
To fully implement the suggestion, you should also:
1. Ensure `MESSAGE_FORMAT_MERGED_FORWARD`, `MESSAGE_FORMAT_DIRECT`, and `MESSAGE_FORMAT_IMAGE` are imported at the top of `src/application/services/agent_xml_push_service.py`, for example:
- `from shared.constants import MESSAGE_FORMAT_MERGED_FORWARD, MESSAGE_FORMAT_DIRECT, MESSAGE_FORMAT_IMAGE`
2. If the file already imports other message format–related constants from `shared/constants.py`, add these three to the existing import line instead of introducing a new one, to match the current style.
</issue_to_address>
### Comment 3
<location path="src/application/services/notification_dispatcher.py" line_range="1251-1256" />
<code_context>
entry_link=entry_link,
platform_name=target.platform_name or "",
send_mode=self._normalize_send_mode_value(send_mode),
+ message_format=message_format or MESSAGE_FORMAT_DEFAULT,
style=style,
sender_strategy=sender_strategy,
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Relying on `or` for `message_format` defaults may obscure future non-zero defaults.
Because `MESSAGE_FORMAT_DEFAULT` is currently `0`, this works for now for `None` and the existing valid values. But if the default ever becomes non-zero, this could hide a deliberate `0` passed from upstream. Using `message_format if message_format is not None else MESSAGE_FORMAT_DEFAULT` makes the intent explicit and avoids future ambiguity around `0` vs. the default.
```suggestion
entry_link=entry_link,
platform_name=target.platform_name or "",
send_mode=self._normalize_send_mode_value(send_mode),
message_format=message_format if message_format is not None else MESSAGE_FORMAT_DEFAULT,
style=style,
sender_strategy=sender_strategy,
```
</issue_to_address>
### Comment 4
<location path="tests/unit/infrastructure/test_onebot_sender_message_format.py" line_range="1-10" />
<code_context>
+"""V3 迁移:在 rsshub_user 和 rsshub_sub 表中添加 message_format 列"""
+
+from __future__ import annotations
+
+from ...utils import get_logger
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen DIRECT mode test by asserting the actual chain content and default text fallback
This test only checks that a chain was sent, not what it contains. Given the non-trivial logic in `_components_to_single_chain` (trimming, media vs tail, `default_text`), please assert that the resulting chain is exactly one Plain node with `"Hello"` and no extras (e.g., by inspecting `sent_chain[0][1]` with the `_Plain` shim). Also consider adding a test where `message=""` but the component list contains media, to verify that `default_text` is not injected when media is present.
</issue_to_address>
### Comment 5
<location path="docs/project/domain-model.md" line_range="15" />
<code_context>
| 配置继承标记 | `-100` | 订阅继承用户配置,用户继承全局默认 | 不恢复 `use_sub_config` / `use_user_config`。 |
| 用户状态 | `1` / `-1` | `USER_STATE_USER` / `USER_STATE_BANNED` | 旧非负状态统一视为普通用户。 |
| `send_mode` | `-1` / `0` / `1` | 仅链接 / 自动 / 直接发送 | 旧 `1=Telegraph` 归一化为 `0`,旧 `2=直接消息` 归一化为 `1`。 |
+| `message_format` | `0` / `1` / `2` | 合并转发 / 直发 / 图片 | OneBot 支持全部三种格式;其他平台 `0` 自动回落为 `1`。 |
| `style` | `0` / `1` / `2` | 自动或平台经典 / RSSRT / original | 旧 `flowerss=1` 迁移为 `0`,不恢复 flowerss UI 文案。 |
| 显示类字段 | 整数状态 | `display_author`、`display_via`、`display_title`、`display_entry_tags`、`display_media` | 需要支持继承,不能简化为 `true/false`。 |
</code_context>
<issue_to_address>
**nitpick (typo):** Consider making the wording consistent with the earlier `message_format` description ("回退" vs "回落").
In `docs/usage/commands.md` it says “其他平台选 `0` 自动回退为 `1`”, while here it says “自动回落为 `1`”. Since they describe the same behavior, please standardize on one verb (e.g., both “回退” or both “回落”) to avoid ambiguity.
```suggestion
| `message_format` | `0` / `1` / `2` | 合并转发 / 直发 / 图片 | OneBot 支持全部三种格式;其他平台 `0` 自动回退为 `1`。 |
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/application/services/notification_dispatcher.py (1)
1254-1254: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value修复基于
or运算符的假值(falsy)判定陷阱。使用
or进行默认值回退时,如果message_format参数值为0(即MESSAGE_FORMAT_MERGED_FORWARD),会被判定为假值(falsy),从而引发短路逻辑并回退到MESSAGE_FORMAT_DEFAULT。虽然目前MESSAGE_FORMAT_DEFAULT恰好也是0使得结果碰巧正确,但如果未来默认值发生更改(例如改为1),这里将产生严重的配置覆写 Bug。建议使用is not None进行显式判定。♻️ 建议的修复
- message_format=message_format or MESSAGE_FORMAT_DEFAULT, + message_format=message_format if message_format is not None else MESSAGE_FORMAT_DEFAULT,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/application/services/notification_dispatcher.py` at line 1254, Update the message_format fallback in the notification dispatch construction to distinguish None from valid falsy values such as 0; use an explicit is-not-None check so MESSAGE_FORMAT_MERGED_FORWARD is preserved and only an absent value falls back to MESSAGE_FORMAT_DEFAULT.tests/unit/infrastructure/test_user_repository_impl.py (1)
44-44: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win补充
message_format的非默认往返断言。当前
User(id="u1")与existing.message_format都是-100;即使映射代码被删除,测试仍可能通过。请设置一个非默认值并断言保存后的实体及 ORM 值。建议修改
user = User(id="u1") user.notify = 1 +user.message_format = 2 ... assert saved.notify == 1 +assert saved.message_format == 2 +assert existing.message_format == 2🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/infrastructure/test_user_repository_impl.py` at line 44, Update the test setup for User and existing in the repository test to use a non-default message_format value instead of -100, then assert that the saved domain entity and ORM record both preserve that value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/project/dispatch.md`:
- Line 62: 修正文档中的配置对象描述,将 `message_format` 明确表述为与 `EffectivePushOptions`
独立的配置值;除非同步调整 `notification_dispatcher.py` 中 `_resolve_message_format()` 和
`_resolve_effective_push_options()` 的实现,否则不要将其描述为 `EffectivePushOptions` 的字段。
In `@src/application/services/notification_dispatcher.py`:
- Line 1440: 更新 dispatch_pending_retries 中的重试逻辑:提取 sub 后获取其 user 上下文,通过
self._resolve_message_format(sub, user) 动态解析并传递 message_format,同时保留实际配置的
send_mode。更新 retry_push_history_once 在 _target_from_history 恢复上下文后获取订阅配置,传递解析出的
message_format 和 send_mode,移除两处对 MESSAGE_FORMAT_DEFAULT 及默认发送模式的硬编码;涉及
src/application/services/notification_dispatcher.py#L1440-L1440 和 `#L1545-L1545`。
In `@src/infrastructure/messaging/senders/base_sender.py`:
- Around line 1484-1493: 更新 base_sender 中构建 media_previews 的逻辑:不要将
pm.original_url 直接用于图片预览,改用已下载内容生成的数据 URI 或受控中继地址;同时为视频媒体补充无 URL 的占位项,使
templates/entry_card.html 的视频分支能够命中。保留现有图片筛选逻辑,并确保预览地址不受订阅源直接控制。
In `@src/infrastructure/messaging/senders/onebot_sender.py`:
- Around line 124-136: 保留下载失败媒体的原始链接:在
src/infrastructure/messaging/senders/onebot_sender.py#L124-L136 的
_build_components 调用中移除显式的 failed_urls=[],并让
src/infrastructure/messaging/senders/base_sender.py#L1454-L1461 与 `#L1548-L1555`
的相关调用使用可收集失败链接的默认行为;确保 _build_components 能自动记录失败媒体 URL,避免静默丢失。
In `@src/interfaces/handlers/config.py`:
- Line 15: 统一会话配置中 message_format 的表示,修正 handle_sub_set_session 与
notification_dispatcher.py 中 _message_format_from_subscription_defaults
的不一致解析。让配置边界统一转换为同一格式,或使解析器同时支持整数 0/1/2,并对其他非法值明确拒绝,避免其静默回退为默认的合并转发。
---
Nitpick comments:
In `@src/application/services/notification_dispatcher.py`:
- Line 1254: Update the message_format fallback in the notification dispatch
construction to distinguish None from valid falsy values such as 0; use an
explicit is-not-None check so MESSAGE_FORMAT_MERGED_FORWARD is preserved and
only an absent value falls back to MESSAGE_FORMAT_DEFAULT.
In `@tests/unit/infrastructure/test_user_repository_impl.py`:
- Line 44: Update the test setup for User and existing in the repository test to
use a non-default message_format value instead of -100, then assert that the
saved domain entity and ORM record both preserve that value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: eb623294-fdd6-4bc8-9981-5449aa3d8208
📒 Files selected for processing (46)
docs/project/application.mddocs/project/architecture.mddocs/project/dispatch.mddocs/project/domain-model.mddocs/project/platforms.mddocs/usage/ai-tools.mddocs/usage/commands.mddocs/usage/configuration.mdpages/dashboard/components/overlays/main-panel.jspages/dashboard/components/overlays/user-panel.jspages/dashboard/components/pages/settings.jspages/dashboard/store/helpers.jspages/dashboard/store/modules/subscriptions.jspages/dashboard/store/modules/users.jspages/dashboard/store/state.jssrc/application/commands/get_user_settings_cmd.pysrc/application/commands/set_user_settings_cmd.pysrc/application/dto/subscription_export_record.pysrc/application/dto/user_dto.pysrc/application/llmtools/xml_push.pysrc/application/ports/message_sender.pysrc/application/services/agent_xml_push_service.pysrc/application/services/notification_dispatcher.pysrc/domain/entities/subscription.pysrc/domain/entities/user.pysrc/infrastructure/config/models/plugin_config_models.pysrc/infrastructure/config/models/runtime_settings.pysrc/infrastructure/config/settings_builder.pysrc/infrastructure/messaging/senders/base_sender.pysrc/infrastructure/messaging/senders/onebot_sender.pysrc/infrastructure/messaging/senders/provider.pysrc/infrastructure/messaging/senders/types.pysrc/infrastructure/persistence/migrations/V3_add_message_format.pysrc/infrastructure/persistence/models.pysrc/infrastructure/persistence/subscription_repository_impl.pysrc/infrastructure/persistence/user_repository_impl.pysrc/interfaces/handlers/config.pysrc/interfaces/web_api.pysrc/shared/constants.pytemplates/entry_card.htmltests/unit/application/test_llmtools.pytests/unit/application/test_notification_dispatcher_message_format.pytests/unit/infrastructure/test_database_manager.pytests/unit/infrastructure/test_onebot_sender_message_format.pytests/unit/infrastructure/test_subscription_repository_impl.pytests/unit/infrastructure/test_user_repository_impl.py
a29166b to
939bf8f
Compare
b8dfd11 to
0f2ea6e
Compare
|
@sourcery-ai summary |
|
@sourcery-ai title |
3de4c14 to
35da201
Compare
…raphy and retina quality
for more information, see https://pre-commit.ci
Closes #98
t2i推送图片示例:
为推送消息增加可配置的发送格式,解决 OneBot 平台固定使用合并转发的问题。新增
message_format选项,支持三种模式:合并转发(默认)、直发、图片(t2i 渲染)。Modifications / 改动点
配置层:
src/shared/constants.py— 新增MessageFormat枚举及MESSAGE_FORMAT_*常量和映射src/infrastructure/config/models/plugin_config_models.py—GlobalConfig新增message_format字段及序列化映射src/infrastructure/config/models/runtime_settings.py—SubscriptionDefaults新增message_format字段src/infrastructure/config/settings_builder.py— 读取message_format配置src/domain/entities/user.py、subscription.py— 实体新增message_format字段src/infrastructure/persistence/models.py— ORM 新增message_format列src/infrastructure/persistence/migrations/V3_add_message_format.py— V3 数据库迁移src/interfaces/handlers/config.py—SESSION_DEFAULT_KEYS加入message_formatsrc/application/commands/set_user_settings_cmd.py— 注册message_format为合法设置项分发层:
src/application/services/notification_dispatcher.py— 新增_resolve_message_format,在PreparedSubscriptionDispatch、send_to_session、MessageContext中贯通发送层:
src/infrastructure/messaging/senders/base_sender.py— 新增_components_to_single_chain、_send_direct、_send_as_image;send_to_user加入 IMAGE 模式分支src/infrastructure/messaging/senders/onebot_sender.py—send_to_user加入 DIRECT / IMAGE 模式分支src/infrastructure/messaging/senders/provider.py— 适配message_format在 port 和 infra 间传递src/infrastructure/messaging/senders/types.py—MessageContext新增message_format字段src/application/ports/message_sender.py— port 层MessageContext新增message_format字段Web API 与前端:
src/interfaces/web_api.py— 订阅/用户 API 响应中序列化message_formatpages/— 设置页、订阅编辑、用户编辑分别新增"消息格式"下拉选项AI 工具:
src/application/llmtools/xml_push.py—rss_push_xml_entry工具暴露message_format参数src/application/services/agent_xml_push_service.py— 解析、传递message_format模板:
templates/entry_card.html— 全新深色卡片风格 t2i 模板,修复右侧空白,提升渲染质量 (JPEG quality 40→85)文档:
docs/usage/commands.md、ai-tools.md、configuration.mddocs/project/architecture.md、application.md、dispatch.md、domain-model.md、platforms.md测试:
tests/unit/infrastructure/test_onebot_sender_message_format.py— 新增 DIRECT/IMAGE 模式测试tests/unit/application/test_notification_dispatcher_message_format.py— 新增_resolve_message_format测试This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed them with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 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./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
为推送通知新增可配置的消息格式,包括合并转发、直发和图片卡片模式,并贯穿调度、发送、持久化和配置各层实现。
New Features:
MessageFormat枚举和配置选项,用于控制按用户和按订阅的消息格式(合并转发、直发、图片)。rss_push_xml_entryAI 工具以及 Web API 和控制面板 UI 中,将message_format暴露为安全的格式化参数。Enhancements:
message_format和原始 HTML,确保在重试和代理推送过程中行为一致。message_format,支持直发单链路发送和图片卡片发送,而不是始终使用合并转发。message_format的语义与格式化流水线文档,包括各平台行为及其与send_mode的交互方式。Deployment:
V3_add_message_format数据库迁移,在用户和订阅表上添加message_format字段。Tests:
NotificationDispatcher中的message_format解析,以及 OneBot 发送器中的直发/图片模式。V3_add_message_format迁移,并验证用户和订阅表的模式期望。Original summary in English
Summary by Sourcery
Add configurable message formatting for push notifications, including merged forward, direct, and image card modes, and wire it through dispatch, sending, persistence, and configuration layers.
New Features:
Enhancements:
Deployment:
Tests:
Original summary in English
Summary by Sourcery
为推送通知新增可配置的消息格式,包括合并转发、直发和图片卡片模式,并贯穿调度、发送、持久化和配置各层实现。
New Features:
MessageFormat枚举和配置选项,用于控制按用户和按订阅的消息格式(合并转发、直发、图片)。rss_push_xml_entryAI 工具以及 Web API 和控制面板 UI 中,将message_format暴露为安全的格式化参数。Enhancements:
message_format和原始 HTML,确保在重试和代理推送过程中行为一致。message_format,支持直发单链路发送和图片卡片发送,而不是始终使用合并转发。message_format的语义与格式化流水线文档,包括各平台行为及其与send_mode的交互方式。Deployment:
V3_add_message_format数据库迁移,在用户和订阅表上添加message_format字段。Tests:
NotificationDispatcher中的message_format解析,以及 OneBot 发送器中的直发/图片模式。V3_add_message_format迁移,并验证用户和订阅表的模式期望。Original summary in English
Summary by Sourcery
Add configurable message formatting for push notifications, including merged forward, direct, and image card modes, and wire it through dispatch, sending, persistence, and configuration layers.
New Features:
Enhancements:
Deployment:
Tests:
Original summary in English
Summary by Sourcery
为推送通知新增可配置的消息格式,包括合并转发、直发和图片卡片模式,并贯穿调度、发送、持久化和配置各层实现。
New Features:
MessageFormat枚举和配置选项,用于控制按用户和按订阅的消息格式(合并转发、直发、图片)。rss_push_xml_entryAI 工具以及 Web API 和控制面板 UI 中,将message_format暴露为安全的格式化参数。Enhancements:
message_format和原始 HTML,确保在重试和代理推送过程中行为一致。message_format,支持直发单链路发送和图片卡片发送,而不是始终使用合并转发。message_format的语义与格式化流水线文档,包括各平台行为及其与send_mode的交互方式。Deployment:
V3_add_message_format数据库迁移,在用户和订阅表上添加message_format字段。Tests:
NotificationDispatcher中的message_format解析,以及 OneBot 发送器中的直发/图片模式。V3_add_message_format迁移,并验证用户和订阅表的模式期望。Original summary in English
Summary by Sourcery
Add configurable message formatting for push notifications, including merged forward, direct, and image card modes, and wire it through dispatch, sending, persistence, and configuration layers.
New Features:
Enhancements:
Deployment:
Tests:
Original summary in English
Summary by Sourcery
为推送通知新增可配置的消息格式,包括合并转发、直发和图片卡片模式,并贯穿调度、发送、持久化和配置各层实现。
New Features:
MessageFormat枚举和配置选项,用于控制按用户和按订阅的消息格式(合并转发、直发、图片)。rss_push_xml_entryAI 工具以及 Web API 和控制面板 UI 中,将message_format暴露为安全的格式化参数。Enhancements:
message_format和原始 HTML,确保在重试和代理推送过程中行为一致。message_format,支持直发单链路发送和图片卡片发送,而不是始终使用合并转发。message_format的语义与格式化流水线文档,包括各平台行为及其与send_mode的交互方式。Deployment:
V3_add_message_format数据库迁移,在用户和订阅表上添加message_format字段。Tests:
NotificationDispatcher中的message_format解析,以及 OneBot 发送器中的直发/图片模式。V3_add_message_format迁移,并验证用户和订阅表的模式期望。Original summary in English
Summary by Sourcery
Add configurable message formatting for push notifications, including merged forward, direct, and image card modes, and wire it through dispatch, sending, persistence, and configuration layers.
New Features:
Enhancements:
Deployment:
Tests:
Summary by CodeRabbit
新功能
改进
文档