From f9b0ad769787ce491ae7b419b758746dc95dc9e6 Mon Sep 17 00:00:00 2001 From: Chiaki Date: Wed, 15 Mar 2023 13:56:23 +0800 Subject: [PATCH] =?UTF-8?q?=201.=E6=96=B0=E5=A2=9Ewechaty=E6=96=B9?= =?UTF-8?q?=E6=A1=88=E7=9A=84=E8=AF=AD=E9=9F=B3=E8=AF=86=E5=88=AB=E3=80=81?= =?UTF-8?q?=E8=AF=AD=E9=9F=B3=E5=9B=9E=E5=A4=8D=E5=8A=9F=E8=83=BD=EF=BC=9B?= =?UTF-8?q?2.=E6=9B=B4=E6=96=B0README=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 48 +++++++++++++++++-- channel/wechat/wechaty_channel.py | 79 +++++++++++++++++++++++++++++++ config-template.json | 11 +++-- 4 files changed, 131 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 8bc62f3..1789860 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .idea +.wechaty/ __pycache__/ venv* *.pyc diff --git a/README.md b/README.md index b339c82..fe34812 100644 --- a/README.md +++ b/README.md @@ -62,15 +62,14 @@ 支持 Linux、MacOS、Windows 系统(可在Linux服务器上长期运行),同时需安装 `Python`。 > 建议Python版本在 3.7.1~3.9.X 之间,3.10及以上版本在 MacOS 可用,其他系统上不确定能否正常运行。 - -1.克隆项目代码: +**1.克隆项目代码:** ```bash git clone https://github.com/zhayujie/chatgpt-on-wechat cd chatgpt-on-wechat/ ``` -2.安装所需核心依赖: +**2.安装所需核心依赖:** ```bash pip3 install itchat-uos==1.5.0.dev0 @@ -78,13 +77,45 @@ pip3 install --upgrade openai ``` 注:`itchat-uos`使用指定版本1.5.0.dev0,`openai`使用最新版本,需高于0.27.0。 +**3.安装所需拓展依赖(接收语音,回复语音):** +### wechaty 处理语音所需 + +```bash +pip3 install pysilk +pip3 install pysilk-mod +pip3 install pydub +``` + +### 百度 + +```bash + pip3 install baidu-aip chardet +``` +### Google + +(1) 安装 SpeechRecognition +```bash + pip3 install SpeechRecognition +``` +(2) 安装 ffmpeg 和 espeak + + MacOS: + ```bash + brew install ffmpeg espeak + ``` + Windows:下载ffmpeg.exe + + Linux: + ```bash + apt-get install ffmpeg espeak + ``` ## 配置 配置文件的模板在根目录的`config-template.json`中,需复制该模板创建最终生效的 `config.json` 文件: ```bash -cp config-template.json config.json + cp config-template.json config.json ``` 然后在`config.json`中填入配置,以下是对默认配置的说明,可根据需要进行自定义修改: @@ -93,14 +124,21 @@ cp config-template.json config.json # config.json文件内容示例 { "open_ai_api_key": "YOUR API KEY", # 填入上面创建的 OpenAI API KEY + "open_ai_api_base": "https://api.openai.com/v1", # 自定义 OpenAI API 地址 "proxy": "127.0.0.1:7890", # 代理客户端的ip和端口 + "baidu_app_id": "", # 百度AI的App Id + "baidu_api_key": "", # 百度AI的API KEY + "baidu_secret_key": "", # 百度AI的Secret KEY + "wechaty_puppet_service_token":"", # wechaty服务token "single_chat_prefix": ["bot", "@bot"], # 私聊时文本需要包含该前缀才能触发机器人回复 "single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人 "group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 "image_create_prefix": ["画", "看", "找"], # 开启图片回复的前缀 "conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 - "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。" # 人格描述 + "speech_recognition": false, # 是否开启语音识别 + "voice_reply_voice": false, # 是否开启语音回复 + "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", # 人格描述 } ``` **配置说明:** diff --git a/channel/wechat/wechaty_channel.py b/channel/wechat/wechaty_channel.py index 5e01464..79ce601 100644 --- a/channel/wechat/wechaty_channel.py +++ b/channel/wechat/wechaty_channel.py @@ -10,12 +10,16 @@ import json import time import asyncio import requests +import pysilk +import wave +from pydub import AudioSegment from typing import Optional, Union from wechaty_puppet import MessageType, FileBox, ScanStatus # type: ignore from wechaty import Wechaty, Contact from wechaty.user import Message, Room, MiniProgram, UrlLink from channel.channel import Channel from common.log import logger +from common.tmp_dir import TmpDir from config import conf @@ -89,6 +93,48 @@ class WechatyChannel(Channel): await self._do_send_img(content, to_user_id) else: await self._do_send(content, to_user_id) + elif room is None and msg.type() == MessageType.MESSAGE_TYPE_AUDIO: + if not msg.is_self(): # 接收语音消息 + # 下载语音文件 + voice_file = await msg.to_file_box() + silk_file = TmpDir().path() + voice_file.name + await voice_file.to_file(silk_file) + logger.info("[WX]receive voice file: " + silk_file) + # 将文件转成wav格式音频 + wav_file = silk_file.replace(".slk", ".wav") + with open(silk_file, 'rb') as f: + silk_data = f.read() + pcm_data = pysilk.decode(silk_data) + + with wave.open(wav_file, 'wb') as wav_data: + wav_data.setnchannels(1) + wav_data.setsampwidth(2) + wav_data.setframerate(24000) + wav_data.writeframes(pcm_data) + if os.path.exists(wav_file): + converter_state = "true" # 转换wav成功 + else: + converter_state = "false" # 转换wav失败 + logger.info("[WX]receive voice converter: " + converter_state) + # 语音识别为文本 + query = super().build_voice_to_text(wav_file) + # 交验关键字 + match_prefix = self.check_prefix(query, conf().get('single_chat_prefix')) + if match_prefix is not None: + if match_prefix != '': + str_list = query.split(match_prefix, 1) + if len(str_list) == 2: + query = str_list[1].strip() + # 返回消息 + if conf().get('voice_reply_voice'): + await self._do_send_voice(query, from_user_id) + else: + await self._do_send(query, from_user_id) + else: + logger.info("[WX]receive voice check prefix: " + 'False') + # 清除缓存文件 + os.remove(wav_file) + os.remove(silk_file) elif room and msg.type() == MessageType.MESSAGE_TYPE_TEXT: # 群组&文本消息 room_id = room.room_id @@ -135,6 +181,39 @@ class WechatyChannel(Channel): except Exception as e: logger.exception(e) + + async def _do_send_voice(self, query, reply_user_id): + try: + if not query: + return + context = dict() + context['session_id'] = reply_user_id + reply_text = super().build_reply_content(query, context) + if reply_text: + # 转换 mp3 文件为 silk 格式 + mp3_file = super().build_text_to_voice(reply_text) + silk_file = mp3_file.replace(".mp3", ".silk") + # Load the MP3 file + audio = AudioSegment.from_file(mp3_file, format="mp3") + # Convert to WAV format + audio = audio.set_frame_rate(24000).set_channels(1) + wav_data = audio.raw_data + sample_width = audio.sample_width + # Encode to SILK format + silk_data = pysilk.encode(wav_data, 24000) + # Save the silk file + with open(silk_file, "wb") as f: + f.write(silk_data) + # 发送语音 + t = int(time.time()) + file_box = FileBox.from_file(silk_file, name=str(t) + '.silk') + await self.send(file_box, reply_user_id) + # 清除缓存文件 + os.remove(mp3_file) + os.remove(silk_file) + except Exception as e: + logger.exception(e) + async def _do_send_img(self, query, reply_user_id): try: if not query: diff --git a/config-template.json b/config-template.json index 9ad9f5d..e344029 100644 --- a/config-template.json +++ b/config-template.json @@ -1,13 +1,18 @@ { "open_ai_api_key": "YOUR API KEY", "proxy": "", + "wechaty_puppet_service_token":"", + "baidu_app_id": "", + "baidu_api_key": "", + "baidu_secret_key": "", "single_chat_prefix": ["bot", "@bot"], "single_chat_reply_prefix": "[bot] ", "group_chat_prefix": ["@bot"], "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], "image_create_prefix": ["画", "看", "找"], - "conversation_max_tokens": 1000, "speech_recognition": false, - "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", - "expires_in_seconds": 3600 + "voice_reply_voice": false, + "conversation_max_tokens": 1000, + "expires_in_seconds": 3600, + "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。" }