Skip to content

feat: support QQ official(Websockets only) button interactions #9355

Open
kamicry wants to merge 6 commits into
AstrBotDevs:masterfrom
kamicry:feat/qqofficial-button-interactions
Open

feat: support QQ official(Websockets only) button interactions #9355
kamicry wants to merge 6 commits into
AstrBotDevs:masterfrom
kamicry:feat/qqofficial-button-interactions

Conversation

@kamicry

@kamicry kamicry commented Jul 22, 2026

Copy link
Copy Markdown

Summary

  • enable the QQ Official WebSocket interaction intent and forward INTERACTION_CREATE events
  • add @filter.on_qqofficial_interaction() for plugins to return QQ acknowledgement codes
  • acknowledge unhandled, invalid, and failed callbacks without entering the message or LLM pipeline
  • document the plugin hook and add QQ Official adapter coverage
  • scope: this PR supports the QQ Official WebSocket adapter only; the QQ Official Webhook adapter is not supported

中文说明

  • 启用 QQ 官方 WebSocket 的 interaction intent,并转发 INTERACTION_CREATE 事件。
  • 新增 @filter.on_qqofficial_interaction(),插件可通过它返回 QQ 回执状态码。
  • 未处理、返回值无效或处理失败的回调都会得到回执,且不会进入普通消息、命令或 LLM 管线。
  • 补充插件钩子开发文档,并增加 QQ 官方适配器测试覆盖。
  • 支持范围:本 PR 仅适配 QQ 官方 WebSocket 适配器,暂不支持 QQ 官方 Webhook 适配器。

Backward Compatibility / 向后兼容性

  • Existing QQ Official message and command plugins require no code changes. / 现有 QQ 官方消息和命令插件无需修改代码。
  • @filter.on_qqofficial_interaction() is opt-in and handles QQ Official WebSocket action.type = 1 callbacks only. / @filter.on_qqofficial_interaction() 为可选钩子,仅处理 QQ 官方 WebSocket 的 action.type = 1 回调。
  • Normal message, command, and LLM pipelines remain unchanged. / 普通消息、命令和 LLM 管线保持不变。
  • QQ Official Webhook behavior remains unchanged and is not supported by this feature. / QQ 官方 Webhook 行为保持不变,本功能暂不支持该适配器。

Fixes #9208

Validation

  • uv run ruff format --check .
  • uv run ruff check .
  • uv run pytest tests/test_qqofficial_group_message_create.py -q (18 passed; existing audioop and aiosqlite shutdown warnings)

中文验证

  • uv run ruff format --check .
  • uv run ruff check .
  • uv run pytest tests/test_qqofficial_group_message_create.py -q(18 项通过;存在既有的 audioop 和 aiosqlite 关闭警告,但不影响测试结果)

Summary by Sourcery

Add QQ Official WebSocket button interaction handling and expose a plugin hook for acknowledging interactions with QQ-specific result codes.

New Features:

  • Enable QQ Official WebSocket interaction intent and forward interaction events from the bot client to the platform adapter.
  • Introduce a new event type and plugin hook to handle QQ Official callback-button interactions and return QQ acknowledgement codes without entering the normal message or LLM pipeline.

Enhancements:

  • Ensure QQ Official interactions are always acknowledged with an appropriate result code, including logging and safe handling of invalid results or missing IDs.

Documentation:

  • Document the QQ Official interaction plugin hook and acknowledgement codes in both English and Chinese developer guides.

Tests:

  • Extend QQ Official WebSocket tests to cover interaction intent enabling, event forwarding, and interaction dispatch acknowledgement behavior.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

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:

  • Consider extracting the QQ interaction result codes (0–5) into named constants or an Enum that can be shared between the platform adapter and plugin docs to avoid magic numbers and keep behavior consistent if codes ever change.
  • The QQOfficialPlatformAdapter construction pattern is repeated across the new tests; consider pulling it into a small helper/factory within the test module to reduce duplication and make future changes to the adapter setup easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider extracting the QQ interaction result codes (0–5) into named constants or an Enum that can be shared between the platform adapter and plugin docs to avoid magic numbers and keep behavior consistent if codes ever change.
- The QQOfficialPlatformAdapter construction pattern is repeated across the new tests; consider pulling it into a small helper/factory within the test module to reduce duplication and make future changes to the adapter setup easier.

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py" line_range="349-353" />
<code_context>
+                    continue
+                if result is None:
+                    continue
+                if type(result) is int and 0 <= result <= 5:
+                    result_code = result
+                    break
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The strict `type(result) is int` check may reject valid integer-like return values.

This strict type check excludes valid `int` subclasses (e.g., `numpy.int64`) that behave like integers. To accept integer-like values while still excluding booleans, consider `isinstance(result, int) and not isinstance(result, bool)` instead.

```suggestion
                if result is None:
                    continue
                if isinstance(result, int) and not isinstance(result, bool) and 0 <= result <= 5:
                    result_code = result
                    break
```
</issue_to_address>

### Comment 2
<location path="astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py" line_range="331" />
<code_context>

         self.test_mode = os.environ.get("TEST_MODE", "off") == "on"

+    async def dispatch_interaction(self, interaction: Any) -> None:
+        """Dispatch a QQ Official callback-button interaction and acknowledge it.
+
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring `dispatch_interaction` into smaller helper methods for result normalization, handler execution, and interaction acknowledgment to simplify control flow and logging.

You can keep the feature exactly as-is while slimming `dispatch_interaction` by extracting the responsibilities the reviewer mentioned.

For example, you can:

1. Extract result normalization.
2. Extract a “run handlers” helper.
3. Extract an “acknowledge interaction” helper.

That flattens control flow, removes nested `try`/`except`, and isolates logging.

```python
# helpers

def _normalize_result_code(self, result: Any, default: int) -> int:
    if result is None:
        return default
    if type(result) is int and 0 <= result <= 5:
        return result
    logger.warning(
        f"QQ Official interaction handler returned an invalid result code: {result!r}"
    )
    return default


async def _run_interaction_handlers(self, interaction: Any) -> int:
    result_code = 1  # default
    for handler in star_handlers_registry.get_handlers_by_event_type(
        EventType.OnQQOfficialInteractionEvent
    ):
        try:
            result = await handler.handler(interaction)
        except Exception:
            logger.exception(
                f"QQ Official interaction handler failed: {handler.handler_full_name}"
            )
            continue

        # normalize and maybe override result_code
        new_code = self._normalize_result_code(result, result_code)
        if new_code != result_code:
            result_code = new_code
            break

    return result_code


async def _acknowledge_interaction(self, interaction: Any, result_code: int) -> None:
    interaction_id = str(getattr(interaction, "id", ""))
    if not interaction_id:
        logger.warning(
            "QQ Official interaction has no id; cannot acknowledge it."
        )
        return
    try:
        await self.client.api.on_interaction_result(interaction_id, result_code)
    except Exception:
        logger.exception(
            f"Failed to acknowledge QQ Official interaction: {interaction_id}"
        )
```

Then `dispatch_interaction` becomes orchestration-only:

```python
async def dispatch_interaction(self, interaction: Any) -> None:
    """Dispatch a QQ Official callback-button interaction and acknowledge it."""
    result_code = await self._run_interaction_handlers(interaction)
    await self._acknowledge_interaction(interaction, result_code)
```

Behavior remains the same:

- All handlers are called in order.
- Exceptions in handlers are logged and skipped.
- Only int result codes in [0, 5] override the default; invalid codes are warned.
- Interaction is always acknowledged (or logged if it cannot be).
</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.

Comment thread astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py Outdated
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. 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 Jul 22, 2026
@kamicry
kamicry marked this pull request as draft July 22, 2026 18:17
@kamicry kamicry changed the title feat: support QQ official button interactions feat: support QQ official(Websockets) button interactions Jul 22, 2026
@kamicry kamicry changed the title feat: support QQ official(Websockets) button interactions feat: support QQ official(Websockets only) button interactions Jul 22, 2026
@kamicry
kamicry marked this pull request as ready for review July 22, 2026 20:28
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The new WebSocket interaction tests are mixed into test_qqofficial_group_message_create.py; consider moving the interaction-specific helpers and cases into a dedicated test_qqofficial_interaction.py to keep message and interaction behavior isolated and easier to maintain.
  • In _normalize_interaction_result_code, logging a warning for every invalid plugin return may become noisy in production; you might want to downgrade this to info/debug or include some rate limiting to avoid log spam from misbehaving plugins.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new WebSocket interaction tests are mixed into `test_qqofficial_group_message_create.py`; consider moving the interaction-specific helpers and cases into a dedicated `test_qqofficial_interaction.py` to keep message and interaction behavior isolated and easier to maintain.
- In `_normalize_interaction_result_code`, logging a warning for every invalid plugin return may become noisy in production; you might want to downgrade this to `info`/`debug` or include some rate limiting to avoid log spam from misbehaving plugins.

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py" line_range="394" />
<code_context>
+                    f"QQ Official interaction handler failed: {handler.handler_full_name}"
+                )
+                continue
+            normalized_result = self._normalize_interaction_result_code(result)
+            if normalized_result is not None:
+                result_code = normalized_result
</code_context>
<issue_to_address>
**suggestion:** Include handler identity when logging invalid interaction result codes for easier debugging.

When `_normalize_interaction_result_code` logs an invalid result code, the log entry doesn’t indicate which handler produced it. Since `dispatch_interaction` has `handler.handler_full_name`, either pass the handler name into `_normalize_interaction_result_code` or emit the warning here so the log includes both the invalid value and the handler identity for easier debugging of misbehaving plugins.
</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.

Comment thread astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py Outdated
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:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]增加对QQ官方机器人 (ws) 回调按钮事件的响应

2 participants