Skip to content

Feat: qqofficial webhook raw event bypass#6691

Open
KBVsent wants to merge 6 commits intoAstrBotDevs:masterfrom
KBVsent:feat/qqofficial-webhook-raw-event-bypass
Open

Feat: qqofficial webhook raw event bypass#6691
KBVsent wants to merge 6 commits intoAstrBotDevs:masterfrom
KBVsent:feat/qqofficial-webhook-raw-event-bypass

Conversation

@KBVsent
Copy link
Contributor

@KBVsent KBVsent commented Mar 20, 2026

关联issue: #6304
QQ Official平台支持推送多种事件,包括Bot被拉入群,退群等事件,目前关于这部分事件是直接丢弃的状态,插件也没有办法直接获取到这部分时间,因此本pr提供一个原始事件钩子,允许插件读取原始事件来根据平台特殊事件类型进行业务逻辑操作。

Modifications / 改动点

  • raw_platform_event.py:新增 RawPlatformEvent,提供平台元信息访问、extra 数据存取、stop_event/continue_event 传播控制。
  • platform.py:新增 emit_raw_platform_event(...);接入全局配置并在未显式传参时应用 plugin_set 过滤。
  • context_utils.py:新增 call_raw_platform_event_hook(...),支持按 platform_name/platform_id/event_type 过滤并处理停止传播语义。
  • star_handler.py:新增 register_on_raw_platform_event(...) 注册器。
  • star_handler.py:新增 EventType.OnRawPlatformEvent 及对应 registry 类型声明。
  • qo_webhook_server.py:在正常 dispatch 前先发射 raw-event;若被 stop,则跳过后续 dispatch。
  • qo_webhook_adapter.py:向 webhook server 注入 platform 实例以支持 raw-event 发射。
  • manager.py:为平台实例注入 _astrbot_config,保证 raw-event 路径可读取 plugin_set
  • test_raw_platform_event.py:新增单测,覆盖 hook 调用、stop 拦截、过滤条件匹配与 plugin_set 行为。
  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

  • uv run pytest test_raw_platform_event.py -q
    结果:10 passed, 3 warnings in 1.58s
  • 同时进行了广泛的插件使用测试,测试实现了QQ Bot的邀请数据统计,以及事件统计等功能,以及在我的实际环境中运行超过一周,功能正常。

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it 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 and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Add framework-level support for emitting and handling raw platform events, and wire QQ Official webhook events into this new hook with optional plugin-based filtering and propagation control.

New Features:

  • Introduce RawPlatformEvent abstraction to represent platform-native payloads with metadata, extras storage, and propagation control.
  • Expose a new OnRawPlatformEvent hook and registration API for plugins to subscribe to raw platform events, with filtering by platform name, platform id, and event type.
  • Enable platforms to emit raw platform events via Platform.emit_raw_platform_event, automatically applying plugin_set filtering from global configuration when not provided explicitly.
  • Integrate QQ Official webhook events with the raw platform event pipeline so plugins can observe/short-circuit incoming webhook payloads before normal dispatch.

Enhancements:

  • Inject global astrbot configuration into platform instances to support plugin_set-based filtering for raw platform events.
  • Extend public API modules to re-export RawPlatformEvent and the new raw platform event registration utilities for plugin developers.

Tests:

  • Add unit tests covering raw platform event hook dispatch, propagation stopping semantics, filtering behavior, and plugin_set-driven plugin scoping for raw platform events.

@auto-assign auto-assign bot requested review from Fridemn and LIghtJUNction March 20, 2026 10:33
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Mar 20, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在增强框架的事件处理能力,允许插件直接访问和处理来自平台(特别是QQ官方平台)的原始事件,这些事件在过去可能被框架忽略。通过引入原始事件钩子和传播控制机制,插件现在可以根据这些底层事件实现更复杂的业务逻辑,例如统计Bot的入群退群情况或处理其他特定平台事件,从而提升了框架的灵活性和可扩展性。

Highlights

  • 新增原始平台事件钩子: 引入了 RawPlatformEvent 类,允许插件直接访问和处理平台推送的原始事件,包括之前被丢弃的非消息事件(如Bot入群、退群等)。
  • 事件传播控制与过滤: 提供了 stop_event()continue_event() 方法来控制原始事件的传播,并且在 call_raw_platform_event_hook 中支持按 platform_nameplatform_idevent_type 进行过滤。
  • 统一配置接入: 在 Platform 实例中注入了全局配置 _astrbot_config,使得 emit_raw_platform_event 方法能够根据 plugin_set 配置对事件进行过滤,确保只有授权的插件能够接收到原始事件。
  • QQ官方Webhook集成: QQ官方Webhook适配器现在会在正常事件分发之前发射原始平台事件。如果原始事件被 stop_event() 拦截,则会跳过后续的正常事件处理流程。
  • 新增注册器与事件类型: 添加了 register_on_raw_platform_event 注册器和 EventType.OnRawPlatformEvent,方便插件开发者声明和使用原始事件处理器。
  • 完善的单元测试: 新增了针对 RawPlatformEvent 的单元测试,覆盖了钩子调用、停止传播拦截、过滤条件匹配以及 plugin_set 行为等关键场景。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. feature:plugin The bug / feature is about AstrBot plugin system. labels Mar 20, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In QQOfficialWebhook.handle_callback, the raw platform event is emitted before the validation branch runs, which means plugins can observe and react to unauthenticated/invalid webhook payloads; consider moving emit_raw_platform_event after successful validation (and any signature checks) so hooks only see trusted events.
  • The _astrbot_config field on Platform is injected and mutated externally in PlatformManager and on webchat_inst; it would be cleaner and less error-prone to pass this via the constructor or a dedicated setter instead of relying on external writes to a protected attribute.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `QQOfficialWebhook.handle_callback`, the raw platform event is emitted before the validation branch runs, which means plugins can observe and react to unauthenticated/invalid webhook payloads; consider moving `emit_raw_platform_event` after successful validation (and any signature checks) so hooks only see trusted events.
- The `_astrbot_config` field on `Platform` is injected and mutated externally in `PlatformManager` and on `webchat_inst`; it would be cleaner and less error-prone to pass this via the constructor or a dedicated setter instead of relying on external writes to a protected attribute.

## Individual Comments

### Comment 1
<location path="astrbot/core/pipeline/context_utils.py" line_range="134-141" />
<code_context>
+        if raw_event_type and raw_event_type != event.event_type:
+            continue
+
+        try:
+            assert inspect.iscoroutinefunction(handler.handler)
+            logger.debug(
+                f"hook({hook_type.name}) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
+            )
+            await handler.handler(event)
+        except BaseException:
+            logger.error(traceback.format_exc())
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Catching `BaseException` is very broad; consider narrowing this to `Exception` unless you explicitly need to handle system-exiting errors.

This `except BaseException` will also catch `KeyboardInterrupt`, `SystemExit`, and other system-level exceptions, potentially masking shutdown signals or critical failures from plugin code. Unless you truly need to intercept those, prefer `except Exception:` here so those signals can still propagate.

```suggestion
        try:
            assert inspect.iscoroutinefunction(handler.handler)
            logger.debug(
                f"hook({hook_type.name}) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}",
            )
            await handler.handler(event)
        except Exception:
            logger.error(traceback.format_exc())
```
</issue_to_address>

### Comment 2
<location path="tests/unit/test_raw_platform_event.py" line_range="245-246" />
<code_context>
+        assert result is False
+
+
+class TestEmitRawPlatformEventPluginSet:
+    """Tests for Platform.emit_raw_platform_event plugin_set filtering."""
+
</code_context>
<issue_to_address>
**suggestion (testing):** Assert that emit_raw_platform_event propagates and returns the underlying hook result, not just adjusts plugins_name.

The current tests cover `plugins_name` behavior well, but they don’t verify that `Platform.emit_raw_platform_event` returns the boolean from `call_raw_platform_event_hook`. Since callers (e.g. the QQ webhook server) rely on this return value to decide whether to short‑circuit dispatch, please add a test that mocks `call_raw_platform_event_hook` to return `True` and asserts `await platform.emit_raw_platform_event(...)` is `True` (and optionally another for `False`). This will confirm the stop‑propagation semantics are preserved end‑to‑end.

```suggestion
class TestEmitRawPlatformEventPluginSet:
    """Tests for Platform.emit_raw_platform_event plugin_set filtering."""

    @pytest.mark.asyncio
    @patch("astrbot.core.platform.platform.call_raw_platform_event_hook", new_callable=AsyncMock)
    async def test_emit_raw_platform_event_propagates_true(self, mock_call_raw_platform_event_hook, platform):
        """emit_raw_platform_event should propagate True from call_raw_platform_event_hook."""
        mock_call_raw_platform_event_hook.return_value = True

        event = RawPlatformEvent(
            platform="qq",
            event_type="test_event",
            raw_payload={"key": "value"},
        )

        result = await platform.emit_raw_platform_event(event)

        assert result is True
        mock_call_raw_platform_event_hook.assert_awaited_once_with(event)

    @pytest.mark.asyncio
    @patch("astrbot.core.platform.platform.call_raw_platform_event_hook", new_callable=AsyncMock)
    async def test_emit_raw_platform_event_propagates_false(self, mock_call_raw_platform_event_hook, platform):
        """emit_raw_platform_event should propagate False from call_raw_platform_event_hook."""
        mock_call_raw_platform_event_hook.return_value = False

        event = RawPlatformEvent(
            platform="qq",
            event_type="test_event",
            raw_payload={"key": "value"},
        )

        result = await platform.emit_raw_platform_event(event)

        assert result is False
        mock_call_raw_platform_event_hook.assert_awaited_once_with(event)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

本次 PR 新增了对平台原始事件的支持,允许插件通过 on_raw_platform_event 钩子来处理之前无法获取的平台原生事件,例如 QQ 官方平台的机器人入群/退群事件。整体实现非常清晰,包括了 RawPlatformEvent 事件类、事件分发和过滤逻辑,以及相应的注册器和单元测试,覆盖了新功能的主要方面。

代码质量很高,只有一处关于过滤器逻辑的健壮性建议,以处理空字符串作为过滤值时的边界情况。除此之外,新功能的设计和实现都非常出色。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. feature:plugin The bug / feature is about AstrBot plugin system. size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant