Skip to content

主动榜单推送,群全量消息处理#189

Merged
Floating-Ocean merged 9 commits into
mainfrom
active-push
Jul 2, 2026
Merged

主动榜单推送,群全量消息处理#189
Floating-Ocean merged 9 commits into
mainfrom
active-push

Conversation

@Floating-Ocean

@Floating-Ocean Floating-Ocean commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • 新功能
    • 支持按配置定时主动推送“昨日总榜”,可设置多个推送目标(obot_push_to)。
  • 配置更新
    • 更新配置示例说明;启用“获取群内全部消息”需填写 role.bot_id
    • 加强对 obot_apply_to / obot_push_to 的校验,并增加一致性要求。
  • 改进/变更
    • 昨日总榜图片标题调整为 {日期} 卷王天梯榜
    • 配置重载时增加定时任务需重启 Bot 才生效的提示。
    • 群聊触发更精确:仅在提及机器人或以 / 开头时处理。

@Floating-Ocean Floating-Ocean requested a review from qwedc001 June 28, 2026 16:39
@Floating-Ocean Floating-Ocean self-assigned this Jun 28, 2026
@Floating-Ocean Floating-Ocean added the enhancement New feature or request label Jun 28, 2026
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

新增定时任务注册与激活链路,扩展 RobotMessage 支持主动消息场景,并在 peeper 中加入 obot_push_to 配置和每日 0:30 的昨日榜单自动推送;robot.py 统一切换为新的调度器入口。

Changes

定时主动消息调度框架与榜单推送

Layer / File(s) Summary
ScheduledJobInfo 数据结构与 scheduled 装饰器
src/core/bot/decorator.py
新增 ScheduledJobInfo__scheduled_jobs__parse_uuidscheduled,用于登记纯定时任务或带目标的定时任务。
RobotMessage 主动消息支持
src/core/bot/message.py
新增 _active 标记、is_active() 和四个主动初始化方法,并在发送参数、上传、分发和回退路径中适配主动场景。
activate_scheduled_jobs 激活与执行包装
src/core/bot/transit.py, pyproject.toml
新增任务包装器与 activate_scheduled_jobs,将已登记任务注册为 CronTrigger 作业并扩展群聊公开消息过滤;同时更新调度相关依赖与来源锁定。
robot.py 调度器启动与接入
robot.py
将原每日调度器替换为统一的 tasks_sched,并在启动与 on_ready 中接入任务激活;同步调整重载提示与群消息路由。
peeper 榜单定时推送业务
src/module/cp/peeper.py, config_example.json
新增 obot_push_to 配置校验与示例,新增目标收集和昨日榜单主动推送,调整每日更新入口与标题生成。
entry.py 导入格式调整
entry.py
导入语句改为多行续行格式,语义不变。

Sequence Diagram(s)

sequenceDiagram
  participant robot.py
  participant activate_scheduled_jobs
  participant RobotMessage
  participant peeper.py
  participant API
  robot.py->>activate_scheduled_jobs: on_ready 激活已注册任务
  activate_scheduled_jobs->>RobotMessage: setup_active_*_message(loop, target)
  activate_scheduled_jobs->>peeper.py: 注册定时回调
  peeper.py->>API: send_yesterday_board(message)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Floating-Ocean/OBot-ACM#21: 同样修改了 src/core/bot/transit.py 的消息分发与过滤逻辑,和本 PR 的群聊公开消息判断有直接代码关联。
  • Floating-Ocean/OBot-ACM#36: 同样涉及 src/module/cp/peeper.py 的配置语义与校验路径,与本 PR 新增的 obot_push_to 及推送逻辑直接相连。

Suggested labels

dependencies

Suggested reviewers

  • qwedc001

Poem

🐇 零点半,风轻轻吹,
兔子推榜入星辉。
_active 一开消息跑,
CronTrigger 也不累。
昨日卷王排成队,
叮咚一声到门扉。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 标题准确概括了本次变更的两项主要内容:主动榜单推送和群全量消息处理。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch active-push

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 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 `@config_example.json`:
- Around line 39-44: The example config placeholders for obot_apply_to and
obot_push_to are inconsistent with the required scene ID prefix contract. Update
the sample values in config_example.json to use valid prefixed IDs such as
guild_, direct_, group_, or c2c_, and make sure the surrounding comments for
obot_apply_to, obot_push_to, and obot_is_private still accurately describe the
configuration semantics.

In `@robot.py`:
- Around line 164-165: The scheduler thread started in robot.py via
threading.Thread(target=tasks_sched_thread, args=[]) should not keep the process
alive after client.run() exits or fails. Update that thread setup to give it a
clear name and set daemon=True so it won’t block shutdown, and keep the
scheduling logic in tasks_sched_thread/activate_scheduled_jobs unchanged; you
can add a later shutdown hook if needed.

In `@src/core/bot/decorator.py`:
- Around line 159-165: `get_scheduled_jobs_info` currently returns `job.target`
as-is, but the declared tuple type expects a string and `sorted(result)` can
fail when `None` and string targets mix. Normalize `job.target` inside
`get_scheduled_jobs_info` before appending to `result` by converting missing
targets to a consistent string value, and keep the returned tuple shape aligned
with the annotation so `sorted` only compares comparable values.
- Around line 86-88: The uuid prefix parsing in the target-id extraction logic
can return an empty target_id for values like guild_ or group_, which then
passes validation as if it were valid. Update the prefix-handling branch in the
uuid parsing helper in decorator.py so that it rejects empty suffixes and only
returns when the part after the prefix is non-empty; otherwise continue to the
normal invalid-UUID path. Use the existing uuid_prefix_map matching flow and the
target-id validation logic to ensure decorated task registration cannot proceed
with an empty target_id.

In `@src/core/bot/transit.py`:
- Line 8: Add the missing APScheduler dependency so the top-level CronTrigger
import in transit.py can resolve at startup. Update the project dependency
declaration in pyproject.toml to include APScheduler (matching the import used
by CronTrigger), and keep the import in src/core/bot/transit.py unchanged so the
bot no longer fails during module import.

In `@src/module/cp/peeper.py`:
- Around line 227-240: The scheduled push path in push_yesterday_board currently
loses the mapping between obot_push_to and the configured board because
_collect_push_targets() returns only UUIDs and send_yesterday_board() falls back
to message.uuid lookup in obot_apply_to. Update the peeper scheduling flow so
the target collection preserves the specific board configuration per UUID, and
make push_yesterday_board invoke that binding-aware path instead of blindly
reusing send_yesterday_board. Use _collect_push_targets, push_yesterday_board,
and send_yesterday_board as the main symbols to locate the fix.
- Around line 87-93: The UUID parsing path in the peeper config validation
currently allows an empty target ID when parse_uuid("group_") passes the prefix
check, so add an explicit check after parsing to reject empty target values
before continuing the loop over peeper.configs. Also update the RuntimeError
raised in this validation block to preserve the original ValueError using
exception chaining with from e, so the failure in the parse_uuid handling
remains traceable.
🪄 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: 827b2739-7971-4565-b2fd-19dc974ac1fc

📥 Commits

Reviewing files that changed from the base of the PR and between ccd7331 and 5ce6ecc.

📒 Files selected for processing (7)
  • config_example.json
  • entry.py
  • robot.py
  • src/core/bot/decorator.py
  • src/core/bot/message.py
  • src/core/bot/transit.py
  • src/module/cp/peeper.py

Comment thread config_example.json
Comment thread robot.py
Comment thread src/core/bot/decorator.py
Comment thread src/core/bot/decorator.py Outdated
Comment thread src/core/bot/transit.py
Comment thread src/module/cp/peeper.py Outdated
Comment thread src/module/cp/peeper.py
@Floating-Ocean

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/core/bot/transit.py (1)

244-251: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

避免不同定时任务使用同一个 job_id 被覆盖。

Line 249 只拼接了模块名、函数名和去前缀后的 target;同一函数注册多个纯定时任务,或不同 MessageType 恰好有相同目标 ID 时,Line 250 的 replace_existing=True 会静默替换前一个任务。

建议修复
-    for module_name, jobs in __scheduled_jobs__.items():
-        for job in jobs:
+    for module_name, jobs in __scheduled_jobs__.items():
+        for job_idx, job in enumerate(jobs):
             wrapper = _make_scheduled_wrapper(
                 job.func, job.message_type, job.target, api, loop)
             trigger = CronTrigger.from_crontab(job.cron)
-            job_id = f"sched.{module_name}.{job.func.__name__}.{job.target or 'task'}"
+            message_type_name = job.message_type.name.lower() if job.message_type else "task"
+            job_id = (
+                f"sched.{module_name}.{job.func.__name__}."
+                f"{job_idx}.{message_type_name}.{job.target or 'none'}"
+            )
             scheduler.add_job(wrapper, trigger=trigger, id=job_id,
                               replace_existing=True)
🤖 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/core/bot/transit.py` around lines 244 - 251, The scheduled job identifier
in transit.py is not unique enough, so distinct jobs can overwrite each other
when scheduler.add_job is called with replace_existing=True. Update the job_id
generation in the scheduling loop around _make_scheduled_wrapper and
CronTrigger.from_crontab to include additional discriminators such as
job.message_type and any target-specific identifier so each scheduled entry gets
a stable unique id. Keep the change localized to the job registration logic in
the __scheduled_jobs__ iteration.
src/core/bot/message.py (1)

274-280: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

不要把 msg_id=None 透传到请求体
_handle_send_request 只按 key 组装参数,msg_id 一旦写进 params 就会随 requests.post(..., json=payload) 发出并变成 null。主动/回退消息如果接口要求省略 msg_id,这里应只在回复场景写入该字段。

🤖 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/core/bot/message.py` around lines 274 - 280, 在 _pack_message_params
中,base_params 现在会无条件把 msg_id 写入请求参数并在 self._active 为真时变成 None,这会让
_handle_send_request 最终通过 requests.post 的 json payload 发送 null。请改成只在回复场景(例如
self._active 为 False 且确实有 message.id 时)才加入 msg_id,主动发送或回退场景直接省略该键;同时检查
_handle_send_request 的参数组装逻辑,确保不会把不存在的 msg_id 补成 null。
🤖 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.

Outside diff comments:
In `@src/core/bot/message.py`:
- Around line 274-280: 在 _pack_message_params 中,base_params 现在会无条件把 msg_id
写入请求参数并在 self._active 为真时变成 None,这会让 _handle_send_request 最终通过 requests.post 的
json payload 发送 null。请改成只在回复场景(例如 self._active 为 False 且确实有 message.id 时)才加入
msg_id,主动发送或回退场景直接省略该键;同时检查 _handle_send_request 的参数组装逻辑,确保不会把不存在的 msg_id 补成
null。

In `@src/core/bot/transit.py`:
- Around line 244-251: The scheduled job identifier in transit.py is not unique
enough, so distinct jobs can overwrite each other when scheduler.add_job is
called with replace_existing=True. Update the job_id generation in the
scheduling loop around _make_scheduled_wrapper and CronTrigger.from_crontab to
include additional discriminators such as job.message_type and any
target-specific identifier so each scheduled entry gets a stable unique id. Keep
the change localized to the job registration logic in the __scheduled_jobs__
iteration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9f1b74ff-1135-4b6d-909a-0aa402fbd689

📥 Commits

Reviewing files that changed from the base of the PR and between b8602f2 and ce6de9e.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • config_example.json
  • pyproject.toml
  • robot.py
  • src/core/bot/message.py
  • src/core/bot/transit.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml

Comment thread src/core/bot/decorator.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/bot/decorator.py (1)

108-122: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

在注册阶段校验 no_targettargets 和函数签名的一致性。

这里现在会静默接受几种无效组合:no_target=True 时传了 targets 会被直接忽略,普通模式下空 targets 会悄悄注册 0 个任务;而 transit.py 下游会分别按 func() / func(packed_message) 调用,签名写错也要等到定时任务真正触发才会暴露。建议在装饰阶段直接拒绝这些组合,尽早把配置错误拦住。

建议修复
 def scheduled(cron: str, targets: list[str], no_target: bool = False):
@@
     def decorator(func):
         module_name = func.__module__ or "default.unknown"
+        signature = inspect.signature(func)
 
         __scheduled_jobs__.setdefault(module_name, [])
         if no_target:
+            if targets:
+                raise ValueError("Pure scheduled jobs must not define targets")
+            try:
+                signature.bind()
+            except TypeError as e:
+                raise ValueError(
+                    f"Pure scheduled job {func.__name__} must not require a message parameter"
+                ) from e
             # 纯定时任务,不传递 message 参数
             __scheduled_jobs__[module_name].append(
                 ScheduledJobInfo(func=func, cron=cron, module_name=module_name))
         else:
+            if not targets:
+                raise ValueError("Message scheduled jobs require at least one target")
+            try:
+                signature.bind(object())
+            except TypeError as e:
+                raise ValueError(
+                    f"Scheduled job {func.__name__} must accept exactly one message argument"
+                ) from e
             for uuid in targets:
                 message_type, target_id = parse_uuid(uuid)
                 __scheduled_jobs__[module_name].append(
                     ScheduledJobInfo(func=func, cron=cron,
                                      message_type=message_type, target=target_id,
                                      module_name=module_name))
import inspect
🤖 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/core/bot/decorator.py` around lines 108 - 122, The decorator registration
logic in decorator(func) should validate the relationship between no_target,
targets, and the wrapped function signature instead of silently accepting
invalid combinations. Add an early check that rejects no_target=True with any
targets, rejects the normal path when targets is empty, and uses
inspect.signature on func to ensure the callable matches the expected call style
for transit.py (func() versus func(packed_message)). Fail fast with a clear
exception before appending to __scheduled_jobs__.
🤖 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.

Outside diff comments:
In `@src/core/bot/decorator.py`:
- Around line 108-122: The decorator registration logic in decorator(func)
should validate the relationship between no_target, targets, and the wrapped
function signature instead of silently accepting invalid combinations. Add an
early check that rejects no_target=True with any targets, rejects the normal
path when targets is empty, and uses inspect.signature on func to ensure the
callable matches the expected call style for transit.py (func() versus
func(packed_message)). Fail fast with a clear exception before appending to
__scheduled_jobs__.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e2ea796-1a49-4a02-854e-5eca1526143c

📥 Commits

Reviewing files that changed from the base of the PR and between ce6de9e and d0cca1d.

📒 Files selected for processing (3)
  • src/core/bot/decorator.py
  • src/core/bot/message.py
  • src/module/cp/peeper.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/module/cp/peeper.py
  • src/core/bot/message.py

@Floating-Ocean Floating-Ocean changed the title 主动榜单推送 主动榜单推送,群全量消息处理 Jun 30, 2026
@Floating-Ocean Floating-Ocean merged commit daf9707 into main Jul 2, 2026
3 checks passed
@Floating-Ocean Floating-Ocean deleted the active-push branch July 2, 2026 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants