├── .dockerignore
├── .env.example
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── config.yml
└── workflows
│ └── publish-docker-hub.yml
├── .gitignore
├── Dockerfile
├── README.md
├── README_ZH.md
├── docker-compose.yml
├── docs
└── images
│ ├── railway-deployed.png
│ ├── railway-deploying.png
│ └── railway-succeed.png
├── package-lock.json
├── package.json
├── public
└── .gitignore
├── src
├── bot.ts
├── config.ts
├── data.ts
├── interface.ts
├── main.ts
├── openai.ts
└── utils.ts
└── tsconfig.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/node
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
3 |
4 | ### Node ###
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 | .pnpm-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # Snowpack dependency directory (https://snowpack.dev/)
50 | web_modules/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | .npm
57 |
58 | # Optional eslint cache
59 | .eslintcache
60 |
61 | # Optional stylelint cache
62 | .stylelintcache
63 |
64 | # Microbundle cache
65 | .rpt2_cache/
66 | .rts2_cache_cjs/
67 | .rts2_cache_es/
68 | .rts2_cache_umd/
69 |
70 | # Optional REPL history
71 | .node_repl_history
72 |
73 | # Output of 'npm pack'
74 | *.tgz
75 |
76 | # Yarn Integrity file
77 | .yarn-integrity
78 |
79 | # dotenv environment variable files
80 | .env
81 | .env.development.local
82 | .env.test.local
83 | .env.production.local
84 | .env.local
85 |
86 | # parcel-bundler cache (https://parceljs.org/)
87 | .cache
88 | .parcel-cache
89 |
90 | # Next.js build output
91 | .next
92 | out
93 |
94 | # Nuxt.js build / generate output
95 | .nuxt
96 | dist
97 |
98 | # Gatsby files
99 | .cache/
100 | # Comment in the public line in if your project uses Gatsby and not Next.js
101 | # https://nextjs.org/blog/next-9-1#public-directory-support
102 | # public
103 |
104 | # vuepress build output
105 | .vuepress/dist
106 |
107 | # vuepress v2.x temp and cache directory
108 | .temp
109 |
110 | # Docusaurus cache and generated files
111 | .docusaurus
112 |
113 | # Serverless directories
114 | .serverless/
115 |
116 | # FuseBox cache
117 | .fusebox/
118 |
119 | # DynamoDB Local files
120 | .dynamodb/
121 |
122 | # TernJS port file
123 | .tern-port
124 |
125 | # Stores VSCode versions used for testing VSCode extensions
126 | .vscode-test
127 |
128 | # yarn v2
129 | .yarn/cache
130 | .yarn/unplugged
131 | .yarn/build-state.yml
132 | .yarn/install-state.gz
133 | .pnp.*
134 |
135 | ### Node Patch ###
136 | # Serverless Webpack directories
137 | .webpack/
138 |
139 | # Optional stylelint cache
140 |
141 | # SvelteKit build / generate output
142 | .svelte-kit
143 |
144 | # End of https://www.toptal.com/developers/gitignore/api/node
145 | n
146 | *memory-card.json
147 | # Created by https://www.toptal.com/developers/gitignore/api/python
148 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
149 |
150 | ### Python ###
151 | # Byte-compiled / optimized / DLL files
152 | __pycache__/
153 | *.py[cod]
154 | *$py.class
155 |
156 | # C extensions
157 | *.so
158 |
159 | # Distribution / packaging
160 | .Python
161 | build/
162 | develop-eggs/
163 | dist/
164 | downloads/
165 | eggs/
166 | .eggs/
167 | lib/
168 | lib64/
169 | parts/
170 | sdist/
171 | var/
172 | wheels/
173 | share/python-wheels/
174 | *.egg-info/
175 | .installed.cfg
176 | *.egg
177 | MANIFEST
178 |
179 | # PyInstaller
180 | # Usually these files are written by a python script from a template
181 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
182 | *.manifest
183 | *.spec
184 |
185 | # Installer logs
186 | pip-log.txt
187 | pip-delete-this-directory.txt
188 |
189 | # Unit test / coverage reports
190 | htmlcov/
191 | .tox/
192 | .nox/
193 | .coverage
194 | .coverage.*
195 | .cache
196 | nosetests.xml
197 | coverage.xml
198 | *.cover
199 | *.py,cover
200 | .hypothesis/
201 | .pytest_cache/
202 | cover/
203 |
204 | # Translations
205 | *.mo
206 | *.pot
207 |
208 | # Django stuff:
209 | *.log
210 | local_settings.py
211 | db.sqlite3
212 | db.sqlite3-journal
213 |
214 | # Flask stuff:
215 | instance/
216 | .webassets-cache
217 |
218 | # Scrapy stuff:
219 | .scrapy
220 |
221 | # Sphinx documentation
222 | docs/_build/
223 |
224 | # PyBuilder
225 | .pybuilder/
226 | target/
227 |
228 | # Jupyter Notebook
229 | .ipynb_checkpoints
230 |
231 | # IPython
232 | profile_default/
233 | ipython_config.py
234 |
235 | # pyenv
236 | # For a library or package, you might want to ignore these files since the code is
237 | # intended to run in multiple environments; otherwise, check them in:
238 | # .python-version
239 |
240 | # pipenv
241 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
242 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
243 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
244 | # install all needed dependencies.
245 | #Pipfile.lock
246 |
247 | # poetry
248 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
249 | # This is especially recommended for binary packages to ensure reproducibility, and is more
250 | # commonly ignored for libraries.
251 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
252 | #poetry.lock
253 |
254 | # pdm
255 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
256 | #pdm.lock
257 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
258 | # in version control.
259 | # https://pdm.fming.dev/#use-with-ide
260 | .pdm.toml
261 |
262 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
263 | __pypackages__/
264 |
265 | # Celery stuff
266 | celerybeat-schedule
267 | celerybeat.pid
268 |
269 | # SageMath parsed files
270 | *.sage.py
271 |
272 | # Environments
273 | .env
274 | .venv
275 | env/
276 | venv/
277 | ENV/
278 | env.bak/
279 | venv.bak/
280 |
281 | # Spyder project settings
282 | .spyderproject
283 | .spyproject
284 |
285 | # Rope project settings
286 | .ropeproject
287 |
288 | # mkdocs documentation
289 | /site
290 |
291 | # mypy
292 | .mypy_cache/
293 | .dmypy.json
294 | dmypy.json
295 |
296 | # Pyre type checker
297 | .pyre/
298 |
299 | # pytype static type analyzer
300 | .pytype/
301 |
302 | # Cython debug symbols
303 | cython_debug/
304 |
305 | # PyCharm
306 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
307 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
308 | # and can be added to the global gitignore or merged into this file. For a more nuclear
309 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
310 | #.idea/
311 |
312 | ### Python Patch ###
313 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
314 | poetry.toml
315 |
316 | docs/
317 |
318 |
319 | # End of https://www.toptal.com/developers/gitignore/api/python
320 | n
321 | config.json
322 | cache.json
323 | config.yaml
324 | .vscode
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | ENDPOINT="https://API.EXAMPLE.COM/v1"
2 | OPENAI_API_KEY="sk-XXXXXXXXXXXXXXXXX"
3 | MODEL="gpt-3.5-turbo"
4 | CHAT_PRIVATE_TRIGGER_KEYWORD=
5 | TEMPERATURE=0.6
6 | BLOCK_WORDS="VPN"
7 | CHATGPT_BLOCK_WORDS="VPN"
8 | WECHATY_PUPPET=wechaty-puppet-wechat
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | title: "[Bug]: "
3 | labels: ["bug"]
4 | description: "Create a report to help us improve."
5 | body:
6 | - type: checkboxes
7 | id: terms
8 | attributes:
9 | label: Welcome
10 | options:
11 | - label: Yes, I've searched similar issues on GitHub and didn't find any.
12 | required: true
13 | - label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).
14 | required: true
15 | - type: checkboxes
16 | id: platform
17 | attributes:
18 | label: Deployment Platform
19 | options:
20 | - label: Railway
21 | - label: Fly.io
22 | - label: Docker & Docker Compose
23 | - label: Node.js
24 | - type: textarea
25 | id: problem
26 | attributes:
27 | label: Description of the problem
28 | placeholder: Your problem description
29 | validations:
30 | required: true
31 | - type: textarea
32 | id: log
33 | attributes:
34 | label: wechat-chatgpt operation log
35 | value: |-
36 |
37 | ```console
38 | # Paste output here
39 | ```
40 |
41 | validations:
42 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Discord Chat
4 | url: https://discord.gg/8xWdppS7bE
5 | about: Join our Discord server to chat with the community and get help with your issue.
--------------------------------------------------------------------------------
/.github/workflows/publish-docker-hub.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker image
2 |
3 | on:
4 | # Test pub with dev tag
5 | push:
6 | branches:
7 | - ci/fix-muti-platform
8 | release:
9 | types: [published]
10 |
11 | jobs:
12 | push_to_registry:
13 | name: Push Docker image to Docker Hub
14 | runs-on: ubuntu-latest
15 | steps:
16 | - name: Check out the repo
17 | uses: actions/checkout@v3
18 |
19 | - name: Set up QEMU
20 | uses: docker/setup-qemu-action@v2
21 |
22 | - name: Set up Docker Buildx
23 | uses: docker/setup-buildx-action@v2
24 |
25 | - name: Log in to Docker Hub
26 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
27 | with:
28 | username: ${{ secrets.DOCKER_USERNAME }}
29 | password: ${{ secrets.DOCKER_PASSWORD }}
30 |
31 | - name: Extract metadata (tags, labels) for Docker
32 | id: meta
33 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
34 | with:
35 | images: holegots/wechat-chatgpt
36 |
37 | - name: Build and push Docker image
38 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
39 | with:
40 | context: .
41 | push: true
42 | tags: ${{ steps.meta.outputs.tags }}
43 | # FIXME: Chrome doesn't seem to install on ARM, so skip it for now
44 | # platforms: linux/amd64,linux/arm64
45 | labels: ${{ steps.meta.outputs.labels }}
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/node
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
3 |
4 | ### Node ###
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 | .pnpm-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # Snowpack dependency directory (https://snowpack.dev/)
50 | web_modules/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | .npm
57 |
58 | # Optional eslint cache
59 | .eslintcache
60 |
61 | # Optional stylelint cache
62 | .stylelintcache
63 |
64 | # Microbundle cache
65 | .rpt2_cache/
66 | .rts2_cache_cjs/
67 | .rts2_cache_es/
68 | .rts2_cache_umd/
69 |
70 | # Optional REPL history
71 | .node_repl_history
72 |
73 | # Output of 'npm pack'
74 | *.tgz
75 |
76 | # Yarn Integrity file
77 | .yarn-integrity
78 |
79 | # dotenv environment variable files
80 | .env
81 | .env.development.local
82 | .env.test.local
83 | .env.production.local
84 | .env.local
85 |
86 | # parcel-bundler cache (https://parceljs.org/)
87 | .cache
88 | .parcel-cache
89 |
90 | # Next.js build output
91 | .next
92 | out
93 |
94 | # Nuxt.js build / generate output
95 | .nuxt
96 | dist
97 |
98 | # Gatsby files
99 | .cache/
100 | # Comment in the public line in if your project uses Gatsby and not Next.js
101 | # https://nextjs.org/blog/next-9-1#public-directory-support
102 | # public
103 |
104 | # vuepress build output
105 | .vuepress/dist
106 |
107 | # vuepress v2.x temp and cache directory
108 | .temp
109 |
110 | # Docusaurus cache and generated files
111 | .docusaurus
112 |
113 | # Serverless directories
114 | .serverless/
115 |
116 | # FuseBox cache
117 | .fusebox/
118 |
119 | # DynamoDB Local files
120 | .dynamodb/
121 |
122 | # TernJS port file
123 | .tern-port
124 |
125 | # Stores VSCode versions used for testing VSCode extensions
126 | .vscode-test
127 |
128 | # yarn v2
129 | .yarn/cache
130 | .yarn/unplugged
131 | .yarn/build-state.yml
132 | .yarn/install-state.gz
133 | .pnp.*
134 |
135 | ### Node Patch ###
136 | # Serverless Webpack directories
137 | .webpack/
138 |
139 | # Optional stylelint cache
140 |
141 | # SvelteKit build / generate output
142 | .svelte-kit
143 |
144 | # End of https://www.toptal.com/developers/gitignore/api/node
145 | n
146 | *memory-card.json
147 | # Created by https://www.toptal.com/developers/gitignore/api/python
148 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
149 |
150 | ### Python ###
151 | # Byte-compiled / optimized / DLL files
152 | __pycache__/
153 | *.py[cod]
154 | *$py.class
155 |
156 | # C extensions
157 | *.so
158 |
159 | # Distribution / packaging
160 | .Python
161 | build/
162 | develop-eggs/
163 | dist/
164 | downloads/
165 | eggs/
166 | .eggs/
167 | lib/
168 | lib64/
169 | parts/
170 | sdist/
171 | var/
172 | wheels/
173 | share/python-wheels/
174 | *.egg-info/
175 | .installed.cfg
176 | *.egg
177 | MANIFEST
178 |
179 | # PyInstaller
180 | # Usually these files are written by a python script from a template
181 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
182 | *.manifest
183 | *.spec
184 |
185 | # Installer logs
186 | pip-log.txt
187 | pip-delete-this-directory.txt
188 |
189 | # Unit test / coverage reports
190 | htmlcov/
191 | .tox/
192 | .nox/
193 | .coverage
194 | .coverage.*
195 | .cache
196 | nosetests.xml
197 | coverage.xml
198 | *.cover
199 | *.py,cover
200 | .hypothesis/
201 | .pytest_cache/
202 | cover/
203 |
204 | # Translations
205 | *.mo
206 | *.pot
207 |
208 | # Django stuff:
209 | *.log
210 | local_settings.py
211 | db.sqlite3
212 | db.sqlite3-journal
213 |
214 | # Flask stuff:
215 | instance/
216 | .webassets-cache
217 |
218 | # Scrapy stuff:
219 | .scrapy
220 |
221 | # Sphinx documentation
222 | docs/_build/
223 |
224 | # PyBuilder
225 | .pybuilder/
226 | target/
227 |
228 | # Jupyter Notebook
229 | .ipynb_checkpoints
230 |
231 | # IPython
232 | profile_default/
233 | ipython_config.py
234 |
235 | # pyenv
236 | # For a library or package, you might want to ignore these files since the code is
237 | # intended to run in multiple environments; otherwise, check them in:
238 | # .python-version
239 |
240 | # pipenv
241 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
242 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
243 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
244 | # install all needed dependencies.
245 | #Pipfile.lock
246 |
247 | # poetry
248 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
249 | # This is especially recommended for binary packages to ensure reproducibility, and is more
250 | # commonly ignored for libraries.
251 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
252 | #poetry.lock
253 |
254 | # pdm
255 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
256 | #pdm.lock
257 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
258 | # in version control.
259 | # https://pdm.fming.dev/#use-with-ide
260 | .pdm.toml
261 |
262 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
263 | __pypackages__/
264 |
265 | # Celery stuff
266 | celerybeat-schedule
267 | celerybeat.pid
268 |
269 | # SageMath parsed files
270 | *.sage.py
271 |
272 | # Environments
273 | .env
274 | .venv
275 | env/
276 | venv/
277 | ENV/
278 | env.bak/
279 | venv.bak/
280 |
281 | # Spyder project settings
282 | .spyderproject
283 | .spyproject
284 |
285 | # Rope project settings
286 | .ropeproject
287 |
288 | # mkdocs documentation
289 | /site
290 |
291 | # mypy
292 | .mypy_cache/
293 | .dmypy.json
294 | dmypy.json
295 |
296 | # Pyre type checker
297 | .pyre/
298 |
299 | # pytype static type analyzer
300 | .pytype/
301 |
302 | # Cython debug symbols
303 | cython_debug/
304 |
305 | # PyCharm
306 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
307 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
308 | # and can be added to the global gitignore or merged into this file. For a more nuclear
309 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
310 | .idea/
311 |
312 | ### Python Patch ###
313 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
314 | poetry.toml
315 |
316 |
317 | # End of https://www.toptal.com/developers/gitignore/api/python
318 | n
319 | config.json
320 | cache.json
321 | config.yaml
322 | .vscode
323 |
324 | data/
325 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:19 AS app
2 |
3 | # We don't need the standalone Chromium
4 | RUN apt-get install -y wget \
5 | && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
6 | && echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
7 | && apt-get update && apt-get -y install google-chrome-stable chromium xvfb\
8 | && rm -rf /var/lib/apt/lists/* \
9 | && echo "Chrome: " && google-chrome --version
10 | WORKDIR /app
11 | COPY package*.json ./
12 | RUN npm install
13 | COPY . .
14 | CMD xvfb-run --server-args="-screen 0 1280x800x24 -ac -nolisten tcp -dpi 96 +extension RANDR" npm run dev
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
!!!! Project Archived 📦 !!!!
4 |
5 | > This project has been archived. Thank you to everyone who contributed! 🙌😔
6 |
7 |
8 |
9 | Welcome to wechat-chatgpt 👋
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | > Use ChatGPT On Wechat via wechaty
25 | > English | [中文文档](README_ZH.md)
26 |
27 | [](https://railway.app/template/dMLG70?referralCode=bIYugQ)
28 |
29 | ## 🌟 Features
30 |
31 | - Interact with WeChat and ChatGPT:
32 | - Use ChatGPT on WeChat with [wechaty](https://github.com/wechaty/wechaty) and [Official API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)
33 | - Add conversation support
34 | - Support command setting
35 |
36 | - Deployment and configuration options:
37 | - Add Dockerfile, deployable with [docker](#use-with-docker)
38 | - Support deployment using [docker compose](#use-with-docker-compose)
39 | - Support [Railway](#use-with-railway) and [Fly.io](#use-with-flyio) deployment
40 |
41 | - Other features:
42 | - Support [Dall·E](https://labs.openai.com/)
43 | - Support [whisper](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)
44 | - Support setting prompt
45 | - Support proxy (in development)
46 |
47 | ## 🚀 Usage
48 | - [Use with Railway](#use-with-railway)(PaaS, Free, Stable, ✅Recommended)
49 | - [Use with Fly.io](#use-with-flyio)(Paas, Free, ✅Recommended)
50 | - [Use with docker](#use-with-docker)(Self-hosted, Stable, ✅Recommended)
51 | - [Use with docker compose](#use-with-docker-compose)(Self-hosted, Stable, ✅Recommended)
52 | - [Use with nodejs](#use-with-nodejs)(Self-hosted)
53 |
54 | ## Use with Railway
55 | > Railway offers $5 or 500 hours of runtime per month
56 | 1. Click the [Railway](https://railway.app/template/dMLG70?referralCode=bIYugQ) button to go to the Railway deployment page
57 | 2. Click the `Deploy Now` button to enter the Railway deployment page
58 | 3. Fill in the repository name and `OPENAI_API_KEY` (need to link GitHub account)
59 | 4. Click the `Deploy` button
60 | 5. Click the `View Logs` button and wait for the deployment to complete
61 |
62 | ## Use with Fly.io
63 | > Please allocate 512MB memory for the application to meet the application requirements
64 |
65 | > fly.io offers free bills up to $5(Free Allowances 3 256MB are not included in the bill)
66 | 1. Install [flyctl](https://fly.io/docs/getting-started/installing-flyctl/)
67 | ```shell
68 | # macOS
69 | brew install flyctl
70 | # Windows
71 | scoop install flyctl
72 | # Linux
73 | curl https://fly.io/install.sh | sh
74 | ```
75 | 2. Clone the project and enter the project directory
76 | ```shell
77 | git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
78 | ```
79 | 3. Create a new app
80 | ```shell
81 | ➜ flyctl launch
82 | ? Would you like to copy its configuration to the new app? No
83 | ? App Name (leave blank to use an auto-generated name):
84 | ? Select region:
85 | ? Would you like to setup a Postgresql database now? No
86 | ? Would you like to deploy now? No
87 | ```
88 | 4. Configure the environment variables
89 | ```shell
90 | flyctl secrets set OPENAI_API_KEY="" MODEL=""
91 | ```
92 | 5. Deploy the app
93 | ```shell
94 | flyctl deploy
95 | ```
96 |
97 | ## Use with docker
98 |
99 | ```sh
100 | # pull image
101 | docker pull holegots/wechat-chatgpt
102 | # run container
103 | docker run -d --name wechat-chatgpt \
104 | -e OPENAI_API_KEY= \
105 | -e MODEL="gpt-3.5-turbo" \
106 | -e CHAT_PRIVATE_TRIGGER_KEYWORD="" \
107 | -v $(pwd)/data:/app/data/wechat-assistant.memory-card.json \
108 | holegots/wechat-chatgpt:latest
109 | # View the QR code to log in to wechat
110 | docker logs -f wechat-chatgpt
111 | ```
112 | > How to get OPENAI API KEY? [Click here](https://platform.openai.com/account/api-keys)
113 |
114 | ## Use with docker compose
115 |
116 | ```sh
117 | # Copy the configuration file according to the template
118 | cp .env.example .env
119 | # Edit the configuration file
120 | vim .env
121 | # Start the container
122 | docker-compose up -d
123 | # View the QR code to log in to wechat
124 | docker logs -f wechat-chatgpt
125 | ```
126 |
127 | ## Use with nodejs
128 |
129 | > You need NodeJS 18.0.0 version and above
130 |
131 | ```sh
132 | # Clone the project
133 | git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
134 | # Install dependencies
135 | npm install
136 | # Copy the configuration file according to the template
137 | cp .env.example .env
138 | # Edit the configuration file
139 | vim .env
140 | # Start project
141 | npm run dev
142 | ```
143 |
144 | > Please make sure your WeChat account can log in [WeChat on web](https://wx.qq.com/)
145 |
146 | ## 📝 Environment Variables
147 |
148 | | name | description |
149 | |------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
150 | | API | API endpoint of ChatGPT |
151 | | OPENAI_API_KEY | [create new secret key](https://platform.openai.com/account/api-keys) |
152 | | MODEL | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported. |
153 | | 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. |
154 | | CHAT_TRIGGER_RULE | Private chat triggering rules. |
155 | | DISABLE_GROUP_MESSAGE | Prohibited to use ChatGPT in group chat. |
156 | | CHAT_PRIVATE_TRIGGER_KEYWORD | Keyword to trigger ChatGPT reply in WeChat private chat |
157 | | BLOCK_WORDS | Chat blocker words, (works for both private and group chats, Use, Split) |
158 | | CHATGPT_BLOCK_WORDS | The blocked words returned by ChatGPT(works for both private and group chats, Use, Split) |
159 |
160 | ## 📝 Using Custom ChatGPT API
161 |
162 | > https://github.com/fuergaosi233/openai-proxy
163 |
164 | ```shell
165 | # Clone the project
166 | git clone https://github.com/fuergaosi233/openai-proxy
167 | # Install dependencies
168 | npm install && npm install -g wrangler && npm run build
169 | # Deploy to CloudFlare Workers
170 | npm run deploy
171 | # Custom domain (optional)
172 | Add `Route` to `wrangler.toml`
173 | routes = [
174 | { pattern = "Your Custom Domain", custom_domain = true },
175 | ]
176 | ```
177 |
178 | ## ⌨️ Commands
179 | > Enter in the WeChat chat box
180 | ```shell
181 | /cmd help # Show help
182 | /cmd prompt # Set prompt
183 | /cmd clear # Clear all sessions since last boot
184 | ```
185 |
186 | ## ✨ Contributor
187 |
188 |
189 |
190 |
191 |
192 | ## 🤝 Contributing
193 |
194 | Contributions, issues and feature requests are welcome!
Feel free to
195 | check [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues).
196 |
197 | ## Show your support
198 |
199 | Give a ⭐️ if this project helped you!
200 |
--------------------------------------------------------------------------------
/README_ZH.md:
--------------------------------------------------------------------------------
1 | 欢迎使用 wechat-chatgpt 👋
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | > 在微信上迅速接入 ChatGPT,让它成为你最好的助手!
16 | > [English](README.md) | 中文文档
17 |
18 | [](https://railway.app/template/dMLG70?referralCode=bIYugQ)
19 |
20 | ## 🌟 功能点
21 |
22 | - 使用 WeChat 和 ChatGPT 进行互动:
23 | - 基于 [wechaty](https://github.com/wechaty/wechaty) 和 [Official API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis) 在微信中使用 ChatGPT
24 | - 支持多轮对话
25 | - 支持[命令](#-命令)设置
26 |
27 | - 部署和配置选项:
28 | - 提供 Dockerfile,可以通过 [docker](#通过docker使用) 进行部署
29 | - 支持使用 [docker compose](#通过docker-compose使用) 进行部署
30 | - 支持在 [Railway](#使用railway进行部署) 和 [Fly.io](#通过flyio进行部署) 上部署
31 |
32 | - 其他功能:
33 | - 支持 [Dall·E](https://labs.openai.com/)
34 | - 支持 [whisper](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)
35 | - 支持设置 prompt
36 | - 支持代理(开发中)
37 |
38 | ## 🚀 使用
39 |
40 | - [在 Railway 部署](#使用railway进行部署)(PaaS, 免费, 稳定, ✅推荐)
41 | - [在 Fly.io 部署](#通过flyio进行部署)(PaaS, 免费, ✅推荐)
42 | - [使用 Docker 部署](#通过docker使用)(自托管, 稳定, ✅推荐)
43 | - [使用 Docker Compose 部署](#通过docker-compose使用)(自托管, 稳定, ✅推荐)
44 | - [使用 NodeJS 部署](#使用nodejs运行)
45 |
46 | ## 使用Railway进行部署
47 |
48 | > Railway 是一个免费的 PaaS 平台,5刀以内的账单免费或者每个月500小时的运行时间
49 |
50 | 1. 点击 [Railway](https://railway.app/template/dMLG70?referralCode=bIYugQ) 按钮,进入 Railway 部署页面
51 | 2. 点击 `Deploy Now` 按钮,进入 Railway 部署页面
52 | 3. 填写 仓库名称和 `OPENAI_API_KEY`(需要连接 GitHub 账号)
53 | 4. 点击 `Deploy` 按钮
54 | 5. 点击 `View Logs` 按钮,等待部署完成
55 |
56 | ## 通过Fly.io进行部署
57 |
58 | > 请为应用程序分配 512 MB 内存,否则可能会出现内存溢出
59 |
60 | > Fly.io 5刀以内的账单免费(免费计划的3个256MB的应用不在账单内)也就是可以同时可以部署 `1*512MB + 3*256MB`
61 |
62 | 1. 安装 [flyctl](https://fly.io/docs/getting-started/installing-flyctl/)
63 | ```shell
64 | # macOS
65 | brew install flyctl
66 | # Windows
67 | scoop install flyctl
68 | # Linux
69 | curl https://fly.io/install.sh | sh
70 | ```
71 | 2. 克隆项目并进入项目目录
72 | ```shell
73 | git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
74 | ```
75 | 3. 创建应用
76 | ```shell
77 | ➜ flyctl launch
78 | ? Would you like to copy its configuration to the new app? No
79 | ? App Name (leave blank to use an auto-generated name):
80 | ? Select region:
81 | ? Would you like to setup a Postgresql database now? No
82 | ? Would you like to deploy now? No
83 | ```
84 | 4. 配置环境变量
85 | ```shell
86 | flyctl secrets set OPENAI_API_KEY="" MODEL=""
87 | ```
88 | 5. 部署应用
89 | ```shell
90 | flyctl deploy
91 | ```
92 |
93 | ## 通过Docker使用
94 |
95 | ```sh
96 | # 拉取镜像
97 | docker pull holegots/wechat-chatgpt:latest
98 | # 运行容器
99 | docker run -it --name wechat-chatgpt \
100 | -e OPENAI_API_KEY= \
101 | -e MODEL="gpt-3.5-turbo" \
102 | -e CHAT_PRIVATE_TRIGGER_KEYWORD="" \
103 | -v $(pwd)/data:/app/data/wechat-assistant.memory-card.json \
104 | holegots/wechat-chatgpt:latest
105 | # 使用二维码登陆
106 | docker logs -f wechat-chatgpt
107 | ```
108 |
109 | > 如何获取 OPENAI API KEY?请参考 [OpenAI API](https://platform.openai.com/account/api-keys)。
110 |
111 | ## 通过docker compose使用
112 |
113 | ```sh
114 | # 根据模板拷贝配置文件
115 | cp .env.example .env
116 | # 使用你喜欢的文本编辑器修改配置文件
117 | vim .env
118 | # 在Linux或WindowsPowerShell上运行如下命令
119 | docker compose up -d
120 | # 使用二维码登陆
121 | docker logs -f wechat-chatgpt
122 | ```
123 |
124 | ## 使用NodeJS运行
125 |
126 | > 请确认安装的NodeJS版本为18.0.0以上
127 |
128 | ```sh
129 | # 克隆项目
130 | git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt
131 | # 安装依赖
132 | npm install
133 | # 编辑配置
134 | cp .env.example .env
135 | vim .env # 使用你喜欢的文本编辑器修改配置文件
136 | # 启动项目
137 | npm run dev
138 | # 如果您是初次登陆,那么需要扫描二维码
139 | ```
140 |
141 | > 请确保您的账号可以登陆 [网页版微信](https://wx.qq.com/)。
142 |
143 | ## 📝 Environment Variables
144 |
145 | | name | default | example | description |
146 | |--------------------------|------------------------|------------------------------------------------|-------------------------------------------------------------|
147 | | API | https://api.openai.com | | 自定义ChatGPT API 地址 |
148 | | OPENAI_API_KEY | 123456789 | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [创建你的 API 密钥](https://platform.openai.com/account/api-keys) |
149 | | MODEL | gpt-3.5-turbo | | 要使用的模型ID, 目前仅支持`gpt-3.5-turbo` 和 `gpt-3.5-turbo-0301` |
150 | | TEMPERATURE | 0.6 | | 在0和2之间。较高的数值如0.8会使 ChatGPT 输出更加随机,而较低的数值如0.2会使其更加稳定。 |
151 | | CHAT_TRIGGER_RULE | | | 私聊触发规则 |
152 | | DISABLE_GROUP_MESSAGE | true | | 禁用在群聊里使用ChatGPT |
153 | | CHAT_PRIVATE_TRIGGER_KEYWORD | | | 在私聊中触发ChatGPT的关键词, 默认是无需关键词即可触发 |
154 | | BLOCK_WORDS | "VPN" | "WORD1,WORD2,WORD3" | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号 |
155 | | CHATGPT_BLOCK_WORDS | "VPN" | "WORD1,WORD2,WORD3" | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复 |
156 |
157 | ## 📝 使用自定义ChatGPT API
158 | > https://github.com/fuergaosi233/openai-proxy
159 | ```shell
160 | # 克隆项目
161 | git clone https://github.com/fuergaosi233/openai-proxy
162 | # 安装依赖
163 | npm install && npm install -g wrangler && npm run build
164 | # 部署到 CloudFlare Workers
165 | npm run deploy
166 | # 自定义域名(可选)
167 | 添加 `Route`` 到 `wrangler.toml`
168 | routes = [
169 | { pattern = "Your Custom Domain", custom_domain = true },
170 | ]
171 | ```
172 |
173 | ## ⌨️ 命令
174 | > 在微信聊天框中输入
175 | ```shell
176 | /cmd help # 显示帮助信息
177 | /cmd prompt # 设置ChatGPT Prompt
178 | /cmd clear # 清除WeChat-ChatGPT保存的会话记录
179 | ```
180 |
181 | ## ✨ Contributor
182 |
183 |
184 |
185 |
186 |
187 | ## 🤝 为项目添砖加瓦
188 |
189 | 欢迎提出 Contributions, issues 与 feature requests!
190 | 随时查看 [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues).
191 |
192 | ## 感谢支持 🙏
193 |
194 | 如果这个项目对你产生了一点的帮助,请为这个项目点上一颗 ⭐️
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | wechat-chatgpt:
4 | image: wechat-chatgpt
5 | build: .
6 | volumes:
7 | - ./data/wechat-assistant.memory-card.json:/app/wechat-assistant.memory-card.json
8 | env_file:
9 | - .env
--------------------------------------------------------------------------------
/docs/images/railway-deployed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuergaosi233/wechat-chatgpt/e96349ffa2173b20135809601eb7ee7b996a3111/docs/images/railway-deployed.png
--------------------------------------------------------------------------------
/docs/images/railway-deploying.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuergaosi233/wechat-chatgpt/e96349ffa2173b20135809601eb7ee7b996a3111/docs/images/railway-deploying.png
--------------------------------------------------------------------------------
/docs/images/railway-succeed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fuergaosi233/wechat-chatgpt/e96349ffa2173b20135809601eb7ee7b996a3111/docs/images/railway-succeed.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wechat-chatgpt",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dist/main.js",
6 | "export": "dist/main.js",
7 | "scripts": {
8 | "dev": "nodemon --exec node --loader ts-node/esm src/main.ts",
9 | "build": "tsc"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "async-retry": "^1.3.3",
15 | "dotenv": "^16.0.3",
16 | "execa": "^6.1.0",
17 | "gpt3-tokenizer": "^1.1.5",
18 | "openai": "^3.2.1",
19 | "qrcode": "^1.5.1",
20 | "uuid": "^9.0.0",
21 | "wechaty": "^1.20.2",
22 | "wechaty-puppet-wechat": "^1.18.4"
23 | },
24 | "devDependencies": {
25 | "@types/async-retry": "^1.4.5",
26 | "@types/qrcode": "^1.5.0",
27 | "@types/uuid": "^9.0.0",
28 | "nodemon": "^2.0.20",
29 | "ts-node": "^10.9.1"
30 | },
31 | "nodemonConfig": {
32 | "watch": [
33 | "src/*.ts"
34 | ],
35 | "ignore": [
36 | "src/main.ts"
37 | ],
38 | "ext": "ts",
39 | "exec": "node --loader ts-node/esm src/main.ts",
40 | "delay": 500
41 | },
42 | "type": "module"
43 | }
44 |
--------------------------------------------------------------------------------
/public/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
--------------------------------------------------------------------------------
/src/bot.ts:
--------------------------------------------------------------------------------
1 | import { config } from "./config.js";
2 | import {ContactImpl, ContactInterface, RoomImpl, RoomInterface} from "wechaty/impls";
3 | import { Message } from "wechaty";
4 | import {FileBox} from "file-box";
5 | import {chatgpt, dalle, whisper} from "./openai.js";
6 | import DBUtils from "./data.js";
7 | import { regexpEncode } from "./utils.js";
8 | enum MessageType {
9 | Unknown = 0,
10 | Attachment = 1, // Attach(6),
11 | Audio = 2, // Audio(1), Voice(34)
12 | Contact = 3, // ShareCard(42)
13 | ChatHistory = 4, // ChatHistory(19)
14 | Emoticon = 5, // Sticker: Emoticon(15), Emoticon(47)
15 | Image = 6, // Img(2), Image(3)
16 | Text = 7, // Text(1)
17 | Location = 8, // Location(48)
18 | MiniProgram = 9, // MiniProgram(33)
19 | GroupNote = 10, // GroupNote(53)
20 | Transfer = 11, // Transfers(2000)
21 | RedEnvelope = 12, // RedEnvelopes(2001)
22 | Recalled = 13, // Recalled(10002)
23 | Url = 14, // Url(5)
24 | Video = 15, // Video(4), Video(43)
25 | Post = 16, // Moment, Channel, Tweet, etc
26 | }
27 | const SINGLE_MESSAGE_MAX_SIZE = 500;
28 | type Speaker = RoomImpl | ContactImpl;
29 | interface ICommand{
30 | name:string;
31 | description:string;
32 | exec: (talker:Speaker, text:string) => Promise;
33 | }
34 | export class ChatGPTBot {
35 | chatPrivateTriggerKeyword = config.chatPrivateTriggerKeyword;
36 | chatTriggerRule = config.chatTriggerRule? new RegExp(config.chatTriggerRule): undefined;
37 | disableGroupMessage = config.disableGroupMessage || false;
38 | botName: string = "";
39 | ready = false;
40 | setBotName(botName: string) {
41 | this.botName = botName;
42 | }
43 | get chatGroupTriggerRegEx(): RegExp {
44 | return new RegExp(`^@${regexpEncode(this.botName)}\\s`);
45 | }
46 | get chatPrivateTriggerRule(): RegExp | undefined {
47 | const { chatPrivateTriggerKeyword, chatTriggerRule } = this;
48 | let regEx = chatTriggerRule
49 | if (!regEx && chatPrivateTriggerKeyword) {
50 | regEx = new RegExp(regexpEncode(chatPrivateTriggerKeyword))
51 | }
52 | return regEx
53 | }
54 | private readonly commands:ICommand[] = [
55 | {
56 | name: "help",
57 | description: "显示帮助信息",
58 | exec: async (talker) => {
59 | await this.trySay(talker,"========\n" +
60 | "/cmd help\n" +
61 | "# 显示帮助信息\n" +
62 | "/cmd prompt \n" +
63 | "# 设置当前会话的 prompt \n" +
64 | "/img \n" +
65 | "# 根据 prompt 生成图片\n" +
66 | "/cmd clear\n" +
67 | "# 清除自上次启动以来的所有会话\n" +
68 | "========");
69 | }
70 | },
71 | {
72 | name: "prompt",
73 | description: "设置当前会话的prompt",
74 | exec: async (talker, prompt) => {
75 | if (talker instanceof RoomImpl) {
76 | DBUtils.setPrompt(await talker.topic(), prompt);
77 | }else {
78 | DBUtils.setPrompt(talker.name(), prompt);
79 | }
80 | }
81 | },
82 | {
83 | name: "clear",
84 | description: "清除自上次启动以来的所有会话",
85 | exec: async (talker) => {
86 | if (talker instanceof RoomImpl) {
87 | DBUtils.clearHistory(await talker.topic());
88 | }else{
89 | DBUtils.clearHistory(talker.name());
90 | }
91 | }
92 | }
93 | ]
94 |
95 | /**
96 | * EXAMPLE:
97 | * /cmd help
98 | * /cmd prompt
99 | * /cmd img
100 | * /cmd clear
101 | * @param contact
102 | * @param rawText
103 | */
104 | async command(contact: any, rawText: string): Promise {
105 | const [commandName, ...args] = rawText.split(/\s+/);
106 | const command = this.commands.find(
107 | (command) => command.name === commandName
108 | );
109 | if (command) {
110 | await command.exec(contact, args.join(" "));
111 | }
112 | }
113 | // remove more times conversation and mention
114 | cleanMessage(rawText: string, privateChat: boolean = false): string {
115 | let text = rawText;
116 | const item = rawText.split("- - - - - - - - - - - - - - -");
117 | if (item.length > 1) {
118 | text = item[item.length - 1];
119 | }
120 |
121 | const { chatTriggerRule, chatPrivateTriggerRule } = this;
122 |
123 | if (privateChat && chatPrivateTriggerRule) {
124 | text = text.replace(chatPrivateTriggerRule, "")
125 | } else if (!privateChat) {
126 | text = text.replace(this.chatGroupTriggerRegEx, "")
127 | text = chatTriggerRule? text.replace(chatTriggerRule, ""): text
128 | }
129 | // remove more text via - - - - - - - - - - - - - - -
130 | return text
131 | }
132 | async getGPTMessage(talkerName: string,text: string): Promise {
133 | let gptMessage = await chatgpt(talkerName,text);
134 | if (gptMessage !=="") {
135 | DBUtils.addAssistantMessage(talkerName,gptMessage);
136 | return gptMessage;
137 | }
138 | return "Sorry, please try again later. 😔";
139 | }
140 | // Check if the message returned by chatgpt contains masked words]
141 | checkChatGPTBlockWords(message: string): boolean {
142 | if (config.chatgptBlockWords.length == 0) {
143 | return false;
144 | }
145 | return config.chatgptBlockWords.some((word) => message.includes(word));
146 | }
147 | // The message is segmented according to its size
148 | async trySay(
149 | talker: RoomInterface | ContactInterface,
150 | mesasge: string
151 | ): Promise {
152 | const messages: Array = [];
153 | if (this.checkChatGPTBlockWords(mesasge)) {
154 | console.log(`🚫 Blocked ChatGPT: ${mesasge}`);
155 | return;
156 | }
157 | let message = mesasge;
158 | while (message.length > SINGLE_MESSAGE_MAX_SIZE) {
159 | messages.push(message.slice(0, SINGLE_MESSAGE_MAX_SIZE));
160 | message = message.slice(SINGLE_MESSAGE_MAX_SIZE);
161 | }
162 | messages.push(message);
163 | for (const msg of messages) {
164 | await talker.say(msg);
165 | }
166 | }
167 | // Check whether the ChatGPT processing can be triggered
168 | triggerGPTMessage(text: string, privateChat: boolean = false): boolean {
169 | const { chatTriggerRule } = this;
170 | let triggered = false;
171 | if (privateChat) {
172 | const regEx = this.chatPrivateTriggerRule
173 | triggered = regEx? regEx.test(text): true;
174 | } else {
175 | triggered = this.chatGroupTriggerRegEx.test(text);
176 | // group message support `chatTriggerRule`
177 | if (triggered && chatTriggerRule) {
178 | triggered = chatTriggerRule.test(text.replace(this.chatGroupTriggerRegEx, ""))
179 | }
180 | }
181 | if (triggered) {
182 | console.log(`🎯 Triggered ChatGPT: ${text}`);
183 | }
184 | return triggered;
185 | }
186 | // Check whether the message contains the blocked words. if so, the message will be ignored. if so, return true
187 | checkBlockWords(message: string): boolean {
188 | if (config.blockWords.length == 0) {
189 | return false;
190 | }
191 | return config.blockWords.some((word) => message.includes(word));
192 | }
193 | // Filter out the message that does not need to be processed
194 | isNonsense(
195 | talker: ContactInterface,
196 | messageType: MessageType,
197 | text: string
198 | ): boolean {
199 | return (
200 | talker.self() ||
201 | // TODO: add doc support
202 | !(messageType == MessageType.Text || messageType == MessageType.Audio) ||
203 | talker.name() === "微信团队" ||
204 | // 语音(视频)消息
205 | text.includes("收到一条视频/语音聊天消息,请在手机上查看") ||
206 | // 红包消息
207 | text.includes("收到红包,请在手机上查看") ||
208 | // Transfer message
209 | text.includes("收到转账,请在手机上查看") ||
210 | // 位置消息
211 | text.includes("/cgi-bin/mmwebwx-bin/webwxgetpubliclinkimg") ||
212 | // 聊天屏蔽词
213 | this.checkBlockWords(text)
214 | );
215 | }
216 |
217 | async onPrivateMessage(talker: ContactInterface, text: string) {
218 | const gptMessage = await this.getGPTMessage(talker.name(),text);
219 | await this.trySay(talker, gptMessage);
220 | }
221 |
222 | async onGroupMessage(
223 | talker: ContactInterface,
224 | text: string,
225 | room: RoomInterface
226 | ) {
227 | const gptMessage = await this.getGPTMessage(await room.topic(),text);
228 | const result = `@${talker.name()} ${text}\n\n------\n ${gptMessage}`;
229 | await this.trySay(room, result);
230 | }
231 | async onMessage(message: Message) {
232 | const talker = message.talker();
233 | const rawText = message.text();
234 | const room = message.room();
235 | const messageType = message.type();
236 | const privateChat = !room;
237 | if (privateChat) {
238 | console.log(`🤵 Contact: ${talker.name()} 💬 Text: ${rawText}`)
239 | } else {
240 | const topic = await room.topic()
241 | console.log(`🚪 Room: ${topic} 🤵 Contact: ${talker.name()} 💬 Text: ${rawText}`)
242 | }
243 | if (this.isNonsense(talker, messageType, rawText)) {
244 | return;
245 | }
246 | if (messageType == MessageType.Audio){
247 | // 保存语音文件
248 | const fileBox = await message.toFileBox();
249 | let fileName = "./public/" + fileBox.name;
250 | await fileBox.toFile(fileName, true).catch((e) => {
251 | console.log("保存语音失败",e);
252 | return;
253 | });
254 | // Whisper
255 | whisper("",fileName).then((text) => {
256 | message.say(text);
257 | })
258 | return;
259 | }
260 | if (rawText.startsWith("/cmd ")){
261 | console.log(`🤖 Command: ${rawText}`)
262 | const cmdContent = rawText.slice(5) // 「/cmd 」一共5个字符(注意空格)
263 | if (privateChat) {
264 | await this.command(talker, cmdContent);
265 | }else{
266 | await this.command(room, cmdContent);
267 | }
268 | return;
269 | }
270 | // 使用DallE生成图片
271 | if (rawText.startsWith("/img")){
272 | console.log(`🤖 Image: ${rawText}`)
273 | const imgContent = rawText.slice(4)
274 | if (privateChat) {
275 | let url = await dalle(talker.name(), imgContent) as string;
276 | const fileBox = FileBox.fromUrl(url)
277 | message.say(fileBox)
278 | }else{
279 | let url = await dalle(await room.topic(), imgContent) as string;
280 | const fileBox = FileBox.fromUrl(url)
281 | message.say(fileBox)
282 | }
283 | return;
284 | }
285 | if (this.triggerGPTMessage(rawText, privateChat)) {
286 | const text = this.cleanMessage(rawText, privateChat);
287 | if (privateChat) {
288 | return await this.onPrivateMessage(talker, text);
289 | } else{
290 | if (!this.disableGroupMessage){
291 | return await this.onGroupMessage(talker, text, room);
292 | } else {
293 | return;
294 | }
295 | }
296 | } else {
297 | return;
298 | }
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from "dotenv";
2 | dotenv.config();
3 | import { IConfig } from "./interface";
4 |
5 | export const config: IConfig = {
6 | api: process.env.API,
7 | openai_api_key: process.env.OPENAI_API_KEY || "123456789",
8 | model: process.env.MODEL || "gpt-3.5-turbo",
9 | chatPrivateTriggerKeyword: process.env.CHAT_PRIVATE_TRIGGER_KEYWORD || "",
10 | chatTriggerRule: process.env.CHAT_TRIGGER_RULE || "",
11 | disableGroupMessage: process.env.DISABLE_GROUP_MESSAGE === "true",
12 | temperature: process.env.TEMPERATURE ? parseFloat(process.env.TEMPERATURE) : 0.6,
13 | blockWords: process.env.BLOCK_WORDS?.split(",") || [],
14 | chatgptBlockWords: process.env.CHATGPT_BLOCK_WORDS?.split(",") || [],
15 | };
16 |
--------------------------------------------------------------------------------
/src/data.ts:
--------------------------------------------------------------------------------
1 | import {ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum} from "openai";
2 | import {User} from "./interface";
3 | import {isTokenOverLimit} from "./utils.js";
4 |
5 | /**
6 | * 使用内存作为数据库
7 | */
8 |
9 | class DB {
10 | private static data: User[] = [];
11 |
12 | /**
13 | * 添加一个用户, 如果用户已存在则返回已存在的用户
14 | * @param username
15 | */
16 | public addUser(username: string): User {
17 | let existUser = DB.data.find((user) => user.username === username);
18 | if (existUser) {
19 | console.log(`用户${username}已存在`);
20 | return existUser;
21 | }
22 | const newUser: User = {
23 | username: username,
24 | chatMessage: [
25 | {
26 | role: ChatCompletionRequestMessageRoleEnum.System,
27 | content: "You are a helpful assistant."
28 | }
29 | ],
30 | };
31 | DB.data.push(newUser);
32 | return newUser;
33 | }
34 |
35 | /**
36 | * 根据用户名获取用户, 如果用户不存在则添加用户
37 | * @param username
38 | */
39 | public getUserByUsername(username: string): User {
40 | return DB.data.find((user) => user.username === username) || this.addUser(username);
41 | }
42 |
43 | /**
44 | * 获取用户的聊天记录
45 | * @param username
46 | */
47 | public getChatMessage(username: string): Array {
48 | return this.getUserByUsername(username).chatMessage;
49 | }
50 |
51 | /**
52 | * 设置用户的prompt
53 | * @param username
54 | * @param prompt
55 | */
56 | public setPrompt(username: string, prompt: string): void {
57 | const user = this.getUserByUsername(username);
58 | if (user) {
59 | user.chatMessage.find(
60 | (msg) => msg.role === ChatCompletionRequestMessageRoleEnum.System
61 | )!.content = prompt;
62 | }
63 | }
64 |
65 | /**
66 | * 添加用户输入的消息
67 | * @param username
68 | * @param message
69 | */
70 | public addUserMessage(username: string, message: string): void {
71 | const user = this.getUserByUsername(username);
72 | if (user) {
73 | while (isTokenOverLimit(user.chatMessage)){
74 | // 删除从第2条开始的消息(因为第一条是prompt)
75 | user.chatMessage.splice(1,1);
76 | }
77 | user.chatMessage.push({
78 | role: ChatCompletionRequestMessageRoleEnum.User,
79 | content: message,
80 | });
81 | }
82 | }
83 |
84 | /**
85 | * 添加ChatGPT的回复
86 | * @param username
87 | * @param message
88 | */
89 | public addAssistantMessage(username: string, message: string): void {
90 | const user = this.getUserByUsername(username);
91 | if (user) {
92 | while (isTokenOverLimit(user.chatMessage)){
93 | // 删除从第2条开始的消息(因为第一条是prompt)
94 | user.chatMessage.splice(1,1);
95 | }
96 | user.chatMessage.push({
97 | role: ChatCompletionRequestMessageRoleEnum.Assistant,
98 | content: message,
99 | });
100 | }
101 | }
102 |
103 | /**
104 | * 清空用户的聊天记录, 并将prompt设置为默认值
105 | * @param username
106 | */
107 | public clearHistory(username: string): void {
108 | const user = this.getUserByUsername(username);
109 | if (user) {
110 | user.chatMessage = [
111 | {
112 | role: ChatCompletionRequestMessageRoleEnum.System,
113 | content: "You are a helpful assistant."
114 | }
115 | ];
116 | }
117 | }
118 |
119 | public getAllData(): User[] {
120 | return DB.data;
121 | }
122 | }
123 | const DBUtils = new DB();
124 | export default DBUtils;
--------------------------------------------------------------------------------
/src/interface.ts:
--------------------------------------------------------------------------------
1 | import {ChatCompletionRequestMessage} from "openai";
2 |
3 | export interface IConfig {
4 | api?: string;
5 | openai_api_key: string;
6 | model: string;
7 | chatTriggerRule: string;
8 | disableGroupMessage: boolean;
9 | temperature: number;
10 | blockWords: string[];
11 | chatgptBlockWords: string[];
12 | chatPrivateTriggerKeyword: string;
13 | }
14 | export interface User {
15 | username: string,
16 | chatMessage: Array,
17 | }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { WechatyBuilder } from "wechaty";
2 | import QRCode from "qrcode";
3 | import { ChatGPTBot } from "./bot.js";
4 | import {config} from "./config.js";
5 | const chatGPTBot = new ChatGPTBot();
6 |
7 | const bot = WechatyBuilder.build({
8 | name: "wechat-assistant", // generate xxxx.memory-card.json and save login data for the next login
9 | puppet: "wechaty-puppet-wechat",
10 | puppetOptions: {
11 | uos: true
12 | }
13 | });
14 | async function main() {
15 | const initializedAt = Date.now()
16 | bot
17 | .on("scan", async (qrcode, status) => {
18 | const url = `https://wechaty.js.org/qrcode/${encodeURIComponent(qrcode)}`;
19 | console.log(`Scan QR Code to login: ${status}\n${url}`);
20 | console.log(
21 | await QRCode.toString(qrcode, { type: "terminal", small: true })
22 | );
23 | })
24 | .on("login", async (user) => {
25 | chatGPTBot.setBotName(user.name());
26 | console.log(`User ${user} logged in`);
27 | console.log(`私聊触发关键词: ${config.chatPrivateTriggerKeyword}`);
28 | console.log(`已设置 ${config.blockWords.length} 个聊天关键词屏蔽. ${config.blockWords}`);
29 | console.log(`已设置 ${config.chatgptBlockWords.length} 个ChatGPT回复关键词屏蔽. ${config.chatgptBlockWords}`);
30 | })
31 | .on("message", async (message) => {
32 | if (message.date().getTime() < initializedAt) {
33 | return;
34 | }
35 | if (message.text().startsWith("/ping")) {
36 | await message.say("pong");
37 | return;
38 | }
39 | try {
40 | await chatGPTBot.onMessage(message);
41 | } catch (e) {
42 | console.error(e);
43 | }
44 | });
45 | try {
46 | await bot.start();
47 | } catch (e) {
48 | console.error(
49 | `⚠️ Bot start failed, can you log in through wechat on the web?: ${e}`
50 | );
51 | }
52 | }
53 | main();
54 |
--------------------------------------------------------------------------------
/src/openai.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Configuration,
3 | CreateImageRequestResponseFormatEnum,
4 | CreateImageRequestSizeEnum,
5 | OpenAIApi
6 | } from "openai";
7 | import fs from "fs";
8 | import DBUtils from "./data.js";
9 | import {config} from "./config.js";
10 |
11 | const configuration = new Configuration({
12 | apiKey: config.openai_api_key,
13 | basePath: config.api,
14 | });
15 | const openai = new OpenAIApi(configuration);
16 |
17 | /**
18 | * Get completion from OpenAI
19 | * @param username
20 | * @param message
21 | */
22 | async function chatgpt(username:string,message: string): Promise {
23 | // 先将用户输入的消息添加到数据库中
24 | DBUtils.addUserMessage(username, message);
25 | const messages = DBUtils.getChatMessage(username);
26 | const response = await openai.createChatCompletion({
27 | model: "gpt-3.5-turbo",
28 | messages: messages,
29 | temperature: config.temperature,
30 | });
31 | let assistantMessage = "";
32 | try {
33 | if (response.status === 200) {
34 | assistantMessage = response.data.choices[0].message?.content.replace(/^\n+|\n+$/g, "") as string;
35 | }else{
36 | console.log(`Something went wrong,Code: ${response.status}, ${response.statusText}`)
37 | }
38 | }catch (e:any) {
39 | if (e.request){
40 | console.log("请求出错");
41 | }
42 | }
43 | return assistantMessage;
44 | }
45 |
46 | /**
47 | * Get image from Dall·E
48 | * @param username
49 | * @param prompt
50 | */
51 | async function dalle(username:string,prompt: string) {
52 | const response = await openai.createImage({
53 | prompt: prompt,
54 | n:1,
55 | size: CreateImageRequestSizeEnum._256x256,
56 | response_format: CreateImageRequestResponseFormatEnum.Url,
57 | user: username
58 | }).then((res) => res.data).catch((err) => console.log(err));
59 | if (response) {
60 | return response.data[0].url;
61 | }else{
62 | return "Generate image failed"
63 | }
64 | }
65 |
66 | /**
67 | * Speech to text
68 | * @param username
69 | * @param videoPath
70 | */
71 | async function whisper(username:string,videoPath: string): Promise {
72 | const file:any= fs.createReadStream(videoPath);
73 | const response = await openai.createTranscription(file,"whisper-1")
74 | .then((res) => res.data).catch((err) => console.log(err));
75 | if (response) {
76 | return response.text;
77 | }else{
78 | return "Speech to text failed"
79 | }
80 | }
81 |
82 | export {chatgpt,dalle,whisper};
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import {ChatCompletionRequestMessage} from "openai";
2 |
3 | import GPT3TokenizerImport from 'gpt3-tokenizer';
4 | import {config} from "./config.js";
5 |
6 | export const regexpEncode = (str: string) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
7 |
8 | const GPT3Tokenizer: typeof GPT3TokenizerImport =
9 | typeof GPT3TokenizerImport === 'function'
10 | ? GPT3TokenizerImport
11 | : (GPT3TokenizerImport as any).default;
12 | // https://github.com/chathub-dev/chathub/blob/main/src/app/bots/chatgpt-api/usage.ts
13 | const tokenizer = new GPT3Tokenizer({ type: 'gpt3' })
14 | function calTokens(chatMessage:ChatCompletionRequestMessage[]):number {
15 | let count = 0
16 | for (const msg of chatMessage) {
17 | count += countTokens(msg.content)
18 | count += countTokens(msg.role)
19 | }
20 | return count + 2
21 | }
22 |
23 | function countTokens(str: string):number {
24 | const encoded = tokenizer.encode(str)
25 | return encoded.bpe.length
26 | }
27 | export function isTokenOverLimit(chatMessage:ChatCompletionRequestMessage[]): boolean {
28 | let limit = 4096;
29 | if (config.model==="gpt-3.5-turbo" || config.model==="gpt-3.5-turbo-0301") {
30 | limit = 4096;
31 | }
32 | return calTokens(chatMessage) > limit;
33 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "esnext", /* Specify what module code is generated. */
28 | "rootDir": "src", /* Specify the root folder within your source files. */
29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "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. */
50 | "outDir": "dist", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | }
101 | }
102 |
--------------------------------------------------------------------------------