diff --git a/README.MD b/README.MD index 4f313ed..cb57c16 100644 --- a/README.MD +++ b/README.MD @@ -85,105 +85,6 @@ pip install -r requirements.txt - uvicorn~=0.34.2 - 更多依赖请查看 requirements.txt -## 🔌 插件依赖说明 - -### 核心服务依赖 -- MySQL数据库:用于存储用户数据、积分记录、群组信息等 -- Redis服务:用于缓存和会话管理 -- Dify服务:用于AI聊天功能([Dify开源地址](https://docs.dify.ai/zh-hans/introduction)) - -### 外部API依赖 -- 天气API:用于天气查询功能 -- 新闻API:用于全球快讯功能 -- 音乐API:用于音乐点播功能 -- 抖音解析服务:用于视频解析功能 - -### 系统依赖 -- Python 3.10+ -- Git:用于系统更新 -- 文件系统存储空间:用于存储媒体文件 -- 网络代理支持(可选):用于访问特定服务 - -### 插件特定依赖 - -1. **Dify插件** - - Dify服务(自部署或云服务) - - API密钥配置 - - 代理设置(可选) - -2. **抖音解析插件** - - 网络请求库 - - 文件系统存储 - - 代理支持(可选) - -3. **群智闯关插件** - - MySQL数据库 - - 积分系统集成 - -4. **积分交易插件** - - MySQL数据库 - - 用户系统集成 - -5. **全球快讯插件** - - 新闻API服务 - - 网络请求库 - -6. **音乐插件** - - 音乐API服务 - - 音频处理库 - -7. **视频插件** - - 视频处理库 - - 存储系统 - -8. **群管理插件** - - 数据库支持 - - 群管理API - -## 📖 使用说明 - -### 基础命令 -1. 群智闯关 - - `/s` - 加入答题游戏 - - `/t` - 获取新问题 - - `/a 任务ID 答案` - 回答问题 - - `/r` - 查看排行榜 - - `/l` - 查看活跃问题 - - `/h` - 查看未解决问题 - -2. 积分系统 - - `积分转账 积分数 @用户` - 转账积分 - - `我的积分` - 查询积分 - - `积分排行` - 查看排行榜 - - `打劫 @用户` - 打劫积分 - - `保释 @用户` - 保释用户 - -3. 新闻功能 - - `全球新闻` - 获取国际新闻 - - 支持:`国际新闻`、`环球新闻`、`政经新闻` - -4. AI聊天 - - `聊天 问题` - 与AI对话 - - 支持:`ai`、`dify`、`AI` 开头 - -5. 插件管理 - - `插件 列表` - 查看插件 - - `插件 启用 [插件名]` - 启用插件 - - `插件 禁用 [插件名]` - 禁用插件 - - `插件 信息 [插件名]` - 查看插件信息 - -### 媒体功能 -- `图来`/`秀人` - 获取图片 -- `猛男` - 获取视频 -- `美腿`/`腿来` - 获取美腿图片 -- `点歌 歌曲名` - 点播音乐 -- 直接发送抖音链接可自动解析 - -### 系统功能 -- `签到` - 每日签到 -- `更新系统` - 系统更新(管理员) -- `#总结` - 消息总结 - ## ⚙️ 配置说明 ### 1. 配置文件 @@ -259,6 +160,50 @@ GameTask: command-format: "游戏命令格式说明" ``` +## 📖 使用说明 + +### 基础命令 +1. 群智闯关 + - `/s` - 加入答题游戏 + - `/t` - 获取新问题 + - `/a 任务ID 答案` - 回答问题 + - `/r` - 查看排行榜 + - `/l` - 查看活跃问题 + - `/h` - 查看未解决问题 + +2. 积分系统 + - `积分转账 积分数 @用户` - 转账积分 + - `我的积分` - 查询积分 + - `积分排行` - 查看排行榜 + - `打劫 @用户` - 打劫积分 + - `保释 @用户` - 保释用户 + +3. 新闻功能 + - `全球新闻` - 获取国际新闻 + - 支持:`国际新闻`、`环球新闻`、`政经新闻` + +4. AI聊天 + - `聊天 问题` - 与AI对话 + - 支持:`ai`、`dify`、`AI` 开头 + +5. 插件管理 + - `插件 列表` - 查看插件 + - `插件 启用 [插件名]` - 启用插件 + - `插件 禁用 [插件名]` - 禁用插件 + - `插件 信息 [插件名]` - 查看插件信息 + +### 媒体功能 +- `图来`/`秀人` - 获取图片 +- `猛男` - 获取视频 +- `美腿`/`腿来` - 获取美腿图片 +- `点歌 歌曲名` - 点播音乐 +- 直接发送抖音链接可自动解析 + +### 系统功能 +- `签到` - 每日签到 +- `更新系统` - 系统更新(管理员) +- `#总结` - 消息总结 + ## 📁 项目结构 ``` abot/ @@ -351,4 +296,4 @@ python -m pip install --upgrade pip ## 🙏 致谢 -感谢所有为本项目做出贡献的开发者。 +感谢所有为本项目做出贡献的开发者。 \ No newline at end of file diff --git a/plugins/dify/main.py b/plugins/dify/main.py index da9ad10..a530a18 100644 --- a/plugins/dify/main.py +++ b/plugins/dify/main.py @@ -19,6 +19,7 @@ from utils.decorator.points_decorator import plugin_points_cost from utils.media_downloader import MediaDownloader from utils.string_utils import remove_trailing_content from wechat_ipad import WechatAPIClient +import aiohttp # 常见的图片和视频文件扩展名 IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'} @@ -146,7 +147,7 @@ class DifyPlugin(MessagePluginInterface): try: # 调用Dify API获取回复 - success, response = self._chat_with_dify((roomid if roomid else sender), sender, query) + success, response = await self._chat_with_dify((roomid if roomid else sender), sender, query) if not success: return False, response # 去除广告内容 @@ -162,7 +163,8 @@ class DifyPlugin(MessagePluginInterface): await bot.send_image_message((roomid if roomid else sender), Path(response)) elif file_type == 2: first_farme = self._get_first_frame(response, f"dify_frame_{int(time.time())}.jpg") - await bot.send_video_message((roomid if roomid else sender), Path(response), Path(first_farme)) + await bot.send_video_message((roomid if roomid else sender), Path(response), + Path(first_farme)) else: return False, "获取媒资失败" else: @@ -219,7 +221,7 @@ class DifyPlugin(MessagePluginInterface): try: # 调用Dify API获取回复 - success, response = self._chat_with_dify(session_id, user_id, query) + success, response = await self._chat_with_dify(session_id, user_id, query) if not success: return False, response # 去除广告内容 @@ -269,7 +271,7 @@ class DifyPlugin(MessagePluginInterface): revoke.add_message_to_revoke((roomid if roomid else sender), client_msg_id, create_time, new_msg_id, 5) return False, f"处理出错: {e}" - def _chat_with_dify(self, session_id: str, user_id: str, query: str) -> Tuple[bool, Optional[str]]: + async def _chat_with_dify(self, session_id: str, user_id: str, query: str) -> Tuple[bool, Optional[str]]: """ 与Dify API交互获取回复 @@ -330,12 +332,7 @@ class DifyPlugin(MessagePluginInterface): data["conversation_history"] = self.conversations[session_id] # 设置代理 - proxies = None - if self.http_proxy: - proxies = { - "http": self.http_proxy, - "https": self.http_proxy - } + proxy = self.http_proxy if self.http_proxy else None # 发送请求 url = f"{self.base_url}/workflows/run" @@ -344,89 +341,89 @@ class DifyPlugin(MessagePluginInterface): self.LOG.info(f"请求数据: {json.dumps(data, ensure_ascii=False)}") try: - # 使用普通请求(非流式) - response = requests.post(url, headers=headers, json=data, proxies=proxies, timeout=40) + async with aiohttp.ClientSession() as session: + response = await session.post(url, headers=headers, json=data, proxy=proxy, timeout=40) + if response.status != 200: + error_text = await response.text() + self.LOG.error(f"Dify API请求失败: {response.status} {error_text}") + return False, f"请求失败,状态码: {response.status}" - if response.status_code != 200: - self.LOG.error(f"Dify API请求失败: {response.status_code} {response.text}") - return False, f"请求失败,状态码: {response.status_code}" + # 解析响应 + response_data = await response.json() + self.LOG.info(f"收到Dify API响应: {json.dumps(response_data, ensure_ascii=False)}") - # 解析响应 - response_data = response.json() - self.LOG.info(f"收到Dify API响应: {json.dumps(response_data, ensure_ascii=False)}") + # 提取回答内容 + answer = "" + total_tokens = 0 - # 提取回答内容 - answer = "" - total_tokens = 0 + # 获取输出内容 + outputs = response_data.get("data", {}).get("outputs", {}) + if outputs: + # 处理媒体类型返回 + 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 + if outputs["type"] == "video": + 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"] + # 兼容旧版处理逻辑 + else: + for key, value in outputs.items(): + if isinstance(value, str) and value.strip(): + answer += value + elif isinstance(value, dict): + # 处理嵌套字典的情况 + for sub_key, sub_value in value.items(): + if isinstance(sub_value, str) and sub_value.strip(): + answer += sub_value + elif isinstance(value, list): + # 处理列表的情况 + for item in value: + if isinstance(item, str) and item.strip(): + answer += item + elif isinstance(item, dict): + # 处理列表中的字典 + for item_key, item_value in item.items(): + if isinstance(item_value, str) and item_value.strip(): + answer += item_value - # 获取输出内容 - outputs = response_data.get("data", {}).get("outputs", {}) - if outputs: - # 处理媒体类型返回 - 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 - if outputs["type"] == "video": - 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"] - # 兼容旧版处理逻辑 - else: - for key, value in outputs.items(): - if isinstance(value, str) and value.strip(): - answer += value - elif isinstance(value, dict): - # 处理嵌套字典的情况 - for sub_key, sub_value in value.items(): - if isinstance(sub_value, str) and sub_value.strip(): - answer += sub_value - elif isinstance(value, list): - # 处理列表的情况 - for item in value: - if isinstance(item, str) and item.strip(): - answer += item - elif isinstance(item, dict): - # 处理列表中的字典 - for item_key, item_value in item.items(): - if isinstance(item_value, str) and item_value.strip(): - answer += item_value + # 获取token使用情况 + total_tokens = response_data.get("data", {}).get("total_tokens", 0) - # 获取token使用情况 - total_tokens = response_data.get("data", {}).get("total_tokens", 0) + # 更新会话历史 + self.conversations[session_id].append({ + "role": "user", + "content": query + }) - # 更新会话历史 - self.conversations[session_id].append({ - "role": "user", - "content": query - }) + self.conversations[session_id].append({ + "role": "assistant", + "content": answer + }) - self.conversations[session_id].append({ - "role": "assistant", - "content": answer - }) + # 限制会话历史长度 + if len(self.conversations[session_id]) > self.max_history_length * 2: + self.conversations[session_id] = self.conversations[session_id][-self.max_history_length * 2:] - # 限制会话历史长度 - if len(self.conversations[session_id]) > self.max_history_length * 2: - self.conversations[session_id] = self.conversations[session_id][-self.max_history_length * 2:] + # 统计token使用情况 + if total_tokens > 0: + if user_id in self.token_usage: + self.token_usage[user_id] += total_tokens + else: + self.token_usage[user_id] = total_tokens - # 统计token使用情况 - if total_tokens > 0: - if user_id in self.token_usage: - self.token_usage[user_id] += total_tokens - else: - self.token_usage[user_id] = total_tokens + self.LOG.info( + f"用户 {user_id} 本次消耗 {total_tokens} tokens,累计 {self.token_usage[user_id]} tokens") - self.LOG.info( - f"用户 {user_id} 本次消耗 {total_tokens} tokens,累计 {self.token_usage[user_id]} tokens") - - return True, answer + return True, answer except Exception as e: self.LOG.error(f"处理Dify响应时出错: {str(e)}")