From 7fd9fad1a22d18107dc1906b1a367b1f5a2e35c2 Mon Sep 17 00:00:00 2001 From: kx-huang Date: Tue, 13 Dec 2022 01:58:39 -0500 Subject: [PATCH 01/12] fix: 'tigger' to 'trigger' --- README.md | 2 +- README_ZH.md | 2 +- config.yaml.example | 2 +- src/chatgpt.ts | 18 +++++++++--------- src/config.ts | 6 +++--- src/interface.ts | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 46329ef..4e0bf3e 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ chatGPTAccountPool: - email: password: # if you hope only some keywords can trigger chatgpt on private chat, you can set it like this: -chatPrivateTiggerKeyword: "" +chatPrivateTriggerKeyword: "" ``` ⚠️ Trigger keywords must appear in the first position of the received message. diff --git a/README_ZH.md b/README_ZH.md index cd21fdd..c0f6dce 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -94,7 +94,7 @@ chatGPTAccountPool: - email: password: # 如果你希望只有一些关键字可以在私人聊天中触发chatgpt,你可以这样设置: -chatPrivateTiggerKeyword: "" +chatPrivateTriggerKeyword: "" ``` ⚠️ 触发关键字必须出现在接收到的消息的第一个位置⚠️ diff --git a/config.yaml.example b/config.yaml.example index 0d789df..c8051e2 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -2,7 +2,7 @@ chatGPTAccountPool: - email: email password: password session_token: session_token -chatPrivateTiggerKeyword: "" +chatPrivateTriggerKeyword: "" openAIProxy: "" clearanceToken: "" userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" \ No newline at end of file diff --git a/src/chatgpt.ts b/src/chatgpt.ts index 85bc144..d768991 100644 --- a/src/chatgpt.ts +++ b/src/chatgpt.ts @@ -253,12 +253,12 @@ export class ChatGPTBot { conversations = new Map(); chatGPTPool = new ChatGPTPoole(); cache = new Cache("cache.json"); - chatPrivateTiggerKeyword = config.chatPrivateTiggerKeyword; + chatPrivateTriggerKeyword = config.chatPrivateTriggerKeyword; botName: string = ""; setBotName(botName: string) { this.botName = botName; } - get chatGroupTiggerKeyword(): string { + get chatGroupTriggerKeyword(): string { return `@${this.botName}`; } async startGPTBot() { @@ -276,7 +276,7 @@ export class ChatGPTBot { text = item[item.length - 1]; } text = text.replace( - privateChat ? this.chatPrivateTiggerKeyword : this.chatGroupTiggerKeyword, + privateChat ? this.chatPrivateTriggerKeyword : this.chatGroupTriggerKeyword, "" ); // remove more text via - - - - - - - - - - - - - - - @@ -302,15 +302,15 @@ export class ChatGPTBot { } } // Check whether the ChatGPT processing can be triggered - tiggerGPTMessage(text: string, privateChat: boolean = false): boolean { - const chatPrivateTiggerKeyword = this.chatPrivateTiggerKeyword; + triggerGPTMessage(text: string, privateChat: boolean = false): boolean { + const chatPrivateTriggerKeyword = this.chatPrivateTriggerKeyword; let triggered = false; if (privateChat) { - triggered = chatPrivateTiggerKeyword - ? text.includes(chatPrivateTiggerKeyword) + triggered = chatPrivateTriggerKeyword + ? text.includes(chatPrivateTriggerKeyword) : true; } else { - triggered = text.includes(this.chatGroupTiggerKeyword); + triggered = text.includes(this.chatGroupTriggerKeyword); } if (triggered) { console.log(`🎯 Triggered ChatGPT: ${text}`); @@ -361,7 +361,7 @@ export class ChatGPTBot { if (this.isNonsense(talker, messageType, rawText)) { return; } - if (this.tiggerGPTMessage(rawText, privateChat)) { + if (this.triggerGPTMessage(rawText, privateChat)) { const text = this.cleanMessage(rawText, privateChat); if (privateChat) { return await this.onPrivateMessage(talker, text); diff --git a/src/config.ts b/src/config.ts index f164fb2..5174d5c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -18,7 +18,7 @@ if (fs.existsSync("./config.yaml")) { }, ], chatGptRetryTimes: Number(process.env.CHAT_GPT_RETRY_TIMES), - chatPrivateTiggerKeyword: process.env.CHAT_PRIVATE_TRIGGER_KEYWORD, + chatPrivateTriggerKeyword: process.env.CHAT_PRIVATE_TRIGGER_KEYWORD, openAIProxy: process.env.OPENAI_PROXY, clearanceToken: process.env.CF_CLEARANCE, userAgent: process.env.USER_AGENT, @@ -29,8 +29,8 @@ dotenv.config(); export const config: IConfig = { chatGPTAccountPool: configFile.chatGPTAccountPool as Array, chatGptRetryTimes: configFile.chatGptRetryTimes || 3, - chatPrivateTiggerKeyword: - configFile.chatPrivateTiggerKeyword || + chatPrivateTriggerKeyword: + configFile.chatPrivateTriggerKeyword || // Try compatible with previous designs (configFile?.botConfig as Array>)?.reduce( (prev: string, curr: Map) => diff --git a/src/interface.ts b/src/interface.ts index 9245656..b00d3c3 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -35,7 +35,7 @@ export interface IConversationItem { export interface IConfig { chatGPTAccountPool: IAccount[]; chatGptRetryTimes: number; - chatPrivateTiggerKeyword: string; + chatPrivateTriggerKeyword: string; openAIProxy?: string; clearanceToken: string; userAgent: string; From 6fa58844e9ad6dd26371573706cac9548cba9678 Mon Sep 17 00:00:00 2001 From: RealTong Date: Sun, 12 Mar 2023 14:09:59 +0800 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20block=20word?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add block words --- .env.example | 5 ++++- README.md | 13 +++++++++++++ README_ZH.md | 29 ++++++++++++++++++++++++++--- src/bot.ts | 23 +++++++++++++++++++++-- src/chatgpt.ts | 12 ++++++++---- src/config.ts | 4 +++- src/interface.ts | 3 +++ src/main.ts | 6 +++++- 8 files changed, 83 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index e121a88..f7d22c4 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ OPENAI_API_KEY="" MODEL="gpt-3.5-turbo" -CHAT_PRIVATE_TRIGGER_KEYWORD= \ No newline at end of file +CHAT_PRIVATE_TRIGGER_KEYWORD= +TEMPERATURE= +BLOCK_WORDS="SB, VPN" +CHATGPT_BLOCK_WORDS="VPN" \ No newline at end of file diff --git a/README.md b/README.md index 7761734..aafbe79 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,19 @@ npm npm dev > Please make sure your WeChat account can log in [WeChat on web](https://wx.qq.com/) +## 📝 Environment Variables + +| name | default | example | description | +|------------------------------|---------------|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [create new secret key](https://platform.openai.com/account/api-keys) | +| MODEL | gpt-3.5-turbo | | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. | +| TEMPERATURE | 0.6 | | What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | +| CHAT_TRIGGER_RULE | | | | +| DISABLE_GROUP_MESSAGE | true | | | +| CHAT_PRIVATE_TRIGGER_KEYWORD | | | Keyword to trigger ChatGPT reply in WeChat private chat | +| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | Chat blocker words, (works for both private and group chats, Use, Split) | +| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | The blocked words returned by ChatGPT(works for both private and group chats, Use, Split) | + ## ✨ Contributor diff --git a/README_ZH.md b/README_ZH.md index ac7f088..5f82a0f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -19,7 +19,8 @@ ## 🌟 功能点 -- [x] 通过 [wechaty](https://github.com/wechaty/wechaty) 和 [官方 API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis),将 ChatGPT 接入微信 +- [x] 通过 [wechaty](https://github.com/wechaty/wechaty) + 和 [官方 API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis),将 ChatGPT 接入微信 - [x] 加入了持续对话的功能 - [x] 加入 Dockerfile, 通过 [Docker](#通过docker使用-推荐) 进行部署 - [x] 发布到 Docker.hub @@ -27,6 +28,7 @@ - [x] 通过 Railway 进行部署 ## 🚀 使用 + - [在 Railway 部署](#使用railway进行部署)(PaaS, 免费, 稳定, ✅推荐) - [在 Fly.io 部署](#通过flyio进行部署)(PaaS, 免费, ✅推荐) - [使用 Docker 部署](#通过docker使用)(自托管, 稳定, ✅推荐) @@ -34,7 +36,9 @@ - [使用 NodeJS 部署](#使用nodejs运行) ## 使用Railway进行部署 + > Railway 是一个免费的 PaaS 平台,5刀以内的账单免费或者每个月500小时的运行时间 + 1. 点击 [Railway](https://railway.app/template/dMLG70?referralCode=bIYugQ) 按钮,进入 Railway 部署页面 2. 点击 `Deploy Now` 按钮,进入 Railway 部署页面 3. 填写 仓库名称和 `OPENAI_API_KEY`(需要连接 GitHub 账号) @@ -42,9 +46,11 @@ 5. 点击 `View Logs` 按钮,等待部署完成 ## 通过Fly.io进行部署 + > 请为应用程序分配 512 MB 内存,否则可能会出现内存溢出 -> Fly.io 5刀以内的账单免费(免费计划的3个256MB的应用不在账单内)也就是可以同时可以部署 `1*512MB + 3*256MB` +> Fly.io 5刀以内的账单免费(免费计划的3个256MB的应用不在账单内)也就是可以同时可以部署 `1*512MB + 3*256MB` + 1. 安装 [flyctl](https://fly.io/docs/getting-started/installing-flyctl/) ```shell # macOS @@ -91,6 +97,7 @@ docker run -it --name wechat-chatgpt \ # 使用二维码登陆 docker logs -f wechat-chatgpt ``` + > 如何获取 OPENAI API KEY?请参考 [OpenAI API](https://platform.openai.com/account/api-keys)。 ## 通过docker compose使用 @@ -107,7 +114,9 @@ docker logs -f wechat-chatgpt ``` ## 使用NodeJS运行 + > 请确认安装的NodeJS版本为18.0.0以上 + ```sh # 克隆项目 git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt @@ -120,8 +129,21 @@ vim .env # 使用你喜欢的文本编辑器修改配置文件 npm run dev # 如果您是初次登陆,那么需要扫描二维码 ``` + > 请确保您的账号可以登陆 [网页版微信](https://wx.qq.com/)。 +## 📝 Environment Variables + +| name | default | example | description | +|------------------------------|---------------|------------------------------------------------|-------------------------------------------------------------| +| OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [创建你的 API 密钥](https://platform.openai.com/account/api-keys) | +| MODEL | gpt-3.5-turbo | | 要使用的模型ID, 目前仅支持`gpt-3.5-turbo` 和 `gpt-3.5-turbo-0301` | +| TEMPERATURE | 0.6 | | 在0和2之间。较高的数值如0.8会使 ChatGPT 输出更加随机,而较低的数值如0.2会使其更加稳定。 | +| CHAT_TRIGGER_RULE | | | | +| DISABLE_GROUP_MESSAGE | true | | | +| CHAT_PRIVATE_TRIGGER_KEYWORD | | | 在私聊中触发ChatGPT的关键词, 默认是无需关键词即可触发 | +| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号 | +| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复 | ## ✨ Contributor @@ -131,7 +153,8 @@ npm run dev ## 🤝 为项目添砖加瓦 -欢迎提出 Contributions, issues 与 feature requests!
随时查看 [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues). +欢迎提出 Contributions, issues 与 feature requests!
+随时查看 [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues). ## 感谢支持 🙏 diff --git a/src/bot.ts b/src/bot.ts index 21ea401..27f41c5 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -67,12 +67,23 @@ export class ChatGPTBot { async getGPTMessage(text: string): Promise { return await sendMessage(text); } + // Check if the message returned by chatgpt contains masked words] + checkChatGPTBlockWords(message: string): boolean { + if (config.chatgptBlockWords.length == 0) { + return false; + } + return config.chatgptBlockWords.some((word) => message.includes(word)); + } // The message is segmented according to its size async trySay( talker: RoomInterface | ContactInterface, mesasge: string ): Promise { const messages: Array = []; + if (this.checkChatGPTBlockWords(mesasge)) { + console.log(`🚫 Blocked ChatGPT: ${mesasge}`); + return; + } let message = mesasge; while (message.length > SINGLE_MESSAGE_MAX_SIZE) { messages.push(message.slice(0, SINGLE_MESSAGE_MAX_SIZE)); @@ -102,6 +113,13 @@ export class ChatGPTBot { } return triggered; } + // Check whether the message contains the blocked words. if so, the message will be ignored. if so, return true + checkBlockWords(message: string): boolean { + if (config.blockWords.length == 0) { + return false; + } + return config.blockWords.some((word) => message.includes(word)); + } // Filter out the message that does not need to be processed isNonsense( talker: ContactInterface, @@ -120,12 +138,13 @@ export class ChatGPTBot { // Transfer message text.includes("收到转账,请在手机上查看") || // 位置消息 - text.includes("/cgi-bin/mmwebwx-bin/webwxgetpubliclinkimg") + text.includes("/cgi-bin/mmwebwx-bin/webwxgetpubliclinkimg") || + // 聊天屏蔽词 + this.checkBlockWords(text) ); } async onPrivateMessage(talker: ContactInterface, text: string) { - const talkerId = talker.id; const gptMessage = await this.getGPTMessage(text); await this.trySay(talker, gptMessage); } diff --git a/src/chatgpt.ts b/src/chatgpt.ts index 0802e6d..99dca9f 100644 --- a/src/chatgpt.ts +++ b/src/chatgpt.ts @@ -2,6 +2,7 @@ import {config} from "./config.js"; let apiKey = config.openai_api_key; let model = config.model; +let temperature = config.temperature; const sendMessage = async (message: string) => { try { const response = await fetch(`https://api.openai.com/v1/chat/completions`, { @@ -18,11 +19,14 @@ const sendMessage = async (message: string) => { "content": message } ], - temperature: 0.6 + temperature: temperature }), - }); - return response.json() - .then((data) => data.choices[0].message.content); + }).then((res) => res.json()); + if (response.error?.message) { + console.log("OpenAI API ERROR: ",response.error.message) + // throw new Error(`OpenAI API ${response.error.message}`); + } + return response.choices[0].message.content; } catch (e) { console.error(e) return "Something went wrong" diff --git a/src/config.ts b/src/config.ts index 8bf8446..ac52007 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,6 @@ import * as dotenv from "dotenv"; dotenv.config(); import { IConfig } from "./interface"; -dotenv.config(); export const config: IConfig = { openai_api_key: process.env.OPENAI_API_KEY || "123456789", @@ -9,4 +8,7 @@ export const config: IConfig = { chatPrivateTiggerKeyword: process.env.CHAT_PRIVATE_TRIGGER_KEYWORD || "", chatTiggerRule: process.env.CHAT_TRIGGER_RULE || "", disableGroupMessage: process.env.DISABLE_GROUP_MESSAGE === "true", + temperature: process.env.TEMPERATURE ? parseFloat(process.env.TEMPERATURE) : 0.6, + blockWords: process.env.BLOCK_WORDS?.split(",") || [], + chatgptBlockWords: process.env.CHATGPT_BLOCK_WORDS?.split(",") || [], }; diff --git a/src/interface.ts b/src/interface.ts index c9e70fc..cb1490f 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -4,4 +4,7 @@ export interface IConfig { chatPrivateTiggerKeyword: string; chatTiggerRule: string; disableGroupMessage: boolean; + temperature: number; + blockWords: string[]; + chatgptBlockWords: string[]; } diff --git a/src/main.ts b/src/main.ts index 0a463b1..e6cf491 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { WechatyBuilder } from "wechaty"; import QRCode from "qrcode"; import { ChatGPTBot } from "./bot.js"; +import {config} from "./config.js"; const chatGPTBot = new ChatGPTBot(); const bot = WechatyBuilder.build({ @@ -17,8 +18,11 @@ async function main() { ); }) .on("login", async (user) => { - console.log(`User ${user} logged in`); chatGPTBot.setBotName(user.name()); + console.log(`User ${user} logged in`); + console.log(`私聊触发关键词: ${config.chatPrivateTiggerKeyword}`); + console.log(`已设置 ${config.blockWords.length} 个聊天关键词屏蔽. ${config.blockWords}`); + console.log(`已设置 ${config.chatgptBlockWords.length} 个ChatGPT回复关键词屏蔽. ${config.chatgptBlockWords}`); }) .on("message", async (message) => { if (message.date().getTime() < initializedAt) { From eafb93f576fd0d62de07b2e53c363d4e74359079 Mon Sep 17 00:00:00 2001 From: RealTong Date: Tue, 14 Mar 2023 15:07:15 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Support=20for=20us?= =?UTF-8?q?ing=20custom=20ChatGPT=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support for using custom ChatGPT API. ✅ Closes: #715 --- README.md | 42 ++++++++++++++++++++++++++++++++---------- README_ZH.md | 41 +++++++++++++++++++++++++++++++---------- src/chatgpt.ts | 3 ++- src/config.ts | 1 + src/interface.ts | 1 + 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index aafbe79..edb21ac 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ - [x] Publish to Docker.hub - [x] Deploy using [docker compose](#use-with-docker-compose---recommended-) - [x] Add Railway deploy +- [x] Add Fly.io deploy +- [x] Supports custom ChatGPT API +- [ ] Support proxy ## 🚀 Usage - [Use with Railway](#use-with-railway)(PaaS, Free, Stable, ✅Recommended) @@ -130,16 +133,35 @@ npm npm dev ## 📝 Environment Variables -| name | default | example | description | -|------------------------------|---------------|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [create new secret key](https://platform.openai.com/account/api-keys) | -| MODEL | gpt-3.5-turbo | | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. | -| TEMPERATURE | 0.6 | | What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | -| CHAT_TRIGGER_RULE | | | | -| DISABLE_GROUP_MESSAGE | true | | | -| CHAT_PRIVATE_TRIGGER_KEYWORD | | | Keyword to trigger ChatGPT reply in WeChat private chat | -| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | Chat blocker words, (works for both private and group chats, Use, Split) | -| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | The blocked words returned by ChatGPT(works for both private and group chats, Use, Split) | +| name | default | example | description | +|------------------------------|------------------------|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| API | https://api.openai.com | | API endpoint of ChatGPT | +| OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [create new secret key](https://platform.openai.com/account/api-keys) | +| MODEL | gpt-3.5-turbo | | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. | +| TEMPERATURE | 0.6 | | What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | +| CHAT_TRIGGER_RULE | | | | +| DISABLE_GROUP_MESSAGE | true | | | +| CHAT_PRIVATE_TRIGGER_KEYWORD | | | Keyword to trigger ChatGPT reply in WeChat private chat | +| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | Chat blocker words, (works for both private and group chats, Use, Split) | +| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | The blocked words returned by ChatGPT(works for both private and group chats, Use, Split) | + +## 📝 Using Custom ChatGPT API + +> https://github.com/fuergaosi233/openai-proxy + +```shell +# Clone the project +git clone https://github.com/fuergaosi233/openai-proxy +# Install dependencies +npm install && npm install -g wrangler && npm run build +# Deploy to CloudFlare Workers +npm run deploy +# Custom domain (optional) +Add `Route` to `wrangler.toml` +routes = [ + { pattern = "Your Custom Domain", custom_domain = true }, +] +``` ## ✨ Contributor diff --git a/README_ZH.md b/README_ZH.md index 5f82a0f..335d4e0 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -26,6 +26,9 @@ - [x] 发布到 Docker.hub - [x] 使用[docker compose](#通过docker-compose使用-推荐)进行部署 - [x] 通过 Railway 进行部署 +- [x] 通过 Fly.io 进行部署 +- [x] 支持自定义ChatGPT API +- [ ] 支持代理 ## 🚀 使用 @@ -134,16 +137,34 @@ npm run dev ## 📝 Environment Variables -| name | default | example | description | -|------------------------------|---------------|------------------------------------------------|-------------------------------------------------------------| -| OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [创建你的 API 密钥](https://platform.openai.com/account/api-keys) | -| MODEL | gpt-3.5-turbo | | 要使用的模型ID, 目前仅支持`gpt-3.5-turbo` 和 `gpt-3.5-turbo-0301` | -| TEMPERATURE | 0.6 | | 在0和2之间。较高的数值如0.8会使 ChatGPT 输出更加随机,而较低的数值如0.2会使其更加稳定。 | -| CHAT_TRIGGER_RULE | | | | -| DISABLE_GROUP_MESSAGE | true | | | -| CHAT_PRIVATE_TRIGGER_KEYWORD | | | 在私聊中触发ChatGPT的关键词, 默认是无需关键词即可触发 | -| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号 | -| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复 | +| name | default | example | description | +|------------------------------|------------------------|------------------------------------------------|-------------------------------------------------------------| +| API | https://api.openai.com | ChatGPT API 地址 | +| OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [创建你的 API 密钥](https://platform.openai.com/account/api-keys) | +| MODEL | gpt-3.5-turbo | | 要使用的模型ID, 目前仅支持`gpt-3.5-turbo` 和 `gpt-3.5-turbo-0301` | +| TEMPERATURE | 0.6 | | 在0和2之间。较高的数值如0.8会使 ChatGPT 输出更加随机,而较低的数值如0.2会使其更加稳定。 | +| CHAT_TRIGGER_RULE | | | | +| DISABLE_GROUP_MESSAGE | true | | | +| CHAT_PRIVATE_TRIGGER_KEYWORD | | | 在私聊中触发ChatGPT的关键词, 默认是无需关键词即可触发 | +| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号 | +| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复 | + + +## 📝 使用自定义ChatGPT API +> https://github.com/fuergaosi233/openai-proxy +```shell +# 克隆项目 +git clone https://github.com/fuergaosi233/openai-proxy +# 安装依赖 +npm install && npm install -g wrangler && npm run build +# 部署到 CloudFlare Workers +npm run deploy +# 自定义域名(可选) +添加 `Route`` 到 `wrangler.toml` +routes = [ + { pattern = "Your Custom Domain", custom_domain = true }, +] +``` ## ✨ Contributor diff --git a/src/chatgpt.ts b/src/chatgpt.ts index 99dca9f..c906ecf 100644 --- a/src/chatgpt.ts +++ b/src/chatgpt.ts @@ -1,11 +1,12 @@ import {config} from "./config.js"; +let api = config.api; let apiKey = config.openai_api_key; let model = config.model; let temperature = config.temperature; const sendMessage = async (message: string) => { try { - const response = await fetch(`https://api.openai.com/v1/chat/completions`, { + const response = await fetch(`${api}/v1/chat/completions`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, diff --git a/src/config.ts b/src/config.ts index ac52007..0e69f18 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,6 +3,7 @@ dotenv.config(); import { IConfig } from "./interface"; export const config: IConfig = { + api: process.env.API || "https://api.openai.com", openai_api_key: process.env.OPENAI_API_KEY || "123456789", model: process.env.MODEL || "gpt-3.5-turbo", chatPrivateTiggerKeyword: process.env.CHAT_PRIVATE_TRIGGER_KEYWORD || "", diff --git a/src/interface.ts b/src/interface.ts index cb1490f..019bd22 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,4 +1,5 @@ export interface IConfig { + api: string; openai_api_key: string; model: string; chatPrivateTiggerKeyword: string; From 8f2909d79b23e5e7052a92ab7fbec2aaa05cd515 Mon Sep 17 00:00:00 2001 From: RealTong Date: Fri, 17 Mar 2023 23:23:12 +0800 Subject: [PATCH 04/12] add continuous dialogue feat --- package-lock.json | 351 +++++++++++++++++++++++++--------------------- package.json | 1 + src/bot.ts | 58 +++++++- src/command.ts | 0 src/data.ts | 73 ++++++++++ src/main.ts | 52 +++++++ src/openai.ts | 64 +++++++++ src/whisper.ts | 34 +++++ 8 files changed, 467 insertions(+), 166 deletions(-) create mode 100644 src/command.ts create mode 100644 src/data.ts create mode 100644 src/openai.ts create mode 100644 src/whisper.ts diff --git a/package-lock.json b/package-lock.json index 111368e..df01a76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "async-retry": "^1.3.3", "dotenv": "^16.0.3", "execa": "^6.1.0", + "openai": "^3.2.1", "qrcode": "^1.5.1", "uuid": "^9.0.0", "wechaty": "^1.20.2", @@ -948,9 +949,9 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "node_modules/axios": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.0.tgz", - "integrity": "sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -1021,9 +1022,9 @@ } }, "node_modules/bl/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1034,7 +1035,7 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/bl/node_modules/safe-buffer": { + "node_modules/bl/node_modules/readable-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" @@ -1047,6 +1048,11 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/bl/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/bmp-js": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", @@ -1378,7 +1384,8 @@ "node_modules/cuid": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.8.tgz", - "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==" + "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==", + "deprecated": "Cuid and other k-sortable and non-cryptographic ids (Ulid, ObjectId, KSUID, all UUIDs) are all insecure. Use @paralleldrive/cuid2 instead." }, "node_modules/dashdash": { "version": "1.14.1", @@ -1416,9 +1423,9 @@ } }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "engines": { "node": ">=0.10.0" } @@ -1543,16 +1550,6 @@ "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/encoding-down": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-7.1.0.tgz", @@ -1567,19 +1564,6 @@ "node": ">=10" } }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -2005,9 +1989,9 @@ "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/har-schema": { "version": "2.0.0", @@ -3046,6 +3030,36 @@ "request": "^2.73.0" } }, + "node_modules/openai": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz", + "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==", + "dependencies": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, + "node_modules/openai/node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/openai/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -3287,6 +3301,7 @@ "version": "13.7.0", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.7.0.tgz", "integrity": "sha512-U1uufzBjz3+PkpCxFrWzh4OrMIdIb2ztzCu0YEPfRHjHswcSwHZswnK+WdsOQJsRV8WeTg3jLhJR4D867+fjsA==", + "deprecated": "< 19.2.0 is no longer supported", "hasInstallScript": true, "dependencies": { "cross-fetch": "3.1.5", @@ -3307,9 +3322,9 @@ } }, "node_modules/puppeteer-extra": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.4.tgz", - "integrity": "sha512-fN5pHvSMJ8d1o7Z8wLLTQOUBpORD2BcFn+KDs7QnkGZs9SV69hcUcce67vX4L4bNSEG3A0P6Osrv+vWNhhdm8w==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.6.tgz", + "integrity": "sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A==", "dependencies": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -3336,9 +3351,9 @@ } }, "node_modules/puppeteer-extra-plugin": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", - "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", + "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", "dependencies": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -3361,13 +3376,13 @@ } }, "node_modules/puppeteer-extra-plugin-stealth": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", - "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz", + "integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==", "dependencies": { "debug": "^4.1.1", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-preferences": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-preferences": "^2.4.1" }, "engines": { "node": ">=8" @@ -3386,13 +3401,13 @@ } }, "node_modules/puppeteer-extra-plugin-user-data-dir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", - "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz", + "integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==", "dependencies": { "debug": "^4.1.1", "fs-extra": "^10.0.0", - "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin": "^3.2.3", "rimraf": "^3.0.2" }, "engines": { @@ -3412,14 +3427,14 @@ } }, "node_modules/puppeteer-extra-plugin-user-preferences": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", - "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", + "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", "dependencies": { "debug": "^4.1.1", "deepmerge": "^4.2.2", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-data-dir": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-data-dir": "^2.4.1" }, "engines": { "node": ">=8" @@ -4103,9 +4118,9 @@ } }, "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", "dev": true, "peer": true, "bin": { @@ -4113,7 +4128,7 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=12.20" } }, "node_modules/unbzip2-stream": { @@ -4213,9 +4228,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/wechat4u": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/wechat4u/-/wechat4u-0.7.8.tgz", - "integrity": "sha512-2k9FRiYV8G4KM9aHJXIpgqiMAcln5vieXWbBAxlUQY/B9buxgEkRHYH4mZSY3AiIS1X+mFQkzCmG5/GgPLLwkw==", + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/wechat4u/-/wechat4u-0.7.10.tgz", + "integrity": "sha512-nUWN2ypjeuMXOAlU+NlwYdJhyonzCBprC5O9Wms1YwgG/TPhvtmKKkpuaD2JJ5/h2wrqXfs+r/dsemAPuySFAA==", "dependencies": { "axios": "^1.1.3", "bl": "^1.1.2", @@ -4412,25 +4427,6 @@ "brolog": "^1.3.3" } }, - "node_modules/wechaty-puppet-wechat4u": { - "version": "1.13.11", - "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.13.11.tgz", - "integrity": "sha512-OM+pXAcEYWcMmoscJLPazn5Ji+8ZFNetyromyzq05crDAM4ra7CQmUkJaQNZHxZZdoioTiBsTCAhDsIpHeOdDA==", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "fast-xml-parser": "^3.21.1", - "promise-retry": "^2.0.1", - "wechat4u": "^0.7.8", - "xml2js": "^0.4.23" - }, - "engines": { - "node": ">=16", - "npm": ">=7" - }, - "peerDependencies": { - "wechaty-puppet": "^1.18.3" - } - }, "node_modules/wechaty-puppet/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -4517,6 +4513,25 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/wechaty/node_modules/wechaty-puppet-wechat4u": { + "version": "1.13.14", + "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.13.14.tgz", + "integrity": "sha512-k32aQJHjQdE/Hsj1OE44lle6zSMaSusHAlS+LOmxW5AcTQni4kEGA1UteN3MvlRc6K8INxc2Ol85HwacVob3bA==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "fast-xml-parser": "^3.21.1", + "promise-retry": "^2.0.1", + "wechat4u": "^0.7.10", + "xml2js": "^0.4.23" + }, + "engines": { + "node": ">=16", + "npm": ">=7" + }, + "peerDependencies": { + "wechaty-puppet": "^1.18.3" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -5425,9 +5440,9 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "axios": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.0.tgz", - "integrity": "sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -5480,9 +5495,9 @@ }, "dependencies": { "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5491,19 +5506,28 @@ "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } } } @@ -5782,9 +5806,9 @@ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "deferred-leveldown": { "version": "7.0.0", @@ -5887,28 +5911,6 @@ "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==" }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "optional": true, - "peer": true, - "requires": { - "iconv-lite": "^0.6.2" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "optional": true, - "peer": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, "encoding-down": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-7.1.0.tgz", @@ -6232,9 +6234,9 @@ "integrity": "sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==" }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "har-schema": { "version": "2.0.0", @@ -7024,6 +7026,35 @@ "request": "^2.73.0" } }, + "openai": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz", + "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==", + "requires": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + }, + "dependencies": { + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -7239,9 +7270,9 @@ } }, "puppeteer-extra": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.4.tgz", - "integrity": "sha512-fN5pHvSMJ8d1o7Z8wLLTQOUBpORD2BcFn+KDs7QnkGZs9SV69hcUcce67vX4L4bNSEG3A0P6Osrv+vWNhhdm8w==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.6.tgz", + "integrity": "sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A==", "requires": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -7249,9 +7280,9 @@ } }, "puppeteer-extra-plugin": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", - "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", + "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", "requires": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -7259,35 +7290,35 @@ } }, "puppeteer-extra-plugin-stealth": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", - "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz", + "integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==", "requires": { "debug": "^4.1.1", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-preferences": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-preferences": "^2.4.1" } }, "puppeteer-extra-plugin-user-data-dir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", - "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz", + "integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==", "requires": { "debug": "^4.1.1", "fs-extra": "^10.0.0", - "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin": "^3.2.3", "rimraf": "^3.0.2" } }, "puppeteer-extra-plugin-user-preferences": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", - "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", + "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", "requires": { "debug": "^4.1.1", "deepmerge": "^4.2.2", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-data-dir": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-data-dir": "^2.4.1" } }, "qr-image": { @@ -7761,9 +7792,9 @@ "integrity": "sha512-bna6Yi1pRznoo6Bz1cE6btB/Yy8Xywytyfrzu/wc+NFW3ZF0I+2iCGImhBsoYYCOWuICtRO4yHcnDlzgo1AdNg==" }, "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", "dev": true, "peer": true }, @@ -7848,9 +7879,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "wechat4u": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/wechat4u/-/wechat4u-0.7.8.tgz", - "integrity": "sha512-2k9FRiYV8G4KM9aHJXIpgqiMAcln5vieXWbBAxlUQY/B9buxgEkRHYH4mZSY3AiIS1X+mFQkzCmG5/GgPLLwkw==", + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/wechat4u/-/wechat4u-0.7.10.tgz", + "integrity": "sha512-nUWN2ypjeuMXOAlU+NlwYdJhyonzCBprC5O9Wms1YwgG/TPhvtmKKkpuaD2JJ5/h2wrqXfs+r/dsemAPuySFAA==", "requires": { "axios": "^1.1.3", "bl": "^1.1.2", @@ -7911,6 +7942,18 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "wechaty-puppet-wechat4u": { + "version": "1.13.14", + "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.13.14.tgz", + "integrity": "sha512-k32aQJHjQdE/Hsj1OE44lle6zSMaSusHAlS+LOmxW5AcTQni4kEGA1UteN3MvlRc6K8INxc2Ol85HwacVob3bA==", + "requires": { + "@alloc/quick-lru": "^5.2.0", + "fast-xml-parser": "^3.21.1", + "promise-retry": "^2.0.1", + "wechat4u": "^0.7.10", + "xml2js": "^0.4.23" + } } } }, @@ -8015,18 +8058,6 @@ } } }, - "wechaty-puppet-wechat4u": { - "version": "1.13.11", - "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.13.11.tgz", - "integrity": "sha512-OM+pXAcEYWcMmoscJLPazn5Ji+8ZFNetyromyzq05crDAM4ra7CQmUkJaQNZHxZZdoioTiBsTCAhDsIpHeOdDA==", - "requires": { - "@alloc/quick-lru": "^5.2.0", - "fast-xml-parser": "^3.21.1", - "promise-retry": "^2.0.1", - "wechat4u": "^0.7.8", - "xml2js": "^0.4.23" - } - }, "wechaty-redux": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/wechaty-redux/-/wechaty-redux-1.20.2.tgz", diff --git a/package.json b/package.json index 1b6ee6f..568aaf6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "async-retry": "^1.3.3", "dotenv": "^16.0.3", "execa": "^6.1.0", + "openai": "^3.2.1", "qrcode": "^1.5.1", "uuid": "^9.0.0", "wechaty": "^1.20.2", diff --git a/src/bot.ts b/src/bot.ts index 8c8b509..16e24f0 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -2,6 +2,8 @@ import { config } from "./config.js"; import { ContactInterface, RoomInterface } from "wechaty/impls"; import { Message } from "wechaty"; import {sendMessage} from "./chatgpt.js"; +import {getCompletion} from "./openai.js"; +import {addSessionByUsername, setPromptByUsername} from "./data.js"; enum MessageType { Unknown = 0, @@ -44,7 +46,36 @@ export class ChatGPTBot { } return regEx } - async command(): Promise {} + async command(talker:any, text:string, privateChat:boolean): Promise { + // 找到第一个空格之前的字符串 + const command = text.split(" ")[0]; + console.log(`command: ${command}`); + switch (command) { + case "help": + await this.trySay(talker,"========\n" + + "/cmd prompt \n" + + "# 设置当前会话的prompt\n" + + "/cmd clear\n" + + "# 清除自上次启动以来的所有会话\n" + + "========"); + break; + case "prompt": + let prompt = text.slice(command.length+1); + console.log(`Prompt: ${prompt}`); + if (privateChat){ + setPromptByUsername(talker.name(), prompt); + await this.trySay(talker,"设置成功"); + }else{ + setPromptByUsername(talker, prompt); + await this.trySay(talker,"设置成功"); + } + break; + case "clear": + console.log("清除会话"); + await this.trySay(talker,"清除成功"); + break; + } + } // remove more times conversation and mention cleanMessage(rawText: string, privateChat: boolean = false): string { let text = rawText; @@ -64,8 +95,10 @@ export class ChatGPTBot { // remove more text via - - - - - - - - - - - - - - - return text } - async getGPTMessage(text: string): Promise { - return await sendMessage(text); + async getGPTMessage(talkerName: string,text: string): Promise { + let gptMessage = await getCompletion(talkerName,text); + addSessionByUsername(talkerName, {assistantMsg:gptMessage}); + return gptMessage; } // Check if the message returned by chatgpt contains masked words] checkChatGPTBlockWords(message: string): boolean { @@ -145,7 +178,7 @@ export class ChatGPTBot { } async onPrivateMessage(talker: ContactInterface, text: string) { - const gptMessage = await this.getGPTMessage(text); + const gptMessage = await this.getGPTMessage(talker.name(),text); await this.trySay(talker, gptMessage); } @@ -154,20 +187,33 @@ export class ChatGPTBot { text: string, room: RoomInterface ) { - const gptMessage = await this.getGPTMessage(text); + const gptMessage = await this.getGPTMessage(talker.name(),text); const result = `@${talker.name()} ${text}\n\n------ ${gptMessage}`; await this.trySay(room, result); } async onMessage(message: Message) { - console.log(`🎯 ${message.date()} Message: ${message}`); const talker = message.talker(); const rawText = message.text(); const room = message.room(); const messageType = message.type(); const privateChat = !room; + if (privateChat) { + console.log(`🤵Contact: ${talker.name()} 💬Text: ${rawText}`) + } else { + const topic = await room.topic() + console.log(`🚪Room: ${topic} 🤵Contact: ${talker.name()} 💬Text: ${rawText}`) + } if (this.isNonsense(talker, messageType, rawText)) { return; } + if (rawText.startsWith("/cmd ")){ + const text = rawText.slice(5) + if (privateChat){ + return await this.command(talker, text, !privateChat); + }else{ + return await this.command(room, text,privateChat); + } + } if (this.triggerGPTMessage(rawText, privateChat)) { const text = this.cleanMessage(rawText, privateChat); if (privateChat) { diff --git a/src/command.ts b/src/command.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/data.ts b/src/data.ts new file mode 100644 index 0000000..282ed6f --- /dev/null +++ b/src/data.ts @@ -0,0 +1,73 @@ +/** + * 使用内存作为数据库 + */ +type session = { + userMsg?: string, + assistantMsg?: string +} +type user = { + username: string, + prompt: string, + session: session[] +} +type data = user[] +// Initialize data +const data: data = [] + +/** + * Add user + * @param username + * @param prompt default: "" + */ +function addUser(username: string, prompt: string = ""): user { + const user = { + username: username, + prompt: prompt, + session: [{ + userMsg: "", + assistantMsg: "" + }] + } + data.push(user) + return data.find(user => user.username === username) as user; +} + +function addSessionByUsername( + username: string, + {userMsg = "", assistantMsg = ""}: session +): void { + const user = getUserByUsername(username) + if (user) { + user.session.push({ + userMsg: userMsg, + assistantMsg: assistantMsg + }) + } +} + +/** + * Get user by username + * @param username + */ +function getUserByUsername(username: string): user | undefined { + let user = data.find(user => user.username === username); + return user +} +function getSessionByUsername(username: string): session[] | undefined { + const user = getUserByUsername(username) + if (user) { + return user.session + } +} +function getAllData(): data { + return data +} +function setPromptByUsername(username: string, prompt: string): void { + const user = getUserByUsername(username) + if (user) { + user.prompt = prompt + }else{ + addUser(username,prompt).prompt= prompt + } +} +export {addUser, addSessionByUsername,getUserByUsername, getSessionByUsername,getAllData,setPromptByUsername} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 2e77740..25899a1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,12 +2,28 @@ import { WechatyBuilder } from "wechaty"; import QRCode from "qrcode"; import { ChatGPTBot } from "./bot.js"; import {config} from "./config.js"; +import {voiceToText} from "./whisper.js"; +import {getAllData} from "./data.js"; + const chatGPTBot = new ChatGPTBot(); const bot = WechatyBuilder.build({ name: "wechat-assistant", // generate xxxx.memory-card.json and save login data for the next login + puppet: "wechaty-puppet-wechat", + puppetOptions: { + uos:true + } }); async function main() { + // 启动一个计时器, 每20秒打印一个数据库 + setInterval(() => { + const data = getAllData() + if (data){ + data.map((item) => { + console.log("item: ",item) + }) + } + }, 20000) const initializedAt = Date.now() bot .on("scan", async (qrcode, status) => { @@ -32,11 +48,47 @@ async function main() { await message.say("pong"); return; } + if (message.type() === bot.Message.Type.Audio) { + console.log("收到语音消息"); + // const fileBox = FileBox.fromFile("/Users/RealTong/Pictures/Snipaste_2022-07-29_17-38-50.png") + // message.say(fileBox) + // const urlLink = new UrlLink({ + // description: 'Wechaty is a Bot SDK for Wechat Individual Account which can help you create a bot in 6 lines of javascript, with cross-platform support including Linux, Windows, Darwin(OSX/Mac) and Docker.', + // thumbnailUrl: 'https://camo.githubusercontent.com/f310a2097d4aa79d6db2962fa42bb3bb2f6d43df/68747470733a2f2f6368617469652e696f2f776563686174792f696d616765732f776563686174792d6c6f676f2d656e2e706e67', + // title: 'Wechaty', + // url: 'https://github.com/wechaty/wechaty', + // }); + // + // await message.say(); + const media = await message.toFileBox(); + const name = media.name; + console.log(`收到语音消息: ${name}`); + media.toFile("/Users/RealTong/Desktop/Medias/"+name, true); + + message.toFileBox().then((fileBox) => { + // 保存文件 + fileBox.toFile("/Users/RealTong/Desktop/Medias/"+fileBox.name, true); + // Whisper + voiceToText("/Users/RealTong/Desktop/Medias/"+fileBox.name).then((text) => { + console.log("语音转文字: ",text); + }) + }) + return; + } + if(message.type() === bot.Message.Type.Post){ + console.log("收到群消息"); + return; + } + + try { await chatGPTBot.onMessage(message); } catch (e) { console.error(e); } + }) + .on("error",()=>{ + console.log("ERROR !!!"); }); try { await bot.start(); diff --git a/src/openai.ts b/src/openai.ts new file mode 100644 index 0000000..40cd0bc --- /dev/null +++ b/src/openai.ts @@ -0,0 +1,64 @@ +import {Configuration, OpenAIApi, ChatCompletionRequestMessageRoleEnum} from "openai"; +import {addSessionByUsername, getUserByUsername} from "./data.js"; +import {ChatCompletionRequestMessage} from "openai/api"; + + +const configuration = new Configuration({ + apiKey: process.env.OPENAI_API_KEY, +}); +const openai = new OpenAIApi(configuration); + +/** + * Get completion from OpenAI + * @param username + * @param message + */ +async function getCompletion(username:string,message: string): Promise { + // 先将用户输入的消息添加到数据库中 + let userData = getUserByUsername(username) + console.log("数据库返回: ", userData) + const messages:ChatCompletionRequestMessage[] = []; + if (userData) { + // 添加用户输入的消息 + console.log(`${username}的session:`, userData.session) + addSessionByUsername(username, {userMsg: message}) + console.log("Database: ", getUserByUsername(username)) + // 填充prompt + if(userData.prompt!==""){ + messages.push({ + role: ChatCompletionRequestMessageRoleEnum.System, + content: userData.prompt + }) + } + // 填充messages + userData.session.map((item) => { + if (item.userMsg!=="") { + messages.push({ + role: ChatCompletionRequestMessageRoleEnum.User, + content: item.userMsg as string + }) + } + if (item.assistantMsg!=="") { + messages.push({ + role: ChatCompletionRequestMessageRoleEnum.Assistant, + content: item.assistantMsg as string + }) + } + }) + }else{ + return "请先执行/cmd prompt命令. \n EXAMPLE: /cmd prompt 你的prompt" + } + console.log("ChatGPT MESSages: ", messages) + const response = await openai.createChatCompletion({ + model: "gpt-3.5-turbo", + messages: messages, + temperature: 0.6 + }).then((res) => res.data); + if (response.choices[0].message) { + return response.choices[0].message.content.replace(/^\n+|\n+$/g, ""); + } else { + return "Something went wrong" + } +} + +export {getCompletion}; \ No newline at end of file diff --git a/src/whisper.ts b/src/whisper.ts new file mode 100644 index 0000000..5835b5b --- /dev/null +++ b/src/whisper.ts @@ -0,0 +1,34 @@ +import {config} from "./config.js"; +// const fs = require('fs'); +import fs from 'fs'; + +let api = config.api; +let apiKey = config.openai_api_key; +const voiceToText = async (path:string) => { + const formData = new FormData(); + formData.append("model", "whisper-1"); + // 根据文件路径读取文件 + const fileContent = fs.readFileSync(path) + // @ts-ignore + formData.append("file",fileContent); + try { + const response = await fetch(`${api}/v1/audio/transcriptions`, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "multipart/form-data", + }, + body: formData + }).then((res) => res.json()); + if (response.error?.message) { + console.log("OpenAI API ERROR: ",response.error.message) + // throw new Error(`OpenAI API ${response.error.message}`); + } + return response.text; + } catch (e) { + console.error(e) + return "Something went wrong" + } +} + +export {voiceToText}; \ No newline at end of file From d046c2bd1f57a1630d717d0e9f2c875d947a8e87 Mon Sep 17 00:00:00 2001 From: RealTong Date: Sat, 18 Mar 2023 12:41:42 +0800 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=BE=A4=E8=81=8A=E6=97=A0=E6=B3=95=E8=AE=BE=E7=BD=AEprompt?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复群聊无法设置prompt的bug --- README.md | 16 +++++++++++++--- README_ZH.md | 16 +++++++++++++--- src/bot.ts | 43 +++++++++++++++++++++++-------------------- src/chatgpt.ts | 37 ------------------------------------- src/command.ts | 0 src/data.ts | 12 +++++++++++- src/main.ts | 44 -------------------------------------------- src/openai.ts | 7 ++----- src/whisper.ts | 34 ---------------------------------- 9 files changed, 62 insertions(+), 147 deletions(-) delete mode 100644 src/chatgpt.ts delete mode 100644 src/command.ts delete mode 100644 src/whisper.ts diff --git a/README.md b/README.md index edb21ac..f785cf2 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,10 @@ - [x] Deploy using [docker compose](#use-with-docker-compose---recommended-) - [x] Add Railway deploy - [x] Add Fly.io deploy -- [x] Supports custom ChatGPT API +- [x] ~~Supports custom ChatGPT API~~ +- [x] Set prompt +- [x] Continuous conversation +- [x] Support command setting - [ ] Support proxy ## 🚀 Usage @@ -81,7 +84,6 @@ flyctl deploy ``` - ## Use with docker ```sh @@ -135,7 +137,7 @@ npm npm dev | name | default | example | description | |------------------------------|------------------------|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| API | https://api.openai.com | | API endpoint of ChatGPT | +| ~~API~~ | https://api.openai.com | | ~~API endpoint of ChatGPT~~ | | OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [create new secret key](https://platform.openai.com/account/api-keys) | | MODEL | gpt-3.5-turbo | | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. | | TEMPERATURE | 0.6 | | What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | @@ -163,6 +165,14 @@ routes = [ ] ``` +## ⌨️ Commands +> Enter in the WeChat chat box +```shell +/cmd help # Show help +/cmd prompt # Set prompt +/cmd clear # Clear all sessions since last boot +``` + ## ✨ Contributor
diff --git a/README_ZH.md b/README_ZH.md index 335d4e0..c6157e3 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -27,7 +27,10 @@ - [x] 使用[docker compose](#通过docker-compose使用-推荐)进行部署 - [x] 通过 Railway 进行部署 - [x] 通过 Fly.io 进行部署 -- [x] 支持自定义ChatGPT API +- [x] ~~支持自定义ChatGPT API~~ +- [x] 支持设置prompt +- [x] 支持连续对话 +- [x] 支持命令设置 - [ ] 支持代理 ## 🚀 使用 @@ -139,7 +142,7 @@ npm run dev | name | default | example | description | |------------------------------|------------------------|------------------------------------------------|-------------------------------------------------------------| -| API | https://api.openai.com | ChatGPT API 地址 | +| ~~API~~ | https://api.openai.com | | ~~ChatGPT API 地址~~ | | OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [创建你的 API 密钥](https://platform.openai.com/account/api-keys) | | MODEL | gpt-3.5-turbo | | 要使用的模型ID, 目前仅支持`gpt-3.5-turbo` 和 `gpt-3.5-turbo-0301` | | TEMPERATURE | 0.6 | | 在0和2之间。较高的数值如0.8会使 ChatGPT 输出更加随机,而较低的数值如0.2会使其更加稳定。 | @@ -149,7 +152,6 @@ npm run dev | BLOCK_WORDS | | "WORD1,WORD2,WORD3" | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号 | | CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复 | - ## 📝 使用自定义ChatGPT API > https://github.com/fuergaosi233/openai-proxy ```shell @@ -166,6 +168,14 @@ routes = [ ] ``` +## ⌨️ 命令 +> 在微信聊天框中输入 +```shell +/cmd help # 显示帮助信息 +/cmd prompt # 设置ChatGPT Prompt +/cmd clear # 清除WeChat-ChatGPT保存的会话记录 +``` + ## ✨ Contributor diff --git a/src/bot.ts b/src/bot.ts index 16e24f0..01b21a4 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,9 +1,8 @@ import { config } from "./config.js"; -import { ContactInterface, RoomInterface } from "wechaty/impls"; +import {ContactImpl, ContactInterface, RoomImpl, RoomInterface} from "wechaty/impls"; import { Message } from "wechaty"; -import {sendMessage} from "./chatgpt.js"; import {getCompletion} from "./openai.js"; -import {addSessionByUsername, setPromptByUsername} from "./data.js"; +import {addSessionByUsername, clearUserData, setPromptByUsername} from "./data.js"; enum MessageType { Unknown = 0, @@ -46,13 +45,15 @@ export class ChatGPTBot { } return regEx } - async command(talker:any, text:string, privateChat:boolean): Promise { + async command(talker:RoomInterface|ContactInterface, text:string): Promise { // 找到第一个空格之前的字符串 const command = text.split(" ")[0]; console.log(`command: ${command}`); switch (command) { case "help": await this.trySay(talker,"========\n" + + "/cmd help\n" + + "# 显示帮助信息\n" + "/cmd prompt \n" + "# 设置当前会话的prompt\n" + "/cmd clear\n" + @@ -61,18 +62,23 @@ export class ChatGPTBot { break; case "prompt": let prompt = text.slice(command.length+1); - console.log(`Prompt: ${prompt}`); - if (privateChat){ + if (talker instanceof RoomImpl) { + setPromptByUsername(talker.id, prompt); + await this.trySay(talker,"设置成功!"); + }else if (talker instanceof ContactImpl) { setPromptByUsername(talker.name(), prompt); await this.trySay(talker,"设置成功"); - }else{ - setPromptByUsername(talker, prompt); - await this.trySay(talker,"设置成功"); } break; case "clear": console.log("清除会话"); - await this.trySay(talker,"清除成功"); + if (talker instanceof RoomImpl) { + clearUserData(talker.id); + await this.trySay(talker,"清除成功!"); + }else if (talker instanceof ContactImpl) { + clearUserData(talker.name()); + await this.trySay(talker,"清除成功"); + } break; } } @@ -187,8 +193,8 @@ export class ChatGPTBot { text: string, room: RoomInterface ) { - const gptMessage = await this.getGPTMessage(talker.name(),text); - const result = `@${talker.name()} ${text}\n\n------ ${gptMessage}`; + const gptMessage = await this.getGPTMessage(room.id,text); + const result = `@${talker.name()} ${text}\n\n------\n ${gptMessage}`; await this.trySay(room, result); } async onMessage(message: Message) { @@ -198,21 +204,18 @@ export class ChatGPTBot { const messageType = message.type(); const privateChat = !room; if (privateChat) { - console.log(`🤵Contact: ${talker.name()} 💬Text: ${rawText}`) + console.log(`🤵 Contact: ${talker.name()} 💬 Text: ${rawText}`) } else { const topic = await room.topic() - console.log(`🚪Room: ${topic} 🤵Contact: ${talker.name()} 💬Text: ${rawText}`) + console.log(`🚪 Room: ${topic} 🤵 Contact: ${talker.name()} 💬 Text: ${rawText}`) } if (this.isNonsense(talker, messageType, rawText)) { return; } if (rawText.startsWith("/cmd ")){ - const text = rawText.slice(5) - if (privateChat){ - return await this.command(talker, text, !privateChat); - }else{ - return await this.command(room, text,privateChat); - } + console.log(`🤖 Command: ${rawText}`) + const text = rawText.slice(5) // 「/cmd 」一共5个字符(注意空格) + return await this.command(privateChat?talker:room, text); } if (this.triggerGPTMessage(rawText, privateChat)) { const text = this.cleanMessage(rawText, privateChat); diff --git a/src/chatgpt.ts b/src/chatgpt.ts deleted file mode 100644 index c906ecf..0000000 --- a/src/chatgpt.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {config} from "./config.js"; - -let api = config.api; -let apiKey = config.openai_api_key; -let model = config.model; -let temperature = config.temperature; -const sendMessage = async (message: string) => { - try { - const response = await fetch(`${api}/v1/chat/completions`, { - method: "POST", - headers: { - Authorization: `Bearer ${apiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: model, - messages: [ - { - "role": "user", - "content": message - } - ], - temperature: temperature - }), - }).then((res) => res.json()); - if (response.error?.message) { - console.log("OpenAI API ERROR: ",response.error.message) - // throw new Error(`OpenAI API ${response.error.message}`); - } - return response.choices[0].message.content; - } catch (e) { - console.error(e) - return "Something went wrong" - } -} - -export {sendMessage}; \ No newline at end of file diff --git a/src/command.ts b/src/command.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/data.ts b/src/data.ts index 282ed6f..4ed0c27 100644 --- a/src/data.ts +++ b/src/data.ts @@ -70,4 +70,14 @@ function setPromptByUsername(username: string, prompt: string): void { addUser(username,prompt).prompt= prompt } } -export {addUser, addSessionByUsername,getUserByUsername, getSessionByUsername,getAllData,setPromptByUsername} \ No newline at end of file +function clearUserData(username: string): void { + const user = getUserByUsername(username) + if (user) { + user.prompt = "" + user.session = [{ + userMsg: "", + assistantMsg: "" + }] + } +} +export {addUser, addSessionByUsername,getUserByUsername, getSessionByUsername,getAllData,setPromptByUsername,clearUserData} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 25899a1..3d1eadf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,8 +2,6 @@ import { WechatyBuilder } from "wechaty"; import QRCode from "qrcode"; import { ChatGPTBot } from "./bot.js"; import {config} from "./config.js"; -import {voiceToText} from "./whisper.js"; -import {getAllData} from "./data.js"; const chatGPTBot = new ChatGPTBot(); @@ -15,15 +13,6 @@ const bot = WechatyBuilder.build({ } }); async function main() { - // 启动一个计时器, 每20秒打印一个数据库 - setInterval(() => { - const data = getAllData() - if (data){ - data.map((item) => { - console.log("item: ",item) - }) - } - }, 20000) const initializedAt = Date.now() bot .on("scan", async (qrcode, status) => { @@ -48,39 +37,6 @@ async function main() { await message.say("pong"); return; } - if (message.type() === bot.Message.Type.Audio) { - console.log("收到语音消息"); - // const fileBox = FileBox.fromFile("/Users/RealTong/Pictures/Snipaste_2022-07-29_17-38-50.png") - // message.say(fileBox) - // const urlLink = new UrlLink({ - // description: 'Wechaty is a Bot SDK for Wechat Individual Account which can help you create a bot in 6 lines of javascript, with cross-platform support including Linux, Windows, Darwin(OSX/Mac) and Docker.', - // thumbnailUrl: 'https://camo.githubusercontent.com/f310a2097d4aa79d6db2962fa42bb3bb2f6d43df/68747470733a2f2f6368617469652e696f2f776563686174792f696d616765732f776563686174792d6c6f676f2d656e2e706e67', - // title: 'Wechaty', - // url: 'https://github.com/wechaty/wechaty', - // }); - // - // await message.say(); - const media = await message.toFileBox(); - const name = media.name; - console.log(`收到语音消息: ${name}`); - media.toFile("/Users/RealTong/Desktop/Medias/"+name, true); - - message.toFileBox().then((fileBox) => { - // 保存文件 - fileBox.toFile("/Users/RealTong/Desktop/Medias/"+fileBox.name, true); - // Whisper - voiceToText("/Users/RealTong/Desktop/Medias/"+fileBox.name).then((text) => { - console.log("语音转文字: ",text); - }) - }) - return; - } - if(message.type() === bot.Message.Type.Post){ - console.log("收到群消息"); - return; - } - - try { await chatGPTBot.onMessage(message); } catch (e) { diff --git a/src/openai.ts b/src/openai.ts index 40cd0bc..4352b22 100644 --- a/src/openai.ts +++ b/src/openai.ts @@ -16,21 +16,18 @@ const openai = new OpenAIApi(configuration); async function getCompletion(username:string,message: string): Promise { // 先将用户输入的消息添加到数据库中 let userData = getUserByUsername(username) - console.log("数据库返回: ", userData) const messages:ChatCompletionRequestMessage[] = []; if (userData) { // 添加用户输入的消息 - console.log(`${username}的session:`, userData.session) addSessionByUsername(username, {userMsg: message}) - console.log("Database: ", getUserByUsername(username)) - // 填充prompt + // fill prompt if(userData.prompt!==""){ messages.push({ role: ChatCompletionRequestMessageRoleEnum.System, content: userData.prompt }) } - // 填充messages + // fill messages userData.session.map((item) => { if (item.userMsg!=="") { messages.push({ diff --git a/src/whisper.ts b/src/whisper.ts deleted file mode 100644 index 5835b5b..0000000 --- a/src/whisper.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {config} from "./config.js"; -// const fs = require('fs'); -import fs from 'fs'; - -let api = config.api; -let apiKey = config.openai_api_key; -const voiceToText = async (path:string) => { - const formData = new FormData(); - formData.append("model", "whisper-1"); - // 根据文件路径读取文件 - const fileContent = fs.readFileSync(path) - // @ts-ignore - formData.append("file",fileContent); - try { - const response = await fetch(`${api}/v1/audio/transcriptions`, { - method: "POST", - headers: { - Authorization: `Bearer ${apiKey}`, - "Content-Type": "multipart/form-data", - }, - body: formData - }).then((res) => res.json()); - if (response.error?.message) { - console.log("OpenAI API ERROR: ",response.error.message) - // throw new Error(`OpenAI API ${response.error.message}`); - } - return response.text; - } catch (e) { - console.error(e) - return "Something went wrong" - } -} - -export {voiceToText}; \ No newline at end of file From d10c880abb08c613aad13a703d5bab62dbdd68e4 Mon Sep 17 00:00:00 2001 From: RealTong Date: Sat, 18 Mar 2023 21:30:44 +0800 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Fix=20docker=20star?= =?UTF-8?q?t=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix docker start error --- package-lock.json | 30 +++++++++++++++++++++++++++++- package.json | 3 ++- src/main.ts | 4 ---- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index df01a76..2d13995 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "qrcode": "^1.5.1", "uuid": "^9.0.0", "wechaty": "^1.20.2", - "wechaty-puppet-wechat": "^1.18.4" + "wechaty-puppet-wechat": "^1.18.4", + "wechaty-puppet-wechat4u": "^1.10.2" }, "devDependencies": { "@types/async-retry": "^1.4.5", @@ -4427,6 +4428,23 @@ "brolog": "^1.3.3" } }, + "node_modules/wechaty-puppet-wechat4u": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.10.2.tgz", + "integrity": "sha512-ufFZUQj/fWDvVPzm4Bp4cSsuSkQX+MaBqNnry8PrOaRlJqZ9jfSSKKjaf3Oqams9IhE2JywUDmTcK+e+0EscoA==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "promise-retry": "^2.0.1", + "wechat4u": "^0.7.7" + }, + "engines": { + "node": ">=16", + "npm": ">=7" + }, + "peerDependencies": { + "wechaty-puppet": "^1.10.2" + } + }, "node_modules/wechaty-puppet/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -8058,6 +8076,16 @@ } } }, + "wechaty-puppet-wechat4u": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.10.2.tgz", + "integrity": "sha512-ufFZUQj/fWDvVPzm4Bp4cSsuSkQX+MaBqNnry8PrOaRlJqZ9jfSSKKjaf3Oqams9IhE2JywUDmTcK+e+0EscoA==", + "requires": { + "@alloc/quick-lru": "^5.2.0", + "promise-retry": "^2.0.1", + "wechat4u": "^0.7.7" + } + }, "wechaty-redux": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/wechaty-redux/-/wechaty-redux-1.20.2.tgz", diff --git a/package.json b/package.json index 568aaf6..4ecbac5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "qrcode": "^1.5.1", "uuid": "^9.0.0", "wechaty": "^1.20.2", - "wechaty-puppet-wechat": "^1.18.4" + "wechaty-puppet-wechat": "^1.18.4", + "wechaty-puppet-wechat4u": "^1.10.2" }, "devDependencies": { "@types/async-retry": "^1.4.5", diff --git a/src/main.ts b/src/main.ts index 3d1eadf..555d495 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,10 +7,6 @@ const chatGPTBot = new ChatGPTBot(); const bot = WechatyBuilder.build({ name: "wechat-assistant", // generate xxxx.memory-card.json and save login data for the next login - puppet: "wechaty-puppet-wechat", - puppetOptions: { - uos:true - } }); async function main() { const initializedAt = Date.now() From 3101b11e3640d345caf6be10f1eee3d037fe3389 Mon Sep 17 00:00:00 2001 From: RealTong Date: Sun, 19 Mar 2023 13:04:04 +0800 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=8F=AF=E8=83=BD=E4=BB=A5wechaty-puppet-wechat4u=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复可能以wechaty-puppet-wechat4u启动的bug --- .env.example | 3 ++- package-lock.json | 30 +----------------------------- package.json | 10 +++++++--- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/.env.example b/.env.example index f7d22c4..c21aba9 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,5 @@ MODEL="gpt-3.5-turbo" CHAT_PRIVATE_TRIGGER_KEYWORD= TEMPERATURE= BLOCK_WORDS="SB, VPN" -CHATGPT_BLOCK_WORDS="VPN" \ No newline at end of file +CHATGPT_BLOCK_WORDS="VPN" +WECHATY_PUPPET=wechaty-puppet-wechat \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2d13995..df01a76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,7 @@ "qrcode": "^1.5.1", "uuid": "^9.0.0", "wechaty": "^1.20.2", - "wechaty-puppet-wechat": "^1.18.4", - "wechaty-puppet-wechat4u": "^1.10.2" + "wechaty-puppet-wechat": "^1.18.4" }, "devDependencies": { "@types/async-retry": "^1.4.5", @@ -4428,23 +4427,6 @@ "brolog": "^1.3.3" } }, - "node_modules/wechaty-puppet-wechat4u": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.10.2.tgz", - "integrity": "sha512-ufFZUQj/fWDvVPzm4Bp4cSsuSkQX+MaBqNnry8PrOaRlJqZ9jfSSKKjaf3Oqams9IhE2JywUDmTcK+e+0EscoA==", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "promise-retry": "^2.0.1", - "wechat4u": "^0.7.7" - }, - "engines": { - "node": ">=16", - "npm": ">=7" - }, - "peerDependencies": { - "wechaty-puppet": "^1.10.2" - } - }, "node_modules/wechaty-puppet/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -8076,16 +8058,6 @@ } } }, - "wechaty-puppet-wechat4u": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/wechaty-puppet-wechat4u/-/wechaty-puppet-wechat4u-1.10.2.tgz", - "integrity": "sha512-ufFZUQj/fWDvVPzm4Bp4cSsuSkQX+MaBqNnry8PrOaRlJqZ9jfSSKKjaf3Oqams9IhE2JywUDmTcK+e+0EscoA==", - "requires": { - "@alloc/quick-lru": "^5.2.0", - "promise-retry": "^2.0.1", - "wechat4u": "^0.7.7" - } - }, "wechaty-redux": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/wechaty-redux/-/wechaty-redux-1.20.2.tgz", diff --git a/package.json b/package.json index 4ecbac5..1115578 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,7 @@ "qrcode": "^1.5.1", "uuid": "^9.0.0", "wechaty": "^1.20.2", - "wechaty-puppet-wechat": "^1.18.4", - "wechaty-puppet-wechat4u": "^1.10.2" + "wechaty-puppet-wechat": "^1.18.4" }, "devDependencies": { "@types/async-retry": "^1.4.5", @@ -29,7 +28,12 @@ "ts-node": "^10.9.1" }, "nodemonConfig": { - "watch": "src", + "watch": [ + "src/*.ts" + ], + "ignore": [ + "src/main.ts" + ], "ext": "ts", "exec": "node --loader ts-node/esm src/main.ts", "delay": 500 From 8b369b14457406dcbe1d521cffa9b78136c7fabf Mon Sep 17 00:00:00 2001 From: RealTong Date: Sun, 19 Mar 2023 21:26:08 +0800 Subject: [PATCH 08/12] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20Refactor=20the?= =?UTF-8?q?=20continuous=20dialogue=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the continuous dialogue section --- src/bot.ts | 88 +++++++++++++++-------- src/data.ts | 180 ++++++++++++++++++++++++++++------------------- src/interface.ts | 6 ++ src/main.ts | 11 +-- src/openai.ts | 39 ++-------- 5 files changed, 184 insertions(+), 140 deletions(-) diff --git a/src/bot.ts b/src/bot.ts index 01b21a4..cd72fec 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -2,10 +2,9 @@ import { config } from "./config.js"; import {ContactImpl, ContactInterface, RoomImpl, RoomInterface} from "wechaty/impls"; import { Message } from "wechaty"; import {getCompletion} from "./openai.js"; -import {addSessionByUsername, clearUserData, setPromptByUsername} from "./data.js"; +import DBUtils from "./data.js"; enum MessageType { Unknown = 0, - Attachment = 1, // Attach(6), Audio = 2, // Audio(1), Voice(34) Contact = 3, // ShareCard(42) @@ -23,8 +22,13 @@ enum MessageType { Video = 15, // Video(4), Video(43) Post = 16, // Moment, Channel, Tweet, etc } - const SINGLE_MESSAGE_MAX_SIZE = 500; +type Speaker = RoomImpl | ContactImpl; +interface ICommand{ + name:string; + description:string; + exec: (talker:Speaker, text:string) => Promise; +} export class ChatGPTBot { chatPrivateTriggerKeyword = config.chatPrivateTriggerKeyword; chatTriggerRule = config.chatTriggerRule? new RegExp(config.chatTriggerRule): undefined; @@ -45,12 +49,11 @@ export class ChatGPTBot { } return regEx } - async command(talker:RoomInterface|ContactInterface, text:string): Promise { - // 找到第一个空格之前的字符串 - const command = text.split(" ")[0]; - console.log(`command: ${command}`); - switch (command) { - case "help": + private readonly commands:ICommand[] = [ + { + name: "help", + description: "显示帮助信息", + exec: async (talker) => { await this.trySay(talker,"========\n" + "/cmd help\n" + "# 显示帮助信息\n" + @@ -59,27 +62,47 @@ export class ChatGPTBot { "/cmd clear\n" + "# 清除自上次启动以来的所有会话\n" + "========"); - break; - case "prompt": - let prompt = text.slice(command.length+1); + } + }, + { + name: "prompt", + description: "设置当前会话的prompt", + exec: async (talker, prompt) => { if (talker instanceof RoomImpl) { - setPromptByUsername(talker.id, prompt); - await this.trySay(talker,"设置成功!"); - }else if (talker instanceof ContactImpl) { - setPromptByUsername(talker.name(), prompt); - await this.trySay(talker,"设置成功"); + DBUtils.setPrompt(await talker.topic(), prompt); + }else { + DBUtils.setPrompt(talker.name(), prompt); } - break; - case "clear": - console.log("清除会话"); + } + }, + { + name: "clear", + description: "清除自上次启动以来的所有会话", + exec: async (talker) => { if (talker instanceof RoomImpl) { - clearUserData(talker.id); - await this.trySay(talker,"清除成功!"); - }else if (talker instanceof ContactImpl) { - clearUserData(talker.name()); - await this.trySay(talker,"清除成功"); + DBUtils.clearHistory(await talker.topic()); + }else{ + DBUtils.clearHistory(talker.name()); } - break; + } + } + ] + + /** + * EXAMPLE: + * /cmd help + * /cmd prompt + * /cmd clear + * @param contact + * @param rawText + */ + async command(contact: any, rawText: string): Promise { + const [commandName, ...args] = rawText.split(/\s+/); + const command = this.commands.find( + (command) => command.name === commandName + ); + if (command) { + await command.exec(contact, args.join(" ")); } } // remove more times conversation and mention @@ -103,7 +126,7 @@ export class ChatGPTBot { } async getGPTMessage(talkerName: string,text: string): Promise { let gptMessage = await getCompletion(talkerName,text); - addSessionByUsername(talkerName, {assistantMsg:gptMessage}); + DBUtils.addAssistantMessage(talkerName,gptMessage); return gptMessage; } // Check if the message returned by chatgpt contains masked words] @@ -193,7 +216,7 @@ export class ChatGPTBot { text: string, room: RoomInterface ) { - const gptMessage = await this.getGPTMessage(room.id,text); + const gptMessage = await this.getGPTMessage(await room.topic(),text); const result = `@${talker.name()} ${text}\n\n------\n ${gptMessage}`; await this.trySay(room, result); } @@ -214,8 +237,13 @@ export class ChatGPTBot { } if (rawText.startsWith("/cmd ")){ console.log(`🤖 Command: ${rawText}`) - const text = rawText.slice(5) // 「/cmd 」一共5个字符(注意空格) - return await this.command(privateChat?talker:room, text); + const cmdContent = rawText.slice(5) // 「/cmd 」一共5个字符(注意空格) + if (privateChat) { + await this.command(talker, cmdContent); + }else{ + await this.command(room, cmdContent); + } + return; } if (this.triggerGPTMessage(rawText, privateChat)) { const text = this.cleanMessage(rawText, privateChat); diff --git a/src/data.ts b/src/data.ts index 4ed0c27..9358f2a 100644 --- a/src/data.ts +++ b/src/data.ts @@ -1,83 +1,121 @@ +import {ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum} from "openai"; +import {User} from "./interface"; + /** * 使用内存作为数据库 */ -type session = { - userMsg?: string, - assistantMsg?: string -} -type user = { - username: string, - prompt: string, - session: session[] -} -type data = user[] -// Initialize data -const data: data = [] +export const initState: Array = new Array( + { + "role": ChatCompletionRequestMessageRoleEnum.System, + "content": "You are a helpful assistant." + } +) -/** - * Add user - * @param username - * @param prompt default: "" - */ -function addUser(username: string, prompt: string = ""): user { - const user = { - username: username, - prompt: prompt, - session: [{ - userMsg: "", - assistantMsg: "" - }] +class DB { + private static data: User[] = []; + + /** + * 添加一个用户, 如果用户已存在则返回已存在的用户 + * @param username + */ + public addUser(username: string): User { + let existUser = DB.data.find((user) => user.username === username); + if (existUser) { + console.log(`用户${username}已存在`); + return existUser; + } + const newUser: User = { + username: username, + chatMessage: [ + { + role: ChatCompletionRequestMessageRoleEnum.System, + content: "You are a helpful assistant." + } + ], + }; + DB.data.push(newUser); + return newUser; } - data.push(user) - return data.find(user => user.username === username) as user; -} -function addSessionByUsername( - username: string, - {userMsg = "", assistantMsg = ""}: session -): void { - const user = getUserByUsername(username) - if (user) { - user.session.push({ - userMsg: userMsg, - assistantMsg: assistantMsg - }) + /** + * 根据用户名获取用户, 如果用户不存在则添加用户 + * @param username + */ + public getUserByUsername(username: string): User { + return DB.data.find((user) => user.username === username) || this.addUser(username); } -} -/** - * Get user by username - * @param username - */ -function getUserByUsername(username: string): user | undefined { - let user = data.find(user => user.username === username); - return user -} -function getSessionByUsername(username: string): session[] | undefined { - const user = getUserByUsername(username) - if (user) { - return user.session + /** + * 获取用户的聊天记录 + * @param username + */ + public getChatMessage(username: string): Array { + return this.getUserByUsername(username).chatMessage; } -} -function getAllData(): data { - return data -} -function setPromptByUsername(username: string, prompt: string): void { - const user = getUserByUsername(username) - if (user) { - user.prompt = prompt - }else{ - addUser(username,prompt).prompt= prompt + + /** + * 设置用户的prompt + * @param username + * @param prompt + */ + public setPrompt(username: string, prompt: string): void { + const user = this.getUserByUsername(username); + if (user) { + user.chatMessage.find( + (msg) => msg.role === ChatCompletionRequestMessageRoleEnum.System + )!.content = prompt; + } } -} -function clearUserData(username: string): void { - const user = getUserByUsername(username) - if (user) { - user.prompt = "" - user.session = [{ - userMsg: "", - assistantMsg: "" - }] + + /** + * 添加用户输入的消息 + * @param username + * @param message + */ + public addUserMessage(username: string, message: string): void { + const user = this.getUserByUsername(username); + if (user) { + user.chatMessage.push({ + role: ChatCompletionRequestMessageRoleEnum.User, + content: message, + }); + } + } + + /** + * 添加ChatGPT的回复 + * @param username + * @param message + */ + public addAssistantMessage(username: string, message: string): void { + const user = this.getUserByUsername(username); + if (user) { + user.chatMessage.push({ + role: ChatCompletionRequestMessageRoleEnum.Assistant, + content: message, + }); + } + } + + /** + * 清空用户的聊天记录, 并将prompt设置为默认值 + * @param username + */ + public clearHistory(username: string): void { + const user = this.getUserByUsername(username); + if (user) { + user.chatMessage = [ + { + role: ChatCompletionRequestMessageRoleEnum.System, + content: "You are a helpful assistant." + } + ]; + } + } + + public getAllData(): User[] { + return DB.data; } } -export {addUser, addSessionByUsername,getUserByUsername, getSessionByUsername,getAllData,setPromptByUsername,clearUserData} \ No newline at end of file +const DBUtils = new DB(); +export default DBUtils; \ No newline at end of file diff --git a/src/interface.ts b/src/interface.ts index 0b2cc9a..bbcb3bc 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,3 +1,5 @@ +import {ChatCompletionRequestMessage} from "openai"; + export interface IConfig { api: string; openai_api_key: string; @@ -9,3 +11,7 @@ export interface IConfig { chatgptBlockWords: string[]; chatPrivateTriggerKeyword: string; } +export interface User { + username: string, + chatMessage: Array, +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 555d495..dce43ca 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,11 +2,14 @@ import { WechatyBuilder } from "wechaty"; import QRCode from "qrcode"; import { ChatGPTBot } from "./bot.js"; import {config} from "./config.js"; - const chatGPTBot = new ChatGPTBot(); const bot = WechatyBuilder.build({ name: "wechat-assistant", // generate xxxx.memory-card.json and save login data for the next login + puppet: "wechaty-puppet-wechat", // generate xxxx.memory-card.json and save login data for the next login + puppetOptions: { + uos: true + } }); async function main() { const initializedAt = Date.now() @@ -39,8 +42,8 @@ async function main() { console.error(e); } }) - .on("error",()=>{ - console.log("ERROR !!!"); + .on("error",(e)=>{ + console.log(`ERROR !!! ${e}`); }); try { await bot.start(); @@ -50,4 +53,4 @@ async function main() { ); } } -main(); +main(); \ No newline at end of file diff --git a/src/openai.ts b/src/openai.ts index 4352b22..d62abf0 100644 --- a/src/openai.ts +++ b/src/openai.ts @@ -1,7 +1,5 @@ -import {Configuration, OpenAIApi, ChatCompletionRequestMessageRoleEnum} from "openai"; -import {addSessionByUsername, getUserByUsername} from "./data.js"; -import {ChatCompletionRequestMessage} from "openai/api"; - +import {Configuration, OpenAIApi} from "openai"; +import DBUtils from "./data.js"; const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, @@ -15,37 +13,8 @@ const openai = new OpenAIApi(configuration); */ async function getCompletion(username:string,message: string): Promise { // 先将用户输入的消息添加到数据库中 - let userData = getUserByUsername(username) - const messages:ChatCompletionRequestMessage[] = []; - if (userData) { - // 添加用户输入的消息 - addSessionByUsername(username, {userMsg: message}) - // fill prompt - if(userData.prompt!==""){ - messages.push({ - role: ChatCompletionRequestMessageRoleEnum.System, - content: userData.prompt - }) - } - // fill messages - userData.session.map((item) => { - if (item.userMsg!=="") { - messages.push({ - role: ChatCompletionRequestMessageRoleEnum.User, - content: item.userMsg as string - }) - } - if (item.assistantMsg!=="") { - messages.push({ - role: ChatCompletionRequestMessageRoleEnum.Assistant, - content: item.assistantMsg as string - }) - } - }) - }else{ - return "请先执行/cmd prompt命令. \n EXAMPLE: /cmd prompt 你的prompt" - } - console.log("ChatGPT MESSages: ", messages) + DBUtils.addUserMessage(username, message); + const messages = DBUtils.getChatMessage(username); const response = await openai.createChatCompletion({ model: "gpt-3.5-turbo", messages: messages, From a465b6bda86f26bfb6d29c6c05b4a766239391d7 Mon Sep 17 00:00:00 2001 From: RealTong Date: Sun, 19 Mar 2023 22:22:30 +0800 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20dalle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add dalle --- src/bot.ts | 20 ++++++++++++++++++-- src/openai.ts | 23 ++++++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/bot.ts b/src/bot.ts index cd72fec..b43e639 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,7 +1,8 @@ import { config } from "./config.js"; import {ContactImpl, ContactInterface, RoomImpl, RoomInterface} from "wechaty/impls"; import { Message } from "wechaty"; -import {getCompletion} from "./openai.js"; +import {FileBox} from "file-box"; +import {chatgpt, dalle} from "./openai.js"; import DBUtils from "./data.js"; enum MessageType { Unknown = 0, @@ -125,7 +126,7 @@ export class ChatGPTBot { return text } async getGPTMessage(talkerName: string,text: string): Promise { - let gptMessage = await getCompletion(talkerName,text); + let gptMessage = await chatgpt(talkerName,text); DBUtils.addAssistantMessage(talkerName,gptMessage); return gptMessage; } @@ -245,6 +246,21 @@ export class ChatGPTBot { } return; } + // 使用DallE生成图片 + if (rawText.startsWith("/img")){ + console.log(`🤖 Image: ${rawText}`) + const imgContent = rawText.slice(4) + if (privateChat) { + let url = await dalle(talker.name(), imgContent) as string; + const fileBox = FileBox.fromUrl(url) + message.say(fileBox) + }else{ + let url = await dalle(await room.topic(), imgContent) as string; + const fileBox = FileBox.fromUrl(url) + message.say(fileBox) + } + return; + } if (this.triggerGPTMessage(rawText, privateChat)) { const text = this.cleanMessage(rawText, privateChat); if (privateChat) { diff --git a/src/openai.ts b/src/openai.ts index d62abf0..d0e3837 100644 --- a/src/openai.ts +++ b/src/openai.ts @@ -1,4 +1,4 @@ -import {Configuration, OpenAIApi} from "openai"; +import {Configuration, CreateImageRequestResponseFormatEnum, CreateImageRequestSizeEnum, OpenAIApi} from "openai"; import DBUtils from "./data.js"; const configuration = new Configuration({ @@ -11,7 +11,7 @@ const openai = new OpenAIApi(configuration); * @param username * @param message */ -async function getCompletion(username:string,message: string): Promise { +async function chatgpt(username:string,message: string): Promise { // 先将用户输入的消息添加到数据库中 DBUtils.addUserMessage(username, message); const messages = DBUtils.getChatMessage(username); @@ -27,4 +27,21 @@ async function getCompletion(username:string,message: string): Promise { } } -export {getCompletion}; \ No newline at end of file +// const response = await openai.createImage({ +// prompt: "a white siamese cat", +// n: 1, +// size: "1024x1024", +// }); +// image_url = response.data.data[0].url; +function dalle(username:string,prompt: string) { + const response = openai.createImage({ + prompt: prompt, + n:1, + size: CreateImageRequestSizeEnum._256x256, + response_format: CreateImageRequestResponseFormatEnum.Url, + user: username + }).then((res) => res.data); + return response.then((res) => res.data[0].url); +} + +export {chatgpt,dalle}; \ No newline at end of file From 4d079a0a3320a9af7227723749d9cafa69edc697 Mon Sep 17 00:00:00 2001 From: RealTong Date: Mon, 20 Mar 2023 12:25:42 +0800 Subject: [PATCH 10/12] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Fix=20the=20bug=20t?= =?UTF-8?q?hat=20docker=20failed=20to=20start?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the bug that docker failed to start --- Dockerfile | 2 +- src/main.ts | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5dcdaea..2559cdd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,5 +11,5 @@ WORKDIR /app COPY package*.json ./ RUN npm install COPY . . -ENV WECHATY_PUPPET_WECHAT_ENDPOINT=/usr/bin/google-chrome +#ENV WECHATY_PUPPET_WECHAT_ENDPOINT=/usr/bin/google-chrome CMD xvfb-run --server-args="-screen 0 1280x800x24 -ac -nolisten tcp -dpi 96 +extension RANDR" npm run dev \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index dce43ca..2e77740 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,10 +6,6 @@ const chatGPTBot = new ChatGPTBot(); const bot = WechatyBuilder.build({ name: "wechat-assistant", // generate xxxx.memory-card.json and save login data for the next login - puppet: "wechaty-puppet-wechat", // generate xxxx.memory-card.json and save login data for the next login - puppetOptions: { - uos: true - } }); async function main() { const initializedAt = Date.now() @@ -41,9 +37,6 @@ async function main() { } catch (e) { console.error(e); } - }) - .on("error",(e)=>{ - console.log(`ERROR !!! ${e}`); }); try { await bot.start(); @@ -53,4 +46,4 @@ async function main() { ); } } -main(); \ No newline at end of file +main(); From a7c329b7e556a0c636f67fb368455dddb7188128 Mon Sep 17 00:00:00 2001 From: RealTong Date: Tue, 21 Mar 2023 15:00:00 +0800 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Add=20whisper=20su?= =?UTF-8?q?pport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add whisper support --- .gitignore | 4 +++- src/bot.ts | 23 ++++++++++++++++++--- src/main.ts | 4 ++++ src/openai.ts | 55 +++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 557d87e..b9bf941 100644 --- a/.gitignore +++ b/.gitignore @@ -321,4 +321,6 @@ cache.json config.yaml .vscode -data/ \ No newline at end of file +data/ + +public/ \ No newline at end of file diff --git a/src/bot.ts b/src/bot.ts index b43e639..5792972 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -2,7 +2,7 @@ import { config } from "./config.js"; import {ContactImpl, ContactInterface, RoomImpl, RoomInterface} from "wechaty/impls"; import { Message } from "wechaty"; import {FileBox} from "file-box"; -import {chatgpt, dalle} from "./openai.js"; +import {chatgpt, dalle, whisper} from "./openai.js"; import DBUtils from "./data.js"; enum MessageType { Unknown = 0, @@ -59,7 +59,9 @@ export class ChatGPTBot { "/cmd help\n" + "# 显示帮助信息\n" + "/cmd prompt \n" + - "# 设置当前会话的prompt\n" + + "# 设置当前会话的 prompt \n" + + "/cmd img \n" + + "# 根据 prompt 生成图片\n" + "/cmd clear\n" + "# 清除自上次启动以来的所有会话\n" + "========"); @@ -93,6 +95,7 @@ export class ChatGPTBot { * EXAMPLE: * /cmd help * /cmd prompt + * /cmd img * /cmd clear * @param contact * @param rawText @@ -192,7 +195,7 @@ export class ChatGPTBot { return ( talker.self() || // TODO: add doc support - messageType !== MessageType.Text || + !(messageType == MessageType.Text || messageType == MessageType.Audio) || talker.name() === "微信团队" || // 语音(视频)消息 text.includes("收到一条视频/语音聊天消息,请在手机上查看") || @@ -236,6 +239,20 @@ export class ChatGPTBot { if (this.isNonsense(talker, messageType, rawText)) { return; } + if (messageType == MessageType.Audio){ + // 保存语音文件 + const fileBox = await message.toFileBox(); + let fileName = "./public/" + fileBox.name; + await fileBox.toFile(fileName, true).catch((e) => { + console.log("保存语音失败",e); + return; + }); + // Whisper + whisper("",fileName).then((text) => { + message.say(text); + }) + return; + } if (rawText.startsWith("/cmd ")){ console.log(`🤖 Command: ${rawText}`) const cmdContent = rawText.slice(5) // 「/cmd 」一共5个字符(注意空格) diff --git a/src/main.ts b/src/main.ts index 2e77740..6556b57 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,10 @@ const chatGPTBot = new ChatGPTBot(); const bot = WechatyBuilder.build({ name: "wechat-assistant", // generate xxxx.memory-card.json and save login data for the next login + puppet: "wechaty-puppet-wechat", + puppetOptions: { + uos: true + } }); async function main() { const initializedAt = Date.now() diff --git a/src/openai.ts b/src/openai.ts index d0e3837..10364be 100644 --- a/src/openai.ts +++ b/src/openai.ts @@ -1,5 +1,11 @@ -import {Configuration, CreateImageRequestResponseFormatEnum, CreateImageRequestSizeEnum, OpenAIApi} from "openai"; +import { + Configuration, + CreateImageRequestResponseFormatEnum, + CreateImageRequestSizeEnum, + OpenAIApi +} from "openai"; import DBUtils from "./data.js"; +import fs from "fs"; const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, @@ -19,29 +25,48 @@ async function chatgpt(username:string,message: string): Promise { model: "gpt-3.5-turbo", messages: messages, temperature: 0.6 - }).then((res) => res.data); - if (response.choices[0].message) { - return response.choices[0].message.content.replace(/^\n+|\n+$/g, ""); + }).then((res) => res.data).catch((err) => console.log(err)); + if (response) { + return (response.choices[0].message as any).content.replace(/^\n+|\n+$/g, ""); } else { return "Something went wrong" } } -// const response = await openai.createImage({ -// prompt: "a white siamese cat", -// n: 1, -// size: "1024x1024", -// }); -// image_url = response.data.data[0].url; -function dalle(username:string,prompt: string) { - const response = openai.createImage({ +/** + * Get image from Dall·E + * @param username + * @param prompt + */ +async function dalle(username:string,prompt: string) { + const response = await openai.createImage({ prompt: prompt, n:1, size: CreateImageRequestSizeEnum._256x256, response_format: CreateImageRequestResponseFormatEnum.Url, user: username - }).then((res) => res.data); - return response.then((res) => res.data[0].url); + }).then((res) => res.data).catch((err) => console.log(err)); + if (response) { + return response.data[0].url; + }else{ + return "Generate image failed" + } +} + +/** + * Speech to text + * @param username + * @param videoPath + */ +async function whisper(username:string,videoPath: string): Promise { + const file:any= fs.createReadStream(videoPath); + const response = await openai.createTranscription(file,"whisper-1") + .then((res) => res.data).catch((err) => console.log(err)); + if (response) { + return response.text; + }else{ + return "Speech to text failed" + } } -export {chatgpt,dalle}; \ No newline at end of file +export {chatgpt,dalle,whisper}; \ No newline at end of file From 0eba8b8be1d7e83f2e96af7205026ee29278dced Mon Sep 17 00:00:00 2001 From: RealTong Date: Wed, 22 Mar 2023 11:45:04 +0800 Subject: [PATCH 12/12] Add WeChat/ChatGPT interaction & deployment/config options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add support for WeChat and ChatGPT interaction, including multi-turn conversations and command setting - Provide deployment and configuration options, including Dockerfile, docker compose, Railway and Fly.io deployment - Support Dall·E, whisper, prompt setting and proxy (in development) - Add chat blocker words for both private and group chats - Add ChatGPT blocked words for both private and group chats [README.md] - Add Interaction with WeChat and ChatGPT, including conversation support and command setting - Add deployment and configuration options, including Dockerfile, docker compose, Railway and Fly.io deployment - Support Dall·E, whisper, prompt setting and proxy (in development) - Add chat blocker words for both private and group chats - Add ChatGPT blocked words for both private and group chats - Support using custom ChatGPT API [Dockerfile] - Remove the `ENV WECHATY_PUPPET_WECHAT_ENDPOINT` line - Change the `CMD` line to include `xvfb-run` [README_ZH.md] - Add support for WeChat and ChatGPT interaction: - Use ChatGPT in WeChat based on Wechaty and Official API - Support multi-turn conversations - Support command setting - Deployment and configuration options: - Provide Dockerfile for deployment with docker - Support deployment with docker compose - Deploy on Railway and Fly.io - Other features: - Support Dall [.env.example] - Remove `SB` from `BLOCK_WORDS` - Update `BLOCK_WORDS` and `CHATGPT_BLOCK_WORDS` to have the same value --- .env.example | 2 +- Dockerfile | 1 - README.md | 40 +++++++++++++++++++++------------------- README_ZH.md | 36 +++++++++++++++++++----------------- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/.env.example b/.env.example index c21aba9..980a2c3 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,6 @@ OPENAI_API_KEY="" MODEL="gpt-3.5-turbo" CHAT_PRIVATE_TRIGGER_KEYWORD= TEMPERATURE= -BLOCK_WORDS="SB, VPN" +BLOCK_WORDS="VPN" CHATGPT_BLOCK_WORDS="VPN" WECHATY_PUPPET=wechaty-puppet-wechat \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2559cdd..0c8c8f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,5 +11,4 @@ WORKDIR /app COPY package*.json ./ RUN npm install COPY . . -#ENV WECHATY_PUPPET_WECHAT_ENDPOINT=/usr/bin/google-chrome CMD xvfb-run --server-args="-screen 0 1280x800x24 -ac -nolisten tcp -dpi 96 +extension RANDR" npm run dev \ No newline at end of file diff --git a/README.md b/README.md index f785cf2..d77c4be 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,23 @@ [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/dMLG70?referralCode=bIYugQ) -## 🌟 Feature - -- [x] Use ChatGPT on WeChat with [wechaty](https://github.com/wechaty/wechaty) - and [Official API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis) -- [x] Add conversation Support -- [x] Add Dockerfile, you can use it with [docker](#use-with-docker---recommended-) -- [x] Publish to Docker.hub -- [x] Deploy using [docker compose](#use-with-docker-compose---recommended-) -- [x] Add Railway deploy -- [x] Add Fly.io deploy -- [x] ~~Supports custom ChatGPT API~~ -- [x] Set prompt -- [x] Continuous conversation -- [x] Support command setting -- [ ] Support proxy +## 🌟 Features + +- Interact with WeChat and ChatGPT: + - Use ChatGPT on WeChat with [wechaty](https://github.com/wechaty/wechaty) and [Official API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis) + - Add conversation support + - Support command setting + +- Deployment and configuration options: + - Add Dockerfile, deployable with [docker](#use-with-docker) + - Support deployment using [docker compose](#use-with-docker-compose) + - Support [Railway](#use-with-railway) and [Fly.io](#use-with-flyio) deployment + +- Other features: + - Support [Dall·E](https://labs.openai.com/) + - Support [whisper](https://openai.com/blog/introducing-chatgpt-and-whisper-apis) + - Support setting prompt + - Support proxy (in development) ## 🚀 Usage - [Use with Railway](#use-with-railway)(PaaS, Free, Stable, ✅Recommended) @@ -141,11 +143,11 @@ npm npm dev | OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [create new secret key](https://platform.openai.com/account/api-keys) | | MODEL | gpt-3.5-turbo | | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. | | TEMPERATURE | 0.6 | | What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | -| CHAT_TRIGGER_RULE | | | | -| DISABLE_GROUP_MESSAGE | true | | | +| CHAT_TRIGGER_RULE | | | Private chat triggering rules. | +| DISABLE_GROUP_MESSAGE | true | | Prohibited to use ChatGPT in group chat. | | CHAT_PRIVATE_TRIGGER_KEYWORD | | | Keyword to trigger ChatGPT reply in WeChat private chat | -| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | Chat blocker words, (works for both private and group chats, Use, Split) | -| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | The blocked words returned by ChatGPT(works for both private and group chats, Use, Split) | +| BLOCK_WORDS | "VPN" | "WORD1,WORD2,WORD3" | Chat blocker words, (works for both private and group chats, Use, Split) | +| CHATGPT_BLOCK_WORDS | "VPN" | "WORD1,WORD2,WORD3" | The blocked words returned by ChatGPT(works for both private and group chats, Use, Split) | ## 📝 Using Custom ChatGPT API diff --git a/README_ZH.md b/README_ZH.md index c6157e3..03b75a0 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -19,19 +19,21 @@ ## 🌟 功能点 -- [x] 通过 [wechaty](https://github.com/wechaty/wechaty) - 和 [官方 API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis),将 ChatGPT 接入微信 -- [x] 加入了持续对话的功能 -- [x] 加入 Dockerfile, 通过 [Docker](#通过docker使用-推荐) 进行部署 -- [x] 发布到 Docker.hub -- [x] 使用[docker compose](#通过docker-compose使用-推荐)进行部署 -- [x] 通过 Railway 进行部署 -- [x] 通过 Fly.io 进行部署 -- [x] ~~支持自定义ChatGPT API~~ -- [x] 支持设置prompt -- [x] 支持连续对话 -- [x] 支持命令设置 -- [ ] 支持代理 +- 使用 WeChat 和 ChatGPT 进行互动: + - 基于 [wechaty](https://github.com/wechaty/wechaty) 和 [Official API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis) 在微信中使用 ChatGPT + - 支持多轮对话 + - 支持[命令](#-命令)设置 + +- 部署和配置选项: + - 提供 Dockerfile,可以通过 [docker](#通过docker使用) 进行部署 + - 支持使用 [docker compose](#通过docker-compose使用) 进行部署 + - 支持在 [Railway](#使用railway进行部署) 和 [Fly.io](#通过flyio进行部署) 上部署 + +- 其他功能: + - 支持 [Dall·E](https://labs.openai.com/) + - 支持 [whisper](https://openai.com/blog/introducing-chatgpt-and-whisper-apis) + - 支持设置 prompt + - 支持代理(开发中) ## 🚀 使用 @@ -146,11 +148,11 @@ npm run dev | OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [创建你的 API 密钥](https://platform.openai.com/account/api-keys) | | MODEL | gpt-3.5-turbo | | 要使用的模型ID, 目前仅支持`gpt-3.5-turbo` 和 `gpt-3.5-turbo-0301` | | TEMPERATURE | 0.6 | | 在0和2之间。较高的数值如0.8会使 ChatGPT 输出更加随机,而较低的数值如0.2会使其更加稳定。 | -| CHAT_TRIGGER_RULE | | | | -| DISABLE_GROUP_MESSAGE | true | | | +| CHAT_TRIGGER_RULE | | | 私聊触发规则 | +| DISABLE_GROUP_MESSAGE | true | | 禁用在群聊里使用ChatGPT | | CHAT_PRIVATE_TRIGGER_KEYWORD | | | 在私聊中触发ChatGPT的关键词, 默认是无需关键词即可触发 | -| BLOCK_WORDS | | "WORD1,WORD2,WORD3" | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号 | -| CHATGPT_BLOCK_WORDS | | "WORD1,WORD2,WORD3" | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复 | +| BLOCK_WORDS | "VPN" | "WORD1,WORD2,WORD3" | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号 | +| CHATGPT_BLOCK_WORDS | "VPN" | "WORD1,WORD2,WORD3" | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复 | ## 📝 使用自定义ChatGPT API > https://github.com/fuergaosi233/openai-proxy