fix(discord): 修复 WebSocket 连接检测并增强跨平台文件处理 (#73)
修复 Discord WebSocket 连接检测逻辑,使用正确的属性检查连接状态 为跨平台消息处理添加文件类型支持,并增加详细的调试日志 优化附件处理逻辑,确保所有文件类型都能正确识别和转发
This commit is contained in:
@@ -327,7 +327,9 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object):
|
|||||||
try:
|
try:
|
||||||
await asyncio.sleep(interval)
|
await asyncio.sleep(interval)
|
||||||
|
|
||||||
if self.ws is not None and self.ws.closed:
|
# discord.py 的 ws 对象是 DiscordWebSocket,它没有 closed 属性
|
||||||
|
# 我们可以通过检查 self.is_closed() 或者 ws.open 来判断
|
||||||
|
if self.ws is not None and not getattr(self.ws, 'open', True):
|
||||||
self.logger.warning("检测到 WebSocket 连接已关闭,触发重连...")
|
self.logger.warning("检测到 WebSocket 连接已关闭,触发重连...")
|
||||||
await self.close()
|
await self.close()
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -281,24 +281,36 @@ class DiscordToOneBotConverter:
|
|||||||
|
|
||||||
# 添加附件信息
|
# 添加附件信息
|
||||||
if discord_message.attachments:
|
if discord_message.attachments:
|
||||||
|
self.logger.debug(f"[DiscordToOneBotConverter] 检测到 {len(discord_message.attachments)} 个附件")
|
||||||
for attachment in discord_message.attachments:
|
for attachment in discord_message.attachments:
|
||||||
filename = attachment.filename.lower()
|
filename = attachment.filename.lower()
|
||||||
|
self.logger.debug(f"[DiscordToOneBotConverter] 处理附件: {attachment.filename}, MIME: {attachment.content_type}")
|
||||||
# 检查是否是语音文件
|
# 检查是否是语音文件
|
||||||
if filename.endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')):
|
if filename.endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')):
|
||||||
seg = OneBotMessageSegment.record(attachment.url)
|
seg = OneBotMessageSegment.record(attachment.url)
|
||||||
seg.data["filename"] = attachment.filename
|
seg.data["filename"] = attachment.filename
|
||||||
message_list.append(seg)
|
message_list.append(seg)
|
||||||
raw_message += f"\n[语音: {attachment.filename}]"
|
raw_message += f"\n[语音: {attachment.filename}]"
|
||||||
|
self.logger.debug(f"[DiscordToOneBotConverter] 识别为语音文件: {attachment.filename}")
|
||||||
elif filename.endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
elif filename.endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
||||||
seg = OneBotMessageSegment.video(attachment.url)
|
seg = OneBotMessageSegment.video(attachment.url)
|
||||||
seg.data["filename"] = attachment.filename
|
seg.data["filename"] = attachment.filename
|
||||||
message_list.append(seg)
|
message_list.append(seg)
|
||||||
raw_message += f"\n[视频: {attachment.filename}]"
|
raw_message += f"\n[视频: {attachment.filename}]"
|
||||||
else:
|
self.logger.debug(f"[DiscordToOneBotConverter] 识别为视频文件: {attachment.filename}")
|
||||||
seg = OneBotMessageSegment.image(attachment.url)
|
elif filename.endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')):
|
||||||
|
image_type = "gif" if filename.endswith('.gif') else None
|
||||||
|
seg = OneBotMessageSegment.image(attachment.url, image_type=image_type)
|
||||||
seg.data["filename"] = attachment.filename
|
seg.data["filename"] = attachment.filename
|
||||||
message_list.append(seg)
|
message_list.append(seg)
|
||||||
raw_message += f"\n[图片: {attachment.filename}]"
|
raw_message += f"\n[图片: {attachment.filename}]"
|
||||||
|
self.logger.debug(f"[DiscordToOneBotConverter] 识别为图片文件: {attachment.filename}")
|
||||||
|
else:
|
||||||
|
seg = OneBotMessageSegment.file(attachment.url)
|
||||||
|
seg.data["filename"] = attachment.filename
|
||||||
|
message_list.append(seg)
|
||||||
|
raw_message += f"\n[文件: {attachment.filename}]"
|
||||||
|
self.logger.success(f"[DiscordToOneBotConverter] 识别为普通文件: {attachment.filename}")
|
||||||
|
|
||||||
# 添加贴纸 (Stickers) 信息
|
# 添加贴纸 (Stickers) 信息
|
||||||
if hasattr(discord_message, 'stickers') and discord_message.stickers:
|
if hasattr(discord_message, 'stickers') and discord_message.stickers:
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ async def handle_discord_message(
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"[CrossPlatform] 收到 Discord 消息: {username}#{discriminator} in {channel_id}")
|
logger.info(f"[CrossPlatform] 收到 Discord 消息: {username}#{discriminator} in {channel_id}")
|
||||||
|
logger.debug(f"[CrossPlatform] 消息内容: '{content}', 附件: {attachments}")
|
||||||
await forward_discord_to_qq(username, discriminator, content, channel_id, attachments)
|
await forward_discord_to_qq(username, discriminator, content, channel_id, attachments)
|
||||||
|
|
||||||
async def handle_qq_message(
|
async def handle_qq_message(
|
||||||
@@ -100,6 +101,16 @@ async def handle_qq_group_message(event: GroupMessageEvent):
|
|||||||
file_name = os.path.basename(file_url.split('?')[0]) or f"record_{len(attachments)}.amr"
|
file_name = os.path.basename(file_url.split('?')[0]) or f"record_{len(attachments)}.amr"
|
||||||
attachments.append({"type": "record", "url": file_url, "filename": file_name})
|
attachments.append({"type": "record", "url": file_url, "filename": file_name})
|
||||||
content += f"\n[语音: {file_name}]\n"
|
content += f"\n[语音: {file_name}]\n"
|
||||||
|
elif segment.type == "file":
|
||||||
|
file_url = segment.data.get("url") or segment.data.get("file")
|
||||||
|
file_name = segment.data.get("filename")
|
||||||
|
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"file_{len(attachments)}"
|
||||||
|
attachments.append({"type": "file", "url": file_url, "filename": file_name})
|
||||||
|
content += f"\n[文件: {file_name}]\n"
|
||||||
|
logger.debug(f"[CrossPlatform] QQ 消息识别到文件: {file_name}, URL: {file_url}")
|
||||||
elif segment.type == "at":
|
elif segment.type == "at":
|
||||||
qq_id = segment.data.get("qq")
|
qq_id = segment.data.get("qq")
|
||||||
if qq_id and qq_id != "all":
|
if qq_id and qq_id != "all":
|
||||||
@@ -143,16 +154,22 @@ async def handle_discord_message_event(event: Any):
|
|||||||
if not config.ENABLE_CROSS_PLATFORM:
|
if not config.ENABLE_CROSS_PLATFORM:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.debug(f"[CrossPlatform] handle_discord_message_event 触发: {event}")
|
||||||
if not hasattr(event, '_is_discord_message'):
|
if not hasattr(event, '_is_discord_message'):
|
||||||
|
logger.debug(f"[CrossPlatform] 事件没有 _is_discord_message 属性,跳过")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.debug(f"[CrossPlatform] 检测到 Discord 事件")
|
||||||
discord_channel_id = getattr(event, 'discord_channel_id', None)
|
discord_channel_id = getattr(event, 'discord_channel_id', None)
|
||||||
if discord_channel_id is None:
|
if discord_channel_id is None:
|
||||||
|
logger.debug(f"[CrossPlatform] discord_channel_id 为 None")
|
||||||
return
|
return
|
||||||
|
|
||||||
content = ""
|
content = ""
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
|
logger.debug(f"[CrossPlatform] 开始处理 Discord 事件消息: channel_id={discord_channel_id}")
|
||||||
|
|
||||||
if hasattr(event, 'message') and isinstance(event.message, list):
|
if hasattr(event, 'message') and isinstance(event.message, list):
|
||||||
for segment in event.message:
|
for segment in event.message:
|
||||||
if isinstance(segment, MessageSegment):
|
if isinstance(segment, MessageSegment):
|
||||||
@@ -182,14 +199,26 @@ async def handle_discord_message_event(event: Any):
|
|||||||
attachment_item = {"type": "record", "url": str(file_url), "filename": file_name}
|
attachment_item = {"type": "record", "url": str(file_url), "filename": file_name}
|
||||||
if attachment_item not in attachments:
|
if attachment_item not in attachments:
|
||||||
attachments.append(attachment_item)
|
attachments.append(attachment_item)
|
||||||
|
elif segment.type == "file":
|
||||||
|
file_url = segment.data.get("url") or segment.data.get("file")
|
||||||
|
file_name = segment.data.get("filename")
|
||||||
|
if file_url:
|
||||||
|
file_name = file_name or os.path.basename(str(file_url).split('?')[0]) or "file"
|
||||||
|
attachment_item = {"type": "file", "url": str(file_url), "filename": file_name}
|
||||||
|
if attachment_item not in attachments:
|
||||||
|
attachments.append(attachment_item)
|
||||||
|
logger.debug(f"[CrossPlatform] Discord 消息识别到文件: {file_name}, URL: {file_url}")
|
||||||
else:
|
else:
|
||||||
content = event.raw_message or ""
|
content = event.raw_message or ""
|
||||||
|
|
||||||
content = content.strip()
|
content = content.strip()
|
||||||
|
|
||||||
|
logger.debug(f"[CrossPlatform] Discord 消息内容: '{content}', 附件数量: {len(attachments)}")
|
||||||
|
|
||||||
discord_username = getattr(event, 'discord_username', 'Unknown')
|
discord_username = getattr(event, 'discord_username', 'Unknown')
|
||||||
discord_discriminator = getattr(event, 'discord_discriminator', '')
|
discord_discriminator = getattr(event, 'discord_discriminator', '')
|
||||||
|
|
||||||
|
logger.debug(f"[CrossPlatform] 调用 handle_discord_message: username={discord_username}, channel_id={discord_channel_id}")
|
||||||
await handle_discord_message(
|
await handle_discord_message(
|
||||||
username=discord_username,
|
username=discord_username,
|
||||||
discriminator=discord_discriminator,
|
discriminator=discord_discriminator,
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ async def format_discord_to_qq_content(
|
|||||||
attachments: List[dict] = None
|
attachments: List[dict] = None
|
||||||
) -> tuple[str, List[dict]]:
|
) -> tuple[str, List[dict]]:
|
||||||
"""将 Discord 消息格式化为 QQ 消息格式"""
|
"""将 Discord 消息格式化为 QQ 消息格式"""
|
||||||
|
logger.debug(f"[CrossPlatform] format_discord_to_qq_content: username={discord_username}, content='{content}', attachments={attachments}")
|
||||||
platform_info = get_platform_info("discord", channel_id)
|
platform_info = get_platform_info("discord", channel_id)
|
||||||
|
|
||||||
message_header = f"{discord_username}:"
|
message_header = f"{discord_username}:"
|
||||||
@@ -256,6 +257,7 @@ async def format_discord_to_qq_content(
|
|||||||
|
|
||||||
processed_attachments = []
|
processed_attachments = []
|
||||||
if attachments:
|
if attachments:
|
||||||
|
logger.debug(f"[CrossPlatform] 处理附件: {attachments}")
|
||||||
for att in attachments:
|
for att in attachments:
|
||||||
if isinstance(att, dict):
|
if isinstance(att, dict):
|
||||||
url = att.get("url", "")
|
url = att.get("url", "")
|
||||||
@@ -268,15 +270,24 @@ async def format_discord_to_qq_content(
|
|||||||
processed_attachments.append({"type": "record", "url": url})
|
processed_attachments.append({"type": "record", "url": url})
|
||||||
elif att_type == "video" or filename.endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
elif att_type == "video" or filename.endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
||||||
processed_attachments.append({"type": "video", "url": url})
|
processed_attachments.append({"type": "video", "url": url})
|
||||||
|
else:
|
||||||
|
processed_attachments.append({"type": "file", "url": url, "filename": filename})
|
||||||
|
logger.debug(f"[CrossPlatform] Discord 消息格式化: 识别为文件 {filename}")
|
||||||
else:
|
else:
|
||||||
url = str(att)
|
url = str(att)
|
||||||
|
logger.debug(f"[CrossPlatform] 处理非字典附件: {url}")
|
||||||
if url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')):
|
if url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')):
|
||||||
processed_attachments.append({"type": "image", "url": url})
|
processed_attachments.append({"type": "image", "url": url})
|
||||||
elif url.lower().endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')):
|
elif url.lower().endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')):
|
||||||
processed_attachments.append({"type": "record", "url": url})
|
processed_attachments.append({"type": "record", "url": url})
|
||||||
elif url.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
elif url.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
||||||
processed_attachments.append({"type": "video", "url": url})
|
processed_attachments.append({"type": "video", "url": url})
|
||||||
|
else:
|
||||||
|
filename = os.path.basename(url.split('?')[0]) or "file"
|
||||||
|
processed_attachments.append({"type": "file", "url": url, "filename": filename})
|
||||||
|
logger.debug(f"[CrossPlatform] Discord 消息格式化: 通过扩展名识别为文件 {filename}")
|
||||||
|
|
||||||
|
logger.debug(f"[CrossPlatform] format_discord_to_qq_content 完成: full_message='{full_message}', processed_attachments={processed_attachments}")
|
||||||
return full_message, processed_attachments
|
return full_message, processed_attachments
|
||||||
|
|
||||||
async def format_qq_to_discord_content(
|
async def format_qq_to_discord_content(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ async def send_to_discord(channel_id: int, content: str, attachments: List[dict]
|
|||||||
|
|
||||||
async def send_to_qq(group_id: int, content: str, attachments: List[dict] = None):
|
async def send_to_qq(group_id: int, content: str, attachments: List[dict] = None):
|
||||||
"""发送消息到 QQ 群"""
|
"""发送消息到 QQ 群"""
|
||||||
|
logger.debug(f"[CrossPlatform] send_to_qq: group_id={group_id}, content='{content}', attachments={attachments}")
|
||||||
try:
|
try:
|
||||||
from core.managers.bot_manager import bot_manager
|
from core.managers.bot_manager import bot_manager
|
||||||
from models.message import MessageSegment
|
from models.message import MessageSegment
|
||||||
@@ -59,21 +60,29 @@ async def send_to_qq(group_id: int, content: str, attachments: List[dict] = None
|
|||||||
full_message.append(MessageSegment.record(attachment_url, cache=True, proxy=True, timeout=30))
|
full_message.append(MessageSegment.record(attachment_url, cache=True, proxy=True, timeout=30))
|
||||||
elif att_type == "video":
|
elif att_type == "video":
|
||||||
full_message.append(MessageSegment.video(attachment_url))
|
full_message.append(MessageSegment.video(attachment_url))
|
||||||
|
elif att_type == "file":
|
||||||
|
full_message.append(MessageSegment.file(attachment_url))
|
||||||
|
logger.success(f"[CrossPlatform] 已添加文件到 QQ 消息: {attachment_url}")
|
||||||
else:
|
else:
|
||||||
attachment_url = str(attachment)
|
attachment_url = str(attachment)
|
||||||
if attachment_url.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
if attachment_url.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')):
|
||||||
full_message.append(MessageSegment.video(attachment_url))
|
full_message.append(MessageSegment.video(attachment_url))
|
||||||
elif attachment_url.lower().endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')):
|
elif attachment_url.lower().endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')):
|
||||||
full_message.append(MessageSegment.record(attachment_url, cache=True, proxy=True, timeout=30))
|
full_message.append(MessageSegment.record(attachment_url, cache=True, proxy=True, timeout=30))
|
||||||
|
elif attachment_url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')):
|
||||||
|
image_type = "flash" if attachment_url.lower().endswith('.gif') else None
|
||||||
|
full_message.append(MessageSegment.image(attachment_url, cache=True, proxy=True, timeout=30, image_type=image_type))
|
||||||
else:
|
else:
|
||||||
full_message.append(MessageSegment.image(attachment_url, cache=True, proxy=True, timeout=30))
|
full_message.append(MessageSegment.file(attachment_url))
|
||||||
|
logger.success(f"[CrossPlatform] 已添加文件到 QQ 消息 (通过扩展名识别): {attachment_url}")
|
||||||
|
|
||||||
logger.debug(f"[CrossPlatform] 准备发送消息到 QQ 群 {group_id}: {full_message}")
|
logger.debug(f"[CrossPlatform] 准备发送消息到 QQ 群 {group_id}: {full_message}")
|
||||||
await bot.send_group_msg(group_id, full_message)
|
await bot.send_group_msg(group_id, full_message)
|
||||||
logger.info(f"[CrossPlatform] 消息已发送到 QQ 群 {group_id}")
|
logger.success(f"[CrossPlatform] 消息已发送到 QQ 群 {group_id}: {full_message}")
|
||||||
else:
|
else:
|
||||||
|
logger.debug(f"[CrossPlatform] 准备发送纯文本消息到 QQ 群 {group_id}: {message}")
|
||||||
await bot.send_group_msg(group_id, message)
|
await bot.send_group_msg(group_id, message)
|
||||||
logger.info(f"[CrossPlatform] 消息已发送到 QQ 群 {group_id}")
|
logger.success(f"[CrossPlatform] 纯文本消息已发送到 QQ 群 {group_id}: {message}")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[CrossPlatform] 发送消息到 QQ 群 {group_id} 失败: {e}")
|
logger.error(f"[CrossPlatform] 发送消息到 QQ 群 {group_id} 失败: {e}")
|
||||||
@@ -89,6 +98,7 @@ async def forward_discord_to_qq(
|
|||||||
attachments: List[dict] = None
|
attachments: List[dict] = None
|
||||||
):
|
):
|
||||||
"""将 Discord 消息转发到所有映射的 QQ 群"""
|
"""将 Discord 消息转发到所有映射的 QQ 群"""
|
||||||
|
logger.debug(f"[CrossPlatform] forward_discord_to_qq: channel_id={channel_id}, attachments={attachments}")
|
||||||
if channel_id not in config.CROSS_PLATFORM_MAP:
|
if channel_id not in config.CROSS_PLATFORM_MAP:
|
||||||
logger.warning(f"[CrossPlatform] 未找到 Discord 频道 {channel_id} 的映射配置")
|
logger.warning(f"[CrossPlatform] 未找到 Discord 频道 {channel_id} 的映射配置")
|
||||||
return
|
return
|
||||||
@@ -104,6 +114,8 @@ async def forward_discord_to_qq(
|
|||||||
attachments
|
attachments
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.debug(f"[CrossPlatform] 格式化后的内容: '{formatted_content}', 图片列表: {image_list}")
|
||||||
|
|
||||||
if formatted_content:
|
if formatted_content:
|
||||||
translated_content = await translate_with_deepseek(formatted_content, "zh-CN", channel_id, "en2zh")
|
translated_content = await translate_with_deepseek(formatted_content, "zh-CN", channel_id, "en2zh")
|
||||||
if translated_content != formatted_content:
|
if translated_content != formatted_content:
|
||||||
@@ -111,6 +123,7 @@ async def forward_discord_to_qq(
|
|||||||
|
|
||||||
await send_to_qq(target_qq_group, formatted_content, image_list)
|
await send_to_qq(target_qq_group, formatted_content, image_list)
|
||||||
logger.success(f"[CrossPlatform] Discord 频道 {channel_id} -> QQ 群 {target_qq_group}")
|
logger.success(f"[CrossPlatform] Discord 频道 {channel_id} -> QQ 群 {target_qq_group}")
|
||||||
|
logger.debug(f"[CrossPlatform] send_to_qq 已调用: group_id={target_qq_group}, formatted_content='{formatted_content}', image_list={image_list}")
|
||||||
|
|
||||||
async def forward_qq_to_discord(
|
async def forward_qq_to_discord(
|
||||||
qq_nickname: str,
|
qq_nickname: str,
|
||||||
|
|||||||
Reference in New Issue
Block a user