[
  {
    "path": ".dockerignore",
    "content": "# Created by https://www.toptal.com/developers/gitignore/api/node\n# Edit at https://www.toptal.com/developers/gitignore?templates=node\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n### Node Patch ###\n# Serverless Webpack directories\n.webpack/\n\n# Optional stylelint cache\n\n# SvelteKit build / generate output\n.svelte-kit\n\n# End of https://www.toptal.com/developers/gitignore/api/node\nn\n*memory-card.json\n# Created by https://www.toptal.com/developers/gitignore/api/python\n# Edit at https://www.toptal.com/developers/gitignore?templates=python\n\n### Python ###\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n### Python Patch ###\n# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration\npoetry.toml\n\ndocs/\n\n\n# End of https://www.toptal.com/developers/gitignore/api/python\nn\nconfig.json\ncache.json\nconfig.yaml\n.vscode"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\ndescription: \"Create a report to help us improve.\"\nbody:\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Welcome\n      options:\n        - label: Yes, I've searched similar issues on GitHub and didn't find any.\n          required: true\n        - label: Yes, I've included all information below (version, **FULL** config, **FULL** log, etc).\n          required: true\n  - type: checkboxes\n    id: platform\n    attributes:\n      label: Deployment Platform\n      options:\n        - label: Railway\n        - label: Fly.io\n        - label: Docker & Docker Compose\n        - label: Node.js\n  - type: textarea\n    id: problem\n    attributes:\n      label: Description of the problem\n      placeholder: Your problem description\n    validations:\n      required: true\n  - type: textarea\n    id: log\n    attributes:\n      label: wechat-chatgpt operation log\n      value: |-\n        <details>\n        ```console\n        # Paste output here\n        ```\n        </details>\n    validations:\n      required: true"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Discord Chat\n    url: https://discord.gg/8xWdppS7bE\n    about: Join our Discord server to chat with the community and get help with your issue."
  },
  {
    "path": ".github/workflows/publish-docker-hub.yml",
    "content": "name: Publish Docker image\n\non:\n  # Test pub with dev tag\n  push:\n    branches:\n      - ci/fix-muti-platform\n  release:\n    types: [published]\n\njobs:\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out the repo\n        uses: actions/checkout@v3\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: Log in to Docker Hub\n        uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38\n        with:\n          images: holegots/wechat-chatgpt\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc\n        with:\n          context: .\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          # FIXME: Chrome doesn't seem to install on ARM, so skip it for now\n          # platforms: linux/amd64,linux/arm64\n          labels: ${{ steps.meta.outputs.labels }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by https://www.toptal.com/developers/gitignore/api/node\n# Edit at https://www.toptal.com/developers/gitignore?templates=node\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n### Node Patch ###\n# Serverless Webpack directories\n.webpack/\n\n# Optional stylelint cache\n\n# SvelteKit build / generate output\n.svelte-kit\n\n# End of https://www.toptal.com/developers/gitignore/api/node\nn\n*memory-card.json\n# Created by https://www.toptal.com/developers/gitignore/api/python\n# Edit at https://www.toptal.com/developers/gitignore?templates=python\n\n### Python ###\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n.idea/\n\n### Python Patch ###\n# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration\npoetry.toml\n\n\n# End of https://www.toptal.com/developers/gitignore/api/python\nn\nconfig.json\ncache.json\nconfig.yaml\n.vscode\n\ndata/\n"
  },
  {
    "path": "Dockerfile",
    "content": "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://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \\\n    && echo \"deb http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list \\\n    && apt-get update && apt-get -y install google-chrome-stable chromium  xvfb\\\n    && rm -rf /var/lib/apt/lists/* \\\n    && echo \"Chrome: \" && google-chrome --version\nWORKDIR /app\nCOPY package*.json ./\nRUN npm install\nCOPY . .\nCMD xvfb-run --server-args=\"-screen 0 1280x800x24 -ac -nolisten tcp -dpi 96 +extension RANDR\" npm run dev"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\" >\n\n<h1 style=\"color:red; font-size:48px;\">!!!! Project Archived 📦 !!!!</h1>\n\n> This project has been archived. Thank you to everyone who contributed! 🙌😔\n</div>\n\n\n<h1 align=\"center\">Welcome to wechat-chatgpt 👋</h1>\n<p>\n  <img alt=\"Version\" src=\"https://img.shields.io/badge/version-1.0.0-blue.svg?cacheSeconds=2592000\" />\n  <a href=\"#\" target=\"_blank\">\n    <img alt=\"License: ISC\" src=\"https://img.shields.io/badge/License-ISC-yellow.svg\" />\n  </a>\n  <a href=\"https://twitter.com/fuergaosi\" target=\"_blank\">\n    <img alt=\"Twitter: fuergaosi\" src=\"https://img.shields.io/twitter/follow/fuergaosi.svg?style=social\" />\n  </a>\n  </a>\n  <a href=\"https://discord.gg/8fXNrxwUJH\" target=\"blank\">\n    <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\"/>\n  </a>\n</p>\n\n> Use ChatGPT On Wechat via wechaty  \n> English | [中文文档](README_ZH.md)\n\n[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/dMLG70?referralCode=bIYugQ)\n\n## 🌟 Features\n\n- Interact with WeChat and ChatGPT:\n   - Use ChatGPT on WeChat with [wechaty](https://github.com/wechaty/wechaty) and [Official API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)\n   - Add conversation support\n   - Support command setting\n\n- Deployment and configuration options:\n   - Add Dockerfile, deployable with [docker](#use-with-docker)\n   - Support deployment using [docker compose](#use-with-docker-compose)\n   - Support [Railway](#use-with-railway) and [Fly.io](#use-with-flyio) deployment\n\n- Other features:\n   - Support [Dall·E](https://labs.openai.com/)\n   - Support [whisper](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)\n   - Support setting prompt\n   - Support proxy (in development)\n\n## 🚀 Usage\n- [Use with Railway](#use-with-railway)(PaaS, Free, Stable, ✅Recommended)\n- [Use with Fly.io](#use-with-flyio)(Paas, Free, ✅Recommended)\n- [Use with docker](#use-with-docker)(Self-hosted, Stable, ✅Recommended)\n- [Use with docker compose](#use-with-docker-compose)(Self-hosted, Stable, ✅Recommended)\n- [Use with nodejs](#use-with-nodejs)(Self-hosted)\n\n## Use with Railway\n> Railway offers $5 or 500 hours of runtime per month\n1. Click the [Railway](https://railway.app/template/dMLG70?referralCode=bIYugQ) button to go to the Railway deployment page\n2. Click the `Deploy Now` button to enter the Railway deployment page\n3. Fill in the repository name and `OPENAI_API_KEY` (need to link GitHub account)\n4. Click the `Deploy` button\n5. Click the `View Logs` button and wait for the deployment to complete\n\n## Use with Fly.io\n> Please allocate 512MB memory for the application to meet the application requirements\n\n> fly.io offers free bills up to $5(Free Allowances 3 256MB are not included in the bill)\n1. Install [flyctl](https://fly.io/docs/getting-started/installing-flyctl/)\n   ```shell\n    # macOS\n    brew install flyctl\n    # Windows\n    scoop install flyctl\n    # Linux\n    curl https://fly.io/install.sh | sh\n   ```\n2. Clone the project and enter the project directory\n   ```shell\n   git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt\n   ```\n3. Create a new app\n   ```shell\n   ➜ flyctl launch \n    ? Would you like to copy its configuration to the new app? No\n    ? App Name (leave blank to use an auto-generated name): <YOUR APP NAME>\n    ? Select region: <YOUR CHOOSE REGION>\n    ? Would you like to setup a Postgresql database now? No\n    ? Would you like to deploy now? No\n   ```\n4. Configure the environment variables\n   ```shell\n   flyctl secrets set OPENAI_API_KEY=\"<YOUR OPENAI API KEY>\" MODEL=\"<CHATGPT-MODEL>\"\n   ```\n5. Deploy the app\n   ```shell\n   flyctl deploy\n   ```\n\n## Use with docker\n\n```sh\n# pull image\ndocker pull holegots/wechat-chatgpt\n# run container\ndocker run -d --name wechat-chatgpt \\\n    -e OPENAI_API_KEY=<YOUR OPENAI API KEY> \\\n    -e MODEL=\"gpt-3.5-turbo\" \\\n    -e CHAT_PRIVATE_TRIGGER_KEYWORD=\"\" \\\n    -v $(pwd)/data:/app/data/wechat-assistant.memory-card.json \\\n    holegots/wechat-chatgpt:latest\n# View the QR code to log in to wechat\ndocker logs -f wechat-chatgpt\n```\n> How to get OPENAI API KEY? [Click here](https://platform.openai.com/account/api-keys)\n\n## Use with docker compose\n\n```sh\n# Copy the configuration file according to the template\ncp .env.example .env\n# Edit the configuration file\nvim .env\n# Start the container\ndocker-compose up -d\n# View the QR code to log in to wechat\ndocker logs -f wechat-chatgpt\n```\n\n## Use with nodejs\n\n> You need NodeJS 18.0.0 version and above\n\n```sh\n# Clone the project\ngit clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt\n# Install dependencies\nnpm install\n# Copy the configuration file according to the template\ncp .env.example .env\n# Edit the configuration file\nvim .env\n# Start project\nnpm run dev\n```\n\n> Please make sure your WeChat account can log in [WeChat on web](https://wx.qq.com/)\n\n## 📝 Environment Variables\n\n| name                         | description                                                                                                                                                                          |\n|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| API                          | API endpoint of ChatGPT                                                                                                                                                              |\n| OPENAI_API_KEY               | [create new secret key](https://platform.openai.com/account/api-keys)                                                                                                                |\n| MODEL                        | ID of the model to use. Currently, only gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported.                                                                                          |\n| 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. |\n| CHAT_TRIGGER_RULE            | Private chat triggering rules.                                                                                                                                                       |\n| DISABLE_GROUP_MESSAGE        | Prohibited to use ChatGPT in group chat.                                                                                                                                             |\n| CHAT_PRIVATE_TRIGGER_KEYWORD | Keyword to trigger ChatGPT reply in WeChat private chat                                                                                                                              |\n| BLOCK_WORDS                  | Chat blocker words, (works for both private and group chats, Use, Split)                                                                                                             |\n| CHATGPT_BLOCK_WORDS          | The blocked words returned by ChatGPT(works for both private and group chats, Use, Split)                                                                                            |\n\n## 📝 Using Custom ChatGPT API\n\n> https://github.com/fuergaosi233/openai-proxy\n\n```shell\n# Clone the project\ngit clone https://github.com/fuergaosi233/openai-proxy\n# Install dependencies\nnpm install && npm install -g wrangler && npm run build\n# Deploy to CloudFlare Workers\nnpm run deploy\n# Custom domain (optional)\nAdd `Route` to `wrangler.toml`\nroutes = [\n    { pattern = \"Your Custom Domain\", custom_domain = true },\n]\n```\n\n## ⌨️ Commands\n> Enter in the WeChat chat box\n```shell\n/cmd help # Show help\n/cmd prompt <PROMPT> # Set prompt\n/cmd clear # Clear all sessions since last boot\n```\n\n## ✨ Contributor\n\n<a href=\"https://github.com/fuergaosi233/wechat-chatgpt/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=fuergaosi233/wechat-chatgpt\" />\n</a>\n\n## 🤝 Contributing\n\nContributions, issues and feature requests are welcome!<br />Feel free to\ncheck [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues).\n\n## Show your support\n\nGive a ⭐️ if this project helped you!\n"
  },
  {
    "path": "README_ZH.md",
    "content": "<h1 align=\"center\">欢迎使用 wechat-chatgpt 👋</h1>\n<p>\n  <img alt=\"Version\" src=\"https://img.shields.io/badge/version-1.0.0-blue.svg?cacheSeconds=2592000\" />\n  <a href=\"#\" target=\"_blank\">\n    <img alt=\"License: ISC\" src=\"https://img.shields.io/badge/License-ISC-yellow.svg\" />\n  </a>\n  <a href=\"https://twitter.com/fuergaosi\" target=\"_blank\">\n    <img alt=\"Twitter: fuergaosi\" src=\"https://img.shields.io/twitter/follow/fuergaosi.svg?style=social\" />\n  </a>\n  <a href=\"https://discord.gg/8fXNrxwUJH\" target=\"blank\">\n    <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\"/>\n  </a>\n</p>\n\n> 在微信上迅速接入 ChatGPT，让它成为你最好的助手！  \n> [English](README.md) | 中文文档\n\n[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/dMLG70?referralCode=bIYugQ)\n\n## 🌟 功能点\n\n- 使用 WeChat 和 ChatGPT 进行互动：\n   - 基于 [wechaty](https://github.com/wechaty/wechaty) 和 [Official API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis) 在微信中使用 ChatGPT\n   - 支持多轮对话\n   - 支持[命令](#-命令)设置\n\n- 部署和配置选项：\n   - 提供 Dockerfile，可以通过 [docker](#通过docker使用) 进行部署\n   - 支持使用 [docker compose](#通过docker-compose使用) 进行部署\n   - 支持在 [Railway](#使用railway进行部署) 和 [Fly.io](#通过flyio进行部署) 上部署\n\n- 其他功能：\n   - 支持 [Dall·E](https://labs.openai.com/)\n   - 支持 [whisper](https://openai.com/blog/introducing-chatgpt-and-whisper-apis)\n   - 支持设置 prompt\n   - 支持代理（开发中）\n\n## 🚀 使用\n\n- [在 Railway 部署](#使用railway进行部署)(PaaS, 免费, 稳定, ✅推荐)\n- [在 Fly.io 部署](#通过flyio进行部署)(PaaS, 免费, ✅推荐)\n- [使用 Docker 部署](#通过docker使用)(自托管, 稳定, ✅推荐)\n- [使用 Docker Compose 部署](#通过docker-compose使用)(自托管, 稳定, ✅推荐)\n- [使用 NodeJS 部署](#使用nodejs运行)\n\n## 使用Railway进行部署\n\n> Railway 是一个免费的 PaaS 平台，5刀以内的账单免费或者每个月500小时的运行时间\n\n1. 点击 [Railway](https://railway.app/template/dMLG70?referralCode=bIYugQ) 按钮，进入 Railway 部署页面\n2. 点击 `Deploy Now` 按钮，进入 Railway 部署页面\n3. 填写 仓库名称和 `OPENAI_API_KEY`(需要连接 GitHub 账号)\n4. 点击 `Deploy` 按钮\n5. 点击 `View Logs` 按钮，等待部署完成\n\n## 通过Fly.io进行部署\n\n> 请为应用程序分配 512 MB 内存，否则可能会出现内存溢出\n\n> Fly.io 5刀以内的账单免费(免费计划的3个256MB的应用不在账单内)也就是可以同时可以部署 `1*512MB + 3*256MB`\n\n1. 安装 [flyctl](https://fly.io/docs/getting-started/installing-flyctl/)\n   ```shell\n    # macOS\n    brew install flyctl\n    # Windows\n    scoop install flyctl\n    # Linux\n    curl https://fly.io/install.sh | sh\n   ```\n2. 克隆项目并进入项目目录\n   ```shell\n   git clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt\n   ```\n3. 创建应用\n   ```shell\n   ➜ flyctl launch \n    ? Would you like to copy its configuration to the new app? No\n    ? App Name (leave blank to use an auto-generated name): <YOUR APP NAME>\n    ? Select region: <YOUR CHOOSE REGION>\n    ? Would you like to setup a Postgresql database now? No\n    ? Would you like to deploy now? No\n   ```\n4. 配置环境变量\n   ```shell\n   flyctl secrets set OPENAI_API_KEY=\"<YOUR OPENAI API KEY>\" MODEL=\"<CHATGPT-MODEL>\"\n   ```\n5. 部署应用\n   ```shell\n   flyctl deploy\n   ```\n\n## 通过Docker使用\n\n```sh\n# 拉取镜像\ndocker pull holegots/wechat-chatgpt:latest\n# 运行容器\ndocker run -it --name wechat-chatgpt \\\n    -e OPENAI_API_KEY=<YOUR OPENAI API KEY> \\\n    -e MODEL=\"gpt-3.5-turbo\" \\\n    -e CHAT_PRIVATE_TRIGGER_KEYWORD=\"\" \\\n    -v $(pwd)/data:/app/data/wechat-assistant.memory-card.json \\\n    holegots/wechat-chatgpt:latest\n# 使用二维码登陆\ndocker logs -f wechat-chatgpt\n```\n\n> 如何获取 OPENAI API KEY？请参考 [OpenAI API](https://platform.openai.com/account/api-keys)。\n\n## 通过docker compose使用\n\n```sh\n# 根据模板拷贝配置文件\ncp .env.example .env\n# 使用你喜欢的文本编辑器修改配置文件\nvim .env\n# 在Linux或WindowsPowerShell上运行如下命令\ndocker compose up -d\n# 使用二维码登陆\ndocker logs -f wechat-chatgpt\n```\n\n## 使用NodeJS运行\n\n> 请确认安装的NodeJS版本为18.0.0以上\n\n```sh\n# 克隆项目\ngit clone https://github.com/fuergaosi233/wechat-chatgpt.git && cd wechat-chatgpt\n# 安装依赖\nnpm install\n# 编辑配置\ncp .env.example .env\nvim .env # 使用你喜欢的文本编辑器修改配置文件\n# 启动项目\nnpm run dev\n# 如果您是初次登陆，那么需要扫描二维码\n```\n\n> 请确保您的账号可以登陆 [网页版微信](https://wx.qq.com/)。\n\n## 📝 Environment Variables\n\n| name                     | default                | example                                        | description                                                 |\n|--------------------------|------------------------|------------------------------------------------|-------------------------------------------------------------|\n| API                      | https://api.openai.com |                                                | 自定义ChatGPT API 地址                                           |\n| OPENAI_API_KEY           | 123456789              | sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | [创建你的 API 密钥](https://platform.openai.com/account/api-keys) |\n| MODEL                    | gpt-3.5-turbo          |                                                | 要使用的模型ID, 目前仅支持`gpt-3.5-turbo` 和 `gpt-3.5-turbo-0301`       |\n| TEMPERATURE              | 0.6                    |                                                | 在0和2之间。较高的数值如0.8会使 ChatGPT 输出更加随机，而较低的数值如0.2会使其更加稳定。        |\n| CHAT_TRIGGER_RULE        |                        |                                                | 私聊触发规则                                                      |\n| DISABLE_GROUP_MESSAGE    | true                   |                                                | 禁用在群聊里使用ChatGPT                                             |\n| CHAT_PRIVATE_TRIGGER_KEYWORD |                        |                                                | 在私聊中触发ChatGPT的关键词, 默认是无需关键词即可触发                             |\n| BLOCK_WORDS              | \"VPN\"                  | \"WORD1,WORD2,WORD3\"                            | 聊天屏蔽关键词(同时在群组和私聊中生效, 避免 bot 用户恶意提问导致封号                      |\n| CHATGPT_BLOCK_WORDS      | \"VPN\"                  | \"WORD1,WORD2,WORD3\"                            | ChatGPT回复屏蔽词, 如果ChatGPT的回复中包含了屏蔽词, 则不回复                     |\n\n## 📝 使用自定义ChatGPT API\n> https://github.com/fuergaosi233/openai-proxy\n```shell\n# 克隆项目\ngit clone https://github.com/fuergaosi233/openai-proxy\n# 安装依赖\nnpm install && npm install -g wrangler && npm run build\n# 部署到 CloudFlare Workers\nnpm run deploy\n# 自定义域名(可选)\n添加 `Route`` 到 `wrangler.toml`\nroutes = [\n\t{ pattern = \"Your Custom Domain\", custom_domain = true },\n]\n```\n\n## ⌨️ 命令\n> 在微信聊天框中输入\n```shell\n/cmd help # 显示帮助信息\n/cmd prompt <PROMPT> # 设置ChatGPT Prompt\n/cmd clear # 清除WeChat-ChatGPT保存的会话记录\n```\n\n## ✨ Contributor\n\n<a href=\"https://github.com/fuergaosi233/wechat-chatgpt/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=fuergaosi233/wechat-chatgpt\" />\n</a>\n\n## 🤝 为项目添砖加瓦\n\n欢迎提出 Contributions, issues 与 feature requests!<br />\n随时查看 [issues page](https://github.com/fuergaosi233/wechat-chatgpt/issues).\n\n## 感谢支持 🙏\n\n如果这个项目对你产生了一点的帮助，请为这个项目点上一颗 ⭐️"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3'\nservices:\n  wechat-chatgpt:\n    image: wechat-chatgpt\n    build: .\n    volumes:\n      - ./data/wechat-assistant.memory-card.json:/app/wechat-assistant.memory-card.json\n    env_file:\n      - .env"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"wechat-chatgpt\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"dist/main.js\",\n  \"export\": \"dist/main.js\",\n  \"scripts\": {\n    \"dev\": \"nodemon --exec node --loader ts-node/esm src/main.ts\",\n    \"build\": \"tsc\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"async-retry\": \"^1.3.3\",\n    \"dotenv\": \"^16.0.3\",\n    \"execa\": \"^6.1.0\",\n    \"gpt3-tokenizer\": \"^1.1.5\",\n    \"openai\": \"^3.2.1\",\n    \"qrcode\": \"^1.5.1\",\n    \"uuid\": \"^9.0.0\",\n    \"wechaty\": \"^1.20.2\",\n    \"wechaty-puppet-wechat\": \"^1.18.4\"\n  },\n  \"devDependencies\": {\n    \"@types/async-retry\": \"^1.4.5\",\n    \"@types/qrcode\": \"^1.5.0\",\n    \"@types/uuid\": \"^9.0.0\",\n    \"nodemon\": \"^2.0.20\",\n    \"ts-node\": \"^10.9.1\"\n  },\n  \"nodemonConfig\": {\n    \"watch\": [\n      \"src/*.ts\"\n    ],\n    \"ignore\": [\n      \"src/main.ts\"\n    ],\n    \"ext\": \"ts\",\n    \"exec\": \"node --loader ts-node/esm src/main.ts\",\n    \"delay\": 500\n  },\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "public/.gitignore",
    "content": "# Ignore everything in this directory\n*\n# Except this file\n!.gitignore"
  },
  {
    "path": "src/bot.ts",
    "content": "import { config } from \"./config.js\";\nimport {ContactImpl, ContactInterface, RoomImpl, RoomInterface} from \"wechaty/impls\";\nimport { Message } from \"wechaty\";\nimport {FileBox} from \"file-box\";\nimport {chatgpt, dalle, whisper} from \"./openai.js\";\nimport DBUtils from \"./data.js\";\nimport { regexpEncode } from \"./utils.js\";\nenum MessageType {\n  Unknown = 0,\n  Attachment = 1, // Attach(6),\n  Audio = 2, // Audio(1), Voice(34)\n  Contact = 3, // ShareCard(42)\n  ChatHistory = 4, // ChatHistory(19)\n  Emoticon = 5, // Sticker: Emoticon(15), Emoticon(47)\n  Image = 6, // Img(2), Image(3)\n  Text = 7, // Text(1)\n  Location = 8, // Location(48)\n  MiniProgram = 9, // MiniProgram(33)\n  GroupNote = 10, // GroupNote(53)\n  Transfer = 11, // Transfers(2000)\n  RedEnvelope = 12, // RedEnvelopes(2001)\n  Recalled = 13, // Recalled(10002)\n  Url = 14, // Url(5)\n  Video = 15, // Video(4), Video(43)\n  Post = 16, // Moment, Channel, Tweet, etc\n}\nconst SINGLE_MESSAGE_MAX_SIZE = 500;\ntype Speaker = RoomImpl | ContactImpl;\ninterface ICommand{\n  name:string;\n  description:string;\n  exec: (talker:Speaker, text:string) => Promise<void>;\n}\nexport class ChatGPTBot {\n  chatPrivateTriggerKeyword = config.chatPrivateTriggerKeyword;\n  chatTriggerRule = config.chatTriggerRule? new RegExp(config.chatTriggerRule): undefined;\n  disableGroupMessage = config.disableGroupMessage || false;\n  botName: string = \"\";\n  ready = false;\n  setBotName(botName: string) {\n    this.botName = botName;\n  }\n  get chatGroupTriggerRegEx(): RegExp {\n    return new RegExp(`^@${regexpEncode(this.botName)}\\\\s`);\n  }\n  get chatPrivateTriggerRule(): RegExp | undefined {\n    const { chatPrivateTriggerKeyword, chatTriggerRule } = this;\n    let regEx = chatTriggerRule\n    if (!regEx && chatPrivateTriggerKeyword) {\n      regEx = new RegExp(regexpEncode(chatPrivateTriggerKeyword))\n    }\n    return regEx\n  }\n  private readonly commands:ICommand[] = [\n    {\n      name: \"help\",\n      description: \"显示帮助信息\",\n      exec: async (talker) => {\n        await this.trySay(talker,\"========\\n\" +\n          \"/cmd help\\n\" +\n          \"# 显示帮助信息\\n\" +\n          \"/cmd prompt <PROMPT>\\n\" +\n          \"# 设置当前会话的 prompt \\n\" +\n          \"/img <PROMPT>\\n\" +\n          \"# 根据 prompt 生成图片\\n\" +\n          \"/cmd clear\\n\" +\n          \"# 清除自上次启动以来的所有会话\\n\" +\n          \"========\");\n      }\n    },\n    {\n      name: \"prompt\",\n      description: \"设置当前会话的prompt\",\n      exec: async (talker, prompt) => {\n        if (talker instanceof RoomImpl) {\n          DBUtils.setPrompt(await talker.topic(), prompt);\n        }else {\n          DBUtils.setPrompt(talker.name(), prompt);\n        }\n      }\n    },\n    {\n      name: \"clear\",\n      description: \"清除自上次启动以来的所有会话\",\n      exec: async (talker) => {\n        if (talker instanceof RoomImpl) {\n          DBUtils.clearHistory(await talker.topic());\n        }else{\n          DBUtils.clearHistory(talker.name());\n        }\n      }\n    }\n  ]\n\n  /**\n   * EXAMPLE:\n   *       /cmd help\n   *       /cmd prompt <PROMPT>\n   *       /cmd img <PROMPT>\n   *       /cmd clear\n   * @param contact\n   * @param rawText\n   */\n  async command(contact: any, rawText: string): Promise<void> {\n    const [commandName, ...args] = rawText.split(/\\s+/);\n    const command = this.commands.find(\n      (command) => command.name === commandName\n    );\n    if (command) {\n      await command.exec(contact, args.join(\" \"));\n    }\n  }\n  // remove more times conversation and mention\n  cleanMessage(rawText: string, privateChat: boolean = false): string {\n    let text = rawText;\n    const item = rawText.split(\"- - - - - - - - - - - - - - -\");\n    if (item.length > 1) {\n      text = item[item.length - 1];\n    }\n\n    const { chatTriggerRule, chatPrivateTriggerRule } = this;\n\n    if (privateChat && chatPrivateTriggerRule) {\n      text = text.replace(chatPrivateTriggerRule, \"\")\n    } else if (!privateChat) {\n      text = text.replace(this.chatGroupTriggerRegEx, \"\")\n      text = chatTriggerRule? text.replace(chatTriggerRule, \"\"): text\n    }\n    // remove more text via - - - - - - - - - - - - - - -\n    return text\n  }\n  async getGPTMessage(talkerName: string,text: string): Promise<string> {\n    let gptMessage = await chatgpt(talkerName,text);\n    if (gptMessage !==\"\") {\n      DBUtils.addAssistantMessage(talkerName,gptMessage);\n      return gptMessage;\n    }\n    return \"Sorry, please try again later. 😔\";\n  }\n  // Check if the message returned by chatgpt contains masked words]\n  checkChatGPTBlockWords(message: string): boolean {\n    if (config.chatgptBlockWords.length == 0) {\n      return false;\n    }\n    return config.chatgptBlockWords.some((word) => message.includes(word));\n  }\n  // The message is segmented according to its size\n  async trySay(\n    talker: RoomInterface | ContactInterface,\n    mesasge: string\n  ): Promise<void> {\n    const messages: Array<string> = [];\n    if (this.checkChatGPTBlockWords(mesasge)) {\n      console.log(`🚫 Blocked ChatGPT: ${mesasge}`);\n      return;\n    }\n    let message = mesasge;\n    while (message.length > SINGLE_MESSAGE_MAX_SIZE) {\n      messages.push(message.slice(0, SINGLE_MESSAGE_MAX_SIZE));\n      message = message.slice(SINGLE_MESSAGE_MAX_SIZE);\n    }\n    messages.push(message);\n    for (const msg of messages) {\n      await talker.say(msg);\n    }\n  }\n  // Check whether the ChatGPT processing can be triggered\n  triggerGPTMessage(text: string, privateChat: boolean = false): boolean {\n    const { chatTriggerRule } = this;\n    let triggered = false;\n    if (privateChat) {\n      const regEx = this.chatPrivateTriggerRule\n      triggered = regEx? regEx.test(text): true;\n    } else {\n      triggered = this.chatGroupTriggerRegEx.test(text);\n      // group message support `chatTriggerRule`\n      if (triggered && chatTriggerRule) {\n        triggered = chatTriggerRule.test(text.replace(this.chatGroupTriggerRegEx, \"\"))\n      }\n    }\n    if (triggered) {\n      console.log(`🎯 Triggered ChatGPT: ${text}`);\n    }\n    return triggered;\n  }\n  // Check whether the message contains the blocked words. if so, the message will be ignored. if so, return true\n  checkBlockWords(message: string): boolean {\n    if (config.blockWords.length == 0) {\n      return false;\n    }\n    return config.blockWords.some((word) => message.includes(word));\n  }\n  // Filter out the message that does not need to be processed\n  isNonsense(\n    talker: ContactInterface,\n    messageType: MessageType,\n    text: string\n  ): boolean {\n    return (\n      talker.self() ||\n      // TODO: add doc support\n      !(messageType == MessageType.Text || messageType == MessageType.Audio) ||\n      talker.name() === \"微信团队\" ||\n      // 语音(视频)消息\n      text.includes(\"收到一条视频/语音聊天消息，请在手机上查看\") ||\n      // 红包消息\n      text.includes(\"收到红包，请在手机上查看\") ||\n      // Transfer message\n      text.includes(\"收到转账，请在手机上查看\") ||\n      // 位置消息\n      text.includes(\"/cgi-bin/mmwebwx-bin/webwxgetpubliclinkimg\") ||\n      // 聊天屏蔽词\n      this.checkBlockWords(text)\n    );\n  }\n\n  async onPrivateMessage(talker: ContactInterface, text: string) {\n    const gptMessage = await this.getGPTMessage(talker.name(),text);\n    await this.trySay(talker, gptMessage);\n  }\n\n  async onGroupMessage(\n    talker: ContactInterface,\n    text: string,\n    room: RoomInterface\n  ) {\n    const gptMessage = await this.getGPTMessage(await room.topic(),text);\n    const result = `@${talker.name()} ${text}\\n\\n------\\n ${gptMessage}`;\n    await this.trySay(room, result);\n  }\n  async onMessage(message: Message) {\n    const talker = message.talker();\n    const rawText = message.text();\n    const room = message.room();\n    const messageType = message.type();\n    const privateChat = !room;\n    if (privateChat) {\n      console.log(`🤵 Contact: ${talker.name()} 💬 Text: ${rawText}`)\n    } else {\n      const topic = await room.topic()\n      console.log(`🚪 Room: ${topic} 🤵 Contact: ${talker.name()} 💬 Text: ${rawText}`)\n    }\n    if (this.isNonsense(talker, messageType, rawText)) {\n      return;\n    }\n    if (messageType == MessageType.Audio){\n      // 保存语音文件\n      const fileBox = await message.toFileBox();\n      let fileName = \"./public/\" + fileBox.name;\n      await fileBox.toFile(fileName, true).catch((e) => {\n        console.log(\"保存语音失败\",e);\n        return;\n      });\n      // Whisper\n      whisper(\"\",fileName).then((text) => {\n        message.say(text);\n      })\n      return;\n    }\n    if (rawText.startsWith(\"/cmd \")){\n      console.log(`🤖 Command: ${rawText}`)\n      const cmdContent = rawText.slice(5) // 「/cmd 」一共5个字符(注意空格)\n      if (privateChat) {\n        await this.command(talker, cmdContent);\n      }else{\n        await this.command(room, cmdContent);\n      }\n      return;\n    }\n    // 使用DallE生成图片\n    if (rawText.startsWith(\"/img\")){\n      console.log(`🤖 Image: ${rawText}`)\n      const imgContent = rawText.slice(4)\n      if (privateChat) {\n        let url = await dalle(talker.name(), imgContent) as string;\n        const fileBox = FileBox.fromUrl(url)\n        message.say(fileBox)\n      }else{\n        let url = await dalle(await room.topic(), imgContent) as string;\n        const fileBox = FileBox.fromUrl(url)\n        message.say(fileBox)\n      }\n      return;\n    }\n    if (this.triggerGPTMessage(rawText, privateChat)) {\n      const text = this.cleanMessage(rawText, privateChat);\n      if (privateChat) {\n        return await this.onPrivateMessage(talker, text);\n      } else{\n        if (!this.disableGroupMessage){\n          return await this.onGroupMessage(talker, text, room);\n        } else {\n          return;\n        }\n      }\n    } else {\n      return;\n    }\n  }\n}\n"
  },
  {
    "path": "src/config.ts",
    "content": "import * as dotenv from \"dotenv\";\ndotenv.config();\nimport { IConfig } from \"./interface\";\n\nexport const config: IConfig = {\n  api: process.env.API,\n  openai_api_key: process.env.OPENAI_API_KEY || \"123456789\",\n  model: process.env.MODEL || \"gpt-3.5-turbo\",\n  chatPrivateTriggerKeyword: process.env.CHAT_PRIVATE_TRIGGER_KEYWORD || \"\",\n  chatTriggerRule: process.env.CHAT_TRIGGER_RULE || \"\",\n  disableGroupMessage: process.env.DISABLE_GROUP_MESSAGE === \"true\",\n  temperature: process.env.TEMPERATURE ? parseFloat(process.env.TEMPERATURE) : 0.6,\n  blockWords: process.env.BLOCK_WORDS?.split(\",\") || [],\n  chatgptBlockWords: process.env.CHATGPT_BLOCK_WORDS?.split(\",\") || [],\n};\n"
  },
  {
    "path": "src/data.ts",
    "content": "import {ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum} from \"openai\";\nimport {User} from \"./interface\";\nimport {isTokenOverLimit} from \"./utils.js\";\n\n/**\n * 使用内存作为数据库\n */\n\nclass DB {\n  private static data: User[] = [];\n\n  /**\n   * 添加一个用户, 如果用户已存在则返回已存在的用户\n   * @param username\n   */\n  public addUser(username: string): User {\n    let existUser = DB.data.find((user) => user.username === username);\n    if (existUser) {\n      console.log(`用户${username}已存在`);\n      return existUser;\n    }\n    const newUser: User = {\n      username: username,\n      chatMessage: [\n        {\n          role: ChatCompletionRequestMessageRoleEnum.System,\n          content: \"You are a helpful assistant.\"\n        }\n      ],\n    };\n    DB.data.push(newUser);\n    return newUser;\n  }\n\n  /**\n   * 根据用户名获取用户, 如果用户不存在则添加用户\n   * @param username\n   */\n  public getUserByUsername(username: string): User {\n    return DB.data.find((user) => user.username === username) || this.addUser(username);\n  }\n\n  /**\n   * 获取用户的聊天记录\n   * @param username\n   */\n  public getChatMessage(username: string): Array<ChatCompletionRequestMessage> {\n    return this.getUserByUsername(username).chatMessage;\n  }\n\n  /**\n   * 设置用户的prompt\n   * @param username\n   * @param prompt\n   */\n  public setPrompt(username: string, prompt: string): void {\n    const user = this.getUserByUsername(username);\n    if (user) {\n      user.chatMessage.find(\n        (msg) => msg.role === ChatCompletionRequestMessageRoleEnum.System\n      )!.content = prompt;\n    }\n  }\n\n  /**\n   * 添加用户输入的消息\n   * @param username\n   * @param message\n   */\n  public addUserMessage(username: string, message: string): void {\n    const user = this.getUserByUsername(username);\n    if (user) {\n      while (isTokenOverLimit(user.chatMessage)){\n        // 删除从第2条开始的消息(因为第一条是prompt)\n        user.chatMessage.splice(1,1);\n      }\n      user.chatMessage.push({\n        role: ChatCompletionRequestMessageRoleEnum.User,\n        content: message,\n      });\n    }\n  }\n\n  /**\n   * 添加ChatGPT的回复\n   * @param username\n   * @param message\n   */\n  public addAssistantMessage(username: string, message: string): void {\n    const user = this.getUserByUsername(username);\n    if (user) {\n      while (isTokenOverLimit(user.chatMessage)){\n        // 删除从第2条开始的消息(因为第一条是prompt)\n        user.chatMessage.splice(1,1);\n      }\n      user.chatMessage.push({\n        role: ChatCompletionRequestMessageRoleEnum.Assistant,\n        content: message,\n      });\n    }\n  }\n\n  /**\n   * 清空用户的聊天记录, 并将prompt设置为默认值\n   * @param username\n   */\n  public clearHistory(username: string): void {\n    const user = this.getUserByUsername(username);\n    if (user) {\n      user.chatMessage = [\n        {\n          role: ChatCompletionRequestMessageRoleEnum.System,\n          content: \"You are a helpful assistant.\"\n        }\n      ];\n    }\n  }\n\n  public getAllData(): User[] {\n    return DB.data;\n  }\n}\nconst DBUtils = new DB();\nexport default DBUtils;"
  },
  {
    "path": "src/interface.ts",
    "content": "import {ChatCompletionRequestMessage} from \"openai\";\n\nexport interface IConfig {\n  api?: string;\n  openai_api_key: string;\n  model: string;\n  chatTriggerRule: string;\n  disableGroupMessage: boolean;\n  temperature: number;\n  blockWords: string[];\n  chatgptBlockWords: string[];\n  chatPrivateTriggerKeyword: string;\n}\nexport interface User {\n  username: string,\n  chatMessage: Array<ChatCompletionRequestMessage>,\n}"
  },
  {
    "path": "src/main.ts",
    "content": "import { WechatyBuilder } from \"wechaty\";\nimport QRCode from \"qrcode\";\nimport { ChatGPTBot } from \"./bot.js\";\nimport {config} from \"./config.js\";\nconst chatGPTBot = new ChatGPTBot();\n\nconst bot =  WechatyBuilder.build({\n  name: \"wechat-assistant\", // generate xxxx.memory-card.json and save login data for the next login\n  puppet: \"wechaty-puppet-wechat\",\n  puppetOptions: {\n    uos: true\n  }\n});\nasync function main() {\n  const initializedAt = Date.now()\n  bot\n    .on(\"scan\", async (qrcode, status) => {\n      const url = `https://wechaty.js.org/qrcode/${encodeURIComponent(qrcode)}`;\n      console.log(`Scan QR Code to login: ${status}\\n${url}`);\n      console.log(\n        await QRCode.toString(qrcode, { type: \"terminal\", small: true })\n      );\n    })\n    .on(\"login\", async (user) => {\n      chatGPTBot.setBotName(user.name());\n      console.log(`User ${user} logged in`);\n      console.log(`私聊触发关键词: ${config.chatPrivateTriggerKeyword}`);\n      console.log(`已设置 ${config.blockWords.length} 个聊天关键词屏蔽. ${config.blockWords}`);\n      console.log(`已设置 ${config.chatgptBlockWords.length} 个ChatGPT回复关键词屏蔽. ${config.chatgptBlockWords}`);\n    })\n    .on(\"message\", async (message) => {\n      if (message.date().getTime() < initializedAt) {\n        return;\n      }\n      if (message.text().startsWith(\"/ping\")) {\n        await message.say(\"pong\");\n        return;\n      }\n      try {\n        await chatGPTBot.onMessage(message);\n      } catch (e) {\n        console.error(e);\n      }\n    });\n  try {\n    await bot.start();\n  } catch (e) {\n    console.error(\n      `⚠️ Bot start failed, can you log in through wechat on the web?: ${e}`\n    );\n  }\n}\nmain();\n"
  },
  {
    "path": "src/openai.ts",
    "content": "import {\n  Configuration,\n  CreateImageRequestResponseFormatEnum,\n  CreateImageRequestSizeEnum,\n  OpenAIApi\n} from \"openai\";\nimport fs from \"fs\";\nimport DBUtils from \"./data.js\";\nimport {config} from \"./config.js\";\n\nconst configuration = new Configuration({\n  apiKey: config.openai_api_key,\n  basePath: config.api,\n});\nconst openai = new OpenAIApi(configuration);\n\n/**\n * Get completion from OpenAI\n * @param username\n * @param message\n */\nasync function chatgpt(username:string,message: string): Promise<string> {\n  // 先将用户输入的消息添加到数据库中\n  DBUtils.addUserMessage(username, message);\n  const messages = DBUtils.getChatMessage(username);\n  const response = await openai.createChatCompletion({\n    model: \"gpt-3.5-turbo\",\n    messages: messages,\n    temperature: config.temperature,\n  });\n  let assistantMessage = \"\";\n  try {\n    if (response.status === 200) {\n      assistantMessage = response.data.choices[0].message?.content.replace(/^\\n+|\\n+$/g, \"\") as string;\n    }else{\n      console.log(`Something went wrong,Code: ${response.status}, ${response.statusText}`)\n    }\n  }catch (e:any) {\n    if (e.request){\n      console.log(\"请求出错\");\n    }\n  }\n  return assistantMessage;\n}\n\n/**\n * Get image from Dall·E\n * @param username\n * @param prompt\n */\nasync function dalle(username:string,prompt: string) {\n  const response = await openai.createImage({\n    prompt: prompt,\n    n:1,\n    size: CreateImageRequestSizeEnum._256x256,\n    response_format: CreateImageRequestResponseFormatEnum.Url,\n    user: username\n  }).then((res) => res.data).catch((err) => console.log(err));\n  if (response) {\n    return response.data[0].url;\n  }else{\n    return \"Generate image failed\"\n  }\n}\n\n/**\n * Speech to text\n * @param username\n * @param videoPath\n */\nasync function whisper(username:string,videoPath: string): Promise<string> {\n  const file:any= fs.createReadStream(videoPath);\n  const response = await openai.createTranscription(file,\"whisper-1\")\n    .then((res) => res.data).catch((err) => console.log(err));\n  if (response) {\n    return response.text;\n  }else{\n    return \"Speech to text failed\"\n  }\n}\n\nexport {chatgpt,dalle,whisper};"
  },
  {
    "path": "src/utils.ts",
    "content": "import {ChatCompletionRequestMessage} from \"openai\";\n\nimport GPT3TokenizerImport from 'gpt3-tokenizer';\nimport {config} from \"./config.js\";\n\nexport const regexpEncode = (str: string) => str.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n\nconst GPT3Tokenizer: typeof GPT3TokenizerImport =\n  typeof GPT3TokenizerImport === 'function'\n    ? GPT3TokenizerImport\n    : (GPT3TokenizerImport as any).default;\n// https://github.com/chathub-dev/chathub/blob/main/src/app/bots/chatgpt-api/usage.ts\nconst tokenizer = new GPT3Tokenizer({ type: 'gpt3' })\nfunction calTokens(chatMessage:ChatCompletionRequestMessage[]):number {\n  let count = 0\n  for (const msg of chatMessage) {\n    count += countTokens(msg.content)\n    count += countTokens(msg.role)\n  }\n  return count + 2\n}\n\nfunction countTokens(str: string):number {\n  const encoded = tokenizer.encode(str)\n  return encoded.bpe.length\n}\nexport function isTokenOverLimit(chatMessage:ChatCompletionRequestMessage[]): boolean {\n  let limit = 4096;\n  if (config.model===\"gpt-3.5-turbo\" || config.model===\"gpt-3.5-turbo-0301\") {\n    limit = 4096;\n  }\n  return calTokens(chatMessage) > limit;\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Visit https://aka.ms/tsconfig.json to read more about this file */\n\n    /* Projects */\n    // \"incremental\": true,                              /* Enable incremental compilation */\n    // \"composite\": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */\n    // \"tsBuildInfoFile\": \"./\",                          /* Specify the folder for .tsbuildinfo incremental compilation files. */\n    // \"disableSourceOfProjectReferenceRedirect\": true,  /* Disable preferring source files instead of declaration files when referencing composite projects */\n    // \"disableSolutionSearching\": true,                 /* Opt a project out of multi-project reference checking when editing. */\n    // \"disableReferencedProjectLoad\": true,             /* Reduce the number of projects loaded automatically by TypeScript. */\n\n    /* Language and Environment */\n    \"target\": \"ESNext\",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */\n    // \"lib\": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */\n    // \"jsx\": \"preserve\",                                /* Specify what JSX code is generated. */\n    // \"experimentalDecorators\": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */\n    // \"emitDecoratorMetadata\": true,                    /* Emit design-type metadata for decorated declarations in source files. */\n    // \"jsxFactory\": \"\",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */\n    // \"jsxFragmentFactory\": \"\",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */\n    // \"jsxImportSource\": \"\",                            /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */\n    // \"reactNamespace\": \"\",                             /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */\n    // \"noLib\": true,                                    /* Disable including any library files, including the default lib.d.ts. */\n    // \"useDefineForClassFields\": true,                  /* Emit ECMAScript-standard-compliant class fields. */\n\n    /* Modules */\n    \"module\": \"esnext\",                                /* Specify what module code is generated. */\n    \"rootDir\": \"src\",                                  /* Specify the root folder within your source files. */\n    \"moduleResolution\": \"node\",                       /* Specify how TypeScript looks up a file from a given module specifier. */\n    // \"baseUrl\": \"./\",                                  /* Specify the base directory to resolve non-relative module names. */\n    // \"paths\": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */\n    // \"rootDirs\": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */\n    // \"typeRoots\": [],                                  /* Specify multiple folders that act like `./node_modules/@types`. */\n    // \"types\": [],                                      /* Specify type package names to be included without being referenced in a source file. */\n    // \"allowUmdGlobalAccess\": true,                     /* Allow accessing UMD globals from modules. */\n    \"resolveJsonModule\": true,                        /* Enable importing .json files */\n    // \"noResolve\": true,                                /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */\n\n    /* JavaScript Support */\n    // \"allowJs\": true,                                  /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */\n    // \"checkJs\": true,                                  /* Enable error reporting in type-checked JavaScript files. */\n    // \"maxNodeModuleJsDepth\": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */\n\n    /* Emit */\n    // \"declaration\": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */\n    // \"declarationMap\": true,                           /* Create sourcemaps for d.ts files. */\n    // \"emitDeclarationOnly\": true,                      /* Only output d.ts files and not JavaScript files. */\n    // \"sourceMap\": true,                                /* Create source map files for emitted JavaScript files. */\n    // \"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. */\n    \"outDir\": \"dist\",                                   /* Specify an output folder for all emitted files. */\n    // \"removeComments\": true,                           /* Disable emitting comments. */\n    // \"noEmit\": true,                                   /* Disable emitting files from a compilation. */\n    // \"importHelpers\": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */\n    // \"importsNotUsedAsValues\": \"remove\",               /* Specify emit/checking behavior for imports that are only used for types */\n    // \"downlevelIteration\": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */\n    // \"sourceRoot\": \"\",                                 /* Specify the root path for debuggers to find the reference source code. */\n    // \"mapRoot\": \"\",                                    /* Specify the location where debugger should locate map files instead of generated locations. */\n    // \"inlineSourceMap\": true,                          /* Include sourcemap files inside the emitted JavaScript. */\n    // \"inlineSources\": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */\n    // \"emitBOM\": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */\n    // \"newLine\": \"crlf\",                                /* Set the newline character for emitting files. */\n    // \"stripInternal\": true,                            /* Disable emitting declarations that have `@internal` in their JSDoc comments. */\n    // \"noEmitHelpers\": true,                            /* Disable generating custom helper functions like `__extends` in compiled output. */\n    // \"noEmitOnError\": true,                            /* Disable emitting files if any type checking errors are reported. */\n    // \"preserveConstEnums\": true,                       /* Disable erasing `const enum` declarations in generated code. */\n    // \"declarationDir\": \"./\",                           /* Specify the output directory for generated declaration files. */\n    // \"preserveValueImports\": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */\n\n    /* Interop Constraints */\n    // \"isolatedModules\": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */\n    // \"allowSyntheticDefaultImports\": true,             /* Allow 'import x from y' when a module doesn't have a default export. */\n    \"esModuleInterop\": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */\n    // \"preserveSymlinks\": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */\n    \"forceConsistentCasingInFileNames\": true,            /* Ensure that casing is correct in imports. */\n\n    /* Type Checking */\n    \"strict\": true,                                      /* Enable all strict type-checking options. */\n    // \"noImplicitAny\": true,                            /* Enable error reporting for expressions and declarations with an implied `any` type.. */\n    // \"strictNullChecks\": true,                         /* When type checking, take into account `null` and `undefined`. */\n    // \"strictFunctionTypes\": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */\n    // \"strictBindCallApply\": true,                      /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */\n    // \"strictPropertyInitialization\": true,             /* Check for class properties that are declared but not set in the constructor. */\n    // \"noImplicitThis\": true,                           /* Enable error reporting when `this` is given the type `any`. */\n    // \"useUnknownInCatchVariables\": true,               /* Type catch clause variables as 'unknown' instead of 'any'. */\n    // \"alwaysStrict\": true,                             /* Ensure 'use strict' is always emitted. */\n    // \"noUnusedLocals\": true,                           /* Enable error reporting when a local variables aren't read. */\n    // \"noUnusedParameters\": true,                       /* Raise an error when a function parameter isn't read */\n    // \"exactOptionalPropertyTypes\": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */\n    // \"noImplicitReturns\": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */\n    // \"noFallthroughCasesInSwitch\": true,               /* Enable error reporting for fallthrough cases in switch statements. */\n    // \"noUncheckedIndexedAccess\": true,                 /* Include 'undefined' in index signature results */\n    // \"noImplicitOverride\": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */\n    // \"noPropertyAccessFromIndexSignature\": true,       /* Enforces using indexed accessors for keys declared using an indexed type */\n    // \"allowUnusedLabels\": true,                        /* Disable error reporting for unused labels. */\n    // \"allowUnreachableCode\": true,                     /* Disable error reporting for unreachable code. */\n\n    /* Completeness */\n    // \"skipDefaultLibCheck\": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */\n    \"skipLibCheck\": true                                 /* Skip type checking all .d.ts files. */\n  }\n}\n"
  }
]