diff --git a/adapters/discord_adapter.py b/adapters/discord_adapter.py index 455d9af..0892223 100644 --- a/adapters/discord_adapter.py +++ b/adapters/discord_adapter.py @@ -32,22 +32,30 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object): def __init__(self, token: str): if not DISCORD_AVAILABLE: raise ImportError("discord.py 未安装,请运行 `pip install discord.py`") - - # 必须声明 Intents,否则无法读取消息内容 - intents = discord.Intents.default() - intents.message_content = True - # 检查是否配置了代理 + self.logger = ModuleLogger("DiscordAdapter") + self.token = token + self.send_channel = None + self.proxy = None self.proxy_type = "http" if global_config.discord.proxy: self.proxy = global_config.discord.proxy self.proxy_type = global_config.discord.proxy_type or "http" + + proxy_url = self.proxy + if self.proxy_type.lower() in ["socks5", "socks4"]: + if not proxy_url.startswith(("socks5://", "socks4://")): + proxy_url = f"{self.proxy_type.lower()}://{proxy_url.split('://')[-1]}" + + os.environ["HTTP_PROXY"] = proxy_url + os.environ["HTTPS_PROXY"] = proxy_url + self.logger.info(f"[DiscordAdapter] 代理已设置: {proxy_url} (类型: {self.proxy_type})") + + intents = discord.Intents.default() + intents.message_content = True super().__init__(intents=intents) - self.token = token - self.logger = ModuleLogger("DiscordAdapter") - self.send_channel = None async def on_ready(self): """当 Bot 成功连接到 Discord 时触发""" @@ -110,6 +118,7 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object): channel_id = data.get("channel_id") content = data.get("content", "") attachments = data.get("attachments", []) + embed_data = data.get("embed") if channel_id is None: self.logger.error("[DiscordAdapter] 缺少 channel_id") @@ -122,9 +131,38 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object): self.logger.info(f"[DiscordAdapter] 正在发送消息到频道 {channel_id}") - # 发送内容和附件(合并为一条消息) - if content or attachments: - await channel.send(content=content, files=[discord.File(fp=io.BytesIO(requests.get(attachment_url).content), filename=os.path.basename(attachment_url)) for attachment_url in attachments if attachment_url.startswith('http')] if attachments else None) + files = [] + if attachments: + proxies = None + if self.proxy: + proxies = { + "http": self.proxy, + "https": self.proxy + } + + for attachment in attachments: + if isinstance(attachment, dict): + attachment_url = attachment.get("url", "") + filename = attachment.get("filename", "") + else: + attachment_url = str(attachment) + filename = "" + + if attachment_url.startswith('http'): + try: + response = requests.get(attachment_url, proxies=proxies, timeout=30) + if not filename: + filename = os.path.basename(attachment_url.split('?')[0]) or "attachment" + files.append(discord.File(fp=io.BytesIO(response.content), filename=filename)) + except Exception as e: + self.logger.error(f"[DiscordAdapter] 下载附件失败: {attachment_url}, 错误: {e}") + + embed = None + if embed_data: + embed = discord.Embed.from_dict(embed_data) + + if content or files or embed: + await channel.send(content=content, files=files if files else None, embed=embed) self.logger.success(f"[DiscordAdapter] 消息已发送到频道 {channel_id}") @@ -139,35 +177,6 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object): try: self.logger.info("正在连接 Discord...") - - # 如果配置了代理,使用自定义的 ClientSession - if self.proxy: - import aiohttp - proxy_url = self.proxy - self.logger.info(f"[DiscordAdapter] 使用代理: {proxy_url} (类型: {self.proxy_type})") - - connector = aiohttp.TCPConnector() - session = aiohttp.ClientSession(connector=connector) - - # discord.py 2.0+ 使用 discord.Client 的 connector 参数 - # 但 discord.Client 不直接支持自定义 connector - # 需要使用 discord.AutoShardedClient 或修改内部实现 - # 这里我们使用 discord.Client 的 __init__ 传递 connector - # 但 discord.Client 的 __init__ 不支持 connector 参数 - # 所以我们需要使用 discord.Client 的 _create_http_client 方法 - - # 简单方案:使用环境变量设置代理 - import os - os.environ["HTTP_PROXY"] = proxy_url - os.environ["HTTPS_PROXY"] = proxy_url - - self.logger.info("[DiscordAdapter] 代理已设置,正在连接 Discord...") - await self.start(self.token) - - # 清理环境变量 - os.environ.pop("HTTP_PROXY", None) - os.environ.pop("HTTPS_PROXY", None) - else: - await self.start(self.token) + await self.start(self.token) except Exception as e: self.logger.error(f"Discord 连接失败: {e}") diff --git a/config.toml b/config.toml index dc9381e..aa58c3b 100644 --- a/config.toml +++ b/config.toml @@ -103,7 +103,7 @@ base_url = "http://101.36.126.55:3003" # 外部访问的 URL [discord] enabled = true token = "MTQ4MjQzODA1NzExNzYxODI4Nw.G9R6uR.ddxHn3pmUf7SyrrOBg_-_lc7Y62lsCitPxpdGM" -proxy = "http://127.0.0.1:7890" +proxy = "http://127.0.0.1:7897" proxy_type = "http" # 跨平台消息互通配置 @@ -126,7 +126,7 @@ name = "Paw" # 日志配置 [logging] # 控制台日志级别(DEBUG, INFO, SUCCESS, WARNING, ERROR) -console_level = "INFO" +console_level = "DEBUG" # 文件日志级别(DEBUG, INFO, SUCCESS, WARNING, ERROR) file_level = "DEBUG" # 全局日志级别(DEBUG, INFO, SUCCESS, WARNING, ERROR) diff --git a/plugins/cross_platform.py b/plugins/cross_platform.py index 8b8f877..15ec0ac 100644 --- a/plugins/cross_platform.py +++ b/plugins/cross_platform.py @@ -6,9 +6,12 @@ - 在消息中自动标注来源平台和子频道/群组 ID - 支持 OneBot v11 协议和数据结构 - 支持图片、视频等媒体消息 +- 支持合并转发消息 """ import asyncio +import html import json +import os import re import time from typing import Dict, List, Optional, Any @@ -35,6 +38,236 @@ CROSS_PLATFORM_CHANNEL = "neobot_cross_platform" ENABLE_CROSS_PLATFORM = True +async def parse_forward_nodes(nodes: List[Dict[str, Any]]) -> tuple[str, List[dict]]: + """ + 解析 OneBot 合并转发消息节点 + + Args: + nodes: 合并转发消息节点列表 + + Returns: + 格式化后的消息内容和附件列表 + """ + content_parts = [] + attachments = [] + + for node in nodes: + if not isinstance(node, dict): + continue + + node_data = node.get("data", {}) + node_content = node_data.get("content", "") + + # 获取发送者信息 + sender_name = node_data.get("name", node_data.get("uin", "Unknown")) + + # 解析节点内容 + if isinstance(node_content, str): + # 检查是否是 [object Object] 格式(OneBot 协议的特殊格式) + if "[object Object]" in node_content: + # OneBot 协议中,合并转发消息的 content 可能是 [object Object],[object Object] + # 实际的消息内容在 nodes 中,直接使用节点作为消息内容 + content = f"[合并转发消息: {sender_name}]" + content_parts.append(f"**{sender_name}**:\n{content}") + elif '[CQ:' in node_content: + # CQ 码字符串格式 + content = parse_cq_code(node_content, attachments) + content_parts.append(f"**{sender_name}**:\n{content}") + else: + content = node_content + content_parts.append(f"**{sender_name}**:\n{content}") + elif isinstance(node_content, list): + # MessageSegment 列表格式 + content = parse_message_segments(node_content, attachments) + content_parts.append(f"**{sender_name}**:\n{content}") + + # 组合完整消息 + full_content = "\n\n".join(content_parts) if content_parts else "" + + return full_content, attachments + + +def parse_cq_code(cq_code: str, attachments: List[dict]) -> str: + """ + 解析 CQ 码字符串 + + Args: + cq_code: CQ 码字符串 + attachments: 附件列表(用于添加图片/视频 URL) + + Returns: + 解析后的文本内容 + """ + import re + + # 匹配 CQ 码 + cq_pattern = r'\[CQ:([^,]+)(?:,([^\]]+))?\]' + matches = list(re.finditer(cq_pattern, cq_code)) + + if not matches: + return cq_code + + result = [] + last_end = 0 + + for match in matches: + if match.start() > last_end: + result.append(cq_code[last_end:match.start()]) + + cq_type = match.group(1) + cq_params_str = match.group(2) or "" + + params = {} + if cq_params_str: + for param in cq_params_str.split(','): + if '=' in param: + k, v = param.split('=', 1) + params[k] = v + + if cq_type == "text": + result.append(params.get("text", "")) + elif cq_type == "image": + file_url = params.get("url") or params.get("file") + if file_url: + file_name = params.get("file", "") + if not file_name: + file_name = os.path.basename(str(file_url).split('?')[0]) or "image" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[图片: {file_name}]\n") + elif cq_type == "video": + file_url = params.get("url") or params.get("file") + if file_url: + file_name = params.get("file", "") + if not file_name: + file_name = os.path.basename(str(file_url).split('?')[0]) or "video" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[视频: {file_name}]\n") + elif cq_type == "at": + qq_id = params.get("qq") + if qq_id == "all": + result.append("@所有人 ") + else: + result.append(f"@{qq_id} ") + elif cq_type == "face": + face_id = params.get("id", "") + result.append(f"[表情:{face_id}] ") + elif cq_type == "reply": + reply_id = params.get("id", "") + result.append(f"[回复:{reply_id}] ") + elif cq_type == "file": + file_url = params.get("file", "") + if file_url: + file_name = os.path.basename(str(file_url).split('?')[0]) or "file" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[文件: {file_name}]\n") + + last_end = match.end() + + if last_end < len(cq_code): + result.append(cq_code[last_end:]) + + return "".join(result) + + +def parse_message_segments(segments: List[Any], attachments: List[dict]) -> str: + """ + 解析 MessageSegment 列表 + + Args: + segments: MessageSegment 列表 + attachments: 附件列表(用于添加图片/视频 URL) + + Returns: + 解析后的文本内容 + """ + result = [] + + for seg in segments: + if isinstance(seg, str): + result.append(seg) + elif isinstance(seg, MessageSegment): + seg_type = seg.type + seg_data = seg.data + + if seg_type == "text": + result.append(seg_data.get("text", "")) + elif seg_type == "image": + file_url = seg_data.get("url") or seg_data.get("file") + if file_url: + file_name = seg_data.get("file", "") + if not file_name: + file_name = os.path.basename(str(file_url).split('?')[0]) or "image" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[图片: {file_name}]\n") + elif seg_type == "video": + file_url = seg_data.get("url") or seg_data.get("file") + if file_url: + file_name = seg_data.get("file", "") + if not file_name: + file_name = os.path.basename(str(file_url).split('?')[0]) or "video" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[视频: {file_name}]\n") + elif seg_type == "at": + qq_id = seg_data.get("qq") + if qq_id == "all": + result.append("@所有人 ") + else: + result.append(f"@{qq_id} ") + elif seg_type == "face": + face_id = seg_data.get("id", "") + result.append(f"[表情:{face_id}] ") + elif seg_type == "reply": + reply_id = seg_data.get("id", "") + result.append(f"[回复:{reply_id}] ") + elif seg_type == "file": + file_url = seg_data.get("file", "") + if file_url: + file_name = os.path.basename(str(file_url).split('?')[0]) or "file" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[文件: {file_name}]\n") + elif seg_type == "json": + # 尝试解析 JSON 数据 + json_data = seg_data.get("data", "") + try: + parsed = json.loads(json_data) + if isinstance(parsed, dict): + result.append(f"\n[JSON数据: {json_data[:100]}...]\n") + except: + result.append(f"\n[JSON数据]\n") + elif seg_type == "xml": + result.append(f"\n[XML数据]\n") + elif isinstance(seg, dict): + seg_type = seg.get("type") + seg_data = seg.get("data", {}) + + if seg_type == "text": + result.append(seg_data.get("text", "")) + elif seg_type == "image": + file_url = seg_data.get("url") or seg_data.get("file") + if file_url: + file_name = seg_data.get("file", "") + if not file_name: + file_name = os.path.basename(str(file_url).split('?')[0]) or "image" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[图片: {file_name}]\n") + elif seg_type == "video": + file_url = seg_data.get("url") or seg_data.get("file") + if file_url: + file_name = seg_data.get("file", "") + if not file_name: + file_name = os.path.basename(str(file_url).split('?')[0]) or "video" + attachments.append({"url": str(file_url), "filename": file_name}) + result.append(f"\n[视频: {file_name}]\n") + elif seg_type == "at": + qq_id = seg_data.get("qq") + if qq_id == "all": + result.append("@所有人 ") + else: + result.append(f"@{qq_id} ") + + return "".join(result) + + def get_platform_info(platform: str, identifier: Any) -> str: """ 获取平台信息字符串,用于在消息中标注来源 @@ -55,7 +288,7 @@ def get_platform_info(platform: str, identifier: Any) -> str: return f"[Discord]" elif platform == "qq": group_id = int(identifier) - return f"[QQ {group_id}]" + return f"[PAW qq]" return "" @@ -64,7 +297,7 @@ async def format_discord_to_qq_content( discord_discriminator: str, content: str, channel_id: int, - attachments: List[str] = None + attachments: List[dict] = None ) -> tuple[str, List[str]]: """ 将 Discord 消息格式化为 QQ 消息格式 @@ -93,7 +326,18 @@ async def format_discord_to_qq_content( else: full_message = message_header - return full_message, attachments or [] + # 提取图片 URL + image_list = [] + if attachments: + for att in attachments: + if isinstance(att, dict): + url = att.get("url", "") + if url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')): + image_list.append(url) + else: + image_list.append(str(att)) + + return full_message, image_list async def format_qq_to_discord_content( @@ -102,10 +346,10 @@ async def format_qq_to_discord_content( group_name: str, group_id: int, content: str, - attachments: List[str] = None -) -> tuple[str, List[str]]: + attachments: List[dict] = None +) -> tuple[str, List[dict], dict]: """ - 将 QQ 消息格式化为 Discord 消息格式 + 将 QQ 消息格式化为 Discord 消息格式(Embed 卡片) Args: qq_nickname: QQ 昵称 @@ -116,26 +360,53 @@ async def format_qq_to_discord_content( attachments: 附件列表 Returns: - 格式化后的消息内容和图片列表 + 格式化后的消息内容、附件列表和 Embed 字典 """ platform_info = get_platform_info("qq", group_id) - # 构建消息头(简化版,只显示名字) - message_header = f"{platform_info} {qq_nickname}:" + # 构建 Embed 卡片 + embed = { + "type": "rich", + "color": 0x5865F2, # Discord 蓝色 + "author": { + "name": f"{platform_info} {qq_nickname}", + "icon_url": f"https://q1.qlogo.cn/g?b=qq&nk={qq_user_id}&s=640" + }, + "description": content if content else "", + "timestamp": None, + "footer": { + "text": f"来自 QQPAW" + } + } - # 构建消息体 - message_body = content if content else "" + # 如果有附件,添加到 description + if attachments: + image_urls = [] + other_urls = [] + for att in attachments: + if att.get("url", "").lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')): + image_urls.append(att.get("url")) + else: + other_urls.append(att.get("url")) + + if image_urls: + embed["description"] = f"{content}\n\n{chr(10).join(image_urls[:3])}" if content else chr(10).join(image_urls[:3]) + if len(image_urls) > 3: + embed["description"] += f"\n...还有 {len(image_urls) - 3} 张图片" + + # 附加文件列表 + if other_urls: + file_list = "\n".join([f"📄 {os.path.basename(u.split('?')[0])}" for u in other_urls[:5]]) + embed["description"] += f"\n\n**附加文件:**\n{file_list}" + if len(other_urls) > 5: + embed["description"] += f"\n...还有 {len(other_urls) - 5} 个文件" - # 组合完整消息(移除分隔符) - if message_body: - full_message = f"{message_header} {message_body}" - else: - full_message = message_header - - return full_message, attachments or [] + # 对于合并转发消息,content 为空,只发送 embed + # 对于普通消息,content 也为空,只发送 embed + return "", attachments or [], embed -async def send_to_discord(channel_id: int, content: str, attachments: List[str] = None): +async def send_to_discord(channel_id: int, content: str, attachments: List[dict] = None, embed: dict = None): """ 发送消息到 Discord 频道 @@ -145,14 +416,16 @@ async def send_to_discord(channel_id: int, content: str, attachments: List[str] Args: channel_id: Discord 频道 ID content: 消息内容 - attachments: 附件 URL 列表 + attachments: 附件列表,每个元素为 {"url": str, "filename": str} + embed: Discord Embed 字典 """ try: publish_data = { "type": "send_message", "channel_id": channel_id, "content": content, - "attachments": attachments or [] + "attachments": attachments or [], + "embed": embed } await redis_manager.redis.publish("neobot_discord_send", json.dumps(publish_data)) logger.info(f"[CrossPlatform] 消息已发布到 Redis 供 Discord 适配器发送: {channel_id}") @@ -161,14 +434,14 @@ async def send_to_discord(channel_id: int, content: str, attachments: List[str] logger.error(f"[CrossPlatform] 发送消息到 Discord 失败: {e}") -async def send_to_qq(group_id: int, content: str, attachments: List[str] = None): +async def send_to_qq(group_id: int, content: str, attachments: List[dict] = None): """ 发送消息到 QQ 群 Args: group_id: QQ 群 ID content: 消息内容 - attachments: 附件 URL 列表 + attachments: 附件列表,每个元素为 {"url": str, "filename": str} """ try: from core.managers.bot_manager import bot_manager @@ -196,7 +469,11 @@ async def send_to_qq(group_id: int, content: str, attachments: List[str] = None) if content: full_message.append(MessageSegment.text(content)) for attachment in attachments: - full_message.append(MessageSegment.image(attachment, cache=True, proxy=True, timeout=30)) + if isinstance(attachment, dict): + attachment_url = attachment.get("url", "") + else: + attachment_url = str(attachment) + full_message.append(MessageSegment.image(attachment_url, cache=True, proxy=True, timeout=30)) logger.debug(f"[CrossPlatform] 准备发送消息到 QQ 群 {group_id}: {full_message}") # 一次性发送 @@ -219,7 +496,7 @@ async def forward_discord_to_qq( discord_discriminator: str, content: str, channel_id: int, - attachments: List[str] = None + attachments: List[dict] = None ): """ 将 Discord 消息转发到所有映射的 QQ 群 @@ -259,7 +536,7 @@ async def forward_qq_to_discord( group_name: str, group_id: int, content: str, - attachments: List[str] = None + attachments: List[dict] = None ): """ 将 QQ 消息转发到所有映射的 Discord 频道 @@ -283,7 +560,7 @@ async def forward_qq_to_discord( return # 格式化消息 - formatted_content, image_list = await format_qq_to_discord_content( + formatted_content, image_list, embed = await format_qq_to_discord_content( qq_nickname, qq_user_id, group_name, @@ -294,7 +571,7 @@ async def forward_qq_to_discord( # 发送到所有映射的 Discord 频道 for channel_id in target_channels: - await send_to_discord(channel_id, formatted_content, image_list) + await send_to_discord(channel_id, formatted_content, image_list, embed) logger.success(f"[CrossPlatform] QQ 群 {group_id} -> Discord 频道 {target_channels}") @@ -325,7 +602,8 @@ async def handle_discord_message( discriminator: str, content: str, channel_id: int, - attachments: List[str] = None + attachments: List[dict] = None, + embed: dict = None ): """ 处理 Discord 消息并转发 @@ -336,6 +614,7 @@ async def handle_discord_message( content: 消息内容 channel_id: Discord 频道 ID attachments: 附件列表 + embed: Discord Embed 字典 """ if not ENABLE_CROSS_PLATFORM: return @@ -352,7 +631,7 @@ async def handle_qq_message( group_name: str, group_id: int, content: str, - attachments: List[str] = None + attachments: List[dict] = None ): """ 处理 QQ 消息并转发 @@ -398,26 +677,45 @@ async def handle_qq_group_message(event: GroupMessageEvent): attachments = [] if isinstance(event.message, list): - for segment in event.message: - if isinstance(segment, MessageSegment): - if segment.type == "text": - content += segment.data.get("text", "") - elif segment.type == "image": - file_url = segment.data.get("url") or segment.data.get("file") - if file_url: - attachments.append(str(file_url)) - elif segment.type == "video": - file_url = segment.data.get("url") or segment.data.get("file") - if file_url: - attachments.append(str(file_url)) - elif segment.type == "at": - qq_id = segment.data.get("qq") - if qq_id and qq_id != "all": - content += f"@{qq_id} " - elif qq_id == "all": - content += "@所有人 " - elif isinstance(segment, str): - content += segment + # 检查是否是合并转发消息 + has_forward_node = any(isinstance(seg, MessageSegment) and seg.type == "node" for seg in event.message) + + if has_forward_node: + # 解析合并转发消息 + forward_nodes = [seg for seg in event.message if isinstance(seg, MessageSegment) and seg.type == "node"] + # 将 MessageSegment 转换为字典格式 + forward_nodes_dict = [{"type": seg.type, "data": seg.data} for seg in forward_nodes] + content, attachments = await parse_forward_nodes(forward_nodes_dict) + else: + # 普通消息 + for segment in event.message: + if isinstance(segment, MessageSegment): + if segment.type == "text": + content += segment.data.get("text", "") + elif segment.type == "image": + file_url = segment.data.get("url") or segment.data.get("file") + file_name = segment.data.get("file", "") + if file_url: + file_url = html.unescape(str(file_url)) + if not file_name: + file_name = os.path.basename(file_url.split('?')[0]) or f"image_{len(attachments)}.jpg" + attachments.append({"url": file_url, "filename": file_name}) + elif segment.type == "video": + file_url = segment.data.get("url") or segment.data.get("file") + file_name = segment.data.get("file", "") + if file_url: + file_url = html.unescape(str(file_url)) + if not file_name: + file_name = os.path.basename(file_url.split('?')[0]) or f"video_{len(attachments)}.mp4" + attachments.append({"url": file_url, "filename": file_name}) + elif segment.type == "at": + qq_id = segment.data.get("qq") + if qq_id and qq_id != "all": + content += f"@{qq_id} " + elif qq_id == "all": + content += "@所有人 " + elif isinstance(segment, str): + content += segment elif isinstance(event.message, str): content = event.message @@ -473,8 +771,12 @@ async def handle_discord_message_event(event: Any): line = line.strip() if re.match(url_pattern, line): # 这是附件 URL - if line not in attachments: - attachments.append(line) + attachment_url = line + # 尝试从 URL 提取文件名 + filename = os.path.basename(attachment_url.split('?')[0]) or "attachment" + attachment_item = {"url": attachment_url, "filename": filename} + if attachment_item not in attachments: + attachments.append(attachment_item) else: # 这是普通文本内容 if line: @@ -490,12 +792,20 @@ async def handle_discord_message_event(event: Any): pass # 已经在 raw_message 中 elif segment.type == "image": file_url = segment.data.get("url") or segment.data.get("file") - if file_url and str(file_url) not in attachments: - attachments.append(str(file_url)) + file_name = segment.data.get("file", "") + if file_url: + file_name = file_name or os.path.basename(str(file_url).split('?')[0]) or "image" + attachment_item = {"url": str(file_url), "filename": file_name} + if attachment_item not in attachments: + attachments.append(attachment_item) elif segment.type == "video": file_url = segment.data.get("url") or segment.data.get("file") - if file_url and str(file_url) not in attachments: - attachments.append(str(file_url)) + file_name = segment.data.get("file", "") + if file_url: + file_name = file_name or os.path.basename(str(file_url).split('?')[0]) or "video" + attachment_item = {"url": str(file_url), "filename": file_name} + if attachment_item not in attachments: + attachments.append(attachment_item) # 获取用户信息 discord_username = getattr(event, 'discord_username', 'Unknown') @@ -507,7 +817,8 @@ async def handle_discord_message_event(event: Any): discriminator=discord_discriminator, content=content, channel_id=discord_channel_id, - attachments=attachments + attachments=attachments, + embed=None ) @@ -551,7 +862,8 @@ async def cross_platform_subscription_loop(): group_name=message_data.get("group_name", ""), group_id=message_data.get("group_id", 0), content=message_data.get("content", ""), - attachments=message_data.get("attachments", []) + attachments=message_data.get("attachments", []), + embed=message_data.get("embed") ) except json.JSONDecodeError as e: