-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat: update QQOfficialPlatformAdapter to support async parsing and attachment preparation #7007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |||||
| import os | ||||||
| import random | ||||||
| import time | ||||||
| import uuid | ||||||
| from pathlib import Path | ||||||
| from types import SimpleNamespace | ||||||
| from typing import Any, cast | ||||||
|
|
||||||
|
|
@@ -24,6 +26,8 @@ | |||||
| ) | ||||||
| from astrbot.core.message.components import BaseMessageComponent | ||||||
| from astrbot.core.platform.astr_message_event import MessageSesion | ||||||
| from astrbot.core.utils.astrbot_path import get_astrbot_temp_path | ||||||
| from astrbot.core.utils.io import download_file | ||||||
|
|
||||||
| from ...register import register_platform_adapter | ||||||
| from .qqofficial_message_event import QQOfficialMessageEvent | ||||||
|
|
@@ -42,7 +46,7 @@ def set_platform(self, platform: QQOfficialPlatformAdapter) -> None: | |||||
| async def on_group_at_message_create( | ||||||
| self, message: botpy.message.GroupMessage | ||||||
| ) -> None: | ||||||
| abm = QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| abm = await QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| message, | ||||||
| MessageType.GROUP_MESSAGE, | ||||||
| ) | ||||||
|
|
@@ -53,7 +57,7 @@ async def on_group_at_message_create( | |||||
|
|
||||||
| # 收到频道消息 | ||||||
| async def on_at_message_create(self, message: botpy.message.Message) -> None: | ||||||
| abm = QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| abm = await QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| message, | ||||||
| MessageType.GROUP_MESSAGE, | ||||||
| ) | ||||||
|
|
@@ -66,7 +70,7 @@ async def on_at_message_create(self, message: botpy.message.Message) -> None: | |||||
| async def on_direct_message_create( | ||||||
| self, message: botpy.message.DirectMessage | ||||||
| ) -> None: | ||||||
| abm = QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| abm = await QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| message, | ||||||
| MessageType.FRIEND_MESSAGE, | ||||||
| ) | ||||||
|
|
@@ -76,7 +80,7 @@ async def on_direct_message_create( | |||||
|
|
||||||
| # 收到 C2C 消息 | ||||||
| async def on_c2c_message_create(self, message: botpy.message.C2CMessage) -> None: | ||||||
| abm = QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| abm = await QQOfficialPlatformAdapter._parse_from_qqofficial( | ||||||
| message, | ||||||
| MessageType.FRIEND_MESSAGE, | ||||||
| ) | ||||||
|
|
@@ -336,7 +340,22 @@ def _normalize_attachment_url(url: str | None) -> str: | |||||
| return f"https://{url}" | ||||||
|
|
||||||
| @staticmethod | ||||||
| def _append_attachments( | ||||||
| async def _prepare_audio_attachment( | ||||||
| url: str, | ||||||
| filename: str, | ||||||
| ) -> Record: | ||||||
| temp_dir = Path(get_astrbot_temp_path()) | ||||||
| temp_dir.mkdir(parents=True, exist_ok=True) | ||||||
|
|
||||||
| ext = Path(filename).suffix.lower() | ||||||
| source_ext = ext or ".audio" | ||||||
| source_path = temp_dir / f"qqofficial_{uuid.uuid4().hex}{source_ext}" | ||||||
| await download_file(url, str(source_path)) | ||||||
|
|
||||||
| return Record(file=str(source_path), url=str(source_path)) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency with how
Suggested change
|
||||||
|
|
||||||
| @staticmethod | ||||||
| async def _append_attachments( | ||||||
| msg: list[BaseMessageComponent], | ||||||
| attachments: list | None, | ||||||
| ) -> None: | ||||||
|
|
@@ -363,7 +382,7 @@ def _append_attachments( | |||||
| or getattr(attachment, "name", None) | ||||||
| or "attachment", | ||||||
| ) | ||||||
| ext = os.path.splitext(filename)[1].lower() | ||||||
| ext = Path(filename).suffix.lower() | ||||||
| image_exts = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"} | ||||||
| audio_exts = { | ||||||
| ".mp3", | ||||||
|
|
@@ -381,8 +400,21 @@ def _append_attachments( | |||||
| ".webm", | ||||||
| } | ||||||
|
|
||||||
| if content_type.startswith("audio") or ext in audio_exts: | ||||||
| msg.append(Record.fromURL(url)) | ||||||
| if content_type.startswith("voice") or ext in audio_exts: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Switching the content-type check from 'audio' to 'voice' may miss standard audio/* payloads. This used to match |
||||||
| try: | ||||||
| msg.append( | ||||||
| await QQOfficialPlatformAdapter._prepare_audio_attachment( | ||||||
| url, | ||||||
| filename, | ||||||
| ) | ||||||
| ) | ||||||
| except Exception as e: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Catching a broad |
||||||
| logger.warning( | ||||||
| "[QQOfficial] Failed to prepare audio attachment %s: %s", | ||||||
| url, | ||||||
| e, | ||||||
| ) | ||||||
| msg.append(Record.fromURL(url)) | ||||||
| elif content_type.startswith("video") or ext in video_exts: | ||||||
| msg.append(Video.fromURL(url)) | ||||||
| elif content_type.startswith("image") or ext in image_exts: | ||||||
|
|
@@ -432,13 +464,13 @@ def replace_face(match): | |||||
| return re.sub(r"<faceType=\d+[^>]*>", replace_face, content) | ||||||
|
|
||||||
| @staticmethod | ||||||
| def _parse_from_qqofficial( | ||||||
| async def _parse_from_qqofficial( | ||||||
| message: botpy.message.Message | ||||||
| | botpy.message.GroupMessage | ||||||
| | botpy.message.DirectMessage | ||||||
| | botpy.message.C2CMessage, | ||||||
| message_type: MessageType, | ||||||
| ): | ||||||
| ) -> AstrBotMessage: | ||||||
| abm = AstrBotMessage() | ||||||
| abm.type = message_type | ||||||
| abm.timestamp = int(time.time()) | ||||||
|
|
@@ -463,7 +495,9 @@ def _parse_from_qqofficial( | |||||
| abm.self_id = "unknown_selfid" | ||||||
| msg.append(At(qq="qq_official")) | ||||||
| msg.append(Plain(abm.message_str)) | ||||||
| QQOfficialPlatformAdapter._append_attachments(msg, message.attachments) | ||||||
| await QQOfficialPlatformAdapter._append_attachments( | ||||||
| msg, message.attachments | ||||||
| ) | ||||||
| abm.message = msg | ||||||
|
|
||||||
| elif isinstance(message, botpy.message.Message) or isinstance( | ||||||
|
|
@@ -482,7 +516,9 @@ def _parse_from_qqofficial( | |||||
| ).strip() | ||||||
| ) | ||||||
|
|
||||||
| QQOfficialPlatformAdapter._append_attachments(msg, message.attachments) | ||||||
| await QQOfficialPlatformAdapter._append_attachments( | ||||||
| msg, message.attachments | ||||||
| ) | ||||||
| abm.message = msg | ||||||
| abm.message_str = plain_content | ||||||
| abm.sender = MessageMember( | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Persisting every audio attachment to a temp file without cleanup may cause temp directory growth over time.
_prepare_audio_attachmentwrites each audio file toget_astrbot_temp_path()and returns aRecordwith that path, but nothing deletes or reuses these files. For a long-running bot this can steadily grow the temp directory. If no external process is cleaning these up, add a lifecycle strategy (e.g., per-session subdirs plus cleanup, pruning old files, or an explicit delete after processing).Suggested implementation:
To make this compile, ensure the following imports exist at the top of
qqofficial_platform_adapter.py(or in the appropriate imports section):from datetime import datetime, timedeltaIf these imports are already present (perhaps used elsewhere in the file), no further changes are needed.