From 31c7768ca009fe9695f2037ea2743c93242bc896 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 30 Apr 2025 21:47:14 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20QQ=20=E4=B8=8B=E8=87=AA=E5=8A=A8=E4=B8=8B=E8=BD=BD=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/message/components.py | 7 ++-- .../aiocqhttp/aiocqhttp_platform_adapter.py | 40 +++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index 5020d6b26..4f9c3afb3 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -29,7 +29,7 @@ import uuid import typing as T from enum import Enum from pydantic.v1 import BaseModel -from astrbot.core.utils.io import download_image_by_url, file_to_base64 +from astrbot.core.utils.io import download_image_by_url, file_to_base64, download_file class ComponentType(Enum): @@ -552,17 +552,18 @@ class Unknown(BaseMessageComponent): class File(BaseMessageComponent): """ - 目前此消息段只适配了 Napcat。 + 文件消息段 """ type: ComponentType = "File" name: T.Optional[str] = "" # 名字 - file: T.Optional[str] = "" # url(本地路径) + file: T.Optional[str] = "" # url 或者本地路径 def __init__(self, name: str, file: str): super().__init__(name=name, file=file) + class WechatEmoji(BaseMessageComponent): type: ComponentType = "WechatEmoji" md5: T.Optional[str] = "" diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 9cf6b5bab..11c25976d 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -225,32 +225,30 @@ class AiocqhttpAdapter(Platform): if m["data"].get("url") and m["data"].get("url").startswith("http"): # Lagrange logger.info("guessing lagrange") - file_name = m["data"].get("file_name", "file") - path = os.path.join("data/temp", file_name) - await download_file(m["data"]["url"], path) - - m["data"] = {"file": path, "name": file_name} - a = ComponentTypes[t](**m["data"]) # noqa: F405 - abm.message.append(a) - + abm.message.append(File(name=file_name, file=m["data"]["url"])) else: try: - # Napcat, LLBot - ret = await self.bot.call_action( - action="get_file", - file_id=event.message[0]["data"]["file_id"], - ) - if not ret.get("file", None): - raise ValueError(f"无法解析文件响应: {ret}") - if not os.path.exists(ret["file"]): - raise FileNotFoundError( - f"文件不存在或者权限问题: {ret['file']}。如果您使用 Docker 部署了 AstrBot 或者消息协议端(Napcat等),请先映射路径。如果路径在 /root 目录下,请用 sudo 打开 AstrBot" + # Napcat + ret = None + if abm.type == MessageType.GROUP_MESSAGE: + ret = await self.bot.call_action( + action="get_group_file_url", + file_id=event.message[0]["data"]["file_id"], + group_id=event.group_id, ) + elif abm.type == MessageType.FRIEND_MESSAGE: + ret = await self.bot.call_action( + action="get_private_file_url", + file_id=event.message[0]["data"]["file_id"], + ) + if ret and "data" in ret and "url" in ret["data"]: + file_url = ret["data"]["url"] # https + a = File(name="", file=file_url) + abm.message.append(a) + else: + logger.error(f"获取文件失败: {ret}") - m["data"] = {"file": ret["file"], "name": ret["file_name"]} - a = ComponentTypes[t](**m["data"]) # noqa: F405 - abm.message.append(a) except ActionFailed as e: logger.error(f"获取文件失败: {e},此消息段将被忽略。") except BaseException as e: From 18bd4c54f4852f67b5e2b6fcf2a9378a9be2705f Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 30 Apr 2025 22:31:56 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sources/aiocqhttp/aiocqhttp_platform_adapter.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 11c25976d..58f4aa522 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -1,4 +1,3 @@ -import os import time import asyncio import logging @@ -21,7 +20,6 @@ from .aiocqhttp_message_event import AiocqhttpMessageEvent from astrbot.core.platform.astr_message_event import MessageSesion from ...register import register_platform_adapter from aiocqhttp.exceptions import ActionFailed -from astrbot.core.utils.io import download_file @register_platform_adapter( @@ -242,8 +240,8 @@ class AiocqhttpAdapter(Platform): action="get_private_file_url", file_id=event.message[0]["data"]["file_id"], ) - if ret and "data" in ret and "url" in ret["data"]: - file_url = ret["data"]["url"] # https + if ret and "url" in ret: + file_url = ret["url"] # https a = File(name="", file=file_url) abm.message.append(a) else: From 321b04772cc275375995935ab112543236f0ad1e Mon Sep 17 00:00:00 2001 From: anka <1350989414@qq.com> Date: Thu, 1 May 2025 01:16:30 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=F0=9F=8D=A9=E5=B0=86=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E8=B7=AF=E5=BE=84=E5=92=8Curl=E5=88=86=E7=A6=BB,=20?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E6=9C=AC=E5=9C=B0=E6=96=87=E4=BB=B6=E6=97=B6?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E4=B8=8B=E8=BD=BD=E6=8E=A5=E5=8F=A3,=20?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E5=90=91=E5=89=8D=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/message/components.py | 83 ++++++++++++++++++- .../aiocqhttp/aiocqhttp_platform_adapter.py | 8 +- .../platform/sources/telegram/tg_adapter.py | 12 ++- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index 4f9c3afb3..74538d097 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -26,9 +26,11 @@ import base64 import json import os import uuid +import asyncio import typing as T from enum import Enum from pydantic.v1 import BaseModel +from astrbot.core import logger from astrbot.core.utils.io import download_image_by_url, file_to_base64, download_file @@ -557,11 +559,86 @@ class File(BaseMessageComponent): type: ComponentType = "File" name: T.Optional[str] = "" # 名字 - file: T.Optional[str] = "" # url 或者本地路径 + _file: T.Optional[str] = "" # 本地路径 + url: T.Optional[str] = "" # url + _downloaded: bool = False # 是否已经下载 - def __init__(self, name: str, file: str): - super().__init__(name=name, file=file) + def __init__(self, name: str = "", file: str = "", url: str = ""): + super().__init__(name=name, _file=file, url=url) + @property + def file(self) -> str: + """ + 获取文件路径,如果文件不存在但有URL,则同步下载文件 + + Returns: + str: 文件路径 + """ + if self._file and os.path.exists(self._file): + return self._file + + if self.url and not self._downloaded: + try: + loop = asyncio.get_event_loop() + if loop.is_running(): + logger.warning( + "不可以在异步上下文中同步等待下载! 请使用 await get_file() 代替" + ) + return "" + else: + # 等待下载完成 + loop.run_until_complete(self._download_file()) + + if self._file and os.path.exists(self._file): + return self._file + except Exception as e: + logger.error(f"文件下载失败: {e}") + + return "" + + @file.setter + def file(self, value: str): + """ + 向前兼容, 设置file属性, 传入的参数可能是文件路径或URL + + Args: + value (str): 文件路径或URL + """ + if value.startswith("http://") or value.startswith("https://"): + self.url = value + else: + self._file = value + + async def get_file(self) -> str: + """ + 异步获取文件 + To 插件开发者: 请注意在使用后清理下载的文件, 以免占用过多空间 + + Returns: + str: 文件路径 + """ + if self._file and os.path.exists(self._file): + return self._file + + if self.url: + await self._download_file() + return self._file + + return "" + + async def _download_file(self): + """下载文件""" + if self._downloaded: + return + + os.makedirs("data/download", exist_ok=True) + filename = self.name or f"{uuid.uuid4().hex}" + file_path = f"data/download/{filename}" + + await download_file(self.url, file_path) + + self._file = file_path + self._downloaded = True class WechatEmoji(BaseMessageComponent): diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py index 58f4aa522..edf7b1216 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py @@ -163,7 +163,9 @@ class AiocqhttpAdapter(Platform): if "sub_type" in event: if event["sub_type"] == "poke" and "target_id" in event: - abm.message.append(Poke(qq=str(event["target_id"]), type="poke")) # noqa: F405 + abm.message.append( + Poke(qq=str(event["target_id"]), type="poke") + ) # noqa: F405 return abm @@ -224,7 +226,7 @@ class AiocqhttpAdapter(Platform): # Lagrange logger.info("guessing lagrange") file_name = m["data"].get("file_name", "file") - abm.message.append(File(name=file_name, file=m["data"]["url"])) + abm.message.append(File(name=file_name, url=m["data"]["url"])) else: try: # Napcat @@ -242,7 +244,7 @@ class AiocqhttpAdapter(Platform): ) if ret and "url" in ret: file_url = ret["url"] # https - a = File(name="", file=file_url) + a = File(name="", url=file_url) abm.message.append(a) else: logger.error(f"获取文件失败: {ret}") diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index d227f1f68..a2ce88736 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -58,8 +58,12 @@ class TelegramPlatformAdapter(Platform): self.base_url = base_url - self.enable_command_register = self.config.get("telegram_command_register", True) - self.enable_command_refresh = self.config.get("telegram_command_auto_refresh", True) + self.enable_command_register = self.config.get( + "telegram_command_register", True + ) + self.enable_command_refresh = self.config.get( + "telegram_command_auto_refresh", True + ) self.last_command_hash = None self.application = ( @@ -123,7 +127,9 @@ class TelegramPlatformAdapter(Platform): commands = self.collect_commands() if commands: - current_hash = hash(tuple((cmd.command, cmd.description) for cmd in commands)) + current_hash = hash( + tuple((cmd.command, cmd.description) for cmd in commands) + ) if current_hash == self.last_command_hash: return self.last_command_hash = current_hash