diff --git a/.env.example b/.env.example index e121a88..980a2c3 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ 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="VPN" +CHATGPT_BLOCK_WORDS="VPN" +WECHATY_PUPPET=wechaty-puppet-wechat \ No newline at end of file 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/Dockerfile b/Dockerfile index 5dcdaea..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 d578ba1..94da314 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,23 @@ [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/dMLG70?referralCode=bIYugQ) -## 🌟 Feature +## 🌟 Features -- [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 +- 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) @@ -78,7 +86,6 @@ flyctl deploy ``` - ## Use with docker ```sh @@ -128,6 +135,46 @@ npm run dev > Please make sure your WeChat account can log in [WeChat on web](https://wx.qq.com/) +## 📝 Environment Variables + +| 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 | | | 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 | "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 + +> 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 }, +] +``` + +## ⌨️ 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 ac7f088..03b75a0 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -19,14 +19,24 @@ ## 🌟 功能点 -- [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 进行部署 +- 使用 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 + - 支持代理(开发中) ## 🚀 使用 + - [在 Railway 部署](#使用railway进行部署)(PaaS, 免费, 稳定, ✅推荐) - [在 Fly.io 部署](#通过flyio进行部署)(PaaS, 免费, ✅推荐) - [使用 Docker 部署](#通过docker使用)(自托管, 稳定, ✅推荐) @@ -34,7 +44,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 +54,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 +105,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 +122,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 +137,46 @@ vim .env # 使用你喜欢的文本编辑器修改配置文件 npm run dev # 如果您是初次登陆,那么需要扫描二维码 ``` + > 请确保您的账号可以登陆 [网页版微信](https://wx.qq.com/)。 +## 📝 Environment Variables + +| 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 | | 禁用在群聊里使用ChatGPT | +| CHAT_PRIVATE_TRIGGER_KEYWORD | | | 在私聊中触发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 +```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 }, +] +``` + +## ⌨️ 命令 +> 在微信聊天框中输入 +```shell +/cmd help # 显示帮助信息 +/cmd prompt # 设置ChatGPT Prompt +/cmd clear # 清除WeChat-ChatGPT保存的会话记录 +``` ## ✨ Contributor @@ -131,7 +186,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/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..1115578 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", @@ -27,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 diff --git a/src/bot.ts b/src/bot.ts index 21ea401..5792972 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,10 +1,11 @@ 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 {FileBox} from "file-box"; +import {chatgpt, dalle, whisper} from "./openai.js"; +import DBUtils from "./data.js"; enum MessageType { Unknown = 0, - Attachment = 1, // Attach(6), Audio = 2, // Audio(1), Voice(34) Contact = 3, // ShareCard(42) @@ -22,29 +23,92 @@ 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 { - chatPrivateTiggerKeyword = config.chatPrivateTiggerKeyword; - chatTiggerRule = config.chatTiggerRule? new RegExp(config.chatTiggerRule): undefined; + chatPrivateTriggerKeyword = config.chatPrivateTriggerKeyword; + chatTriggerRule = config.chatTriggerRule? new RegExp(config.chatTriggerRule): undefined; disableGroupMessage = config.disableGroupMessage || false; botName: string = ""; ready = false; setBotName(botName: string) { this.botName = botName; } - get chatGroupTiggerRegEx(): RegExp { + get chatGroupTriggerRegEx(): RegExp { return new RegExp(`^@${this.botName}\\s`); } - get chatPrivateTiggerRule(): RegExp | undefined { - const { chatPrivateTiggerKeyword, chatTiggerRule } = this; - let regEx = chatTiggerRule - if (!regEx && chatPrivateTiggerKeyword) { - regEx = new RegExp(chatPrivateTiggerKeyword) + get chatPrivateTriggerRule(): RegExp | undefined { + const { chatPrivateTriggerKeyword, chatTriggerRule } = this; + let regEx = chatTriggerRule + if (!regEx && chatPrivateTriggerKeyword) { + regEx = new RegExp(chatPrivateTriggerKeyword) } return regEx } - async command(): Promise {} + private readonly commands:ICommand[] = [ + { + name: "help", + description: "显示帮助信息", + exec: async (talker) => { + await this.trySay(talker,"========\n" + + "/cmd help\n" + + "# 显示帮助信息\n" + + "/cmd prompt \n" + + "# 设置当前会话的 prompt \n" + + "/cmd img \n" + + "# 根据 prompt 生成图片\n" + + "/cmd clear\n" + + "# 清除自上次启动以来的所有会话\n" + + "========"); + } + }, + { + name: "prompt", + description: "设置当前会话的prompt", + exec: async (talker, prompt) => { + if (talker instanceof RoomImpl) { + DBUtils.setPrompt(await talker.topic(), prompt); + }else { + DBUtils.setPrompt(talker.name(), prompt); + } + } + }, + { + name: "clear", + description: "清除自上次启动以来的所有会话", + exec: async (talker) => { + if (talker instanceof RoomImpl) { + DBUtils.clearHistory(await talker.topic()); + }else{ + DBUtils.clearHistory(talker.name()); + } + } + } + ] + + /** + * EXAMPLE: + * /cmd help + * /cmd prompt + * /cmd img + * /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 cleanMessage(rawText: string, privateChat: boolean = false): string { let text = rawText; @@ -53,19 +117,28 @@ export class ChatGPTBot { text = item[item.length - 1]; } - const { chatTiggerRule, chatPrivateTiggerRule } = this; + const { chatTriggerRule, chatPrivateTriggerRule } = this; - if (privateChat && chatPrivateTiggerRule) { - text = text.replace(chatPrivateTiggerRule, "") + if (privateChat && chatPrivateTriggerRule) { + text = text.replace(chatPrivateTriggerRule, "") } else if (!privateChat) { - text = text.replace(this.chatGroupTiggerRegEx, "") - text = chatTiggerRule? text.replace(chatTiggerRule, ""): text + text = text.replace(this.chatGroupTriggerRegEx, "") + text = chatTriggerRule? text.replace(chatTriggerRule, ""): text } // 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 chatgpt(talkerName,text); + DBUtils.addAssistantMessage(talkerName,gptMessage); + return gptMessage; + } + // 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( @@ -73,6 +146,10 @@ export class ChatGPTBot { 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)); @@ -84,17 +161,17 @@ export class ChatGPTBot { } } // Check whether the ChatGPT processing can be triggered - tiggerGPTMessage(text: string, privateChat: boolean = false): boolean { - const { chatTiggerRule } = this; + triggerGPTMessage(text: string, privateChat: boolean = false): boolean { + const { chatTriggerRule } = this; let triggered = false; if (privateChat) { - const regEx = this.chatPrivateTiggerRule + const regEx = this.chatPrivateTriggerRule triggered = regEx? regEx.test(text): true; } else { - triggered = this.chatGroupTiggerRegEx.test(text); - // group message support `chatTiggerRule` - if (triggered && chatTiggerRule) { - triggered = chatTiggerRule.test(text.replace(this.chatGroupTiggerRegEx, "")) + triggered = this.chatGroupTriggerRegEx.test(text); + // group message support `chatTriggerRule` + if (triggered && chatTriggerRule) { + triggered = chatTriggerRule.test(text.replace(this.chatGroupTriggerRegEx, "")) } } if (triggered) { @@ -102,6 +179,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, @@ -111,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("收到一条视频/语音聊天消息,请在手机上查看") || @@ -120,13 +204,14 @@ 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); + const gptMessage = await this.getGPTMessage(talker.name(),text); await this.trySay(talker, gptMessage); } @@ -135,21 +220,65 @@ export class ChatGPTBot { text: string, room: RoomInterface ) { - const gptMessage = await this.getGPTMessage(text); - const result = `@${talker.name()} ${text}\n\n------ ${gptMessage}`; + const gptMessage = await this.getGPTMessage(await room.topic(),text); + const result = `@${talker.name()} ${text}\n\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 (this.tiggerGPTMessage(rawText, privateChat)) { + 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个字符(注意空格) + if (privateChat) { + await this.command(talker, cmdContent); + }else{ + await this.command(room, cmdContent); + } + 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) { return await this.onPrivateMessage(talker, text); diff --git a/src/chatgpt.ts b/src/chatgpt.ts deleted file mode 100644 index 0802e6d..0000000 --- a/src/chatgpt.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {config} from "./config.js"; - -let apiKey = config.openai_api_key; -let model = config.model; -const sendMessage = async (message: string) => { - try { - const response = await fetch(`https://api.openai.com/v1/chat/completions`, { - method: "POST", - headers: { - Authorization: `Bearer ${apiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: model, - messages: [ - { - "role": "user", - "content": message - } - ], - temperature: 0.6 - }), - }); - return response.json() - .then((data) => data.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/config.ts b/src/config.ts index 8bf8446..60d659c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,12 +1,15 @@ import * as dotenv from "dotenv"; dotenv.config(); import { IConfig } from "./interface"; -dotenv.config(); 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 || "", - chatTiggerRule: process.env.CHAT_TRIGGER_RULE || "", + chatPrivateTriggerKeyword: process.env.CHAT_PRIVATE_TRIGGER_KEYWORD || "", + chatTriggerRule: 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/data.ts b/src/data.ts new file mode 100644 index 0000000..9358f2a --- /dev/null +++ b/src/data.ts @@ -0,0 +1,121 @@ +import {ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum} from "openai"; +import {User} from "./interface"; + +/** + * 使用内存作为数据库 + */ +export const initState: Array = new Array( + { + "role": ChatCompletionRequestMessageRoleEnum.System, + "content": "You are a helpful assistant." + } +) + +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; + } + + /** + * 根据用户名获取用户, 如果用户不存在则添加用户 + * @param username + */ + public getUserByUsername(username: string): User { + return DB.data.find((user) => user.username === username) || this.addUser(username); + } + + /** + * 获取用户的聊天记录 + * @param username + */ + public getChatMessage(username: string): Array { + return this.getUserByUsername(username).chatMessage; + } + + /** + * 设置用户的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; + } + } + + /** + * 添加用户输入的消息 + * @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; + } +} +const DBUtils = new DB(); +export default DBUtils; \ No newline at end of file diff --git a/src/interface.ts b/src/interface.ts index c9e70fc..bbcb3bc 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,7 +1,17 @@ +import {ChatCompletionRequestMessage} from "openai"; + export interface IConfig { + api: string; openai_api_key: string; model: string; - chatPrivateTiggerKeyword: string; - chatTiggerRule: string; + chatTriggerRule: string; disableGroupMessage: boolean; + temperature: number; + blockWords: string[]; + 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 0a463b1..6556b57 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,15 @@ 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", + puppetOptions: { + uos: true + } }); async function main() { const initializedAt = Date.now() @@ -17,8 +22,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.chatPrivateTriggerKeyword}`); + console.log(`已设置 ${config.blockWords.length} 个聊天关键词屏蔽. ${config.blockWords}`); + console.log(`已设置 ${config.chatgptBlockWords.length} 个ChatGPT回复关键词屏蔽. ${config.chatgptBlockWords}`); }) .on("message", async (message) => { if (message.date().getTime() < initializedAt) { diff --git a/src/openai.ts b/src/openai.ts new file mode 100644 index 0000000..10364be --- /dev/null +++ b/src/openai.ts @@ -0,0 +1,72 @@ +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, +}); +const openai = new OpenAIApi(configuration); + +/** + * Get completion from OpenAI + * @param username + * @param message + */ +async function chatgpt(username:string,message: string): Promise { + // 先将用户输入的消息添加到数据库中 + DBUtils.addUserMessage(username, message); + const messages = DBUtils.getChatMessage(username); + const response = await openai.createChatCompletion({ + model: "gpt-3.5-turbo", + messages: messages, + temperature: 0.6 + }).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" + } +} + +/** + * 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).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,whisper}; \ No newline at end of file