From 9825f67f51568f7ef47de1bc18741c2c55eef430 Mon Sep 17 00:00:00 2001
From: IQ-Director <2498726461@qq.com>
Date: Wed, 15 Jul 2026 03:58:14 +0800
Subject: [PATCH 1/3] fix(qqofficial): render @ mentions in group messages
- serialize At components as <@openid> markup
- send mention-bearing replies and proactive messages as Markdown
- preserve payload compatibility for media and guild channel messages
- support legacy and current incoming mention formats
- add regression tests for QQ Official @ mentions
---
.../qqofficial/qqofficial_message_event.py | 14 +++--
.../qqofficial/qqofficial_platform_adapter.py | 41 +++++++++++++-
tests/test_qqofficial_group_message_create.py | 54 ++++++++++++++++++-
3 files changed, 103 insertions(+), 6 deletions(-)
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
index e80c8296a5..899c57696e 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
@@ -25,7 +25,7 @@
from astrbot.api import logger
from astrbot.api.event import AstrMessageEvent, MessageChain
-from astrbot.api.message_components import File, Image, Plain, Record, Video
+from astrbot.api.message_components import At, File, Image, Plain, Record, Video
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
from astrbot.core.utils.media_utils import MediaResolver, file_uri_to_path, is_file_uri
@@ -304,9 +304,13 @@ async def _post_send_one(
):
plain_text = plain_text + "\n"
- # 根据消息链的 use_markdown_ 标记决定发送模式
+ # QQ only resolves <@openid> mentions in Markdown messages.
+ has_mention = any(
+ isinstance(item, At) and str(item.qq) != "all"
+ for item in message_to_send.chain
+ )
use_md = getattr(self.send_buffer, "use_markdown_", None)
- if use_md is False:
+ if use_md is False and not has_mention:
payload: dict = {
"content": plain_text,
"msg_type": 0,
@@ -741,6 +745,10 @@ async def _parse_to_qqofficial(message: MessageChain):
for i in message.chain:
if isinstance(i, Plain):
plain_text += i.text
+ elif isinstance(i, At):
+ qq_id = str(getattr(i, "qq", ""))
+ if qq_id and qq_id != "all":
+ plain_text += f"<@{qq_id}>"
elif isinstance(i, Image) and not image_base64:
if not i.file:
raise ValueError("Unsupported image file format")
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
index 66d2fc3472..364ff21e05 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
@@ -379,7 +379,20 @@ async def _send_by_session_common(
)
return
- payload: dict[str, Any] = {"content": plain_text}
+ has_mention = any(
+ isinstance(item, At) and str(item.qq) != "all"
+ for item in message_chain.chain
+ )
+ use_markdown = (
+ has_mention or getattr(message_chain, "use_markdown_", None) is True
+ )
+ if use_markdown and plain_text:
+ payload: dict[str, Any] = {
+ "markdown": botpy.types.message.MarkdownPayload(content=plain_text),
+ "msg_type": 2,
+ }
+ else:
+ payload = {"content": plain_text, "msg_type": 0}
if msg_id and not allow_group_proactive_send:
payload["msg_id"] = msg_id
ret: Any = None
@@ -397,6 +410,8 @@ async def _send_by_session_common(
)
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
if record_file_path:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -407,6 +422,8 @@ async def _send_by_session_common(
if media:
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
if video_file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -417,6 +434,8 @@ async def _send_by_session_common(
if media:
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
payload.pop("msg_id", None)
if file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
@@ -429,6 +448,8 @@ async def _send_by_session_common(
if media:
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
payload.pop("msg_id", None)
ret = await self.client.api.post_group_message(
group_openid=session.session_id,
@@ -437,6 +458,7 @@ async def _send_by_session_common(
else:
if image_path:
payload["file_image"] = image_path
+ payload.pop("msg_type", None)
ret = await self.client.api.post_message(
channel_id=session.session_id,
**payload,
@@ -456,6 +478,8 @@ async def _send_by_session_common(
)
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
if record_file_path:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -466,6 +490,8 @@ async def _send_by_session_common(
if media:
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
if video_file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -476,6 +502,8 @@ async def _send_by_session_common(
if media:
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
if file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -487,6 +515,8 @@ async def _send_by_session_common(
if media:
payload["media"] = media
payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
ret = await QQOfficialMessageEvent.post_c2c_message(
send_helper, # type: ignore
@@ -782,6 +812,9 @@ async def _parse_from_qqofficial(
plain_content_raw = message.content or ""
for mention_id in bot_mention_ids:
plain_content_raw = plain_content_raw.replace(
+ f'',
+ "",
+ ).replace(
f"<@{mention_id}>",
"",
).replace(
@@ -824,6 +857,12 @@ async def _parse_from_qqofficial(
plain_content = QQOfficialPlatformAdapter._parse_face_message(
message.content.replace(
+ f'',
+ "",
+ ).replace(
+ "<@" + str(abm.self_id) + ">",
+ "",
+ ).replace(
"<@!" + str(abm.self_id) + ">",
"",
).strip()
diff --git a/tests/test_qqofficial_group_message_create.py b/tests/test_qqofficial_group_message_create.py
index ee064646a9..54cd1d1dfd 100644
--- a/tests/test_qqofficial_group_message_create.py
+++ b/tests/test_qqofficial_group_message_create.py
@@ -23,6 +23,9 @@
QQOfficialPlatformAdapter,
_ensure_group_message_create_parser,
)
+from astrbot.core.platform.sources.qqofficial.qqofficial_message_event import (
+ QQOfficialMessageEvent,
+)
from astrbot.core.platform.sources.qqofficial.qqofficial_platform_adapter import (
botClient as QQOfficialBotClient,
)
@@ -161,11 +164,17 @@ async def test_parse_group_message_create_quoted_context():
][-1] == "answer"
+@pytest.mark.parametrize(
+ "mention_markup",
+ ["<@bot-123>", "<@!bot-123>", ''],
+)
@pytest.mark.asyncio
-async def test_parse_group_message_create_bot_mention_cleans_plain_text():
+async def test_parse_group_message_create_bot_mention_cleans_plain_text(
+ mention_markup: str,
+):
_, message = _dispatch_group_message(
_make_group_payload(
- content="<@!bot-123> hello there",
+ content=f"{mention_markup} hello there",
mentions=[{"id": "bot-123", "is_you": True}],
)
)
@@ -185,6 +194,15 @@ async def test_parse_group_message_create_bot_mention_cleans_plain_text():
assert abm.group_id == "group-1"
+@pytest.mark.asyncio
+async def test_parse_to_qqofficial_preserves_at_component_order():
+ parsed = await QQOfficialMessageEvent._parse_to_qqofficial(
+ MessageChain(chain=[At(qq="member-1"), Plain(" hello"), At(qq="all")])
+ )
+
+ assert parsed[0] == "<@member-1> hello"
+
+
@pytest.mark.asyncio
async def test_legacy_group_at_path_forces_bot_mention_when_mentions_missing():
message = botpy.message.GroupMessage(
@@ -307,6 +325,38 @@ async def test_ws_group_send_by_session_with_cached_msg_id_still_omits_msg_id():
assert "msg_seq" in kwargs
+@pytest.mark.asyncio
+async def test_ws_group_send_by_session_with_at_uses_markdown():
+ adapter = QQOfficialPlatformAdapter(
+ {
+ "id": "qq-official-test",
+ "appid": "123",
+ "secret": "secret",
+ "enable_group_c2c": True,
+ "enable_guild_direct_message": False,
+ },
+ {},
+ asyncio.Queue(),
+ )
+ adapter.client.api = SimpleNamespace(
+ post_group_message=AsyncMock(return_value={"id": "sent-at"}),
+ post_message=AsyncMock(),
+ )
+ adapter._session_scene["group-1"] = "group"
+ chain = MessageChain(chain=[At(qq="member-1"), Plain(" hello")])
+ chain.use_markdown(False)
+
+ await adapter.send_by_session(
+ MessageSession("qq_official", MessageType.GROUP_MESSAGE, "group-1"),
+ chain,
+ )
+
+ kwargs = adapter.client.api.post_group_message.await_args.kwargs
+ assert kwargs["msg_type"] == 2
+ assert kwargs["markdown"]["content"] == "<@member-1> hello"
+ assert "content" not in kwargs
+
+
@pytest.mark.asyncio
async def test_webhook_group_send_by_session_without_cached_msg_id_omits_msg_id():
adapter = QQOfficialWebhookPlatformAdapter(
From 7f2f05c13d728f941df69db783d27baf9e06e5e8 Mon Sep 17 00:00:00 2001
From: IQ-Director <2498726461@qq.com>
Date: Wed, 15 Jul 2026 04:52:00 +0800
Subject: [PATCH 2/3] fix(qqofficial): address review feedback
- validate mention IDs before serialization
- centralize mention detection and media payload normalization
- consolidate incoming mention markup cleanup
- add regression tests for invalid mention IDs
---
.../qqofficial/qqofficial_message_event.py | 72 +++++++-------
.../qqofficial/qqofficial_platform_adapter.py | 94 ++++++++-----------
tests/test_qqofficial_group_message_create.py | 19 +++-
3 files changed, 90 insertions(+), 95 deletions(-)
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
index 899c57696e..68439f703d 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
@@ -196,6 +196,29 @@ def _extract_response_message_id(ret) -> str | None:
ret_id = getattr(ret, "id", None)
return str(ret_id) if ret_id is not None else None
+ @staticmethod
+ def _get_mention_id(component: At) -> str | None:
+ qq = getattr(component, "qq", None)
+ if not qq:
+ return None
+ qq_id = str(qq)
+ return qq_id if qq_id != "all" else None
+
+ @classmethod
+ def _has_mention(cls, message: MessageChain) -> bool:
+ return any(
+ isinstance(component, At)
+ and cls._get_mention_id(component) is not None
+ for component in message.chain
+ )
+
+ @staticmethod
+ def _set_media_payload(payload: dict, media: Media, plain_text: str) -> None:
+ payload["media"] = media
+ payload["msg_type"] = 7
+ payload.pop("markdown", None)
+ payload["content"] = plain_text or None
+
@staticmethod
def _split_message_chain_by_media(message: MessageChain) -> list[MessageChain]:
chunks: list[MessageChain] = []
@@ -305,10 +328,7 @@ async def _post_send_one(
plain_text = plain_text + "\n"
# QQ only resolves <@openid> mentions in Markdown messages.
- has_mention = any(
- isinstance(item, At) and str(item.qq) != "all"
- for item in message_to_send.chain
- )
+ has_mention = self._has_mention(message_to_send)
use_md = getattr(self.send_buffer, "use_markdown_", None)
if use_md is False and not has_mention:
payload: dict = {
@@ -340,10 +360,7 @@ async def _post_send_one(
self.IMAGE_FILE_TYPE,
group_openid=source.group_openid,
)
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
if record_file_path: # group record msg
media = await self.upload_group_and_c2c_media(
record_file_path,
@@ -351,10 +368,7 @@ async def _post_send_one(
group_openid=source.group_openid,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
if video_file_source:
media = await self.upload_group_and_c2c_media(
video_file_source,
@@ -362,10 +376,7 @@ async def _post_send_one(
group_openid=source.group_openid,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
if file_source:
media = await self.upload_group_and_c2c_media(
file_source,
@@ -374,10 +385,7 @@ async def _post_send_one(
group_openid=source.group_openid,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
ret = await self._send_with_markdown_fallback(
send_func=lambda retry_payload: self.bot.api.post_group_message(
group_openid=source.group_openid, # type: ignore
@@ -395,10 +403,7 @@ async def _post_send_one(
self.IMAGE_FILE_TYPE,
openid=source.author.user_openid,
)
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
if record_file_path: # c2c record
media = await self.upload_group_and_c2c_media(
record_file_path,
@@ -406,10 +411,7 @@ async def _post_send_one(
openid=source.author.user_openid,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
if video_file_source:
media = await self.upload_group_and_c2c_media(
video_file_source,
@@ -417,10 +419,7 @@ async def _post_send_one(
openid=source.author.user_openid,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
if file_source:
media = await self.upload_group_and_c2c_media(
file_source,
@@ -429,10 +428,7 @@ async def _post_send_one(
openid=source.author.user_openid,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ self._set_media_payload(payload, media, plain_text)
if stream:
ret = await self._send_with_markdown_fallback(
send_func=lambda retry_payload: self.post_c2c_message(
@@ -746,8 +742,8 @@ async def _parse_to_qqofficial(message: MessageChain):
if isinstance(i, Plain):
plain_text += i.text
elif isinstance(i, At):
- qq_id = str(getattr(i, "qq", ""))
- if qq_id and qq_id != "all":
+ qq_id = QQOfficialMessageEvent._get_mention_id(i)
+ if qq_id:
plain_text += f"<@{qq_id}>"
elif isinstance(i, Image) and not image_base64:
if not i.file:
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
index 364ff21e05..a7656c98cc 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py
@@ -379,10 +379,7 @@ async def _send_by_session_common(
)
return
- has_mention = any(
- isinstance(item, At) and str(item.qq) != "all"
- for item in message_chain.chain
- )
+ has_mention = QQOfficialMessageEvent._has_mention(message_chain)
use_markdown = (
has_mention or getattr(message_chain, "use_markdown_", None) is True
)
@@ -408,10 +405,9 @@ async def _send_by_session_common(
QQOfficialMessageEvent.IMAGE_FILE_TYPE,
group_openid=session.session_id,
)
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(
+ payload, media, plain_text
+ )
if record_file_path:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -420,10 +416,9 @@ async def _send_by_session_common(
group_openid=session.session_id,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(
+ payload, media, plain_text
+ )
if video_file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -432,10 +427,9 @@ async def _send_by_session_common(
group_openid=session.session_id,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(
+ payload, media, plain_text
+ )
payload.pop("msg_id", None)
if file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
@@ -446,10 +440,9 @@ async def _send_by_session_common(
group_openid=session.session_id,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(
+ payload, media, plain_text
+ )
payload.pop("msg_id", None)
ret = await self.client.api.post_group_message(
group_openid=session.session_id,
@@ -476,10 +469,7 @@ async def _send_by_session_common(
QQOfficialMessageEvent.IMAGE_FILE_TYPE,
openid=session.session_id,
)
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(payload, media, plain_text)
if record_file_path:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -488,10 +478,9 @@ async def _send_by_session_common(
openid=session.session_id,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(
+ payload, media, plain_text
+ )
if video_file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -500,10 +489,9 @@ async def _send_by_session_common(
openid=session.session_id,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(
+ payload, media, plain_text
+ )
if file_source:
media = await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper, # type: ignore
@@ -513,10 +501,9 @@ async def _send_by_session_common(
openid=session.session_id,
)
if media:
- payload["media"] = media
- payload["msg_type"] = 7
- payload.pop("markdown", None)
- payload["content"] = plain_text or None
+ QQOfficialMessageEvent._set_media_payload(
+ payload, media, plain_text
+ )
ret = await QQOfficialMessageEvent.post_c2c_message(
send_helper, # type: ignore
@@ -727,6 +714,17 @@ def replace_face(match):
# Match face tags:
return re.sub(r"]*>", replace_face, content)
+ @staticmethod
+ def _strip_bot_mention_markup(content: str | None, mention_id: str) -> str:
+ normalized = content or ""
+ for markup in (
+ f'',
+ f"<@{mention_id}>",
+ f"<@!{mention_id}>",
+ ):
+ normalized = normalized.replace(markup, "")
+ return normalized
+
@staticmethod
async def _parse_from_qqofficial(
message: botpy.message.Message
@@ -811,15 +809,10 @@ async def _parse_from_qqofficial(
group_mentioned = bool(bot_mention_ids) or force_group_mention
plain_content_raw = message.content or ""
for mention_id in bot_mention_ids:
- plain_content_raw = plain_content_raw.replace(
- f'',
- "",
- ).replace(
- f"<@{mention_id}>",
- "",
- ).replace(
- f"<@!{mention_id}>",
- "",
+ plain_content_raw = (
+ QQOfficialPlatformAdapter._strip_bot_mention_markup(
+ plain_content_raw, mention_id
+ )
)
abm.message_str = QQOfficialPlatformAdapter._parse_face_message(
plain_content_raw.strip()
@@ -856,15 +849,8 @@ async def _parse_from_qqofficial(
abm.self_id = ""
plain_content = QQOfficialPlatformAdapter._parse_face_message(
- message.content.replace(
- f'',
- "",
- ).replace(
- "<@" + str(abm.self_id) + ">",
- "",
- ).replace(
- "<@!" + str(abm.self_id) + ">",
- "",
+ QQOfficialPlatformAdapter._strip_bot_mention_markup(
+ message.content, str(abm.self_id)
).strip()
)
diff --git a/tests/test_qqofficial_group_message_create.py b/tests/test_qqofficial_group_message_create.py
index 54cd1d1dfd..43936e8b89 100644
--- a/tests/test_qqofficial_group_message_create.py
+++ b/tests/test_qqofficial_group_message_create.py
@@ -19,13 +19,13 @@
from astrbot.core.pipeline.result_decorate.stage import ResultDecorateStage
from astrbot.core.platform.message_session import MessageSession
from astrbot.core.platform.message_type import MessageType
+from astrbot.core.platform.sources.qqofficial.qqofficial_message_event import (
+ QQOfficialMessageEvent,
+)
from astrbot.core.platform.sources.qqofficial.qqofficial_platform_adapter import (
QQOfficialPlatformAdapter,
_ensure_group_message_create_parser,
)
-from astrbot.core.platform.sources.qqofficial.qqofficial_message_event import (
- QQOfficialMessageEvent,
-)
from astrbot.core.platform.sources.qqofficial.qqofficial_platform_adapter import (
botClient as QQOfficialBotClient,
)
@@ -203,6 +203,19 @@ async def test_parse_to_qqofficial_preserves_at_component_order():
assert parsed[0] == "<@member-1> hello"
+@pytest.mark.parametrize("qq", [None, ""])
+@pytest.mark.asyncio
+async def test_parse_to_qqofficial_ignores_empty_at_component(qq: str | None):
+ mention = At(qq="placeholder")
+ mention.qq = cast(Any, qq)
+ chain = MessageChain(chain=[mention, Plain("hello")])
+
+ parsed = await QQOfficialMessageEvent._parse_to_qqofficial(chain)
+
+ assert parsed[0] == "hello"
+ assert QQOfficialMessageEvent._has_mention(chain) is False
+
+
@pytest.mark.asyncio
async def test_legacy_group_at_path_forces_bot_mention_when_mentions_missing():
message = botpy.message.GroupMessage(
From f642a60c8fc42d9169bbcb745bf7285b5f07ef01 Mon Sep 17 00:00:00 2001
From: IQ-Director <2498726461@qq.com>
Date: Wed, 15 Jul 2026 16:32:51 +0800
Subject: [PATCH 3/3] style(qqofficial): apply Ruff formatting
---
.../platform/sources/qqofficial/qqofficial_message_event.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
index 68439f703d..abd4af6dd5 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
@@ -207,8 +207,7 @@ def _get_mention_id(component: At) -> str | None:
@classmethod
def _has_mention(cls, message: MessageChain) -> bool:
return any(
- isinstance(component, At)
- and cls._get_mention_id(component) is not None
+ isinstance(component, At) and cls._get_mention_id(component) is not None
for component in message.chain
)