Repository: fuergaosi233/wechat-chatgpt
Branch: main
Commit: e96349ffa217
Files: 19
Total size: 59.2 KB
Directory structure:
gitextract_52g6r067/
├── .dockerignore
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ └── config.yml
│ └── workflows/
│ └── publish-docker-hub.yml
├── .gitignore
├── Dockerfile
├── README.md
├── README_ZH.md
├── docker-compose.yml
├── package.json
├── public/
│ └── .gitignore
├── src/
│ ├── bot.ts
│ ├── config.ts
│ ├── data.ts
│ ├── interface.ts
│ ├── main.ts
│ ├── openai.ts
│ └── utils.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node
n
*memory-card.json
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
docs/
# End of https://www.toptal.com/developers/gitignore/api/python
n
config.json
cache.json
config.yaml
.vscode
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
title: "[Bug]: "
labels: ["bug"]
description: "Create a report to help us improve."
body:
- type: checkboxes
id: terms
attributes:
label: Welcome
options:
- label: Yes, I've searched similar issues on GitHub and didn't find any.
required: true
- label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
required: true
- type: checkboxes
id: platform
attributes:
label: Deployment Platform
options:
- label: Railway
- label: Fly.io
- label: Docker & Docker Compose
- label: Node.js
- type: textarea
id: problem
attributes:
label: Description of the problem
placeholder: Your problem description
validations:
required: true
- type: textarea
id: log
attributes:
label: wechat-chatgpt operation log
value: |-
<details>
```console
# Paste output here
```
</details>
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Discord Chat
url: https://discord.gg/8xWdppS7bE
about: Join our Discord server to chat with the community and get help with your issue.
================================================
FILE: .github/workflows/publish-docker-hub.yml
================================================
name: Publish Docker image
on:
# Test pub with dev tag
push:
branches:
- ci/fix-muti-platform
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: holegots/wechat-chatgpt
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
# FIXME: Chrome doesn't seem to install on ARM, so skip it for now
# platforms: linux/amd64,linux/arm64
labels: ${{ steps.meta.outputs.labels }}
================================================
FILE: .gitignore
================================================
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node
n
*memory-card.json
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# End of https://www.toptal.com/developers/gitignore/api/python
n
config.json
cache.json
config.yaml
.vscode
data/
================================================
FILE: Dockerfile
================================================
FROM node:19 AS app
# We don't need the standalone Chromium
RUN apt-get install -y wget \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
&& apt-get update && apt-get -y install google-chrome-stable chromium xvfb\
&& rm -rf /var/lib/apt/lists/* \
&& echo "Chrome: " && google-chrome --version
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD xvfb-run --server-args="-screen 0 1280x800x24 -ac -nolisten tcp -dpi 96 +extension RANDR" npm run dev
================================================
FILE: README.md
================================================
<div align="center" >
<h1 style="color:red; font-size:48px;">!!!! Project Archived 📦 !!!!</h1>
> This project has been archived. Thank you to everyone who contributed! 🙌😔
</div>
<h1 align="center">Welcome to wechat-chatgpt 👋</h1>
<p>
<img alt="Version" src="https://img.shields.io/badge/version-1.0.0-blue.svg?cacheSeconds=2592000" />
<a href="#" target="_blank">
<img alt="License: ISC" src="https://img.shields.io/badge/License-ISC-yellow.svg" />
</a>
<a href="https://twitter.com/fuergaosi" target="_blank">
<img alt="Twitter: fuergaosi" src="https://img.shields.io/twitter/follow/fuergaosi.svg?style=social" />
</a>
</a>
<a href="https://discord.gg/8fXNrxwUJH" target="blank">
<img src="https://img.shields.io/discord/1058994816446369832?label=Join%20Community&logo=discord&style=flat-square" alt="join discord community of github profile readme generator"/>
</a>
</p>
> Use ChatGPT On Wechat via wechaty
> English | [中文文档](README_ZH.md)
[](https://railway.app/template/dMLG70?referralCode=bIYugQ)
## 🌟 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)
- [Use with Fly.io](#use-with-flyio)(Paas, Free, ✅Recommended)
- [Use with docker](#use-with-docker)(Self-hosted, Stable, ✅Recommended)
- [Use with docker compose](#use-with-docker-compose)(Self-hosted, Stable, ✅Recommended)
- [Use with nodejs](#use-with-nodejs)(Self-hosted)
## Use with Railway
> Railway offers $5 or 500 hours of runtime per month
1. Click the [Railway](https://railway.app/template/dMLG70?referralCode=bIYugQ) button to go to the Railway deployment page
2. Click the `Deploy Now` button to enter the Railway deployment page
3. Fill in the repository name and `OPENAI_API_KEY` (need to link GitHub account)
4. Click the `Deploy` button
5. Click the `View Logs` button and wait for the deployment to complete
## Use with Fly.io
> Please allocate 512MB memory for the application to meet the application requirements
> fly.io offers free bills up to $5(Free Allowances 3 256MB are not included in the bill)
1. Install [flyctl](https://fly.io/docs/getting-started/installing-flyctl/)
```shell
# macOS
brew install flyctl
# Windows
scoop install flyctl
# Linux
curl https://fly.io/install.sh | sh
```
2. Clone the project and enter the project directory
```shell
git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
```
3. Create a new app
```shell
➜ flyctl launch
? Would you like to copy its configuration to the new app? No
? App Name (leave blank to use an auto-generated name): <YOUR APP NAME>
? Select region: <YOUR CHOOSE REGION>
? Would you like to setup a Postgresql database now? No
? Would you like to deploy now? No
```
4. Configure the environment variables
```shell
flyctl secrets set OPENAI_API_KEY="<YOUR OPENAI API KEY>" MODEL="<CHATGPT-MODEL>"
```
5. Deploy the app
```shell
flyctl deploy
```
## Use with docker
```sh
# pull image
docker pull holegots/wechat-chatgpt
# run container
docker run -d --name wechat-chatgpt \
-e OPENAI_API_KEY=<YOUR OPENAI API KEY> \
-e MODEL="gpt-3.5-turbo" \
-e CHAT_PRIVATE_TRIGGER_KEYWORD="" \
-v $(pwd)/data:/app/data/wechat-assistant.memory-card.json \
holegots/wechat-chatgpt:latest
# View the QR code to log in to wechat
docker logs -f wechat-chatgpt
```
> How to get OPENAI API KEY? [Click here](https://platform.openai.com/account/api-keys)
## Use with docker compose
```sh
# Copy the configuration file according to the template
cp .env.example .env
# Edit the configuration file
vim .env
# Start the container
docker-compose up -d
# View the QR code to log in to wechat
docker logs -f wechat-chatgpt
```
## Use with nodejs
> You need NodeJS 18.0.0 version and above
```sh
# Clone the project
git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
# Install dependencies
npm install
# Copy the configuration file according to the template
cp .env.example .env
# Edit the configuration file
vim .env
# Start project
npm run dev
```
> Please make sure your WeChat account can log in [WeChat on web](https://wx.qq.com/)
## 📝 Environment Variables
| name | description |
|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| API | API endpoint of ChatGPT |
| OPENAI_API_KEY | [create new secret key](https://platform.openai.com/account/api-keys) |
| MODEL | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. |
| TEMPERATURE | 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 | Prohibited to use ChatGPT in group chat. |
| CHAT_PRIVATE_TRIGGER_KEYWORD | Keyword to trigger ChatGPT reply in WeChat private chat |
| BLOCK_WORDS | Chat blocker words, (works for both private and group chats, Use, Split) |
| CHATGPT_BLOCK_WORDS | 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 <PROMPT> # Set prompt
/cmd clear # Clear all sessions since last boot
```
## ✨ Contributor
<a href="https://github.com/fuergaosi233/wechat-chatgpt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=fuergaosi233/wechat-chatgpt" />
</a>
## 🤝 Contributing
Contributions, issues and feature requests are welcome!<br />Feel free to
check [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues).
## Show your support
Give a ⭐️ if this project helped you!
================================================
FILE: README_ZH.md
================================================
<h1 align="center">欢迎使用 wechat-chatgpt 👋</h1>
<p>
<img alt="Version" src="https://img.shields.io/badge/version-1.0.0-blue.svg?cacheSeconds=2592000" />
<a href="#" target="_blank">
<img alt="License: ISC" src="https://img.shields.io/badge/License-ISC-yellow.svg" />
</a>
<a href="https://twitter.com/fuergaosi" target="_blank">
<img alt="Twitter: fuergaosi" src="https://img.shields.io/twitter/follow/fuergaosi.svg?style=social" />
</a>
<a href="https://discord.gg/8fXNrxwUJH" target="blank">
<img src="https://img.shields.io/discord/1058994816446369832?label=Join%20Community&logo=discord&style=flat-square" alt="join discord community of github profile readme generator"/>
</a>
</p>
> 在微信上迅速接入 ChatGPT,让它成为你最好的助手!
> [English](README.md) | 中文文档
[](https://railway.app/template/dMLG70?referralCode=bIYugQ)
## 🌟 功能点
- 使用 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使用)(自托管, 稳定, ✅推荐)
- [使用 Docker Compose 部署](#通过docker-compose使用)(自托管, 稳定, ✅推荐)
- [使用 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 账号)
4. 点击 `Deploy` 按钮
5. 点击 `View Logs` 按钮,等待部署完成
## 通过Fly.io进行部署
> 请为应用程序分配 512 MB 内存,否则可能会出现内存溢出
> Fly.io 5刀以内的账单免费(免费计划的3个256MB的应用不在账单内)也就是可以同时可以部署 `1*512MB + 3*256MB`
1. 安装 [flyctl](https://fly.io/docs/getting-started/installing-flyctl/)
```shell
# macOS
brew install flyctl
# Windows
scoop install flyctl
# Linux
curl https://fly.io/install.sh | sh
```
2. 克隆项目并进入项目目录
```shell
git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
```
3. 创建应用
```shell
➜ flyctl launch
? Would you like to copy its configuration to the new app? No
? App Name (leave blank to use an auto-generated name): <YOUR APP NAME>
? Select region: <YOUR CHOOSE REGION>
? Would you like to setup a Postgresql database now? No
? Would you like to deploy now? No
```
4. 配置环境变量
```shell
flyctl secrets set OPENAI_API_KEY="<YOUR OPENAI API KEY>" MODEL="<CHATGPT-MODEL>"
```
5. 部署应用
```shell
flyctl deploy
```
## 通过Docker使用
```sh
# 拉取镜像
docker pull holegots/wechat-chatgpt:latest
# 运行容器
docker run -it --name wechat-chatgpt \
-e OPENAI_API_KEY=<YOUR OPENAI API KEY> \
-e MODEL="gpt-3.5-turbo" \
-e CHAT_PRIVATE_TRIGGER_KEYWORD="" \
-v $(pwd)/data:/app/data/wechat-assistant.memory-card.json \
holegots/wechat-chatgpt:latest
# 使用二维码登陆
docker logs -f wechat-chatgpt
```
> 如何获取 OPENAI API KEY?请参考 [OpenAI API](https://platform.openai.com/account/api-keys)。
## 通过docker compose使用
```sh
# 根据模板拷贝配置文件
cp .env.example .env
# 使用你喜欢的文本编辑器修改配置文件
vim .env
# 在Linux或WindowsPowerShell上运行如下命令
docker compose up -d
# 使用二维码登陆
docker logs -f wechat-chatgpt
```
## 使用NodeJS运行
> 请确认安装的NodeJS版本为18.0.0以上
```sh
# 克隆项目
git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
# 安装依赖
npm install
# 编辑配置
cp .env.example .env
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 <PROMPT> # 设置ChatGPT Prompt
/cmd clear # 清除WeChat-ChatGPT保存的会话记录
```
## ✨ Contributor
<a href="https://github.com/fuergaosi233/wechat-chatgpt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=fuergaosi233/wechat-chatgpt" />
</a>
## 🤝 为项目添砖加瓦
欢迎提出 Contributions, issues 与 feature requests!<br />
随时查看 [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues).
## 感谢支持 🙏
如果这个项目对你产生了一点的帮助,请为这个项目点上一颗 ⭐️
================================================
FILE: docker-compose.yml
================================================
version: '3'
services:
wechat-chatgpt:
image: wechat-chatgpt
build: .
volumes:
- ./data/wechat-assistant.memory-card.json:/app/wechat-assistant.memory-card.json
env_file:
- .env
================================================
FILE: package.json
================================================
{
"name": "wechat-chatgpt",
"version": "1.0.0",
"description": "",
"main": "dist/main.js",
"export": "dist/main.js",
"scripts": {
"dev": "nodemon --exec node --loader ts-node/esm src/main.ts",
"build": "tsc"
},
"author": "",
"license": "ISC",
"dependencies": {
"async-retry": "^1.3.3",
"dotenv": "^16.0.3",
"execa": "^6.1.0",
"gpt3-tokenizer": "^1.1.5",
"openai": "^3.2.1",
"qrcode": "^1.5.1",
"uuid": "^9.0.0",
"wechaty": "^1.20.2",
"wechaty-puppet-wechat": "^1.18.4"
},
"devDependencies": {
"@types/async-retry": "^1.4.5",
"@types/qrcode": "^1.5.0",
"@types/uuid": "^9.0.0",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1"
},
"nodemonConfig": {
"watch": [
"src/*.ts"
],
"ignore": [
"src/main.ts"
],
"ext": "ts",
"exec": "node --loader ts-node/esm src/main.ts",
"delay": 500
},
"type": "module"
}
================================================
FILE: public/.gitignore
================================================
# Ignore everything in this directory
*
# Except this file
!.gitignore
================================================
FILE: src/bot.ts
================================================
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, whisper} from "./openai.js";
import DBUtils from "./data.js";
import { regexpEncode } from "./utils.js";
enum MessageType {
Unknown = 0,
Attachment = 1, // Attach(6),
Audio = 2, // Audio(1), Voice(34)
Contact = 3, // ShareCard(42)
ChatHistory = 4, // ChatHistory(19)
Emoticon = 5, // Sticker: Emoticon(15), Emoticon(47)
Image = 6, // Img(2), Image(3)
Text = 7, // Text(1)
Location = 8, // Location(48)
MiniProgram = 9, // MiniProgram(33)
GroupNote = 10, // GroupNote(53)
Transfer = 11, // Transfers(2000)
RedEnvelope = 12, // RedEnvelopes(2001)
Recalled = 13, // Recalled(10002)
Url = 14, // Url(5)
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<void>;
}
export class ChatGPTBot {
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 chatGroupTriggerRegEx(): RegExp {
return new RegExp(`^@${regexpEncode(this.botName)}\\s`);
}
get chatPrivateTriggerRule(): RegExp | undefined {
const { chatPrivateTriggerKeyword, chatTriggerRule } = this;
let regEx = chatTriggerRule
if (!regEx && chatPrivateTriggerKeyword) {
regEx = new RegExp(regexpEncode(chatPrivateTriggerKeyword))
}
return regEx
}
private readonly commands:ICommand[] = [
{
name: "help",
description: "显示帮助信息",
exec: async (talker) => {
await this.trySay(talker,"========\n" +
"/cmd help\n" +
"# 显示帮助信息\n" +
"/cmd prompt <PROMPT>\n" +
"# 设置当前会话的 prompt \n" +
"/img <PROMPT>\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 <PROMPT>
* /cmd img <PROMPT>
* /cmd clear
* @param contact
* @param rawText
*/
async command(contact: any, rawText: string): Promise<void> {
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;
const item = rawText.split("- - - - - - - - - - - - - - -");
if (item.length > 1) {
text = item[item.length - 1];
}
const { chatTriggerRule, chatPrivateTriggerRule } = this;
if (privateChat && chatPrivateTriggerRule) {
text = text.replace(chatPrivateTriggerRule, "")
} else if (!privateChat) {
text = text.replace(this.chatGroupTriggerRegEx, "")
text = chatTriggerRule? text.replace(chatTriggerRule, ""): text
}
// remove more text via - - - - - - - - - - - - - - -
return text
}
async getGPTMessage(talkerName: string,text: string): Promise<string> {
let gptMessage = await chatgpt(talkerName,text);
if (gptMessage !=="") {
DBUtils.addAssistantMessage(talkerName,gptMessage);
return gptMessage;
}
return "Sorry, please try again later. 😔";
}
// 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<void> {
const messages: Array<string> = [];
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));
message = message.slice(SINGLE_MESSAGE_MAX_SIZE);
}
messages.push(message);
for (const msg of messages) {
await talker.say(msg);
}
}
// Check whether the ChatGPT processing can be triggered
triggerGPTMessage(text: string, privateChat: boolean = false): boolean {
const { chatTriggerRule } = this;
let triggered = false;
if (privateChat) {
const regEx = this.chatPrivateTriggerRule
triggered = regEx? regEx.test(text): true;
} else {
triggered = this.chatGroupTriggerRegEx.test(text);
// group message support `chatTriggerRule`
if (triggered && chatTriggerRule) {
triggered = chatTriggerRule.test(text.replace(this.chatGroupTriggerRegEx, ""))
}
}
if (triggered) {
console.log(`🎯 Triggered ChatGPT: ${text}`);
}
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,
messageType: MessageType,
text: string
): boolean {
return (
talker.self() ||
// TODO: add doc support
!(messageType == MessageType.Text || messageType == MessageType.Audio) ||
talker.name() === "微信团队" ||
// 语音(视频)消息
text.includes("收到一条视频/语音聊天消息,请在手机上查看") ||
// 红包消息
text.includes("收到红包,请在手机上查看") ||
// Transfer message
text.includes("收到转账,请在手机上查看") ||
// 位置消息
text.includes("/cgi-bin/mmwebwx-bin/webwxgetpubliclinkimg") ||
// 聊天屏蔽词
this.checkBlockWords(text)
);
}
async onPrivateMessage(talker: ContactInterface, text: string) {
const gptMessage = await this.getGPTMessage(talker.name(),text);
await this.trySay(talker, gptMessage);
}
async onGroupMessage(
talker: ContactInterface,
text: string,
room: RoomInterface
) {
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) {
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 (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);
} else{
if (!this.disableGroupMessage){
return await this.onGroupMessage(talker, text, room);
} else {
return;
}
}
} else {
return;
}
}
}
================================================
FILE: src/config.ts
================================================
import * as dotenv from "dotenv";
dotenv.config();
import { IConfig } from "./interface";
export const config: IConfig = {
api: process.env.API,
openai_api_key: process.env.OPENAI_API_KEY || "123456789",
model: process.env.MODEL || "gpt-3.5-turbo",
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(",") || [],
};
================================================
FILE: src/data.ts
================================================
import {ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum} from "openai";
import {User} from "./interface";
import {isTokenOverLimit} from "./utils.js";
/**
* 使用内存作为数据库
*/
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<ChatCompletionRequestMessage> {
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) {
while (isTokenOverLimit(user.chatMessage)){
// 删除从第2条开始的消息(因为第一条是prompt)
user.chatMessage.splice(1,1);
}
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) {
while (isTokenOverLimit(user.chatMessage)){
// 删除从第2条开始的消息(因为第一条是prompt)
user.chatMessage.splice(1,1);
}
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;
================================================
FILE: src/interface.ts
================================================
import {ChatCompletionRequestMessage} from "openai";
export interface IConfig {
api?: string;
openai_api_key: string;
model: string;
chatTriggerRule: string;
disableGroupMessage: boolean;
temperature: number;
blockWords: string[];
chatgptBlockWords: string[];
chatPrivateTriggerKeyword: string;
}
export interface User {
username: string,
chatMessage: Array<ChatCompletionRequestMessage>,
}
================================================
FILE: src/main.ts
================================================
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()
bot
.on("scan", async (qrcode, status) => {
const url = `https://wechaty.js.org/qrcode/${encodeURIComponent(qrcode)}`;
console.log(`Scan QR Code to login: ${status}\n${url}`);
console.log(
await QRCode.toString(qrcode, { type: "terminal", small: true })
);
})
.on("login", async (user) => {
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) {
return;
}
if (message.text().startsWith("/ping")) {
await message.say("pong");
return;
}
try {
await chatGPTBot.onMessage(message);
} catch (e) {
console.error(e);
}
});
try {
await bot.start();
} catch (e) {
console.error(
`⚠️ Bot start failed, can you log in through wechat on the web?: ${e}`
);
}
}
main();
================================================
FILE: src/openai.ts
================================================
import {
Configuration,
CreateImageRequestResponseFormatEnum,
CreateImageRequestSizeEnum,
OpenAIApi
} from "openai";
import fs from "fs";
import DBUtils from "./data.js";
import {config} from "./config.js";
const configuration = new Configuration({
apiKey: config.openai_api_key,
basePath: config.api,
});
const openai = new OpenAIApi(configuration);
/**
* Get completion from OpenAI
* @param username
* @param message
*/
async function chatgpt(username:string,message: string): Promise<string> {
// 先将用户输入的消息添加到数据库中
DBUtils.addUserMessage(username, message);
const messages = DBUtils.getChatMessage(username);
const response = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: messages,
temperature: config.temperature,
});
let assistantMessage = "";
try {
if (response.status === 200) {
assistantMessage = response.data.choices[0].message?.content.replace(/^\n+|\n+$/g, "") as string;
}else{
console.log(`Something went wrong,Code: ${response.status}, ${response.statusText}`)
}
}catch (e:any) {
if (e.request){
console.log("请求出错");
}
}
return assistantMessage;
}
/**
* 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<string> {
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};
================================================
FILE: src/utils.ts
================================================
import {ChatCompletionRequestMessage} from "openai";
import GPT3TokenizerImport from 'gpt3-tokenizer';
import {config} from "./config.js";
export const regexpEncode = (str: string) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
const GPT3Tokenizer: typeof GPT3TokenizerImport =
typeof GPT3TokenizerImport === 'function'
? GPT3TokenizerImport
: (GPT3TokenizerImport as any).default;
// https://github.com/chathub-dev/chathub/blob/main/src/app/bots/chatgpt-api/usage.ts
const tokenizer = new GPT3Tokenizer({ type: 'gpt3' })
function calTokens(chatMessage:ChatCompletionRequestMessage[]):number {
let count = 0
for (const msg of chatMessage) {
count += countTokens(msg.content)
count += countTokens(msg.role)
}
return count + 2
}
function countTokens(str: string):number {
const encoded = tokenizer.encode(str)
return encoded.bpe.length
}
export function isTokenOverLimit(chatMessage:ChatCompletionRequestMessage[]): boolean {
let limit = 4096;
if (config.model==="gpt-3.5-turbo" || config.model==="gpt-3.5-turbo-0301") {
limit = 4096;
}
return calTokens(chatMessage) > limit;
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "esnext", /* Specify what module code is generated. */
"rootDir": "src", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
"resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
gitextract_52g6r067/ ├── .dockerignore ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── config.yml │ └── workflows/ │ └── publish-docker-hub.yml ├── .gitignore ├── Dockerfile ├── README.md ├── README_ZH.md ├── docker-compose.yml ├── package.json ├── public/ │ └── .gitignore ├── src/ │ ├── bot.ts │ ├── config.ts │ ├── data.ts │ ├── interface.ts │ ├── main.ts │ ├── openai.ts │ └── utils.ts └── tsconfig.json
SYMBOL INDEX (37 symbols across 6 files)
FILE: src/bot.ts
type MessageType (line 8) | enum MessageType {
constant SINGLE_MESSAGE_MAX_SIZE (line 27) | const SINGLE_MESSAGE_MAX_SIZE = 500;
type Speaker (line 28) | type Speaker = RoomImpl | ContactImpl;
type ICommand (line 29) | interface ICommand{
class ChatGPTBot (line 34) | class ChatGPTBot {
method setBotName (line 40) | setBotName(botName: string) {
method chatGroupTriggerRegEx (line 43) | get chatGroupTriggerRegEx(): RegExp {
method chatPrivateTriggerRule (line 46) | get chatPrivateTriggerRule(): RegExp | undefined {
method command (line 104) | async command(contact: any, rawText: string): Promise<void> {
method cleanMessage (line 114) | cleanMessage(rawText: string, privateChat: boolean = false): string {
method getGPTMessage (line 132) | async getGPTMessage(talkerName: string,text: string): Promise<string> {
method checkChatGPTBlockWords (line 141) | checkChatGPTBlockWords(message: string): boolean {
method trySay (line 148) | async trySay(
method triggerGPTMessage (line 168) | triggerGPTMessage(text: string, privateChat: boolean = false): boolean {
method checkBlockWords (line 187) | checkBlockWords(message: string): boolean {
method isNonsense (line 194) | isNonsense(
method onPrivateMessage (line 217) | async onPrivateMessage(talker: ContactInterface, text: string) {
method onGroupMessage (line 222) | async onGroupMessage(
method onMessage (line 231) | async onMessage(message: Message) {
FILE: src/data.ts
class DB (line 9) | class DB {
method addUser (line 16) | public addUser(username: string): User {
method getUserByUsername (line 39) | public getUserByUsername(username: string): User {
method getChatMessage (line 47) | public getChatMessage(username: string): Array<ChatCompletionRequestMe...
method setPrompt (line 56) | public setPrompt(username: string, prompt: string): void {
method addUserMessage (line 70) | public addUserMessage(username: string, message: string): void {
method addAssistantMessage (line 89) | public addAssistantMessage(username: string, message: string): void {
method clearHistory (line 107) | public clearHistory(username: string): void {
method getAllData (line 119) | public getAllData(): User[] {
FILE: src/interface.ts
type IConfig (line 3) | interface IConfig {
type User (line 14) | interface User {
FILE: src/main.ts
function main (line 14) | async function main() {
FILE: src/openai.ts
function chatgpt (line 22) | async function chatgpt(username:string,message: string): Promise<string> {
function dalle (line 51) | async function dalle(username:string,prompt: string) {
function whisper (line 71) | async function whisper(username:string,videoPath: string): Promise<strin...
FILE: src/utils.ts
function calTokens (line 14) | function calTokens(chatMessage:ChatCompletionRequestMessage[]):number {
function countTokens (line 23) | function countTokens(str: string):number {
function isTokenOverLimit (line 27) | function isTokenOverLimit(chatMessage:ChatCompletionRequestMessage[]): b...
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (64K chars).
[
{
"path": ".dockerignore",
"chars": 5898,
"preview": "# Created by https://www.toptal.com/developers/gitignore/api/node\n# Edit at https://www.toptal.com/developers/gitignore?"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1059,
"preview": "name: Bug Report\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\ndescription: \"Create a report to help us improve.\"\nbody:\n - type: che"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 196,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Discord Chat\n url: https://discord.gg/8xWdppS7bE\n about: Join"
},
{
"path": ".github/workflows/publish-docker-hub.yml",
"chars": 1303,
"preview": "name: Publish Docker image\n\non:\n # Test pub with dev tag\n push:\n branches:\n - ci/fix-muti-platform\n release:\n"
},
{
"path": ".gitignore",
"chars": 5898,
"preview": "# Created by https://www.toptal.com/developers/gitignore/api/node\n# Edit at https://www.toptal.com/developers/gitignore?"
},
{
"path": "Dockerfile",
"chars": 626,
"preview": "FROM node:19 AS app\n\n# We don't need the standalone Chromium\nRUN apt-get install -y wget \\\n && wget -q -O - https://d"
},
{
"path": "README.md",
"chars": 8392,
"preview": "<div align=\"center\" >\n\n<h1 style=\"color:red; font-size:48px;\">!!!! Project Archived 📦 !!!!</h1>\n\n> This project has been"
},
{
"path": "README_ZH.md",
"chars": 6597,
"preview": "<h1 align=\"center\">欢迎使用 wechat-chatgpt 👋</h1>\n<p>\n <img alt=\"Version\" src=\"https://img.shields.io/badge/version-1.0.0-b"
},
{
"path": "docker-compose.yml",
"chars": 207,
"preview": "version: '3'\nservices:\n wechat-chatgpt:\n image: wechat-chatgpt\n build: .\n volumes:\n - ./data/wechat-assis"
},
{
"path": "package.json",
"chars": 929,
"preview": "{\n \"name\": \"wechat-chatgpt\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"dist/main.js\",\n \"export\": \"dist/mai"
},
{
"path": "public/.gitignore",
"chars": 70,
"preview": "# Ignore everything in this directory\n*\n# Except this file\n!.gitignore"
},
{
"path": "src/bot.ts",
"chars": 9516,
"preview": "import { config } from \"./config.js\";\nimport {ContactImpl, ContactInterface, RoomImpl, RoomInterface} from \"wechaty/impl"
},
{
"path": "src/config.ts",
"chars": 674,
"preview": "import * as dotenv from \"dotenv\";\ndotenv.config();\nimport { IConfig } from \"./interface\";\n\nexport const config: IConfig "
},
{
"path": "src/data.ts",
"chars": 2936,
"preview": "import {ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum} from \"openai\";\nimport {User} from \"./interfa"
},
{
"path": "src/interface.ts",
"chars": 413,
"preview": "import {ChatCompletionRequestMessage} from \"openai\";\n\nexport interface IConfig {\n api?: string;\n openai_api_key: strin"
},
{
"path": "src/main.ts",
"chars": 1653,
"preview": "import { WechatyBuilder } from \"wechaty\";\nimport QRCode from \"qrcode\";\nimport { ChatGPTBot } from \"./bot.js\";\nimport {co"
},
{
"path": "src/openai.ts",
"chars": 2119,
"preview": "import {\n Configuration,\n CreateImageRequestResponseFormatEnum,\n CreateImageRequestSizeEnum,\n OpenAIApi\n} from \"open"
},
{
"path": "src/utils.ts",
"chars": 1123,
"preview": "import {ChatCompletionRequestMessage} from \"openai\";\n\nimport GPT3TokenizerImport from 'gpt3-tokenizer';\nimport {config} "
},
{
"path": "tsconfig.json",
"chars": 10989,
"preview": "{\n \"compilerOptions\": {\n /* Visit https://aka.ms/tsconfig.json to read more about this file */\n\n /* Projects */\n "
}
]
About this extraction
This page contains the full source code of the fuergaosi233/wechat-chatgpt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (59.2 KB), approximately 16.0k tokens, and a symbol index with 37 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.