-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat(platform): add Forward message support for aiocqhttp adapter #2003
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 3 commits
8252a09
20f0e99
a48157a
4a99817
87d47b5
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 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -375,13 +375,109 @@ async def shutdown_trigger_placeholder(): | |||||||||||||||||||||
| self.bot = aiocqhttp.CQHttp() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain): | ||||||||||||||||||||||
| # Check if message contains a Forward component | ||||||||||||||||||||||
| forward_msg = message.get_first(platform_message.Forward) | ||||||||||||||||||||||
| if forward_msg: | ||||||||||||||||||||||
| if target_type == 'group': | ||||||||||||||||||||||
| # Send as merged forward message via OneBot API | ||||||||||||||||||||||
| await self._send_forward_message(int(target_id), forward_msg) | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| await self.logger.warning( | ||||||||||||||||||||||
| f'Forward message is only supported for group targets, got target_type={target_type}. Falling through to normal send.' | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if target_type == 'group': | ||||||||||||||||||||||
| await self.bot.send_group_msg(group_id=int(target_id), message=aiocq_msg) | ||||||||||||||||||||||
| elif target_type == 'person': | ||||||||||||||||||||||
| await self.bot.send_private_msg(user_id=int(target_id), message=aiocq_msg) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async def _send_forward_message(self, group_id: int, forward: platform_message.Forward): | ||||||||||||||||||||||
| """Send a merged forward message to a group using NapCat extended API.""" | ||||||||||||||||||||||
| messages = [] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for node in forward.node_list: | ||||||||||||||||||||||
| # Build content for each node | ||||||||||||||||||||||
| content = [] | ||||||||||||||||||||||
| if node.message_chain: | ||||||||||||||||||||||
| for component in node.message_chain: | ||||||||||||||||||||||
| if isinstance(component, platform_message.Plain): | ||||||||||||||||||||||
| if component.text: | ||||||||||||||||||||||
| content.append({'type': 'text', 'data': {'text': component.text}}) | ||||||||||||||||||||||
| elif isinstance(component, platform_message.Image): | ||||||||||||||||||||||
| img_data = {} | ||||||||||||||||||||||
| if component.base64: | ||||||||||||||||||||||
| b64 = component.base64 | ||||||||||||||||||||||
| if b64.startswith('data:'): | ||||||||||||||||||||||
| b64 = b64.split(',', 1)[-1] if ',' in b64 else b64 | ||||||||||||||||||||||
|
||||||||||||||||||||||
| b64 = b64.split(',', 1)[-1] if ',' in b64 else b64 | |
| b64 = b64.split(',', 1)[1] if ',' in b64 else b64[5:] |
Copilot
AI
Feb 25, 2026
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.
The _send_forward_message method only handles Plain and Image components in forward messages. However, the existing yiri2target converter in this file (lines 28-75) supports many more component types including At, AtAll, Voice, File, Face, and nested Forward messages. This creates an inconsistency where forward messages with these component types will be silently ignored rather than properly converted. Consider either supporting all component types that yiri2target supports, or at least handling unsupported types explicitly (e.g., converting them to text as done in yiri2target line 74).
| else: | |
| # Fallback: convert other component types to text, similar to yiri2target | |
| text = getattr(component, 'text', None) | |
| if not text: | |
| text = str(component) | |
| if text: | |
| content.append({'type': 'text', 'data': {'text': text}}) |
Copilot
AI
Feb 25, 2026
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.
When a forward message node has no valid content (empty content list), the node is skipped with 'continue' at line 420. If all nodes are skipped this way, the method returns early at line 436 without sending anything or notifying the caller. This could lead to silent failures where the user expects a forward message to be sent but nothing happens. Consider logging a warning when all nodes are empty, or throwing an exception to make the failure explicit.
| if not messages: | |
| if not messages: | |
| # No valid nodes produced any content; nothing will be sent. | |
| # Log this condition to avoid silent failures for callers. | |
| await self.logger.info( | |
| f'Forward message to group {group_id} not sent: all nodes had empty or invalid content.' | |
| ) |
Copilot
AI
Feb 25, 2026
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.
When the primary send_forward_msg API fails, the fallback at line 470 calls send_group_forward_msg with only group_id and messages parameters. However, the primary API call at line 463 uses additional parameters from the payload dict including user_id, and potentially news, prompt, summary, and source fields. The fallback doesn't include these display settings, which means if the primary API fails, the forward message will be sent without the custom display settings (title, brief, summary, source) that the user may have configured in forward.display. Consider including these parameters in the fallback call, or documenting that display settings are not supported in fallback mode.
| # Fallback: try standard OneBot API with integer group_id | |
| try: | |
| await self.logger.info('Trying fallback API send_group_forward_msg') | |
| # Fallback: try standard OneBot API with integer group_id. | |
| # Note: OneBot's send_group_forward_msg does not support the extended display | |
| # fields (news/prompt/summary/source) or the NapCat-specific user_id behavior, | |
| # so only group_id and messages are sent here and any forward.display settings | |
| # will not affect the fallback message rendering. | |
| try: | |
| await self.logger.info('Trying fallback API send_group_forward_msg without extended display settings') |
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.
The type checking pattern is inconsistent with the existing yiri2target method. In yiri2target (lines 29-71), Plain and Image components use 'type(component) is platform_message.Plain' and 'type(component) is platform_message.Image' for exact type matching. However, in _send_forward_message, lines 401 and 404 use 'isinstance(component, platform_message.Plain)' and 'isinstance(component, platform_message.Image)'. For consistency with the existing codebase pattern, these should use 'type(component) is' instead of 'isinstance'.