project push
This commit is contained in:
parent
12ef55e756
commit
2399a31aac
31 changed files with 20580 additions and 0 deletions
30
.dockerignore
Normal file
30
.dockerignore
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Ignore node_modules
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Ignore dotenv files
|
||||||
|
.env
|
||||||
|
.env.example
|
||||||
|
|
||||||
|
# Ignore logs
|
||||||
|
logs
|
||||||
|
|
||||||
|
# Ignore test files
|
||||||
|
tests
|
||||||
|
|
||||||
|
# Ignore session files
|
||||||
|
sessions
|
||||||
|
sessions_test
|
||||||
|
|
||||||
|
# Ignore git related files
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Ignore other unnecessary files
|
||||||
|
README.md
|
||||||
|
CONTRIBUTING.md
|
||||||
|
LICENSE.md
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
swagger.yml
|
||||||
|
.github
|
||||||
|
assets
|
||||||
23
.env.example
Normal file
23
.env.example
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
## Application ##
|
||||||
|
PORT=3000 # OPTIONAL, DEFAULT 3000
|
||||||
|
API_KEY=your_global_api_key_here # OPTIONAL, DEFAULT EMPTY
|
||||||
|
BASE_WEBHOOK_URL=http://localhost:3000/localCallbackExample # MANDATORY
|
||||||
|
ENABLE_LOCAL_CALLBACK_EXAMPLE=TRUE # OPTIONAL, DISABLE FOR PRODUCTION
|
||||||
|
RATE_LIMIT_MAX=1000 # OPTIONAL, THE MAXIUM NUMBER OF CONNECTIONS TO ALLOW PER TIME FRAME
|
||||||
|
RATE_LIMIT_WINDOW_MS=1000 # OPTIONAL, TIME FRAME FOR WHICH REQUESTS ARE CHECKED IN MS
|
||||||
|
|
||||||
|
## Client ##
|
||||||
|
MAX_ATTACHMENT_SIZE=10000000 # IF REACHED, MEDIA ATTACHMENT BODY WILL BE NULL
|
||||||
|
SET_MESSAGES_AS_SEEN=TRUE # WILL MARK THE MESSAGES AS READ AUTOMATICALLY
|
||||||
|
# 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|unread_count|message_edit|message_ciphertext
|
||||||
|
DISABLED_CALLBACKS=message_ack|message_reaction|unread_count|message_edit|message_ciphertext # PREVENT SENDING CERTAIN TYPES OF CALLBACKS BACK TO THE WEBHOOK
|
||||||
|
WEB_VERSION='2.2328.5' # OPTIONAL, THE VERSION OF WHATSAPP WEB TO USE
|
||||||
|
WEB_VERSION_CACHE_TYPE=none # OPTIONAL, DETERMINTES WHERE TO GET THE WHATSAPP WEB VERSION(local, remote or none), DEFAULT 'none'
|
||||||
|
RECOVER_SESSIONS=TRUE # OPTIONAL, SHOULD WE RECOVER THE SESSION IN CASE OF PAGE FAILURES
|
||||||
|
CHROME_BIN= # OPTIONAL, PATH TO CHROME BINARY
|
||||||
|
HEADLESS=TRUE # OPTIONAL, RUN CHROME IN HEADLESS MODE
|
||||||
|
|
||||||
|
## Session File Storage ##
|
||||||
|
SESSIONS_PATH=./sessions # OPTIONAL
|
||||||
|
|
||||||
|
ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL
|
||||||
16
.eslintrc.js
Normal file
16
.eslintrc.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
jest: true
|
||||||
|
},
|
||||||
|
extends: 'standard',
|
||||||
|
overrides: [
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
}
|
||||||
|
}
|
||||||
25
.github/workflows/pull_request.yml
vendored
Normal file
25
.github/workflows/pull_request.yml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: CI/CD Pipeline for Pull Requests to Master
|
||||||
|
|
||||||
|
'on':
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version:
|
||||||
|
- 18.x
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: 'Use Node.js ${{ matrix.node-version }}'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '${{ matrix.node-version }}'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
timeout-minutes: 1
|
||||||
50
.github/workflows/push.yml
vendored
Normal file
50
.github/workflows/push.yml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
name: CI/CD Pipeline for Docker Tag Push
|
||||||
|
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version:
|
||||||
|
- 18.x
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: 'Use Node.js ${{ matrix.node-version }}'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '${{ matrix.node-version }}'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
timeout-minutes: 1
|
||||||
|
|
||||||
|
docker:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: '${{ secrets.DOCKER_HUB_USERNAME }}'
|
||||||
|
password: '${{ secrets.DOCKER_HUB_TOKEN }}'
|
||||||
|
- name: Build and push with dynamic tag
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
avoylenko/wwebjs-api:${{ github.ref_name }}
|
||||||
|
avoylenko/wwebjs-api:latest
|
||||||
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Ignore node_modules
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Ignore dotenv files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Ignore sessions
|
||||||
|
sessions
|
||||||
|
sessions_test
|
||||||
|
.wwebjs_cache
|
||||||
|
|
||||||
|
# Ignore logs
|
||||||
|
logs
|
||||||
|
|
||||||
|
# Ignore test coverage reports
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Ignore other unnecessary files
|
||||||
|
.DS_Store
|
||||||
|
.vscode
|
||||||
57
CONTRIBUTING.md
Normal file
57
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Contributing to WWebJS REST API Wrapper
|
||||||
|
|
||||||
|
Welcome to WWebJS API Wrapper! We appreciate your interest in contributing to this project. Please follow the guidelines below to contribute effectively.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Clone your forked repository to your local machine.
|
||||||
|
3. Install the necessary dependencies by running `npm install`.
|
||||||
|
4. Create a new branch for your contribution.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Follow the existing code style and conventions in the project.
|
||||||
|
- Use meaningful variable and function names.
|
||||||
|
- Add comments to your code, especially for complex or tricky parts.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
- Create a pull request from your branch to the `master` branch of this repository.
|
||||||
|
- Provide a clear and descriptive title for your pull request.
|
||||||
|
- Include a detailed description of the changes you made in the pull request.
|
||||||
|
- Reference any related issues in your pull request description using the `#` symbol followed by the issue number.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Write appropriate unit tests for your code.
|
||||||
|
- Make sure all existing tests pass.
|
||||||
|
- Provide instructions for testing your changes, if necessary.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- Update the README.md file with any relevant information about your contribution, including installation instructions, usage examples, and API documentation.
|
||||||
|
|
||||||
|
## Code Block Example
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
\```
|
||||||
|
// Example code block
|
||||||
|
const hello = "Hello, world!";
|
||||||
|
console.log(hello);
|
||||||
|
\```
|
||||||
|
|
||||||
|
This will render as:
|
||||||
|
|
||||||
|
```
|
||||||
|
// Example code block
|
||||||
|
const hello = "Hello, world!";
|
||||||
|
console.log(hello);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contact Us
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Thank you for your contribution!
|
||||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Use the official Node.js Alpine image as the base image
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Install Chromium
|
||||||
|
ENV CHROME_BIN="/usr/bin/chromium-browser" \
|
||||||
|
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD="true" \
|
||||||
|
NODE_ENV="production"
|
||||||
|
RUN set -x \
|
||||||
|
&& apk update \
|
||||||
|
&& apk upgrade \
|
||||||
|
&& apk add --no-cache \
|
||||||
|
udev \
|
||||||
|
ttf-freefont \
|
||||||
|
chromium \
|
||||||
|
ffmpeg
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json to the working directory
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install the dependencies
|
||||||
|
RUN npm ci --only=production --ignore-scripts
|
||||||
|
|
||||||
|
# Copy the rest of the source code to the working directory
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose the port the API will run on
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Start the API
|
||||||
|
CMD ["npm", "start"]
|
||||||
19
LICENSE.md
Normal file
19
LICENSE.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# MIT License
|
||||||
|
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
```
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
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.
|
||||||
|
```
|
||||||
178
README.md
Normal file
178
README.md
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
# WWebJS REST API
|
||||||
|
|
||||||
|
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.
|
||||||
|
It is designed to be used as a docker container, scalable, secure, and easy to integrate with other non-NodeJS projects.
|
||||||
|
|
||||||
|
This project is a fork of [whatsapp-api](https://github.com/chrishubert/whatsapp-api). As the project was abandoned by the original author, all future improvements will be in this repo.
|
||||||
|
|
||||||
|
The project is a work in progress: star it, create issues, features or pull requests ❣️
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
[1. Quick Start with Docker](#quick-start-with-docker)
|
||||||
|
|
||||||
|
[2. Features](#features)
|
||||||
|
|
||||||
|
[3. Run Locally](#run-locally)
|
||||||
|
|
||||||
|
[4. Testing](#testing)
|
||||||
|
|
||||||
|
[5. Documentation](#documentation)
|
||||||
|
|
||||||
|
[6. Deploy to Production](#deploy-to-production)
|
||||||
|
|
||||||
|
[7. Contributing](#contributing)
|
||||||
|
|
||||||
|
[8. License](#license)
|
||||||
|
|
||||||
|
[9. Star History](#star-history)
|
||||||
|
|
||||||
|
## Quick Start with Docker
|
||||||
|
|
||||||
|
[](https://hub.docker.com/r/avoylenko/wwebjs-api)
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/avoylenko/wwebjs-api.git
|
||||||
|
cd wwebjs-api
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run the Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose pull && docker-compose up
|
||||||
|
```
|
||||||
|
4. Visit http://localhost:3000/session/start/ABCD
|
||||||
|
|
||||||
|
5. Scan the QR on your console using WhatsApp mobile app -> Linked Device -> Link a Device (it may take time to setup the session)
|
||||||
|
|
||||||
|
6. Visit http://localhost:3000/client/getContacts/ABCD
|
||||||
|
|
||||||
|
7. EXTRA: Look at all the callbacks data in `./session/message_log.txt`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
1. API and Callbacks
|
||||||
|
|
||||||
|
| Actions | Status | Sessions | Status | Callbacks | Status |
|
||||||
|
| ----------------------------| ------| ----------------------------------------| ------| ----------------------------------------------| ------|
|
||||||
|
| Send Image Message | ✅ | Initiate session | ✅ | Callback QR code | ✅ |
|
||||||
|
| Send Video Message | ✅ | Terminate session | ✅ | Callback new message | ✅ |
|
||||||
|
| Send Audio Message | ✅ | Terminate inactive sessions | ✅ | Callback status change | ✅ |
|
||||||
|
| Send Document Message | ✅ | Terminate all sessions | ✅ | Callback message media attachment | ✅ |
|
||||||
|
| Send File URL | ✅ | Healthcheck | ✅ | | |
|
||||||
|
| Send Button Message | ✅ | Local test callback | | | |
|
||||||
|
| Send Contact Message | ✅ | | | | |
|
||||||
|
| Send List Message | ✅ | | | | |
|
||||||
|
| Set Status | ✅ | | | | |
|
||||||
|
| Send Button With Media | ✅ | | | | |
|
||||||
|
| Is On Whatsapp? | ✅ | | | | |
|
||||||
|
| Download Profile Pic | ✅ | | | | |
|
||||||
|
| User Status | ✅ | | | | |
|
||||||
|
| Block/Unblock User | ✅ | | | | |
|
||||||
|
| Update Profile Picture | ✅ | | | | |
|
||||||
|
| Create Group | ✅ | | | | |
|
||||||
|
| Leave Group | ✅ | | | | |
|
||||||
|
| All Groups | ✅ | | | | |
|
||||||
|
| Invite User | ✅ | | | | |
|
||||||
|
| Make Admin | ✅ | | | | |
|
||||||
|
| Demote Admin | ✅ | | | | |
|
||||||
|
| Group Invite Code | ✅ | | | | |
|
||||||
|
| Update Group Participants | ✅ | | | | |
|
||||||
|
| Update Group Setting | ✅ | | | | |
|
||||||
|
| Update Group Subject | ✅ | | | | |
|
||||||
|
| Update Group Description | ✅ | | | | |
|
||||||
|
|
||||||
|
3. Handle multiple client sessions (session data saved locally), identified by unique id
|
||||||
|
|
||||||
|
4. All endpoints may be secured by a global API key
|
||||||
|
|
||||||
|
5. On server start, all existing sessions are restored
|
||||||
|
|
||||||
|
6. Set messages automatically as read
|
||||||
|
|
||||||
|
7. Disable any of the callbacks
|
||||||
|
|
||||||
|
## Run Locally
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/avoylenko/wwebjs-api.git
|
||||||
|
cd wwebjs-api
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install the dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Copy the `.env.example` file to `.env` and update the required environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run the application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Access the API at `http://localhost:3000`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run the test suite with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
API documentation can be found in the [`swagger.json`](https://raw.githubusercontent.com/avoylenko/wwebjs-api/master/swagger.json) file. See this file directly into [Swagger Editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/avoylenko/wwebjs-api/master/swagger.json) or any other OpenAPI-compatible tool to view and interact with the API documentation.
|
||||||
|
|
||||||
|
This documentation is straightforward if you are familiar with whatsapp-web.js library (https://docs.wwebjs.dev/)
|
||||||
|
If you are still confused - open an issue and I'll improve it.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
By default, all callback events are delivered to the webhook defined with the `BASE_WEBHOOK_URL` environment variable.
|
||||||
|
This can be overridden by setting the `*_WEBHOOK_URL` environment variable, where `*` is your sessionId.
|
||||||
|
For example, if you have the sessionId defined as `DEMO`, the environment variable must be `DEMO_WEBHOOK_URL`.
|
||||||
|
|
||||||
|
By setting the `DISABLED_CALLBACKS` environment variable you can specify what events you are **not** willing to receive on your webhook.
|
||||||
|
|
||||||
|
### Scanning QR code
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## Deploy to Production
|
||||||
|
|
||||||
|
- Load the docker image in docker-compose, or your Kubernetes environment
|
||||||
|
- Disable the `ENABLE_LOCAL_CALLBACK_EXAMPLE` environment variable
|
||||||
|
- Set the `API_KEY` environment variable to protect the REST endpoints
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE.md) file for details.
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#avoylenko/wwebjs-api&Date)
|
||||||
BIN
assets/basic_start.gif
Normal file
BIN
assets/basic_start.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 591 KiB |
26
docker-compose.yml
Normal file
26
docker-compose.yml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
container_name: wwebjs_api
|
||||||
|
# build: .
|
||||||
|
image: avoylenko/wwebjs-api:latest # Pull the image from Docker Hub
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
# - API_KEY=your_global_api_key_here # OPTIONAL
|
||||||
|
- BASE_WEBHOOK_URL=http://localhost:3000/localCallbackExample
|
||||||
|
- ENABLE_LOCAL_CALLBACK_EXAMPLE=TRUE # OPTIONAL, NOT RECOMMENDED FOR PRODUCTION
|
||||||
|
- MAX_ATTACHMENT_SIZE=5000000 # IN BYTES
|
||||||
|
- SET_MESSAGES_AS_SEEN=TRUE # WILL MARK THE MESSAGES AS READ AUTOMATICALLY
|
||||||
|
# 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
|
||||||
|
- DISABLED_CALLBACKS=message_ack|message_reaction # PREVENT SENDING CERTAIN TYPES OF CALLBACKS BACK TO THE WEBHOOK
|
||||||
|
- ENABLE_SWAGGER_ENDPOINT=TRUE # OPTIONAL, ENABLES THE /api-docs ENDPOINT
|
||||||
|
# - RATE_LIMIT_MAX=1000 # OPTIONAL, THE MAXIUM NUMBER OF CONNECTIONS TO ALLOW PER TIME FRAME
|
||||||
|
# - RATE_LIMIT_WINDOW_MS=1000 # OPTIONAL, TIME FRAME FOR WHICH REQUESTS ARE CHECKED IN MS
|
||||||
|
# - WEB_VERSION='2.2328.5' # OPTIONAL, THE VERSION OF WHATSAPP WEB TO USE
|
||||||
|
# - WEB_VERSION_CACHE_TYPE=none # OPTIONAL, DETERMINES WHERE TO GET THE WHATSAPP WEB VERSION(local, remote or none), DEFAULT 'none'
|
||||||
|
# - RECOVER_SESSIONS=TRUE # OPTIONAL, SHOULD WE RECOVER THE SESSION IN CASE OF PAGE FAILURES
|
||||||
|
volumes:
|
||||||
|
- ./sessions:/usr/src/app/sessions # Mount the local ./sessions/ folder to the container's /usr/src/app/sessions folder
|
||||||
8282
package-lock.json
generated
Normal file
8282
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
57
package.json
Normal file
57
package.json
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"name": "wwebjs-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "REST API wrapper for whatsapp-web.js",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"test": "jest --runInBand",
|
||||||
|
"swagger": "node swagger.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.8",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.21.1",
|
||||||
|
"express-rate-limit": "^7.4.1",
|
||||||
|
"qr-image": "^3.2.0",
|
||||||
|
"qrcode-terminal": "^0.12.0",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
|
"whatsapp-web.js": "^1.26.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.38.0",
|
||||||
|
"eslint-config-standard": "^17.0.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-n": "^15.7.0",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"supertest": "^6.3.3",
|
||||||
|
"swagger-autogen": "^2.23.7"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"whatsapp",
|
||||||
|
"whatsapp-web",
|
||||||
|
"wwebjs",
|
||||||
|
"api",
|
||||||
|
"wrapper",
|
||||||
|
"rest",
|
||||||
|
"express",
|
||||||
|
"axios"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Anton Voylenko",
|
||||||
|
"email": "anton.voylenko@gmail.com"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/avoylenko/wwebjs-api.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/avoylenko/wwebjs-api/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/avoylenko/wwebjs-api"
|
||||||
|
}
|
||||||
16
server.js
Normal file
16
server.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
const app = require('./src/app')
|
||||||
|
const { baseWebhookURL } = require('./src/config')
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
const port = process.env.PORT || 3000
|
||||||
|
|
||||||
|
// Check if BASE_WEBHOOK_URL environment variable is available
|
||||||
|
if (!baseWebhookURL) {
|
||||||
|
console.error('BASE_WEBHOOK_URL environment variable is not set. Exiting...')
|
||||||
|
process.exit(1) // Terminate the application with an error code
|
||||||
|
}
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server running on port ${port}`)
|
||||||
|
})
|
||||||
17
src/app.js
Normal file
17
src/app.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
require('./routes')
|
||||||
|
const express = require('express')
|
||||||
|
const { restoreSessions } = require('./sessions')
|
||||||
|
const { routes } = require('./routes')
|
||||||
|
const { maxAttachmentSize } = require('./config')
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
// Initialize Express app
|
||||||
|
app.disable('x-powered-by')
|
||||||
|
app.use(express.json({ limit: maxAttachmentSize + 1000000 }))
|
||||||
|
app.use(express.urlencoded({ limit: maxAttachmentSize + 1000000, extended: true }))
|
||||||
|
app.use('/', routes)
|
||||||
|
|
||||||
|
restoreSessions()
|
||||||
|
|
||||||
|
module.exports = app
|
||||||
37
src/config.js
Normal file
37
src/config.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Load environment variables from .env file
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
// setup global const
|
||||||
|
const sessionFolderPath = process.env.SESSIONS_PATH || './sessions'
|
||||||
|
const enableLocalCallbackExample = (process.env.ENABLE_LOCAL_CALLBACK_EXAMPLE || '').toLowerCase() === 'true'
|
||||||
|
const globalApiKey = process.env.API_KEY
|
||||||
|
const baseWebhookURL = process.env.BASE_WEBHOOK_URL
|
||||||
|
const maxAttachmentSize = parseInt(process.env.MAX_ATTACHMENT_SIZE) || 10000000
|
||||||
|
const setMessagesAsSeen = (process.env.SET_MESSAGES_AS_SEEN || '').toLowerCase() === 'true'
|
||||||
|
const disabledCallbacks = process.env.DISABLED_CALLBACKS ? process.env.DISABLED_CALLBACKS.split('|') : []
|
||||||
|
const enableSwaggerEndpoint = (process.env.ENABLE_SWAGGER_ENDPOINT || '').toLowerCase() === 'true'
|
||||||
|
const webVersion = process.env.WEB_VERSION
|
||||||
|
const webVersionCacheType = process.env.WEB_VERSION_CACHE_TYPE || 'none'
|
||||||
|
const rateLimitMax = parseInt(process.env.RATE_LIMIT_MAX) || 1000
|
||||||
|
const rateLimitWindowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 1000
|
||||||
|
const recoverSessions = (process.env.RECOVER_SESSIONS || '').toLowerCase() === 'true'
|
||||||
|
const chromeBin = process.env.CHROME_BIN || null
|
||||||
|
const headless = process.env.HEADLESS ? (process.env.HEADLESS).toLowerCase() === 'true' : true
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sessionFolderPath,
|
||||||
|
enableLocalCallbackExample,
|
||||||
|
globalApiKey,
|
||||||
|
baseWebhookURL,
|
||||||
|
maxAttachmentSize,
|
||||||
|
setMessagesAsSeen,
|
||||||
|
disabledCallbacks,
|
||||||
|
enableSwaggerEndpoint,
|
||||||
|
webVersion,
|
||||||
|
webVersionCacheType,
|
||||||
|
rateLimitMax,
|
||||||
|
rateLimitWindowMs,
|
||||||
|
recoverSessions,
|
||||||
|
chromeBin,
|
||||||
|
headless
|
||||||
|
}
|
||||||
237
src/controllers/chatController.js
Normal file
237
src/controllers/chatController.js
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
const { sessions } = require('../sessions')
|
||||||
|
const { sendErrorResponse } = require('../utils')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @name getClassInfo
|
||||||
|
* @description Gets information about a chat using the chatId and sessionId
|
||||||
|
* @param {Object} req - Request object
|
||||||
|
* @param {Object} res - Response object
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat to get information for
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session to use
|
||||||
|
* @returns {Object} - Returns a JSON object with the success status and chat information
|
||||||
|
* @throws {Error} - Throws an error if chat is not found or if there is a server error
|
||||||
|
*/
|
||||||
|
const getClassInfo = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
res.json({ success: true, chat })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all messages in a chat.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat to clear messages from.
|
||||||
|
* @throws {Error} If the chat is not found or there is an internal server error.
|
||||||
|
* @returns {Object} The success status and the cleared messages.
|
||||||
|
*/
|
||||||
|
const clearMessages = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
const clearMessages = await chat.clearMessages()
|
||||||
|
res.json({ success: true, clearMessages })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops typing or recording in chat immediately.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - Request object.
|
||||||
|
* @param {Object} res - Response object.
|
||||||
|
* @param {string} req.body.chatId - ID of the chat to clear the state for.
|
||||||
|
* @param {string} req.params.sessionId - ID of the session the chat belongs to.
|
||||||
|
* @returns {Promise<void>} - A Promise that resolves with a JSON object containing a success flag and the result of clearing the state.
|
||||||
|
* @throws {Error} - If there was an error while clearing the state.
|
||||||
|
*/
|
||||||
|
const clearState = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
const clearState = await chat.clearState()
|
||||||
|
res.json({ success: true, clearState })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a chat.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat to be deleted.
|
||||||
|
* @returns {Object} A JSON response indicating whether the chat was deleted successfully.
|
||||||
|
* @throws {Object} If there is an error while deleting the chat, an error response is sent with a status code of 500.
|
||||||
|
* @throws {Object} If the chat is not found, an error response is sent with a status code of 404.
|
||||||
|
*/
|
||||||
|
const deleteChat = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
const deleteChat = await chat.delete()
|
||||||
|
res.json({ success: true, deleteChat })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches messages from a specified chat.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
*
|
||||||
|
* @param {Object} req - The request object containing sessionId, chatId, and searchOptions.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session associated with the chat.
|
||||||
|
* @param {Object} req.body - The body of the request containing chatId and searchOptions.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat from which to fetch messages.
|
||||||
|
* @param {Object} req.body.searchOptions - The search options to use when fetching messages.
|
||||||
|
*
|
||||||
|
* @param {Object} res - The response object to send the fetched messages.
|
||||||
|
* @returns {Promise<Object>} A JSON object containing the success status and fetched messages.
|
||||||
|
*
|
||||||
|
* @throws {Error} If the chat is not found or there is an error fetching messages.
|
||||||
|
*/
|
||||||
|
const fetchMessages = async (req, res) => {
|
||||||
|
try {
|
||||||
|
/*
|
||||||
|
#swagger.requestBody = {
|
||||||
|
required: true,
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
chatId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Unique whatsApp identifier for the given Chat (either group or personnal)',
|
||||||
|
example: '6281288888888@c.us'
|
||||||
|
},
|
||||||
|
searchOptions: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Search options for fetching messages',
|
||||||
|
example: '{}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const { chatId, searchOptions } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
const messages = await chat.fetchMessages(searchOptions)
|
||||||
|
res.json({ success: true, messages })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the contact for a chat
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The HTTP request object
|
||||||
|
* @param {Object} res - The HTTP response object
|
||||||
|
* @param {string} req.params.sessionId - The ID of the current session
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat to get the contact for
|
||||||
|
* @returns {Promise<void>} - Promise that resolves with the chat's contact information
|
||||||
|
* @throws {Error} - Throws an error if chat is not found or if there is an error getting the contact information
|
||||||
|
*/
|
||||||
|
const getContact = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
const contact = await chat.getContact()
|
||||||
|
res.json({ success: true, contact })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a recording state to a WhatsApp chat.
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {object} req - The request object.
|
||||||
|
* @param {object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {object} req.body - The request body.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat to send the recording state to.
|
||||||
|
* @returns {object} - An object containing a success message and the result of the sendStateRecording method.
|
||||||
|
* @throws {object} - An error object containing a status code and error message if an error occurs.
|
||||||
|
*/
|
||||||
|
const sendStateRecording = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
const sendStateRecording = await chat.sendStateRecording()
|
||||||
|
res.json({ success: true, sendStateRecording })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a typing state to a WhatsApp chat.
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {object} req - The request object.
|
||||||
|
* @param {object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {object} req.body - The request body.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat to send the typing state to.
|
||||||
|
* @returns {object} - An object containing a success message and the result of the sendStateTyping method.
|
||||||
|
* @throws {object} - An error object containing a status code and error message if an error occurs.
|
||||||
|
*/
|
||||||
|
const sendStateTyping = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat) { sendErrorResponse(res, 404, 'Chat not Found') }
|
||||||
|
const sendStateTyping = await chat.sendStateTyping()
|
||||||
|
res.json({ success: true, sendStateTyping })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getClassInfo,
|
||||||
|
clearMessages,
|
||||||
|
clearState,
|
||||||
|
deleteChat,
|
||||||
|
fetchMessages,
|
||||||
|
getContact,
|
||||||
|
sendStateRecording,
|
||||||
|
sendStateTyping
|
||||||
|
}
|
||||||
1314
src/controllers/clientController.js
Normal file
1314
src/controllers/clientController.js
Normal file
File diff suppressed because it is too large
Load diff
218
src/controllers/contactController.js
Normal file
218
src/controllers/contactController.js
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
const { sessions } = require('../sessions')
|
||||||
|
const { sendErrorResponse } = require('../utils')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves information about a WhatsApp contact by ID.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the current session.
|
||||||
|
* @param {string} req.body.contactId - The ID of the contact to retrieve information for.
|
||||||
|
* @throws {Error} If there is an error retrieving the contact information.
|
||||||
|
* @returns {Object} The contact information object.
|
||||||
|
*/
|
||||||
|
const getClassInfo = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) {
|
||||||
|
sendErrorResponse(res, 404, 'Contact not Found')
|
||||||
|
}
|
||||||
|
res.json({ success: true, result: contact })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks a WhatsApp contact by ID.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the current session.
|
||||||
|
* @param {string} req.body.contactId - The ID of the contact to block.
|
||||||
|
* @throws {Error} If there is an error blocking the contact.
|
||||||
|
* @returns {Object} The result of the blocking operation.
|
||||||
|
*/
|
||||||
|
const block = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) {
|
||||||
|
sendErrorResponse(res, 404, 'Contact not Found')
|
||||||
|
}
|
||||||
|
const result = await contact.block()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the 'About' information of a WhatsApp contact by ID.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the current session.
|
||||||
|
* @param {string} req.body.contactId - The ID of the contact to retrieve 'About' information for.
|
||||||
|
* @throws {Error} If there is an error retrieving the contact information.
|
||||||
|
* @returns {Object} The 'About' information of the contact.
|
||||||
|
*/
|
||||||
|
const getAbout = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) {
|
||||||
|
sendErrorResponse(res, 404, 'Contact not Found')
|
||||||
|
}
|
||||||
|
const result = await contact.getAbout()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the chat information of a contact with a given contactId.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function getChat
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved.
|
||||||
|
* @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information.
|
||||||
|
* @returns {Promise<void>} A promise that resolves with the chat information of the contact.
|
||||||
|
*/
|
||||||
|
const getChat = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') }
|
||||||
|
const result = await contact.getChat()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the formatted number of a contact with a given contactId.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function getFormattedNumber
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved.
|
||||||
|
* @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information.
|
||||||
|
* @returns {Promise<void>} A promise that resolves with the formatted number of the contact.
|
||||||
|
*/
|
||||||
|
const getFormattedNumber = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') }
|
||||||
|
const result = await contact.getFormattedNumber()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the country code of a contact with a given contactId.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function getCountryCode
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved.
|
||||||
|
* @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information.
|
||||||
|
* @returns {Promise<void>} A promise that resolves with the country code of the contact.
|
||||||
|
*/
|
||||||
|
const getCountryCode = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') }
|
||||||
|
const result = await contact.getCountryCode()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the profile picture url of a contact with a given contactId.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function getProfilePicUrl
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.contactId - The ID of the client whose chat information is being retrieved.
|
||||||
|
* @throws {Error} If the contact with the given contactId is not found or if there is an error retrieving the chat information.
|
||||||
|
* @returns {Promise<void>} A promise that resolves with the profile picture url of the contact.
|
||||||
|
*/
|
||||||
|
const getProfilePicUrl = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') }
|
||||||
|
const result = await contact.getProfilePicUrl() || null
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unblocks the contact with a given contactId.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function unblock
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.contactId - The ID of the client whose contact is being unblocked.
|
||||||
|
* @throws {Error} If the contact with the given contactId is not found or if there is an error unblocking the contact.
|
||||||
|
* @returns {Promise<void>} A promise that resolves with the result of unblocking the contact.
|
||||||
|
*/
|
||||||
|
const unblock = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { contactId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const contact = await client.getContactById(contactId)
|
||||||
|
if (!contact) { sendErrorResponse(res, 404, 'Contact not Found') }
|
||||||
|
const result = await contact.unblock()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getClassInfo,
|
||||||
|
block,
|
||||||
|
getAbout,
|
||||||
|
getChat,
|
||||||
|
unblock,
|
||||||
|
getFormattedNumber,
|
||||||
|
getCountryCode,
|
||||||
|
getProfilePicUrl
|
||||||
|
}
|
||||||
355
src/controllers/groupChatController.js
Normal file
355
src/controllers/groupChatController.js
Normal file
|
|
@ -0,0 +1,355 @@
|
||||||
|
const { MessageMedia } = require('whatsapp-web.js')
|
||||||
|
const { sessions } = require('../sessions')
|
||||||
|
const { sendErrorResponse } = require('../utils')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds participants to a group chat.
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object containing the chatId and contactIds in the body.
|
||||||
|
* @param {string} req.body.chatId - The ID of the group chat.
|
||||||
|
* @param {Array<string>} req.body.contactIds - An array of contact IDs to be added to the group.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @returns {Object} Returns a JSON object containing a success flag and the updated participants list.
|
||||||
|
* @throws {Error} Throws an error if the chat is not a group chat.
|
||||||
|
*/
|
||||||
|
const addParticipants = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, contactIds } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
await chat.addParticipants(contactIds)
|
||||||
|
res.json({ success: true, participants: chat.participants })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes participants from a group chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<Object>} Returns a JSON object with success flag and updated participants list
|
||||||
|
* @throws {Error} If chat is not a group
|
||||||
|
*/
|
||||||
|
const removeParticipants = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, contactIds } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
await chat.removeParticipants(contactIds)
|
||||||
|
res.json({ success: true, participants: chat.participants })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promotes participants in a group chat to admin
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<Object>} Returns a JSON object with success flag and updated participants list
|
||||||
|
* @throws {Error} If chat is not a group
|
||||||
|
*/
|
||||||
|
const promoteParticipants = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, contactIds } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
await chat.promoteParticipants(contactIds)
|
||||||
|
res.json({ success: true, participants: chat.participants })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demotes admin participants in a group chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<Object>} Returns a JSON object with success flag and updated participants list
|
||||||
|
* @throws {Error} If chat is not a group
|
||||||
|
*/
|
||||||
|
const demoteParticipants = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, contactIds } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
await chat.demoteParticipants(contactIds)
|
||||||
|
res.json({ success: true, participants: chat.participants })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the invite code for a group chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<Object>} Returns a JSON object with success flag and invite code
|
||||||
|
* @throws {Error} If chat is not a group
|
||||||
|
*/
|
||||||
|
const getInviteCode = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const inviteCode = await chat.getInviteCode()
|
||||||
|
res.json({ success: true, inviteCode })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the subject of a group chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<Object>} Returns a JSON object with success flag and updated chat object
|
||||||
|
* @throws {Error} If chat is not a group
|
||||||
|
*/
|
||||||
|
const setSubject = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, subject } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const success = await chat.setSubject(subject)
|
||||||
|
res.json({ success, chat })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the description of a group chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<Object>} Returns a JSON object with success flag and updated chat object
|
||||||
|
* @throws {Error} If chat is not a group
|
||||||
|
*/
|
||||||
|
const setDescription = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, description } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const success = await chat.setDescription(description)
|
||||||
|
res.json({ success, chat })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaves a group chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<Object>} Returns a JSON object with success flag and outcome of leaving the chat
|
||||||
|
* @throws {Error} If chat is not a group
|
||||||
|
*/
|
||||||
|
const leave = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const outcome = await chat.leave()
|
||||||
|
res.json({ success: true, outcome })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves information about a chat based on the provided chatId
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function getClassInfo
|
||||||
|
* @param {object} req - The request object
|
||||||
|
* @param {object} res - The response object
|
||||||
|
* @param {string} req.body.chatId - The chatId of the chat to retrieve information about
|
||||||
|
* @param {string} req.params.sessionId - The sessionId of the client making the request
|
||||||
|
* @throws {Error} The chat is not a group.
|
||||||
|
* @returns {Promise<void>} - A JSON response with success true and chat object containing chat information
|
||||||
|
*/
|
||||||
|
const getClassInfo = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
res.json({ success: true, chat })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revokes the invite link for a group chat based on the provided chatId
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function revokeInvite
|
||||||
|
* @param {object} req - The request object
|
||||||
|
* @param {object} res - The response object
|
||||||
|
* @param {string} req.body.chatId - The chatId of the group chat to revoke the invite for
|
||||||
|
* @param {string} req.params.sessionId - The sessionId of the client making the request
|
||||||
|
* @throws {Error} The chat is not a group.
|
||||||
|
* @returns {Promise<void>} - A JSON response with success true and the new invite code for the group chat
|
||||||
|
*/
|
||||||
|
const revokeInvite = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const newInviteCode = await chat.revokeInvite()
|
||||||
|
res.json({ success: true, newInviteCode })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets admins-only status of a group chat's info or messages.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function setInfoAdminsOnly
|
||||||
|
* @param {Object} req - Request object.
|
||||||
|
* @param {Object} res - Response object.
|
||||||
|
* @param {string} req.params.sessionId - ID of the user's session.
|
||||||
|
* @param {Object} req.body - Request body.
|
||||||
|
* @param {string} req.body.chatId - ID of the group chat.
|
||||||
|
* @param {boolean} req.body.adminsOnly - Desired admins-only status.
|
||||||
|
* @returns {Promise<void>} Promise representing the success or failure of the operation.
|
||||||
|
* @throws {Error} If the chat is not a group.
|
||||||
|
*/
|
||||||
|
const setInfoAdminsOnly = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, adminsOnly } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const result = await chat.setInfoAdminsOnly(adminsOnly)
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets admins-only status of a group chat's messages.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function setMessagesAdminsOnly
|
||||||
|
* @param {Object} req - Request object.
|
||||||
|
* @param {Object} res - Response object.
|
||||||
|
* @param {string} req.params.sessionId - ID of the user's session.
|
||||||
|
* @param {Object} req.body - Request body.
|
||||||
|
* @param {string} req.body.chatId - ID of the group chat.
|
||||||
|
* @param {boolean} req.body.adminsOnly - Desired admins-only status.
|
||||||
|
* @returns {Promise<void>} Promise representing the success or failure of the operation.
|
||||||
|
* @throws {Error} If the chat is not a group.
|
||||||
|
*/
|
||||||
|
const setMessagesAdminsOnly = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId, adminsOnly } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const result = await chat.setMessagesAdminsOnly(adminsOnly)
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the group Picture
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {Object} req.body.pictureMimetype - The mimetype of the image.
|
||||||
|
* @param {Object} req.body.pictureData - The new group picture in base64 format.
|
||||||
|
* @param {Object} req.body.chatId - ID of the group chat.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session for the user.
|
||||||
|
* @returns {Object} Returns a JSON object with a success status and the result of the function.
|
||||||
|
* @throws {Error} If there is an issue setting the group picture, an error will be thrown.
|
||||||
|
*/
|
||||||
|
const setPicture = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { pictureMimetype, pictureData, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const media = new MessageMedia(pictureMimetype, pictureData)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const result = await chat.setPicture(media)
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the group Picture
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {Object} req.body.chatId - ID of the group chat.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session for the user.
|
||||||
|
* @returns {Object} Returns a JSON object with a success status and the result of the function.
|
||||||
|
* @throws {Error} If there is an issue setting the group picture, an error will be thrown.
|
||||||
|
*/
|
||||||
|
const deletePicture = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
if (!chat.isGroup) { throw new Error('The chat is not a group') }
|
||||||
|
const result = await chat.deletePicture()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getClassInfo,
|
||||||
|
addParticipants,
|
||||||
|
demoteParticipants,
|
||||||
|
getInviteCode,
|
||||||
|
leave,
|
||||||
|
promoteParticipants,
|
||||||
|
removeParticipants,
|
||||||
|
revokeInvite,
|
||||||
|
setDescription,
|
||||||
|
setInfoAdminsOnly,
|
||||||
|
setMessagesAdminsOnly,
|
||||||
|
setSubject,
|
||||||
|
setPicture,
|
||||||
|
deletePicture
|
||||||
|
}
|
||||||
55
src/controllers/healthController.js
Normal file
55
src/controllers/healthController.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
const fsp = require('fs').promises
|
||||||
|
const qrcode = require('qrcode-terminal')
|
||||||
|
const { sessionFolderPath } = require('../config')
|
||||||
|
const { sendErrorResponse } = require('../utils')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responds to ping request with 'pong'
|
||||||
|
*
|
||||||
|
* @function ping
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<void>} - Promise that resolves once response is sent
|
||||||
|
* @throws {Object} - Throws error if response fails
|
||||||
|
*/
|
||||||
|
const ping = async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Various']
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
res.json({ success: true, message: 'pong' })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local callback function that generates a QR code and writes a log file
|
||||||
|
*
|
||||||
|
* @function localCallbackExample
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - Express request object containing a body object with dataType and data
|
||||||
|
* @param {string} req.body.dataType - Type of data (in this case, 'qr')
|
||||||
|
* @param {Object} req.body.data - Data to generate a QR code from
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @returns {Promise<void>} - Promise that resolves once response is sent
|
||||||
|
* @throws {Object} - Throws error if response fails
|
||||||
|
*/
|
||||||
|
const localCallbackExample = async (req, res) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Various']
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
const { dataType, data } = req.body
|
||||||
|
if (dataType === 'qr') { qrcode.generate(data.qr, { small: true }) }
|
||||||
|
await fsp.writeFile(`${sessionFolderPath}/message_log.txt`, `${JSON.stringify(req.body)}\r\n`, { flag: 'a+' }, _ => _)
|
||||||
|
res.json({ success: true })
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
await fsp.writeFile(`${sessionFolderPath}/message_log.txt`, `(ERROR) ${JSON.stringify(error)}\r\n`, { flag: 'a+' }, _ => _)
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { ping, localCallbackExample }
|
||||||
376
src/controllers/messageController.js
Normal file
376
src/controllers/messageController.js
Normal file
|
|
@ -0,0 +1,376 @@
|
||||||
|
const { sessions } = require('../sessions')
|
||||||
|
const { sendErrorResponse } = require('../utils')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get message by its ID from a given chat using the provided client.
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {object} client - The chat client.
|
||||||
|
* @param {string} messageId - The ID of the message to get.
|
||||||
|
* @param {string} chatId - The ID of the chat to search in.
|
||||||
|
* @returns {Promise<object>} - A Promise that resolves with the message object that matches the provided ID, or undefined if no such message exists.
|
||||||
|
* @throws {Error} - Throws an error if the provided client, message ID or chat ID is invalid.
|
||||||
|
*/
|
||||||
|
const _getMessageById = async (client, messageId, chatId) => {
|
||||||
|
const chat = await client.getChatById(chatId)
|
||||||
|
const messages = await chat.fetchMessages({ limit: 100 })
|
||||||
|
const message = messages.find((message) => { return message.id.id === messageId })
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about a message's class.
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.messageId - The message ID.
|
||||||
|
* @param {string} req.body.chatId - The chat ID.
|
||||||
|
* @returns {Promise<void>} - A Promise that resolves with no value when the function completes.
|
||||||
|
*/
|
||||||
|
const getClassInfo = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
res.json({ success: true, message })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a message.
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.messageId - The message ID.
|
||||||
|
* @param {string} req.body.chatId - The chat ID.
|
||||||
|
* @param {boolean} req.body.everyone - Whether to delete the message for everyone or just the sender.
|
||||||
|
* @returns {Promise<void>} - A Promise that resolves with no value when the function completes.
|
||||||
|
*/
|
||||||
|
const deleteMessage = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId, everyone } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const result = await message.delete(everyone)
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads media from a message.
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.messageId - The message ID.
|
||||||
|
* @param {string} req.body.chatId - The chat ID.
|
||||||
|
* @param {boolean} req.body.everyone - Whether to download the media for everyone or just the sender.
|
||||||
|
* @returns {Promise<void>} - A Promise that resolves with no value when the function completes.
|
||||||
|
*/
|
||||||
|
const downloadMedia = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId, everyone } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const messageMedia = await message.downloadMedia(everyone)
|
||||||
|
res.json({ success: true, messageMedia })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards a message to a destination chat.
|
||||||
|
* @async
|
||||||
|
* @function forward
|
||||||
|
* @param {Object} req - The request object received by the server.
|
||||||
|
* @param {Object} req.body - The body of the request object.
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to forward.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat that contains the message to forward.
|
||||||
|
* @param {string} req.body.destinationChatId - The ID of the chat to forward the message to.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session to use the Telegram API with.
|
||||||
|
* @param {Object} res - The response object to be sent back to the client.
|
||||||
|
* @returns {Object} - The response object with a JSON body containing the result of the forward operation.
|
||||||
|
* @throws Will throw an error if the message is not found or if there is an error during the forward operation.
|
||||||
|
*/
|
||||||
|
const forward = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId, destinationChatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const result = await message.forward(destinationChatId)
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets information about a message.
|
||||||
|
* @async
|
||||||
|
* @function getInfo
|
||||||
|
* @param {Object} req - The request object received by the server.
|
||||||
|
* @param {Object} req.body - The body of the request object.
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to get information about.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat that contains the message to get information about.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session to use the Telegram API with.
|
||||||
|
* @param {Object} res - The response object to be sent back to the client.
|
||||||
|
* @returns {Object} - The response object with a JSON body containing the information about the message.
|
||||||
|
* @throws Will throw an error if the message is not found or if there is an error during the get info operation.
|
||||||
|
*/
|
||||||
|
const getInfo = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const info = await message.getInfo()
|
||||||
|
res.json({ success: true, info })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of contacts mentioned in a specific message
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The HTTP request object
|
||||||
|
* @param {Object} req.body - The request body
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to retrieve mentions from
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat where the message was sent
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session for the client making the request
|
||||||
|
* @param {Object} res - The HTTP response object
|
||||||
|
* @returns {Promise<void>} - The JSON response with the list of contacts
|
||||||
|
* @throws {Error} - If there's an error retrieving the message or mentions
|
||||||
|
*/
|
||||||
|
const getMentions = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const contacts = await message.getMentions()
|
||||||
|
res.json({ success: true, contacts })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the order information contained in a specific message
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - The HTTP request object
|
||||||
|
* @param {Object} req.body - The request body
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to retrieve the order from
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat where the message was sent
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session for the client making the request
|
||||||
|
* @param {Object} res - The HTTP response object
|
||||||
|
* @returns {Promise<void>} - The JSON response with the order information
|
||||||
|
* @throws {Error} - If there's an error retrieving the message or order information
|
||||||
|
*/
|
||||||
|
const getOrder = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const order = await message.getOrder()
|
||||||
|
res.json({ success: true, order })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the payment information from a specific message identified by its ID.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function getPayment
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID associated with the client making the request.
|
||||||
|
* @param {Object} req.body - The message ID and chat ID associated with the message to retrieve payment information from.
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to retrieve payment information from.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat the message is associated with.
|
||||||
|
* @returns {Object} An object containing a success status and the payment information for the specified message.
|
||||||
|
* @throws {Object} If the specified message is not found or if an error occurs during the retrieval process.
|
||||||
|
*/
|
||||||
|
const getPayment = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const payment = await message.getPayment()
|
||||||
|
res.json({ success: true, payment })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the quoted message information from a specific message identified by its ID.
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function getQuotedMessage
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID associated with the client making the request.
|
||||||
|
* @param {Object} req.body - The message ID and chat ID associated with the message to retrieve quoted message information from.
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to retrieve quoted message information from.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat the message is associated with.
|
||||||
|
* @returns {Object} An object containing a success status and the quoted message information for the specified message.
|
||||||
|
* @throws {Object} If the specified message is not found or if an error occurs during the retrieval process.
|
||||||
|
*/
|
||||||
|
const getQuotedMessage = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const quotedMessage = await message.getQuotedMessage()
|
||||||
|
res.json({ success: true, quotedMessage })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React to a specific message in a chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function react
|
||||||
|
* @param {Object} req - The HTTP request object containing the request parameters and body.
|
||||||
|
* @param {Object} res - The HTTP response object to send the result.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session to use.
|
||||||
|
* @param {Object} req.body - The body of the request.
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to react to.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat the message is in.
|
||||||
|
* @param {string} req.body.reaction - The reaction to add to the message.
|
||||||
|
* @returns {Object} The HTTP response containing the result of the operation.
|
||||||
|
* @throws {Error} If there was an error during the operation.
|
||||||
|
*/
|
||||||
|
const react = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId, reaction } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const result = await message.react(reaction)
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reply to a specific message in a chat
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function reply
|
||||||
|
* @param {Object} req - The HTTP request object containing the request parameters and body.
|
||||||
|
* @param {Object} res - The HTTP response object to send the result.
|
||||||
|
* @param {string} req.params.sessionId - The ID of the session to use.
|
||||||
|
* @param {string} req.body.messageId - The ID of the message to reply to.
|
||||||
|
* @param {string} req.body.chatId - The ID of the chat the message is in.
|
||||||
|
* @param {string} req.body.content - The content of the message to send.
|
||||||
|
* @param {string} req.body.destinationChatId - The ID of the chat to send the reply to.
|
||||||
|
* @param {Object} req.body.options - Additional options for sending the message.
|
||||||
|
* @returns {Object} The HTTP response containing the result of the operation.
|
||||||
|
* @throws {Error} If there was an error during the operation.
|
||||||
|
*/
|
||||||
|
const reply = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId, content, destinationChatId, options } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const repliedMessage = await message.reply(content, destinationChatId, options)
|
||||||
|
res.json({ success: true, repliedMessage })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function star
|
||||||
|
* @async
|
||||||
|
* @description Stars a message by message ID and chat ID.
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.messageId - The message ID.
|
||||||
|
* @param {string} req.body.chatId - The chat ID.
|
||||||
|
* @returns {Promise} A Promise that resolves with the result of the message.star() call.
|
||||||
|
* @throws {Error} If message is not found, it throws an error with the message "Message not Found".
|
||||||
|
*/
|
||||||
|
const star = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const result = await message.star()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function unstar
|
||||||
|
* @async
|
||||||
|
* @description Unstars a message by message ID and chat ID.
|
||||||
|
* @param {Object} req - The request object.
|
||||||
|
* @param {Object} res - The response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID.
|
||||||
|
* @param {string} req.body.messageId - The message ID.
|
||||||
|
* @param {string} req.body.chatId - The chat ID.
|
||||||
|
* @returns {Promise} A Promise that resolves with the result of the message.unstar() call.
|
||||||
|
* @throws {Error} If message is not found, it throws an error with the message "Message not Found".
|
||||||
|
*/
|
||||||
|
const unstar = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { messageId, chatId } = req.body
|
||||||
|
const client = sessions.get(req.params.sessionId)
|
||||||
|
const message = await _getMessageById(client, messageId, chatId)
|
||||||
|
if (!message) { throw new Error('Message not Found') }
|
||||||
|
const result = await message.unstar()
|
||||||
|
res.json({ success: true, result })
|
||||||
|
} catch (error) {
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getClassInfo,
|
||||||
|
deleteMessage,
|
||||||
|
downloadMedia,
|
||||||
|
forward,
|
||||||
|
getInfo,
|
||||||
|
getMentions,
|
||||||
|
getOrder,
|
||||||
|
getPayment,
|
||||||
|
getQuotedMessage,
|
||||||
|
react,
|
||||||
|
reply,
|
||||||
|
star,
|
||||||
|
unstar
|
||||||
|
}
|
||||||
374
src/controllers/sessionController.js
Normal file
374
src/controllers/sessionController.js
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
const qr = require('qr-image')
|
||||||
|
const { setupSession, deleteSession, reloadSession, validateSession, flushSessions, sessions } = require('../sessions')
|
||||||
|
const { sendErrorResponse, waitForNestedObject } = require('../utils')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a session for the given session ID.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID to start.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error starting the session.
|
||||||
|
*/
|
||||||
|
const startSession = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Start new session'
|
||||||
|
// #swagger.description = 'Starts a session for the given session ID.'
|
||||||
|
try {
|
||||||
|
const sessionId = req.params.sessionId
|
||||||
|
const setupSessionReturn = setupSession(sessionId)
|
||||||
|
if (!setupSessionReturn.success) {
|
||||||
|
/* #swagger.responses[422] = {
|
||||||
|
description: "Unprocessable Entity.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
sendErrorResponse(res, 422, setupSessionReturn.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/* #swagger.responses[200] = {
|
||||||
|
description: "Status of the initiated session.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/StartSessionResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// wait until the client is created
|
||||||
|
await waitForNestedObject(setupSessionReturn.client, 'pupPage')
|
||||||
|
res.json({ success: true, message: setupSessionReturn.message })
|
||||||
|
} catch (error) {
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
console.log('startSession ERROR', error)
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status of the session with the given session ID.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID to start.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error getting status of the session.
|
||||||
|
*/
|
||||||
|
const statusSession = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Get session status'
|
||||||
|
// #swagger.description = 'Status of the session with the given session ID.'
|
||||||
|
try {
|
||||||
|
const sessionId = req.params.sessionId
|
||||||
|
const sessionData = await validateSession(sessionId)
|
||||||
|
/* #swagger.responses[200] = {
|
||||||
|
description: "Status of the session.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/StatusSessionResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
res.json(sessionData)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('statusSession ERROR', error)
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QR code of the session with the given session ID.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID to start.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error getting status of the session.
|
||||||
|
*/
|
||||||
|
const sessionQrCode = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Get session QR code'
|
||||||
|
// #swagger.description = 'QR code of the session with the given session ID.'
|
||||||
|
try {
|
||||||
|
const sessionId = req.params.sessionId
|
||||||
|
const session = sessions.get(sessionId)
|
||||||
|
if (!session) {
|
||||||
|
return res.json({ success: false, message: 'session_not_found' })
|
||||||
|
}
|
||||||
|
if (session.qr) {
|
||||||
|
return res.json({ success: true, qr: session.qr })
|
||||||
|
}
|
||||||
|
return res.json({ success: false, message: 'qr code not ready or already scanned' })
|
||||||
|
} catch (error) {
|
||||||
|
console.log('sessionQrCode ERROR', error)
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QR code as image of the session with the given session ID.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID to start.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error getting status of the session.
|
||||||
|
*/
|
||||||
|
const sessionQrCodeImage = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Get session QR code as image'
|
||||||
|
// #swagger.description = 'QR code as image of the session with the given session ID.'
|
||||||
|
try {
|
||||||
|
const sessionId = req.params.sessionId
|
||||||
|
const session = sessions.get(sessionId)
|
||||||
|
if (!session) {
|
||||||
|
return res.json({ success: false, message: 'session_not_found' })
|
||||||
|
}
|
||||||
|
if (session.qr) {
|
||||||
|
const qrImage = qr.image(session.qr)
|
||||||
|
/* #swagger.responses[200] = {
|
||||||
|
description: "QR image.",
|
||||||
|
content: {
|
||||||
|
"image/png": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'image/png'
|
||||||
|
})
|
||||||
|
return qrImage.pipe(res)
|
||||||
|
}
|
||||||
|
return res.json({ success: false, message: 'qr code not ready or already scanned' })
|
||||||
|
} catch (error) {
|
||||||
|
console.log('sessionQrCodeImage ERROR', error)
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restarts the session with the given session ID.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID to terminate.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error terminating the session.
|
||||||
|
*/
|
||||||
|
const restartSession = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Restart session'
|
||||||
|
// #swagger.description = 'Restarts the session with the given session ID.'
|
||||||
|
try {
|
||||||
|
const sessionId = req.params.sessionId
|
||||||
|
const validation = await validateSession(sessionId)
|
||||||
|
if (validation.message === 'session_not_found') {
|
||||||
|
return res.json(validation)
|
||||||
|
}
|
||||||
|
await reloadSession(sessionId)
|
||||||
|
/* #swagger.responses[200] = {
|
||||||
|
description: "Sessions restarted.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/RestartSessionResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
res.json({ success: true, message: 'Restarted successfully' })
|
||||||
|
} catch (error) {
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
console.log('restartSession ERROR', error)
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates the session with the given session ID.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @param {string} req.params.sessionId - The session ID to terminate.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error terminating the session.
|
||||||
|
*/
|
||||||
|
const terminateSession = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Terminate session'
|
||||||
|
// #swagger.description = 'Terminates the session with the given session ID.'
|
||||||
|
try {
|
||||||
|
const sessionId = req.params.sessionId
|
||||||
|
const validation = await validateSession(sessionId)
|
||||||
|
if (validation.message === 'session_not_found') {
|
||||||
|
return res.json(validation)
|
||||||
|
}
|
||||||
|
await deleteSession(sessionId, validation)
|
||||||
|
/* #swagger.responses[200] = {
|
||||||
|
description: "Sessions terminated.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/TerminateSessionResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
res.json({ success: true, message: 'Logged out successfully' })
|
||||||
|
} catch (error) {
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
console.log('terminateSession ERROR', error)
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates all inactive sessions.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error terminating the sessions.
|
||||||
|
*/
|
||||||
|
const terminateInactiveSessions = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Terminate inactive sessions'
|
||||||
|
// #swagger.description = 'Terminates all inactive sessions.'
|
||||||
|
try {
|
||||||
|
await flushSessions(true)
|
||||||
|
/* #swagger.responses[200] = {
|
||||||
|
description: "Sessions terminated.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/TerminateSessionsResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
res.json({ success: true, message: 'Flush completed successfully' })
|
||||||
|
} catch (error) {
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
console.log('terminateInactiveSessions ERROR', error)
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates all sessions.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @async
|
||||||
|
* @param {Object} req - The HTTP request object.
|
||||||
|
* @param {Object} res - The HTTP response object.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @throws {Error} If there was an error terminating the sessions.
|
||||||
|
*/
|
||||||
|
const terminateAllSessions = async (req, res) => {
|
||||||
|
// #swagger.summary = 'Terminate all sessions'
|
||||||
|
// #swagger.description = 'Terminates all sessions.'
|
||||||
|
try {
|
||||||
|
await flushSessions(false)
|
||||||
|
/* #swagger.responses[200] = {
|
||||||
|
description: "Sessions terminated.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/TerminateSessionsResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
res.json({ success: true, message: 'Flush completed successfully' })
|
||||||
|
} catch (error) {
|
||||||
|
/* #swagger.responses[500] = {
|
||||||
|
description: "Server Failure.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
console.log('terminateAllSessions ERROR', error)
|
||||||
|
sendErrorResponse(res, 500, error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
startSession,
|
||||||
|
statusSession,
|
||||||
|
sessionQrCode,
|
||||||
|
sessionQrCodeImage,
|
||||||
|
restartSession,
|
||||||
|
terminateSession,
|
||||||
|
terminateInactiveSessions,
|
||||||
|
terminateAllSessions
|
||||||
|
}
|
||||||
188
src/middleware.js
Normal file
188
src/middleware.js
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
const { globalApiKey, rateLimitMax, rateLimitWindowMs } = require('./config')
|
||||||
|
const { sendErrorResponse } = require('./utils')
|
||||||
|
const { validateSession } = require('./sessions')
|
||||||
|
const rateLimiting = require('express-rate-limit')
|
||||||
|
|
||||||
|
const apikey = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.security = [{
|
||||||
|
"apiKeyAuth": []
|
||||||
|
}]
|
||||||
|
*/
|
||||||
|
/* #swagger.responses[403] = {
|
||||||
|
description: "Forbidden.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ForbiddenResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (globalApiKey) {
|
||||||
|
const apiKey = req.headers['x-api-key']
|
||||||
|
if (!apiKey || apiKey !== globalApiKey) {
|
||||||
|
return sendErrorResponse(res, 403, 'Invalid API key')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionNameValidation = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.parameters['sessionId'] = {
|
||||||
|
in: 'path',
|
||||||
|
description: 'Unique identifier for the session (alphanumeric and - allowed)',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
example: 'f8377d8d-a589-4242-9ba6-9486a04ef80c'
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if ((!/^[\w-]+$/.test(req.params.sessionId))) {
|
||||||
|
/* #swagger.responses[422] = {
|
||||||
|
description: "Unprocessable Entity.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return sendErrorResponse(res, 422, 'Session should be alphanumerical or -')
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionValidation = async (req, res, next) => {
|
||||||
|
const validation = await validateSession(req.params.sessionId)
|
||||||
|
if (validation.success !== true) {
|
||||||
|
/* #swagger.responses[404] = {
|
||||||
|
description: "Not Found.",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { "$ref": "#/definitions/NotFoundResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return sendErrorResponse(res, 404, validation.message)
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const rateLimiter = rateLimiting({
|
||||||
|
limit: rateLimitMax,
|
||||||
|
windowMs: rateLimitWindowMs,
|
||||||
|
message: "You can't make any more requests at the moment. Try again later"
|
||||||
|
})
|
||||||
|
|
||||||
|
const sessionSwagger = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Session']
|
||||||
|
*/
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientSwagger = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Client']
|
||||||
|
*/
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const contactSwagger = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Contact']
|
||||||
|
#swagger.requestBody = {
|
||||||
|
required: true,
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
contactId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Unique whatsApp identifier for the contact',
|
||||||
|
example: '6281288888888@c.us'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageSwagger = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Message']
|
||||||
|
#swagger.requestBody = {
|
||||||
|
required: true,
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
chatId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The Chat id which contains the message',
|
||||||
|
example: '6281288888888@c.us'
|
||||||
|
},
|
||||||
|
messageId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Unique whatsApp identifier for the message',
|
||||||
|
example: 'ABCDEF999999999'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatSwagger = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Chat']
|
||||||
|
#swagger.requestBody = {
|
||||||
|
required: true,
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
chatId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Unique whatsApp identifier for the given Chat (either group or personnal)',
|
||||||
|
example: '6281288888888@c.us'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupChatSwagger = async (req, res, next) => {
|
||||||
|
/*
|
||||||
|
#swagger.tags = ['Group Chat']
|
||||||
|
#swagger.requestBody = {
|
||||||
|
required: true,
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
chatId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Unique whatsApp identifier for the given Chat (either group or personnal)',
|
||||||
|
example: '6281288888888@c.us'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sessionValidation,
|
||||||
|
apikey,
|
||||||
|
sessionNameValidation,
|
||||||
|
sessionSwagger,
|
||||||
|
clientSwagger,
|
||||||
|
contactSwagger,
|
||||||
|
messageSwagger,
|
||||||
|
chatSwagger,
|
||||||
|
groupChatSwagger,
|
||||||
|
rateLimiter
|
||||||
|
}
|
||||||
191
src/routes.js
Normal file
191
src/routes.js
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
const express = require('express')
|
||||||
|
const routes = express.Router()
|
||||||
|
const swaggerUi = require('swagger-ui-express')
|
||||||
|
const swaggerDocument = require('../swagger.json')
|
||||||
|
const { enableLocalCallbackExample, enableSwaggerEndpoint } = require('./config')
|
||||||
|
|
||||||
|
const middleware = require('./middleware')
|
||||||
|
const healthController = require('./controllers/healthController')
|
||||||
|
const sessionController = require('./controllers/sessionController')
|
||||||
|
const clientController = require('./controllers/clientController')
|
||||||
|
const chatController = require('./controllers/chatController')
|
||||||
|
const groupChatController = require('./controllers/groupChatController')
|
||||||
|
const messageController = require('./controllers/messageController')
|
||||||
|
const contactController = require('./controllers/contactController')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* HEALTH ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
|
||||||
|
// API endpoint to check if server is alive
|
||||||
|
routes.get('/ping', healthController.ping)
|
||||||
|
// API basic callback
|
||||||
|
if (enableLocalCallbackExample) {
|
||||||
|
routes.post('/localCallbackExample', [middleware.apikey, middleware.rateLimiter], healthController.localCallbackExample)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* SESSION ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
const sessionRouter = express.Router()
|
||||||
|
sessionRouter.use(middleware.apikey)
|
||||||
|
sessionRouter.use(middleware.sessionSwagger)
|
||||||
|
routes.use('/session', sessionRouter)
|
||||||
|
|
||||||
|
sessionRouter.get('/start/:sessionId', middleware.sessionNameValidation, sessionController.startSession)
|
||||||
|
sessionRouter.get('/status/:sessionId', middleware.sessionNameValidation, sessionController.statusSession)
|
||||||
|
sessionRouter.get('/qr/:sessionId', middleware.sessionNameValidation, sessionController.sessionQrCode)
|
||||||
|
sessionRouter.get('/qr/:sessionId/image', middleware.sessionNameValidation, sessionController.sessionQrCodeImage)
|
||||||
|
sessionRouter.get('/restart/:sessionId', middleware.sessionNameValidation, sessionController.restartSession)
|
||||||
|
sessionRouter.get('/terminate/:sessionId', middleware.sessionNameValidation, sessionController.terminateSession)
|
||||||
|
sessionRouter.get('/terminateInactive', sessionController.terminateInactiveSessions)
|
||||||
|
sessionRouter.get('/terminateAll', sessionController.terminateAllSessions)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* CLIENT ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
|
||||||
|
const clientRouter = express.Router()
|
||||||
|
clientRouter.use(middleware.apikey)
|
||||||
|
sessionRouter.use(middleware.clientSwagger)
|
||||||
|
routes.use('/client', clientRouter)
|
||||||
|
|
||||||
|
clientRouter.get('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getClassInfo)
|
||||||
|
clientRouter.post('/acceptInvite/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.acceptInvite)
|
||||||
|
clientRouter.post('/archiveChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.archiveChat)
|
||||||
|
clientRouter.post('/createGroup/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.createGroup)
|
||||||
|
clientRouter.post('/getBlockedContacts/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getBlockedContacts)
|
||||||
|
clientRouter.post('/getChatById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatById)
|
||||||
|
clientRouter.post('/getChatLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatLabels)
|
||||||
|
clientRouter.get('/getChats/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChats)
|
||||||
|
clientRouter.post('/getChatsByLabelId/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getChatsByLabelId)
|
||||||
|
clientRouter.post('/getCommonGroups/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getCommonGroups)
|
||||||
|
clientRouter.post('/getContactById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getContactById)
|
||||||
|
clientRouter.get('/getContacts/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getContacts)
|
||||||
|
clientRouter.post('/getInviteInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getInviteInfo)
|
||||||
|
clientRouter.post('/getLabelById/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getLabelById)
|
||||||
|
clientRouter.post('/getLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getLabels)
|
||||||
|
clientRouter.post('/addOrRemoveLabels/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.addOrRemoveLabels)
|
||||||
|
clientRouter.post('/getNumberId/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getNumberId)
|
||||||
|
clientRouter.post('/isRegisteredUser/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.isRegisteredUser)
|
||||||
|
clientRouter.post('/getProfilePicUrl/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getProfilePictureUrl)
|
||||||
|
clientRouter.get('/getState/:sessionId', [middleware.sessionNameValidation], clientController.getState)
|
||||||
|
clientRouter.post('/markChatUnread/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.markChatUnread)
|
||||||
|
clientRouter.post('/muteChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.muteChat)
|
||||||
|
clientRouter.post('/pinChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.pinChat)
|
||||||
|
clientRouter.post('/searchMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.searchMessages)
|
||||||
|
clientRouter.post('/sendMessage/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendMessage)
|
||||||
|
clientRouter.post('/sendPresenceAvailable/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendPresenceAvailable)
|
||||||
|
clientRouter.post('/sendPresenceUnavailable/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendPresenceUnavailable)
|
||||||
|
clientRouter.post('/sendSeen/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.sendSeen)
|
||||||
|
clientRouter.post('/setDisplayName/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setDisplayName)
|
||||||
|
clientRouter.post('/setProfilePicture/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setProfilePicture)
|
||||||
|
clientRouter.post('/setStatus/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.setStatus)
|
||||||
|
clientRouter.post('/unarchiveChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unarchiveChat)
|
||||||
|
clientRouter.post('/unmuteChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unmuteChat)
|
||||||
|
clientRouter.post('/unpinChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.unpinChat)
|
||||||
|
clientRouter.get('/getWWebVersion/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], clientController.getWWebVersion)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* CHAT ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
const chatRouter = express.Router()
|
||||||
|
chatRouter.use(middleware.apikey)
|
||||||
|
sessionRouter.use(middleware.chatSwagger)
|
||||||
|
routes.use('/chat', chatRouter)
|
||||||
|
|
||||||
|
chatRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.getClassInfo)
|
||||||
|
chatRouter.post('/clearMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.clearMessages)
|
||||||
|
chatRouter.post('/clearState/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.clearState)
|
||||||
|
chatRouter.post('/delete/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.deleteChat)
|
||||||
|
chatRouter.post('/fetchMessages/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.fetchMessages)
|
||||||
|
chatRouter.post('/getContact/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.getContact)
|
||||||
|
chatRouter.post('/sendStateRecording/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.sendStateRecording)
|
||||||
|
chatRouter.post('/sendStateTyping/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], chatController.sendStateTyping)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* GROUP CHAT ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
const groupChatRouter = express.Router()
|
||||||
|
groupChatRouter.use(middleware.apikey)
|
||||||
|
sessionRouter.use(middleware.groupChatSwagger)
|
||||||
|
routes.use('/groupChat', groupChatRouter)
|
||||||
|
|
||||||
|
groupChatRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.getClassInfo)
|
||||||
|
groupChatRouter.post('/addParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.addParticipants)
|
||||||
|
groupChatRouter.post('/demoteParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.demoteParticipants)
|
||||||
|
groupChatRouter.post('/getInviteCode/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.getInviteCode)
|
||||||
|
groupChatRouter.post('/leave/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.leave)
|
||||||
|
groupChatRouter.post('/promoteParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.promoteParticipants)
|
||||||
|
groupChatRouter.post('/removeParticipants/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.removeParticipants)
|
||||||
|
groupChatRouter.post('/revokeInvite/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.revokeInvite)
|
||||||
|
groupChatRouter.post('/setDescription/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setDescription)
|
||||||
|
groupChatRouter.post('/setInfoAdminsOnly/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setInfoAdminsOnly)
|
||||||
|
groupChatRouter.post('/setMessagesAdminsOnly/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setMessagesAdminsOnly)
|
||||||
|
groupChatRouter.post('/setSubject/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setSubject)
|
||||||
|
groupChatRouter.post('/setPicture/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.setPicture)
|
||||||
|
groupChatRouter.post('/deletePicture/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], groupChatController.deletePicture)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* MESSAGE ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
const messageRouter = express.Router()
|
||||||
|
messageRouter.use(middleware.apikey)
|
||||||
|
sessionRouter.use(middleware.messageSwagger)
|
||||||
|
routes.use('/message', messageRouter)
|
||||||
|
|
||||||
|
messageRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getClassInfo)
|
||||||
|
messageRouter.post('/delete/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.deleteMessage)
|
||||||
|
messageRouter.post('/downloadMedia/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.downloadMedia)
|
||||||
|
messageRouter.post('/forward/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.forward)
|
||||||
|
messageRouter.post('/getInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getInfo)
|
||||||
|
messageRouter.post('/getMentions/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getMentions)
|
||||||
|
messageRouter.post('/getOrder/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getOrder)
|
||||||
|
messageRouter.post('/getPayment/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getPayment)
|
||||||
|
messageRouter.post('/getQuotedMessage/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.getQuotedMessage)
|
||||||
|
messageRouter.post('/react/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.react)
|
||||||
|
messageRouter.post('/reply/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.reply)
|
||||||
|
messageRouter.post('/star/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.star)
|
||||||
|
messageRouter.post('/unstar/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], messageController.unstar)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* MESSAGE ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
const contactRouter = express.Router()
|
||||||
|
contactRouter.use(middleware.apikey)
|
||||||
|
sessionRouter.use(middleware.contactSwagger)
|
||||||
|
routes.use('/contact', contactRouter)
|
||||||
|
|
||||||
|
contactRouter.post('/getClassInfo/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getClassInfo)
|
||||||
|
contactRouter.post('/block/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.block)
|
||||||
|
contactRouter.post('/getAbout/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getAbout)
|
||||||
|
contactRouter.post('/getChat/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getChat)
|
||||||
|
contactRouter.post('/unblock/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.unblock)
|
||||||
|
contactRouter.post('/getFormattedNumber/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getFormattedNumber)
|
||||||
|
contactRouter.post('/getCountryCode/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getCountryCode)
|
||||||
|
contactRouter.post('/getProfilePicUrl/:sessionId', [middleware.sessionNameValidation, middleware.sessionValidation], contactController.getProfilePicUrl)
|
||||||
|
/**
|
||||||
|
* ================
|
||||||
|
* SWAGGER ENDPOINTS
|
||||||
|
* ================
|
||||||
|
*/
|
||||||
|
if (enableSwaggerEndpoint) {
|
||||||
|
routes.use('/api-docs', swaggerUi.serve)
|
||||||
|
routes.get('/api-docs', swaggerUi.setup(swaggerDocument) /* #swagger.ignore = true */)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { routes }
|
||||||
474
src/sessions.js
Normal file
474
src/sessions.js
Normal file
|
|
@ -0,0 +1,474 @@
|
||||||
|
const { Client, LocalAuth } = require('whatsapp-web.js')
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const sessions = new Map()
|
||||||
|
const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless } = require('./config')
|
||||||
|
const { triggerWebhook, waitForNestedObject, checkIfEventisEnabled } = require('./utils')
|
||||||
|
|
||||||
|
// Function to validate if the session is ready
|
||||||
|
const validateSession = async (sessionId) => {
|
||||||
|
try {
|
||||||
|
const returnData = { success: false, state: null, message: '' }
|
||||||
|
|
||||||
|
// Session not Connected 😢
|
||||||
|
if (!sessions.has(sessionId) || !sessions.get(sessionId)) {
|
||||||
|
returnData.message = 'session_not_found'
|
||||||
|
return returnData
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = sessions.get(sessionId)
|
||||||
|
// wait until the client is created
|
||||||
|
await waitForNestedObject(client, 'pupPage')
|
||||||
|
.catch((err) => { return { success: false, state: null, message: err.message } })
|
||||||
|
|
||||||
|
// Wait for client.pupPage to be evaluable
|
||||||
|
let maxRetry = 0
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
if (client.pupPage.isClosed()) {
|
||||||
|
return { success: false, state: null, message: 'browser tab closed' }
|
||||||
|
}
|
||||||
|
await Promise.race([
|
||||||
|
client.pupPage.evaluate('1'),
|
||||||
|
new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
])
|
||||||
|
break
|
||||||
|
} catch (error) {
|
||||||
|
if (maxRetry === 2) {
|
||||||
|
return { success: false, state: null, message: 'session closed' }
|
||||||
|
}
|
||||||
|
maxRetry++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = await client.getState()
|
||||||
|
returnData.state = state
|
||||||
|
if (state !== 'CONNECTED') {
|
||||||
|
returnData.message = 'session_not_connected'
|
||||||
|
return returnData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session Connected 🎉
|
||||||
|
returnData.success = true
|
||||||
|
returnData.message = 'session_connected'
|
||||||
|
return returnData
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
return { success: false, state: null, message: error.message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle client session restoration
|
||||||
|
const restoreSessions = () => {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(sessionFolderPath)) {
|
||||||
|
fs.mkdirSync(sessionFolderPath) // Create the session directory if it doesn't exist
|
||||||
|
}
|
||||||
|
// Read the contents of the folder
|
||||||
|
fs.readdir(sessionFolderPath, (_, files) => {
|
||||||
|
// Iterate through the files in the parent folder
|
||||||
|
for (const file of files) {
|
||||||
|
// Use regular expression to extract the string from the folder name
|
||||||
|
const match = file.match(/^session-(.+)$/)
|
||||||
|
if (match) {
|
||||||
|
const sessionId = match[1]
|
||||||
|
console.log('existing session detected', sessionId)
|
||||||
|
setupSession(sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
console.error('Failed to restore sessions:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup Session
|
||||||
|
const setupSession = (sessionId) => {
|
||||||
|
try {
|
||||||
|
if (sessions.has(sessionId)) {
|
||||||
|
return { success: false, message: `Session already exists for: ${sessionId}`, client: sessions.get(sessionId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the delete folder from the logout function (will be handled separately)
|
||||||
|
const localAuth = new LocalAuth({ clientId: sessionId, dataPath: sessionFolderPath })
|
||||||
|
delete localAuth.logout
|
||||||
|
localAuth.logout = () => { }
|
||||||
|
|
||||||
|
const clientOptions = {
|
||||||
|
puppeteer: {
|
||||||
|
executablePath: chromeBin,
|
||||||
|
headless,
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--disable-dev-shm-usage']
|
||||||
|
},
|
||||||
|
authStrategy: localAuth
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webVersion) {
|
||||||
|
clientOptions.webVersion = webVersion
|
||||||
|
switch (webVersionCacheType.toLowerCase()) {
|
||||||
|
case 'local':
|
||||||
|
clientOptions.webVersionCache = {
|
||||||
|
type: 'local'
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'remote':
|
||||||
|
clientOptions.webVersionCache = {
|
||||||
|
type: 'remote',
|
||||||
|
remotePath: 'https://raw.githubusercontent.com/wppconnect-team/wa-version/main/html/' + webVersion + '.html'
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
clientOptions.webVersionCache = {
|
||||||
|
type: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new Client(clientOptions)
|
||||||
|
|
||||||
|
client.initialize().catch(err => console.log('Initialize error:', err.message))
|
||||||
|
|
||||||
|
initializeEvents(client, sessionId)
|
||||||
|
|
||||||
|
// Save the session to the Map
|
||||||
|
sessions.set(sessionId, client)
|
||||||
|
return { success: true, message: 'Session initiated successfully', client }
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, message: error.message, client: null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeEvents = (client, sessionId) => {
|
||||||
|
// check if the session webhook is overridden
|
||||||
|
const sessionWebhook = process.env[sessionId.toUpperCase() + '_WEBHOOK_URL'] || baseWebhookURL
|
||||||
|
|
||||||
|
if (recoverSessions) {
|
||||||
|
waitForNestedObject(client, 'pupPage').then(() => {
|
||||||
|
const restartSession = async (sessionId) => {
|
||||||
|
sessions.delete(sessionId)
|
||||||
|
await client.destroy().catch(e => { })
|
||||||
|
setupSession(sessionId)
|
||||||
|
}
|
||||||
|
client.pupPage.once('close', function () {
|
||||||
|
// emitted when the page closes
|
||||||
|
console.log(`Browser page closed for ${sessionId}. Restoring`)
|
||||||
|
restartSession(sessionId)
|
||||||
|
})
|
||||||
|
client.pupPage.once('error', function () {
|
||||||
|
// emitted when the page crashes
|
||||||
|
console.log(`Error occurred on browser page for ${sessionId}. Restoring`)
|
||||||
|
restartSession(sessionId)
|
||||||
|
})
|
||||||
|
}).catch(e => { })
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIfEventisEnabled('auth_failure')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('auth_failure', (msg) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'status', { msg })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('authenticated')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('authenticated', () => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'authenticated')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('call')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('call', async (call) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'call', { call })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('change_state')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('change_state', state => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'change_state', { state })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('disconnected')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('disconnected', (reason) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'disconnected', { reason })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('group_join')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('group_join', (notification) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'group_join', { notification })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('group_leave')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('group_leave', (notification) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'group_leave', { notification })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('group_update')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('group_update', (notification) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'group_update', { notification })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('loading_screen')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('loading_screen', (percent, message) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'loading_screen', { percent, message })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('media_uploaded')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('media_uploaded', (message) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'media_uploaded', { message })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('message', async (message) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message', { message })
|
||||||
|
if (message.hasMedia && message._data?.size < maxAttachmentSize) {
|
||||||
|
// custom service event
|
||||||
|
checkIfEventisEnabled('media').then(_ => {
|
||||||
|
message.downloadMedia().then(messageMedia => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'media', { messageMedia, message })
|
||||||
|
}).catch(e => {
|
||||||
|
console.log('Download media error:', e.message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (setMessagesAsSeen) {
|
||||||
|
const chat = await message.getChat()
|
||||||
|
chat.sendSeen()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message_ack')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('message_ack', async (message, ack) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message_ack', { message, ack })
|
||||||
|
if (setMessagesAsSeen) {
|
||||||
|
const chat = await message.getChat()
|
||||||
|
chat.sendSeen()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message_create')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('message_create', async (message) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message_create', { message })
|
||||||
|
if (setMessagesAsSeen) {
|
||||||
|
const chat = await message.getChat()
|
||||||
|
chat.sendSeen()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message_reaction')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('message_reaction', (reaction) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message_reaction', { reaction })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message_edit')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('message_edit', (message, newBody, prevBody) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message_edit', { message, newBody, prevBody })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message_ciphertext')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('message_ciphertext', (message) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message_ciphertext', { message })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message_revoke_everyone')
|
||||||
|
.then(_ => {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
client.on('message_revoke_everyone', async (message) => {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message_revoke_everyone', { message })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('message_revoke_me')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('message_revoke_me', async (message) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'message_revoke_me', { message })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on('qr', (qr) => {
|
||||||
|
// inject qr code into session
|
||||||
|
client.qr = qr
|
||||||
|
checkIfEventisEnabled('qr')
|
||||||
|
.then(_ => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'qr', { qr })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('ready')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('ready', () => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'ready')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('contact_changed')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('contact_changed', async (message, oldId, newId, isContact) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'contact_changed', { message, oldId, newId, isContact })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('chat_removed')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('chat_removed', async (chat) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'chat_removed', { chat })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('chat_archived')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('chat_archived', async (chat, currState, prevState) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'chat_archived', { chat, currState, prevState })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
checkIfEventisEnabled('unread_count')
|
||||||
|
.then(_ => {
|
||||||
|
client.on('unread_count', async (chat) => {
|
||||||
|
triggerWebhook(sessionWebhook, sessionId, 'unread_count', { chat })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to delete client session folder
|
||||||
|
const deleteSessionFolder = async (sessionId) => {
|
||||||
|
try {
|
||||||
|
const targetDirPath = path.join(sessionFolderPath, `session-${sessionId}`)
|
||||||
|
const resolvedTargetDirPath = await fs.promises.realpath(targetDirPath)
|
||||||
|
const resolvedSessionPath = await fs.promises.realpath(sessionFolderPath)
|
||||||
|
|
||||||
|
// Ensure the target directory path ends with a path separator
|
||||||
|
const safeSessionPath = `${resolvedSessionPath}${path.sep}`
|
||||||
|
|
||||||
|
// Validate the resolved target directory path is a subdirectory of the session folder path
|
||||||
|
if (!resolvedTargetDirPath.startsWith(safeSessionPath)) {
|
||||||
|
throw new Error('Invalid path: Directory traversal detected')
|
||||||
|
}
|
||||||
|
await fs.promises.rm(resolvedTargetDirPath, { recursive: true, force: true })
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Folder deletion error', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to reload client session without removing browser cache
|
||||||
|
const reloadSession = async (sessionId) => {
|
||||||
|
try {
|
||||||
|
const client = sessions.get(sessionId)
|
||||||
|
if (!client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.pupPage?.removeAllListeners('close')
|
||||||
|
client.pupPage?.removeAllListeners('error')
|
||||||
|
try {
|
||||||
|
const pages = await client.pupBrowser.pages()
|
||||||
|
await Promise.all(pages.map((page) => page.close()))
|
||||||
|
await Promise.race([
|
||||||
|
client.pupBrowser.close(),
|
||||||
|
new Promise(resolve => setTimeout(resolve, 5000))
|
||||||
|
])
|
||||||
|
} catch (e) {
|
||||||
|
const childProcess = client.pupBrowser.process()
|
||||||
|
if (childProcess) {
|
||||||
|
childProcess.kill(9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessions.delete(sessionId)
|
||||||
|
setupSession(sessionId)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSession = async (sessionId, validation) => {
|
||||||
|
try {
|
||||||
|
const client = sessions.get(sessionId)
|
||||||
|
if (!client) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client.pupPage?.removeAllListeners('close')
|
||||||
|
client.pupPage?.removeAllListeners('error')
|
||||||
|
if (validation.success) {
|
||||||
|
// Client Connected, request logout
|
||||||
|
console.log(`Logging out session ${sessionId}`)
|
||||||
|
await client.logout()
|
||||||
|
} else if (validation.message === 'session_not_connected') {
|
||||||
|
// Client not Connected, request destroy
|
||||||
|
console.log(`Destroying session ${sessionId}`)
|
||||||
|
await client.destroy()
|
||||||
|
}
|
||||||
|
// Wait 10 secs for client.pupBrowser to be disconnected before deleting the folder
|
||||||
|
let maxDelay = 0
|
||||||
|
while (client.pupBrowser.isConnected() && (maxDelay < 10)) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
maxDelay++
|
||||||
|
}
|
||||||
|
await deleteSessionFolder(sessionId)
|
||||||
|
sessions.delete(sessionId)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle session flush
|
||||||
|
const flushSessions = async (deleteOnlyInactive) => {
|
||||||
|
try {
|
||||||
|
// Read the contents of the sessions folder
|
||||||
|
const files = await fs.promises.readdir(sessionFolderPath)
|
||||||
|
// Iterate through the files in the parent folder
|
||||||
|
for (const file of files) {
|
||||||
|
// Use regular expression to extract the string from the folder name
|
||||||
|
const match = file.match(/^session-(.+)$/)
|
||||||
|
if (match) {
|
||||||
|
const sessionId = match[1]
|
||||||
|
const validation = await validateSession(sessionId)
|
||||||
|
if (!deleteOnlyInactive || !validation.success) {
|
||||||
|
await deleteSession(sessionId, validation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sessions,
|
||||||
|
setupSession,
|
||||||
|
restoreSessions,
|
||||||
|
validateSession,
|
||||||
|
deleteSession,
|
||||||
|
reloadSession,
|
||||||
|
flushSessions
|
||||||
|
}
|
||||||
46
src/utils.js
Normal file
46
src/utils.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
const axios = require('axios')
|
||||||
|
const { globalApiKey, disabledCallbacks } = require('./config')
|
||||||
|
|
||||||
|
// Trigger webhook endpoint
|
||||||
|
const triggerWebhook = (webhookURL, sessionId, dataType, data) => {
|
||||||
|
axios.post(webhookURL, { dataType, data, sessionId }, { headers: { 'x-api-key': globalApiKey } })
|
||||||
|
.catch(error => console.error('Failed to send new message webhook:', sessionId, dataType, error.message, data || ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send a response with error status and message
|
||||||
|
const sendErrorResponse = (res, status, message) => {
|
||||||
|
res.status(status).json({ success: false, error: message })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to wait for a specific item not to be null
|
||||||
|
const waitForNestedObject = (rootObj, nestedPath, maxWaitTime = 10000, interval = 100) => {
|
||||||
|
const start = Date.now()
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const checkObject = () => {
|
||||||
|
const nestedObj = nestedPath.split('.').reduce((obj, key) => obj ? obj[key] : undefined, rootObj)
|
||||||
|
if (nestedObj) {
|
||||||
|
// Nested object exists, resolve the promise
|
||||||
|
resolve()
|
||||||
|
} else if (Date.now() - start > maxWaitTime) {
|
||||||
|
// Maximum wait time exceeded, reject the promise
|
||||||
|
console.log('Timed out waiting for nested object')
|
||||||
|
reject(new Error('Timeout waiting for nested object'))
|
||||||
|
} else {
|
||||||
|
// Nested object not yet created, continue waiting
|
||||||
|
setTimeout(checkObject, interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkObject()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkIfEventisEnabled = (event) => {
|
||||||
|
return new Promise((resolve, reject) => { if (!disabledCallbacks.includes(event)) { resolve() } })
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
triggerWebhook,
|
||||||
|
sendErrorResponse,
|
||||||
|
waitForNestedObject,
|
||||||
|
checkIfEventisEnabled
|
||||||
|
}
|
||||||
80
swagger.js
Normal file
80
swagger.js
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0', autoBody: false })
|
||||||
|
|
||||||
|
const outputFile = './swagger.json'
|
||||||
|
const endpointsFiles = ['./src/routes.js']
|
||||||
|
|
||||||
|
const doc = {
|
||||||
|
info: {
|
||||||
|
title: 'WWebJS API',
|
||||||
|
description: 'API wrapper for WhatsAppWebJS'
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: '',
|
||||||
|
description: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'http://localhost:3000',
|
||||||
|
description: 'localhost'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
securityDefinitions: {
|
||||||
|
apiKeyAuth: {
|
||||||
|
type: 'apiKey',
|
||||||
|
in: 'header',
|
||||||
|
name: 'x-api-key'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
produces: ['application/json'],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
name: 'Session',
|
||||||
|
description: 'Handling multiple sessions logic, creation and deletion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Client',
|
||||||
|
description: 'All functions related to the client'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Message',
|
||||||
|
description: 'May fail if the message is too old (Only from the last 100 Messages of the given chat)'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
definitions: {
|
||||||
|
StartSessionResponse: {
|
||||||
|
success: true,
|
||||||
|
message: 'Session initiated successfully'
|
||||||
|
},
|
||||||
|
StatusSessionResponse: {
|
||||||
|
success: true,
|
||||||
|
state: 'CONNECTED',
|
||||||
|
message: 'session_connected'
|
||||||
|
},
|
||||||
|
RestartSessionResponse: {
|
||||||
|
success: true,
|
||||||
|
message: 'Restarted successfully'
|
||||||
|
},
|
||||||
|
TerminateSessionResponse: {
|
||||||
|
success: true,
|
||||||
|
message: 'Logged out successfully'
|
||||||
|
},
|
||||||
|
TerminateSessionsResponse: {
|
||||||
|
success: true,
|
||||||
|
message: 'Flush completed successfully'
|
||||||
|
},
|
||||||
|
ErrorResponse: {
|
||||||
|
success: false,
|
||||||
|
error: 'Some server error'
|
||||||
|
},
|
||||||
|
NotFoundResponse: {
|
||||||
|
success: false,
|
||||||
|
error: 'Some server error'
|
||||||
|
},
|
||||||
|
ForbiddenResponse: {
|
||||||
|
success: false,
|
||||||
|
error: 'Invalid API key'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swaggerAutogen(outputFile, endpointsFiles, doc)
|
||||||
7643
swagger.json
Normal file
7643
swagger.json
Normal file
File diff suppressed because it is too large
Load diff
143
tests/api.test.js
Normal file
143
tests/api.test.js
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
const request = require('supertest')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
// Mock your application's environment variables
|
||||||
|
process.env.API_KEY = 'test_api_key'
|
||||||
|
process.env.SESSIONS_PATH = './sessions_test'
|
||||||
|
process.env.ENABLE_LOCAL_CALLBACK_EXAMPLE = 'TRUE'
|
||||||
|
process.env.BASE_WEBHOOK_URL = 'http://localhost:3000/localCallbackExample'
|
||||||
|
|
||||||
|
const app = require('../src/app')
|
||||||
|
jest.mock('qrcode-terminal')
|
||||||
|
|
||||||
|
let server
|
||||||
|
beforeAll(() => {
|
||||||
|
server = app.listen(3000)
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
if (fs.existsSync('./sessions_test/message_log.txt')) {
|
||||||
|
fs.writeFileSync('./sessions_test/message_log.txt', '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
server.close()
|
||||||
|
fs.rmSync('./sessions_test', { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
describe('API health checks', () => {
|
||||||
|
it('should return valid healthcheck', async () => {
|
||||||
|
const response = await request(app).get('/ping')
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.body).toEqual({ message: 'pong', success: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return a valid callback', async () => {
|
||||||
|
const response = await request(app).post('/localCallbackExample')
|
||||||
|
.set('x-api-key', 'test_api_key')
|
||||||
|
.send({ sessionId: '1', dataType: 'testDataType', data: 'testData' })
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.body).toEqual({ success: true })
|
||||||
|
|
||||||
|
expect(fs.existsSync('./sessions_test/message_log.txt')).toBe(true)
|
||||||
|
expect(fs.readFileSync('./sessions_test/message_log.txt', 'utf-8')).toEqual('{"sessionId":"1","dataType":"testDataType","data":"testData"}\r\n')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('API Authentication Tests', () => {
|
||||||
|
it('should return 403 Forbidden for invalid API key', async () => {
|
||||||
|
const response = await request(app).get('/session/start/1')
|
||||||
|
expect(response.status).toBe(403)
|
||||||
|
expect(response.body).toEqual({ success: false, error: 'Invalid API key' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail invalid sessionId', async () => {
|
||||||
|
const response = await request(app).get('/session/start/ABCD1@').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response.status).toBe(422)
|
||||||
|
expect(response.body).toEqual({ success: false, error: 'Session should be alphanumerical or -' })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should setup and terminate a client session', async () => {
|
||||||
|
const response = await request(app).get('/session/start/1').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.body).toEqual({ success: true, message: 'Session initiated successfully' })
|
||||||
|
expect(fs.existsSync('./sessions_test/session-1')).toBe(true)
|
||||||
|
|
||||||
|
const response2 = await request(app).get('/session/terminate/1').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response2.status).toBe(200)
|
||||||
|
expect(response2.body).toEqual({ success: true, message: 'Logged out successfully' })
|
||||||
|
|
||||||
|
expect(fs.existsSync('./sessions_test/session-1')).toBe(false)
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
it('should setup and flush multiple client sessions', async () => {
|
||||||
|
const response = await request(app).get('/session/start/2').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.body).toEqual({ success: true, message: 'Session initiated successfully' })
|
||||||
|
expect(fs.existsSync('./sessions_test/session-2')).toBe(true)
|
||||||
|
|
||||||
|
const response2 = await request(app).get('/session/start/3').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response2.status).toBe(200)
|
||||||
|
expect(response2.body).toEqual({ success: true, message: 'Session initiated successfully' })
|
||||||
|
expect(fs.existsSync('./sessions_test/session-3')).toBe(true)
|
||||||
|
|
||||||
|
const response3 = await request(app).get('/session/terminateInactive').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response3.status).toBe(200)
|
||||||
|
expect(response3.body).toEqual({ success: true, message: 'Flush completed successfully' })
|
||||||
|
|
||||||
|
expect(fs.existsSync('./sessions_test/session-2')).toBe(false)
|
||||||
|
expect(fs.existsSync('./sessions_test/session-3')).toBe(false)
|
||||||
|
}, 10000)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('API Action Tests', () => {
|
||||||
|
it('should setup, create at least a QR, and terminate a client session', async () => {
|
||||||
|
const response = await request(app).get('/session/start/4').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(response.body).toEqual({ success: true, message: 'Session initiated successfully' })
|
||||||
|
expect(fs.existsSync('./sessions_test/session-4')).toBe(true)
|
||||||
|
|
||||||
|
// Wait for message_log.txt to not be empty
|
||||||
|
const result = await waitForFileNotToBeEmpty('./sessions_test/message_log.txt')
|
||||||
|
.then(() => { return true })
|
||||||
|
.catch(() => { return false })
|
||||||
|
expect(result).toBe(true)
|
||||||
|
|
||||||
|
// Verify the message content
|
||||||
|
const expectedMessage = {
|
||||||
|
dataType: 'qr',
|
||||||
|
data: expect.objectContaining({ qr: expect.any(String) }),
|
||||||
|
sessionId: '4'
|
||||||
|
}
|
||||||
|
expect(JSON.parse(fs.readFileSync('./sessions_test/message_log.txt', 'utf-8'))).toEqual(expectedMessage)
|
||||||
|
|
||||||
|
const response2 = await request(app).get('/session/terminate/4').set('x-api-key', 'test_api_key')
|
||||||
|
expect(response2.status).toBe(200)
|
||||||
|
expect(response2.body).toEqual({ success: true, message: 'Logged out successfully' })
|
||||||
|
expect(fs.existsSync('./sessions_test/session-4')).toBe(false)
|
||||||
|
}, 15000)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Function to wait for a specific item to be equal a specific value
|
||||||
|
const waitForFileNotToBeEmpty = (filePath, maxWaitTime = 10000, interval = 100) => {
|
||||||
|
const start = Date.now()
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const checkObject = () => {
|
||||||
|
const filecontent = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
if (filecontent !== '') {
|
||||||
|
// Nested object exists, resolve the promise
|
||||||
|
resolve()
|
||||||
|
} else if (Date.now() - start > maxWaitTime) {
|
||||||
|
// Maximum wait time exceeded, reject the promise
|
||||||
|
console.log('Timed out waiting for nested object')
|
||||||
|
reject(new Error('Timeout waiting for nested object'))
|
||||||
|
} else {
|
||||||
|
// Nested object not yet created, continue waiting
|
||||||
|
setTimeout(checkObject, interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkObject()
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue