From 74a648fd4b5f4a5abeee3e371c80121ec5ad90fa Mon Sep 17 00:00:00 2001 From: ihavecoke Date: Mon, 6 May 2024 23:26:57 +0800 Subject: [PATCH 1/2] Adjust text formatting per Chinese copywriting guidelines Refined text formatting to follow the recommendations from: https://sparanoid.com/note/chinese-copywriting-guidelines/ --- README.md | 104 ++++++++++++++++++------------------ bridge/bridge.py | 2 +- bridge/reply.py | 6 +-- common/linkai_client.py | 2 +- common/time_check.py | 8 +-- common/token_bucket.py | 4 +- docker/docker-compose.yml | 4 +- docs/version/old-version.md | 12 ++--- lib/itchat/returnvalues.py | 2 +- 9 files changed, 72 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 6f962d137..a4bb81971 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # 简介 -> chatgpt-on-wechat(简称CoW)项目是基于大模型的智能对话机器人,支持微信公众号、企业微信应用、微信、飞书、钉钉接入,可选择GPT3.5/GPT4.0/Claude/Gemini/LinkAI/ChatGLM/KIMI/文心一言/讯飞星火/通义千问/LinkAI,能处理文本、语音和图片,通过插件访问操作系统和互联网等外部资源,支持基于自有知识库定制企业AI应用。 +> chatgpt-on-wechat(简称 CoW)项目是基于大模型的智能对话机器人,支持微信公众号、企业微信应用、微信、飞书、钉钉接入,可选择 GPT3.5/GPT4.0/Claude/文心一言/讯飞星火/通义千问/Gemini/LinkAI/ChatGLM/KIMI,能处理文本、语音和图片,通过插件访问操作系统和互联网等外部资源,支持基于自有知识库定制企业 AI 应用。 最新版本支持的功能如下: -- ✅ **多端部署:** 有多种部署方式可选择且功能完备,目前已支持微信生态下公众号、企业微信应用、飞书、钉钉等部署方式 -- ✅ **基础对话:** 私聊及群聊的消息智能回复,支持多轮会话上下文记忆,支持 GPT-3.5, GPT-4, Claude-3, Gemini, 文心一言, 讯飞星火, 通义千问,ChatGLM-4,Kimi(月之暗面) -- ✅ **语音能力:** 可识别语音消息,通过文字或语音回复,支持 azure, baidu, google, openai(whisper/tts) 等多种语音模型 -- ✅ **图像能力:** 支持图片生成、图片识别、图生图(如照片修复),可选择 Dall-E-3, stable diffusion, replicate, midjourney, CogView-3, vision模型 -- ✅ **丰富插件:** 支持个性化插件扩展,已实现多角色切换、文字冒险、敏感词过滤、聊天记录总结、文档总结和对话、联网搜索等插件 -- ✅ **知识库:** 通过上传知识库文件自定义专属机器人,可作为数字分身、智能客服、私域助手使用,基于 [LinkAI](https://link-ai.tech) 实现 +- [x] **多端部署:** 有多种部署方式可选择且功能完备,目前已支持微信生态下公众号、企业微信应用、飞书、钉钉等部署方式 +- [x] **基础对话:** 私聊及群聊的消息智能回复,支持多轮会话上下文记忆,支持 GPT-3.5, GPT-4, Claude-3, Gemini, 文心一言,讯飞星火,通义千问,ChatGLM-4,Kimi(月之暗面) +- [x] **语音能力:** 可识别语音消息,通过文字或语音回复,支持 azure, baidu, google, openai(whisper/tts) 等多种语音模型 +- [x] **图像能力:** 支持图片生成、图片识别、图生图(如照片修复),可选择 Dall-E-3, stable diffusion, replicate, midjourney, CogView-3, vision 模型 +- [x] **丰富插件:** 支持个性化插件扩展,已实现多角色切换、文字冒险、敏感词过滤、聊天记录总结、文档总结和对话、联网搜索等插件 +- [x] **知识库:** 通过上传知识库文件自定义专属机器人,可作为数字分身、智能客服、私域助手使用,基于 [LinkAI](https://link-ai.tech) 实现 ## 演示 @@ -23,15 +23,15 @@ https://github.com/zhayujie/chatgpt-on-wechat/assets/26161723/d5154020-36e3-41db
-# 企业服务 +# 商业服务 - + -> [LinkAI](https://link-ai.tech/) 是面向企业和开发者的一站式AI应用平台,聚合多模态大模型、知识库、Agent 插件、工作流等能力,支持一键接入主流平台并进行管理,支持SaaS、私有化部署多种模式。 +> 我们还提供企业级的 **一站式 AI 应用搭建与接入平台** - [LinkAI](https://link-ai.tech/),聚合多模态大模型、知识库、Agent 插件、工作流等能力,并支持一键接入主流 IM 和办公协同平台并进行管理,支持 SaaS、私有化部署、稳定托管接入多种模式。 > -> LinkAI 目前 已在私域运营、智能客服、企业效率助手等场景积累了丰富的 AI 解决方案, 在电商、文教、健康、新消费、科技制造等各行业沉淀了大模型落地应用的最佳实践,致力于帮助更多企业和开发者拥抱 AI 生产力。 +> LinkAI 目前 已在私域运营、智能客服、企业效率助手等场景积累了丰富的 AI 解决方案,在电商、文教、健康、新消费、科技制造等各行业沉淀了大模型落地应用的最佳实践,致力于帮助更多团队和企业拥抱 AI 生产力。 -**企业服务和产品咨询** 可联系产品顾问: +**企业服务和商用咨询** 可联系产品顾问: @@ -39,19 +39,19 @@ https://github.com/zhayujie/chatgpt-on-wechat/assets/26161723/d5154020-36e3-41db # 🏷 更新日志 ->**2024.04.26:** [1.6.0版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.0),新增 Kimi 接入、gpt-4-turbo版本升级、文件总结和语音识别问题修复 +>**2024.04.26:** [1.6.0 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.6.0),新增 Kimi 接入、gpt-4-turbo 版本升级、文件总结和语音识别问题修复 ->**2024.03.26:** [1.5.8版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.8) 和 [1.5.7版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.7),新增 GLM-4、Claude-3 模型,edge-tts 语音支持 +>**2024.03.26:** [1.5.8 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.8) 和 [1.5.7 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.7),新增 GLM-4、Claude-3 模型,edge-tts 语音支持 ->**2024.01.26:** [1.5.6版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.6) 和 [1.5.5版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.5),钉钉接入,tool插件升级,4-turbo模型更新 +>**2024.01.26:** [1.5.6 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.6) 和 [1.5.5 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.5),钉钉接入,tool 插件升级,4-turbo 模型更新 ->**2023.11.11:** [1.5.3版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.3) 和 [1.5.4版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.4),新增通义千问模型、Google Gemini +>**2023.11.11:** [1.5.3 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.3) 和 [1.5.4 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.4),新增通义千问模型、Google Gemini ->**2023.11.10:** [1.5.2版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.2),新增飞书通道、图像识别对话、黑名单配置 +>**2023.11.10:** [1.5.2 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.2),新增飞书通道、图像识别对话、黑名单配置 ->**2023.11.10:** [1.5.0版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.0),新增 `gpt-4-turbo`, `dall-e-3`, `tts` 模型接入,完善图像理解&生成、语音识别&生成的多模态能力 +>**2023.11.10:** [1.5.0 版本](https://github.com/zhayujie/chatgpt-on-wechat/releases/tag/1.5.0),新增 `gpt-4-turbo`, `dall-e-3`, `tts` 模型接入,完善图像理解&生成、语音识别&生成的多模态能力 ->**2023.10.16:** 支持通过意图识别使用LinkAI联网搜索、数学计算、网页访问等插件,参考[插件文档](https://docs.link-ai.tech/platform/plugins) +>**2023.10.16:** 支持通过意图识别使用 LinkAI 联网搜索、数学计算、网页访问等插件,参考[插件文档](https://docs.link-ai.tech/platform/plugins) >**2023.09.26:** 插件增加 文件/文章链接 一键总结和对话的功能,使用参考:[插件说明](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/linkai#3%E6%96%87%E6%A1%A3%E6%80%BB%E7%BB%93%E5%AF%B9%E8%AF%9D%E5%8A%9F%E8%83%BD) @@ -59,7 +59,7 @@ https://github.com/zhayujie/chatgpt-on-wechat/assets/26161723/d5154020-36e3-41db >**2023.06.12:** 接入 [LinkAI](https://link-ai.tech/console) 平台,可在线创建领域知识库,打造专属客服机器人。使用参考 [接入文档](https://link-ai.tech/platform/link-app/wechat)。 -更早更新日志查看: [归档日志](/docs/version/old-version.md) +更早更新日志查看:[归档日志](/docs/version/old-version.md)
@@ -71,18 +71,18 @@ https://github.com/zhayujie/chatgpt-on-wechat/assets/26161723/d5154020-36e3-41db ### 1. 账号注册 -项目默认使用OpenAI接口,需前往 [OpenAI注册页面](https://beta.openai.com/signup) 创建账号,创建完账号则前往 [API管理页面](https://beta.openai.com/account/api-keys) 创建一个 API Key 并保存下来,后面需要在项目中配置这个key。接口需要海外网络访问及绑定信用卡支付。 +项目默认使用 OpenAI 接口,需前往 [OpenAI 注册页面](https://beta.openai.com/signup) 创建账号,创建完账号则前往 [API 管理页面](https://beta.openai.com/account/api-keys) 创建一个 API Key 并保存下来,后面需要在项目中配置这个 key。接口需要海外网络访问及绑定信用卡支付。 -> 默认对话模型是 openai 的 gpt-3.5-turbo,计费方式是约每 1000tokens (约750个英文单词 或 500汉字,包含请求和回复) 消耗 $0.002,图片生成是Dell E模型,每张消耗 $0.016。 +> 默认对话模型是 openai 的 gpt-3.5-turbo,计费方式是约每 1000tokens (约 750 个英文单词 或 500 汉字,包含请求和回复) 消耗 $0.002,图片生成是 Dell E 模型,每张消耗 $0.016。 -项目同时也支持使用 LinkAI 接口,无需代理,可使用 文心、讯飞、GPT-3、GPT-4 等模型,支持 定制化知识库、联网搜索、MJ绘图、文档总结和对话等能力。修改配置即可一键切换,参考 [接入文档](https://link-ai.tech/platform/link-app/wechat)。 +项目同时也支持使用 LinkAI 接口,无需代理,可使用 文心、讯飞、GPT-3、GPT-4 等模型,支持 定制化知识库、联网搜索、MJ 绘图、文档总结和对话等能力。修改配置即可一键切换,参考 [接入文档](https://link-ai.tech/platform/link-app/wechat)。 ### 2.运行环境 -支持 Linux、MacOS、Windows 系统(可在Linux服务器上长期运行),同时需安装 `Python`。 -> 建议Python版本在 3.7.1~3.9.X 之间,推荐3.8版本,3.10及以上版本在 MacOS 可用,其他系统上不确定能否正常运行。 +支持 Linux、MacOS、Windows 系统(可在 Linux 服务器上长期运行),同时需安装 `Python`。 +> 建议 Python 版本在 3.7.1~3.9.X 之间,推荐 3.8 版本,3.10 及以上版本在 MacOS 可用,其他系统上不确定能否正常运行。 -> 注意:Docker 或 Railway 部署无需安装python环境和下载源码,可直接快进到下一节。 +> 注意:Docker 或 Railway 部署无需安装 python 环境和下载源码,可直接快进到下一节。 **(1) 克隆项目代码:** @@ -91,7 +91,7 @@ git clone https://github.com/zhayujie/chatgpt-on-wechat cd chatgpt-on-wechat/ ``` -注: 如遇到网络问题可选择国内镜像 https://gitee.com/zhayujie/chatgpt-on-wechat +注:如遇到网络问题可选择国内镜像 https://gitee.com/zhayujie/chatgpt-on-wechat **(2) 安装核心依赖 (必选):** > 能够使用`itchat`创建机器人,并具有文字交流功能所需的最小依赖集合。 @@ -114,7 +114,7 @@ pip3 install -r requirements-optional.txt cp config-template.json config.json ``` -然后在`config.json`中填入配置,以下是对默认配置的说明,可根据需要进行自定义修改(注意实际使用时请去掉注释,保证JSON格式的完整): +然后在`config.json`中填入配置,以下是对默认配置的说明,可根据需要进行自定义修改(注意实际使用时请去掉注释,保证 JSON 格式的完整): ```bash # config.json文件内容示例 @@ -145,40 +145,40 @@ pip3 install -r requirements-optional.txt **1.个人聊天** + 个人聊天中,需要以 "bot"或"@bot" 为开头的内容触发机器人,对应配置项 `single_chat_prefix` (如果不需要以前缀触发可以填写 `"single_chat_prefix": [""]`) -+ 机器人回复的内容会以 "[bot] " 作为前缀, 以区分真人,对应的配置项为 `single_chat_reply_prefix` (如果不需要前缀可以填写 `"single_chat_reply_prefix": ""`) ++ 机器人回复的内容会以 "[bot] " 作为前缀,以区分真人,对应的配置项为 `single_chat_reply_prefix` (如果不需要前缀可以填写 `"single_chat_reply_prefix": ""`) **2.群组聊天** + 群组聊天中,群名称需配置在 `group_name_white_list ` 中才能开启群聊自动回复。如果想对所有群聊生效,可以直接填写 `"group_name_white_list": ["ALL_GROUP"]` + 默认只要被人 @ 就会触发机器人自动回复;另外群聊天中只要检测到以 "@bot" 开头的内容,同样会自动回复(方便自己触发),这对应配置项 `group_chat_prefix` -+ 可选配置: `group_name_keyword_white_list`配置项支持模糊匹配群名称,`group_chat_keyword`配置项则支持模糊匹配群消息内容,用法与上述两个配置项相同。(Contributed by [evolay](https://github.com/evolay)) ++ 可选配置:`group_name_keyword_white_list`配置项支持模糊匹配群名称,`group_chat_keyword`配置项则支持模糊匹配群消息内容,用法与上述两个配置项相同。(Contributed by [evolay](https://github.com/evolay)) + `group_chat_in_one_session`:使群聊共享一个会话上下文,配置 `["ALL_GROUP"]` 则作用于所有群聊 **3.语音识别** -+ 添加 `"speech_recognition": true` 将开启语音识别,默认使用openai的whisper模型识别为文字,同时以文字回复,该参数仅支持私聊 (注意由于语音消息无法匹配前缀,一旦开启将对所有语音自动回复,支持语音触发画图); -+ 添加 `"group_speech_recognition": true` 将开启群组语音识别,默认使用openai的whisper模型识别为文字,同时以文字回复,参数仅支持群聊 (会匹配group_chat_prefix和group_chat_keyword, 支持语音触发画图); -+ 添加 `"voice_reply_voice": true` 将开启语音回复语音(同时作用于私聊和群聊),但是需要配置对应语音合成平台的key,由于itchat协议的限制,只能发送语音mp3文件,若使用wechaty则回复的是微信语音。 ++ 添加 `"speech_recognition": true` 将开启语音识别,默认使用 openai 的 whisper 模型识别为文字,同时以文字回复,该参数仅支持私聊 (注意由于语音消息无法匹配前缀,一旦开启将对所有语音自动回复,支持语音触发画图); ++ 添加 `"group_speech_recognition": true` 将开启群组语音识别,默认使用 openai 的 whisper 模型识别为文字,同时以文字回复,参数仅支持群聊 (会匹配 group_chat_prefix 和 group_chat_keyword, 支持语音触发画图); ++ 添加 `"voice_reply_voice": true` 将开启语音回复语音(同时作用于私聊和群聊),但是需要配置对应语音合成平台的 key,由于 itchat 协议的限制,只能发送语音 mp3 文件,若使用 wechaty 则回复的是微信语音。 **4.其他配置** -+ `model`: 模型名称,目前支持 `gpt-3.5-turbo`, `text-davinci-003`, `gpt-4`, `gpt-4-32k`, `wenxin` , `claude` , `xunfei`(其中gpt-4 api暂未完全开放,申请通过后可使用) -+ `temperature`,`frequency_penalty`,`presence_penalty`: Chat API接口参数,详情参考[OpenAI官方文档。](https://platform.openai.com/docs/api-reference/chat) ++ `model`: 模型名称,目前支持 `gpt-3.5-turbo`, `text-davinci-003`, `gpt-4`, `gpt-4-32k`, `wenxin` , `claude` , `xunfei`(其中 gpt-4 api 暂未完全开放,申请通过后可使用) ++ `temperature`,`frequency_penalty`,`presence_penalty`: Chat API 接口参数,详情参考[OpenAI 官方文档。](https://platform.openai.com/docs/api-reference/chat) + `proxy`:由于目前 `openai` 接口国内无法访问,需配置代理客户端的地址,详情参考 [#351](https://github.com/zhayujie/chatgpt-on-wechat/issues/351) + 对于图像生成,在满足个人或群组触发条件外,还需要额外的关键词前缀来触发,对应配置 `image_create_prefix ` -+ 关于OpenAI对话及图片接口的参数配置(内容自由度、回复字数限制、图片大小等),可以参考 [对话接口](https://beta.openai.com/docs/api-reference/completions) 和 [图像接口](https://beta.openai.com/docs/api-reference/completions) 文档,在[`config.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/config.py)中检查哪些参数在本项目中是可配置的。 ++ 关于 OpenAI 对话及图片接口的参数配置(内容自由度、回复字数限制、图片大小等),可以参考 [对话接口](https://beta.openai.com/docs/api-reference/completions) 和 [图像接口](https://beta.openai.com/docs/api-reference/completions) 文档,在[`config.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/config.py)中检查哪些参数在本项目中是可配置的。 + `conversation_max_tokens`:表示能够记忆的上下文最大字数(一问一答为一组对话,如果累积的对话字数超出限制,就会优先移除最早的一组对话) + `rate_limit_chatgpt`,`rate_limit_dalle`:每分钟最高问答速率、画图速率,超速后排队按序处理。 + `clear_memory_commands`: 对话内指令,主动清空前文记忆,字符串数组可自定义指令别名。 + `hot_reload`: 程序退出后,暂存微信扫码状态,默认关闭。 + `character_desc` 配置中保存着你对机器人说的一段话,他会记住这段话并作为他的设定,你可以为他定制任何人格 (关于会话上下文的更多内容参考该 [issue](https://github.com/zhayujie/chatgpt-on-wechat/issues/43)) -+ `subscribe_msg`:订阅消息,公众号和企业微信channel中请填写,当被订阅时会自动回复, 可使用特殊占位符。目前支持的占位符有{trigger_prefix},在程序中它会自动替换成bot的触发词。 ++ `subscribe_msg`:订阅消息,公众号和企业微信 channel 中请填写,当被订阅时会自动回复,可使用特殊占位符。目前支持的占位符有{trigger_prefix},在程序中它会自动替换成 bot 的触发词。 -**5.LinkAI配置 (可选)** +**5.LinkAI 配置 (可选)** -+ `use_linkai`: 是否使用LinkAI接口,开启后可国内访问,使用知识库和 `Midjourney` 绘画, 参考 [文档](https://link-ai.tech/platform/link-app/wechat) ++ `use_linkai`: 是否使用 LinkAI 接口,开启后可国内访问,使用知识库和 `Midjourney` 绘画,参考 [文档](https://link-ai.tech/platform/link-app/wechat) + `linkai_api_key`: LinkAI Api Key,可在 [控制台](https://link-ai.tech/console/interface) 创建 -+ `linkai_app_code`: LinkAI 应用code,选填 ++ `linkai_app_code`: LinkAI 应用 code,选填 **本说明文档可能会未及时更新,当前所有可选的配置项均在该[`config.py`](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/config.py)中列出。** @@ -196,7 +196,7 @@ python3 app.py # windows环境下该命令通 ### 2.服务器部署 -使用nohup命令在后台运行程序: +使用 nohup 命令在后台运行程序: ```bash nohup python3 app.py & tail -f nohup.out # 在后台运行程序并通过日志输出二维码 @@ -208,11 +208,11 @@ nohup python3 app.py & tail -f nohup.out # 在后台运行程序并通 > **特殊指令:** 用户向机器人发送 **#reset** 即可清空该用户的上下文记忆。 -### 3.Docker部署 +### 3.Docker 部署 -> 使用docker部署无需下载源码和安装依赖,只需要获取 docker-compose.yml 配置文件并启动容器即可。 +> 使用 docker 部署无需下载源码和安装依赖,只需要获取 docker-compose.yml 配置文件并启动容器即可。 -> 前提是需要安装好 `docker` 及 `docker-compose`,安装成功的表现是执行 `docker -v` 和 `docker-compose version` (或 docker compose version) 可以查看到版本号,可前往 [docker官网](https://docs.docker.com/engine/install/) 进行下载。 +> 前提是需要安装好 `docker` 及 `docker-compose`,安装成功的表现是执行 `docker -v` 和 `docker-compose version` (或 docker compose version) 可以查看到版本号,可前往 [docker 官网](https://docs.docker.com/engine/install/) 进行下载。 **(1) 下载 docker-compose.yml 文件** @@ -245,23 +245,23 @@ sudo docker logs -f chatgpt-on-wechat **(3) 插件使用** -如果需要在docker容器中修改插件配置,可通过挂载的方式完成,将 [插件配置文件](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/config.json.template) -重命名为 `config.json`,放置于 `docker-compose.yml` 相同目录下,并在 `docker-compose.yml` 中的 `chatgpt-on-wechat` 部分下添加 `volumes` 映射: +如果需要在 docker 容器中修改插件配置,可通过挂载的方式完成,将 [插件配置文件](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/config.json.template) +重命名为 `config.json`,放置于 `docker-compose.yml` 相同目录下,并在 `docker-compose.yml` 中的 `chatgpt-on-wechat` 部分下添加 `volumes` 映射: ``` volumes: - ./config.json:/app/plugins/config.json ``` -### 4. Railway部署 +### 4. Railway 部署 -> Railway 每月提供5刀和最多500小时的免费额度。 (07.11更新: 目前大部分账号已无法免费部署) +> Railway 每月提供 5 刀和最多 500 小时的免费额度。 (07.11 更新:目前大部分账号已无法免费部署) 1. 进入 [Railway](https://railway.app/template/qApznZ?referralCode=RC3znh) 2. 点击 `Deploy Now` 按钮。 3. 设置环境变量来重载程序运行的参数,例如`open_ai_api_key`, `character_desc`。 -**一键部署:** +**一键部署:** [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/qApznZ?referralCode=RC3znh) @@ -269,17 +269,17 @@ volumes: # 🔎 常见问题 -FAQs: +FAQs: 或直接在线咨询 [项目小助手](https://link-ai.tech/app/Kv2fXJcH) (语料持续完善中,回复仅供参考) # 🛠️ 开发 -欢迎接入更多应用,参考 [Terminal代码](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/channel/terminal/terminal_channel.py) 实现接收和发送消息逻辑即可接入。 同时欢迎增加新的插件,参考 [插件说明文档](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins)。 +欢迎接入更多应用,参考 [Terminal 代码](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/channel/terminal/terminal_channel.py) 实现接收和发送消息逻辑即可接入。同时欢迎增加新的插件,参考 [插件说明文档](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins)。 # ✉ 联系 -欢迎提交PR、Issues,以及Star支持一下。程序运行遇到问题可以查看 [常见问题列表](https://github.com/zhayujie/chatgpt-on-wechat/wiki/FAQs) ,其次前往 [Issues](https://github.com/zhayujie/chatgpt-on-wechat/issues) 中搜索。个人开发者可加入开源交流群参与更多讨论,企业用户可联系[产品顾问](https://img-1317903499.cos.ap-guangzhou.myqcloud.com/docs/product-manager-qrcode.jpg)咨询。 +欢迎提交 PR、Issues,以及 Star 支持一下。程序运行遇到问题可以查看 [常见问题列表](https://github.com/zhayujie/chatgpt-on-wechat/wiki/FAQs) ,其次前往 [Issues](https://github.com/zhayujie/chatgpt-on-wechat/issues) 中搜索。个人开发者可加入开源交流群参与更多讨论,企业用户可联系[产品顾问](https://img-1317903499.cos.ap-guangzhou.myqcloud.com/docs/product-manager-qrcode.jpg)咨询。 # 🌟 贡献者 diff --git a/bridge/bridge.py b/bridge/bridge.py index 6733701e3..b775676ef 100644 --- a/bridge/bridge.py +++ b/bridge/bridge.py @@ -91,6 +91,6 @@ def find_chat_bot(self, bot_type: str): def reset_bot(self): """ - 重置bot路由 + 重置 bot 路由 """ self.__init__() diff --git a/bridge/reply.py b/bridge/reply.py index f2293bdfb..3caeab9c4 100644 --- a/bridge/reply.py +++ b/bridge/reply.py @@ -7,10 +7,10 @@ class ReplyType(Enum): TEXT = 1 # 文本 VOICE = 2 # 音频文件 IMAGE = 3 # 图片文件 - IMAGE_URL = 4 # 图片URL - VIDEO_URL = 5 # 视频URL + IMAGE_URL = 4 # 图片 URL + VIDEO_URL = 5 # 视频 URL FILE = 6 # 文件 - CARD = 7 # 微信名片,仅支持ntchat + CARD = 7 # 微信名片,仅支持 ntchat INVITE_ROOM = 8 # 邀请好友进群 INFO = 9 ERROR = 10 diff --git a/common/linkai_client.py b/common/linkai_client.py index 3329f74fc..71c96313f 100644 --- a/common/linkai_client.py +++ b/common/linkai_client.py @@ -29,7 +29,7 @@ def on_message(self, push_msg: PushMsg): def on_config(self, config: dict): if not self.client_id: return - logger.info(f"[LinkAI] 从客户端管理加载远程配置: {config}") + logger.info(f"[LinkAI] 从客户端管理加载远程配置:{config}") if config.get("enabled") != "Y": return diff --git a/common/time_check.py b/common/time_check.py index 5c2dacba6..9269fe447 100644 --- a/common/time_check.py +++ b/common/time_check.py @@ -13,7 +13,7 @@ def _time_checker(self, *args, **kwargs): if chat_time_module: chat_start_time = _config.get("chat_start_time", "00:00") chat_stopt_time = _config.get("chat_stop_time", "24:00") - time_regex = re.compile(r"^([01]?[0-9]|2[0-4])(:)([0-5][0-9])$") # 时间匹配,包含24:00 + time_regex = re.compile(r"^([01]?[0-9]|2[0-4])(:)([0-5][0-9])$") # 时间匹配,包含 24:00 starttime_format_check = time_regex.match(chat_start_time) # 检查停止时间格式 stoptime_format_check = time_regex.match(chat_stopt_time) # 检查停止时间格式 @@ -21,9 +21,9 @@ def _time_checker(self, *args, **kwargs): # 时间格式检查 if not (starttime_format_check and stoptime_format_check and chat_time_check): - logger.warn("时间格式不正确,请在config.json中修改您的CHAT_START_TIME/CHAT_STOP_TIME,否则可能会影响您正常使用,开始({})-结束({})".format(starttime_format_check, stoptime_format_check)) + logger.warn("时间格式不正确,请在 config.json 中修改您的 CHAT_START_TIME/CHAT_STOP_TIME,否则可能会影响您正常使用,开始 ({})-结束 ({})".format(starttime_format_check, stoptime_format_check)) if chat_start_time > "23:59": - logger.error("启动时间可能存在问题,请修改!") + logger.error("启动时间可能存在问题,请修改!") # 服务时间检查 now_time = time.strftime("%H:%M", time.localtime()) @@ -34,7 +34,7 @@ def _time_checker(self, *args, **kwargs): if args[0]["Content"] == "#更新配置": # 不在服务时间内也可以更新配置 f(self, *args, **kwargs) else: - logger.info("非服务时间内,不接受访问") + logger.info("非服务时间内,不接受访问") return None else: f(self, *args, **kwargs) # 未开启时间模块则直接回答 diff --git a/common/token_bucket.py b/common/token_bucket.py index 23901b67e..6fe8b187b 100644 --- a/common/token_bucket.py +++ b/common/token_bucket.py @@ -5,7 +5,7 @@ class TokenBucket: def __init__(self, tpm, timeout=None): self.capacity = int(tpm) # 令牌桶容量 - self.tokens = 0 # 初始令牌数为0 + self.tokens = 0 # 初始令牌数为 0 self.rate = int(tpm) / 60 # 令牌每秒生成速率 self.timeout = timeout # 等待令牌超时时间 self.cond = threading.Condition() # 条件变量 @@ -37,7 +37,7 @@ def close(self): if __name__ == "__main__": - token_bucket = TokenBucket(20, None) # 创建一个每分钟生产20个tokens的令牌桶 + token_bucket = TokenBucket(20, None) # 创建一个每分钟生产 20 个 tokens 的令牌桶 # token_bucket = TokenBucket(20, 0.1) for i in range(3): if token_bucket.get_token(): diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8dbb1e41e..102b5f527 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -12,11 +12,11 @@ services: SINGLE_CHAT_PREFIX: '["bot", "@bot"]' SINGLE_CHAT_REPLY_PREFIX: '"[bot] "' GROUP_CHAT_PREFIX: '["@bot"]' - GROUP_NAME_WHITE_LIST: '["ChatGPT测试群", "ChatGPT测试群2"]' + GROUP_NAME_WHITE_LIST: '["ChatGPT 测试群", "ChatGPT 测试群 2"]' IMAGE_CREATE_PREFIX: '["画", "看", "找"]' CONVERSATION_MAX_TOKENS: 1000 SPEECH_RECOGNITION: 'False' - CHARACTER_DESC: '你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。' + CHARACTER_DESC: '你是 ChatGPT, 一个由 OpenAI 训练的大型语言模型,你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。' EXPIRES_IN_SECONDS: 3600 USE_GLOBAL_PLUGIN_CONFIG: 'True' USE_LINKAI: 'False' diff --git a/docs/version/old-version.md b/docs/version/old-version.md index ec719cd2b..6afbd1137 100644 --- a/docs/version/old-version.md +++ b/docs/version/old-version.md @@ -1,13 +1,13 @@ ## 归档更新日志 -2023.04.26: 支持企业微信应用号部署,兼容插件,并支持语音图片交互,私人助理理想选择,使用文档。(contributed by @lanvent in #944) +2023.04.26:支持企业微信应用号部署,兼容插件,并支持语音图片交互,私人助理理想选择,使用文档。(contributed by @lanvent in #944) -2023.04.05: 支持微信公众号部署,兼容插件,并支持语音图片交互,使用文档。(contributed by @JS00000 in #686) +2023.04.05:支持微信公众号部署,兼容插件,并支持语音图片交互,使用文档。(contributed by @JS00000 in #686) -2023.04.05: 增加能让ChatGPT使用工具的tool插件,使用文档。工具相关issue可反馈至chatgpt-tool-hub。(contributed by @goldfishh in #663) +2023.04.05:增加能让 ChatGPT 使用工具的 tool 插件,使用文档。工具相关 issue 可反馈至 chatgpt-tool-hub。(contributed by @goldfishh in #663) -2023.03.25: 支持插件化开发,目前已实现 多角色切换、文字冒险游戏、管理员指令、Stable Diffusion等插件,使用参考 #578。(contributed by @lanvent in #565) +2023.03.25:支持插件化开发,目前已实现 多角色切换、文字冒险游戏、管理员指令、Stable Diffusion 等插件,使用参考 #578。(contributed by @lanvent in #565) -2023.03.09: 基于 whisper API(后续已接入更多的语音API服务) 实现对微信语音消息的解析和回复,添加配置项 "speech_recognition":true 即可启用,使用参考 #415。(contributed by wanggang1987 in #385) +2023.03.09:基于 whisper API(后续已接入更多的语音 API 服务) 实现对微信语音消息的解析和回复,添加配置项 "speech_recognition":true 即可启用,使用参考 #415。(contributed by wanggang1987 in #385) -2023.02.09: 扫码登录存在账号限制风险,请谨慎使用,参考#58 \ No newline at end of file +2023.02.09:扫码登录存在账号限制风险,请谨慎使用,参考#58 \ No newline at end of file diff --git a/lib/itchat/returnvalues.py b/lib/itchat/returnvalues.py index f42f4e866..baf7d0d3c 100644 --- a/lib/itchat/returnvalues.py +++ b/lib/itchat/returnvalues.py @@ -55,7 +55,7 @@ def __repr__(self): TRANSLATION = { 'Chinese': { - -1000: u'返回值不带BaseResponse', + -1000: u'返回值不带 BaseResponse', -1001: u'无法找到对应的成员', -1002: u'文件位置错误', -1003: u'服务器拒绝连接', From a47c0736dda4725f72ae0a9b0e9ca2a6da2f127c Mon Sep 17 00:00:00 2001 From: ihavecoke Date: Mon, 6 May 2024 23:29:47 +0800 Subject: [PATCH 2/2] Adjust more text formatting per Chinese copywriting guidelines --- bot/ali/ali_qwen_bot.py | 10 +- bot/ali/ali_qwen_session.py | 4 +- bot/baidu/baidu_unit_bot.py | 2 +- bot/baidu/baidu_wenxin.py | 4 +- bot/baidu/baidu_wenxin_session.py | 4 +- bot/bot_factory.py | 6 +- bot/chatgpt/chat_gpt_bot.py | 12 +- bot/claude/claude_ai_bot.py | 12 +- bot/claude/claude_ai_session.py | 2 +- bot/claudeapi/claude_api_bot.py | 2 +- bot/dashscope/dashscope_bot.py | 4 +- bot/gemini/google_gemini_bot.py | 4 +- bot/linkai/link_ai_bot.py | 14 +- bot/moonshot/moonshot_bot.py | 6 +- bot/openai/open_ai_bot.py | 10 +- bot/openai/open_ai_image.py | 4 +- bot/session_manager.py | 6 +- bot/xunfei/xunfei_spark_bot.py | 42 +++--- bot/zhipuai/zhipu_ai_image.py | 4 +- bot/zhipuai/zhipuai_bot.py | 8 +- channel/channel.py | 2 +- channel/chat_channel.py | 40 +++--- channel/chat_message.py | 22 +-- channel/dingtalk/dingtalk_channel.py | 2 +- channel/feishu/feishu_channel.py | 2 +- channel/feishu/feishu_message.py | 2 +- channel/wechat/wechat_channel.py | 22 +-- channel/wechat/wechat_message.py | 14 +- channel/wechat/wechaty_channel.py | 6 +- channel/wechat/wechaty_message.py | 14 +- channel/wechatcom/README.md | 48 +++---- channel/wechatcom/wechatcomapp_channel.py | 2 +- channel/wechatcom/wechatcomapp_client.py | 2 +- channel/wechatcom/wechatcomapp_message.py | 8 +- channel/wechatmp/README.md | 40 +++--- channel/wechatmp/passive_reply.py | 2 +- channel/wechatmp/wechatmp_channel.py | 2 +- channel/wechatmp/wechatmp_client.py | 4 +- channel/wechatmp/wechatmp_message.py | 8 +- channel/wework/wework_channel.py | 26 ++-- channel/wework/wework_message.py | 18 +-- config-template.json | 8 +- config.py | 156 +++++++++++----------- plugins/banwords/README.md | 4 +- plugins/banwords/banwords.py | 4 +- plugins/banwords/lib/WordsSearch.py | 2 +- voice/ali/ali_api.py | 38 +++--- voice/ali/ali_voice.py | 16 +-- voice/audio_convert.py | 12 +- voice/azure/azure_voice.py | 4 +- voice/baidu/README.md | 34 ++--- voice/baidu/baidu_voice.py | 14 +- 52 files changed, 369 insertions(+), 369 deletions(-) diff --git a/bot/ali/ali_qwen_bot.py b/bot/ali/ali_qwen_bot.py index ae9d7674b..7f870f7c3 100644 --- a/bot/ali/ali_qwen_bot.py +++ b/bot/ali/ali_qwen_bot.py @@ -90,7 +90,7 @@ def reply(self, query, context=None): return reply else: - reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type)) + reply = Reply(ReplyType.ERROR, "Bot 不支持处理{}类型的消息".format(context.type)) return reply def reply_text(self, session: AliQwenSession, retry_count=0) -> dict: @@ -103,7 +103,7 @@ def reply_text(self, session: AliQwenSession, retry_count=0) -> dict: try: prompt, history = self.convert_messages_format(session.messages) self.update_api_key_if_expired() - # NOTE 阿里百炼的call()函数未提供temperature参数,考虑到temperature和top_p参数作用相同,取两者较小的值作为top_p参数传入,详情见文档 https://help.aliyun.com/document_detail/2587502.htm + # NOTE 阿里百炼的 call() 函数未提供 temperature 参数,考虑到 temperature 和 top_p 参数作用相同,取两者较小的值作为 top_p 参数传入,详情见文档 https://help.aliyun.com/document_detail/2587502.htm response = broadscope_bailian.Completions().call(app_id=self.app_id(), prompt=prompt, history=history, top_p=min(self.temperature(), self.top_p())) completion_content = self.get_completion_content(response, self.node_id()) completion_tokens, total_tokens = self.calc_tokens(session.messages, completion_content) @@ -173,7 +173,7 @@ def convert_messages_format(self, messages) -> Tuple[str, List[ChatQaMessage]]: if user_content == '': raise Exception('no user message') if system_content != '': - # NOTE 模拟系统消息,测试发现人格描述以"你需要扮演ChatGPT"开头能够起作用,而以"你是ChatGPT"开头模型会直接否认 + # NOTE 模拟系统消息,测试发现人格描述以"你需要扮演 ChatGPT"开头能够起作用,而以"你是 ChatGPT"开头模型会直接否认 system_qa = ChatQaMessage(system_content, '好的,我会严格按照你的设定回答问题') history.insert(0, system_qa) logger.debug("[QWEN] converted qa messages: {}".format([item.to_dict() for item in history])) @@ -186,7 +186,7 @@ def get_completion_content(self, response, node_id): text = response['Data']['Text'] if node_id == '': return text - # TODO: 当使用流程编排创建大模型应用时,响应结构如下,最终结果在['finalResult'][node_id]['response']['text']中,暂时先这么写 + # TODO: 当使用流程编排创建大模型应用时,响应结构如下,最终结果在 ['finalResult'][node_id]['response']['text'] 中,暂时先这么写 # { # 'Success': True, # 'Code': None, @@ -194,7 +194,7 @@ def get_completion_content(self, response, node_id): # 'Data': { # 'ResponseId': '9822f38dbacf4c9b8daf5ca03a2daf15', # 'SessionId': 'session_id', - # 'Text': '{"finalResult":{"LLM_T7islK":{"params":{"modelId":"qwen-plus-v1","prompt":"${systemVars.query}${bizVars.Text}"},"response":{"text":"作为一个AI语言模型,我没有年龄,因为我没有生日。\n我只是一个程序,没有生命和身体。"}}}}', + # 'Text': '{"finalResult":{"LLM_T7islK":{"params":{"modelId":"qwen-plus-v1","prompt":"${systemVars.query}${bizVars.Text}"},"response":{"text":"作为一个 AI 语言模型,我没有年龄,因为我没有生日。\n我只是一个程序,没有生命和身体。"}}}}', # 'Thoughts': [], # 'Debug': {}, # 'DocReferences': [] diff --git a/bot/ali/ali_qwen_session.py b/bot/ali/ali_qwen_session.py index 0eb1c4a1e..f5965c4d2 100644 --- a/bot/ali/ali_qwen_session.py +++ b/bot/ali/ali_qwen_session.py @@ -53,9 +53,9 @@ def calc_tokens(self): def num_tokens_from_messages(messages, model): """Returns the number of tokens used by a list of messages.""" - # 官方token计算规则:"对于中文文本来说,1个token通常对应一个汉字;对于英文文本来说,1个token通常对应3至4个字母或1个单词" + # 官方 token 计算规则:"对于中文文本来说,1 个 token 通常对应一个汉字;对于英文文本来说,1 个 token 通常对应 3 至 4 个字母或 1 个单词" # 详情请产看文档:https://help.aliyun.com/document_detail/2586397.html - # 目前根据字符串长度粗略估计token数,不影响正常使用 + # 目前根据字符串长度粗略估计 token 数,不影响正常使用 tokens = 0 for msg in messages: tokens += len(msg["content"]) diff --git a/bot/baidu/baidu_unit_bot.py b/bot/baidu/baidu_unit_bot.py index f7714e4f4..bcfdedb71 100644 --- a/bot/baidu/baidu_unit_bot.py +++ b/bot/baidu/baidu_unit_bot.py @@ -6,7 +6,7 @@ from bridge.reply import Reply, ReplyType -# Baidu Unit对话接口 (可用, 但能力较弱) +# Baidu Unit 对话接口 (可用,但能力较弱) class BaiduUnitBot(Bot): def reply(self, query, context=None): token = self.get_token() diff --git a/bot/baidu/baidu_wenxin.py b/bot/baidu/baidu_wenxin.py index f35e0fa38..b7cc6dd22 100644 --- a/bot/baidu/baidu_wenxin.py +++ b/bot/baidu/baidu_wenxin.py @@ -94,13 +94,13 @@ def reply_text(self, session: BaiduWenxinSession, retry_count=0): logger.warn("[BAIDU] Exception: {}".format(e)) need_retry = False self.sessions.clear_session(session.session_id) - result = {"completion_tokens": 0, "content": "出错了: {}".format(e)} + result = {"completion_tokens": 0, "content": "出错了:{}".format(e)} return result def get_access_token(self): """ 使用 AK,SK 生成鉴权签名(Access Token) - :return: access_token,或是None(如果错误) + :return: access_token,或是 None(如果错误) """ url = "https://aip.baidubce.com/oauth/2.0/token" params = {"grant_type": "client_credentials", "client_id": BAIDU_API_KEY, "client_secret": BAIDU_SECRET_KEY} diff --git a/bot/baidu/baidu_wenxin_session.py b/bot/baidu/baidu_wenxin_session.py index 5ba2f1787..54e7bc655 100644 --- a/bot/baidu/baidu_wenxin_session.py +++ b/bot/baidu/baidu_wenxin_session.py @@ -14,7 +14,7 @@ class BaiduWenxinSession(Session): def __init__(self, session_id, system_prompt=None, model="gpt-3.5-turbo"): super().__init__(session_id, system_prompt) self.model = model - # 百度文心不支持system prompt + # 百度文心不支持 system prompt # self.reset() def discard_exceeding(self, max_tokens, cur_tokens=None): @@ -47,7 +47,7 @@ def num_tokens_from_messages(messages, model): """Returns the number of tokens used by a list of messages.""" tokens = 0 for msg in messages: - # 官方token计算规则暂不明确: "大约为 token数为 "中文字 + 其他语种单词数 x 1.3" + # 官方 token 计算规则暂不明确: "大约为 token 数为 "中文字 + 其他语种单词数 x 1.3" # 这里先直接根据字数粗略估算吧,暂不影响正常使用,仅在判断是否丢弃历史会话的时候会有偏差 tokens += len(msg["content"]) return tokens diff --git a/bot/bot_factory.py b/bot/bot_factory.py index 3b31af02b..43d875698 100644 --- a/bot/bot_factory.py +++ b/bot/bot_factory.py @@ -12,19 +12,19 @@ def create_bot(bot_type): :return: bot instance """ if bot_type == const.BAIDU: - # 替换Baidu Unit为Baidu文心千帆对话接口 + # 替换 Baidu Unit 为 Baidu 文心千帆对话接口 # from bot.baidu.baidu_unit_bot import BaiduUnitBot # return BaiduUnitBot() from bot.baidu.baidu_wenxin import BaiduWenxinBot return BaiduWenxinBot() elif bot_type == const.CHATGPT: - # ChatGPT 网页端web接口 + # ChatGPT 网页端 web 接口 from bot.chatgpt.chat_gpt_bot import ChatGPTBot return ChatGPTBot() elif bot_type == const.OPEN_AI: - # OpenAI 官方对话模型API + # OpenAI 官方对话模型 API from bot.openai.open_ai_bot import OpenAIBot return OpenAIBot() diff --git a/bot/chatgpt/chat_gpt_bot.py b/bot/chatgpt/chat_gpt_bot.py index 979ce4c4e..3683b83ed 100644 --- a/bot/chatgpt/chat_gpt_bot.py +++ b/bot/chatgpt/chat_gpt_bot.py @@ -17,7 +17,7 @@ from config import conf, load_config -# OpenAI对话模型API (可用) +# OpenAI 对话模型 API (可用) class ChatGPTBot(Bot, OpenAIImage): def __init__(self): super().__init__() @@ -34,12 +34,12 @@ def __init__(self): self.sessions = SessionManager(ChatGPTSession, model=conf().get("model") or "gpt-3.5-turbo") self.args = { "model": conf().get("model") or "gpt-3.5-turbo", # 对话模型的名称 - "temperature": conf().get("temperature", 0.9), # 值在[0,1]之间,越大表示回复越具有不确定性 + "temperature": conf().get("temperature", 0.9), # 值在 [0,1] 之间,越大表示回复越具有不确定性 # "max_tokens":4096, # 回复最大的字符数 "top_p": conf().get("top_p", 1), - "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 - "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 - "request_timeout": conf().get("request_timeout", None), # 请求超时时间,openai接口默认设置为600,对于难问题一般需要较长时间 + "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 + "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 + "request_timeout": conf().get("request_timeout", None), # 请求超时时间,openai 接口默认设置为 600,对于难问题一般需要较长时间 "timeout": conf().get("request_timeout", None), # 重试超时时间,在这个时间内,将会自动重试 } @@ -103,7 +103,7 @@ def reply(self, query, context=None): reply = Reply(ReplyType.ERROR, retstring) return reply else: - reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type)) + reply = Reply(ReplyType.ERROR, "Bot 不支持处理{}类型的消息".format(context.type)) return reply def reply_text(self, session: ChatGPTSession, api_key=None, args=None, retry_count=0) -> dict: diff --git a/bot/claude/claude_ai_bot.py b/bot/claude/claude_ai_bot.py index faad274cb..ee5c7dd12 100644 --- a/bot/claude/claude_ai_bot.py +++ b/bot/claude/claude_ai_bot.py @@ -47,7 +47,7 @@ def reply(self, query, context: Context = None) -> Reply: reply = Reply(ReplyType.ERROR, res) return reply else: - reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type)) + reply = Reply(ReplyType.ERROR, "Bot 不支持处理{}类型的消息".format(context.type)) return reply def get_organization_id(self): @@ -71,10 +71,10 @@ def get_organization_id(self): except: if "App unavailable" in response.text: logger.error("IP error: The IP is not allowed to be used on Claude") - self.error = "ip所在地区不被claude支持" + self.error = "ip 所在地区不被 claude 支持" elif "Invalid authorization" in response.text: logger.error("Cookie error: Invalid authorization of claude, check cookie please.") - self.error = "无法通过claude身份验证,请检查cookie" + self.error = "无法通过 claude 身份验证,请检查 cookie" return None return uuid @@ -93,8 +93,8 @@ def check_cookie(self): def create_new_chat(self, con_uuid): """ - 新建claude对话实体 - :param con_uuid: 对话id + 新建 claude 对话实体 + :param con_uuid: 对话 id :return: """ url = f"https://claude.ai/api/organizations/{self.org_uuid}/chat_conversations" @@ -193,7 +193,7 @@ def _chat(self, query, context, retry_count=0) -> Reply: if "rate limi" in reply_content: logger.error("rate limit error: The conversation has reached the system speed limit and is synchronized with Cladue. Please go to the official website to check the lifting time") - return Reply(ReplyType.ERROR, "对话达到系统速率限制,与cladue同步,请进入官网查看解除限制时间") + return Reply(ReplyType.ERROR, "对话达到系统速率限制,与 cladue 同步,请进入官网查看解除限制时间") logger.info(f"[CLAUDE] reply={reply_content}, total_tokens=invisible") self.sessions.session_reply(reply_content, session_id, 100) return Reply(ReplyType.TEXT, reply_content) diff --git a/bot/claude/claude_ai_session.py b/bot/claude/claude_ai_session.py index ede9e51d4..7f7417e2c 100644 --- a/bot/claude/claude_ai_session.py +++ b/bot/claude/claude_ai_session.py @@ -5,5 +5,5 @@ class ClaudeAiSession(Session): def __init__(self, session_id, system_prompt=None, model="claude"): super().__init__(session_id, system_prompt) self.model = model - # claude逆向不支持role prompt + # claude 逆向不支持 role prompt # self.reset() diff --git a/bot/claudeapi/claude_api_bot.py b/bot/claudeapi/claude_api_bot.py index dfb4b061a..14cb3a728 100644 --- a/bot/claudeapi/claude_api_bot.py +++ b/bot/claudeapi/claude_api_bot.py @@ -19,7 +19,7 @@ user_session = dict() -# OpenAI对话模型API (可用) +# OpenAI 对话模型 API (可用) class ClaudeAPIBot(Bot, OpenAIImage): def __init__(self): super().__init__() diff --git a/bot/dashscope/dashscope_bot.py b/bot/dashscope/dashscope_bot.py index 07554c4d2..cff37955d 100644 --- a/bot/dashscope/dashscope_bot.py +++ b/bot/dashscope/dashscope_bot.py @@ -19,7 +19,7 @@ "qwen-max": dashscope.Generation.Models.qwen_max, "qwen-bailian-v1": dashscope.Generation.Models.bailian_v1 } -# ZhipuAI对话模型API +# ZhipuAI 对话模型 API class DashscopeBot(Bot): def __init__(self): super().__init__() @@ -70,7 +70,7 @@ def reply(self, query, context=None): logger.debug("[DASHSCOPE] reply {} used 0 tokens.".format(reply_content)) return reply else: - reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type)) + reply = Reply(ReplyType.ERROR, "Bot 不支持处理{}类型的消息".format(context.type)) return reply def reply_text(self, session: DashscopeSession, retry_count=0) -> dict: diff --git a/bot/gemini/google_gemini_bot.py b/bot/gemini/google_gemini_bot.py index 6132b7856..880a6f944 100644 --- a/bot/gemini/google_gemini_bot.py +++ b/bot/gemini/google_gemini_bot.py @@ -16,13 +16,13 @@ from bot.baidu.baidu_wenxin_session import BaiduWenxinSession -# OpenAI对话模型API (可用) +# OpenAI 对话模型 API (可用) class GoogleGeminiBot(Bot): def __init__(self): super().__init__() self.api_key = conf().get("gemini_api_key") - # 复用文心的token计算方式 + # 复用文心的 token 计算方式 self.sessions = SessionManager(BaiduWenxinSession, model=conf().get("model") or "gpt-3.5-turbo") def reply(self, query, context: Context = None) -> Reply: diff --git a/bot/linkai/link_ai_bot.py b/bot/linkai/link_ai_bot.py index 3fe813164..6f74df95d 100644 --- a/bot/linkai/link_ai_bot.py +++ b/bot/linkai/link_ai_bot.py @@ -41,7 +41,7 @@ def reply(self, query, context: Context = None) -> Reply: reply = Reply(ReplyType.ERROR, res) return reply else: - reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type)) + reply = Reply(ReplyType.ERROR, "Bot 不支持处理{}类型的消息".format(context.type)) return reply def _chat(self, query, context, retry_count=0) -> Reply: @@ -86,11 +86,11 @@ def _chat(self, query, context, retry_count=0) -> Reply: body = { "app_code": app_code, "messages": session_message, - "model": model, # 对话模型的名称, 支持 gpt-3.5-turbo, gpt-3.5-turbo-16k, gpt-4, wenxin, xunfei + "model": model, # 对话模型的名称,支持 gpt-3.5-turbo, gpt-3.5-turbo-16k, gpt-4, wenxin, xunfei "temperature": conf().get("temperature"), "top_p": conf().get("top_p", 1), - "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 - "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 + "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 + "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 "session_id": session_id, "sender_id": session_id, "channel_type": conf().get("channel_type", "wx") @@ -250,11 +250,11 @@ def reply_text(self, session: ChatGPTSession, app_code="", retry_count=0) -> dic body = { "app_code": app_code, "messages": session.messages, - "model": conf().get("model") or "gpt-3.5-turbo", # 对话模型的名称, 支持 gpt-3.5-turbo, gpt-3.5-turbo-16k, gpt-4, wenxin, xunfei + "model": conf().get("model") or "gpt-3.5-turbo", # 对话模型的名称,支持 gpt-3.5-turbo, gpt-3.5-turbo-16k, gpt-4, wenxin, xunfei "temperature": conf().get("temperature"), "top_p": conf().get("top_p", 1), - "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 - "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 + "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 + "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 } if self.args.get("max_tokens"): body["max_tokens"] = self.args.get("max_tokens") diff --git a/bot/moonshot/moonshot_bot.py b/bot/moonshot/moonshot_bot.py index 7d2589cda..b75c67003 100644 --- a/bot/moonshot/moonshot_bot.py +++ b/bot/moonshot/moonshot_bot.py @@ -14,7 +14,7 @@ import requests -# ZhipuAI对话模型API +# ZhipuAI 对话模型 API class MoonshotBot(Bot): def __init__(self): super().__init__() @@ -76,7 +76,7 @@ def reply(self, query, context=None): logger.debug("[MOONSHOT_AI] reply {} used 0 tokens.".format(reply_content)) return reply else: - reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type)) + reply = Reply(ReplyType.ERROR, "Bot 不支持处理{}类型的消息".format(context.type)) return reply def reply_text(self, session: MoonshotSession, args=None, retry_count=0) -> dict: @@ -121,7 +121,7 @@ def reply_text(self, session: MoonshotSession, args=None, retry_count=0) -> dict logger.warn(f"[MOONSHOT_AI] do retry, times={retry_count}") need_retry = retry_count < 2 elif res.status_code == 401: - result["content"] = "授权失败,请检查API Key是否正确" + result["content"] = "授权失败,请检查 API Key 是否正确" elif res.status_code == 429: result["content"] = "请求过于频繁,请稍后再试" need_retry = retry_count < 2 diff --git a/bot/openai/open_ai_bot.py b/bot/openai/open_ai_bot.py index 160562526..790a5281a 100644 --- a/bot/openai/open_ai_bot.py +++ b/bot/openai/open_ai_bot.py @@ -17,7 +17,7 @@ user_session = dict() -# OpenAI对话模型API (可用) +# OpenAI 对话模型 API (可用) class OpenAIBot(Bot, OpenAIImage): def __init__(self): super().__init__() @@ -31,12 +31,12 @@ def __init__(self): self.sessions = SessionManager(OpenAISession, model=conf().get("model") or "text-davinci-003") self.args = { "model": conf().get("model") or "text-davinci-003", # 对话模型的名称 - "temperature": conf().get("temperature", 0.9), # 值在[0,1]之间,越大表示回复越具有不确定性 + "temperature": conf().get("temperature", 0.9), # 值在 [0,1] 之间,越大表示回复越具有不确定性 "max_tokens": 1200, # 回复最大的字符数 "top_p": 1, - "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 - "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 - "request_timeout": conf().get("request_timeout", None), # 请求超时时间,openai接口默认设置为600,对于难问题一般需要较长时间 + "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 + "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2] 之间,该值越大则更倾向于产生不同的内容 + "request_timeout": conf().get("request_timeout", None), # 请求超时时间,openai 接口默认设置为 600,对于难问题一般需要较长时间 "timeout": conf().get("request_timeout", None), # 重试超时时间,在这个时间内,将会自动重试 "stop": ["\n\n\n"], } diff --git a/bot/openai/open_ai_image.py b/bot/openai/open_ai_image.py index 3ff56c175..2a1f3a824 100644 --- a/bot/openai/open_ai_image.py +++ b/bot/openai/open_ai_image.py @@ -8,7 +8,7 @@ from config import conf -# OPENAI提供的画图接口 +# OPENAI 提供的画图接口 class OpenAIImage(object): def __init__(self): openai.api_key = conf().get("open_ai_api_key") @@ -25,7 +25,7 @@ def create_img(self, query, retry_count=0, api_key=None, api_base=None): prompt=query, # 图片描述 n=1, # 每次生成图片的数量 model=conf().get("text_to_image") or "dall-e-2", - # size=conf().get("image_create_size", "256x256"), # 图片大小,可选有 256x256, 512x512, 1024x1024 + # size=conf().get("image_create_size", "256x256"), # 图片大小,可选有 256x256, 512x512, 1024x1024 ) image_url = response["data"][0]["url"] logger.info("[OPEN_AI] image_url={}".format(image_url)) diff --git a/bot/session_manager.py b/bot/session_manager.py index a6e89f956..9e6c43449 100644 --- a/bot/session_manager.py +++ b/bot/session_manager.py @@ -48,15 +48,15 @@ def __init__(self, sessioncls, **session_args): def build_session(self, session_id, system_prompt=None): """ - 如果session_id不在sessions中,创建一个新的session并添加到sessions中 - 如果system_prompt不会空,会更新session的system_prompt并重置session + 如果 session_id 不在 sessions 中,创建一个新的 session 并添加到 sessions 中 + 如果 system_prompt 不会空,会更新 session 的 system_prompt 并重置 session """ if session_id is None: return self.sessioncls(session_id, system_prompt, **self.session_args) if session_id not in self.sessions: self.sessions[session_id] = self.sessioncls(session_id, system_prompt, **self.session_args) - elif system_prompt is not None: # 如果有新的system_prompt,更新并重置session + elif system_prompt is not None: # 如果有新的 system_prompt,更新并重置 session self.sessions[session_id].set_system_prompt(system_prompt) session = self.sessions[session_id] return session diff --git a/bot/xunfei/xunfei_spark_bot.py b/bot/xunfei/xunfei_spark_bot.py index 9ca6b964a..b00849e31 100644 --- a/bot/xunfei/xunfei_spark_bot.py +++ b/bot/xunfei/xunfei_spark_bot.py @@ -40,18 +40,18 @@ def __init__(self): self.app_id = conf().get("xunfei_app_id") self.api_key = conf().get("xunfei_api_key") self.api_secret = conf().get("xunfei_api_secret") - # 默认使用v2.0版本: "generalv2" - # v1.5版本为 "general" - # v3.0版本为: "generalv3" + # 默认使用 v2.0 版本:"generalv2" + # v1.5 版本为 "general" + # v3.0 版本为:"generalv3" self.domain = "generalv3" - # 默认使用v2.0版本: "ws://spark-api.xf-yun.com/v2.1/chat" - # v1.5版本为: "ws://spark-api.xf-yun.com/v1.1/chat" - # v3.0版本为: "ws://spark-api.xf-yun.com/v3.1/chat" - # v3.5版本为: "wss://spark-api.xf-yun.com/v3.5/chat" + # 默认使用 v2.0 版本:"ws://spark-api.xf-yun.com/v2.1/chat" + # v1.5 版本为:"ws://spark-api.xf-yun.com/v1.1/chat" + # v3.0 版本为:"ws://spark-api.xf-yun.com/v3.1/chat" + # v3.5 版本为:"wss://spark-api.xf-yun.com/v3.5/chat" self.spark_url = "wss://spark-api.xf-yun.com/v3.5/chat" self.host = urlparse(self.spark_url).netloc self.path = urlparse(self.spark_url).path - # 和wenxin使用相同的session机制 + # 和 wenxin 使用相同的 session 机制 self.sessions = SessionManager(BaiduWenxinSession, model=const.XUNFEI) def reply(self, query, context: Context = None) -> Reply: @@ -99,7 +99,7 @@ def reply(self, query, context: Context = None) -> Reply: return reply else: reply = Reply(ReplyType.ERROR, - "Bot不支持处理{}类型的消息".format(context.type)) + "Bot 不支持处理{}类型的消息".format(context.type)) return reply def create_web_socket(self, prompt, session_id, temperature=0.5): @@ -124,9 +124,9 @@ def gen_request_id(self, session_id: str): return session_id + "_" + str(int(time.time())) + "" + str( random.randint(0, 100)) - # 生成url + # 生成 url def create_url(self): - # 生成RFC1123格式的时间戳 + # 生成 RFC1123 格式的时间戳 now = datetime.now() date = format_date_time(mktime(now.timetuple())) @@ -135,7 +135,7 @@ def create_url(self): signature_origin += "date: " + date + "\n" signature_origin += "GET " + self.path + " HTTP/1.1" - # 进行hmac-sha256进行加密 + # 进行 hmac-sha256 进行加密 signature_sha = hmac.new(self.api_secret.encode('utf-8'), signature_origin.encode('utf-8'), digestmod=hashlib.sha256).digest() @@ -151,14 +151,14 @@ def create_url(self): # 将请求的鉴权参数组合为字典 v = {"authorization": authorization, "date": date, "host": self.host} - # 拼接鉴权参数,生成url + # 拼接鉴权参数,生成 url url = self.spark_url + '?' + urlencode(v) - # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致 + # 此处打印出建立连接时候的 url,参考本 demo 的时候可取消上方打印的注释,比对相同参数时生成的 url 与自己代码生成的 url 是否一致 return url def gen_params(self, appid, domain, question): """ - 通过appid和用户的提问来生成请参数 + 通过 appid 和用户的提问来生成请参数 """ data = { "header": { @@ -189,18 +189,18 @@ def __init__(self, reply, usage=None, is_end=False): self.usage = usage -# 收到websocket错误的处理 +# 收到 websocket 错误的处理 def on_error(ws, error): logger.error(f"[XunFei] error: {str(error)}") -# 收到websocket关闭的处理 +# 收到 websocket 关闭的处理 def on_close(ws, one, two): data_queue = queue_map.get(ws.session_id) data_queue.put("END") -# 收到websocket连接建立的处理 +# 收到 websocket 连接建立的处理 def on_open(ws): logger.info(f"[XunFei] Start websocket, session_id={ws.session_id}") thread.start_new_thread(run, (ws, )) @@ -216,12 +216,12 @@ def run(ws, *args): # Websocket 操作 -# 收到websocket消息的处理 +# 收到 websocket 消息的处理 def on_message(ws, message): data = json.loads(message) code = data['header']['code'] if code != 0: - logger.error(f'请求错误: {code}, {data}') + logger.error(f'请求错误:{code}, {data}') ws.close() else: choices = data["payload"]["choices"] @@ -243,7 +243,7 @@ def on_message(ws, message): def gen_params(appid, domain, question, temperature=0.5): """ - 通过appid和用户的提问来生成请参数 + 通过 appid 和用户的提问来生成请参数 """ data = { "header": { diff --git a/bot/zhipuai/zhipu_ai_image.py b/bot/zhipuai/zhipu_ai_image.py index 84eb5671e..bd1a266c0 100644 --- a/bot/zhipuai/zhipu_ai_image.py +++ b/bot/zhipuai/zhipu_ai_image.py @@ -2,7 +2,7 @@ from config import conf -# ZhipuAI提供的画图接口 +# ZhipuAI 提供的画图接口 class ZhipuAIImage(object): def __init__(self): @@ -18,7 +18,7 @@ def create_img(self, query, retry_count=0, api_key=None, api_base=None): prompt=query, n=1, # 每次生成图片的数量 model=conf().get("text_to_image") or "cogview-3", - size=conf().get("image_create_size", "1024x1024"), # 图片大小,可选有 256x256, 512x512, 1024x1024 + size=conf().get("image_create_size", "1024x1024"), # 图片大小,可选有 256x256, 512x512, 1024x1024 quality="standard", ) image_url = response.data[0].url diff --git a/bot/zhipuai/zhipuai_bot.py b/bot/zhipuai/zhipuai_bot.py index d8eed4d35..819b29332 100644 --- a/bot/zhipuai/zhipuai_bot.py +++ b/bot/zhipuai/zhipuai_bot.py @@ -15,15 +15,15 @@ from zhipuai import ZhipuAI -# ZhipuAI对话模型API +# ZhipuAI 对话模型 API class ZHIPUAIBot(Bot, ZhipuAIImage): def __init__(self): super().__init__() self.sessions = SessionManager(ZhipuAISession, model=conf().get("model") or "ZHIPU_AI") self.args = { "model": conf().get("model") or "glm-4", # 对话模型的名称 - "temperature": conf().get("temperature", 0.9), # 值在(0,1)之间(智谱AI 的温度不能取 0 或者 1) - "top_p": conf().get("top_p", 0.7), # 值在(0,1)之间(智谱AI 的 top_p 不能取 0 或者 1) + "temperature": conf().get("temperature", 0.9), # 值在 (0,1) 之间 (智谱 AI 的温度不能取 0 或者 1) + "top_p": conf().get("top_p", 0.7), # 值在 (0,1) 之间 (智谱 AI 的 top_p 不能取 0 或者 1) } self.client = ZhipuAI(api_key=conf().get("zhipu_ai_api_key")) @@ -87,7 +87,7 @@ def reply(self, query, context=None): return reply else: - reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type)) + reply = Reply(ReplyType.ERROR, "Bot 不支持处理{}类型的消息".format(context.type)) return reply def reply_text(self, session: ZhipuAISession, api_key=None, args=None, retry_count=0) -> dict: diff --git a/channel/channel.py b/channel/channel.py index c22534273..f8dab2d5e 100644 --- a/channel/channel.py +++ b/channel/channel.py @@ -24,7 +24,7 @@ def handle_text(self, msg): """ raise NotImplementedError - # 统一的发送函数,每个Channel自行实现,根据reply的type字段发送不同类型的消息 + # 统一的发送函数,每个 Channel 自行实现,根据 reply 的 type 字段发送不同类型的消息 def send(self, reply: Reply, context: Context): """ send message to user diff --git a/channel/chat_channel.py b/channel/chat_channel.py index b3f8d1aea..aaf3becbb 100644 --- a/channel/chat_channel.py +++ b/channel/chat_channel.py @@ -20,32 +20,32 @@ handler_pool = ThreadPoolExecutor(max_workers=8) # 处理消息的线程池 -# 抽象类, 它包含了与消息通道无关的通用处理逻辑 +# 抽象类,它包含了与消息通道无关的通用处理逻辑 class ChatChannel(Channel): name = None # 登录的用户名 - user_id = None # 登录的用户id - futures = {} # 记录每个session_id提交到线程池的future对象, 用于重置会话时把没执行的future取消掉,正在执行的不会被取消 - sessions = {} # 用于控制并发,每个session_id同时只能有一个context在处理 - lock = threading.Lock() # 用于控制对sessions的访问 + user_id = None # 登录的用户 id + futures = {} # 记录每个 session_id 提交到线程池的 future 对象,用于重置会话时把没执行的 future 取消掉,正在执行的不会被取消 + sessions = {} # 用于控制并发,每个 session_id 同时只能有一个 context 在处理 + lock = threading.Lock() # 用于控制对 sessions 的访问 def __init__(self): _thread = threading.Thread(target=self.consume) _thread.setDaemon(True) _thread.start() - # 根据消息构造context,消息内容相关的触发项写在这里 + # 根据消息构造 context,消息内容相关的触发项写在这里 def _compose_context(self, ctype: ContextType, content, **kwargs): context = Context(ctype, content) context.kwargs = kwargs - # context首次传入时,origin_ctype是None, - # 引入的起因是:当输入语音时,会嵌套生成两个context,第一步语音转文本,第二步通过文本生成文字回复。 - # origin_ctype用于第二步文本回复时,判断是否需要匹配前缀,如果是私聊的语音,就不需要匹配前缀 + # context 首次传入时,origin_ctype 是 None, + # 引入的起因是:当输入语音时,会嵌套生成两个 context,第一步语音转文本,第二步通过文本生成文字回复。 + # origin_ctype 用于第二步文本回复时,判断是否需要匹配前缀,如果是私聊的语音,就不需要匹配前缀 if "origin_ctype" not in context: context["origin_ctype"] = ctype - # context首次传入时,receiver是None,根据类型设置receiver + # context 首次传入时,receiver 是 None,根据类型设置 receiver first_in = "receiver" not in context - # 群名匹配过程,设置session_id和receiver - if first_in: # context首次传入时,receiver是None,根据类型设置receiver + # 群名匹配过程,设置 session_id 和 receiver + if first_in: # context 首次传入时,receiver 是 None,根据类型设置 receiver config = conf() cmsg = context["msg"] user_data = conf().get_user_data(cmsg.from_user_id) @@ -89,7 +89,7 @@ def _compose_context(self, ctype: ContextType, content, **kwargs): logger.debug("[WX]self message skipped") return None - # 消息内容匹配过程,并处理content + # 消息内容匹配过程,并处理 content if ctype == ContextType.TEXT: if first_in and "」\n- - - - - - -" in content: # 初次匹配 过滤引用消息 logger.debug(content) @@ -140,7 +140,7 @@ def _compose_context(self, ctype: ContextType, content, **kwargs): return None match_prefix = check_prefix(content, conf().get("single_chat_prefix", [""])) - if match_prefix is not None: # 判断如果匹配到自定义前缀,则返回过滤掉前缀+空格后的内容 + if match_prefix is not None: # 判断如果匹配到自定义前缀,则返回过滤掉前缀 + 空格后的内容 content = content.replace(match_prefix, "", 1).strip() elif context["origin_ctype"] == ContextType.VOICE: # 如果源消息是私聊的语音消息,允许不匹配前缀,放宽条件 pass @@ -166,16 +166,16 @@ def _handle(self, context: Context): if context is None or not context.content: return logger.debug("[WX] ready to handle context: {}".format(context)) - # reply的构建步骤 + # reply 的构建步骤 reply = self._generate_reply(context) logger.debug("[WX] ready to decorate reply: {}".format(reply)) - # reply的包装步骤 + # reply 的包装步骤 if reply and reply.content: reply = self._decorate_reply(context, reply) - # reply的发送步骤 + # reply 的发送步骤 self._send_reply(context, reply) def _generate_reply(self, context: Context, reply: Reply = Reply()) -> Reply: @@ -198,7 +198,7 @@ def _generate_reply(self, context: Context, reply: Reply = Reply()) -> Reply: wav_path = os.path.splitext(file_path)[0] + ".wav" try: any_to_wav(file_path, wav_path) - except Exception as e: # 转换失败,直接使用mp3,对于某些api,mp3也可以识别 + except Exception as e: # 转换失败,直接使用 mp3,对于某些 api,mp3 也可以识别 logger.warning("[WX]any to wav error, use raw path. " + str(e)) wav_path = file_path # 语音识别 @@ -246,7 +246,7 @@ def _decorate_reply(self, context: Context, reply: Reply) -> Reply: if reply.type in self.NOT_SUPPORT_REPLYTYPE: logger.error("[WX]reply type not support: " + str(reply.type)) reply.type = ReplyType.ERROR - reply.content = "不支持发送的消息类型: " + str(reply.type) + reply.content = "不支持发送的消息类型:" + str(reply.type) if reply.type == ReplyType.TEXT: reply_text = reply.content @@ -356,7 +356,7 @@ def consume(self): semaphore.release() time.sleep(0.1) - # 取消session_id对应的所有任务,只能取消排队的消息和已提交线程池但未执行的任务 + # 取消 session_id 对应的所有任务,只能取消排队的消息和已提交线程池但未执行的任务 def cancel_session(self, session_id): with self.lock: if session_id in self.sessions: diff --git a/channel/chat_message.py b/channel/chat_message.py index ac0e5c2be..882d8bb42 100644 --- a/channel/chat_message.py +++ b/channel/chat_message.py @@ -1,32 +1,32 @@ """ -本类表示聊天消息,用于对itchat和wechaty的消息进行统一的封装。 +本类表示聊天消息,用于对 itchat 和 wechaty 的消息进行统一的封装。 -填好必填项(群聊6个,非群聊8个),即可接入ChatChannel,并支持插件,参考TerminalChannel +填好必填项 (群聊 6 个,非群聊 8 个),即可接入 ChatChannel,并支持插件,参考 TerminalChannel ChatMessage -msg_id: 消息id (必填) +msg_id: 消息 id (必填) create_time: 消息创建时间 ctype: 消息类型 : ContextType (必填) -content: 消息内容, 如果是声音/图片,这里是文件路径 (必填) +content: 消息内容,如果是声音/图片,这里是文件路径 (必填) -from_user_id: 发送者id (必填) +from_user_id: 发送者 id (必填) from_user_nickname: 发送者昵称 -to_user_id: 接收者id (必填) +to_user_id: 接收者 id (必填) to_user_nickname: 接收者昵称 -other_user_id: 对方的id,如果你是发送者,那这个就是接收者id,如果你是接收者,那这个就是发送者id,如果是群消息,那这一直是群id (必填) +other_user_id: 对方的 id,如果你是发送者,那这个就是接收者 id,如果你是接收者,那这个就是发送者 id,如果是群消息,那这一直是群 id (必填) other_user_nickname: 同上 is_group: 是否是群消息 (群聊必填) -is_at: 是否被at +is_at: 是否被 at -- (群消息时,一般会存在实际发送者,是群内某个成员的id和昵称,下列项仅在群消息时存在) -actual_user_id: 实际发送者id (群聊必填) +- (群消息时,一般会存在实际发送者,是群内某个成员的 id 和昵称,下列项仅在群消息时存在) +actual_user_id: 实际发送者 id (群聊必填) actual_user_nickname:实际发送者昵称 self_display_name: 自身的展示名,设置群昵称时,该字段表示群昵称 -_prepare_fn: 准备函数,用于准备消息的内容,比如下载图片等, +_prepare_fn: 准备函数,用于准备消息的内容,比如下载图片等, _prepared: 是否已经调用过准备函数 _rawmsg: 原始消息对象 diff --git a/channel/dingtalk/dingtalk_channel.py b/channel/dingtalk/dingtalk_channel.py index 22ef889bb..c5609a879 100644 --- a/channel/dingtalk/dingtalk_channel.py +++ b/channel/dingtalk/dingtalk_channel.py @@ -38,7 +38,7 @@ def __init__(self): super().__init__() super(dingtalk_stream.ChatbotHandler, self).__init__() self.logger = self.setup_logger() - # 历史消息id暂存,用于幂等控制 + # 历史消息 id 暂存,用于幂等控制 self.receivedMsgs = ExpiredDict(60 * 60 * 7.1) logger.info("[dingtalk] client_id={}, client_secret={} ".format( self.dingtalk_client_id, self.dingtalk_client_secret)) diff --git a/channel/feishu/feishu_channel.py b/channel/feishu/feishu_channel.py index 76fbbf1b6..36f21db09 100644 --- a/channel/feishu/feishu_channel.py +++ b/channel/feishu/feishu_channel.py @@ -34,7 +34,7 @@ class FeiShuChanel(ChatChannel): def __init__(self): super().__init__() - # 历史消息id暂存,用于幂等控制 + # 历史消息 id 暂存,用于幂等控制 self.receivedMsgs = ExpiredDict(60 * 60 * 7.1) logger.info("[FeiShu] app_id={}, app_secret={} verification_token={}".format( self.feishu_app_id, self.feishu_app_secret, self.feishu_token)) diff --git a/channel/feishu/feishu_message.py b/channel/feishu/feishu_message.py index e2054c127..278ff1f14 100644 --- a/channel/feishu/feishu_message.py +++ b/channel/feishu/feishu_message.py @@ -31,7 +31,7 @@ def __init__(self, event: dict, is_group=False, access_token=None): self.content = TmpDir().path() + file_key + "." + utils.get_path_suffix(file_name) def _download_file(): - # 如果响应状态码是200,则将响应内容写入本地文件 + # 如果响应状态码是 200,则将响应内容写入本地文件 url = f"https://open.feishu.cn/open-apis/im/v1/messages/{self.msg_id}/resources/{file_key}" headers = { "Authorization": "Bearer " + access_token, diff --git a/channel/wechat/wechat_channel.py b/channel/wechat/wechat_channel.py index b681e1243..aef4e3d91 100644 --- a/channel/wechat/wechat_channel.py +++ b/channel/wechat/wechat_channel.py @@ -56,7 +56,7 @@ def wrapper(self, cmsg: ChatMessage): return self.receivedMsgs[msgId] = True create_time = cmsg.create_time # 消息时间戳 - if conf().get("hot_reload") == True and int(create_time) < int(time.time()) - 60: # 跳过1分钟前的历史消息 + if conf().get("hot_reload") == True and int(create_time) < int(time.time()) - 60: # 跳过 1 分钟前的历史消息 logger.debug("[WX]history message {} skipped".format(msgId)) return if cmsg.my_msg and not cmsg.is_group: @@ -151,17 +151,17 @@ def loginCallback(self): logger.debug("Login success") _send_login_success() - # handle_* 系列函数处理收到的消息后构造Context,然后传入produce函数中处理Context和发送回复 - # Context包含了消息的所有信息,包括以下属性 - # type 消息类型, 包括TEXT、VOICE、IMAGE_CREATE - # content 消息内容,如果是TEXT类型,content就是文本内容,如果是VOICE类型,content就是语音文件名,如果是IMAGE_CREATE类型,content就是图片生成命令 - # kwargs 附加参数字典,包含以下的key: - # session_id: 会话id + # handle_* 系列函数处理收到的消息后构造 Context,然后传入 produce 函数中处理 Context 和发送回复 + # Context 包含了消息的所有信息,包括以下属性 + # type 消息类型,包括 TEXT、VOICE、IMAGE_CREATE + # content 消息内容,如果是 TEXT 类型,content 就是文本内容,如果是 VOICE 类型,content 就是语音文件名,如果是 IMAGE_CREATE 类型,content 就是图片生成命令 + # kwargs 附加参数字典,包含以下的 key: + # session_id: 会话 id # isgroup: 是否是群聊 # receiver: 需要回复的对象 - # msg: ChatMessage消息对象 + # msg: ChatMessage 消息对象 # origin_ctype: 原始消息类型,语音转文字后,私聊时如果匹配前缀失败,会根据初始消息是否是语音来放宽触发规则 - # desire_rtype: 希望回复类型,默认是文本回复,设置为ReplyType.VOICE是语音回复 + # desire_rtype: 希望回复类型,默认是文本回复,设置为 ReplyType.VOICE 是语音回复 @time_checker @_check def handle_single(self, cmsg: ChatMessage): @@ -206,7 +206,7 @@ def handle_group(self, cmsg: ChatMessage): if context: self.produce(context) - # 统一的发送函数,每个Channel自行实现,根据reply的type字段发送不同类型的消息 + # 统一的发送函数,每个 Channel 自行实现,根据 reply 的 type 字段发送不同类型的消息 def send(self, reply: Reply, context: Context): receiver = context["receiver"] if reply.type == ReplyType.TEXT: @@ -244,7 +244,7 @@ def send(self, reply: Reply, context: Context): video_storage = reply.content itchat.send_video(video_storage, toUserName=receiver) logger.info("[WX] sendFile, receiver={}".format(receiver)) - elif reply.type == ReplyType.VIDEO_URL: # 新增视频URL回复类型 + elif reply.type == ReplyType.VIDEO_URL: # 新增视频 URL 回复类型 video_url = reply.content logger.debug(f"[WX] start download video, video_url={video_url}") video_res = requests.get(video_url, stream=True) diff --git a/channel/wechat/wechat_message.py b/channel/wechat/wechat_message.py index b8b1d91c5..7c1be9d35 100644 --- a/channel/wechat/wechat_message.py +++ b/channel/wechat/wechat_message.py @@ -19,15 +19,15 @@ def __init__(self, itchat_msg, is_group=False): self.content = itchat_msg["Text"] elif itchat_msg["Type"] == VOICE: self.ctype = ContextType.VOICE - self.content = TmpDir().path() + itchat_msg["FileName"] # content直接存临时目录路径 + self.content = TmpDir().path() + itchat_msg["FileName"] # content 直接存临时目录路径 self._prepare_fn = lambda: itchat_msg.download(self.content) elif itchat_msg["Type"] == PICTURE and itchat_msg["MsgType"] == 3: self.ctype = ContextType.IMAGE - self.content = TmpDir().path() + itchat_msg["FileName"] # content直接存临时目录路径 + self.content = TmpDir().path() + itchat_msg["FileName"] # content 直接存临时目录路径 self._prepare_fn = lambda: itchat_msg.download(self.content) elif itchat_msg["Type"] == NOTE and itchat_msg["MsgType"] == 10000: if is_group and ("加入群聊" in itchat_msg["Content"] or "加入了群聊" in itchat_msg["Content"]): - # 这里只能得到nickname, actual_user_id还是机器人的id + # 这里只能得到 nickname,actual_user_id 还是机器人的 id if "加入了群聊" in itchat_msg["Content"]: self.ctype = ContextType.JOIN_GROUP self.content = itchat_msg["Content"] @@ -54,7 +54,7 @@ def __init__(self, itchat_msg, is_group=False): raise NotImplementedError("Unsupported note message: " + itchat_msg["Content"]) elif itchat_msg["Type"] == ATTACHMENT: self.ctype = ContextType.FILE - self.content = TmpDir().path() + itchat_msg["FileName"] # content直接存临时目录路径 + self.content = TmpDir().path() + itchat_msg["FileName"] # content 直接存临时目录路径 self._prepare_fn = lambda: itchat_msg.download(self.content) elif itchat_msg["Type"] == SHARING: self.ctype = ContextType.SHARING @@ -69,14 +69,14 @@ def __init__(self, itchat_msg, is_group=False): user_id = itchat.instance.storageClass.userName nickname = itchat.instance.storageClass.nickName - # 虽然from_user_id和to_user_id用的少,但是为了保持一致性,还是要填充一下 + # 虽然 from_user_id 和 to_user_id 用的少,但是为了保持一致性,还是要填充一下 # 以下很繁琐,一句话总结:能填的都填了。 if self.from_user_id == user_id: self.from_user_nickname = nickname if self.to_user_id == user_id: self.to_user_nickname = nickname - try: # 陌生人时候, User字段可能不存在 - # my_msg 为True是表示是自己发送的消息 + try: # 陌生人时候,User 字段可能不存在 + # my_msg 为 True 是表示是自己发送的消息 self.my_msg = itchat_msg["ToUserName"] == itchat_msg["User"]["UserName"] and \ itchat_msg["ToUserName"] != itchat_msg["FromUserName"] self.other_user_id = itchat_msg["User"]["UserName"] diff --git a/channel/wechat/wechaty_channel.py b/channel/wechat/wechaty_channel.py index 051a9cf10..458d488b1 100644 --- a/channel/wechat/wechaty_channel.py +++ b/channel/wechat/wechaty_channel.py @@ -43,7 +43,7 @@ def startup(self): async def main(self): loop = asyncio.get_event_loop() - # 将asyncio的loop传入处理线程 + # 将 asyncio 的 loop 传入处理线程 self.handler_pool._initializer = lambda: asyncio.set_event_loop(loop) self.bot = Wechaty() self.bot.on("login", self.on_login) @@ -55,7 +55,7 @@ async def on_login(self, contact: Contact): self.name = contact.name logger.info("[WX] login user={}".format(contact)) - # 统一的发送函数,每个Channel自行实现,根据reply的type字段发送不同类型的消息 + # 统一的发送函数,每个 Channel 自行实现,根据 reply 的 type 字段发送不同类型的消息 def send(self, reply: Reply, context: Context): receiver_id = context["receiver"] loop = asyncio.get_event_loop() @@ -120,7 +120,7 @@ async def on_message(self, msg: Message): logger.exception("[WX] {}".format(e)) return logger.debug("[WX] message:{}".format(cmsg)) - room = msg.room() # 获取消息来自的群聊. 如果消息不是来自群聊, 则返回None + room = msg.room() # 获取消息来自的群聊。如果消息不是来自群聊,则返回 None isgroup = room is not None ctype = cmsg.ctype context = self._compose_context(ctype, cmsg.content, isgroup=isgroup, msg=cmsg) diff --git a/channel/wechat/wechaty_message.py b/channel/wechat/wechaty_message.py index cdb41ddf2..6a060c279 100644 --- a/channel/wechat/wechaty_message.py +++ b/channel/wechat/wechaty_message.py @@ -41,7 +41,7 @@ async def __init__(self, wechaty_msg: Message): elif wechaty_msg.type() == MessageType.MESSAGE_TYPE_AUDIO: self.ctype = ContextType.VOICE voice_file = await wechaty_msg.to_file_box() - self.content = TmpDir().path() + voice_file.name # content直接存临时目录路径 + self.content = TmpDir().path() + voice_file.name # content 直接存临时目录路径 def func(): loop = asyncio.get_event_loop() @@ -56,10 +56,10 @@ def func(): self.from_user_id = from_contact.contact_id self.from_user_nickname = from_contact.name - # group中的from和to,wechaty跟itchat含义不一样 - # wecahty: from是消息实际发送者, to:所在群 - # itchat: 如果是你发送群消息,from和to是你自己和所在群,如果是别人发群消息,from和to是所在群和你自己 - # 但这个差别不影响逻辑,group中只使用到:1.用from来判断是否是自己发的,2.actual_user_id来判断实际发送用户 + # group 中的 from 和 to,wechaty 跟 itchat 含义不一样 + # wecahty: from 是消息实际发送者,to:所在群 + # itchat: 如果是你发送群消息,from 和 to 是你自己和所在群,如果是别人发群消息,from 和 to 是所在群和你自己 + # 但这个差别不影响逻辑,group 中只使用到:1.用 from 来判断是否是自己发的,2.actual_user_id 来判断实际发送用户 if self.is_group: self.to_user_id = room.room_id @@ -69,14 +69,14 @@ def func(): self.to_user_id = to_contact.contact_id self.to_user_nickname = to_contact.name - if self.is_group or wechaty_msg.is_self(): # 如果是群消息,other_user设置为群,如果是私聊消息,而且自己发的,就设置成对方。 + if self.is_group or wechaty_msg.is_self(): # 如果是群消息,other_user 设置为群,如果是私聊消息,而且自己发的,就设置成对方。 self.other_user_id = self.to_user_id self.other_user_nickname = self.to_user_nickname else: self.other_user_id = self.from_user_id self.other_user_nickname = self.from_user_nickname - if self.is_group: # wechaty群聊中,实际发送用户就是from_user + if self.is_group: # wechaty 群聊中,实际发送用户就是 from_user self.is_at = await wechaty_msg.mention_self() if not self.is_at: # 有时候复制粘贴的消息,不算做@,但是内容里面会有@xxx,这里做一下兼容 name = wechaty_msg.wechaty.user_self().name diff --git a/channel/wechatcom/README.md b/channel/wechatcom/README.md index 2f54a79fd..be14ed556 100644 --- a/channel/wechatcom/README.md +++ b/channel/wechatcom/README.md @@ -1,18 +1,18 @@ -# 企业微信应用号channel +# 企业微信应用号 channel -企业微信官方提供了客服、应用等API,本channel使用的是企业微信的自建应用API的能力。 +企业微信官方提供了客服、应用等 API,本 channel 使用的是企业微信的自建应用 API 的能力。 -因为未来可能还会开发客服能力,所以本channel的类型名叫作`wechatcom_app`。 +因为未来可能还会开发客服能力,所以本 channel 的类型名叫作`wechatcom_app`。 -`wechatcom_app` channel支持插件系统和图片声音交互等能力,除了无法加入群聊,作为个人使用的私人助理已绰绰有余。 +`wechatcom_app` channel 支持插件系统和图片声音交互等能力,除了无法加入群聊,作为个人使用的私人助理已绰绰有余。 ## 开始之前 - 在企业中确认自己拥有在企业内自建应用的权限。 - 如果没有权限或者是个人用户,也可创建未认证的企业。操作方式:登录手机企业微信,选择`创建/加入企业`来创建企业,类型请选择企业,企业名称可随意填写。 - 未认证的企业有100人的服务人数上限,其他功能与认证企业没有差异。 + 未认证的企业有 100 人的服务人数上限,其他功能与认证企业没有差异。 -本channel需安装的依赖与公众号一致,需要安装`wechatpy`和`web.py`,它们包含在`requirements-optional.txt`中。 +本 channel 需安装的依赖与公众号一致,需要安装`wechatpy`和`web.py`,它们包含在`requirements-optional.txt`中。 此外,如果你是`Linux`系统,除了`ffmpeg`还需要安装`amr`编码器,否则会出现找不到编码器的错误,无法正常使用语音功能。 @@ -28,34 +28,34 @@ apt-get install libavcodec-extra ## 使用方法 -1.查看企业ID +1.查看企业 ID - 扫码登陆[企业微信后台](https://work.weixin.qq.com) - 选择`我的企业`,点击`企业信息`,记住该`企业ID` 2.创建自建应用 -- 选择应用管理, 在自建区选创建应用来创建企业自建应用 -- 上传应用logo,填写应用名称等项 +- 选择应用管理,在自建区选创建应用来创建企业自建应用 +- 上传应用 logo,填写应用名称等项 - 创建应用后进入应用详情页面,记住`AgentId`和`Secert` 3.配置应用 -- 在详情页点击`企业可信IP`的配置(没看到可以不管),填入你服务器的公网IP,如果不知道可以先不填 -- 点击`接收消息`下的启用API接收消息 -- `URL`填写格式为`http://url:port/wxcomapp`,`port`是程序监听的端口,默认是9898 - 如果是未认证的企业,url可直接使用服务器的IP。如果是认证企业,需要使用备案的域名,可使用二级域名。 +- 在详情页点击`企业可信IP`的配置 (没看到可以不管),填入你服务器的公网 IP,如果不知道可以先不填 +- 点击`接收消息`下的启用 API 接收消息 +- `URL`填写格式为`http://url:port/wxcomapp`,`port`是程序监听的端口,默认是 9898 + 如果是未认证的企业,url 可直接使用服务器的 IP。如果是认证企业,需要使用备案的域名,可使用二级域名。 - `Token`可随意填写,停留在这个页面 - 在程序根目录`config.json`中增加配置(**去掉注释**),`wechatcomapp_aes_key`是当前页面的`wechatcomapp_aes_key` ```python "channel_type": "wechatcom_app", - "wechatcom_corp_id": "", # 企业微信公司的corpID - "wechatcomapp_token": "", # 企业微信app的token - "wechatcomapp_port": 9898, # 企业微信app的服务端口, 不需要端口转发 - "wechatcomapp_secret": "", # 企业微信app的secret - "wechatcomapp_agent_id": "", # 企业微信app的agent_id - "wechatcomapp_aes_key": "", # 企业微信app的aes_key + "wechatcom_corp_id": "", # 企业微信公司的 corpID + "wechatcomapp_token": "", # 企业微信 app 的 token + "wechatcomapp_port": 9898, # 企业微信 app 的服务端口,不需要端口转发 + "wechatcomapp_secret": "", # 企业微信 app 的 secret + "wechatcomapp_agent_id": "", # 企业微信 app 的 agent_id + "wechatcomapp_aes_key": "", # 企业微信 app 的 aes_key ``` - 运行程序,在页面中点击保存,保存成功说明验证成功 @@ -64,22 +64,22 @@ apt-get install libavcodec-extra 选择`我的企业`,点击`微信插件`,下面有个邀请关注的二维码。微信扫码后,即可在微信中看到对应企业,在这里你便可以和机器人沟通。 -向机器人发送消息,如果日志里出现报错: +向机器人发送消息,如果日志里出现报错: ```bash Error code: 60020, message: "not allow to access from your ip, ...from ip: xx.xx.xx.xx" ``` -意思是IP不可信,需要参考上一步的`企业可信IP`配置,把这里的IP加进去。 +意思是 IP 不可信,需要参考上一步的`企业可信IP`配置,把这里的 IP 加进去。 -~~### Railway部署方式~~(2023-06-08已失效) +~~### Railway 部署方式~~(2023-06-08 已失效) ~~公众号不能在`Railway`上部署,但企业微信应用[可以](https://railway.app/template/-FHS--?referralCode=RC3znh)!~~ -~~填写配置后,将部署完成后的网址```**.railway.app/wxcomapp```,填写在上一步的URL中。发送信息后观察日志,把报错的IP加入到可信IP。(每次重启后都需要加入可信IP)~~ +~~填写配置后,将部署完成后的网址```**.railway.app/wxcomapp```,填写在上一步的 URL 中。发送信息后观察日志,把报错的 IP 加入到可信 IP。(每次重启后都需要加入可信 IP)~~ ## 测试体验 -AIGC开放社区中已经部署了多个可免费使用的Bot,扫描下方的二维码会自动邀请你来体验。 +AIGC 开放社区中已经部署了多个可免费使用的 Bot,扫描下方的二维码会自动邀请你来体验。 diff --git a/channel/wechatcom/wechatcomapp_channel.py b/channel/wechatcom/wechatcomapp_channel.py index 1a0859690..e10a04f8d 100644 --- a/channel/wechatcom/wechatcomapp_channel.py +++ b/channel/wechatcom/wechatcomapp_channel.py @@ -59,7 +59,7 @@ def send(self, reply: Reply, context: Context): for i, text in enumerate(texts): self.client.message.send_text(self.agent_id, receiver, text) if i != len(texts) - 1: - time.sleep(0.5) # 休眠0.5秒,防止发送过快乱序 + time.sleep(0.5) # 休眠 0.5 秒,防止发送过快乱序 logger.info("[wechatcom] Do send text to {}: {}".format(receiver, reply_text)) elif reply.type == ReplyType.VOICE: try: diff --git a/channel/wechatcom/wechatcomapp_client.py b/channel/wechatcom/wechatcomapp_client.py index c0feb7a18..b4aea92f0 100644 --- a/channel/wechatcom/wechatcomapp_client.py +++ b/channel/wechatcom/wechatcomapp_client.py @@ -9,7 +9,7 @@ def __init__(self, corp_id, secret, access_token=None, session=None, timeout=Non super(WechatComAppClient, self).__init__(corp_id, secret, access_token, session, timeout, auto_retry) self.fetch_access_token_lock = threading.Lock() - def fetch_access_token(self): # 重载父类方法,加锁避免多线程重复获取access_token + def fetch_access_token(self): # 重载父类方法,加锁避免多线程重复获取 access_token with self.fetch_access_token_lock: access_token = self.session.get(self.access_token_key) if access_token: diff --git a/channel/wechatcom/wechatcomapp_message.py b/channel/wechatcom/wechatcomapp_message.py index a70f7556e..8fde3f193 100644 --- a/channel/wechatcom/wechatcomapp_message.py +++ b/channel/wechatcom/wechatcomapp_message.py @@ -18,10 +18,10 @@ def __init__(self, msg, client: WeChatClient, is_group=False): self.content = msg.content elif msg.type == "voice": self.ctype = ContextType.VOICE - self.content = TmpDir().path() + msg.media_id + "." + msg.format # content直接存临时目录路径 + self.content = TmpDir().path() + msg.media_id + "." + msg.format # content 直接存临时目录路径 def download_voice(): - # 如果响应状态码是200,则将响应内容写入本地文件 + # 如果响应状态码是 200,则将响应内容写入本地文件 response = client.media.download(msg.media_id) if response.status_code == 200: with open(self.content, "wb") as f: @@ -32,10 +32,10 @@ def download_voice(): self._prepare_fn = download_voice elif msg.type == "image": self.ctype = ContextType.IMAGE - self.content = TmpDir().path() + msg.media_id + ".png" # content直接存临时目录路径 + self.content = TmpDir().path() + msg.media_id + ".png" # content 直接存临时目录路径 def download_image(): - # 如果响应状态码是200,则将响应内容写入本地文件 + # 如果响应状态码是 200,则将响应内容写入本地文件 response = client.media.download(msg.media_id) if response.status_code == 200: with open(self.content, "wb") as f: diff --git a/channel/wechatmp/README.md b/channel/wechatmp/README.md index 8d753d8a8..a87049ee1 100644 --- a/channel/wechatmp/README.md +++ b/channel/wechatmp/README.md @@ -1,14 +1,14 @@ -# 微信公众号channel +# 微信公众号 channel -鉴于个人微信号在服务器上通过itchat登录有封号风险,这里新增了微信公众号channel,提供无风险的服务。 +鉴于个人微信号在服务器上通过 itchat 登录有封号风险,这里新增了微信公众号 channel,提供无风险的服务。 目前支持订阅号和服务号两种类型的公众号,它们都支持文本交互,语音和图片输入。其中个人主体的微信订阅号由于无法通过微信认证,存在回复时间限制,每天的图片和声音回复次数也有限制。 ## 使用方法(订阅号,服务号类似) -在开始部署前,你需要一个拥有公网IP的服务器,以提供微信服务器和我们自己服务器的连接。或者你需要进行内网穿透,否则微信服务器无法将消息发送给我们的服务器。 +在开始部署前,你需要一个拥有公网 IP 的服务器,以提供微信服务器和我们自己服务器的连接。或者你需要进行内网穿透,否则微信服务器无法将消息发送给我们的服务器。 -此外,需要在我们的服务器上安装python的web框架web.py和wechatpy。 -以ubuntu为例(在ubuntu 22.04上测试): +此外,需要在我们的服务器上安装 python 的 web 框架 web.py 和 wechatpy。 +以 ubuntu 为例 (在 ubuntu 22.04 上测试): ``` pip3 install web.py pip3 install wechatpy @@ -16,7 +16,7 @@ pip3 install wechatpy 然后在[微信公众平台](https://mp.weixin.qq.com)注册一个自己的公众号,类型选择订阅号,主体为个人即可。 -然后根据[接入指南](https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html)的说明,在[微信公众平台](https://mp.weixin.qq.com)的“设置与开发”-“基本配置”-“服务器配置”中填写服务器地址`URL`和令牌`Token`。`URL`填写格式为`http://url/wx`,可使用IP(成功几率看脸),`Token`是你自己编的一个特定的令牌。消息加解密方式如果选择了需要加密的模式,需要在配置中填写`wechatmp_aes_key`。 +然后根据[接入指南](https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html)的说明,在[微信公众平台](https://mp.weixin.qq.com)的“设置与开发” - “基本配置” - “服务器配置”中填写服务器地址`URL`和令牌`Token`。`URL`填写格式为`http://url/wx`,可使用 IP(成功几率看脸),`Token`是你自己编的一个特定的令牌。消息加解密方式如果选择了需要加密的模式,需要在配置中填写`wechatmp_aes_key`。 相关的服务器验证代码已经写好,你不需要再添加任何代码。你只需要在本项目根目录的`config.json`中添加 ``` @@ -30,19 +30,19 @@ pip3 install wechatpy "single_chat_reply_prefix": "", # 推荐设置,回复不设置前缀 "plugin_trigger_prefix": "&", # 推荐设置,在手机微信客户端中,$%^等符号与中文连在一起时会自动显示一段较大的间隔,用户体验不好。请不要使用管理员指令前缀"#",这会造成未知问题。 ``` -然后运行`python3 app.py`启动web服务器。这里会默认监听8080端口,但是微信公众号的服务器配置只支持80/443端口,有两种方法来解决这个问题。第一个是推荐的方法,使用端口转发命令将80端口转发到8080端口: +然后运行`python3 app.py`启动 web 服务器。这里会默认监听 8080 端口,但是微信公众号的服务器配置只支持 80/443 端口,有两种方法来解决这个问题。第一个是推荐的方法,使用端口转发命令将 80 端口转发到 8080 端口: ``` sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080 sudo iptables-save > /etc/iptables/rules.v4 ``` -第二个方法是让python程序直接监听80端口,在配置文件中设置`"wechatmp_port": 80` ,在linux上需要使用`sudo python3 app.py`启动程序。然而这会导致一系列环境和权限问题,因此不是推荐的方法。 +第二个方法是让 python 程序直接监听 80 端口,在配置文件中设置`"wechatmp_port": 80` ,在 linux 上需要使用`sudo python3 app.py`启动程序。然而这会导致一系列环境和权限问题,因此不是推荐的方法。 -443端口同理,注意需要支持SSL,也就是https的访问,在`wechatmp_channel.py`中需要修改相应的证书路径。 +443 端口同理,注意需要支持 SSL,也就是 https 的访问,在`wechatmp_channel.py`中需要修改相应的证书路径。 程序启动并监听端口后,在刚才的“服务器配置”中点击`提交`即可验证你的服务器。 -随后在[微信公众平台](https://mp.weixin.qq.com)启用服务器,关闭手动填写规则的自动回复,即可实现ChatGPT的自动回复。 +随后在[微信公众平台](https://mp.weixin.qq.com)启用服务器,关闭手动填写规则的自动回复,即可实现 ChatGPT 的自动回复。 -之后需要在公众号开发信息下将本机IP加入到IP白名单。 +之后需要在公众号开发信息下将本机 IP 加入到 IP 白名单。 不然在启用后,发送语音、图片等消息可能会遇到如下报错: ``` @@ -51,12 +51,12 @@ sudo iptables-save > /etc/iptables/rules.v4 ## 个人微信公众号的限制 -由于人微信公众号不能通过微信认证,所以没有客服接口,因此公众号无法主动发出消息,只能被动回复。而微信官方对被动回复有5秒的时间限制,最多重试2次,因此最多只有15秒的自动回复时间窗口。因此如果问题比较复杂或者我们的服务器比较忙,ChatGPT的回答就没办法及时回复给用户。为了解决这个问题,这里做了回答缓存,它需要你在回复超时后,再次主动发送任意文字(例如1)来尝试拿到回答缓存。为了优化使用体验,目前设置了两分钟(120秒)的timeout,用户在至多两分钟后即可得到查询到回复或者错误原因。 +由于人微信公众号不能通过微信认证,所以没有客服接口,因此公众号无法主动发出消息,只能被动回复。而微信官方对被动回复有 5 秒的时间限制,最多重试 2 次,因此最多只有 15 秒的自动回复时间窗口。因此如果问题比较复杂或者我们的服务器比较忙,ChatGPT 的回答就没办法及时回复给用户。为了解决这个问题,这里做了回答缓存,它需要你在回复超时后,再次主动发送任意文字(例如 1)来尝试拿到回答缓存。为了优化使用体验,目前设置了两分钟(120 秒)的 timeout,用户在至多两分钟后即可得到查询到回复或者错误原因。 -另外,由于微信官方的限制,自动回复有长度限制。因此这里将ChatGPT的回答进行了拆分,以满足限制。 +另外,由于微信官方的限制,自动回复有长度限制。因此这里将 ChatGPT 的回答进行了拆分,以满足限制。 -## 私有api_key -公共api有访问频率限制(免费账号每分钟最多3次ChatGPT的API调用),这在服务多人的时候会遇到问题。因此这里多加了一个设置私有api_key的功能。目前通过godcmd插件的命令来设置私有api_key。 +## 私有 api_key +公共 api 有访问频率限制(免费账号每分钟最多 3 次 ChatGPT 的 API 调用),这在服务多人的时候会遇到问题。因此这里多加了一个设置私有 api_key 的功能。目前通过 godcmd 插件的命令来设置私有 api_key。 ## 语音输入 利用微信自带的语音识别功能,提供语音输入能力。需要在公众号管理页面的“设置与开发”->“接口权限”页面开启“接收语音识别结果”。 @@ -75,22 +75,22 @@ sudo iptables-save > /etc/iptables/rules.v4 "text_to_voice": "pytts" ``` -pytts是本地的语音合成引擎。还支持baidu,azure,这些你需要自行配置相关的依赖和key。 +pytts 是本地的语音合成引擎。还支持 baidu,azure,这些你需要自行配置相关的依赖和 key。 -如果使用pytts,在ubuntu上需要安装如下依赖: +如果使用 pytts,在 ubuntu 上需要安装如下依赖: ``` sudo apt update sudo apt install espeak sudo apt install ffmpeg python3 -m pip install pyttsx3 ``` -不是很建议开启pytts语音回复,因为它是离线本地计算,算的慢会拖垮服务器,且声音不好听。 +不是很建议开启 pytts 语音回复,因为它是离线本地计算,算的慢会拖垮服务器,且声音不好听。 ## 图片回复 -现在认证公众号和非认证公众号都可以实现的图片和语音回复。但是非认证公众号使用了永久素材接口,每天有1000次的调用上限(每个月有10次重置机会,程序中已设定遇到上限会自动重置),且永久素材库存也有上限。因此对于非认证公众号,我们会在回复图片或者语音消息后的10秒内从永久素材库存内删除该素材。 +现在认证公众号和非认证公众号都可以实现的图片和语音回复。但是非认证公众号使用了永久素材接口,每天有 1000 次的调用上限(每个月有 10 次重置机会,程序中已设定遇到上限会自动重置),且永久素材库存也有上限。因此对于非认证公众号,我们会在回复图片或者语音消息后的 10 秒内从永久素材库存内删除该素材。 ## 测试 -目前在`RoboStyle`这个公众号上进行了测试(基于[wechatmp分支](https://github.com/JS00000/chatgpt-on-wechat/tree/wechatmp)),感兴趣的可以关注并体验。开启了godcmd, Banwords, role, dungeon, finish这五个插件,其他的插件还没有详尽测试。百度的接口暂未测试。[wechatmp-stable分支](https://github.com/JS00000/chatgpt-on-wechat/tree/wechatmp-stable)是较稳定的上个版本,但也缺少最新的功能支持。 +目前在`RoboStyle`这个公众号上进行了测试(基于[wechatmp 分支](https://github.com/JS00000/chatgpt-on-wechat/tree/wechatmp)),感兴趣的可以关注并体验。开启了 godcmd, Banwords, role, dungeon, finish 这五个插件,其他的插件还没有详尽测试。百度的接口暂未测试。[wechatmp-stable 分支](https://github.com/JS00000/chatgpt-on-wechat/tree/wechatmp-stable)是较稳定的上个版本,但也缺少最新的功能支持。 ## TODO - [x] 语音输入 diff --git a/channel/wechatmp/passive_reply.py b/channel/wechatmp/passive_reply.py index d03efc4d1..e7331a1fe 100644 --- a/channel/wechatmp/passive_reply.py +++ b/channel/wechatmp/passive_reply.py @@ -71,7 +71,7 @@ def POST(self): reply_text = textwrap.dedent( f"""\ 请输入'{trigger_prefix}'接你想说的话跟我说话。 - 例如: + 例如: {trigger_prefix}你好,很高兴见到你。""" ) else: diff --git a/channel/wechatmp/wechatmp_channel.py b/channel/wechatmp/wechatmp_channel.py index 2ae88228f..b7d0490b0 100644 --- a/channel/wechatmp/wechatmp_channel.py +++ b/channel/wechatmp/wechatmp_channel.py @@ -149,7 +149,7 @@ def send(self, reply: Reply, context: Context): for i, text in enumerate(texts): self.client.message.send_text(receiver, text) if i != len(texts) - 1: - time.sleep(0.5) # 休眠0.5秒,防止发送过快乱序 + time.sleep(0.5) # 休眠 0.5 秒,防止发送过快乱序 logger.info("[wechatmp] Do send text to {}: {}".format(receiver, reply_text)) elif reply.type == ReplyType.VOICE: try: diff --git a/channel/wechatmp/wechatmp_client.py b/channel/wechatmp/wechatmp_client.py index 19dca3219..9b8ce63cd 100644 --- a/channel/wechatmp/wechatmp_client.py +++ b/channel/wechatmp/wechatmp_client.py @@ -21,7 +21,7 @@ def clear_quota(self): def clear_quota_v2(self): return self.post("clear_quota/v2", params={"appid": self.appid, "appsecret": self.secret}) - def fetch_access_token(self): # 重载父类方法,加锁避免多线程重复获取access_token + def fetch_access_token(self): # 重载父类方法,加锁避免多线程重复获取 access_token with self.fetch_access_token_lock: access_token = self.session.get(self.access_token_key) if access_token: @@ -32,7 +32,7 @@ def fetch_access_token(self): # 重载父类方法,加锁避免多线程重 return access_token return super().fetch_access_token() - def _request(self, method, url_or_endpoint, **kwargs): # 重载父类方法,遇到API限流时,清除quota后重试 + def _request(self, method, url_or_endpoint, **kwargs): # 重载父类方法,遇到 API 限流时,清除 quota 后重试 try: return super()._request(method, url_or_endpoint, **kwargs) except APILimitedException as e: diff --git a/channel/wechatmp/wechatmp_message.py b/channel/wechatmp/wechatmp_message.py index 27c9fbb85..0af7c24a0 100644 --- a/channel/wechatmp/wechatmp_message.py +++ b/channel/wechatmp/wechatmp_message.py @@ -19,10 +19,10 @@ def __init__(self, msg, client=None): elif msg.type == "voice": if msg.recognition == None: self.ctype = ContextType.VOICE - self.content = TmpDir().path() + msg.media_id + "." + msg.format # content直接存临时目录路径 + self.content = TmpDir().path() + msg.media_id + "." + msg.format # content 直接存临时目录路径 def download_voice(): - # 如果响应状态码是200,则将响应内容写入本地文件 + # 如果响应状态码是 200,则将响应内容写入本地文件 response = client.media.download(msg.media_id) if response.status_code == 200: with open(self.content, "wb") as f: @@ -36,10 +36,10 @@ def download_voice(): self.content = msg.recognition elif msg.type == "image": self.ctype = ContextType.IMAGE - self.content = TmpDir().path() + msg.media_id + ".png" # content直接存临时目录路径 + self.content = TmpDir().path() + msg.media_id + ".png" # content 直接存临时目录路径 def download_image(): - # 如果响应状态码是200,则将响应内容写入本地文件 + # 如果响应状态码是 200,则将响应内容写入本地文件 response = client.media.download(msg.media_id) if response.status_code == 200: with open(self.content, "wb") as f: diff --git a/channel/wework/wework_channel.py b/channel/wework/wework_channel.py index 102026105..b74fae9c0 100644 --- a/channel/wework/wework_channel.py +++ b/channel/wework/wework_channel.py @@ -28,7 +28,7 @@ def get_wxid_by_name(room_members, group_wxid, name): for member in room_members[group_wxid]['member_list']: if member['room_nickname'] == name or member['username'] == name: return member['user_id'] - return None # 如果没有找到对应的group_wxid或name,则返回None + return None # 如果没有找到对应的 group_wxid 或 name,则返回 None def download_and_compress_image(url, filename, quality=30): @@ -79,7 +79,7 @@ def download_video(url, filename): for block in response.iter_content(1024): total_size += len(block) - # 如果视频的总大小超过30MB (30 * 1024 * 1024 bytes),则停止下载并返回 + # 如果视频的总大小超过 30MB (30 * 1024 * 1024 bytes),则停止下载并返回 if total_size > 30 * 1024 * 1024: logger.info("[WX] Video is larger than 30MB, skipping...") return None @@ -111,7 +111,7 @@ def wrapper(self, cmsg: ChatMessage): create_time = cmsg.create_time # 消息时间戳 if create_time is None: return func(self, cmsg) - if int(create_time) < int(time.time()) - 60: # 跳过1分钟前的历史消息 + if int(create_time) < int(time.time()) - 60: # 跳过 1 分钟前的历史消息 logger.debug("[WX]history message {} skipped".format(msgId)) return return func(self, cmsg) @@ -122,16 +122,16 @@ def wrapper(self, cmsg: ChatMessage): @wework.msg_register( [ntwork.MT_RECV_TEXT_MSG, ntwork.MT_RECV_IMAGE_MSG, 11072, ntwork.MT_RECV_LINK_CARD_MSG,ntwork.MT_RECV_FILE_MSG, ntwork.MT_RECV_VOICE_MSG]) def all_msg_handler(wework_instance: ntwork.WeWork, message): - logger.debug(f"收到消息: {message}") + logger.debug(f"收到消息:{message}") if 'data' in message: - # 首先查找conversation_id,如果没有找到,则查找room_conversation_id + # 首先查找 conversation_id,如果没有找到,则查找 room_conversation_id conversation_id = message['data'].get('conversation_id', message['data'].get('room_conversation_id')) if conversation_id is not None: is_group = "R:" in conversation_id try: cmsg = create_message(wework_instance=wework_instance, message=message, is_group=is_group) except NotImplementedError as e: - logger.error(f"[WX]{message.get('MsgId', 'unknown')} 跳过: {e}") + logger.error(f"[WX]{message.get('MsgId', 'unknown')} 跳过:{e}") return None delay = random.randint(1, 2) timer = threading.Timer(delay, handle_message, args=(cmsg, is_group)) @@ -188,18 +188,18 @@ def startup(self): self.user_id = login_info['user_id'] self.name = login_info['nickname'] logger.info(f"登录信息:>>>user_id:{self.user_id}>>>>>>>>name:{self.name}") - logger.info("静默延迟60s,等待客户端刷新数据,请勿进行任何操作······") + logger.info("静默延迟 60s,等待客户端刷新数据,请勿进行任何操作······") time.sleep(60) contacts = get_with_retry(wework.get_external_contacts) rooms = get_with_retry(wework.get_rooms) directory = os.path.join(os.getcwd(), "tmp") if not contacts or not rooms: - logger.error("获取contacts或rooms失败,程序退出") + logger.error("获取 contacts 或 rooms 失败,程序退出") ntwork.exit_() os.exit(0) if not os.path.exists(directory): os.makedirs(directory) - # 将contacts保存到json文件中 + # 将 contacts 保存到 json 文件中 with open(os.path.join(directory, 'wework_contacts.json'), 'w', encoding='utf-8') as f: json.dump(contacts, f, ensure_ascii=False, indent=4) with open(os.path.join(directory, 'wework_rooms.json'), 'w', encoding='utf-8') as f: @@ -209,7 +209,7 @@ def startup(self): # 遍历列表中的每个字典 for room in rooms['room_list']: - # 获取聊天室ID + # 获取聊天室 ID room_wxid = room['conversation_id'] # 获取聊天室成员 @@ -218,10 +218,10 @@ def startup(self): # 将聊天室成员保存到结果字典中 result[room_wxid] = room_members - # 将结果保存到json文件中 + # 将结果保存到 json 文件中 with open(os.path.join(directory, 'wework_room_members.json'), 'w', encoding='utf-8') as f: json.dump(result, f, ensure_ascii=False, indent=4) - logger.info("wework程序初始化完成········") + logger.info("wework 程序初始化完成········") run.forever() @time_checker @@ -265,7 +265,7 @@ def handle_group(self, cmsg: ChatMessage): if context: self.produce(context) - # 统一的发送函数,每个Channel自行实现,根据reply的type字段发送不同类型的消息 + # 统一的发送函数,每个 Channel 自行实现,根据 reply 的 type 字段发送不同类型的消息 def send(self, reply: Reply, context: Context): logger.debug(f"context: {context}") receiver = context["receiver"] diff --git a/channel/wework/wework_message.py b/channel/wework/wework_message.py index 0d9e96ef7..89b879e73 100644 --- a/channel/wework/wework_message.py +++ b/channel/wework/wework_message.py @@ -28,10 +28,10 @@ def get_room_info(wework, conversation_id): logger.debug(f"传入的 conversation_id: {conversation_id}") rooms = wework.get_rooms() if not rooms or 'room_list' not in rooms: - logger.error(f"获取群聊信息失败: {rooms}") + logger.error(f"获取群聊信息失败:{rooms}") return None time.sleep(1) - logger.debug(f"获取到的群聊信息: {rooms}") + logger.debug(f"获取到的群聊信息:{rooms}") for room in rooms['room_list']: if room['conversation_id'] == conversation_id: return room @@ -51,9 +51,9 @@ def cdn_download(wework, message, file_name): if "url" in data["cdn"].keys() and "auth_key" in data["cdn"].keys(): url = data["cdn"]["url"] auth_key = data["cdn"]["auth_key"] - # result = wework.wx_cdn_download(url, auth_key, aes_key, file_size, save_path) # ntwork库本身接口有问题,缺失了aes_key这个参数 + # result = wework.wx_cdn_download(url, auth_key, aes_key, file_size, save_path) # ntwork 库本身接口有问题,缺失了 aes_key 这个参数 """ - 下载wx类型的cdn文件,以https开头 + 下载 wx 类型的 cdn 文件,以 https 开头 """ data = { 'url': url, @@ -62,7 +62,7 @@ def cdn_download(wework, message, file_name): 'size': file_size, 'save_path': save_path } - result = wework._WeWork__send_sync(send_type.MT_WXCDN_DOWNLOAD_MSG, data) # 直接用wx_cdn_download的接口内部实现来调用 + result = wework._WeWork__send_sync(send_type.MT_WXCDN_DOWNLOAD_MSG, data) # 直接用 wx_cdn_download 的接口内部实现来调用 elif "file_id" in data["cdn"].keys(): if message["type"] == 11042: file_type = 2 @@ -90,12 +90,12 @@ def c2c_download_and_convert(wework, message, file_name): result = wework.c2c_cdn_download(file_id, aes_key, file_size, file_type, save_path) logger.debug(result) - # 在下载完SILK文件之后,立即将其转换为WAV文件 + # 在下载完 SILK 文件之后,立即将其转换为 WAV 文件 base_name, _ = os.path.splitext(save_path) wav_file = base_name + ".wav" pilk.silk_to_wav(save_path, wav_file, rate=24000) - # 删除SILK文件 + # 删除 SILK 文件 try: os.remove(save_path) except Exception as e: @@ -107,7 +107,7 @@ def __init__(self, wework_msg, wework, is_group=False): try: super().__init__(wework_msg) self.msg_id = wework_msg['data'].get('conversation_id', wework_msg['data'].get('room_conversation_id')) - # 使用.get()防止 'send_time' 键不存在时抛出错误 + # 使用.get() 防止 'send_time' 键不存在时抛出错误 self.create_time = wework_msg['data'].get("send_time") self.is_group = is_group self.wework = wework @@ -156,7 +156,7 @@ def __init__(self, wework_msg, wework, is_group=False): else: result = {} for room in rooms['room_list']: - # 获取聊天室ID + # 获取聊天室 ID room_wxid = room['conversation_id'] # 获取聊天室成员 diff --git a/config-template.json b/config-template.json index d0268d3b1..fdf5b9b9e 100644 --- a/config-template.json +++ b/config-template.json @@ -17,8 +17,8 @@ "@bot" ], "group_name_white_list": [ - "ChatGPT测试群", - "ChatGPT测试群2" + "ChatGPT 测试群", + "ChatGPT 测试群 2" ], "image_create_prefix": [ "画" @@ -28,9 +28,9 @@ "voice_reply_voice": false, "conversation_max_tokens": 2500, "expires_in_seconds": 3600, - "character_desc": "你是基于大语言模型的AI智能助手,旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", + "character_desc": "你是 ChatGPT, 一个由 OpenAI 训练的大型语言模型,你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", "temperature": 0.7, - "subscribe_msg": "感谢您的关注!\n这里是AI智能助手,可以自由对话。\n支持语音对话。\n支持图片输入。\n支持图片输出,画字开头的消息将按要求创作图片。\n支持tool、角色扮演和文字冒险等丰富的插件。\n输入{trigger_prefix}#help 查看详细指令。", + "subscribe_msg": "感谢您的关注!\n这里是 AI 智能助手,可以自由对话。\n支持语音对话。\n支持图片输入。\n支持图片输出,画字开头的消息将按要求创作图片。\n支持 tool、角色扮演和文字冒险等丰富的插件。\n输入{trigger_prefix}#help 查看详细指令。", "use_linkai": false, "linkai_api_key": "", "linkai_app_code": "" diff --git a/config.py b/config.py index 7d07379b3..465991fd6 100644 --- a/config.py +++ b/config.py @@ -7,20 +7,20 @@ from common.log import logger -# 将所有可用的配置项写在字典里, 请使用小写字母 -# 此处的配置值无实际意义,程序不会读取此处的配置,仅用于提示格式,请将配置加入到config.json中 +# 将所有可用的配置项写在字典里,请使用小写字母 +# 此处的配置值无实际意义,程序不会读取此处的配置,仅用于提示格式,请将配置加入到 config.json 中 available_setting = { - # openai api配置 + # openai api 配置 "open_ai_api_key": "", # openai api key - # openai apibase,当use_azure_chatgpt为true时,需要设置对应的api base + # openai apibase,当 use_azure_chatgpt 为 true 时,需要设置对应的 api base "open_ai_api_base": "https://api.openai.com/v1", - "proxy": "", # openai使用的代理 - # chatgpt模型, 当use_azure_chatgpt为true时,其名称为Azure上model deployment名称 + "proxy": "", # openai 使用的代理 + # chatgpt 模型,当 use_azure_chatgpt 为 true 时,其名称为 Azure 上 model deployment 名称 "model": "gpt-3.5-turbo", # 还支持 gpt-4, gpt-4-turbo, wenxin, xunfei, qwen - "use_azure_chatgpt": False, # 是否使用azure的chatgpt + "use_azure_chatgpt": False, # 是否使用 azure 的 chatgpt "azure_deployment_id": "", # azure 模型部署名称 - "azure_api_version": "", # azure api版本 - # Bot触发配置 + "azure_api_version": "", # azure api 版本 + # Bot 触发配置 "single_chat_prefix": ["bot", "@bot"], # 私聊时文本需要包含该前缀才能触发机器人回复 "single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人 "single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行 @@ -28,40 +28,40 @@ "group_chat_reply_prefix": "", # 群聊时自动回复的前缀 "group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行 "group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复 - "group_at_off": False, # 是否关闭群聊时@bot的触发 - "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 + "group_at_off": False, # 是否关闭群聊时@bot 的触发 + "group_name_white_list": ["ChatGPT 测试群", "ChatGPT 测试群 2"], # 开启自动回复的群名称列表 "group_name_keyword_white_list": [], # 开启自动回复的群名称关键词列表 - "group_chat_in_one_session": ["ChatGPT测试群"], # 支持会话上下文共享的群名称 + "group_chat_in_one_session": ["ChatGPT 测试群"], # 支持会话上下文共享的群名称 "nick_name_black_list": [], # 用户昵称黑名单 "group_welcome_msg": "", # 配置新人进群固定欢迎语,不配置则使用随机风格欢迎 "trigger_by_self": False, # 是否允许机器人触发 "text_to_image": "dall-e-2", # 图片生成模型,可选 dall-e-2, dall-e-3 - "image_proxy": True, # 是否需要图片代理,国内访问LinkAI时需要 + "image_proxy": True, # 是否需要图片代理,国内访问 LinkAI 时需要 "image_create_prefix": ["画", "看", "找"], # 开启图片回复的前缀 - "concurrency_in_session": 1, # 同一会话最多有多少条消息在处理中,大于1可能乱序 - "image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024 (dall-e-3默认为1024x1024) + "concurrency_in_session": 1, # 同一会话最多有多少条消息在处理中,大于 1 可能乱序 + "image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024 (dall-e-3 默认为 1024x1024) "group_chat_exit_group": False, - # chatgpt会话参数 + # chatgpt 会话参数 "expires_in_seconds": 3600, # 无操作会话的过期时间 # 人格描述 - "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", + "character_desc": "你是 ChatGPT, 一个由 OpenAI 训练的大型语言模型,你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", "conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 - # chatgpt限流配置 - "rate_limit_chatgpt": 20, # chatgpt的调用频率限制 - "rate_limit_dalle": 50, # openai dalle的调用频率限制 - # chatgpt api参数 参考https://platform.openai.com/docs/api-reference/chat/create + # chatgpt 限流配置 + "rate_limit_chatgpt": 20, # chatgpt 的调用频率限制 + "rate_limit_dalle": 50, # openai dalle 的调用频率限制 + # chatgpt api 参数 参考 https://platform.openai.com/docs/api-reference/chat/create "temperature": 0.9, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, - "request_timeout": 180, # chatgpt请求超时时间,openai接口默认设置为600,对于难问题一般需要较长时间 - "timeout": 120, # chatgpt重试超时时间,在这个时间内,将会自动重试 + "request_timeout": 180, # chatgpt 请求超时时间,openai 接口默认设置为 600,对于难问题一般需要较长时间 + "timeout": 120, # chatgpt 重试超时时间,在这个时间内,将会自动重试 # Baidu 文心一言参数 - "baidu_wenxin_model": "eb-instant", # 默认使用ERNIE-Bot-turbo模型 + "baidu_wenxin_model": "eb-instant", # 默认使用 ERNIE-Bot-turbo 模型 "baidu_wenxin_api_key": "", # Baidu api key "baidu_wenxin_secret_key": "", # Baidu secret key - # 讯飞星火API - "xunfei_app_id": "", # 讯飞应用ID + # 讯飞星火 API + "xunfei_app_id": "", # 讯飞应用 ID "xunfei_api_key": "", # 讯飞 API key "xunfei_api_secret": "", # 讯飞 API secret # claude 配置 @@ -69,84 +69,84 @@ "claude_uuid": "", # claude api key "claude_api_key":"", - # 通义千问API, 获取方式查看文档 https://help.aliyun.com/document_detail/2587494.html + # 通义千问 API, 获取方式查看文档 https://help.aliyun.com/document_detail/2587494.html "qwen_access_key_id": "", "qwen_access_key_secret": "", "qwen_agent_key": "", "qwen_app_id": "", - "qwen_node_id": "", # 流程编排模型用到的id,如果没有用到qwen_node_id,请务必保持为空字符串 - # 阿里灵积模型api key + "qwen_node_id": "", # 流程编排模型用到的 id,如果没有用到 qwen_node_id,请务必保持为空字符串 + # 阿里灵积模型 api key "dashscope_api_key": "", # Google Gemini Api Key "gemini_api_key": "", - # wework的通用配置 - "wework_smart": True, # 配置wework是否使用已登录的企业微信,False为多开 + # wework 的通用配置 + "wework_smart": True, # 配置 wework 是否使用已登录的企业微信,False 为多开 # 语音设置 "speech_recognition": True, # 是否开启语音识别 "group_speech_recognition": False, # 是否开启群组语音识别 - "voice_reply_voice": False, # 是否使用语音回复语音,需要设置对应语音合成引擎的api key + "voice_reply_voice": False, # 是否使用语音回复语音,需要设置对应语音合成引擎的 api key "always_reply_voice": False, # 是否一直使用语音回复 - "voice_to_text": "openai", # 语音识别引擎,支持openai,baidu,google,azure - "text_to_voice": "openai", # 语音合成引擎,支持openai,baidu,google,pytts(offline),azure,elevenlabs,edge(online) + "voice_to_text": "openai", # 语音识别引擎,支持 openai,baidu,google,azure + "text_to_voice": "openai", # 语音合成引擎,支持 openai,baidu,google,pytts(offline),azure,elevenlabs,edge(online) "text_to_voice_model": "tts-1", "tts_voice_id": "alloy", - # baidu 语音api配置, 使用百度语音识别和语音合成时需要 + # baidu 语音 api 配置,使用百度语音识别和语音合成时需要 "baidu_app_id": "", "baidu_api_key": "", "baidu_secret_key": "", - # 1536普通话(支持简单的英文识别) 1737英语 1637粤语 1837四川话 1936普通话远场 + # 1536 普通话 (支持简单的英文识别) 1737 英语 1637 粤语 1837 四川话 1936 普通话远场 "baidu_dev_pid": "1536", - # azure 语音api配置, 使用azure语音识别和语音合成时需要 + # azure 语音 api 配置,使用 azure 语音识别和语音合成时需要 "azure_voice_api_key": "", "azure_voice_region": "japaneast", - # elevenlabs 语音api配置 - "xi_api_key": "", #获取ap的方法可以参考https://docs.elevenlabs.io/api-reference/quick-start/authentication - "xi_voice_id": "", #ElevenLabs提供了9种英式、美式等英语发音id,分别是“Adam/Antoni/Arnold/Bella/Domi/Elli/Josh/Rachel/Sam” - # 服务时间限制,目前支持itchat + # elevenlabs 语音 api 配置 + "xi_api_key": "", #获取 ap 的方法可以参考 https://docs.elevenlabs.io/api-reference/quick-start/authentication + "xi_voice_id": "", #ElevenLabs 提供了 9 种英式、美式等英语发音 id,分别是“Adam/Antoni/Arnold/Bella/Domi/Elli/Josh/Rachel/Sam” + # 服务时间限制,目前支持 itchat "chat_time_module": False, # 是否开启服务时间限制 "chat_start_time": "00:00", # 服务开始时间 "chat_stop_time": "24:00", # 服务结束时间 - # 翻译api - "translate": "baidu", # 翻译api,支持baidu - # baidu翻译api的配置 - "baidu_translate_app_id": "", # 百度翻译api的appid - "baidu_translate_app_key": "", # 百度翻译api的秘钥 - # itchat的配置 + # 翻译 api + "translate": "baidu", # 翻译 api,支持 baidu + # baidu 翻译 api 的配置 + "baidu_translate_app_id": "", # 百度翻译 api 的 appid + "baidu_translate_app_key": "", # 百度翻译 api 的秘钥 + # itchat 的配置 "hot_reload": False, # 是否开启热重载 - # wechaty的配置 - "wechaty_puppet_service_token": "", # wechaty的token - # wechatmp的配置 - "wechatmp_token": "", # 微信公众平台的Token - "wechatmp_port": 8080, # 微信公众平台的端口,需要端口转发到80或443 - "wechatmp_app_id": "", # 微信公众平台的appID - "wechatmp_app_secret": "", # 微信公众平台的appsecret - "wechatmp_aes_key": "", # 微信公众平台的EncodingAESKey,加密模式需要 - # wechatcom的通用配置 - "wechatcom_corp_id": "", # 企业微信公司的corpID - # wechatcomapp的配置 - "wechatcomapp_token": "", # 企业微信app的token - "wechatcomapp_port": 9898, # 企业微信app的服务端口,不需要端口转发 - "wechatcomapp_secret": "", # 企业微信app的secret - "wechatcomapp_agent_id": "", # 企业微信app的agent_id - "wechatcomapp_aes_key": "", # 企业微信app的aes_key + # wechaty 的配置 + "wechaty_puppet_service_token": "", # wechaty 的 token + # wechatmp 的配置 + "wechatmp_token": "", # 微信公众平台的 Token + "wechatmp_port": 8080, # 微信公众平台的端口,需要端口转发到 80 或 443 + "wechatmp_app_id": "", # 微信公众平台的 appID + "wechatmp_app_secret": "", # 微信公众平台的 appsecret + "wechatmp_aes_key": "", # 微信公众平台的 EncodingAESKey,加密模式需要 + # wechatcom 的通用配置 + "wechatcom_corp_id": "", # 企业微信公司的 corpID + # wechatcomapp 的配置 + "wechatcomapp_token": "", # 企业微信 app 的 token + "wechatcomapp_port": 9898, # 企业微信 app 的服务端口,不需要端口转发 + "wechatcomapp_secret": "", # 企业微信 app 的 secret + "wechatcomapp_agent_id": "", # 企业微信 app 的 agent_id + "wechatcomapp_aes_key": "", # 企业微信 app 的 aes_key # 飞书配置 - "feishu_port": 80, # 飞书bot监听端口 - "feishu_app_id": "", # 飞书机器人应用APP Id - "feishu_app_secret": "", # 飞书机器人APP secret + "feishu_port": 80, # 飞书 bot 监听端口 + "feishu_app_id": "", # 飞书机器人应用 APP Id + "feishu_app_secret": "", # 飞书机器人 APP secret "feishu_token": "", # 飞书 verification token "feishu_bot_name": "", # 飞书机器人的名字 # 钉钉配置 - "dingtalk_client_id": "", # 钉钉机器人Client ID - "dingtalk_client_secret": "", # 钉钉机器人Client Secret + "dingtalk_client_id": "", # 钉钉机器人 Client ID + "dingtalk_client_secret": "", # 钉钉机器人 Client Secret - # chatgpt指令自定义触发词 + # chatgpt 指令自定义触发词 "clear_memory_commands": ["#清除记忆"], # 重置会话指令,必须以#开头 - # channel配置 + # channel 配置 "channel_type": "wx", # 通道类型,支持:{wx,wxy,terminal,wechatmp,wechatmp_service,wechatcom_app} - "subscribe_msg": "", # 订阅消息, 支持: wechatmp, wechatmp_service, wechatcom_app - "debug": False, # 是否开启debug模式,开启后会打印更多日志 + "subscribe_msg": "", # 订阅消息,支持:wechatmp, wechatmp_service, wechatcom_app + "debug": False, # 是否开启 debug 模式,开启后会打印更多日志 "appdata_dir": "", # 数据目录 # 插件配置 "plugin_trigger_prefix": "$", # 规范插件提供聊天相关指令的前缀,建议不要和管理员指令前缀"#"冲突 @@ -154,16 +154,16 @@ "use_global_plugin_config": False, "max_media_send_count": 3, # 单次最大发送媒体资源的个数 "media_send_interval": 1, # 发送图片的事件间隔,单位秒 - # 智谱AI 平台配置 + # 智谱 AI 平台配置 "zhipu_ai_api_key": "", "zhipu_ai_api_base": "https://open.bigmodel.cn/api/paas/v4", "moonshot_api_key": "", "moonshot_base_url":"https://api.moonshot.cn/v1/chat/completions", - # LinkAI平台配置 + # LinkAI 平台配置 "use_linkai": False, "linkai_api_key": "", "linkai_app_code": "", - "linkai_api_base": "https://api.link-ai.tech", # linkAI服务地址 + "linkai_api_base": "https://api.link-ai.tech", # linkAI 服务地址 } @@ -174,7 +174,7 @@ def __init__(self, d=None): d = {} for k, v in d.items(): self[k] = v - # user_datas: 用户数据,key为用户名,value为用户数据,也是dict + # user_datas: 用户数据,key 为用户名,value 为用户数据,也是 dict self.user_datas = {} def __getitem__(self, key): @@ -228,13 +228,13 @@ def load_config(): global config config_path = "./config.json" if not os.path.exists(config_path): - logger.info("配置文件不存在,将使用config-template.json模板") + logger.info("配置文件不存在,将使用 config-template.json 模板") config_path = "./config-template.json" config_str = read_file(config_path) logger.debug("[INIT] config str: {}".format(config_str)) - # 将json字符串反序列化为dict类型 + # 将 json 字符串反序列化为 dict 类型 config = Config(json.loads(config_str)) # override config with environment variables. diff --git a/plugins/banwords/README.md b/plugins/banwords/README.md index 39517f681..6e9239020 100644 --- a/plugins/banwords/README.md +++ b/plugins/banwords/README.md @@ -19,9 +19,9 @@ 在以上配置项中: - `action`: 对用户消息的默认处理行为 -- `reply_filter`: 是否对ChatGPT的回复也进行敏感词过滤 +- `reply_filter`: 是否对 ChatGPT 的回复也进行敏感词过滤 - `reply_action`: 如果开启了回复过滤,对回复的默认处理行为 ## 致谢 -搜索功能实现来自https://github.com/toolgood/ToolGood.Words \ No newline at end of file +搜索功能实现来自 https://github.com/toolgood/ToolGood.Words \ No newline at end of file diff --git a/plugins/banwords/banwords.py b/plugins/banwords/banwords.py index 2a33a5aff..1383ea270 100644 --- a/plugins/banwords/banwords.py +++ b/plugins/banwords/banwords.py @@ -71,7 +71,7 @@ def on_handle_context(self, e_context: EventContext): return elif self.action == "replace": if self.searchr.ContainsAny(content): - reply = Reply(ReplyType.INFO, "发言中包含敏感词,请重试: \n" + self.searchr.Replace(content)) + reply = Reply(ReplyType.INFO, "发言中包含敏感词,请重试:\n" + self.searchr.Replace(content)) e_context["reply"] = reply e_context.action = EventAction.BREAK_PASS return @@ -91,7 +91,7 @@ def on_decorate_reply(self, e_context: EventContext): return elif self.reply_action == "replace": if self.searchr.ContainsAny(content): - reply = Reply(ReplyType.INFO, "已替换回复中的敏感词: \n" + self.searchr.Replace(content)) + reply = Reply(ReplyType.INFO, "已替换回复中的敏感词:\n" + self.searchr.Replace(content)) e_context["reply"] = reply e_context.action = EventAction.CONTINUE return diff --git a/plugins/banwords/lib/WordsSearch.py b/plugins/banwords/lib/WordsSearch.py index d41d6e7f2..b58b809d4 100644 --- a/plugins/banwords/lib/WordsSearch.py +++ b/plugins/banwords/lib/WordsSearch.py @@ -5,7 +5,7 @@ # Licensed under the Apache License 2.0 # 更新日志 # 2020.04.06 第一次提交 -# 2020.05.16 修改,支持大于0xffff的字符 +# 2020.05.16 修改,支持大于 0xffff 的字符 __all__ = ['WordsSearch'] __author__ = 'Lin Zhijun' diff --git a/voice/ali/ali_api.py b/voice/ali/ali_api.py index cac0c8c13..d05a5dace 100644 --- a/voice/ali/ali_api.py +++ b/voice/ali/ali_api.py @@ -26,14 +26,14 @@ def text_to_speech_aliyun(url, text, appkey, token): """ 使用阿里云的文本转语音服务将文本转换为语音。 - 参数: - - url (str): 阿里云文本转语音服务的端点URL。 + 参数: + - url (str): 阿里云文本转语音服务的端点 URL。 - text (str): 要转换为语音的文本。 - - appkey (str): 您的阿里云appkey。 - - token (str): 阿里云API的认证令牌。 + - appkey (str): 您的阿里云 appkey。 + - token (str): 阿里云 API 的认证令牌。 - 返回值: - - str: 成功时输出音频文件的路径,否则为None。 + 返回值: + - str: 成功时输出音频文件的路径,否则为 None。 """ headers = { "Content-Type": "application/json", @@ -55,8 +55,8 @@ def text_to_speech_aliyun(url, text, appkey, token): file.write(response.content) logger.debug(f"音频文件保存成功,文件名:{output_file}") else: - logger.debug("响应状态码: {}".format(response.status_code)) - logger.debug("响应内容: {}".format(response.text)) + logger.debug("响应状态码:{}".format(response.status_code)) + logger.debug("响应内容:{}".format(response.text)) output_file = None return output_file @@ -66,8 +66,8 @@ class AliyunTokenGenerator: """ 用于生成阿里云服务认证令牌的类。 - 属性: - - access_key_id (str): 您的阿里云访问密钥ID。 + 属性: + - access_key_id (str): 您的阿里云访问密钥 ID。 - access_key_secret (str): 您的阿里云访问密钥秘密。 """ @@ -79,10 +79,10 @@ def sign_request(self, parameters): """ 为阿里云服务签名请求。 - 参数: + 参数: - parameters (dict): 请求的参数字典。 - 返回值: + 返回值: - str: 请求的签名签章。 """ # 将参数按照字典顺序排序 @@ -94,9 +94,9 @@ def sign_request(self, parameters): canonicalized_query_string += '&' + self.percent_encode(k) + '=' + self.percent_encode(v) # 构造用于签名的字符串 - string_to_sign = 'GET&%2F&' + self.percent_encode(canonicalized_query_string[1:]) # 使用GET方法 + string_to_sign = 'GET&%2F&' + self.percent_encode(canonicalized_query_string[1:]) # 使用 GET 方法 - # 使用HMAC算法计算签名 + # 使用 HMAC 算法计算签名 h = hmac.new((self.access_key_secret + "&").encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1) signature = base64.encodebytes(h.digest()).strip() @@ -106,10 +106,10 @@ def percent_encode(self, encode_str): """ 对字符串进行百分比编码。 - 参数: + 参数: - encode_str (str): 要编码的字符串。 - 返回值: + 返回值: - str: 编码后的字符串。 """ encode_str = str(encode_str) @@ -123,7 +123,7 @@ def get_token(self): """ 获取阿里云服务的令牌。 - 返回值: + 返回值: - str: 获取到的令牌。 """ # 设置请求参数 @@ -134,7 +134,7 @@ def get_token(self): 'SignatureMethod': 'HMAC-SHA1', 'Timestamp': datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), 'SignatureVersion': '1.0', - 'SignatureNonce': str(uuid.uuid4()), # 使用uuid生成唯一的随机数 + 'SignatureNonce': str(uuid.uuid4()), # 使用 uuid 生成唯一的随机数 'Action': 'CreateToken', 'RegionId': 'cn-shanghai' } @@ -143,7 +143,7 @@ def get_token(self): signature = self.sign_request(params) params['Signature'] = signature - # 构造请求URL + # 构造请求 URL url = 'http://nls-meta.cn-shanghai.aliyuncs.com/?' + urllib.parse.urlencode(params) # 发送请求 diff --git a/voice/ali/ali_voice.py b/voice/ali/ali_voice.py index 79a9aaa78..2c972c411 100644 --- a/voice/ali/ali_voice.py +++ b/voice/ali/ali_voice.py @@ -24,7 +24,7 @@ class AliVoice(Voice): def __init__(self): """ - 初始化AliVoice类,从配置文件加载必要的配置。 + 初始化 AliVoice 类,从配置文件加载必要的配置。 """ try: curdir = os.path.dirname(__file__) @@ -46,12 +46,12 @@ def textToVoice(self, text): 将文本转换为语音文件。 :param text: 要转换的文本。 - :return: 返回一个Reply对象,其中包含转换得到的语音文件或错误信息。 + :return: 返回一个 Reply 对象,其中包含转换得到的语音文件或错误信息。 """ # 清除文本中的非中文、非英文和非基本字符 text = re.sub(r'[^\u4e00-\u9fa5\u3040-\u30FF\uAC00-\uD7AFa-zA-Z0-9' r'äöüÄÖÜáéíóúÁÉÍÓÚàèìòùÀÈÌÒÙâêîôûÂÊÎÔÛçÇñÑ,。!?,.]', '', text) - # 提取有效的token + # 提取有效的 token token_id = self.get_valid_token() fileName = text_to_speech_aliyun(self.api_url, text, self.app_key, token_id) if fileName: @@ -63,9 +63,9 @@ def textToVoice(self, text): def get_valid_token(self): """ - 获取有效的阿里云token。 + 获取有效的阿里云 token。 - :return: 返回有效的token字符串。 + :return: 返回有效的 token 字符串。 """ current_time = time.time() if self.token is None or current_time >= self.token_expire_time: @@ -73,9 +73,9 @@ def get_valid_token(self): token_str = get_token.get_token() token_data = json.loads(token_str) self.token = token_data["Token"]["Id"] - # 将过期时间减少一小段时间(例如5分钟),以避免在边界条件下的过期 + # 将过期时间减少一小段时间(例如 5 分钟),以避免在边界条件下的过期 self.token_expire_time = token_data["Token"]["ExpireTime"] - 300 - logger.debug(f"新获取的阿里云token:{self.token}") + logger.debug(f"新获取的阿里云 token:{self.token}") else: - logger.debug("使用缓存的token") + logger.debug("使用缓存的 token") return self.token diff --git a/voice/audio_convert.py b/voice/audio_convert.py index 426367883..5b5524a76 100644 --- a/voice/audio_convert.py +++ b/voice/audio_convert.py @@ -10,7 +10,7 @@ from pydub import AudioSegment -sil_supports = [8000, 12000, 16000, 24000, 32000, 44100, 48000] # slk转wav时,支持的采样率 +sil_supports = [8000, 12000, 16000, 24000, 32000, 44100, 48000] # slk 转 wav 时,支持的采样率 def find_closest_sil_supports(sample_rate): @@ -42,7 +42,7 @@ def get_pcm_from_wav(wav_path): def any_to_mp3(any_path, mp3_path): """ - 把任意格式转成mp3文件 + 把任意格式转成 mp3 文件 """ if any_path.endswith(".mp3"): shutil.copy2(any_path, mp3_path) @@ -56,7 +56,7 @@ def any_to_mp3(any_path, mp3_path): def any_to_wav(any_path, wav_path): """ - 把任意格式转成wav文件 + 把任意格式转成 wav 文件 """ if any_path.endswith(".wav"): shutil.copy2(any_path, wav_path) @@ -64,14 +64,14 @@ def any_to_wav(any_path, wav_path): if any_path.endswith(".sil") or any_path.endswith(".silk") or any_path.endswith(".slk"): return sil_to_wav(any_path, wav_path) audio = AudioSegment.from_file(any_path) - audio.set_frame_rate(8000) # 百度语音转写支持8000采样率, pcm_s16le, 单通道语音识别 + audio.set_frame_rate(8000) # 百度语音转写支持 8000 采样率,pcm_s16le, 单通道语音识别 audio.set_channels(1) audio.export(wav_path, format="wav", codec='pcm_s16le') def any_to_sil(any_path, sil_path): """ - 把任意格式转成sil文件 + 把任意格式转成 sil 文件 """ if any_path.endswith(".sil") or any_path.endswith(".silk") or any_path.endswith(".slk"): shutil.copy2(any_path, sil_path) @@ -90,7 +90,7 @@ def any_to_sil(any_path, sil_path): def any_to_amr(any_path, amr_path): """ - 把任意格式转成amr文件 + 把任意格式转成 amr 文件 """ if any_path.endswith(".amr"): shutil.copy2(any_path, amr_path) diff --git a/voice/azure/azure_voice.py b/voice/azure/azure_voice.py index b5884ed4f..39360d821 100644 --- a/voice/azure/azure_voice.py +++ b/voice/azure/azure_voice.py @@ -16,9 +16,9 @@ """ Azure voice -主目录设置文件中需填写azure_voice_api_key和azure_voice_region +主目录设置文件中需填写 azure_voice_api_key 和 azure_voice_region -查看可用的 voice: https://speech.microsoft.com/portal/voicegallery +查看可用的 voice:https://speech.microsoft.com/portal/voicegallery """ diff --git a/voice/baidu/README.md b/voice/baidu/README.md index d4628a1f8..48c0d4ceb 100644 --- a/voice/baidu/README.md +++ b/voice/baidu/README.md @@ -4,11 +4,11 @@ pip install baidu-aip pip install pydub pip install pysilk -还有ffmpeg,不同系统安装方式不同 +还有 ffmpeg,不同系统安装方式不同 -系统中收到的语音文件为mp3格式(wx)或者sil格式(wxy),如果要识别需要转换为pcm格式,转换后的文件为16k采样率,单声道,16bit的pcm文件 -发送时又需要(wx)转换为mp3格式,转换后的文件为16k采样率,单声道,16bit的pcm文件,(wxy)转换为sil格式,还要计算声音长度,发送时需要带上声音长度 -这些事情都在audio_convert.py中封装了,直接调用即可 +系统中收到的语音文件为 mp3 格式(wx)或者 sil 格式(wxy),如果要识别需要转换为 pcm 格式,转换后的文件为 16k 采样率,单声道,16bit 的 pcm 文件 +发送时又需要(wx)转换为 mp3 格式,转换后的文件为 16k 采样率,单声道,16bit 的 pcm 文件,(wxy)转换为 sil 格式,还要计算声音长度,发送时需要带上声音长度 +这些事情都在 audio_convert.py 中封装了,直接调用即可 参数说明 @@ -20,25 +20,25 @@ https://ai.baidu.com/ai-doc/SPEECH/Gk38y8lzk ## 使用说明 分两个地方配置 -1、对于def voiceToText(self, filename)函数中调用的百度语音识别API,中接口调用asr(参数)这个配置见CHATGPT-ON-WECHAT工程目录下的`config.json`文件和config.py文件。 +1、对于 def voiceToText(self, filename) 函数中调用的百度语音识别 API,中接口调用 asr(参数)这个配置见 CHATGPT-ON-WECHAT 工程目录下的`config.json`文件和 config.py 文件。 参数 可需 描述 -app_id 必填 应用的APPID -api_key 必填 应用的APIKey -secret_key 必填 应用的SecretKey -dev_pid 必填 语言选择,填写语言对应的dev_pid值 +app_id 必填 应用的 APPID +api_key 必填 应用的 APIKey +secret_key 必填 应用的 SecretKey +dev_pid 必填 语言选择,填写语言对应的 dev_pid 值 -2、对于def textToVoice(self, text)函数中调用的百度语音合成API,中接口调用synthesis(参数)在本目录下的`config.json`文件中进行配置。 +2、对于 def textToVoice(self, text) 函数中调用的百度语音合成 API,中接口调用 synthesis(参数)在本目录下的`config.json`文件中进行配置。 参数 可需 描述 -tex 必填 合成的文本,使用UTF-8编码,请注意文本长度必须小于1024字节 -lan 必填 固定值zh。语言选择,目前只有中英文混合模式,填写固定值zh -spd 选填 语速,取值0-15,默认为5中语速 -pit 选填 音调,取值0-15,默认为5中语调 -vol 选填 音量,取值0-15,默认为5中音量(取值为0时为音量最小值,并非为无声) +tex 必填 合成的文本,使用 UTF-8 编码,请注意文本长度必须小于 1024 字节 +lan 必填 固定值 zh。语言选择,目前只有中英文混合模式,填写固定值 zh +spd 选填 语速,取值 0-15,默认为 5 中语速 +pit 选填 音调,取值 0-15,默认为 5 中语调 +vol 选填 音量,取值 0-15,默认为 5 中音量(取值为 0 时为音量最小值,并非为无声) per(基础音库) 选填 度小宇=1,度小美=0,度逍遥(基础)=3,度丫丫=4 per(精品音库) 选填 度逍遥(精品)=5003,度小鹿=5118,度博文=106,度小童=110,度小萌=111,度米朵=103,度小娇=5 -aue 选填 3为mp3格式(默认); 4为pcm-16k;5为pcm-8k;6为wav(内容同pcm-16k); 注意aue=4或者6是语音识别要求的格式,但是音频内容不是语音识别要求的自然人发音,所以识别效果会受影响。 +aue 选填 3 为 mp3 格式 (默认);4 为 pcm-16k;5 为 pcm-8k;6 为 wav(内容同 pcm-16k); 注意 aue=4 或者 6 是语音识别要求的格式,但是音频内容不是语音识别要求的自然人发音,所以识别效果会受影响。 -关于per参数的说明,注意您购买的哪个音库,就填写哪个音库的参数,否则会报错。如果您购买的是基础音库,那么per参数只能填写0到4,如果您购买的是精品音库,那么per参数只能填写5003,5118,106,110,111,103,5其他的都会报错。 +关于 per 参数的说明,注意您购买的哪个音库,就填写哪个音库的参数,否则会报错。如果您购买的是基础音库,那么 per 参数只能填写 0 到 4,如果您购买的是精品音库,那么 per 参数只能填写 5003,5118,106,110,111,103,5 其他的都会报错。 ### 配置文件 将文件夹中`config.json.template`复制为`config.json`。 diff --git a/voice/baidu/baidu_voice.py b/voice/baidu/baidu_voice.py index 66ba4d892..02a26c94d 100644 --- a/voice/baidu/baidu_voice.py +++ b/voice/baidu/baidu_voice.py @@ -15,17 +15,17 @@ from voice.voice import Voice """ - 百度的语音识别API. + 百度的语音识别 API. dev_pid: - 1936: 普通话远场 - - 1536:普通话(支持简单的英文识别) - - 1537:普通话(纯中文识别) + - 1536:普通话 (支持简单的英文识别) + - 1537:普通话 (纯中文识别) - 1737:英语 - 1637:粤语 - 1837:四川话 - 要使用本模块, 首先到 yuyin.baidu.com 注册一个开发者账号, - 之后创建一个新应用, 然后在应用管理的"查看key"中获得 API Key 和 Secret Key - 然后在 config.json 中填入这两个值, 以及 app_id, dev_pid + 要使用本模块,首先到 yuyin.baidu.com 注册一个开发者账号, + 之后创建一个新应用,然后在应用管理的"查看 key"中获得 API Key 和 Secret Key + 然后在 config.json 中填入这两个值,以及 app_id, dev_pid """ @@ -68,7 +68,7 @@ def voiceToText(self, voice_file): text = "".join(res["result"]) reply = Reply(ReplyType.TEXT, text) else: - logger.info("百度语音识别出错了: {}".format(res["err_msg"])) + logger.info("百度语音识别出错了:{}".format(res["err_msg"])) if res["err_msg"] == "request pv too much": logger.info(" 出现这个原因很可能是你的百度语音服务调用量超出限制,或未开通付费") reply = Reply(ReplyType.ERROR, "百度语音识别出错了;{0}".format(res["err_msg"]))