From e61383398d4dbb1bc7d67ba1c0469a7942f17c9f Mon Sep 17 00:00:00 2001 From: K2Cr2O1 <2221577113@qq.com> Date: Sun, 15 Feb 2026 09:59:02 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(douyin):=20=E6=B7=BB=E5=8A=A0=E5=A4=9A?= =?UTF-8?q?API=E5=B9=B6=E5=8F=91=E8=A7=A3=E6=9E=90=E6=8A=96=E9=9F=B3?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构抖音解析器,将原有单API解析拆分为独立方法,并新增mmp API作为备选方案 使用asyncio.as_completed并发请求多个API,取最快返回的有效结果 --- plugins/web_parser/parsers/douyin.py | 104 +++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 13 deletions(-) diff --git a/plugins/web_parser/parsers/douyin.py b/plugins/web_parser/parsers/douyin.py index 72cd12b..933a4db 100644 --- a/plugins/web_parser/parsers/douyin.py +++ b/plugins/web_parser/parsers/douyin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import re import aiohttp +import asyncio from typing import Optional, Dict, Any, List from core.utils.logger import logger @@ -24,9 +25,9 @@ class DouyinParser(BaseParser): # 消息去重缓存 self.processed_messages: TTLCache[int, bool] = TTLCache(maxsize=100, ttl=10) - async def parse(self, url: str) -> Optional[Dict[str, Any]]: + async def _parse_api_xhus(self, url: str) -> Optional[Dict[str, Any]]: """ - 解析抖音视频信息 + 使用 xhus API 解析抖音视频 Args: url (str): 抖音视频URL @@ -35,31 +36,29 @@ class DouyinParser(BaseParser): Optional[Dict[str, Any]]: 视频信息字典,如果失败则返回None """ try: - # 使用第三方API解析抖音视频 api_url = f"http://api.xhus.cn/api/douyin?url={url}" session = self.get_session() async with session.get(api_url, headers=self.HEADERS, timeout=aiohttp.ClientTimeout(total=10)) as response: if response.status != 200: - logger.error(f"[{self.name}] API请求失败,状态码: {response.status}") + logger.error(f"[{self.name}] xhus API请求失败,状态码: {response.status}") return None response_data = await response.json() if not isinstance(response_data, dict): - logger.error(f"[{self.name}] API返回格式错误: {response_data}") + logger.error(f"[{self.name}] xhus API返回格式错误: {response_data}") return None if response_data.get("code") != 200: - logger.error(f"[{self.name}] API返回错误: {response_data}") + logger.error(f"[{self.name}] xhus API返回错误: {response_data}") return None data = response_data.get("data", {}) if not data: - logger.error(f"[{self.name}] API返回数据为空") + logger.error(f"[{self.name}] xhus API返回数据为空") return None - # 转换API响应格式 return { "type": "video" if not data.get("images") or not isinstance(data.get("images"), list) else "image", "video_url": data.get("url", ""), @@ -74,13 +73,92 @@ class DouyinParser(BaseParser): "music": data.get("music", {}), } - except (aiohttp.ClientError, KeyError, AttributeError, ValueError) as e: - logger.error(f"[{self.name}] 解析抖音视频信息失败: {e}") - logger.debug(f"失败的URL: {url}") except Exception as e: - logger.error(f"[{self.name}] 解析抖音视频时发生未知错误: {e}") - logger.debug(f"失败的URL: {url}") + logger.error(f"[{self.name}] xhus API解析失败: {e}") + return None + + async def _parse_api_mmp(self, url: str) -> Optional[Dict[str, Any]]: + """ + 使用 mmp API 解析抖音视频 + Args: + url (str): 抖音视频URL + + Returns: + Optional[Dict[str, Any]]: 视频信息字典,如果失败则返回None + """ + try: + api_url = f"https://api.mmp.cc/api/Jiexi?url={url}" + + session = self.get_session() + async with session.get(api_url, headers=self.HEADERS, timeout=aiohttp.ClientTimeout(total=10)) as response: + if response.status != 200: + logger.error(f"[{self.name}] mmp API请求失败,状态码: {response.status}") + return None + + response_data = await response.json() + + if not isinstance(response_data, dict): + logger.error(f"[{self.name}] mmp API返回格式错误: {response_data}") + return None + + if response_data.get("code") != 200: + logger.error(f"[{self.name}] mmp API返回错误: {response_data}") + return None + + data = response_data.get("data", {}) + if not data: + logger.error(f"[{self.name}] mmp API返回数据为空") + return None + + return { + "type": data.get("type", "video"), + "video_url": data.get("video_url", ""), + "video_url_HQ": data.get("video_url_HQ", ""), + "nickname": data.get("nickname", "未知作者"), + "desc": data.get("desc", "无描述"), + "aweme_id": data.get("aweme_id", ""), + "like": data.get("like", 0), + "cover": data.get("cover", ""), + "time": data.get("time", 0), + "author_avatar": data.get("author_avatar", ""), + "music": data.get("music", {}), + } + + except Exception as e: + logger.error(f"[{self.name}] mmp API解析失败: {e}") + return None + + async def parse(self, url: str) -> Optional[Dict[str, Any]]: + """ + 解析抖音视频信息(并发请求多个API,取最快返回的结果) + + Args: + url (str): 抖音视频URL + + Returns: + Optional[Dict[str, Any]]: 视频信息字典,如果失败则返回None + """ + async def try_api(coro, api_name: str) -> tuple: + try: + result = await coro + return (result, api_name) + except Exception as e: + logger.error(f"[{self.name}] {api_name} API异常: {e}") + return (None, api_name) + + tasks = [ + try_api(self._parse_api_xhus(url), "xhus"), + try_api(self._parse_api_mmp(url), "mmp"), + ] + + for coro in asyncio.as_completed(tasks): + result, api_name = await coro + if result: + logger.info(f"[{self.name}] 使用 {api_name} API 成功解析") + return result + + logger.error(f"[{self.name}] 所有API解析均失败") return None async def get_real_url(self, short_url: str) -> Optional[str]: From e031afaa4a9c45d300d34eb01dd2d7d22b758159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=80=E9=93=AC=E9=85=B8=E9=92=BE?= <148796996+K2cr2O1@users.noreply.github.com> Date: Sun, 1 Mar 2026 11:27:57 +0800 Subject: [PATCH 2/6] =?UTF-8?q?Change=20command=20prefix=20from=20'?= =?UTF-8?q?=E3=80=82'=20to=20'/'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.toml b/config.toml index 25c55be..cb60f44 100644 --- a/config.toml +++ b/config.toml @@ -19,7 +19,7 @@ token = "" # Bot 基础配置 [bot] # 命令前缀列表 -command = ["。"] +command = ["/"] # 是否忽略自己的消息 ignore_self_message = true # 权限不足时的消息 From 4a7267037913b21abd9a0aa4ace5c1d86e99fd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=80=E9=93=AC=E9=85=B8=E9=92=BE?= <148796996+K2cr2O1@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:58:31 +0800 Subject: [PATCH 3/6] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3334885..4dabbd9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ anyio==4.12.1 astroid==4.0.3 attrs==25.4.0 beautifulsoup4==4.14.3 -bilibili-api-python==2024.12.1 +bilibili-api-python bs4==0.0.2 cachetools==6.2.4 certifi==2026.1.4 From ae5fe32ba7fcc31f4f4221bf98dc564658a2b0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=80=E9=93=AC=E9=85=B8=E9=92=BE?= <148796996+K2cr2O1@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:56:15 +0800 Subject: [PATCH 4/6] Update main.yml --- .github/workflows/main.yml | 76 +++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b343d6d..cc72412 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,4 @@ -name: Auto Deploy NeoBot (Full Env Secrets) - -# 触发条件:推送到main分支 或 手动触发 +name: Auto Deploy NeoBot (FRP + SSH 密码登录) on: push: branches: [ main ] @@ -8,50 +6,60 @@ on: jobs: deploy-to-server: - # 关联你的仓库环境(ENV) environment: ENV runs-on: ubuntu-latest + timeout-minutes: 10 # 防超时堵塞 steps: + # ========== 1. 检查环境密钥配置 ========== - name: 检查环境密钥配置 run: | echo "✅ 已关联环境: ${{ github.environment }}" - echo "✅ API_URL 密钥是否存在: ${{ secrets.API_URL != '' }}" - echo "✅ API_TOKEN 密钥是否存在: ${{ secrets.NEOBOT_DEPLOY_TOKEN != '' }}" + # 仅检查密码登录必需的3个密钥 + echo "✅ PROD_SERVER_HOST 密钥是否存在: ${{ secrets.PROD_SERVER_HOST != '' }}" + echo "✅ PROD_SERVER_USER 密钥是否存在: ${{ secrets.PROD_SERVER_USER != '' }}" + echo "✅ PROD_SERVER_PASS 密钥是否存在: ${{ secrets.PROD_SERVER_PASS != '' }}" - - name: 调用部署API - env: - # 从环境密钥中读取API地址和Token(均为密文) - API_URL: ${{ secrets.API_URL }} - API_TOKEN: ${{ secrets.NEOBOT_DEPLOY_TOKEN }} + # ========== 2. 安装 sshpass(密码登录必需) ========== + - name: 安装 sshpass 工具 run: | - # 安装jq用于解析JSON - sudo apt-get update && sudo apt-get install -y jq - - # 打印关键信息(脱敏,仅验证是否读取到值) - echo "📌 调用的API地址(脱敏): $(echo $API_URL | sed 's/http:\/\///; s/\/deploy//')" - - # 发送POST请求到部署API(所有配置均来自密钥) - RESPONSE=$(curl -s -X POST \ - $API_URL \ - -H "Content-Type: application/json" \ - -H "X-API-Token: $API_TOKEN" \ - -d '{"script_name":"deploy.sh"}') - - # 打印完整响应(便于调试) - echo "📝 API响应详情:" - echo $RESPONSE | jq . - - # 解析status字段判断部署结果 - STATUS=$(echo $RESPONSE | jq -r '.status') - if [ "$STATUS" = "success" ]; then - echo "✅ 部署成功!" + sudo apt-get update && sudo apt-get install -y sshpass + + # ========== 3. 密码登录服务器 + 执行部署 ========== + - name: 执行FRP穿透部署(用户名+密码登录) + id: ssh_deploy_step + continue-on-error: true + run: | + # 核心:sshpass 实现密码登录,-p 8000 是FRP转发端口 + sshpass -p "${{ secrets.PROD_SERVER_PASS }}" \ + ssh -o StrictHostKeyChecking=no -p 8000 ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' + set -e + # 适配NeoBot项目:更新依赖+重启systemd服务 + cd /home/k/NeoBot + pip install -r requirements.txt --upgrade --timeout 300 --only-binary=:all: + sudo systemctl daemon-reload + sudo systemctl restart neobot + # 验证服务状态 + if ! sudo systemctl is-active --quiet neobot; then + echo "❌ NeoBot服务启动失败,最后10行日志:" + sudo journalctl -u neobot -n 10 --no-pager + exit 1 + fi + echo "✅ NeoBot服务重启成功" + EOF + + # ========== 4. 判定最终部署结果 ========== + - name: 判定最终部署结果 + run: | + if [ ${{ steps.ssh_deploy_step.outcome }} = 'success' ]; then + echo "✅ 最终部署成功!已更新依赖并重启NeoBot systemd服务" exit 0 else - echo "❌ 部署失败!错误信息:$(echo $RESPONSE | jq -r '.message')" + echo "❌ 最终部署失败!核心SSH部署步骤执行出错" exit 1 fi + # ========== 5. 部署失败通知(可选) ========== - name: 部署失败通知(可选) if: failure() run: | - echo "⚠️ 部署失败,可在此添加通知逻辑" + echo "⚠️ 部署失败,可在此添加钉钉/企业微信通知逻辑" From 7226c3a28dbc71f2dd816cbff5cc8fb52acce968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=80=E9=93=AC=E9=85=B8=E9=92=BE?= <148796996+K2cr2O1@users.noreply.github.com> Date: Wed, 11 Mar 2026 18:28:48 +0800 Subject: [PATCH 5/6] Modify deployment script to include git pull Update dependencies and restart the NeoBot service after pulling the latest code. --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc72412..67ee7e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,7 @@ jobs: set -e # 适配NeoBot项目:更新依赖+重启systemd服务 cd /home/k/NeoBot + git pull pip install -r requirements.txt --upgrade --timeout 300 --only-binary=:all: sudo systemctl daemon-reload sudo systemctl restart neobot From bc18449300065733e87709d9a108d76906202006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=80=E9=93=AC=E9=85=B8=E9=92=BE?= <148796996+K2cr2O1@users.noreply.github.com> Date: Sat, 21 Mar 2026 14:27:23 +0800 Subject: [PATCH 6/6] =?UTF-8?q?fix(discord):=20=E4=BF=AE=E5=A4=8D=20WebSoc?= =?UTF-8?q?ket=20=E8=BF=9E=E6=8E=A5=E6=A3=80=E6=B5=8B=E5=B9=B6=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E8=B7=A8=E5=B9=B3=E5=8F=B0=E6=96=87=E4=BB=B6=E5=A4=84?= =?UTF-8?q?=E7=90=86=20(#73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 Discord WebSocket 连接检测逻辑,使用正确的属性检查连接状态 为跨平台消息处理添加文件类型支持,并增加详细的调试日志 优化附件处理逻辑,确保所有文件类型都能正确识别和转发 --- adapters/discord_adapter.py | 4 +++- adapters/router.py | 16 ++++++++++++++-- plugins/discord-cross/handlers.py | 29 +++++++++++++++++++++++++++++ plugins/discord-cross/parser.py | 11 +++++++++++ plugins/discord-cross/sender.py | 19 ++++++++++++++++--- 5 files changed, 73 insertions(+), 6 deletions(-) diff --git a/adapters/discord_adapter.py b/adapters/discord_adapter.py index 3f80e8f..b9565e5 100644 --- a/adapters/discord_adapter.py +++ b/adapters/discord_adapter.py @@ -327,7 +327,9 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object): try: 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 连接已关闭,触发重连...") await self.close() break diff --git a/adapters/router.py b/adapters/router.py index b182876..245366c 100644 --- a/adapters/router.py +++ b/adapters/router.py @@ -281,24 +281,36 @@ class DiscordToOneBotConverter: # 添加附件信息 if discord_message.attachments: + self.logger.debug(f"[DiscordToOneBotConverter] 检测到 {len(discord_message.attachments)} 个附件") for attachment in discord_message.attachments: filename = attachment.filename.lower() + self.logger.debug(f"[DiscordToOneBotConverter] 处理附件: {attachment.filename}, MIME: {attachment.content_type}") # 检查是否是语音文件 if filename.endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')): seg = OneBotMessageSegment.record(attachment.url) seg.data["filename"] = attachment.filename message_list.append(seg) raw_message += f"\n[语音: {attachment.filename}]" + self.logger.debug(f"[DiscordToOneBotConverter] 识别为语音文件: {attachment.filename}") elif filename.endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')): seg = OneBotMessageSegment.video(attachment.url) seg.data["filename"] = attachment.filename message_list.append(seg) raw_message += f"\n[视频: {attachment.filename}]" - else: - seg = OneBotMessageSegment.image(attachment.url) + self.logger.debug(f"[DiscordToOneBotConverter] 识别为视频文件: {attachment.filename}") + 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 message_list.append(seg) 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) 信息 if hasattr(discord_message, 'stickers') and discord_message.stickers: diff --git a/plugins/discord-cross/handlers.py b/plugins/discord-cross/handlers.py index 5441b5b..a8a9cf0 100644 --- a/plugins/discord-cross/handlers.py +++ b/plugins/discord-cross/handlers.py @@ -27,6 +27,7 @@ async def handle_discord_message( return 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) 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" attachments.append({"type": "record", "url": file_url, "filename": file_name}) 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": qq_id = segment.data.get("qq") 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: return + logger.debug(f"[CrossPlatform] handle_discord_message_event 触发: {event}") if not hasattr(event, '_is_discord_message'): + logger.debug(f"[CrossPlatform] 事件没有 _is_discord_message 属性,跳过") return + logger.debug(f"[CrossPlatform] 检测到 Discord 事件") discord_channel_id = getattr(event, 'discord_channel_id', None) if discord_channel_id is None: + logger.debug(f"[CrossPlatform] discord_channel_id 为 None") return content = "" attachments = [] + logger.debug(f"[CrossPlatform] 开始处理 Discord 事件消息: channel_id={discord_channel_id}") + if hasattr(event, 'message') and isinstance(event.message, list): for segment in event.message: 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} if attachment_item not in attachments: 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: content = event.raw_message or "" content = content.strip() + logger.debug(f"[CrossPlatform] Discord 消息内容: '{content}', 附件数量: {len(attachments)}") + discord_username = getattr(event, 'discord_username', 'Unknown') 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( username=discord_username, discriminator=discord_discriminator, diff --git a/plugins/discord-cross/parser.py b/plugins/discord-cross/parser.py index 08a579e..4ccb307 100644 --- a/plugins/discord-cross/parser.py +++ b/plugins/discord-cross/parser.py @@ -244,6 +244,7 @@ async def format_discord_to_qq_content( attachments: List[dict] = None ) -> tuple[str, List[dict]]: """将 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) message_header = f"{discord_username}:" @@ -256,6 +257,7 @@ async def format_discord_to_qq_content( processed_attachments = [] if attachments: + logger.debug(f"[CrossPlatform] 处理附件: {attachments}") for att in attachments: if isinstance(att, dict): url = att.get("url", "") @@ -268,15 +270,24 @@ async def format_discord_to_qq_content( processed_attachments.append({"type": "record", "url": url}) elif att_type == "video" or filename.endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')): processed_attachments.append({"type": "video", "url": url}) + else: + processed_attachments.append({"type": "file", "url": url, "filename": filename}) + logger.debug(f"[CrossPlatform] Discord 消息格式化: 识别为文件 {filename}") else: url = str(att) + logger.debug(f"[CrossPlatform] 处理非字典附件: {url}") if url.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.webp')): processed_attachments.append({"type": "image", "url": url}) elif url.lower().endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')): processed_attachments.append({"type": "record", "url": url}) elif url.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')): 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 async def format_qq_to_discord_content( diff --git a/plugins/discord-cross/sender.py b/plugins/discord-cross/sender.py index 1337086..32ac5d4 100644 --- a/plugins/discord-cross/sender.py +++ b/plugins/discord-cross/sender.py @@ -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): """发送消息到 QQ 群""" + logger.debug(f"[CrossPlatform] send_to_qq: group_id={group_id}, content='{content}', attachments={attachments}") try: from core.managers.bot_manager import bot_manager 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)) elif att_type == "video": 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: attachment_url = str(attachment) if attachment_url.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv')): full_message.append(MessageSegment.video(attachment_url)) elif attachment_url.lower().endswith(('.amr', '.silk', '.mp3', '.wav', '.ogg', '.m4a')): 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: - 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}") 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: + logger.debug(f"[CrossPlatform] 准备发送纯文本消息到 QQ 群 {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 except Exception as e: logger.error(f"[CrossPlatform] 发送消息到 QQ 群 {group_id} 失败: {e}") @@ -89,6 +98,7 @@ async def forward_discord_to_qq( attachments: List[dict] = None ): """将 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: logger.warning(f"[CrossPlatform] 未找到 Discord 频道 {channel_id} 的映射配置") return @@ -104,6 +114,8 @@ async def forward_discord_to_qq( attachments ) + logger.debug(f"[CrossPlatform] 格式化后的内容: '{formatted_content}', 图片列表: {image_list}") + if formatted_content: translated_content = await translate_with_deepseek(formatted_content, "zh-CN", channel_id, "en2zh") 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) 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( qq_nickname: str,