From 67431d87fbbd4d4b2a077bb75ecf718ff4c3508b Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Mon, 28 Apr 2025 23:31:45 +0800 Subject: [PATCH 1/4] fix: gewechat file --- .../core/platform/sources/gewechat/client.py | 57 ++++++++++++++----- .../sources/gewechat/gewechat_event.py | 54 +++++++++--------- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 360dddff5..44c549527 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -3,6 +3,7 @@ import base64 import datetime import os import re +import uuid import threading import aiohttp @@ -63,7 +64,7 @@ class SimpleGewechatClient: "/astrbot-gewechat/callback", view_func=self._callback, methods=["POST"] ) self.server.add_url_rule( - "/astrbot-gewechat/file/", + "/astrbot-gewechat/file/", view_func=self._handle_file, methods=["GET"], ) @@ -81,6 +82,9 @@ class SimpleGewechatClient: self.shutdown_event = asyncio.Event() + self.staged_files = {} + """存储了允许外部访问的文件列表。auth_token: file_path。通过 register_file 方法注册。""" + async def get_token_id(self): """获取 Gewechat Token。""" async with aiohttp.ClientSession() as session: @@ -302,9 +306,31 @@ class SimpleGewechatClient: return quart.jsonify({"r": "AstrBot ACK"}) - async def _handle_file(self, file_id): - file_path = f"data/temp/{file_id}" - return await quart.send_file(file_path) + async def _register_file(self, file_path: str) -> str: + """向 AstrBot 回调服务器 注册一个允许外部访问的文件。 + + Args: + file_path (str): 文件路径。 + Returns: + str: 返回一个 auth_token,文件路径为 file_path。通过 /astrbot-gewechat/file/auth_token 得到文件。 + """ + if not os.path.exists(file_path): + raise Exception(f"文件不存在: {file_path}") + + file_token = str(uuid.uuid4()) + self.staged_files[file_token] = file_path + return file_token + + async def _handle_file(self, file_token): + if file_token not in self.staged_files: + logger.warning(f"请求的文件 {file_token} 不存在。") + return quart.abort(404) + if not os.path.exists(self.staged_files[file_token]): + logger.warning(f"请求的文件 {self.staged_files[file_token]} 不存在。") + return quart.abort(404) + file_token = self.staged_files[file_token] + self.staged_files.pop(file_token, None) + return await quart.send_file(file_token) async def _set_callback_url(self): logger.info("设置回调,请等待...") @@ -454,17 +480,18 @@ class SimpleGewechatClient: "此次登录需要安全验证码,请在管理面板聊天页输入 /gewe_code 验证码 来验证,如 /gewe_code 123456" ) else: - status = json_blob["data"]["status"] - nickname = json_blob["data"].get("nickName", "") - if status == 1: - logger.info(f"等待确认...{nickname}") - elif status == 2: - logger.info(f"绿泡泡平台登录成功: {nickname}") - break - elif status == 0: - logger.info("等待扫码...") - else: - logger.warning(f"未知状态: {status}") + if "status" in json_blob["data"]: + status = json_blob["data"]["status"] + nickname = json_blob["data"].get("nickName", "") + if status == 1: + logger.info(f"等待确认...{nickname}") + elif status == 2: + logger.info(f"绿泡泡平台登录成功: {nickname}") + break + elif status == 0: + logger.info("等待扫码...") + else: + logger.warning(f"未知状态: {status}") await asyncio.sleep(5) if appid: diff --git a/astrbot/core/platform/sources/gewechat/gewechat_event.py b/astrbot/core/platform/sources/gewechat/gewechat_event.py index 5efff3f5a..3a62b7a81 100644 --- a/astrbot/core/platform/sources/gewechat/gewechat_event.py +++ b/astrbot/core/platform/sources/gewechat/gewechat_event.py @@ -83,15 +83,9 @@ class GewechatPlatformEvent(AstrMessageEvent): elif isinstance(comp, Image): img_path = await comp.convert_to_file_path() - - # 检查 record_path 是否在 data/temp 目录中 - temp_directory = os.path.abspath("data/temp") - if os.path.commonpath([temp_directory, img_path]) != temp_directory: - with open(img_path, "rb") as f: - img_path = save_temp_img(f.read()) - - file_id = os.path.basename(img_path) - img_url = f"{client.file_server_url}/{file_id}" + # 为了安全,向 AstrBot 回调服务注册可被 gewechat 访问的文件,并获得文件 token + token = await client._register_file(img_path) + img_url = f"{client.file_server_url}/{token}" logger.debug(f"gewe callback img url: {img_url}") await client.post_image(to_wxid, img_url) elif isinstance(comp, Video): @@ -110,20 +104,29 @@ class GewechatPlatformEvent(AstrMessageEvent): video_url = comp.file # 根据 url 下载视频 - video_filename = f"{uuid.uuid4()}.mp4" - video_path = f"data/temp/{video_filename}" - await download_file(video_url, video_path) + if video_url.startswith("http"): + video_filename = f"{uuid.uuid4()}.mp4" + video_path = f"data/temp/{video_filename}" + await download_file(video_url, video_path) + else: + video_path = video_url + + video_token = await client._register_file(video_path) + video_callback_url = f"{client.file_server_url}/{video_token}" # 获取视频第一帧 - thumb_path = f"data/temp/{uuid.uuid4()}.jpg" + thumb_path = f"data/temp/gewechat_video_thumb_{uuid.uuid4()}.jpg" + + video_path = video_path.replace(" ", "\\ ") try: ff = FFmpeg() - command = f'-i "{video_path}" -ss 0 -vframes 1 "{thumb_path}"' + command = f"-i {video_path} -ss 0 -vframes 1 {thumb_path}" ff.options(command) - thumb_file_id = os.path.basename(thumb_path) - thumb_url = f"{client.file_server_url}/{thumb_file_id}" + thumb_token = await client._register_file(thumb_path) + thumb_url = f"{client.file_server_url}/{thumb_token}" except Exception as e: logger.error(f"获取视频第一帧失败: {e}") + # 获取视频时长 try: from pyffmpeg import FFprobe @@ -138,15 +141,12 @@ class GewechatPlatformEvent(AstrMessageEvent): logger.error(f"获取时长失败: {e}") video_duration = 10 - file_id = os.path.basename(video_path) - video_url = f"{client.file_server_url}/{file_id}" + # 发送视频 await client.post_video( - to_wxid, video_url, thumb_url, video_duration + to_wxid, video_callback_url, thumb_url, video_duration ) - # 删除临时视频和缩略图文件 - if os.path.exists(video_path): - os.remove(video_path) + # 删除临时缩略图文件 if os.path.exists(thumb_path): os.remove(thumb_path) elif isinstance(comp, Record): @@ -163,8 +163,8 @@ class GewechatPlatformEvent(AstrMessageEvent): logger.info("Silk 语音文件格式转换至: " + record_path) if duration == 0: duration = get_wav_duration(record_path) - file_id = os.path.basename(silk_path) - record_url = f"{client.file_server_url}/{file_id}" + token = await client._register_file(silk_path) + record_url = f"{client.file_server_url}/{token}" logger.debug(f"gewe callback record url: {record_url}") await client.post_voice(to_wxid, record_url, duration * 1000) elif isinstance(comp, File): @@ -177,10 +177,10 @@ class GewechatPlatformEvent(AstrMessageEvent): else: file_path = file_path - file_id = os.path.basename(file_path) - file_url = f"{client.file_server_url}/{file_id}" + token = await client._register_file(file_path) + file_url = f"{client.file_server_url}/{token}" logger.debug(f"gewe callback file url: {file_url}") - await client.post_file(to_wxid, file_url, file_id) + await client.post_file(to_wxid, file_url, file_name) elif isinstance(comp, Emoji): await client.post_emoji(to_wxid, comp.md5, comp.md5_len, comp.cdnurl) elif isinstance(comp, At): From 6d49bf5346bdf97d6af8b53454f96d9d99396714 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:49:36 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=20=5Fhandle=5Ffil?= =?UTF-8?q?e=20=E6=96=B9=E6=B3=95=E4=B8=8B=E7=9A=84=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/platform/sources/gewechat/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 44c549527..43dcf3eb6 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -328,9 +328,9 @@ class SimpleGewechatClient: if not os.path.exists(self.staged_files[file_token]): logger.warning(f"请求的文件 {self.staged_files[file_token]} 不存在。") return quart.abort(404) - file_token = self.staged_files[file_token] + file_path = self.staged_files[file_token] self.staged_files.pop(file_token, None) - return await quart.send_file(file_token) + return await quart.send_file(file_path) async def _set_callback_url(self): logger.info("设置回调,请等待...") From bf3ca13961d14a8454db736918c83f1bf3f71e7d Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Wed, 30 Apr 2025 00:03:21 +0800 Subject: [PATCH 3/4] Update astrbot/core/platform/sources/gewechat/client.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 渡鸦95676 --- .../core/platform/sources/gewechat/client.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 43dcf3eb6..0256afcee 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -314,23 +314,25 @@ class SimpleGewechatClient: Returns: str: 返回一个 auth_token,文件路径为 file_path。通过 /astrbot-gewechat/file/auth_token 得到文件。 """ - if not os.path.exists(file_path): - raise Exception(f"文件不存在: {file_path}") + async with self.lock: + if not os.path.exists(file_path): + raise Exception(f"文件不存在: {file_path}") - file_token = str(uuid.uuid4()) - self.staged_files[file_token] = file_path - return file_token + file_token = str(uuid.uuid4()) + self.staged_files[file_token] = file_path + return file_token async def _handle_file(self, file_token): - if file_token not in self.staged_files: - logger.warning(f"请求的文件 {file_token} 不存在。") - return quart.abort(404) - if not os.path.exists(self.staged_files[file_token]): - logger.warning(f"请求的文件 {self.staged_files[file_token]} 不存在。") - return quart.abort(404) - file_path = self.staged_files[file_token] - self.staged_files.pop(file_token, None) - return await quart.send_file(file_path) + async with self.lock: + if file_token not in self.staged_files: + logger.warning(f"请求的文件 {file_token} 不存在。") + return quart.abort(404) + if not os.path.exists(self.staged_files[file_token]): + logger.warning(f"请求的文件 {self.staged_files[file_token]} 不存在。") + return quart.abort(404) + file_path = self.staged_files[file_token] + self.staged_files.pop(file_token, None) + return await quart.send_file(file_path) async def _set_callback_url(self): logger.info("设置回调,请等待...") From 6ec643e9d1af848a4b998d25c239cbeb65b0b9c9 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 30 Apr 2025 00:51:49 +0800 Subject: [PATCH 4/4] fix: add self.lock --- astrbot/core/platform/sources/gewechat/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/astrbot/core/platform/sources/gewechat/client.py b/astrbot/core/platform/sources/gewechat/client.py index 0256afcee..59b4cc195 100644 --- a/astrbot/core/platform/sources/gewechat/client.py +++ b/astrbot/core/platform/sources/gewechat/client.py @@ -85,6 +85,8 @@ class SimpleGewechatClient: self.staged_files = {} """存储了允许外部访问的文件列表。auth_token: file_path。通过 register_file 方法注册。""" + self.lock = asyncio.Lock() + async def get_token_id(self): """获取 Gewechat Token。""" async with aiohttp.ClientSession() as session: