├── .dockerignore ├── .env ├── .env.example ├── .eslintrc.js ├── .github └── workflows │ ├── pull_request.yml │ └── push.yml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── assets └── basic_start.gif ├── docker-compose.yml ├── package-lock.json ├── package.json ├── server.js ├── src ├── app.js ├── config.js ├── controllers │ ├── chatController.js │ ├── clientController.js │ ├── contactController.js │ ├── groupChatController.js │ ├── healthController.js │ ├── messageController.js │ └── sessionController.js ├── middleware.js ├── routes.js ├── sessions.js └── utils.js ├── swagger.js ├── swagger.json └── tests └── api.test.js /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore node_modules 2 | node_modules 3 | 4 | # Ignore dotenv files 5 | .env 6 | .env.example 7 | 8 | # Ignore logs 9 | logs 10 | 11 | # Ignore test files 12 | tests 13 | 14 | # Ignore session files 15 | sessions 16 | sessions_test 17 | 18 | # Ignore git related files 19 | .git 20 | .gitignore 21 | 22 | # Ignore other unnecessary files 23 | README.md 24 | CONTRIBUTING.md 25 | LICENSE.md 26 | Dockerfile 27 | docker-compose.yml 28 | swagger.yml 29 | .github 30 | assets -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ## Application ## 2 | PORT=3000 # OPTIONAL, DEFAULT 3000 3 | API_KEY=comunidadezdg.com.br # OPTIONAL, DEFAULT EMPTY 4 | BASE_WEBHOOK_URL=http://localhost:3000/localCallbackExample # MANDATORY 5 | ENABLE_LOCAL_CALLBACK_EXAMPLE=TRUE # OPTIONAL, DISABLE FOR PRODUCTION 6 | RATE_LIMIT_MAX=1000 # OPTIONAL, THE MAXIUM NUMBER OF CONNECTIONS TO ALLOW PER TIME FRAME 7 | RATE_LIMIT_WINDOW_MS=1000 # OPTIONAL, TIME FRAME FOR WHICH REQUESTS ARE CHECKED IN MS 8 | 9 | ## Client ## 10 | MAX_ATTACHMENT_SIZE=10000000 # IF REACHED, MEDIA ATTACHMENT BODY WILL BE NULL 11 | SET_MESSAGES_AS_SEEN=TRUE # WILL MARK THE MESSAGES AS READ AUTOMATICALLY 12 | # ALL CALLBACKS: auth_failure|authenticated|call|change_state|disconnected|group_join|group_leave|group_update|loading_screen|media_uploaded|message|message_ack|message_create|message_reaction|message_revoke_everyone|qr|ready|contact_changed|media 13 | DISABLED_CALLBACKS=message_ack|message_reaction # PREVENT SENDING CERTAIN TYPES OF CALLBACKS BACK TO THE WEBHOOK 14 | WEB_VERSION='2.2328.5' # OPTIONAL, THE VERSION OF WHATSAPP WEB TO USE 15 | WEB_VERSION_CACHE_TYPE=none # OPTIONAL, DETERMINTES WHERE TO GET THE WHATSAPP WEB VERSION(local, remote or none), DEFAULT 'none' 16 | RECOVER_SESSIONS=TRUE # OPTIONAL, SHOULD WE RECOVER THE SESSION IN CASE OF PAGE FAILURES 17 | 18 | ## Session File Storage ## 19 | SESSIONS_PATH=./sessions # OPTIONAL 20 | 21 | ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL 22 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | ## Application ## 2 | PORT=3000 # OPTIONAL, DEFAULT 3000 3 | API_KEY=your_global_api_key_here # OPTIONAL, DEFAULT EMPTY 4 | BASE_WEBHOOK_URL=http://localhost:3000/localCallbackExample # MANDATORY 5 | ENABLE_LOCAL_CALLBACK_EXAMPLE=TRUE # OPTIONAL, DISABLE FOR PRODUCTION 6 | RATE_LIMIT_MAX=1000 # OPTIONAL, THE MAXIUM NUMBER OF CONNECTIONS TO ALLOW PER TIME FRAME 7 | RATE_LIMIT_WINDOW_MS=1000 # OPTIONAL, TIME FRAME FOR WHICH REQUESTS ARE CHECKED IN MS 8 | 9 | ## Client ## 10 | MAX_ATTACHMENT_SIZE=10000000 # IF REACHED, MEDIA ATTACHMENT BODY WILL BE NULL 11 | SET_MESSAGES_AS_SEEN=TRUE # WILL MARK THE MESSAGES AS READ AUTOMATICALLY 12 | # ALL CALLBACKS: auth_failure|authenticated|call|change_state|disconnected|group_join|group_leave|group_update|loading_screen|media_uploaded|message|message_ack|message_create|message_reaction|message_revoke_everyone|qr|ready|contact_changed|media 13 | DISABLED_CALLBACKS=message_ack|message_reaction # PREVENT SENDING CERTAIN TYPES OF CALLBACKS BACK TO THE WEBHOOK 14 | WEB_VERSION='2.2328.5' # OPTIONAL, THE VERSION OF WHATSAPP WEB TO USE 15 | WEB_VERSION_CACHE_TYPE=none # OPTIONAL, DETERMINTES WHERE TO GET THE WHATSAPP WEB VERSION(local, remote or none), DEFAULT 'none' 16 | RECOVER_SESSIONS=TRUE # OPTIONAL, SHOULD WE RECOVER THE SESSION IN CASE OF PAGE FAILURES 17 | 18 | ## Session File Storage ## 19 | SESSIONS_PATH=./sessions # OPTIONAL 20 | 21 | ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | jest: true 6 | }, 7 | extends: 'standard', 8 | overrides: [ 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 'latest', 12 | sourceType: 'module' 13 | }, 14 | rules: { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD Pipeline for Pull Requests to Master 2 | 'on': 3 | pull_request: 4 | branches: 5 | - master 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: 12 | - 14.x 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | - name: 'Use Node.js ${{ matrix.node-version }}' 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '${{ matrix.node-version }}' 20 | - name: Install dependencies 21 | run: npm ci 22 | - name: Run tests 23 | run: npm test 24 | timeout-minutes: 1 -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD Pipeline for Push to Master 2 | 'on': 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: 12 | - 14.x 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | - name: 'Use Node.js ${{ matrix.node-version }}' 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '${{ matrix.node-version }}' 20 | - name: Install dependencies 21 | run: npm ci 22 | - name: Run tests 23 | run: npm test 24 | timeout-minutes: 1 25 | docker: 26 | needs: test 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Fetch latest wwebjs tag 30 | id: fetch-wwebjs-tag 31 | run: | 32 | repo="pedroslopez/whatsapp-web.js" 33 | latest_tag=$(curl -s "https://api.github.com/repos/$repo/tags" | jq -r .[0].name) 34 | docker_friendly_tag=$(echo "$latest_tag" | tr '.-' '__') 35 | echo "tag=$docker_friendly_tag" >> "$GITHUB_OUTPUT" 36 | - name: Set up QEMU 37 | uses: docker/setup-qemu-action@v2 38 | - name: Set up Docker Buildx 39 | uses: docker/setup-buildx-action@v2 40 | - name: Login to Docker Hub 41 | uses: docker/login-action@v2 42 | with: 43 | username: '${{ secrets.DOCKER_HUB_USERNAME }}' 44 | password: '${{ secrets.DOCKER_HUB_TOKEN }}' 45 | - name: Actual tag from fetch-tag job 46 | run: | 47 | echo "Tag from fetch-tag job: ${{ steps.fetch-wwebjs-tag.outputs.tag }}" 48 | - name: Build and push with dynamic tag 49 | uses: docker/build-push-action@v4 50 | with: 51 | platforms: linux/amd64,linux/arm64,linux/arm/v7 52 | push: true 53 | tags: | 54 | chrishubert/whatsapp-web-api:${{ steps.fetch-wwebjs-tag.outputs.tag }} 55 | chrishubert/whatsapp-web-api:latest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore node_modules 2 | node_modules 3 | 4 | # Ignore dotenv files 5 | .env 6 | 7 | # Ignore sessions 8 | sessions 9 | sessions_test 10 | .wwebjs_cache 11 | 12 | # Ignore logs 13 | logs 14 | 15 | # Ignore test coverage reports 16 | coverage 17 | 18 | # Ignore other unnecessary files 19 | .DS_Store 20 | .vscode 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to WhatsApp Web.js REST API Wrapper 2 | 3 | Welcome to WhatsApp Web.js REST API Wrapper! We appreciate your interest in contributing to this project. Please follow the guidelines below to contribute effectively. 4 | 5 | ## Getting Started 6 | 7 | 1. Fork the repository. 8 | 2. Clone your forked repository to your local machine. 9 | 3. Install the necessary dependencies by running `npm install`. 10 | 4. Create a new branch for your contribution. 11 | 12 | ## Code Style 13 | 14 | - Follow the existing code style and conventions in the project. 15 | - Use meaningful variable and function names. 16 | - Add comments to your code, especially for complex or tricky parts. 17 | 18 | ## Pull Requests 19 | 20 | - Create a pull request from your branch to the `master` branch of this repository. 21 | - Provide a clear and descriptive title for your pull request. 22 | - Include a detailed description of the changes you made in the pull request. 23 | - Reference any related issues in your pull request description using the `#` symbol followed by the issue number. 24 | 25 | ## Testing 26 | 27 | - Write appropriate unit tests for your code. 28 | - Make sure all existing tests pass. 29 | - Provide instructions for testing your changes, if necessary. 30 | 31 | ## Documentation 32 | 33 | - Update the README.md file with any relevant information about your contribution, including installation instructions, usage examples, and API documentation. 34 | 35 | ## Code Block Example 36 | 37 | When providing code examples or error messages, please use code blocks. You can create a code block by wrapping your code or message with triple backticks (\```) on separate lines, like this: 38 | 39 | \``` 40 | // Example code block 41 | const hello = "Hello, world!"; 42 | console.log(hello); 43 | \``` 44 | 45 | This will render as: 46 | 47 | ``` 48 | // Example code block 49 | const hello = "Hello, world!"; 50 | console.log(hello); 51 | ``` 52 | 53 | ## Contact Us 54 | 55 | If you have any questions or need further assistance, feel free to contact us by opening an issue or reaching out to us through email or chat. 56 | 57 | Thank you for your contribution to WhatsApp Web.js REST API Wrapper! -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Node.js Alpine image as the base image 2 | FROM node:14-alpine 3 | 4 | # Set the working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Install Chromium 8 | ENV CHROME_BIN="/usr/bin/chromium-browser" \ 9 | PUPPETEER_SKIP_CHROMIUM_DOWNLOAD="true" \ 10 | NODE_ENV="production" 11 | RUN set -x \ 12 | && apk update \ 13 | && apk upgrade \ 14 | && apk add --no-cache \ 15 | udev \ 16 | ttf-freefont \ 17 | chromium 18 | 19 | # Copy package.json and package-lock.json to the working directory 20 | COPY package*.json ./ 21 | 22 | # Install the dependencies 23 | RUN npm ci --only=production --ignore-scripts 24 | 25 | # Copy the rest of the source code to the working directory 26 | COPY . . 27 | 28 | # Expose the port the API will run on 29 | EXPOSE 3000 30 | 31 | # Start the API 32 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | The WhatsApp Web.js REST API Wrapper is licensed under the MIT License, which is a permissive open source license that allows you to use, modify, and distribute the software for both commercial and non-commercial purposes. Please see the full license text below. 6 | 7 | ## License 8 | 9 | MIT License 10 | 11 | ``` 12 | MIT License 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WhatsApp REST API 2 | 3 | REST API wrapper for the [whatsapp-web.js](https://github.com/pedroslopez/whatsapp-web.js) library, providing an easy-to-use interface to interact with the WhatsApp Web platform. 4 | It is designed to be used as a docker container, scalable, secure, and easy to integrate with other non-NodeJs projects. 5 | 6 | This project is a work in progress: star it, create issues, features or pull requests ❣️ 7 | 8 | **NOTE**: I can't guarantee you will not be blocked by using this method, although it has worked for me. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe. 9 | 10 | ## Table of Contents 11 | 12 | [1. Quick Start with Docker](#quick-start-with-docker) 13 | 14 | [2. Features](#features) 15 | 16 | [3. Run Locally](#run-locally) 17 | 18 | [4. Testing](#testing) 19 | 20 | [5. Documentation](#documentation) 21 | 22 | [6. Deploy to Production](#deploy-to-production) 23 | 24 | [7. Contributing](#contributing) 25 | 26 | [8. License](#license) 27 | 28 | [9. Star History](#star-history) 29 | 30 | ## Quick Start with Docker 31 | 32 | [![dockeri.co](https://dockerico.blankenship.io/image/chrishubert/whatsapp-web-api)](https://hub.docker.com/r/chrishubert/whatsapp-web-api) 33 | 34 | 1. Clone the repository: 35 | 36 | ```bash 37 | git clone https://github.com/chrishubert/whatsapp-api.git 38 | cd whatsapp-api 39 | ``` 40 | 41 | 3. Run the Docker Compose: 42 | 43 | ```bash 44 | docker-compose pull && docker-compose up 45 | ``` 46 | 4. Visit http://localhost:3000/session/start/ABCD 47 | 48 | 5. Scan the QR on your console using WhatsApp mobile app -> Linked Device -> Link a Device (it may take time to setup the session) 49 | 50 | 6. Visit http://localhost:3000/client/getContacts/ABCD 51 | 52 | 7. EXTRA: Look at all the callbacks data in `./session/message_log.txt` 53 | 54 | ![Quick Start](./assets/basic_start.gif) 55 | 56 | ## Features 57 | 58 | 1. API and Callbacks 59 | 60 | | Actions | Status | Sessions | Status | Callbacks | Status | 61 | | ----------------------------| ------| ----------------------------------------| ------| ----------------------------------------------| ------| 62 | | Send Image Message | ✅ | Initiate session | ✅ | Callback QR code | ✅ | 63 | | Send Video Message | ✅ | Terminate session | ✅ | Callback new message | ✅ | 64 | | Send Audio Message | ✅ | Terminate inactive sessions | ✅ | Callback status change | ✅ | 65 | | Send Document Message | ✅ | Terminate all sessions | ✅ | Callback message media attachment | ✅ | 66 | | Send File URL | ✅ | Healthcheck | ✅ | | | 67 | | Send Button Message | ✅ | Local test callback | | | | 68 | | Send Contact Message | ✅ | | | | | 69 | | Send List Message | ✅ | | | | | 70 | | Set Status | ✅ | | | | | 71 | | Send Button With Media | ✅ | | | | | 72 | | Is On Whatsapp? | ✅ | | | | | 73 | | Download Profile Pic | ✅ | | | | | 74 | | User Status | ✅ | | | | | 75 | | Block/Unblock User | ✅ | | | | | 76 | | Update Profile Picture | ✅ | | | | | 77 | | Create Group | ✅ | | | | | 78 | | Leave Group | ✅ | | | | | 79 | | All Groups | ✅ | | | | | 80 | | Invite User | ✅ | | | | | 81 | | Make Admin | ✅ | | | | | 82 | | Demote Admin | ✅ | | | | | 83 | | Group Invite Code | ✅ | | | | | 84 | | Update Group Participants | ✅ | | | | | 85 | | Update Group Setting | ✅ | | | | | 86 | | Update Group Subject | ✅ | | | | | 87 | | Update Group Description | ✅ | | | | | 88 | 89 | 3. Handle multiple client sessions (session data saved locally), identified by unique id 90 | 91 | 4. All endpoints may be secured by a global API key 92 | 93 | 5. On server start, all existing sessions are restored 94 | 95 | 6. Set messages automatically as read 96 | 97 | 7. Disable any of the callbacks 98 | 99 | ## Run Locally 100 | 101 | 1. Clone the repository: 102 | 103 | ```bash 104 | git clone https://github.com/chrishubert/whatsapp-api.git 105 | cd whatsapp-api 106 | ``` 107 | 108 | 2. Install the dependencies: 109 | 110 | ```bash 111 | npm install 112 | ``` 113 | 114 | 3. Copy the `.env.example` file to `.env` and update the required environment variables: 115 | 116 | ```bash 117 | cp .env.example .env 118 | ``` 119 | 120 | 4. Run the application: 121 | 122 | ```bash 123 | npm run start 124 | ``` 125 | 126 | 5. Access the API at `http://localhost:3000` 127 | 128 | ## Testing 129 | 130 | Run the test suite with the following command: 131 | 132 | ```bash 133 | npm run test 134 | ``` 135 | 136 | ## Documentation 137 | 138 | API documentation can be found in the [`swagger.json`](https://raw.githubusercontent.com/chrishubert/whatsapp-api/master/swagger.json) file. See this file directly into [Swagger Editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/chrishubert/whatsapp-api/master/swagger.json) or any other OpenAPI-compatible tool to view and interact with the API documentation. 139 | 140 | This documentation is straightforward if you are familiar with whatsapp-web.js library (https://docs.wwebjs.dev/) 141 | If you are still confused - open an issue and I'll improve it. 142 | 143 | Also, there is an option to run the documentation endpoint locally by setting the `ENABLE_SWAGGER_ENDPOINT` environment variable. Restart the service and go to `/api-docs` endpoint to see it. 144 | 145 | By default, all callback events are delivered to the webhook defined with the `BASE_WEBHOOK_URL` environment variable. 146 | This can be overridden by setting the `*_WEBHOOK_URL` environment variable, where `*` is your sessionId. 147 | For example, if you have the sessionId defined as `DEMO`, the environment variable must be `DEMO_WEBHOOK_URL`. 148 | 149 | By setting the `DISABLED_CALLBACKS` environment variable you can specify what events you are **not** willing to receive on your webhook. 150 | 151 | ### Scanning QR code 152 | 153 | In order to validate a new WhatsApp Web instance you need to scan the QR code using your mobile phone. Official documentation can be found at (https://faq.whatsapp.com/1079327266110265/?cms_platform=android) page. The service itself delivers the QR code content as a webhook event or you can use the REST endpoints (`/session/qr/:sessionId` or `/session/qr/:sessionId/image` to get the QR code as a png image). 154 | 155 | ## Deploy to Production 156 | 157 | - Load the docker image in docker-compose, or your Kubernetes environment 158 | - Disable the `ENABLE_LOCAL_CALLBACK_EXAMPLE` environment variable 159 | - Set the `API_KEY` environment variable to protect the REST endpoints 160 | - Run periodically the `/api/terminateInactiveSessions` endpoint to prevent useless sessions to take up space and resources(only in case you are not in control of the sessions) 161 | 162 | ## Contributing 163 | 164 | Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 165 | 166 | ## Disclaimer 167 | 168 | This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or its affiliates. The official WhatsApp website can be found at https://whatsapp.com. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners. 169 | 170 | ## License 171 | 172 | This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE.md) file for details. 173 | 174 | ## Star History 175 | 176 | [![Star History Chart](https://api.star-history.com/svg?repos=chrishubert/whatsapp-api&type=Date)](https://star-history.com/#chrishubert/whatsapp-api&Date) 177 | -------------------------------------------------------------------------------- /assets/basic_start.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedroherpeto/whatsapp-api/eda88a742a7dea3915bc84b358cb9c90140ed0cf/assets/basic_start.gif -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | api_zdg: 5 | image: chrishubert/whatsapp-web-api:latest 6 | networks: 7 | - comunidadezdg 8 | volumes: 9 | - api_zdg_sessions:/usr/src/app/sessions 10 | environment: 11 | - API_KEY=comunidadezdg # OPTIONAL 12 | - BASE_WEBHOOK_URL=https://api2024.comunidadezdg.com.br/localCallbackExample 13 | - ENABLE_LOCAL_CALLBACK_EXAMPLE=TRUE # OPTIONAL, NOT RECOMMENDED FOR PRODUCTION 14 | - MAX_ATTACHMENT_SIZE=5000000 # IN BYTES 15 | - SET_MESSAGES_AS_SEEN=TRUE # WILL MARK THE MESSAGES AS READ AUTOMATICALLY 16 | # ALL CALLBACKS: auth_failure|authenticated|call|change_state|disconnected|group_join|group_leave|group_update|loading_screen|media_uploaded|message|message_ack|message_create|message_reaction|message_revoke_everyone|qr|ready|contact_changed 17 | - DISABLED_CALLBACKS=message_ack # PREVENT SENDING CERTAIN TYPES OF CALLBACKS BACK TO THE WEBHOOK 18 | - ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL, ENABLES THE /api-docs ENDPOINT 19 | deploy: 20 | mode: replicated 21 | replicas: 1 22 | placement: 23 | constraints: 24 | - node.role == manager 25 | resources: 26 | limits: 27 | cpus: "1" 28 | memory: 2048M 29 | labels: 30 | - traefik.enable=true 31 | - traefik.http.routers.api_zdg.rule=Host(`api2024.comunidadezdg.com.br`) 32 | - traefik.http.routers.api_zdg.entrypoints=websecure 33 | - traefik.http.routers.api_zdg.tls.certresolver=letsencryptresolver 34 | - traefik.http.routers.api_zdg.priority=1 35 | - traefik.http.routers.api_zdg.service=api_zdg 36 | - traefik.http.services.api_zdg.loadbalancer.server.port=3000 37 | - traefik.http.services.api_zdg.loadbalancer.passHostHeader=true 38 | 39 | volumes: 40 | api_zdg_sessions: 41 | external: true 42 | name: api_zdg_sessions 43 | 44 | networks: 45 | comunidadezdg: 46 | name: comunidadezdg 47 | external: true 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whatsapp-web-api", 3 | "version": "1.0.0", 4 | "description": "REST API wrapper for whatsapp-web.js", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "jest --runInBand", 9 | "swagger": "node swagger.js" 10 | }, 11 | "dependencies": { 12 | "axios": "^1.5.0", 13 | "dotenv": "^16.3.1", 14 | "express": "^4.18.2", 15 | "express-rate-limit": "^6.10.0", 16 | "qr-image": "^3.2.0", 17 | "qrcode-terminal": "^0.12.0", 18 | "swagger-ui-express": "^4.6.3", 19 | "whatsapp-web.js": "^1.26.0" 20 | }, 21 | "devDependencies": { 22 | "eslint": "^8.38.0", 23 | "eslint-config-standard": "^17.0.0", 24 | "eslint-plugin-import": "^2.27.5", 25 | "eslint-plugin-n": "^15.7.0", 26 | "eslint-plugin-promise": "^6.1.1", 27 | "jest": "^29.5.0", 28 | "supertest": "^6.3.3", 29 | "swagger-autogen": "^2.23.1" 30 | }, 31 | "keywords": [ 32 | "whatsapp", 33 | "whatsapp-web", 34 | "api", 35 | "wrapper", 36 | "rest", 37 | "express", 38 | "axios" 39 | ], 40 | "author": "Christophe Hubert", 41 | "license": "MIT", 42 | "engines": { 43 | "node": ">=14.17.0" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/chrishubert/whatsapp-web-api.git" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/chrishubert/whatsapp-web-api/issues" 51 | }, 52 | "homepage": "https://github.com/chrishubert/whatsapp-web-api", 53 | "private": true 54 | } 55 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const app = require('./src/app') 2 | const { baseWebhookURL } = require('./src/config') 3 | require('dotenv').config() 4 | 5 | // Start the server 6 | const port = process.env.PORT || 3000 7 | 8 | // Check if BASE_WEBHOOK_URL environment variable is available 9 | if (!baseWebhookURL) { 10 | console.error('BASE_WEBHOOK_URL environment variable is not available. Exiting...') 11 | process.exit(1) // Terminate the application with an error code 12 | } 13 | 14 | app.listen(port, () => { 15 | console.log(`Server running on port ${port}`) 16 | }) 17 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | require('./routes') 2 | const { restoreSessions } = require('./sessions') 3 | const { routes } = require('./routes') 4 | const app = require('express')() 5 | const bodyParser = require('body-parser') 6 | const { maxAttachmentSize } = require('./config') 7 | 8 | // Initialize Express app 9 | app.disable('x-powered-by') 10 | app.use(bodyParser.json({ limit: maxAttachmentSize + 1000000 })) 11 | app.use(bodyParser.urlencoded({ limit: maxAttachmentSize + 1000000, extended: true })) 12 | app.use('/', routes) 13 | 14 | restoreSessions() 15 | 16 | module.exports = app 17 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | // Load environment variables from .env file 2 | require('dotenv').config() 3 | 4 | // setup global const 5 | const sessionFolderPath = process.env.SESSIONS_PATH || './sessions' 6 | const enableLocalCallbackExample = (process.env.ENABLE_LOCAL_CALLBACK_EXAMPLE || '').toLowerCase() === 'true' 7 | const globalApiKey = process.env.API_KEY 8 | const baseWebhookURL = process.env.BASE_WEBHOOK_URL 9 | const maxAttachmentSize = parseInt(process.env.MAX_ATTACHMENT_SIZE) || 10000000 10 | const setMessagesAsSeen = (process.env.SET_MESSAGES_AS_SEEN || '').toLowerCase() === 'true' 11 | const disabledCallbacks = process.env.DISABLED_CALLBACKS ? process.env.DISABLED_CALLBACKS.split('|') : [] 12 | const enableSwaggerEndpoint = (process.env.ENABLE_SWAGGER_ENDPOINT || '').toLowerCase() === 'true' 13 | const webVersion = process.env.WEB_VERSION 14 | const webVersionCacheType = process.env.WEB_VERSION_CACHE_TYPE || 'none' 15 | const rateLimitMax = process.env.RATE_LIMIT_MAX || 1000 16 | const rateLimitWindowMs = process.env.RATE_LIMIT_WINDOW_MS || 1000 17 | const recoverSessions = (process.env.RECOVER_SESSIONS || '').toLowerCase() === 'true' 18 | 19 | module.exports = { 20 | sessionFolderPath, 21 | enableLocalCallbackExample, 22 | globalApiKey, 23 | baseWebhookURL, 24 | maxAttachmentSize, 25 | setMessagesAsSeen, 26 | disabledCallbacks, 27 | enableSwaggerEndpoint, 28 | webVersion, 29 | webVersionCacheType, 30 | rateLimitMax, 31 | rateLimitWindowMs, 32 | recoverSessions 33 | } 34 | -------------------------------------------------------------------------------- /src/controllers/chatController.js: -------------------------------------------------------------------------------- 1 | const { sessions } = require('../sessions') 2 | const { sendErrorResponse } = require('../utils') 3 | 4 | /** 5 | * @function 6 | * @async 7 | * @name getClassInfo 8 | * @description Gets information about a chat using the chatId and sessionId 9 | * @param {Object} req - Request object 10 | * @param {Object} res - Response object 11 | * @param {string} req.body.chatId - The ID of the chat to get information for 12 | * @param {string} req.params.sessionId - The ID of the session to use 13 | * @returns {Object} - Returns a JSON object with the success status and chat information 14 | * @throws {Error} - Throws an error if chat is not found or if there is a server error 15 | */ 16 | const getClassInfo = async (req, res) => { 17 | try { 18 | const { chatId } = req.body 19 | const client = sessions.get(req.params.sessionId) 20 | const chat = await client.getChatById(chatId) 21 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 22 | res.json({ success: true, chat }) 23 | } catch (error) { 24 | sendErrorResponse(res, 500, error.message) 25 | } 26 | } 27 | 28 | /** 29 | * Clears all messages in a chat. 30 | * 31 | * @function 32 | * @async 33 | * @param {Object} req - The request object. 34 | * @param {Object} res - The response object. 35 | * @param {string} req.params.sessionId - The ID of the session. 36 | * @param {string} req.body.chatId - The ID of the chat to clear messages from. 37 | * @throws {Error} If the chat is not found or there is an internal server error. 38 | * @returns {Object} The success status and the cleared messages. 39 | */ 40 | const clearMessages = async (req, res) => { 41 | try { 42 | const { chatId } = req.body 43 | const client = sessions.get(req.params.sessionId) 44 | const chat = await client.getChatById(chatId) 45 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 46 | const clearMessages = await chat.clearMessages() 47 | res.json({ success: true, clearMessages }) 48 | } catch (error) { 49 | sendErrorResponse(res, 500, error.message) 50 | } 51 | } 52 | 53 | /** 54 | * Stops typing or recording in chat immediately. 55 | * 56 | * @function 57 | * @async 58 | * @param {Object} req - Request object. 59 | * @param {Object} res - Response object. 60 | * @param {string} req.body.chatId - ID of the chat to clear the state for. 61 | * @param {string} req.params.sessionId - ID of the session the chat belongs to. 62 | * @returns {Promise} - A Promise that resolves with a JSON object containing a success flag and the result of clearing the state. 63 | * @throws {Error} - If there was an error while clearing the state. 64 | */ 65 | const clearState = async (req, res) => { 66 | try { 67 | const { chatId } = req.body 68 | const client = sessions.get(req.params.sessionId) 69 | const chat = await client.getChatById(chatId) 70 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 71 | const clearState = await chat.clearState() 72 | res.json({ success: true, clearState }) 73 | } catch (error) { 74 | sendErrorResponse(res, 500, error.message) 75 | } 76 | } 77 | 78 | /** 79 | * Delete a chat. 80 | * 81 | * @async 82 | * @function 83 | * @param {Object} req - The request object. 84 | * @param {Object} res - The response object. 85 | * @param {string} req.params.sessionId - The session ID. 86 | * @param {string} req.body.chatId - The ID of the chat to be deleted. 87 | * @returns {Object} A JSON response indicating whether the chat was deleted successfully. 88 | * @throws {Object} If there is an error while deleting the chat, an error response is sent with a status code of 500. 89 | * @throws {Object} If the chat is not found, an error response is sent with a status code of 404. 90 | */ 91 | const deleteChat = async (req, res) => { 92 | try { 93 | const { chatId } = req.body 94 | const client = sessions.get(req.params.sessionId) 95 | const chat = await client.getChatById(chatId) 96 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 97 | const deleteChat = await chat.delete() 98 | res.json({ success: true, deleteChat }) 99 | } catch (error) { 100 | sendErrorResponse(res, 500, error.message) 101 | } 102 | } 103 | 104 | /** 105 | * Fetches messages from a specified chat. 106 | * 107 | * @function 108 | * @async 109 | * 110 | * @param {Object} req - The request object containing sessionId, chatId, and searchOptions. 111 | * @param {string} req.params.sessionId - The ID of the session associated with the chat. 112 | * @param {Object} req.body - The body of the request containing chatId and searchOptions. 113 | * @param {string} req.body.chatId - The ID of the chat from which to fetch messages. 114 | * @param {Object} req.body.searchOptions - The search options to use when fetching messages. 115 | * 116 | * @param {Object} res - The response object to send the fetched messages. 117 | * @returns {Promise} A JSON object containing the success status and fetched messages. 118 | * 119 | * @throws {Error} If the chat is not found or there is an error fetching messages. 120 | */ 121 | const fetchMessages = async (req, res) => { 122 | try { 123 | /* 124 | #swagger.requestBody = { 125 | required: true, 126 | schema: { 127 | type: 'object', 128 | properties: { 129 | chatId: { 130 | type: 'string', 131 | description: 'Unique whatsApp identifier for the given Chat (either group or personnal)', 132 | example: '6281288888888@c.us' 133 | }, 134 | searchOptions: { 135 | type: 'object', 136 | description: 'Search options for fetching messages', 137 | example: '{}' 138 | } 139 | } 140 | } 141 | } 142 | */ 143 | const { chatId, searchOptions } = req.body 144 | const client = sessions.get(req.params.sessionId) 145 | const chat = await client.getChatById(chatId) 146 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 147 | const messages = await chat.fetchMessages(searchOptions) 148 | res.json({ success: true, messages }) 149 | } catch (error) { 150 | sendErrorResponse(res, 500, error.message) 151 | } 152 | } 153 | 154 | /** 155 | * Gets the contact for a chat 156 | * @async 157 | * @function 158 | * @param {Object} req - The HTTP request object 159 | * @param {Object} res - The HTTP response object 160 | * @param {string} req.params.sessionId - The ID of the current session 161 | * @param {string} req.body.chatId - The ID of the chat to get the contact for 162 | * @returns {Promise} - Promise that resolves with the chat's contact information 163 | * @throws {Error} - Throws an error if chat is not found or if there is an error getting the contact information 164 | */ 165 | const getContact = async (req, res) => { 166 | try { 167 | const { chatId } = req.body 168 | const client = sessions.get(req.params.sessionId) 169 | const chat = await client.getChatById(chatId) 170 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 171 | const contact = await chat.getContact() 172 | res.json({ success: true, contact }) 173 | } catch (error) { 174 | sendErrorResponse(res, 500, error.message) 175 | } 176 | } 177 | 178 | /** 179 | * Send a recording state to a WhatsApp chat. 180 | * @async 181 | * @function 182 | * @param {object} req - The request object. 183 | * @param {object} res - The response object. 184 | * @param {string} req.params.sessionId - The session ID. 185 | * @param {object} req.body - The request body. 186 | * @param {string} req.body.chatId - The ID of the chat to send the recording state to. 187 | * @returns {object} - An object containing a success message and the result of the sendStateRecording method. 188 | * @throws {object} - An error object containing a status code and error message if an error occurs. 189 | */ 190 | const sendStateRecording = async (req, res) => { 191 | try { 192 | const { chatId } = req.body 193 | const client = sessions.get(req.params.sessionId) 194 | const chat = await client.getChatById(chatId) 195 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 196 | const sendStateRecording = await chat.sendStateRecording() 197 | res.json({ success: true, sendStateRecording }) 198 | } catch (error) { 199 | sendErrorResponse(res, 500, error.message) 200 | } 201 | } 202 | 203 | /** 204 | * Send a typing state to a WhatsApp chat. 205 | * @async 206 | * @function 207 | * @param {object} req - The request object. 208 | * @param {object} res - The response object. 209 | * @param {string} req.params.sessionId - The session ID. 210 | * @param {object} req.body - The request body. 211 | * @param {string} req.body.chatId - The ID of the chat to send the typing state to. 212 | * @returns {object} - An object containing a success message and the result of the sendStateTyping method. 213 | * @throws {object} - An error object containing a status code and error message if an error occurs. 214 | */ 215 | const sendStateTyping = async (req, res) => { 216 | try { 217 | const { chatId } = req.body 218 | const client = sessions.get(req.params.sessionId) 219 | const chat = await client.getChatById(chatId) 220 | if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') } 221 | const sendStateTyping = await chat.sendStateTyping() 222 | res.json({ success: true, sendStateTyping }) 223 | } catch (error) { 224 | sendErrorResponse(res, 500, error.message) 225 | } 226 | } 227 | 228 | module.exports = { 229 | getClassInfo, 230 | clearMessages, 231 | clearState, 232 | deleteChat, 233 | fetchMessages, 234 | getContact, 235 | sendStateRecording, 236 | sendStateTyping 237 | } 238 | -------------------------------------------------------------------------------- /src/controllers/clientController.js: -------------------------------------------------------------------------------- 1 | const { MessageMedia, Location, Buttons, List, Poll } = require('whatsapp-web.js') 2 | const { sessions } = require('../sessions') 3 | const { sendErrorResponse } = require('../utils') 4 | 5 | /** 6 | * Send a message to a chat using the WhatsApp API 7 | * 8 | * @async 9 | * @function sendMessage 10 | * @param {Object} req - The request object containing the request parameters 11 | * @param {Object} req.body - The request body containing the chatId, content, contentType and options 12 | * @param {string} req.body.chatId - The chat id where the message will be sent 13 | * @param {string|Object} req.body.content - The message content to be sent, can be a string or an object containing the MessageMedia data 14 | * @param {string} req.body.contentType - The type of the message content, must be one of the following: 'string', 'MessageMedia', 'MessageMediaFromURL', 'Location', 'Buttons', or 'List' 15 | * @param {Object} req.body.options - Additional options to be passed to the WhatsApp API 16 | * @param {string} req.params.sessionId - The id of the WhatsApp session to be used 17 | * @param {Object} res - The response object 18 | * @returns {Object} - The response object containing a success flag and the sent message data 19 | * @throws {Error} - If there is an error while sending the message 20 | */ 21 | const sendMessage = async (req, res) => { 22 | /* 23 | #swagger.requestBody = { 24 | required: true, 25 | '@content': { 26 | "application/json": { 27 | schema: { 28 | type: 'object', 29 | properties: { 30 | chatId: { 31 | type: 'string', 32 | description: 'The Chat id which contains the message (Group or Individual)', 33 | }, 34 | contentType: { 35 | type: 'string', 36 | description: 'The type of message content, must be one of the following: string, MessageMedia, MessageMediaFromURL, Location, Buttons, or List', 37 | }, 38 | content: { 39 | type: 'object', 40 | description: 'The content of the message, can be a string or an object', 41 | }, 42 | options: { 43 | type: 'object', 44 | description: 'The message send options', 45 | } 46 | } 47 | }, 48 | examples: { 49 | string: { value: { chatId: '6281288888888@c.us', contentType: 'string', content: 'Hello World!' } }, 50 | MessageMedia: { value: { chatId: '6281288888888@c.us', contentType: 'MessageMedia', content: { mimetype: 'image/jpeg', data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', filename: 'image.jpg' } } }, 51 | MessageMediaFromURL: { value: { chatId: '6281288888888@c.us', contentType: 'MessageMediaFromURL', content: 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Example' } }, 52 | Location: { value: { chatId: '6281288888888@c.us', contentType: 'Location', content: { latitude: -6.2, longitude: 106.8, description: 'Jakarta' } } }, 53 | Buttons: { value: { chatId: '6281288888888@c.us', contentType: 'Buttons', content: { body: 'Hello World!', buttons: [{ body: 'button 1' }], title: 'Hello World!', footer: 'Hello World!' } } }, 54 | List: { 55 | value: { chatId: '6281288888888@c.us', contentType: 'List', content: { body: 'Hello World!', buttonText: 'Hello World!', sections: [{ title: 'sectionTitle', rows: [{ id: 'customId', title: 'ListItem2', description: 'desc' }, { title: 'ListItem2' }] }], title: 'Hello World!', footer: 'Hello World!' } } 56 | }, 57 | Contact: { 58 | value: { chatId: '6281288888888@c.us', contentType: 'Contact', content: { contactId: '6281288888889@c.us' } } 59 | }, 60 | Poll: { 61 | value: { chatId: '6281288888888@c.us', contentType: 'Poll', content: { pollName: 'Cats or Dogs?', pollOptions: ['Cats', 'Dogs'], options: { allowMultipleAnswers: true } } } 62 | }, 63 | } 64 | } 65 | } 66 | } 67 | */ 68 | 69 | try { 70 | const { chatId, content, contentType, options } = req.body 71 | const client = sessions.get(req.params.sessionId) 72 | 73 | let messageOut 74 | switch (contentType) { 75 | case 'string': 76 | if (options && options.media) { 77 | const media = options.media 78 | options.media = new MessageMedia(media.mimetype, media.data, media.filename = null, media.filesize = null) 79 | } 80 | messageOut = await client.sendMessage(chatId, content, options) 81 | break 82 | case 'MessageMediaFromURL': { 83 | const messageMediaFromURL = await MessageMedia.fromUrl(content, { unsafeMime: true }) 84 | messageOut = await client.sendMessage(chatId, messageMediaFromURL, options) 85 | break 86 | } 87 | case 'MessageMedia': { 88 | const messageMedia = new MessageMedia(content.mimetype, content.data, content.filename, content.filesize) 89 | messageOut = await client.sendMessage(chatId, messageMedia, options) 90 | break 91 | } 92 | case 'Location': { 93 | const location = new Location(content.latitude, content.longitude, content.description) 94 | messageOut = await client.sendMessage(chatId, location, options) 95 | break 96 | } 97 | case 'Buttons': { 98 | const buttons = new Buttons(content.body, content.buttons, content.title, content.footer) 99 | messageOut = await client.sendMessage(chatId, buttons, options) 100 | break 101 | } 102 | case 'List': { 103 | const list = new List(content.body, content.buttonText, content.sections, content.title, content.footer) 104 | messageOut = await client.sendMessage(chatId, list, options) 105 | break 106 | } 107 | case 'Contact': { 108 | const contact = await client.getContactById(typeof content.contactId === 'number' ? content.contactId + '@c.us' : content.contactId) 109 | messageOut = await client.sendMessage(chatId, contact, options) 110 | break 111 | } 112 | case 'Poll': { 113 | const poll = new Poll(content.pollName, content.pollOptions, content.options) 114 | messageOut = await client.sendMessage(chatId, poll, options) 115 | break 116 | } 117 | default: 118 | return sendErrorResponse(res, 404, 'contentType invalid, must be string, MessageMedia, MessageMediaFromURL, Location, Buttons, List, Contact or Poll') 119 | } 120 | 121 | res.json({ success: true, message: messageOut }) 122 | } catch (error) { 123 | console.log(error) 124 | sendErrorResponse(res, 500, error.message) 125 | } 126 | } 127 | 128 | /** 129 | * Get session information for a given sessionId 130 | * 131 | * @async 132 | * @function getClientInfo 133 | * @param {Object} req - Express request object 134 | * @param {Object} res - Express response object 135 | * @param {string} req.params.sessionId - The sessionId for which the session info is requested 136 | * @returns {Object} - Response object with session info 137 | * @throws Will throw an error if session info cannot be retrieved 138 | */ 139 | const getClassInfo = async (req, res) => { 140 | try { 141 | const client = sessions.get(req.params.sessionId) 142 | const sessionInfo = await client.info 143 | res.json({ success: true, sessionInfo }) 144 | } catch (error) { 145 | sendErrorResponse(res, 500, error.message) 146 | } 147 | } 148 | 149 | /** 150 | * Check if a user is registered on WhatsApp 151 | * 152 | * @async 153 | * @function isRegisteredUser 154 | * @param {Object} req - Express request object 155 | * @param {Object} res - Express response object 156 | * @param {string} req.params.sessionId - The sessionId in which the user is registered 157 | * @param {string} req.body.id - The id of the user to check 158 | * @returns {Object} - Response object with a boolean indicating whether the user is registered 159 | * @throws Will throw an error if user registration cannot be checked 160 | */ 161 | const isRegisteredUser = async (req, res) => { 162 | /* 163 | #swagger.requestBody = { 164 | required: true, 165 | schema: { 166 | type: 'object', 167 | properties: { 168 | number: { 169 | type: 'string', 170 | description: 'The number or ID (\"@c.us\" will be automatically appended if not specified)', 171 | example: '6281288888888' 172 | }, 173 | } 174 | }, 175 | } 176 | */ 177 | try { 178 | const { number } = req.body 179 | const client = sessions.get(req.params.sessionId) 180 | const result = await client.isRegisteredUser(number) 181 | res.json({ success: true, result }) 182 | } catch (error) { 183 | sendErrorResponse(res, 500, error.message) 184 | } 185 | } 186 | 187 | /** 188 | * Retrieves the registered WhatsApp ID for a number 189 | * 190 | * @async 191 | * @function getNumberId 192 | * @param {Object} req - Express request object 193 | * @param {Object} res - Express response object 194 | * @param {string} req.params.sessionId - The sessionId in which the user is registered 195 | * @param {string} req.body.id - The id of the user to check 196 | * @returns {Object} - Response object with a boolean indicating whether the user is registered 197 | * @throws Will throw an error if user registration cannot be checked 198 | */ 199 | const getNumberId = async (req, res) => { 200 | /* 201 | #swagger.requestBody = { 202 | required: true, 203 | schema: { 204 | type: 'object', 205 | properties: { 206 | number: { 207 | type: 'string', 208 | description: 'The number or ID (\"@c.us\" will be automatically appended if not specified)', 209 | example: '6281288888888' 210 | }, 211 | } 212 | }, 213 | } 214 | */ 215 | try { 216 | const { number } = req.body 217 | const client = sessions.get(req.params.sessionId) 218 | const result = await client.getNumberId(number) 219 | res.json({ success: true, result }) 220 | } catch (error) { 221 | sendErrorResponse(res, 500, error.message) 222 | } 223 | } 224 | 225 | /** 226 | * Create a group with the given name and participants 227 | * 228 | * @async 229 | * @function createGroup 230 | * @param {Object} req - Express request object 231 | * @param {Object} res - Express response object 232 | * @param {string} req.params.sessionId - The sessionId in which to create the group 233 | * @param {string} req.body.name - The name of the group to create 234 | * @param {Array} req.body.participants - Array of user ids to add to the group 235 | * @returns {Object} - Response object with information about the created group 236 | * @throws Will throw an error if group cannot be created 237 | */ 238 | const createGroup = async (req, res) => { 239 | try { 240 | const { name, participants } = req.body 241 | const client = sessions.get(req.params.sessionId) 242 | const response = await client.createGroup(name, participants) 243 | res.json({ success: true, response }) 244 | } catch (error) { 245 | sendErrorResponse(res, 500, error.message) 246 | } 247 | } 248 | 249 | /** 250 | * Set the status of the user in a given session 251 | * 252 | * @async 253 | * @function setStatus 254 | * @param {Object} req - Express request object 255 | * @param {Object} res - Express response object 256 | * @param {string} req.params.sessionId - The sessionId in which to set the status 257 | * @param {string} req.body.status - The status to set 258 | * @returns {Object} - Response object indicating success 259 | * @throws Will throw an error if status cannot be set 260 | */ 261 | const setStatus = async (req, res) => { 262 | /* 263 | #swagger.requestBody = { 264 | required: true, 265 | schema: { 266 | type: 'object', 267 | properties: { 268 | status: { 269 | type: 'string', 270 | description: 'New status message', 271 | example: 'I\'m running WhatsApp Web Api' 272 | }, 273 | } 274 | }, 275 | } 276 | */ 277 | try { 278 | const { status } = req.body 279 | const client = sessions.get(req.params.sessionId) 280 | await client.setStatus(status) 281 | res.json({ success: true }) 282 | } catch (error) { 283 | sendErrorResponse(res, 500, error.message) 284 | } 285 | } 286 | 287 | /** 288 | * Retrieves the contacts of the current session. 289 | * @async 290 | * @function 291 | * @param {Object} req - The request object. 292 | * @param {string} req.params.sessionId - The session ID associated with the client. 293 | * @param {Object} res - The response object. 294 | * @returns {Promise} - A Promise that resolves with the retrieved contacts or rejects with an error. 295 | */ 296 | const getContacts = async (req, res) => { 297 | try { 298 | const client = sessions.get(req.params.sessionId) 299 | const contacts = await client.getContacts() 300 | res.json({ success: true, contacts }) 301 | } catch (error) { 302 | sendErrorResponse(res, 500, error.message) 303 | } 304 | } 305 | 306 | /** 307 | * Retrieve all chats for the given session ID. 308 | * 309 | * @function 310 | * @async 311 | * 312 | * @param {Object} req - The request object. 313 | * @param {string} req.params.sessionId - The session ID. 314 | * @param {Object} res - The response object. 315 | * 316 | * @returns {Promise} A Promise that resolves when the operation is complete. 317 | * 318 | * @throws {Error} If the operation fails, an error is thrown. 319 | */ 320 | const getChats = async (req, res) => { 321 | try { 322 | const client = sessions.get(req.params.sessionId) 323 | const chats = await client.getChats() 324 | res.json({ success: true, chats }) 325 | } catch (error) { 326 | sendErrorResponse(res, 500, error.message) 327 | } 328 | } 329 | 330 | /** 331 | * Returns the profile picture URL for a given contact ID. 332 | * 333 | * @async 334 | * @function 335 | * @param {Object} req - Express request object. 336 | * @param {Object} res - Express response object. 337 | * @param {string} req.params.sessionId - The ID of the current session. 338 | * @param {string} req.body.contactId - The ID of the contact to get the profile picture for. 339 | * @returns {Promise} - A Promise that resolves with the profile picture URL. 340 | * @throws {Error} - If there is an error retrieving the profile picture URL. 341 | */ 342 | const getProfilePictureUrl = async (req, res) => { 343 | /* 344 | #swagger.requestBody = { 345 | required: true, 346 | schema: { 347 | type: 'object', 348 | properties: { 349 | contactId: { 350 | type: 'string', 351 | description: 'The contact ID\'s of profile', 352 | example: '6281288888888@c.us' 353 | }, 354 | } 355 | }, 356 | } 357 | */ 358 | try { 359 | const { contactId } = req.body 360 | const client = sessions.get(req.params.sessionId) 361 | const result = await client.getProfilePicUrl(contactId) 362 | res.json({ success: true, result }) 363 | } catch (error) { 364 | sendErrorResponse(res, 500, error.message) 365 | } 366 | } 367 | 368 | /** 369 | * Accepts an invite. 370 | * 371 | * @async 372 | * @function 373 | * @param {Object} req - The HTTP request object. 374 | * @param {Object} req.body - The request body. 375 | * @param {Object} req.params - The request parameters. 376 | * @param {string} req.params.sessionId - The ID of the session. 377 | * @param {Object} res - The HTTP response object. 378 | * @returns {Object} The response object. 379 | * @throws {Error} If there is an error while accepting the invite. 380 | */ 381 | const acceptInvite = async (req, res) => { 382 | /* 383 | #swagger.requestBody = { 384 | required: true, 385 | schema: { 386 | type: 'object', 387 | properties: { 388 | inviteCode: { 389 | type: 'string', 390 | description: 'Invitation code', 391 | example: '' 392 | }, 393 | } 394 | }, 395 | } 396 | */ 397 | try { 398 | const { inviteCode } = req.body 399 | const client = sessions.get(req.params.sessionId) 400 | const acceptInvite = await client.acceptInvite(inviteCode) 401 | res.json({ success: true, acceptInvite }) 402 | } catch (error) { 403 | sendErrorResponse(res, 500, error.message) 404 | } 405 | } 406 | 407 | /** 408 | * Retrieves the version of WhatsApp Web currently being run. 409 | * 410 | * @async 411 | * @function getWWebVersion 412 | * @param {Object} req - The HTTP request object. 413 | * @param {Object} req.params - The request parameters. 414 | * @param {string} req.params.sessionId - The ID of the session. 415 | * @param {Object} res - The HTTP response object. 416 | * @returns {Object} The response object. 417 | * @throws {Error} If there is an error while accepting the invite. 418 | */ 419 | const getWWebVersion = async (req, res) => { 420 | try { 421 | const client = sessions.get(req.params.sessionId) 422 | const result = await client.getWWebVersion() 423 | res.json({ success: true, result }) 424 | } catch (error) { 425 | sendErrorResponse(res, 500, error.message) 426 | } 427 | } 428 | 429 | /** 430 | * Archives a chat. 431 | * 432 | * @async 433 | * @function 434 | * @param {Object} req - The HTTP request object. 435 | * @param {Object} req.body - The request body. 436 | * @param {Object} req.params - The request parameters. 437 | * @param {string} req.params.sessionId - The ID of the session. 438 | * @param {Object} res - The HTTP response object. 439 | * @returns {Object} The response object. 440 | * @throws {Error} If there is an error while archiving the chat. 441 | */ 442 | const archiveChat = async (req, res) => { 443 | /* 444 | #swagger.requestBody = { 445 | required: true, 446 | schema: { 447 | type: 'object', 448 | properties: { 449 | chatId: { 450 | type: 'string', 451 | description: 'ID of the chat', 452 | example: '' 453 | }, 454 | } 455 | }, 456 | } 457 | */ 458 | try { 459 | const { chatId } = req.body 460 | const client = sessions.get(req.params.sessionId) 461 | const result = await client.archiveChat(chatId) 462 | res.json({ success: true, result }) 463 | } catch (error) { 464 | sendErrorResponse(res, 500, error.message) 465 | } 466 | } 467 | 468 | /** 469 | * Get the list of blocked contacts for the user's client. 470 | * 471 | * @async 472 | * @function getBlockedContacts 473 | * @param {Object} req - The request object. 474 | * @param {string} req.params.sessionId - The session ID to use for the client. 475 | * @param {Object} res - The response object. 476 | * @returns {Promise} - A promise that resolves to an object with a success property and an array of blocked contacts. 477 | * @throws {Error} - Throws an error if the operation fails. 478 | */ 479 | const getBlockedContacts = async (req, res) => { 480 | try { 481 | const client = sessions.get(req.params.sessionId) 482 | const blockedContacts = await client.getBlockedContacts() 483 | res.json({ success: true, blockedContacts }) 484 | } catch (error) { 485 | sendErrorResponse(res, 500, error.message) 486 | } 487 | } 488 | 489 | /** 490 | * Get the chat with the given ID. 491 | * 492 | * @async 493 | * @function getChatById 494 | * @param {Object} req - The request object. 495 | * @param {string} req.params.sessionId - The session ID to use for the client. 496 | * @param {string} req.body.chatId - The ID of the chat to get. 497 | * @param {Object} res - The response object. 498 | * @returns {Promise} - A promise that resolves to an object with a success property and the chat object. 499 | * @throws {Error} - Throws an error if the operation fails. 500 | */ 501 | const getChatById = async (req, res) => { 502 | /* 503 | #swagger.requestBody = { 504 | required: true, 505 | schema: { 506 | type: 'object', 507 | properties: { 508 | chatId: { 509 | type: 'string', 510 | description: 'ID of the chat', 511 | example: '' 512 | }, 513 | } 514 | }, 515 | } 516 | */ 517 | try { 518 | const { chatId } = req.body 519 | const client = sessions.get(req.params.sessionId) 520 | const chat = await client.getChatById(chatId) 521 | res.json({ success: true, chat }) 522 | } catch (error) { 523 | sendErrorResponse(res, 500, error.message) 524 | } 525 | } 526 | 527 | /** 528 | * Get the labels for the chat with the given ID. 529 | * 530 | * @async 531 | * @function getChatLabels 532 | * @param {Object} req - The request object. 533 | * @param {string} req.params.sessionId - The session ID to use for the client. 534 | * @param {string} req.body.chatId - The ID of the chat to get labels for. 535 | * @param {Object} res - The response object. 536 | * @returns {Promise} - A promise that resolves to an object with a success property and an array of labels for the chat. 537 | * @throws {Error} - Throws an error if the operation fails. 538 | */ 539 | const getChatLabels = async (req, res) => { 540 | /* 541 | #swagger.requestBody = { 542 | required: true, 543 | schema: { 544 | type: 'object', 545 | properties: { 546 | chatId: { 547 | type: 'string', 548 | description: 'ID of the chat', 549 | example: '' 550 | }, 551 | } 552 | }, 553 | } 554 | */ 555 | try { 556 | const { chatId } = req.body 557 | const client = sessions.get(req.params.sessionId) 558 | const chatLabels = await client.getChatLabels(chatId) 559 | res.json({ success: true, chatLabels }) 560 | } catch (error) { 561 | sendErrorResponse(res, 500, error.message) 562 | } 563 | } 564 | 565 | /** 566 | * Get the chats with the given label ID. 567 | * 568 | * @async 569 | * @function getChatsByLabelId 570 | * @param {Object} req - The request object. 571 | * @param {string} req.params.sessionId - The session ID to use for the client. 572 | * @param {string} req.body.labelId - The ID of the label to get chats for. 573 | * @param {Object} res - The response object. 574 | * @returns {Promise} - A promise that resolves to an object with a success property and an array of chats with the given label. 575 | * @throws {Error} - Throws an error if the operation fails. 576 | */ 577 | const getChatsByLabelId = async (req, res) => { 578 | /* 579 | #swagger.requestBody = { 580 | required: true, 581 | schema: { 582 | type: 'object', 583 | properties: { 584 | labelId: { 585 | type: 'string', 586 | description: 'ID of the label', 587 | example: '' 588 | }, 589 | } 590 | }, 591 | } 592 | */ 593 | try { 594 | const { labelId } = req.body 595 | const client = sessions.get(req.params.sessionId) 596 | const chats = await client.getChatsByLabelId(labelId) 597 | res.json({ success: true, chats }) 598 | } catch (error) { 599 | sendErrorResponse(res, 500, error.message) 600 | } 601 | } 602 | 603 | /** 604 | * Retrieves the common groups between the client's session and the specified contact. 605 | * @async 606 | * @function getCommonGroups 607 | * @param {Object} req - The request object. 608 | * @param {string} req.params.sessionId - The session ID of the client. 609 | * @param {string} req.body.contactId - The ID of the contact to retrieve the common groups with. 610 | * @param {Object} res - The response object. 611 | * @returns {Object} - An object containing a success flag and the retrieved groups. 612 | * @throws {Error} - If an error occurs while retrieving the common groups. 613 | */ 614 | const getCommonGroups = async (req, res) => { 615 | /* 616 | #swagger.requestBody = { 617 | required: true, 618 | schema: { 619 | type: 'object', 620 | properties: { 621 | contactId: { 622 | type: 'string', 623 | description: 'The whatsapp user\'s ID (_serialized format)', 624 | example: '' 625 | }, 626 | } 627 | }, 628 | } 629 | */ 630 | try { 631 | const { contactId } = req.body 632 | const client = sessions.get(req.params.sessionId) 633 | const groups = await client.getCommonGroups(contactId) 634 | res.json({ success: true, groups }) 635 | } catch (error) { 636 | sendErrorResponse(res, 500, error.message) 637 | } 638 | } 639 | 640 | /** 641 | * Retrieves the contact with the specified ID. 642 | * @async 643 | * @function getContactById 644 | * @param {Object} req - The request object. 645 | * @param {string} req.params.sessionId - The session ID of the client. 646 | * @param {string} req.body.contactId - The ID of the contact to retrieve. 647 | * @param {Object} res - The response object. 648 | * @returns {Object} - An object containing a success flag and the retrieved contact. 649 | * @throws {Error} - If an error occurs while retrieving the contact. 650 | */ 651 | const getContactById = async (req, res) => { 652 | /* 653 | #swagger.requestBody = { 654 | required: true, 655 | schema: { 656 | type: 'object', 657 | properties: { 658 | contactId: { 659 | type: 'string', 660 | description: 'The whatsapp user\'s ID', 661 | example: '' 662 | }, 663 | } 664 | }, 665 | } 666 | */ 667 | try { 668 | const { contactId } = req.body 669 | const client = sessions.get(req.params.sessionId) 670 | const contact = await client.getContactById(contactId) 671 | res.json({ success: true, contact }) 672 | } catch (error) { 673 | sendErrorResponse(res, 500, error.message) 674 | } 675 | } 676 | 677 | /** 678 | * Retrieves the invite information for the specified invite code. 679 | * @async 680 | * @function getInviteInfo 681 | * @param {Object} req - The request object. 682 | * @param {string} req.params.sessionId - The session ID of the client. 683 | * @param {string} req.body.inviteCode - The invite code to retrieve information for. 684 | * @param {Object} res - The response object. 685 | * @returns {Object} - An object containing a success flag and the retrieved invite information. 686 | * @throws {Error} - If an error occurs while retrieving the invite information. 687 | */ 688 | const getInviteInfo = async (req, res) => { 689 | /* 690 | #swagger.requestBody = { 691 | required: true, 692 | schema: { 693 | type: 'object', 694 | properties: { 695 | inviteCode: { 696 | type: 'string', 697 | description: 'Invitation code', 698 | example: '' 699 | }, 700 | } 701 | }, 702 | } 703 | */ 704 | try { 705 | const { inviteCode } = req.body 706 | const client = sessions.get(req.params.sessionId) 707 | const inviteInfo = await client.getInviteInfo(inviteCode) 708 | res.json({ success: true, inviteInfo }) 709 | } catch (error) { 710 | sendErrorResponse(res, 500, error.message) 711 | } 712 | } 713 | 714 | /** 715 | * Retrieves the label with the given ID for a particular session. 716 | * @async 717 | * @function 718 | * @param {Object} req - The request object. 719 | * @param {string} req.params.sessionId - The ID of the session to retrieve the label for. 720 | * @param {Object} req.body - The request body object. 721 | * @param {string} req.body.labelId - The ID of the label to retrieve. 722 | * @param {Object} res - The response object. 723 | * @returns {Promise} 724 | * @throws {Error} If there is an error retrieving the label. 725 | */ 726 | const getLabelById = async (req, res) => { 727 | /* 728 | #swagger.requestBody = { 729 | required: true, 730 | schema: { 731 | type: 'object', 732 | properties: { 733 | labelId: { 734 | type: 'string', 735 | description: 'ID of the label', 736 | example: '' 737 | }, 738 | } 739 | }, 740 | } 741 | */ 742 | try { 743 | const { labelId } = req.body 744 | const client = sessions.get(req.params.sessionId) 745 | const label = await client.getLabelById(labelId) 746 | res.json({ success: true, label }) 747 | } catch (error) { 748 | sendErrorResponse(res, 500, error.message) 749 | } 750 | } 751 | 752 | /** 753 | * Retrieves all labels for a particular session. 754 | * @async 755 | * @function 756 | * @param {Object} req - The request object. 757 | * @param {string} req.params.sessionId - The ID of the session to retrieve the labels for. 758 | * @param {Object} res - The response object. 759 | * @returns {Promise} 760 | * @throws {Error} If there is an error retrieving the labels. 761 | */ 762 | const getLabels = async (req, res) => { 763 | try { 764 | const client = sessions.get(req.params.sessionId) 765 | const labels = await client.getLabels() 766 | res.json({ success: true, labels }) 767 | } catch (error) { 768 | sendErrorResponse(res, 500, error.message) 769 | } 770 | } 771 | 772 | /** 773 | * Retrieves the state for a particular session. 774 | * @async 775 | * @function 776 | * @param {Object} req - The request object. 777 | * @param {string} req.params.sessionId - The ID of the session to retrieve the state for. 778 | * @param {Object} res - The response object. 779 | * @returns {Promise} 780 | * @throws {Error} If there is an error retrieving the state. 781 | */ 782 | const getState = async (req, res) => { 783 | try { 784 | const client = sessions.get(req.params.sessionId) 785 | const state = await client.getState() 786 | res.json({ success: true, state }) 787 | } catch (error) { 788 | sendErrorResponse(res, 500, error.message) 789 | } 790 | } 791 | 792 | /** 793 | * Marks a chat as unread. 794 | * 795 | * @async 796 | * @function markChatUnread 797 | * @param {Object} req - The request object. 798 | * @param {Object} res - The response object. 799 | * @param {string} req.params.sessionId - The session ID. 800 | * @param {string} req.body.chatId - The ID of the chat to mark as unread. 801 | * @returns {Promise} - A Promise that resolves when the chat is marked as unread. 802 | * @throws {Error} - If an error occurs while marking the chat as unread. 803 | */ 804 | const markChatUnread = async (req, res) => { 805 | /* 806 | #swagger.requestBody = { 807 | required: true, 808 | schema: { 809 | type: 'object', 810 | properties: { 811 | chatId: { 812 | type: 'string', 813 | description: 'ID of the chat', 814 | example: '' 815 | }, 816 | } 817 | }, 818 | } 819 | */ 820 | try { 821 | const { chatId } = req.body 822 | const client = sessions.get(req.params.sessionId) 823 | const mark = await client.markChatUnread(chatId) 824 | res.json({ success: true, mark }) 825 | } catch (error) { 826 | sendErrorResponse(res, 500, error.message) 827 | } 828 | } 829 | 830 | /** 831 | * Mutes a chat. 832 | * 833 | * @async 834 | * @function muteChat 835 | * @param {Object} req - The request object. 836 | * @param {Object} res - The response object. 837 | * @param {string} req.params.sessionId - The session ID. 838 | * @param {string} req.body.chatId - The ID of the chat to mute. 839 | * @param {Date} [req.body.unmuteDate] - The date and time when the chat should be unmuted. If not provided, the chat will be muted indefinitely. 840 | * @returns {Promise} - A Promise that resolves when the chat is muted. 841 | * @throws {Error} - If an error occurs while muting the chat. 842 | */ 843 | const muteChat = async (req, res) => { 844 | /* 845 | #swagger.requestBody = { 846 | required: true, 847 | schema: { 848 | type: 'object', 849 | properties: { 850 | chatId: { 851 | type: 'string', 852 | description: 'ID of the chat', 853 | example: '' 854 | }, 855 | unmuteDate: { 856 | type: 'string', 857 | description: 'Date when the chat will be muted, leave as is to mute forever', 858 | example: '' 859 | }, 860 | } 861 | }, 862 | } 863 | */ 864 | try { 865 | const { chatId, unmuteDate } = req.body 866 | const client = sessions.get(req.params.sessionId) 867 | let mute 868 | if (unmuteDate) { 869 | mute = await client.muteChat(chatId, new Date(unmuteDate)) 870 | } else { 871 | mute = await client.muteChat(chatId, null) 872 | } 873 | res.json({ success: true, mute }) 874 | } catch (error) { 875 | sendErrorResponse(res, 500, error.message) 876 | } 877 | } 878 | 879 | /** 880 | * Pins a chat. 881 | * 882 | * @async 883 | * @function pinChat 884 | * @param {Object} req - The request object. 885 | * @param {Object} res - The response object. 886 | * @param {string} req.params.sessionId - The session ID. 887 | * @param {string} req.body.chatId - The ID of the chat to pin. 888 | * @returns {Promise} - A Promise that resolves when the chat is pinned. 889 | * @throws {Error} - If an error occurs while pinning the chat. 890 | */ 891 | const pinChat = async (req, res) => { 892 | /* 893 | #swagger.requestBody = { 894 | required: true, 895 | schema: { 896 | type: 'object', 897 | properties: { 898 | chatId: { 899 | type: 'string', 900 | description: 'ID of the chat', 901 | example: '' 902 | }, 903 | } 904 | }, 905 | } 906 | */ 907 | try { 908 | const { chatId } = req.body 909 | const client = sessions.get(req.params.sessionId) 910 | const result = await client.pinChat(chatId) 911 | res.json({ success: true, result }) 912 | } catch (error) { 913 | sendErrorResponse(res, 500, error.message) 914 | } 915 | } 916 | /** 917 | * Search messages with the given query and options. 918 | * @async 919 | * @function searchMessages 920 | * @param {Object} req - The request object. 921 | * @param {Object} res - The response object. 922 | * @param {string} req.params.sessionId - The session ID. 923 | * @param {Object} req.body - The request body. 924 | * @param {string} req.body.query - The search query. 925 | * @param {Object} [req.body.options] - The search options (optional). 926 | * @returns {Promise} - A Promise that resolves with the search results. 927 | * @throws {Error} - If there's an error during the search. 928 | */ 929 | const searchMessages = async (req, res) => { 930 | /* 931 | #swagger.requestBody = { 932 | required: true, 933 | schema: { 934 | type: 'object', 935 | properties: { 936 | query: { 937 | type: 'string', 938 | description: 'Search string', 939 | example: '' 940 | }, 941 | options: { 942 | type: 'object', 943 | description: 'Search options', 944 | example: {} 945 | }, 946 | } 947 | }, 948 | } 949 | */ 950 | try { 951 | const { query, options } = req.body 952 | const client = sessions.get(req.params.sessionId) 953 | let messages 954 | if (options) { 955 | messages = await client.searchMessages(query, options) 956 | } else { 957 | messages = await client.searchMessages(query) 958 | } 959 | res.json({ success: true, messages }) 960 | } catch (error) { 961 | sendErrorResponse(res, 500, error.message) 962 | } 963 | } 964 | 965 | /** 966 | * Send presence available to the XMPP server. 967 | * @async 968 | * @function sendPresenceAvailable 969 | * @param {Object} req - The request object. 970 | * @param {Object} res - The response object. 971 | * @param {string} req.params.sessionId - The session ID. 972 | * @returns {Promise} - A Promise that resolves with the presence status. 973 | * @throws {Error} - If there's an error during the presence sending. 974 | */ 975 | const sendPresenceAvailable = async (req, res) => { 976 | try { 977 | const client = sessions.get(req.params.sessionId) 978 | const presence = await client.sendPresenceAvailable() 979 | res.json({ success: true, presence }) 980 | } catch (error) { 981 | sendErrorResponse(res, 500, error.message) 982 | } 983 | } 984 | 985 | /** 986 | * Send presence unavailable to the XMPP server. 987 | * @async 988 | * @function sendPresenceUnavailable 989 | * @param {Object} req - The request object. 990 | * @param {Object} res - The response object. 991 | * @param {string} req.params.sessionId - The session ID. 992 | * @returns {Promise} - A Promise that resolves with the presence status. 993 | * @throws {Error} - If there's an error during the presence sending. 994 | */ 995 | const sendPresenceUnavailable = async (req, res) => { 996 | try { 997 | const client = sessions.get(req.params.sessionId) 998 | const presence = await client.sendPresenceUnavailable() 999 | res.json({ success: true, presence }) 1000 | } catch (error) { 1001 | sendErrorResponse(res, 500, error.message) 1002 | } 1003 | } 1004 | 1005 | /** 1006 | * Send a 'seen' message status for a given chat ID. 1007 | * @async 1008 | * @function 1009 | * @param {Object} req - The request object. 1010 | * @param {Object} res - The response object. 1011 | * @param {string} req.body.chatId - The ID of the chat to set the seen status for. 1012 | * @param {string} req.params.sessionId - The ID of the session for the user. 1013 | * @returns {Object} Returns a JSON object with a success status and the result of the function. 1014 | * @throws {Error} If there is an issue sending the seen status message, an error will be thrown. 1015 | */ 1016 | const sendSeen = async (req, res) => { 1017 | /* 1018 | #swagger.requestBody = { 1019 | required: true, 1020 | schema: { 1021 | type: 'object', 1022 | properties: { 1023 | chatId: { 1024 | type: 'string', 1025 | description: 'ID of the chat', 1026 | example: '' 1027 | }, 1028 | } 1029 | }, 1030 | } 1031 | */ 1032 | try { 1033 | const { chatId } = req.body 1034 | const client = sessions.get(req.params.sessionId) 1035 | const result = await client.sendSeen(chatId) 1036 | res.json({ success: true, result }) 1037 | } catch (error) { 1038 | sendErrorResponse(res, 500, error.message) 1039 | } 1040 | } 1041 | 1042 | /** 1043 | * Set the display name for the user's WhatsApp account. 1044 | * @async 1045 | * @function 1046 | * @param {Object} req - The request object. 1047 | * @param {Object} res - The response object. 1048 | * @param {string} req.body.displayName - The new display name to set for the user's WhatsApp account. 1049 | * @param {string} req.params.sessionId - The ID of the session for the user. 1050 | * @returns {Object} Returns a JSON object with a success status and the result of the function. 1051 | * @throws {Error} If there is an issue setting the display name, an error will be thrown. 1052 | */ 1053 | const setDisplayName = async (req, res) => { 1054 | /* 1055 | #swagger.requestBody = { 1056 | required: true, 1057 | schema: { 1058 | type: 'object', 1059 | properties: { 1060 | displayName: { 1061 | type: 'string', 1062 | description: 'New display name', 1063 | example: '' 1064 | }, 1065 | } 1066 | }, 1067 | } 1068 | */ 1069 | try { 1070 | const { displayName } = req.body 1071 | const client = sessions.get(req.params.sessionId) 1072 | const result = await client.setDisplayName(displayName) 1073 | res.json({ success: true, result }) 1074 | } catch (error) { 1075 | sendErrorResponse(res, 500, error.message) 1076 | } 1077 | } 1078 | 1079 | /** 1080 | * Unarchive a chat for the user's WhatsApp account. 1081 | * @async 1082 | * @function 1083 | * @param {Object} req - The request object. 1084 | * @param {Object} res - The response object. 1085 | * @param {string} req.body.chatId - The ID of the chat to unarchive. 1086 | * @param {string} req.params.sessionId - The ID of the session for the user. 1087 | * @returns {Object} Returns a JSON object with a success status and the result of the function. 1088 | * @throws {Error} If there is an issue unarchiving the chat, an error will be thrown. 1089 | */ 1090 | const unarchiveChat = async (req, res) => { 1091 | /* 1092 | #swagger.requestBody = { 1093 | required: true, 1094 | schema: { 1095 | type: 'object', 1096 | properties: { 1097 | chatId: { 1098 | type: 'string', 1099 | description: 'ID of the chat', 1100 | example: '' 1101 | }, 1102 | } 1103 | }, 1104 | } 1105 | */ 1106 | try { 1107 | const { chatId } = req.body 1108 | const client = sessions.get(req.params.sessionId) 1109 | const result = await client.unarchiveChat(chatId) 1110 | res.json({ success: true, result }) 1111 | } catch (error) { 1112 | sendErrorResponse(res, 500, error.message) 1113 | } 1114 | } 1115 | 1116 | /** 1117 | * Unmutes the chat identified by chatId using the client associated with the given sessionId. 1118 | * 1119 | * @async 1120 | * @function 1121 | * @param {Object} req - The HTTP request object containing the chatId and sessionId. 1122 | * @param {string} req.body.chatId - The unique identifier of the chat to unmute. 1123 | * @param {string} req.params.sessionId - The unique identifier of the session associated with the client to use. 1124 | * @param {Object} res - The HTTP response object. 1125 | * @returns {Promise} - A Promise that resolves with a JSON object containing a success flag and the result of the operation. 1126 | * @throws {Error} - If an error occurs during the operation, it is thrown and handled by the catch block. 1127 | */ 1128 | const unmuteChat = async (req, res) => { 1129 | /* 1130 | #swagger.requestBody = { 1131 | required: true, 1132 | schema: { 1133 | type: 'object', 1134 | properties: { 1135 | chatId: { 1136 | type: 'string', 1137 | description: 'ID of the chat', 1138 | example: '' 1139 | }, 1140 | } 1141 | }, 1142 | } 1143 | */ 1144 | try { 1145 | const { chatId } = req.body 1146 | const client = sessions.get(req.params.sessionId) 1147 | const result = await client.unmuteChat(chatId) 1148 | res.json({ success: true, result }) 1149 | } catch (error) { 1150 | sendErrorResponse(res, 500, error.message) 1151 | } 1152 | } 1153 | 1154 | /** 1155 | * Unpins the chat identified by chatId using the client associated with the given sessionId. 1156 | * 1157 | * @async 1158 | * @function 1159 | * @param {Object} req - The HTTP request object containing the chatId and sessionId. 1160 | * @param {string} req.body.chatId - The unique identifier of the chat to unpin. 1161 | * @param {string} req.params.sessionId - The unique identifier of the session associated with the client to use. 1162 | * @param {Object} res - The HTTP response object. 1163 | * @returns {Promise} - A Promise that resolves with a JSON object containing a success flag and the result of the operation. 1164 | * @throws {Error} - If an error occurs during the operation, it is thrown and handled by the catch block. 1165 | */ 1166 | const unpinChat = async (req, res) => { 1167 | /* 1168 | #swagger.requestBody = { 1169 | required: true, 1170 | schema: { 1171 | type: 'object', 1172 | properties: { 1173 | chatId: { 1174 | type: 'string', 1175 | description: 'ID of the chat', 1176 | example: '' 1177 | }, 1178 | } 1179 | }, 1180 | } 1181 | */ 1182 | try { 1183 | const { chatId } = req.body 1184 | const client = sessions.get(req.params.sessionId) 1185 | const result = await client.unpinChat(chatId) 1186 | res.json({ success: true, result }) 1187 | } catch (error) { 1188 | sendErrorResponse(res, 500, error.message) 1189 | } 1190 | } 1191 | 1192 | /** 1193 | * update the profile Picture of the session user 1194 | * @param {Object} req - The request object. 1195 | * @param {Object} res - The response object. 1196 | * @param {Object} req.body.media - The new profile picture to set for the user's WhatsApp account. 1197 | * @param {string} req.params.sessionId - The ID of the session for the user. 1198 | * @returns {Object} Returns a JSON object with a success status and the result of the function. 1199 | * @throws {Error} If there is an issue setting the profile picture, an error will be thrown. 1200 | */ 1201 | 1202 | const setProfilePicture = async (req, res) => { 1203 | /* 1204 | #swagger.requestBody = { 1205 | required: true, 1206 | schema: { 1207 | type: "object", 1208 | properties: { 1209 | pictureMimetype: { 1210 | type: "string", 1211 | description: "The mimetype of the picture to set as the profile picture for the user WhatsApp account.", 1212 | example: "image/png" 1213 | }, 1214 | pictureData: { 1215 | type: "string", 1216 | description: "The base64 data of the picture to set as the profile picture for the user WhatsApp account.", 1217 | example: "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=" 1218 | } 1219 | } 1220 | } 1221 | } 1222 | */ 1223 | 1224 | try { 1225 | const { pictureMimetype, pictureData } = req.body 1226 | const client = sessions.get(req.params.sessionId) 1227 | const media = new MessageMedia(pictureMimetype, pictureData) 1228 | const result = await client.setProfilePicture(media) 1229 | res.json({ success: true, result }) 1230 | } catch (error) { 1231 | sendErrorResponse(res, 500, error.message) 1232 | } 1233 | } 1234 | 1235 | module.exports = { 1236 | getClassInfo, 1237 | acceptInvite, 1238 | archiveChat, 1239 | createGroup, 1240 | getBlockedContacts, 1241 | getChatById, 1242 | getChatLabels, 1243 | getChats, 1244 | getChatsByLabelId, 1245 | getCommonGroups, 1246 | getContactById, 1247 | getContacts, 1248 | getInviteInfo, 1249 | getLabelById, 1250 | getLabels, 1251 | isRegisteredUser, 1252 | getNumberId, 1253 | getProfilePictureUrl, 1254 | getState, 1255 | markChatUnread, 1256 | muteChat, 1257 | pinChat, 1258 | searchMessages, 1259 | sendMessage, 1260 | sendPresenceAvailable, 1261 | sendPresenceUnavailable, 1262 | sendSeen, 1263 | setDisplayName, 1264 | setProfilePicture, 1265 | setStatus, 1266 | unarchiveChat, 1267 | unmuteChat, 1268 | unpinChat, 1269 | getWWebVersion 1270 | } 1271 | -------------------------------------------------------------------------------- /src/controllers/contactController.js: -------------------------------------------------------------------------------- 1 | const { sessions } = require('../sessions') 2 | const { sendErrorResponse } = require('../utils') 3 | 4 | /** 5 | * Retrieves information about a WhatsApp contact by ID. 6 | * 7 | * @async 8 | * @function 9 | * @param {Object} req - The request object. 10 | * @param {Object} res - The response object. 11 | * @param {string} req.params.sessionId - The ID of the current session. 12 | * @param {string} req.body.contactId - The ID of the contact to retrieve information for. 13 | * @throws {Error} If there is an error retrieving the contact information. 14 | * @returns {Object} The contact information object. 15 | */ 16 | const getClassInfo = async (req, res) => { 17 | try { 18 | const { contactId } = req.body 19 | const client = sessions.get(req.params.sessionId) 20 | const contact = await client.getContactById(contactId) 21 | if (!contact) { 22 | sendErrorResponse(res, 404, 'Contact not Found') 23 | } 24 | res.json({ success: true, result: contact }) 25 | } catch (error) { 26 | sendErrorResponse(res, 500, error.message) 27 | } 28 | } 29 | 30 | /** 31 | * Blocks a WhatsApp contact by ID. 32 | * 33 | * @async 34 | * @function 35 | * @param {Object} req - The request object. 36 | * @param {Object} res - The response object. 37 | * @param {string} req.params.sessionId - The ID of the current session. 38 | * @param {string} req.body.contactId - The ID of the contact to block. 39 | * @throws {Error} If there is an error blocking the contact. 40 | * @returns {Object} The result of the blocking operation. 41 | */ 42 | const block = async (req, res) => { 43 | try { 44 | const { contactId } = req.body 45 | const client = sessions.get(req.params.sessionId) 46 | const contact = await client.getContactById(contactId) 47 | if (!contact) { 48 | sendErrorResponse(res, 404, 'Contact not Found') 49 | } 50 | const result = await contact.block() 51 | res.json({ success: true, result }) 52 | } catch (error) { 53 | sendErrorResponse(res, 500, error.message) 54 | } 55 | } 56 | 57 | /** 58 | * Retrieves the 'About' information of a WhatsApp contact by ID. 59 | * 60 | * @async 61 | * @function 62 | * @param {Object} req - The request object. 63 | * @param {Object} res - The response object. 64 | * @param {string} req.params.sessionId - The ID of the current session. 65 | * @param {string} req.body.contactId - The ID of the contact to retrieve 'About' information for. 66 | * @throws {Error} If there is an error retrieving the contact information. 67 | * @returns {Object} The 'About' information of the contact. 68 | */ 69 | const getAbout = async (req, res) => { 70 | try { 71 | const { contactId } = req.body 72 | const client = sessions.get(req.params.sessionId) 73 | const contact = await client.getContactById(contactId) 74 | if (!contact) { 75 | sendErrorResponse(res, 404, 'Contact not Found') 76 | } 77 | const result = await contact.getAbout() 78 | res.json({ success: true, result }) 79 | } catch (error) { 80 | sendErrorResponse(res, 500, error.message) 81 | } 82 | } 83 | 84 | /** 85 | * Retrieves the chat information of a contact with a given contactId. 86 | * 87 | * @async 88 | * @function getChat 89 | * @param {Object} req - The request object. 90 | * @param {Object} res - The response object. 91 | * @param {string} req.params.sessionId - The session ID. 92 | * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. 93 | * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. 94 | * @returns {Promise} A promise that resolves with the chat information of the contact. 95 | */ 96 | const getChat = async (req, res) => { 97 | try { 98 | const { contactId } = req.body 99 | const client = sessions.get(req.params.sessionId) 100 | const contact = await client.getContactById(contactId) 101 | if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } 102 | const result = await contact.getChat() 103 | res.json({ success: true, result }) 104 | } catch (error) { 105 | sendErrorResponse(res, 500, error.message) 106 | } 107 | } 108 | 109 | /** 110 | * Retrieves the formatted number of a contact with a given contactId. 111 | * 112 | * @async 113 | * @function getFormattedNumber 114 | * @param {Object} req - The request object. 115 | * @param {Object} res - The response object. 116 | * @param {string} req.params.sessionId - The session ID. 117 | * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. 118 | * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. 119 | * @returns {Promise} A promise that resolves with the formatted number of the contact. 120 | */ 121 | const getFormattedNumber = async (req, res) => { 122 | try { 123 | const { contactId } = req.body 124 | const client = sessions.get(req.params.sessionId) 125 | const contact = await client.getContactById(contactId) 126 | if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } 127 | const result = await contact.getFormattedNumber() 128 | res.json({ success: true, result }) 129 | } catch (error) { 130 | sendErrorResponse(res, 500, error.message) 131 | } 132 | } 133 | 134 | /** 135 | * Retrieves the country code of a contact with a given contactId. 136 | * 137 | * @async 138 | * @function getCountryCode 139 | * @param {Object} req - The request object. 140 | * @param {Object} res - The response object. 141 | * @param {string} req.params.sessionId - The session ID. 142 | * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. 143 | * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. 144 | * @returns {Promise} A promise that resolves with the country code of the contact. 145 | */ 146 | const getCountryCode = async (req, res) => { 147 | try { 148 | const { contactId } = req.body 149 | const client = sessions.get(req.params.sessionId) 150 | const contact = await client.getContactById(contactId) 151 | if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } 152 | const result = await contact.getCountryCode() 153 | res.json({ success: true, result }) 154 | } catch (error) { 155 | sendErrorResponse(res, 500, error.message) 156 | } 157 | } 158 | 159 | /** 160 | * Retrieves the profile picture url of a contact with a given contactId. 161 | * 162 | * @async 163 | * @function getProfilePicUrl 164 | * @param {Object} req - The request object. 165 | * @param {Object} res - The response object. 166 | * @param {string} req.params.sessionId - The session ID. 167 | * @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved. 168 | * @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information. 169 | * @returns {Promise} A promise that resolves with the profile picture url of the contact. 170 | */ 171 | const getProfilePicUrl = async (req, res) => { 172 | try { 173 | const { contactId } = req.body 174 | const client = sessions.get(req.params.sessionId) 175 | const contact = await client.getContactById(contactId) 176 | if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } 177 | const result = await contact.getProfilePicUrl() || null 178 | res.json({ success: true, result }) 179 | } catch (error) { 180 | sendErrorResponse(res, 500, error.message) 181 | } 182 | } 183 | 184 | /** 185 | * Unblocks the contact with a given contactId. 186 | * 187 | * @async 188 | * @function unblock 189 | * @param {Object} req - The request object. 190 | * @param {Object} res - The response object. 191 | * @param {string} req.params.sessionId - The session ID. 192 | * @param {string} req.body.contactId - The ID of the client whose contact is being unblocked. 193 | * @throws {Error} If the contact with the given contactId is not found or if there is an error unblocking the contact. 194 | * @returns {Promise} A promise that resolves with the result of unblocking the contact. 195 | */ 196 | const unblock = async (req, res) => { 197 | try { 198 | const { contactId } = req.body 199 | const client = sessions.get(req.params.sessionId) 200 | const contact = await client.getContactById(contactId) 201 | if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') } 202 | const result = await contact.unblock() 203 | res.json({ success: true, result }) 204 | } catch (error) { 205 | sendErrorResponse(res, 500, error.message) 206 | } 207 | } 208 | 209 | module.exports = { 210 | getClassInfo, 211 | block, 212 | getAbout, 213 | getChat, 214 | unblock, 215 | getFormattedNumber, 216 | getCountryCode, 217 | getProfilePicUrl 218 | } 219 | -------------------------------------------------------------------------------- /src/controllers/groupChatController.js: -------------------------------------------------------------------------------- 1 | const { sessions } = require('../sessions') 2 | const { sendErrorResponse } = require('../utils') 3 | 4 | /** 5 | * Adds participants to a group chat. 6 | * @async 7 | * @function 8 | * @param {Object} req - The request object containing the chatId and contactIds in the body. 9 | * @param {string} req.body.chatId - The ID of the group chat. 10 | * @param {Array} req.body.contactIds - An array of contact IDs to be added to the group. 11 | * @param {Object} res - The response object. 12 | * @returns {Object} Returns a JSON object containing a success flag and the updated participants list. 13 | * @throws {Error} Throws an error if the chat is not a group chat. 14 | */ 15 | const addParticipants = async (req, res) => { 16 | try { 17 | const { chatId, contactIds } = req.body 18 | const client = sessions.get(req.params.sessionId) 19 | const chat = await client.getChatById(chatId) 20 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 21 | await chat.addParticipants(contactIds) 22 | res.json({ success: true, participants: chat.participants }) 23 | } catch (error) { 24 | sendErrorResponse(res, 500, error.message) 25 | } 26 | } 27 | 28 | /** 29 | * Removes participants from a group chat 30 | * 31 | * @async 32 | * @function 33 | * @param {Object} req - Express request object 34 | * @param {Object} res - Express response object 35 | * @returns {Promise} Returns a JSON object with success flag and updated participants list 36 | * @throws {Error} If chat is not a group 37 | */ 38 | const removeParticipants = async (req, res) => { 39 | try { 40 | const { chatId, contactIds } = req.body 41 | const client = sessions.get(req.params.sessionId) 42 | const chat = await client.getChatById(chatId) 43 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 44 | await chat.removeParticipants(contactIds) 45 | res.json({ success: true, participants: chat.participants }) 46 | } catch (error) { 47 | sendErrorResponse(res, 500, error.message) 48 | } 49 | } 50 | 51 | /** 52 | * Promotes participants in a group chat to admin 53 | * 54 | * @async 55 | * @function 56 | * @param {Object} req - Express request object 57 | * @param {Object} res - Express response object 58 | * @returns {Promise} Returns a JSON object with success flag and updated participants list 59 | * @throws {Error} If chat is not a group 60 | */ 61 | const promoteParticipants = async (req, res) => { 62 | try { 63 | const { chatId, contactIds } = req.body 64 | const client = sessions.get(req.params.sessionId) 65 | const chat = await client.getChatById(chatId) 66 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 67 | await chat.promoteParticipants(contactIds) 68 | res.json({ success: true, participants: chat.participants }) 69 | } catch (error) { 70 | sendErrorResponse(res, 500, error.message) 71 | } 72 | } 73 | 74 | /** 75 | * Demotes admin participants in a group chat 76 | * 77 | * @async 78 | * @function 79 | * @param {Object} req - Express request object 80 | * @param {Object} res - Express response object 81 | * @returns {Promise} Returns a JSON object with success flag and updated participants list 82 | * @throws {Error} If chat is not a group 83 | */ 84 | const demoteParticipants = async (req, res) => { 85 | try { 86 | const { chatId, contactIds } = req.body 87 | const client = sessions.get(req.params.sessionId) 88 | const chat = await client.getChatById(chatId) 89 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 90 | await chat.demoteParticipants(contactIds) 91 | res.json({ success: true, participants: chat.participants }) 92 | } catch (error) { 93 | sendErrorResponse(res, 500, error.message) 94 | } 95 | } 96 | 97 | /** 98 | * Gets the invite code for a group chat 99 | * 100 | * @async 101 | * @function 102 | * @param {Object} req - Express request object 103 | * @param {Object} res - Express response object 104 | * @returns {Promise} Returns a JSON object with success flag and invite code 105 | * @throws {Error} If chat is not a group 106 | */ 107 | const getInviteCode = async (req, res) => { 108 | try { 109 | const { chatId } = req.body 110 | const client = sessions.get(req.params.sessionId) 111 | const chat = await client.getChatById(chatId) 112 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 113 | const inviteCode = await chat.getInviteCode() 114 | res.json({ success: true, inviteCode }) 115 | } catch (error) { 116 | sendErrorResponse(res, 500, error.message) 117 | } 118 | } 119 | 120 | /** 121 | * Sets the subject of a group chat 122 | * 123 | * @async 124 | * @function 125 | * @param {Object} req - Express request object 126 | * @param {Object} res - Express response object 127 | * @returns {Promise} Returns a JSON object with success flag and updated chat object 128 | * @throws {Error} If chat is not a group 129 | */ 130 | const setSubject = async (req, res) => { 131 | try { 132 | const { chatId, subject } = req.body 133 | const client = sessions.get(req.params.sessionId) 134 | const chat = await client.getChatById(chatId) 135 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 136 | const success = await chat.setSubject(subject) 137 | res.json({ success, chat }) 138 | } catch (error) { 139 | sendErrorResponse(res, 500, error.message) 140 | } 141 | } 142 | 143 | /** 144 | * Sets the description of a group chat 145 | * 146 | * @async 147 | * @function 148 | * @param {Object} req - Express request object 149 | * @param {Object} res - Express response object 150 | * @returns {Promise} Returns a JSON object with success flag and updated chat object 151 | * @throws {Error} If chat is not a group 152 | */ 153 | const setDescription = async (req, res) => { 154 | try { 155 | const { chatId, description } = req.body 156 | const client = sessions.get(req.params.sessionId) 157 | const chat = await client.getChatById(chatId) 158 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 159 | const success = await chat.setDescription(description) 160 | res.json({ success, chat }) 161 | } catch (error) { 162 | sendErrorResponse(res, 500, error.message) 163 | } 164 | } 165 | 166 | /** 167 | * Leaves a group chat 168 | * 169 | * @async 170 | * @function 171 | * @param {Object} req - Express request object 172 | * @param {Object} res - Express response object 173 | * @returns {Promise} Returns a JSON object with success flag and outcome of leaving the chat 174 | * @throws {Error} If chat is not a group 175 | */ 176 | const leave = async (req, res) => { 177 | try { 178 | const { chatId } = req.body 179 | const client = sessions.get(req.params.sessionId) 180 | const chat = await client.getChatById(chatId) 181 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 182 | const outcome = await chat.leave() 183 | res.json({ success: true, outcome }) 184 | } catch (error) { 185 | sendErrorResponse(res, 500, error.message) 186 | } 187 | } 188 | 189 | /** 190 | * Retrieves information about a chat based on the provided chatId 191 | * 192 | * @async 193 | * @function getClassInfo 194 | * @param {object} req - The request object 195 | * @param {object} res - The response object 196 | * @param {string} req.body.chatId - The chatId of the chat to retrieve information about 197 | * @param {string} req.params.sessionId - The sessionId of the client making the request 198 | * @throws {Error} The chat is not a group. 199 | * @returns {Promise} - A JSON response with success true and chat object containing chat information 200 | */ 201 | const getClassInfo = async (req, res) => { 202 | try { 203 | const { chatId } = req.body 204 | const client = sessions.get(req.params.sessionId) 205 | const chat = await client.getChatById(chatId) 206 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 207 | res.json({ success: true, chat }) 208 | } catch (error) { 209 | sendErrorResponse(res, 500, error.message) 210 | } 211 | } 212 | 213 | /** 214 | * Revokes the invite link for a group chat based on the provided chatId 215 | * 216 | * @async 217 | * @function revokeInvite 218 | * @param {object} req - The request object 219 | * @param {object} res - The response object 220 | * @param {string} req.body.chatId - The chatId of the group chat to revoke the invite for 221 | * @param {string} req.params.sessionId - The sessionId of the client making the request 222 | * @throws {Error} The chat is not a group. 223 | * @returns {Promise} - A JSON response with success true and the new invite code for the group chat 224 | */ 225 | const revokeInvite = async (req, res) => { 226 | try { 227 | const { chatId } = req.body 228 | const client = sessions.get(req.params.sessionId) 229 | const chat = await client.getChatById(chatId) 230 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 231 | const newInviteCode = await chat.revokeInvite() 232 | res.json({ success: true, newInviteCode }) 233 | } catch (error) { 234 | sendErrorResponse(res, 500, error.message) 235 | } 236 | } 237 | 238 | /** 239 | * Sets admins-only status of a group chat's info or messages. 240 | * 241 | * @async 242 | * @function setInfoAdminsOnly 243 | * @param {Object} req - Request object. 244 | * @param {Object} res - Response object. 245 | * @param {string} req.params.sessionId - ID of the user's session. 246 | * @param {Object} req.body - Request body. 247 | * @param {string} req.body.chatId - ID of the group chat. 248 | * @param {boolean} req.body.adminsOnly - Desired admins-only status. 249 | * @returns {Promise} Promise representing the success or failure of the operation. 250 | * @throws {Error} If the chat is not a group. 251 | */ 252 | const setInfoAdminsOnly = async (req, res) => { 253 | try { 254 | const { chatId, adminsOnly } = req.body 255 | const client = sessions.get(req.params.sessionId) 256 | const chat = await client.getChatById(chatId) 257 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 258 | const result = await chat.setInfoAdminsOnly(adminsOnly) 259 | res.json({ success: true, result }) 260 | } catch (error) { 261 | sendErrorResponse(res, 500, error.message) 262 | } 263 | } 264 | 265 | /** 266 | * Sets admins-only status of a group chat's messages. 267 | * 268 | * @async 269 | * @function setMessagesAdminsOnly 270 | * @param {Object} req - Request object. 271 | * @param {Object} res - Response object. 272 | * @param {string} req.params.sessionId - ID of the user's session. 273 | * @param {Object} req.body - Request body. 274 | * @param {string} req.body.chatId - ID of the group chat. 275 | * @param {boolean} req.body.adminsOnly - Desired admins-only status. 276 | * @returns {Promise} Promise representing the success or failure of the operation. 277 | * @throws {Error} If the chat is not a group. 278 | */ 279 | const setMessagesAdminsOnly = async (req, res) => { 280 | try { 281 | const { chatId, adminsOnly } = req.body 282 | const client = sessions.get(req.params.sessionId) 283 | const chat = await client.getChatById(chatId) 284 | if (!chat.isGroup) { throw new Error('The chat is not a group') } 285 | const result = await chat.setMessagesAdminsOnly(adminsOnly) 286 | res.json({ success: true, result }) 287 | } catch (error) { 288 | sendErrorResponse(res, 500, error.message) 289 | } 290 | } 291 | 292 | module.exports = { 293 | getClassInfo, 294 | addParticipants, 295 | demoteParticipants, 296 | getInviteCode, 297 | leave, 298 | promoteParticipants, 299 | removeParticipants, 300 | revokeInvite, 301 | setDescription, 302 | setInfoAdminsOnly, 303 | setMessagesAdminsOnly, 304 | setSubject 305 | } 306 | -------------------------------------------------------------------------------- /src/controllers/healthController.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const qrcode = require('qrcode-terminal') 3 | const { sessionFolderPath } = require('../config') 4 | const { sendErrorResponse } = require('../utils') 5 | 6 | /** 7 | * Responds to ping request with 'pong' 8 | * 9 | * @function ping 10 | * @async 11 | * @param {Object} req - Express request object 12 | * @param {Object} res - Express response object 13 | * @returns {Promise} - Promise that resolves once response is sent 14 | * @throws {Object} - Throws error if response fails 15 | */ 16 | const ping = async (req, res) => { 17 | /* 18 | #swagger.tags = ['Various'] 19 | */ 20 | try { 21 | res.json({ success: true, message: 'pong' }) 22 | } catch (error) { 23 | sendErrorResponse(res, 500, error.message) 24 | } 25 | } 26 | 27 | /** 28 | * Example local callback function that generates a QR code and writes a log file 29 | * 30 | * @function localCallbackExample 31 | * @async 32 | * @param {Object} req - Express request object containing a body object with dataType and data 33 | * @param {string} req.body.dataType - Type of data (in this case, 'qr') 34 | * @param {Object} req.body.data - Data to generate a QR code from 35 | * @param {Object} res - Express response object 36 | * @returns {Promise} - Promise that resolves once response is sent 37 | * @throws {Object} - Throws error if response fails 38 | */ 39 | const localCallbackExample = async (req, res) => { 40 | /* 41 | #swagger.tags = ['Various'] 42 | */ 43 | try { 44 | const { dataType, data } = req.body 45 | if (dataType === 'qr') { qrcode.generate(data.qr, { small: true }) } 46 | fs.writeFile(`${sessionFolderPath}/message_log.txt`, `${JSON.stringify(req.body)}\r\n`, { flag: 'a+' }, _ => _) 47 | res.json({ success: true }) 48 | } catch (error) { 49 | console.log(error) 50 | fs.writeFile(`${sessionFolderPath}/message_log.txt`, `(ERROR) ${JSON.stringify(error)}\r\n`, { flag: 'a+' }, _ => _) 51 | sendErrorResponse(res, 500, error.message) 52 | } 53 | } 54 | 55 | module.exports = { ping, localCallbackExample } 56 | -------------------------------------------------------------------------------- /src/controllers/messageController.js: -------------------------------------------------------------------------------- 1 | const { sessions } = require('../sessions') 2 | const { sendErrorResponse } = require('../utils') 3 | 4 | /** 5 | * Get message by its ID from a given chat using the provided client. 6 | * @async 7 | * @function 8 | * @param {object} client - The chat client. 9 | * @param {string} messageId - The ID of the message to get. 10 | * @param {string} chatId - The ID of the chat to search in. 11 | * @returns {Promise} - A Promise that resolves with the message object that matches the provided ID, or undefined if no such message exists. 12 | * @throws {Error} - Throws an error if the provided client, message ID or chat ID is invalid. 13 | */ 14 | const _getMessageById = async (client, messageId, chatId) => { 15 | const chat = await client.getChatById(chatId) 16 | const messages = await chat.fetchMessages({ limit: 100 }) 17 | const message = messages.find((message) => { return message.id.id === messageId }) 18 | return message 19 | } 20 | 21 | /** 22 | * Gets information about a message's class. 23 | * @async 24 | * @function 25 | * @param {Object} req - The request object. 26 | * @param {Object} res - The response object. 27 | * @param {string} req.params.sessionId - The session ID. 28 | * @param {string} req.body.messageId - The message ID. 29 | * @param {string} req.body.chatId - The chat ID. 30 | * @returns {Promise} - A Promise that resolves with no value when the function completes. 31 | */ 32 | const getClassInfo = async (req, res) => { 33 | try { 34 | const { messageId, chatId } = req.body 35 | const client = sessions.get(req.params.sessionId) 36 | const message = await _getMessageById(client, messageId, chatId) 37 | if (!message) { throw new Error('Message not Found') } 38 | res.json({ success: true, message }) 39 | } catch (error) { 40 | sendErrorResponse(res, 500, error.message) 41 | } 42 | } 43 | 44 | /** 45 | * Deletes a message. 46 | * @async 47 | * @function 48 | * @param {Object} req - The request object. 49 | * @param {Object} res - The response object. 50 | * @param {string} req.params.sessionId - The session ID. 51 | * @param {string} req.body.messageId - The message ID. 52 | * @param {string} req.body.chatId - The chat ID. 53 | * @param {boolean} req.body.everyone - Whether to delete the message for everyone or just the sender. 54 | * @returns {Promise} - A Promise that resolves with no value when the function completes. 55 | */ 56 | const deleteMessage = async (req, res) => { 57 | try { 58 | const { messageId, chatId, everyone } = req.body 59 | const client = sessions.get(req.params.sessionId) 60 | const message = await _getMessageById(client, messageId, chatId) 61 | if (!message) { throw new Error('Message not Found') } 62 | const result = await message.delete(everyone) 63 | res.json({ success: true, result }) 64 | } catch (error) { 65 | sendErrorResponse(res, 500, error.message) 66 | } 67 | } 68 | 69 | /** 70 | * Downloads media from a message. 71 | * @async 72 | * @function 73 | * @param {Object} req - The request object. 74 | * @param {Object} res - The response object. 75 | * @param {string} req.params.sessionId - The session ID. 76 | * @param {string} req.body.messageId - The message ID. 77 | * @param {string} req.body.chatId - The chat ID. 78 | * @param {boolean} req.body.everyone - Whether to download the media for everyone or just the sender. 79 | * @returns {Promise} - A Promise that resolves with no value when the function completes. 80 | */ 81 | const downloadMedia = async (req, res) => { 82 | try { 83 | const { messageId, chatId, everyone } = req.body 84 | const client = sessions.get(req.params.sessionId) 85 | const message = await _getMessageById(client, messageId, chatId) 86 | if (!message) { throw new Error('Message not Found') } 87 | const messageMedia = await message.downloadMedia(everyone) 88 | res.json({ success: true, messageMedia }) 89 | } catch (error) { 90 | sendErrorResponse(res, 500, error.message) 91 | } 92 | } 93 | 94 | /** 95 | * Forwards a message to a destination chat. 96 | * @async 97 | * @function forward 98 | * @param {Object} req - The request object received by the server. 99 | * @param {Object} req.body - The body of the request object. 100 | * @param {string} req.body.messageId - The ID of the message to forward. 101 | * @param {string} req.body.chatId - The ID of the chat that contains the message to forward. 102 | * @param {string} req.body.destinationChatId - The ID of the chat to forward the message to. 103 | * @param {string} req.params.sessionId - The ID of the session to use the Telegram API with. 104 | * @param {Object} res - The response object to be sent back to the client. 105 | * @returns {Object} - The response object with a JSON body containing the result of the forward operation. 106 | * @throws Will throw an error if the message is not found or if there is an error during the forward operation. 107 | */ 108 | const forward = async (req, res) => { 109 | try { 110 | const { messageId, chatId, destinationChatId } = req.body 111 | const client = sessions.get(req.params.sessionId) 112 | const message = await _getMessageById(client, messageId, chatId) 113 | if (!message) { throw new Error('Message not Found') } 114 | const result = await message.forward(destinationChatId) 115 | res.json({ success: true, result }) 116 | } catch (error) { 117 | sendErrorResponse(res, 500, error.message) 118 | } 119 | } 120 | 121 | /** 122 | * Gets information about a message. 123 | * @async 124 | * @function getInfo 125 | * @param {Object} req - The request object received by the server. 126 | * @param {Object} req.body - The body of the request object. 127 | * @param {string} req.body.messageId - The ID of the message to get information about. 128 | * @param {string} req.body.chatId - The ID of the chat that contains the message to get information about. 129 | * @param {string} req.params.sessionId - The ID of the session to use the Telegram API with. 130 | * @param {Object} res - The response object to be sent back to the client. 131 | * @returns {Object} - The response object with a JSON body containing the information about the message. 132 | * @throws Will throw an error if the message is not found or if there is an error during the get info operation. 133 | */ 134 | const getInfo = async (req, res) => { 135 | try { 136 | const { messageId, chatId } = req.body 137 | const client = sessions.get(req.params.sessionId) 138 | const message = await _getMessageById(client, messageId, chatId) 139 | if (!message) { throw new Error('Message not Found') } 140 | const info = await message.getInfo() 141 | res.json({ success: true, info }) 142 | } catch (error) { 143 | sendErrorResponse(res, 500, error.message) 144 | } 145 | } 146 | 147 | /** 148 | * Retrieves a list of contacts mentioned in a specific message 149 | * 150 | * @async 151 | * @function 152 | * @param {Object} req - The HTTP request object 153 | * @param {Object} req.body - The request body 154 | * @param {string} req.body.messageId - The ID of the message to retrieve mentions from 155 | * @param {string} req.body.chatId - The ID of the chat where the message was sent 156 | * @param {string} req.params.sessionId - The ID of the session for the client making the request 157 | * @param {Object} res - The HTTP response object 158 | * @returns {Promise} - The JSON response with the list of contacts 159 | * @throws {Error} - If there's an error retrieving the message or mentions 160 | */ 161 | const getMentions = async (req, res) => { 162 | try { 163 | const { messageId, chatId } = req.body 164 | const client = sessions.get(req.params.sessionId) 165 | const message = await _getMessageById(client, messageId, chatId) 166 | if (!message) { throw new Error('Message not Found') } 167 | const contacts = await message.getMentions() 168 | res.json({ success: true, contacts }) 169 | } catch (error) { 170 | sendErrorResponse(res, 500, error.message) 171 | } 172 | } 173 | 174 | /** 175 | * Retrieves the order information contained in a specific message 176 | * 177 | * @async 178 | * @function 179 | * @param {Object} req - The HTTP request object 180 | * @param {Object} req.body - The request body 181 | * @param {string} req.body.messageId - The ID of the message to retrieve the order from 182 | * @param {string} req.body.chatId - The ID of the chat where the message was sent 183 | * @param {string} req.params.sessionId - The ID of the session for the client making the request 184 | * @param {Object} res - The HTTP response object 185 | * @returns {Promise} - The JSON response with the order information 186 | * @throws {Error} - If there's an error retrieving the message or order information 187 | */ 188 | const getOrder = async (req, res) => { 189 | try { 190 | const { messageId, chatId } = req.body 191 | const client = sessions.get(req.params.sessionId) 192 | const message = await _getMessageById(client, messageId, chatId) 193 | if (!message) { throw new Error('Message not Found') } 194 | const order = await message.getOrder() 195 | res.json({ success: true, order }) 196 | } catch (error) { 197 | sendErrorResponse(res, 500, error.message) 198 | } 199 | } 200 | 201 | /** 202 | * Retrieves the payment information from a specific message identified by its ID. 203 | * 204 | * @async 205 | * @function getPayment 206 | * @param {Object} req - The HTTP request object. 207 | * @param {Object} res - The HTTP response object. 208 | * @param {string} req.params.sessionId - The session ID associated with the client making the request. 209 | * @param {Object} req.body - The message ID and chat ID associated with the message to retrieve payment information from. 210 | * @param {string} req.body.messageId - The ID of the message to retrieve payment information from. 211 | * @param {string} req.body.chatId - The ID of the chat the message is associated with. 212 | * @returns {Object} An object containing a success status and the payment information for the specified message. 213 | * @throws {Object} If the specified message is not found or if an error occurs during the retrieval process. 214 | */ 215 | const getPayment = async (req, res) => { 216 | try { 217 | const { messageId, chatId } = req.body 218 | const client = sessions.get(req.params.sessionId) 219 | const message = await _getMessageById(client, messageId, chatId) 220 | if (!message) { throw new Error('Message not Found') } 221 | const payment = await message.getPayment() 222 | res.json({ success: true, payment }) 223 | } catch (error) { 224 | sendErrorResponse(res, 500, error.message) 225 | } 226 | } 227 | 228 | /** 229 | * Retrieves the quoted message information from a specific message identified by its ID. 230 | * 231 | * @async 232 | * @function getQuotedMessage 233 | * @param {Object} req - The HTTP request object. 234 | * @param {Object} res - The HTTP response object. 235 | * @param {string} req.params.sessionId - The session ID associated with the client making the request. 236 | * @param {Object} req.body - The message ID and chat ID associated with the message to retrieve quoted message information from. 237 | * @param {string} req.body.messageId - The ID of the message to retrieve quoted message information from. 238 | * @param {string} req.body.chatId - The ID of the chat the message is associated with. 239 | * @returns {Object} An object containing a success status and the quoted message information for the specified message. 240 | * @throws {Object} If the specified message is not found or if an error occurs during the retrieval process. 241 | */ 242 | const getQuotedMessage = async (req, res) => { 243 | try { 244 | const { messageId, chatId } = req.body 245 | const client = sessions.get(req.params.sessionId) 246 | const message = await _getMessageById(client, messageId, chatId) 247 | if (!message) { throw new Error('Message not Found') } 248 | const quotedMessage = await message.getQuotedMessage() 249 | res.json({ success: true, quotedMessage }) 250 | } catch (error) { 251 | sendErrorResponse(res, 500, error.message) 252 | } 253 | } 254 | 255 | /** 256 | * React to a specific message in a chat 257 | * 258 | * @async 259 | * @function react 260 | * @param {Object} req - The HTTP request object containing the request parameters and body. 261 | * @param {Object} res - The HTTP response object to send the result. 262 | * @param {string} req.params.sessionId - The ID of the session to use. 263 | * @param {string} req.body.messageId - The ID of the message to react to. 264 | * @param {string} req.body.chatId - The ID of the chat the message is in. 265 | * @param {string} req.body.reaction - The reaction to add to the message. 266 | * @returns {Object} The HTTP response containing the result of the operation. 267 | * @throws {Error} If there was an error during the operation. 268 | */ 269 | const react = async (req, res) => { 270 | try { 271 | const { messageId, chatId, reaction } = req.body 272 | const client = sessions.get(req.params.sessionId) 273 | const message = await _getMessageById(client, messageId, chatId) 274 | if (!message) { throw new Error('Message not Found') } 275 | const result = await message.react(reaction) 276 | res.json({ success: true, result }) 277 | } catch (error) { 278 | sendErrorResponse(res, 500, error.message) 279 | } 280 | } 281 | 282 | /** 283 | * Reply to a specific message in a chat 284 | * 285 | * @async 286 | * @function reply 287 | * @param {Object} req - The HTTP request object containing the request parameters and body. 288 | * @param {Object} res - The HTTP response object to send the result. 289 | * @param {string} req.params.sessionId - The ID of the session to use. 290 | * @param {string} req.body.messageId - The ID of the message to reply to. 291 | * @param {string} req.body.chatId - The ID of the chat the message is in. 292 | * @param {string} req.body.content - The content of the message to send. 293 | * @param {string} req.body.destinationChatId - The ID of the chat to send the reply to. 294 | * @param {Object} req.body.options - Additional options for sending the message. 295 | * @returns {Object} The HTTP response containing the result of the operation. 296 | * @throws {Error} If there was an error during the operation. 297 | */ 298 | const reply = async (req, res) => { 299 | try { 300 | const { messageId, chatId, content, destinationChatId, options } = req.body 301 | const client = sessions.get(req.params.sessionId) 302 | const message = await _getMessageById(client, messageId, chatId) 303 | if (!message) { throw new Error('Message not Found') } 304 | const repliedMessage = await message.reply(content, destinationChatId, options) 305 | res.json({ success: true, repliedMessage }) 306 | } catch (error) { 307 | sendErrorResponse(res, 500, error.message) 308 | } 309 | } 310 | 311 | /** 312 | * @function star 313 | * @async 314 | * @description Stars a message by message ID and chat ID. 315 | * @param {Object} req - The request object. 316 | * @param {Object} res - The response object. 317 | * @param {string} req.params.sessionId - The session ID. 318 | * @param {string} req.body.messageId - The message ID. 319 | * @param {string} req.body.chatId - The chat ID. 320 | * @returns {Promise} A Promise that resolves with the result of the message.star() call. 321 | * @throws {Error} If message is not found, it throws an error with the message "Message not Found". 322 | */ 323 | const star = async (req, res) => { 324 | try { 325 | const { messageId, chatId } = req.body 326 | const client = sessions.get(req.params.sessionId) 327 | const message = await _getMessageById(client, messageId, chatId) 328 | if (!message) { throw new Error('Message not Found') } 329 | const result = await message.star() 330 | res.json({ success: true, result }) 331 | } catch (error) { 332 | sendErrorResponse(res, 500, error.message) 333 | } 334 | } 335 | 336 | /** 337 | * @function unstar 338 | * @async 339 | * @description Unstars a message by message ID and chat ID. 340 | * @param {Object} req - The request object. 341 | * @param {Object} res - The response object. 342 | * @param {string} req.params.sessionId - The session ID. 343 | * @param {string} req.body.messageId - The message ID. 344 | * @param {string} req.body.chatId - The chat ID. 345 | * @returns {Promise} A Promise that resolves with the result of the message.unstar() call. 346 | * @throws {Error} If message is not found, it throws an error with the message "Message not Found". 347 | */ 348 | const unstar = async (req, res) => { 349 | try { 350 | const { messageId, chatId } = req.body 351 | const client = sessions.get(req.params.sessionId) 352 | const message = await _getMessageById(client, messageId, chatId) 353 | if (!message) { throw new Error('Message not Found') } 354 | const result = await message.unstar() 355 | res.json({ success: true, result }) 356 | } catch (error) { 357 | sendErrorResponse(res, 500, error.message) 358 | } 359 | } 360 | 361 | module.exports = { 362 | getClassInfo, 363 | deleteMessage, 364 | downloadMedia, 365 | forward, 366 | getInfo, 367 | getMentions, 368 | getOrder, 369 | getPayment, 370 | getQuotedMessage, 371 | react, 372 | reply, 373 | star, 374 | unstar 375 | } 376 | -------------------------------------------------------------------------------- /src/controllers/sessionController.js: -------------------------------------------------------------------------------- 1 | 2 | const qr = require('qr-image') 3 | const { setupSession, deleteSession, validateSession, flushSessions, sessions } = require('../sessions') 4 | const { sendErrorResponse, waitForNestedObject } = require('../utils') 5 | 6 | /** 7 | * Starts a session for the given session ID. 8 | * 9 | * @function 10 | * @async 11 | * @param {Object} req - The HTTP request object. 12 | * @param {Object} res - The HTTP response object. 13 | * @param {string} req.params.sessionId - The session ID to start. 14 | * @returns {Promise} 15 | * @throws {Error} If there was an error starting the session. 16 | */ 17 | const startSession = async (req, res) => { 18 | // #swagger.summary = 'Start new session' 19 | // #swagger.description = 'Starts a session for the given session ID.' 20 | try { 21 | const sessionId = req.params.sessionId 22 | const setupSessionReturn = setupSession(sessionId) 23 | if (!setupSessionReturn.success) { 24 | /* #swagger.responses[422] = { 25 | description: "Unprocessable Entity.", 26 | content: { 27 | "application/json": { 28 | schema: { "$ref": "#/definitions/ErrorResponse" } 29 | } 30 | } 31 | } 32 | */ 33 | sendErrorResponse(res, 422, setupSessionReturn.message) 34 | return 35 | } 36 | /* #swagger.responses[200] = { 37 | description: "Status of the initiated session.", 38 | content: { 39 | "application/json": { 40 | schema: { "$ref": "#/definitions/StartSessionResponse" } 41 | } 42 | } 43 | } 44 | */ 45 | // wait until the client is created 46 | waitForNestedObject(setupSessionReturn.client, 'pupPage') 47 | .then(res.json({ success: true, message: setupSessionReturn.message })) 48 | .catch((err) => { sendErrorResponse(res, 500, err.message) }) 49 | } catch (error) { 50 | /* #swagger.responses[500] = { 51 | description: "Server Failure.", 52 | content: { 53 | "application/json": { 54 | schema: { "$ref": "#/definitions/ErrorResponse" } 55 | } 56 | } 57 | } 58 | */ 59 | console.log('startSession ERROR', error) 60 | sendErrorResponse(res, 500, error.message) 61 | } 62 | } 63 | 64 | /** 65 | * Status of the session with the given session ID. 66 | * 67 | * @function 68 | * @async 69 | * @param {Object} req - The HTTP request object. 70 | * @param {Object} res - The HTTP response object. 71 | * @param {string} req.params.sessionId - The session ID to start. 72 | * @returns {Promise} 73 | * @throws {Error} If there was an error getting status of the session. 74 | */ 75 | const statusSession = async (req, res) => { 76 | // #swagger.summary = 'Get session status' 77 | // #swagger.description = 'Status of the session with the given session ID.' 78 | try { 79 | const sessionId = req.params.sessionId 80 | const sessionData = await validateSession(sessionId) 81 | /* #swagger.responses[200] = { 82 | description: "Status of the session.", 83 | content: { 84 | "application/json": { 85 | schema: { "$ref": "#/definitions/StatusSessionResponse" } 86 | } 87 | } 88 | } 89 | */ 90 | res.json(sessionData) 91 | } catch (error) { 92 | console.log('statusSession ERROR', error) 93 | /* #swagger.responses[500] = { 94 | description: "Server Failure.", 95 | content: { 96 | "application/json": { 97 | schema: { "$ref": "#/definitions/ErrorResponse" } 98 | } 99 | } 100 | } 101 | */ 102 | sendErrorResponse(res, 500, error.message) 103 | } 104 | } 105 | 106 | /** 107 | * QR code of the session with the given session ID. 108 | * 109 | * @function 110 | * @async 111 | * @param {Object} req - The HTTP request object. 112 | * @param {Object} res - The HTTP response object. 113 | * @param {string} req.params.sessionId - The session ID to start. 114 | * @returns {Promise} 115 | * @throws {Error} If there was an error getting status of the session. 116 | */ 117 | const sessionQrCode = async (req, res) => { 118 | // #swagger.summary = 'Get session QR code' 119 | // #swagger.description = 'QR code of the session with the given session ID.' 120 | try { 121 | const sessionId = req.params.sessionId 122 | const session = sessions.get(sessionId) 123 | if (!session) { 124 | return res.json({ success: false, message: 'session_not_found' }) 125 | } 126 | if (session.qr) { 127 | return res.json({ success: true, qr: session.qr }) 128 | } 129 | return res.json({ success: false, message: 'qr code not ready or already scanned' }) 130 | } catch (error) { 131 | console.log('sessionQrCode ERROR', error) 132 | /* #swagger.responses[500] = { 133 | description: "Server Failure.", 134 | content: { 135 | "application/json": { 136 | schema: { "$ref": "#/definitions/ErrorResponse" } 137 | } 138 | } 139 | } 140 | */ 141 | sendErrorResponse(res, 500, error.message) 142 | } 143 | } 144 | 145 | /** 146 | * QR code as image of the session with the given session ID. 147 | * 148 | * @function 149 | * @async 150 | * @param {Object} req - The HTTP request object. 151 | * @param {Object} res - The HTTP response object. 152 | * @param {string} req.params.sessionId - The session ID to start. 153 | * @returns {Promise} 154 | * @throws {Error} If there was an error getting status of the session. 155 | */ 156 | const sessionQrCodeImage = async (req, res) => { 157 | // #swagger.summary = 'Get session QR code as image' 158 | // #swagger.description = 'QR code as image of the session with the given session ID.' 159 | try { 160 | const sessionId = req.params.sessionId 161 | const session = sessions.get(sessionId) 162 | if (!session) { 163 | return res.json({ success: false, message: 'session_not_found' }) 164 | } 165 | if (session.qr) { 166 | const qrImage = qr.image(session.qr) 167 | /* #swagger.responses[200] = { 168 | description: "QR image.", 169 | content: { 170 | "image/png": {} 171 | } 172 | } 173 | */ 174 | res.writeHead(200, { 175 | 'Content-Type': 'image/png' 176 | }) 177 | return qrImage.pipe(res) 178 | } 179 | return res.json({ success: false, message: 'qr code not ready or already scanned' }) 180 | } catch (error) { 181 | console.log('sessionQrCodeImage ERROR', error) 182 | /* #swagger.responses[500] = { 183 | description: "Server Failure.", 184 | content: { 185 | "application/json": { 186 | schema: { "$ref": "#/definitions/ErrorResponse" } 187 | } 188 | } 189 | } 190 | */ 191 | sendErrorResponse(res, 500, error.message) 192 | } 193 | } 194 | 195 | /** 196 | * Terminates the session with the given session ID. 197 | * 198 | * @function 199 | * @async 200 | * @param {Object} req - The HTTP request object. 201 | * @param {Object} res - The HTTP response object. 202 | * @param {string} req.params.sessionId - The session ID to terminate. 203 | * @returns {Promise} 204 | * @throws {Error} If there was an error terminating the session. 205 | */ 206 | const terminateSession = async (req, res) => { 207 | // #swagger.summary = 'Terminate session' 208 | // #swagger.description = 'Terminates the session with the given session ID.' 209 | try { 210 | const sessionId = req.params.sessionId 211 | const validation = await validateSession(sessionId) 212 | if (validation.message === 'session_not_found') { 213 | return res.json(validation) 214 | } 215 | await deleteSession(sessionId, validation) 216 | /* #swagger.responses[200] = { 217 | description: "Sessions terminated.", 218 | content: { 219 | "application/json": { 220 | schema: { "$ref": "#/definitions/TerminateSessionResponse" } 221 | } 222 | } 223 | } 224 | */ 225 | res.json({ success: true, message: 'Logged out successfully' }) 226 | } catch (error) { 227 | /* #swagger.responses[500] = { 228 | description: "Server Failure.", 229 | content: { 230 | "application/json": { 231 | schema: { "$ref": "#/definitions/ErrorResponse" } 232 | } 233 | } 234 | } 235 | */ 236 | console.log('terminateSession ERROR', error) 237 | sendErrorResponse(res, 500, error.message) 238 | } 239 | } 240 | 241 | /** 242 | * Terminates all inactive sessions. 243 | * 244 | * @function 245 | * @async 246 | * @param {Object} req - The HTTP request object. 247 | * @param {Object} res - The HTTP response object. 248 | * @returns {Promise} 249 | * @throws {Error} If there was an error terminating the sessions. 250 | */ 251 | const terminateInactiveSessions = async (req, res) => { 252 | // #swagger.summary = 'Terminate inactive sessions' 253 | // #swagger.description = 'Terminates all inactive sessions.' 254 | try { 255 | await flushSessions(true) 256 | /* #swagger.responses[200] = { 257 | description: "Sessions terminated.", 258 | content: { 259 | "application/json": { 260 | schema: { "$ref": "#/definitions/TerminateSessionsResponse" } 261 | } 262 | } 263 | } 264 | */ 265 | res.json({ success: true, message: 'Flush completed successfully' }) 266 | } catch (error) { 267 | /* #swagger.responses[500] = { 268 | description: "Server Failure.", 269 | content: { 270 | "application/json": { 271 | schema: { "$ref": "#/definitions/ErrorResponse" } 272 | } 273 | } 274 | } 275 | */ 276 | console.log('terminateInactiveSessions ERROR', error) 277 | sendErrorResponse(res, 500, error.message) 278 | } 279 | } 280 | 281 | /** 282 | * Terminates all sessions. 283 | * 284 | * @function 285 | * @async 286 | * @param {Object} req - The HTTP request object. 287 | * @param {Object} res - The HTTP response object. 288 | * @returns {Promise} 289 | * @throws {Error} If there was an error terminating the sessions. 290 | */ 291 | const terminateAllSessions = async (req, res) => { 292 | // #swagger.summary = 'Terminate all sessions' 293 | // #swagger.description = 'Terminates all sessions.' 294 | try { 295 | await flushSessions(false) 296 | /* #swagger.responses[200] = { 297 | description: "Sessions terminated.", 298 | content: { 299 | "application/json": { 300 | schema: { "$ref": "#/definitions/TerminateSessionsResponse" } 301 | } 302 | } 303 | } 304 | */ 305 | res.json({ success: true, message: 'Flush completed successfully' }) 306 | } catch (error) { 307 | /* #swagger.responses[500] = { 308 | description: "Server Failure.", 309 | content: { 310 | "application/json": { 311 | schema: { "$ref": "#/definitions/ErrorResponse" } 312 | } 313 | } 314 | } 315 | */ 316 | console.log('terminateAllSessions ERROR', error) 317 | sendErrorResponse(res, 500, error.message) 318 | } 319 | } 320 | 321 | module.exports = { 322 | startSession, 323 | statusSession, 324 | sessionQrCode, 325 | sessionQrCodeImage, 326 | terminateSession, 327 | terminateInactiveSessions, 328 | terminateAllSessions 329 | } 330 | -------------------------------------------------------------------------------- /src/middleware.js: -------------------------------------------------------------------------------- 1 | const { globalApiKey, rateLimitMax, rateLimitWindowMs } = require('./config') 2 | const { sendErrorResponse } = require('./utils') 3 | const { validateSession } = require('./sessions') 4 | const rateLimiting = require('express-rate-limit') 5 | 6 | const apikey = async (req, res, next) => { 7 | /* 8 | #swagger.security = [{ 9 | "apiKeyAuth": [] 10 | }] 11 | */ 12 | /* #swagger.responses[403] = { 13 | description: "Forbidden.", 14 | content: { 15 | "application/json": { 16 | schema: { "$ref": "#/definitions/ForbiddenResponse" } 17 | } 18 | } 19 | } 20 | */ 21 | if (globalApiKey) { 22 | const apiKey = req.headers['x-api-key'] 23 | if (!apiKey || apiKey !== globalApiKey) { 24 | return sendErrorResponse(res, 403, 'Invalid API key') 25 | } 26 | } 27 | next() 28 | } 29 | 30 | const sessionNameValidation = async (req, res, next) => { 31 | /* 32 | #swagger.parameters['sessionId'] = { 33 | in: 'path', 34 | description: 'Unique identifier for the session (alphanumeric and - allowed)', 35 | required: true, 36 | type: 'string', 37 | example: 'f8377d8d-a589-4242-9ba6-9486a04ef80c' 38 | } 39 | */ 40 | if ((!/^[\w-]+$/.test(req.params.sessionId))) { 41 | /* #swagger.responses[422] = { 42 | description: "Unprocessable Entity.", 43 | content: { 44 | "application/json": { 45 | schema: { "$ref": "#/definitions/ErrorResponse" } 46 | } 47 | } 48 | } 49 | */ 50 | return sendErrorResponse(res, 422, 'Session should be alphanumerical or -') 51 | } 52 | next() 53 | } 54 | 55 | const sessionValidation = async (req, res, next) => { 56 | const validation = await validateSession(req.params.sessionId) 57 | if (validation.success !== true) { 58 | /* #swagger.responses[404] = { 59 | description: "Not Found.", 60 | content: { 61 | "application/json": { 62 | schema: { "$ref": "#/definitions/NotFoundResponse" } 63 | } 64 | } 65 | } 66 | */ 67 | return sendErrorResponse(res, 404, validation.message) 68 | } 69 | next() 70 | } 71 | 72 | const rateLimiter = rateLimiting({ 73 | max: rateLimitMax, 74 | windowMS: rateLimitWindowMs, 75 | message: "You can't make any more requests at the moment. Try again later" 76 | }) 77 | 78 | const sessionSwagger = async (req, res, next) => { 79 | /* 80 | #swagger.tags = ['Session'] 81 | */ 82 | next() 83 | } 84 | 85 | const clientSwagger = async (req, res, next) => { 86 | /* 87 | #swagger.tags = ['Client'] 88 | */ 89 | next() 90 | } 91 | 92 | const contactSwagger = async (req, res, next) => { 93 | /* 94 | #swagger.tags = ['Contact'] 95 | #swagger.requestBody = { 96 | required: true, 97 | schema: { 98 | type: 'object', 99 | properties: { 100 | contactId: { 101 | type: 'string', 102 | description: 'Unique whatsApp identifier for the contact', 103 | example: '6281288888888@c.us' 104 | } 105 | } 106 | } 107 | } 108 | */ 109 | next() 110 | } 111 | 112 | const messageSwagger = async (req, res, next) => { 113 | /* 114 | #swagger.tags = ['Message'] 115 | #swagger.requestBody = { 116 | required: true, 117 | schema: { 118 | type: 'object', 119 | properties: { 120 | chatId: { 121 | type: 'string', 122 | description: 'The Chat id which contains the message', 123 | example: '6281288888888@c.us' 124 | }, 125 | messageId: { 126 | type: 'string', 127 | description: 'Unique whatsApp identifier for the message', 128 | example: 'ABCDEF999999999' 129 | } 130 | } 131 | } 132 | } 133 | */ 134 | next() 135 | } 136 | 137 | const chatSwagger = async (req, res, next) => { 138 | /* 139 | #swagger.tags = ['Chat'] 140 | #swagger.requestBody = { 141 | required: true, 142 | schema: { 143 | type: 'object', 144 | properties: { 145 | chatId: { 146 | type: 'string', 147 | description: 'Unique whatsApp identifier for the given Chat (either group or personnal)', 148 | example: '6281288888888@c.us' 149 | } 150 | } 151 | } 152 | } 153 | */ 154 | next() 155 | } 156 | 157 | const groupChatSwagger = async (req, res, next) => { 158 | /* 159 | #swagger.tags = ['Group Chat'] 160 | #swagger.requestBody = { 161 | required: true, 162 | schema: { 163 | type: 'object', 164 | properties: { 165 | chatId: { 166 | type: 'string', 167 | description: 'Unique whatsApp identifier for the given Chat (either group or personnal)', 168 | example: '6281288888888@c.us' 169 | } 170 | } 171 | } 172 | } 173 | */ 174 | next() 175 | } 176 | 177 | module.exports = { 178 | sessionValidation, 179 | apikey, 180 | sessionNameValidation, 181 | sessionSwagger, 182 | clientSwagger, 183 | contactSwagger, 184 | messageSwagger, 185 | chatSwagger, 186 | groupChatSwagger, 187 | rateLimiter 188 | } 189 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const routes = express.Router() 3 | const swaggerUi = require('swagger-ui-express') 4 | const swaggerDocument = require('../swagger.json') 5 | const { enableLocalCallbackExample, enableSwaggerEndpoint } = require('./config') 6 | 7 | const middleware = require('./middleware') 8 | const healthController = require('./controllers/healthController') 9 | const sessionController = require('./controllers/sessionController') 10 | const clientController = require('./controllers/clientController') 11 | const chatController = require('./controllers/chatController') 12 | const groupChatController = require('./controllers/groupChatController') 13 | const messageController = require('./controllers/messageController') 14 | const contactController = require('./controllers/contactController') 15 | 16 | /** 17 | * ================ 18 | * HEALTH ENDPOINTS 19 | * ================ 20 | */ 21 | 22 | // API endpoint to check if server is alive 23 | routes.get('/ping', healthController.ping) 24 | // API basic callback 25 | if (enableLocalCallbackExample) { 26 | routes.post('/localCallbackExample', [middleware.apikey, middleware.rateLimiter], healthController.localCallbackExample) 27 | } 28 | 29 | /** 30 | * ================ 31 | * SESSION ENDPOINTS 32 | * ================ 33 | */ 34 | const sessionRouter = express.Router() 35 | sessionRouter.use(middleware.apikey) 36 | sessionRouter.use(middleware.sessionSwagger) 37 | routes.use('/session', sessionRouter) 38 | 39 | sessionRouter.get('/start/:sessionId', middleware.sessionNameValidation, sessionController.startSession) 40 | sessionRouter.get('/status/:sessionId', middleware.sessionNameValidation, sessionController.statusSession) 41 | sessionRouter.get('/qr/:sessionId', middleware.sessionNameValidation, sessionController.sessionQrCode) 42 | sessionRouter.get('/qr/:sessionId/image', middleware.sessionNameValidation, sessionController.sessionQrCodeImage) 43 | sessionRouter.get('/terminate/:sessionId', middleware.sessionNameValidation, sessionController.terminateSession) 44 | sessionRouter.get('/terminateInactive', sessionController.terminateInactiveSessions) 45 | sessionRouter.get('/terminateAll', sessionController.terminateAllSessions) 46 | 47 | /** 48 | * ================ 49 | * CLIENT ENDPOINTS 50 | * ================ 51 | */ 52 | 53 | const clientRouter = express.Router() 54 | clientRouter.use(middleware.apikey) 55 | sessionRouter.use(middleware.clientSwagger) 56 | routes.use('/client', clientRouter) 57 | 58 | clientRouter.get('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getClassInfo) 59 | clientRouter.post('/acceptInvite/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.acceptInvite) 60 | clientRouter.post('/archiveChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.archiveChat) 61 | clientRouter.post('/createGroup/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.createGroup) 62 | clientRouter.post('/getBlockedContacts/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getBlockedContacts) 63 | clientRouter.post('/getChatById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatById) 64 | clientRouter.post('/getChatLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatLabels) 65 | clientRouter.get('/getChats/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChats) 66 | clientRouter.post('/getChatsByLabelId/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatsByLabelId) 67 | clientRouter.post('/getCommonGroups/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getCommonGroups) 68 | clientRouter.post('/getContactById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getContactById) 69 | clientRouter.get('/getContacts/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getContacts) 70 | clientRouter.post('/getInviteInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getInviteInfo) 71 | clientRouter.post('/getLabelById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getLabelById) 72 | clientRouter.post('/getLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getLabels) 73 | clientRouter.post('/getNumberId/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getNumberId) 74 | clientRouter.post('/isRegisteredUser/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.isRegisteredUser) 75 | clientRouter.post('/getProfilePicUrl/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getProfilePictureUrl) 76 | clientRouter.get('/getState/:sessionId', [middleware.sessionNameValidation], clientController.getState) 77 | clientRouter.post('/markChatUnread/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.markChatUnread) 78 | clientRouter.post('/muteChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.muteChat) 79 | clientRouter.post('/pinChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.pinChat) 80 | clientRouter.post('/searchMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.searchMessages) 81 | clientRouter.post('/sendMessage/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendMessage) 82 | clientRouter.post('/sendPresenceAvailable/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendPresenceAvailable) 83 | clientRouter.post('/sendPresenceUnavailable/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendPresenceUnavailable) 84 | clientRouter.post('/sendSeen/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendSeen) 85 | clientRouter.post('/setDisplayName/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setDisplayName) 86 | clientRouter.post('/setProfilePicture/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setProfilePicture) 87 | clientRouter.post('/setStatus/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setStatus) 88 | clientRouter.post('/unarchiveChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unarchiveChat) 89 | clientRouter.post('/unmuteChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unmuteChat) 90 | clientRouter.post('/unpinChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unpinChat) 91 | clientRouter.get('/getWWebVersion/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getWWebVersion) 92 | 93 | /** 94 | * ================ 95 | * CHAT ENDPOINTS 96 | * ================ 97 | */ 98 | const chatRouter = express.Router() 99 | chatRouter.use(middleware.apikey) 100 | sessionRouter.use(middleware.chatSwagger) 101 | routes.use('/chat', chatRouter) 102 | 103 | chatRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.getClassInfo) 104 | chatRouter.post('/clearMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.clearMessages) 105 | chatRouter.post('/clearState/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.clearState) 106 | chatRouter.post('/delete/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.deleteChat) 107 | chatRouter.post('/fetchMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.fetchMessages) 108 | chatRouter.post('/getContact/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.getContact) 109 | chatRouter.post('/sendStateRecording/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.sendStateRecording) 110 | chatRouter.post('/sendStateTyping/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.sendStateTyping) 111 | 112 | /** 113 | * ================ 114 | * GROUP CHAT ENDPOINTS 115 | * ================ 116 | */ 117 | const groupChatRouter = express.Router() 118 | groupChatRouter.use(middleware.apikey) 119 | sessionRouter.use(middleware.groupChatSwagger) 120 | routes.use('/groupChat', groupChatRouter) 121 | 122 | groupChatRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.getClassInfo) 123 | groupChatRouter.post('/addParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.addParticipants) 124 | groupChatRouter.post('/demoteParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.demoteParticipants) 125 | groupChatRouter.post('/getInviteCode/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.getInviteCode) 126 | groupChatRouter.post('/leave/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.leave) 127 | groupChatRouter.post('/promoteParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.promoteParticipants) 128 | groupChatRouter.post('/removeParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.removeParticipants) 129 | groupChatRouter.post('/revokeInvite/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.revokeInvite) 130 | groupChatRouter.post('/setDescription/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setDescription) 131 | groupChatRouter.post('/setInfoAdminsOnly/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setInfoAdminsOnly) 132 | groupChatRouter.post('/setMessagesAdminsOnly/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setMessagesAdminsOnly) 133 | groupChatRouter.post('/setSubject/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setSubject) 134 | 135 | /** 136 | * ================ 137 | * MESSAGE ENDPOINTS 138 | * ================ 139 | */ 140 | const messageRouter = express.Router() 141 | messageRouter.use(middleware.apikey) 142 | sessionRouter.use(middleware.messageSwagger) 143 | routes.use('/message', messageRouter) 144 | 145 | messageRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getClassInfo) 146 | messageRouter.post('/delete/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.deleteMessage) 147 | messageRouter.post('/downloadMedia/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.downloadMedia) 148 | messageRouter.post('/forward/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.forward) 149 | messageRouter.post('/getInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getInfo) 150 | messageRouter.post('/getMentions/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getMentions) 151 | messageRouter.post('/getOrder/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getOrder) 152 | messageRouter.post('/getPayment/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getPayment) 153 | messageRouter.post('/getQuotedMessage/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getQuotedMessage) 154 | messageRouter.post('/react/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.react) 155 | messageRouter.post('/reply/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.reply) 156 | messageRouter.post('/star/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.star) 157 | messageRouter.post('/unstar/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.unstar) 158 | 159 | /** 160 | * ================ 161 | * MESSAGE ENDPOINTS 162 | * ================ 163 | */ 164 | const contactRouter = express.Router() 165 | contactRouter.use(middleware.apikey) 166 | sessionRouter.use(middleware.contactSwagger) 167 | routes.use('/contact', contactRouter) 168 | 169 | contactRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getClassInfo) 170 | contactRouter.post('/block/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.block) 171 | contactRouter.post('/getAbout/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getAbout) 172 | contactRouter.post('/getChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getChat) 173 | contactRouter.post('/unblock/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.unblock) 174 | contactRouter.post('/getFormattedNumber/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getFormattedNumber) 175 | contactRouter.post('/getCountryCode/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getCountryCode) 176 | contactRouter.post('/getProfilePicUrl/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getProfilePicUrl) 177 | /** 178 | * ================ 179 | * SWAGGER ENDPOINTS 180 | * ================ 181 | */ 182 | if (enableSwaggerEndpoint) { 183 | routes.use('/api-docs', swaggerUi.serve) 184 | routes.get('/api-docs', swaggerUi.setup(swaggerDocument) /* #swagger.ignore = true */) 185 | } 186 | 187 | module.exports = { routes } 188 | -------------------------------------------------------------------------------- /src/sessions.js: -------------------------------------------------------------------------------- 1 | const { Client, LocalAuth } = require('whatsapp-web.js') 2 | const fs = require('fs') 3 | const sessions = new Map() 4 | const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions } = require('./config') 5 | const { triggerWebhook, waitForNestedObject, checkIfEventisEnabled } = require('./utils') 6 | 7 | // Function to validate if the session is ready 8 | const validateSession = async (sessionId) => { 9 | try { 10 | const returnData = { success: false, state: null, message: '' } 11 | 12 | // Session not Connected 😢 13 | if (!sessions.has(sessionId) || !sessions.get(sessionId)) { 14 | returnData.message = 'session_not_found' 15 | return returnData 16 | } 17 | 18 | const client = sessions.get(sessionId) 19 | // wait until the client is created 20 | await waitForNestedObject(client, 'pupPage') 21 | .catch((err) => { return { success: false, state: null, message: err.message } }) 22 | 23 | // Wait for client.pupPage to be evaluable 24 | while (true) { 25 | try { 26 | if (client.pupPage.isClosed()) { 27 | return { success: false, state: null, message: 'browser tab closed' } 28 | } 29 | await client.pupPage.evaluate('1'); break 30 | } catch (error) { 31 | // Ignore error and wait for a bit before trying again 32 | await new Promise(resolve => setTimeout(resolve, 100)) 33 | } 34 | } 35 | 36 | const state = await client.getState() 37 | returnData.state = state 38 | if (state !== 'CONNECTED') { 39 | returnData.message = 'session_not_connected' 40 | return returnData 41 | } 42 | 43 | // Session Connected 🎉 44 | returnData.success = true 45 | returnData.message = 'session_connected' 46 | return returnData 47 | } catch (error) { 48 | console.log(error) 49 | return { success: false, state: null, message: error.message } 50 | } 51 | } 52 | 53 | // Function to handle client session restoration 54 | const restoreSessions = () => { 55 | try { 56 | if (!fs.existsSync(sessionFolderPath)) { 57 | fs.mkdirSync(sessionFolderPath) // Create the session directory if it doesn't exist 58 | } 59 | // Read the contents of the folder 60 | fs.readdir(sessionFolderPath, (_, files) => { 61 | // Iterate through the files in the parent folder 62 | for (const file of files) { 63 | // Use regular expression to extract the string from the folder name 64 | const match = file.match(/^session-(.+)$/) 65 | if (match) { 66 | const sessionId = match[1] 67 | console.log('existing session detected', sessionId) 68 | setupSession(sessionId) 69 | } 70 | } 71 | }) 72 | } catch (error) { 73 | console.log(error) 74 | console.error('Failed to restore sessions:', error) 75 | } 76 | } 77 | 78 | // Setup Session 79 | const setupSession = (sessionId) => { 80 | try { 81 | if (sessions.has(sessionId)) { 82 | return { success: false, message: `Session already exists for: ${sessionId}`, client: sessions.get(sessionId) } 83 | } 84 | 85 | // Disable the delete folder from the logout function (will be handled separately) 86 | const localAuth = new LocalAuth({ clientId: sessionId, dataPath: sessionFolderPath }) 87 | delete localAuth.logout 88 | localAuth.logout = () => { } 89 | 90 | const clientOptions = { 91 | puppeteer: { 92 | executablePath: process.env.CHROME_BIN || null, 93 | // headless: false, 94 | args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--disable-dev-shm-usage'] 95 | }, 96 | userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36', 97 | authStrategy: localAuth 98 | } 99 | 100 | if (webVersion) { 101 | clientOptions.webVersion = webVersion 102 | switch (webVersionCacheType.toLowerCase()) { 103 | case 'local': 104 | clientOptions.webVersionCache = { 105 | type: 'local' 106 | } 107 | break 108 | case 'remote': 109 | clientOptions.webVersionCache = { 110 | type: 'remote', 111 | remotePath: 'https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/' + webVersion + '.html' 112 | } 113 | break 114 | default: 115 | clientOptions.webVersionCache = { 116 | type: 'none' 117 | } 118 | } 119 | } 120 | 121 | const client = new Client(clientOptions) 122 | 123 | client.initialize().catch(err => console.log('Initialize error:', err.message)) 124 | 125 | initializeEvents(client, sessionId) 126 | 127 | // Save the session to the Map 128 | sessions.set(sessionId, client) 129 | return { success: true, message: 'Session initiated successfully', client } 130 | } catch (error) { 131 | return { success: false, message: error.message, client: null } 132 | } 133 | } 134 | 135 | const initializeEvents = (client, sessionId) => { 136 | // check if the session webhook is overridden 137 | const sessionWebhook = process.env[sessionId.toUpperCase() + '_WEBHOOK_URL'] || baseWebhookURL 138 | 139 | if (recoverSessions) { 140 | waitForNestedObject(client, 'pupPage').then(() => { 141 | const restartSession = async (sessionId) => { 142 | sessions.delete(sessionId) 143 | await client.destroy().catch(e => {}) 144 | setupSession(sessionId) 145 | } 146 | client.pupPage.once('close', function () { 147 | // emitted when the page closes 148 | console.log(`Browser page closed for ${sessionId}. Restoring`) 149 | restartSession(sessionId) 150 | }) 151 | client.pupPage.once('error', function () { 152 | // emitted when the page crashes 153 | console.log(`Error occurred on browser page for ${sessionId}. Restoring`) 154 | restartSession(sessionId) 155 | }) 156 | }).catch(e => {}) 157 | } 158 | 159 | checkIfEventisEnabled('auth_failure') 160 | .then(_ => { 161 | client.on('auth_failure', (msg) => { 162 | triggerWebhook(sessionWebhook, sessionId, 'status', { msg }) 163 | }) 164 | }) 165 | 166 | checkIfEventisEnabled('authenticated') 167 | .then(_ => { 168 | client.on('authenticated', () => { 169 | triggerWebhook(sessionWebhook, sessionId, 'authenticated') 170 | }) 171 | }) 172 | 173 | checkIfEventisEnabled('call') 174 | .then(_ => { 175 | client.on('call', async (call) => { 176 | triggerWebhook(sessionWebhook, sessionId, 'call', { call }) 177 | }) 178 | }) 179 | 180 | checkIfEventisEnabled('change_state') 181 | .then(_ => { 182 | client.on('change_state', state => { 183 | triggerWebhook(sessionWebhook, sessionId, 'change_state', { state }) 184 | }) 185 | }) 186 | 187 | checkIfEventisEnabled('disconnected') 188 | .then(_ => { 189 | client.on('disconnected', (reason) => { 190 | triggerWebhook(sessionWebhook, sessionId, 'disconnected', { reason }) 191 | }) 192 | }) 193 | 194 | checkIfEventisEnabled('group_join') 195 | .then(_ => { 196 | client.on('group_join', (notification) => { 197 | triggerWebhook(sessionWebhook, sessionId, 'group_join', { notification }) 198 | }) 199 | }) 200 | 201 | checkIfEventisEnabled('group_leave') 202 | .then(_ => { 203 | client.on('group_leave', (notification) => { 204 | triggerWebhook(sessionWebhook, sessionId, 'group_leave', { notification }) 205 | }) 206 | }) 207 | 208 | checkIfEventisEnabled('group_update') 209 | .then(_ => { 210 | client.on('group_update', (notification) => { 211 | triggerWebhook(sessionWebhook, sessionId, 'group_update', { notification }) 212 | }) 213 | }) 214 | 215 | checkIfEventisEnabled('loading_screen') 216 | .then(_ => { 217 | client.on('loading_screen', (percent, message) => { 218 | triggerWebhook(sessionWebhook, sessionId, 'loading_screen', { percent, message }) 219 | }) 220 | }) 221 | 222 | checkIfEventisEnabled('media_uploaded') 223 | .then(_ => { 224 | client.on('media_uploaded', (message) => { 225 | triggerWebhook(sessionWebhook, sessionId, 'media_uploaded', { message }) 226 | }) 227 | }) 228 | 229 | checkIfEventisEnabled('message') 230 | .then(_ => { 231 | client.on('message', async (message) => { 232 | triggerWebhook(sessionWebhook, sessionId, 'message', { message }) 233 | if (message.hasMedia && message._data?.size < maxAttachmentSize) { 234 | // custom service event 235 | checkIfEventisEnabled('media').then(_ => { 236 | message.downloadMedia().then(messageMedia => { 237 | triggerWebhook(sessionWebhook, sessionId, 'media', { messageMedia, message }) 238 | }).catch(e => { 239 | console.log('Download media error:', e.message) 240 | }) 241 | }) 242 | } 243 | if (setMessagesAsSeen) { 244 | const chat = await message.getChat() 245 | chat.sendSeen() 246 | } 247 | }) 248 | }) 249 | 250 | checkIfEventisEnabled('message_ack') 251 | .then(_ => { 252 | client.on('message_ack', async (message, ack) => { 253 | triggerWebhook(sessionWebhook, sessionId, 'message_ack', { message, ack }) 254 | if (setMessagesAsSeen) { 255 | const chat = await message.getChat() 256 | chat.sendSeen() 257 | } 258 | }) 259 | }) 260 | 261 | checkIfEventisEnabled('message_create') 262 | .then(_ => { 263 | client.on('message_create', async (message) => { 264 | triggerWebhook(sessionWebhook, sessionId, 'message_create', { message }) 265 | if (setMessagesAsSeen) { 266 | const chat = await message.getChat() 267 | chat.sendSeen() 268 | } 269 | }) 270 | }) 271 | 272 | checkIfEventisEnabled('message_reaction') 273 | .then(_ => { 274 | client.on('message_reaction', (reaction) => { 275 | triggerWebhook(sessionWebhook, sessionId, 'message_reaction', { reaction }) 276 | }) 277 | }) 278 | 279 | checkIfEventisEnabled('message_revoke_everyone') 280 | .then(_ => { 281 | client.on('message_revoke_everyone', async (after, before) => { 282 | triggerWebhook(sessionWebhook, sessionId, 'message_revoke_everyone', { after, before }) 283 | }) 284 | }) 285 | 286 | client.on('qr', (qr) => { 287 | // inject qr code into session 288 | client.qr = qr 289 | checkIfEventisEnabled('qr') 290 | .then(_ => { 291 | triggerWebhook(sessionWebhook, sessionId, 'qr', { qr }) 292 | }) 293 | }) 294 | 295 | checkIfEventisEnabled('ready') 296 | .then(_ => { 297 | client.on('ready', () => { 298 | triggerWebhook(sessionWebhook, sessionId, 'ready') 299 | }) 300 | }) 301 | 302 | checkIfEventisEnabled('contact_changed') 303 | .then(_ => { 304 | client.on('contact_changed', async (message, oldId, newId, isContact) => { 305 | triggerWebhook(sessionWebhook, sessionId, 'contact_changed', { message, oldId, newId, isContact }) 306 | }) 307 | }) 308 | } 309 | 310 | // Function to check if folder is writeable 311 | const deleteSessionFolder = async (sessionId) => { 312 | try { 313 | const targetDirPath = `${sessionFolderPath}/session-${sessionId}/` 314 | const resolvedTargetDirPath = await fs.promises.realpath(targetDirPath) 315 | const resolvedSessionPath = await fs.promises.realpath(sessionFolderPath) 316 | // Check if the target directory path is a subdirectory of the sessions folder path 317 | if (!resolvedTargetDirPath.startsWith(resolvedSessionPath)) { 318 | throw new Error('Invalid path') 319 | } 320 | await fs.promises.rm(targetDirPath, { recursive: true, force: true }) 321 | } catch (error) { 322 | console.log('Folder deletion error', error) 323 | throw error 324 | } 325 | } 326 | 327 | // Function to delete client session 328 | const deleteSession = async (sessionId, validation) => { 329 | try { 330 | const client = sessions.get(sessionId) 331 | if (!client) { 332 | return 333 | } 334 | client.pupPage.removeAllListeners('close') 335 | client.pupPage.removeAllListeners('error') 336 | if (validation.success) { 337 | // Client Connected, request logout 338 | console.log(`Logging out session ${sessionId}`) 339 | await client.logout() 340 | } else if (validation.message === 'session_not_connected') { 341 | // Client not Connected, request destroy 342 | console.log(`Destroying session ${sessionId}`) 343 | await client.destroy() 344 | } 345 | 346 | // Wait for client.pupBrowser to be disconnected before deleting the folder 347 | while (client.pupBrowser.isConnected()) { 348 | await new Promise(resolve => setTimeout(resolve, 100)) 349 | } 350 | await deleteSessionFolder(sessionId) 351 | sessions.delete(sessionId) 352 | } catch (error) { 353 | console.log(error) 354 | throw error 355 | } 356 | } 357 | 358 | // Function to handle session flush 359 | const flushSessions = async (deleteOnlyInactive) => { 360 | try { 361 | // Read the contents of the sessions folder 362 | const files = await fs.promises.readdir(sessionFolderPath) 363 | // Iterate through the files in the parent folder 364 | for (const file of files) { 365 | // Use regular expression to extract the string from the folder name 366 | const match = file.match(/^session-(.+)$/) 367 | if (match && match[1]) { 368 | const sessionId = match[1] 369 | const validation = await validateSession(sessionId) 370 | if (!deleteOnlyInactive || !validation.success) { 371 | await deleteSession(sessionId, validation) 372 | } 373 | } 374 | } 375 | } catch (error) { 376 | console.log(error) 377 | throw error 378 | } 379 | } 380 | 381 | module.exports = { 382 | sessions, 383 | setupSession, 384 | restoreSessions, 385 | validateSession, 386 | deleteSession, 387 | flushSessions 388 | } 389 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const { globalApiKey, disabledCallbacks } = require('./config') 3 | 4 | // Trigger webhook endpoint 5 | const triggerWebhook = (webhookURL, sessionId, dataType, data) => { 6 | axios.post(webhookURL, { dataType, data, sessionId }, { headers: { 'x-api-key': globalApiKey } }) 7 | .catch(error => console.error('Failed to send new message webhook:', sessionId, dataType, error.message, data)) 8 | } 9 | 10 | // Function to send a response with error status and message 11 | const sendErrorResponse = (res, status, message) => { 12 | res.status(status).json({ success: false, error: message }) 13 | } 14 | 15 | // Function to wait for a specific item not to be null 16 | const waitForNestedObject = (rootObj, nestedPath, maxWaitTime = 10000, interval = 100) => { 17 | const start = Date.now() 18 | return new Promise((resolve, reject) => { 19 | const checkObject = () => { 20 | const nestedObj = nestedPath.split('.').reduce((obj, key) => obj ? obj[key] : undefined, rootObj) 21 | if (nestedObj) { 22 | // Nested object exists, resolve the promise 23 | resolve() 24 | } else if (Date.now() - start > maxWaitTime) { 25 | // Maximum wait time exceeded, reject the promise 26 | console.log('Timed out waiting for nested object') 27 | reject(new Error('Timeout waiting for nested object')) 28 | } else { 29 | // Nested object not yet created, continue waiting 30 | setTimeout(checkObject, interval) 31 | } 32 | } 33 | checkObject() 34 | }) 35 | } 36 | 37 | const checkIfEventisEnabled = (event) => { 38 | return new Promise((resolve, reject) => { if (!disabledCallbacks.includes(event)) { resolve() } }) 39 | } 40 | 41 | module.exports = { 42 | triggerWebhook, 43 | sendErrorResponse, 44 | waitForNestedObject, 45 | checkIfEventisEnabled 46 | } 47 | -------------------------------------------------------------------------------- /swagger.js: -------------------------------------------------------------------------------- 1 | const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0', autoBody: false }) 2 | 3 | const outputFile = './swagger.json' 4 | const endpointsFiles = ['./src/routes.js'] 5 | 6 | const doc = { 7 | info: { 8 | title: 'WhatsApp API', 9 | description: 'API Wrapper for WhatsAppWebJS' 10 | }, 11 | host: '', 12 | securityDefinitions: { 13 | apiKeyAuth: { 14 | type: 'apiKey', 15 | in: 'header', 16 | name: 'x-api-key' 17 | } 18 | }, 19 | produces: ['application/json'], 20 | tags: [ 21 | { 22 | name: 'Session', 23 | description: 'Handling multiple sessions logic, creation and deletion' 24 | }, 25 | { 26 | name: 'Client', 27 | description: 'All functions related to the client' 28 | }, 29 | { 30 | name: 'Message', 31 | description: 'May fail if the message is too old (Only from the last 100 Messages of the given chat)' 32 | } 33 | ], 34 | definitions: { 35 | StartSessionResponse: { 36 | success: true, 37 | message: 'Session initiated successfully' 38 | }, 39 | StatusSessionResponse: { 40 | success: true, 41 | state: 'CONNECTED', 42 | message: 'session_connected' 43 | }, 44 | TerminateSessionResponse: { 45 | success: true, 46 | message: 'Logged out successfully' 47 | }, 48 | TerminateSessionsResponse: { 49 | success: true, 50 | message: 'Flush completed successfully' 51 | }, 52 | ErrorResponse: { 53 | success: false, 54 | error: 'Some server error' 55 | }, 56 | NotFoundResponse: { 57 | success: false, 58 | error: 'Some server error' 59 | }, 60 | ForbiddenResponse: { 61 | success: false, 62 | error: 'Invalid API key' 63 | } 64 | } 65 | } 66 | 67 | swaggerAutogen(outputFile, endpointsFiles, doc) 68 | -------------------------------------------------------------------------------- /tests/api.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest') 2 | const fs = require('fs') 3 | 4 | // Mock your application's environment variables 5 | process.env.API_KEY = 'test_api_key' 6 | process.env.SESSIONS_PATH = './sessions_test' 7 | process.env.ENABLE_LOCAL_CALLBACK_EXAMPLE = 'TRUE' 8 | process.env.BASE_WEBHOOK_URL = 'http://localhost:3000/localCallbackExample' 9 | 10 | const app = require('../src/app') 11 | jest.mock('qrcode-terminal') 12 | 13 | let server 14 | beforeAll(() => { 15 | server = app.listen(3000) 16 | }) 17 | 18 | beforeEach(async () => { 19 | if (fs.existsSync('./sessions_test/message_log.txt')) { 20 | fs.writeFileSync('./sessions_test/message_log.txt', '') 21 | } 22 | }) 23 | 24 | afterAll(() => { 25 | server.close() 26 | fs.rmSync('./sessions_test', { recursive: true, force: true }) 27 | }) 28 | 29 | // Define test cases 30 | describe('API health checks', () => { 31 | it('should return valid healthcheck', async () => { 32 | const response = await request(app).get('/ping') 33 | expect(response.status).toBe(200) 34 | expect(response.body).toEqual({ message: 'pong', success: true }) 35 | }) 36 | 37 | it('should return a valid callback', async () => { 38 | const response = await request(app).post('/localCallbackExample') 39 | .set('x-api-key', 'test_api_key') 40 | .send({ sessionId: '1', dataType: 'testDataType', data: 'testData' }) 41 | expect(response.status).toBe(200) 42 | expect(response.body).toEqual({ success: true }) 43 | 44 | expect(fs.existsSync('./sessions_test/message_log.txt')).toBe(true) 45 | expect(fs.readFileSync('./sessions_test/message_log.txt', 'utf-8')).toEqual('{"sessionId":"1","dataType":"testDataType","data":"testData"}\r\n') 46 | }) 47 | }) 48 | 49 | describe('API Authentication Tests', () => { 50 | it('should return 403 Forbidden for invalid API key', async () => { 51 | const response = await request(app).get('/session/start/1') 52 | expect(response.status).toBe(403) 53 | expect(response.body).toEqual({ success: false, error: 'Invalid API key' }) 54 | }) 55 | 56 | it('should fail invalid sessionId', async () => { 57 | const response = await request(app).get('/session/start/ABCD1@').set('x-api-key', 'test_api_key') 58 | expect(response.status).toBe(422) 59 | expect(response.body).toEqual({ success: false, error: 'Session should be alphanumerical or -' }) 60 | }) 61 | 62 | it('should setup and terminate a client session', async () => { 63 | const response = await request(app).get('/session/start/1').set('x-api-key', 'test_api_key') 64 | expect(response.status).toBe(200) 65 | expect(response.body).toEqual({ success: true, message: 'Session initiated successfully' }) 66 | expect(fs.existsSync('./sessions_test/session-1')).toBe(true) 67 | 68 | const response2 = await request(app).get('/session/terminate/1').set('x-api-key', 'test_api_key') 69 | expect(response2.status).toBe(200) 70 | expect(response2.body).toEqual({ success: true, message: 'Logged out successfully' }) 71 | 72 | expect(fs.existsSync('./sessions_test/session-1')).toBe(false) 73 | }, 10000) 74 | 75 | it('should setup and flush multiple client sessions', async () => { 76 | const response = await request(app).get('/session/start/2').set('x-api-key', 'test_api_key') 77 | expect(response.status).toBe(200) 78 | expect(response.body).toEqual({ success: true, message: 'Session initiated successfully' }) 79 | expect(fs.existsSync('./sessions_test/session-2')).toBe(true) 80 | 81 | const response2 = await request(app).get('/session/start/3').set('x-api-key', 'test_api_key') 82 | expect(response2.status).toBe(200) 83 | expect(response2.body).toEqual({ success: true, message: 'Session initiated successfully' }) 84 | expect(fs.existsSync('./sessions_test/session-3')).toBe(true) 85 | 86 | const response3 = await request(app).get('/session/terminateInactive').set('x-api-key', 'test_api_key') 87 | expect(response3.status).toBe(200) 88 | expect(response3.body).toEqual({ success: true, message: 'Flush completed successfully' }) 89 | 90 | expect(fs.existsSync('./sessions_test/session-2')).toBe(false) 91 | expect(fs.existsSync('./sessions_test/session-3')).toBe(false) 92 | }, 10000) 93 | }) 94 | 95 | describe('API Action Tests', () => { 96 | it('should setup, create at least a QR, and terminate a client session', async () => { 97 | const response = await request(app).get('/session/start/4').set('x-api-key', 'test_api_key') 98 | expect(response.status).toBe(200) 99 | expect(response.body).toEqual({ success: true, message: 'Session initiated successfully' }) 100 | expect(fs.existsSync('./sessions_test/session-4')).toBe(true) 101 | 102 | // Wait for message_log.txt to not be empty 103 | const result = await waitForFileNotToBeEmpty('./sessions_test/message_log.txt') 104 | .then(() => { return true }) 105 | .catch(() => { return false }) 106 | expect(result).toBe(true) 107 | 108 | // Verify the message content 109 | const expectedMessage = { 110 | dataType: 'qr', 111 | data: expect.objectContaining({ qr: expect.any(String) }), 112 | sessionId: '4' 113 | } 114 | expect(JSON.parse(fs.readFileSync('./sessions_test/message_log.txt', 'utf-8'))).toEqual(expectedMessage) 115 | 116 | const response2 = await request(app).get('/session/terminate/4').set('x-api-key', 'test_api_key') 117 | expect(response2.status).toBe(200) 118 | expect(response2.body).toEqual({ success: true, message: 'Logged out successfully' }) 119 | expect(fs.existsSync('./sessions_test/session-4')).toBe(false) 120 | }, 15000) 121 | }) 122 | 123 | // Function to wait for a specific item to be equal a specific value 124 | const waitForFileNotToBeEmpty = (filePath, maxWaitTime = 10000, interval = 100) => { 125 | const start = Date.now() 126 | return new Promise((resolve, reject) => { 127 | const checkObject = () => { 128 | const filecontent = fs.readFileSync(filePath, 'utf-8') 129 | if (filecontent !== '') { 130 | // Nested object exists, resolve the promise 131 | resolve() 132 | } else if (Date.now() - start > maxWaitTime) { 133 | // Maximum wait time exceeded, reject the promise 134 | console.log('Timed out waiting for nested object') 135 | reject(new Error('Timeout waiting for nested object')) 136 | } else { 137 | // Nested object not yet created, continue waiting 138 | setTimeout(checkObject, interval) 139 | } 140 | } 141 | checkObject() 142 | }) 143 | } 144 | --------------------------------------------------------------------------------