From 3b34be2e7a34375bd98dfb36c7b87c6a9cff19b0 Mon Sep 17 00:00:00 2001 From: liuwei Date: Mon, 7 Apr 2025 15:53:19 +0800 Subject: [PATCH] =?UTF-8?q?dify=20=E6=94=AF=E6=8C=81=E5=9B=BE=E7=89=87http?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=EF=BC=8C=E6=96=B9=E4=BE=BF=E9=9A=8F=E6=97=B6?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/dify/main.py | 22 +++++- utils/media_downloader.py | 149 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 utils/media_downloader.py diff --git a/plugins/dify/main.py b/plugins/dify/main.py index 473212c..caa8c37 100644 --- a/plugins/dify/main.py +++ b/plugins/dify/main.py @@ -11,6 +11,7 @@ from plugin_common.message_plugin_interface import MessagePluginInterface from plugin_common.plugin_interface import PluginStatus from plugins.stats_collector.decorators import plugin_stats_decorator from robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager +from utils.media_downloader import MediaDownloader class DifyPlugin(MessagePluginInterface): @@ -152,7 +153,13 @@ class DifyPlugin(MessagePluginInterface): # 发送回复 if response: - wcf.send_text(response, (roomid if roomid else sender), sender if roomid else "") + # 判断是否为本地文件路径 + if os.path.isfile(response): + # 如果是文件路径,使用发送文件方法 + wcf.send_file(response, (roomid if roomid else sender)) + else: + # 如果是普通文本,使用发送文本方法 + wcf.send_text(response, (roomid if roomid else sender), sender if roomid else "") return True, "发送成功" else: wcf.send_text("❌未能获取到回复,请稍后再试", (roomid if roomid else sender), sender if roomid else "") @@ -256,10 +263,17 @@ class DifyPlugin(MessagePluginInterface): # 获取输出内容 outputs = response_data.get("data", {}).get("outputs", {}) if outputs: - # 尝试从text字段获取回答 - if "text" in outputs and isinstance(outputs["text"], str): + # 处理媒体类型返回 + if "result" in outputs and "type" in outputs: + if outputs["type"] == "image": + downloader = MediaDownloader() + image_url = outputs["result"] + image_path = downloader.download_media(image_url) + answer = image_path + # 处理文本类型返回 + elif "text" in outputs and isinstance(outputs["text"], str): answer = outputs["text"] - # 如果没有text字段,尝试从其他字段获取 + # 兼容旧版处理逻辑 else: for key, value in outputs.items(): if isinstance(value, str) and value.strip(): diff --git a/utils/media_downloader.py b/utils/media_downloader.py new file mode 100644 index 0000000..5e070ed --- /dev/null +++ b/utils/media_downloader.py @@ -0,0 +1,149 @@ +import os +import requests +import uuid +from typing import Optional +from urllib.parse import urlparse +import logging +import time + +class MediaDownloader: + """媒体下载工具类,用于下载图片等媒体文件""" + + def __init__(self, download_dir: str = None): + """ + 初始化下载器 + + Args: + download_dir: 下载目录,默认为项目下的 media_downloads 目录 + """ + self.logger = logging.getLogger("MediaDownloader") + self.download_dir = download_dir or os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "media_downloads" + ) + os.makedirs(self.download_dir, exist_ok=True) + self.logger.info(f"媒体下载目录: {self.download_dir}") + + def download_media(self, url: str, file_type: str = None) -> Optional[str]: + """ + 下载媒体文件 + + Args: + url: 媒体文件URL + file_type: 文件类型(如'jpg','png'等),如果不指定则从URL中推断 + + Returns: + 下载文件的本地绝对路径,如果下载失败则返回None + """ + try: + # 从URL获取文件名和扩展名 + parsed_url = urlparse(url) + filename = os.path.basename(parsed_url.path) + + # 如果没有文件名或扩展名,则生成一个随机文件名 + if not filename or '.' not in filename: + ext = file_type if file_type else self._guess_file_type(url) + filename = f"{uuid.uuid4().hex}.{ext}" if ext else f"{uuid.uuid4().hex}" + + local_path = os.path.join(self.download_dir, filename) + + self.logger.info(f"开始下载媒体文件: {url} -> {local_path}") + + # 下载文件 + response = requests.get(url, stream=True, timeout=30) + response.raise_for_status() + + with open(local_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + + self.logger.info(f"媒体文件下载成功: {local_path}") + + # 下载成功后清理旧文件 + self.clear_downloads() + + return os.path.abspath(local_path) + + except Exception as e: + self.logger.error(f"下载媒体文件失败: {url}, 错误: {str(e)}") + return None + + def _guess_file_type(self, url: str) -> Optional[str]: + """ + 从URL推断文件类型 + + Args: + url: 媒体文件URL + + Returns: + 文件扩展名(不含点),如果无法推断则返回None + """ + try: + parsed_url = urlparse(url) + path = parsed_url.path.lower() + + if path.endswith('.jpg') or path.endswith('.jpeg'): + return 'jpg' + elif path.endswith('.png'): + return 'png' + elif path.endswith('.gif'): + return 'gif' + elif path.endswith('.mp4'): + return 'mp4' + elif path.endswith('.mp3'): + return 'mp3' + elif path.endswith('.pdf'): + return 'pdf' + else: + # 检查Content-Type + response = requests.head(url, timeout=5) + content_type = response.headers.get('Content-Type', '') + + if 'image/jpeg' in content_type: + return 'jpg' + elif 'image/png' in content_type: + return 'png' + elif 'image/gif' in content_type: + return 'gif' + + return None + except: + return None + + def clear_downloads(self, max_age_days: int = 3) -> None: + """ + 清理超过指定天数的下载文件 + + Args: + max_age_days: 最大保留天数,默认为3天 + """ + try: + current_time = time.time() + max_age_seconds = max_age_days * 24 * 60 * 60 + cleared_count = 0 + + # 遍历下载目录中的所有文件 + for filename in os.listdir(self.download_dir): + file_path = os.path.join(self.download_dir, filename) + + # 检查是否为文件 + if os.path.isfile(file_path): + # 获取文件最后修改时间 + file_mtime = os.path.getmtime(file_path) + file_age = current_time - file_mtime + + # 如果文件超过最大保留时间,则删除 + if file_age > max_age_seconds: + try: + os.remove(file_path) + cleared_count += 1 + self.logger.debug(f"已删除过期文件: {file_path}") + except Exception as e: + self.logger.error(f"删除文件失败 {file_path}: {str(e)}") + + if cleared_count > 0: + self.logger.info(f"清理完成,共删除 {cleared_count} 个过期文件") + + except Exception as e: + self.logger.error(f"清理下载文件时出错: {str(e)}") \ No newline at end of file