Compare commits
115
Commits
2022.11.11
...
2023.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e60d972bff | ||
|
|
3c4a139f52 | ||
|
|
bf615efa96 | ||
|
|
2af3940be7 | ||
|
|
64e145909e | ||
|
|
e90f34cc96 | ||
|
|
a23cf2dd26 | ||
|
|
7b623d0faa | ||
|
|
14788b1d53 | ||
|
|
e1bf8cf735 | ||
|
|
2117731b46 | ||
|
|
74f8fe1caf | ||
|
|
9388a3b277 | ||
|
|
13e7a05e1b | ||
|
|
885fa25c0c | ||
|
|
966b35094f | ||
|
|
65607bf3b3 | ||
|
|
5fcba7a7db | ||
|
|
f9ec3cb753 | ||
|
|
bf6ffd65a9 | ||
|
|
d680b6b43a | ||
|
|
3ead3a0ba3 | ||
|
|
4c5c977310 | ||
|
|
89a9ce87da | ||
|
|
52e469f6ac | ||
|
|
4f275de8d4 | ||
|
|
d9de41e350 | ||
|
|
a4c279ba25 | ||
|
|
e68282a9e4 | ||
|
|
8613206c3a | ||
|
|
31b8cde8bf | ||
|
|
2f2f9520db | ||
|
|
2501ae836e | ||
|
|
2c8bf91e77 | ||
|
|
0ac063498e | ||
|
|
2bf672f0cd | ||
|
|
ca209adca0 | ||
|
|
dd10c82c5e | ||
|
|
c3ad2a0931 | ||
|
|
30aacc8946 | ||
|
|
723a644937 | ||
|
|
165eeb5bf2 | ||
|
|
f1776d4a26 | ||
|
|
2f2a1b3182 | ||
|
|
0477cf7db5 | ||
|
|
2d7c7986aa | ||
|
|
bf84ea8fca | ||
|
|
f3ef8a7f96 | ||
|
|
ec214f610a | ||
|
|
013fd46722 | ||
|
|
32d583b9ef | ||
|
|
df6e201ba5 | ||
|
|
789b613b82 | ||
|
|
b698342db4 | ||
|
|
d853f2fe99 | ||
|
|
20d66b5b31 | ||
|
|
da7264c5e8 | ||
|
|
f3299f78d9 | ||
|
|
7ef0805c91 | ||
|
|
ba1afa2569 | ||
|
|
936e2d0863 | ||
|
|
166410f66a | ||
|
|
f8c9298694 | ||
|
|
b0b642b6bb | ||
|
|
5fa20343da | ||
|
|
bbbf181011 | ||
|
|
6af547d2af | ||
|
|
d72b1822cb | ||
|
|
d34df18da8 | ||
|
|
27cb247760 | ||
|
|
7a66d6fccd | ||
|
|
f1cb372339 | ||
|
|
46d65e6fc0 | ||
|
|
6168298b5e | ||
|
|
d5fef413ca | ||
|
|
0802ee6106 | ||
|
|
65d0f87eeb | ||
|
|
488868d80e | ||
|
|
154965efcb | ||
|
|
f364f0b495 | ||
|
|
82e2f5bcb9 | ||
|
|
f7a2228925 | ||
|
|
dadc9d4efd | ||
|
|
df59867431 | ||
|
|
a7c6e9dc00 | ||
|
|
6b51cfd448 | ||
|
|
98b86ca527 | ||
|
|
9a2ef4c52a | ||
|
|
d69d4bd29d | ||
|
|
97cdc56c26 | ||
|
|
c2440b9c79 | ||
|
|
f0b69d089a | ||
|
|
ac79549934 | ||
|
|
e6b8b5e904 | ||
|
|
5a22e5b2b2 | ||
|
|
431d7bf9d9 | ||
|
|
865ea6e66b | ||
|
|
2f787d6a53 | ||
|
|
939ac28d00 | ||
|
|
76068add09 | ||
|
|
0730dd6756 | ||
|
|
64cce8e8be | ||
|
|
2d12dc0f3e | ||
|
|
82a60e99cc | ||
|
|
1cbd18db76 | ||
|
|
48ccd636cd | ||
|
|
573dd0da7e | ||
|
|
df8583801f | ||
|
|
ff2b434133 | ||
|
|
30cae74d50 | ||
|
|
a896d32d37 | ||
|
|
d9d4527894 | ||
|
|
e93caac34b | ||
|
|
cd5182f8ee | ||
|
|
b185c02252 |
No files matched your search
+1
-1
@@ -1,5 +1,5 @@
|
||||
Dockerfile
|
||||
.sessions
|
||||
.*sessions
|
||||
files
|
||||
venv
|
||||
.wwebjs_auth
|
||||
|
||||
@@ -13,6 +13,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: 'amd64'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
@@ -33,12 +38,11 @@ jobs:
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: 'amd64'
|
||||
push: ${{ github.ref_type == 'tag' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha
|
||||
@@ -29,8 +29,8 @@ jobs:
|
||||
- name: Upload folder
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: docs/site/public
|
||||
name: public
|
||||
path: public
|
||||
|
||||
deploy-github-pages:
|
||||
needs: build
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
.sessions
|
||||
.*sessions
|
||||
tokens
|
||||
files
|
||||
venv
|
||||
.wwebjs_auth
|
||||
|
||||
+21
-6
@@ -1,4 +1,19 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v2.7.1'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [javascript, ts]
|
||||
files: |
|
||||
(?x)^(
|
||||
src/.*|
|
||||
)$
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
|
||||
rev: v1.1.2
|
||||
hooks:
|
||||
- id: markdown-toc
|
||||
name: README.md
|
||||
files: ^README.md$
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: no-plus-in-core
|
||||
@@ -6,9 +21,9 @@ repos:
|
||||
language: pygrep
|
||||
entry: 'plus'
|
||||
files: |
|
||||
(?x)^(
|
||||
src/api/.*|
|
||||
src/core/.*|
|
||||
src/structures/.*|
|
||||
src/config.service.ts|
|
||||
)$
|
||||
(?x)^(
|
||||
src/api/.*|
|
||||
src/core/.*|
|
||||
src/structures/.*|
|
||||
src/config.service.ts|
|
||||
)$
|
||||
@@ -0,0 +1,2 @@
|
||||
# Changelog
|
||||
You can find changelog on [the changelog page](https://waha.devlike.pro/docs/help/changelog/)
|
||||
+37
-8
@@ -2,12 +2,22 @@
|
||||
# Build
|
||||
#
|
||||
FROM node:18 as build
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
|
||||
|
||||
# Install https://github.com/WhiskeySockets/Baileys
|
||||
WORKDIR /
|
||||
RUN git clone https://github.com/WhiskeySockets/Baileys WhiskeySockets-Baileys && \
|
||||
cd WhiskeySockets-Baileys && \
|
||||
git checkout e3ac5f596f16b706420ed016a349fa75bd3a157f && \
|
||||
yarn install && \
|
||||
yarn build:all
|
||||
|
||||
# npm packages
|
||||
WORKDIR /src
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm install
|
||||
COPY yarn.lock .
|
||||
RUN yarn install
|
||||
|
||||
# App
|
||||
WORKDIR /src
|
||||
@@ -18,17 +28,36 @@ RUN npm run build && find ./dist -name "*.d.ts" -delete
|
||||
# Final
|
||||
#
|
||||
FROM node:18 as release
|
||||
ENV PUPPETEER_SKIP_DOWNLOAD=True
|
||||
ARG USE_BROWSER=chromium
|
||||
|
||||
# Install Chrome
|
||||
# Install fonts
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y wget gnupg \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
|
||||
&& apt-get install -y fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Chromium
|
||||
RUN if [ "$USE_BROWSER" = "chromium" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y chromium \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# Install Chrome
|
||||
RUN if [ "$USE_BROWSER" = "chrome" ]; then \
|
||||
apt-get update \
|
||||
&& apt-get install -y wget gnupg \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
# Attach sources, install packages
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
build:
|
||||
docker build . -t devlikeapro/whatsapp-http-api
|
||||
|
||||
run:
|
||||
docker run \
|
||||
--rm -d \
|
||||
-p 127.0.0.1:3000:3000/tcp \
|
||||
-v `pwd`/.sessions:/app/.sessions \
|
||||
-e WHATSAPP_HOOK_URL=https://httpbin.org/post \
|
||||
-e WHATSAPP_HOOK_EVENTS=* \
|
||||
--name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
build-plus:
|
||||
docker build . -t devlikeapro/whatsapp-http-api-plus
|
||||
|
||||
stop:
|
||||
docker stop whatsapp-http-api
|
||||
@@ -21,3 +15,7 @@ push:
|
||||
|
||||
update-swagger:
|
||||
wget http://localhost:3000/-json -O ./docs/site/static/swagger/openapi.json
|
||||
|
||||
update-dependencies:
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn add whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js
|
||||
. ${NVM_DIR}/nvm.sh && nvm exec yarn add venom-bot
|
||||
@@ -9,9 +9,33 @@
|
||||
- Documentation: [https://waha.devlike.pro/](https://waha.devlike.pro/)
|
||||
- Swagger Example: [https://waha.devlike.pro/swagger](https://waha.devlike.pro/swagger)
|
||||
|
||||
## Features
|
||||
# Tables of Contents
|
||||
|
||||
### Messages
|
||||
<!-- toc -->
|
||||
|
||||
- [Features](#features)
|
||||
* [Messages](#messages)
|
||||
* [Sessions](#sessions)
|
||||
* [Security](#security)
|
||||
* [Updates](#updates)
|
||||
* [Groups](#groups)
|
||||
* [Contacts](#contacts)
|
||||
- [Quick start](#quick-start)
|
||||
* [Requirements](#requirements)
|
||||
* [Send your first message](#send-your-first-message)
|
||||
+ [1. Download image](#1-download-image)
|
||||
+ [2. Run WhatsApp HTTP API](#2-run-whatsapp-http-api)
|
||||
+ [3. Start a new session](#3-start-a-new-session)
|
||||
+ [4. Get and scan QR](#4-get-and-scan-qr)
|
||||
+ [5. Get the screenshot](#5-get-the-screenshot)
|
||||
+ [6. Send a text message](#6-send-a-text-message)
|
||||
* [What is next?](#what-is-next)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# Features
|
||||
|
||||
## Messages
|
||||
- Send messages
|
||||
- Receive messages
|
||||
- Message replies
|
||||
@@ -24,34 +48,159 @@
|
||||
- Receive media (images/audio/video/documents)
|
||||
- Receive messages - webhook retries
|
||||
|
||||
### Sessions
|
||||
## Sessions
|
||||
- Multi Device
|
||||
- Get the screenshot
|
||||
- Session saving (don't have to scan QR on every restart)
|
||||
- Single WhatsApp account running inside one container
|
||||
- Multiple WhatsApp account running inside one container
|
||||
|
||||
### Security
|
||||
## Security
|
||||
- Swagger panel authentication
|
||||
- API authentication
|
||||
|
||||
### Updates
|
||||
## Updates
|
||||
- Bug fixes and updates come as soon as they are implemented.
|
||||
- One month delay in bug fixes and updates.
|
||||
|
||||
|
||||
### Groups [soon]
|
||||
- Join groups by invite
|
||||
## Groups
|
||||
- Create a group
|
||||
- Get invite for group
|
||||
- Modify group info (subject, description)
|
||||
- Modify group settings (send messages, edit info)
|
||||
- Add group participants
|
||||
- Kick group participants
|
||||
- Promote/demote group participants
|
||||
|
||||
### Contacts [soon]
|
||||
- Mention users
|
||||
## Contacts
|
||||
- Mute/unmute chats
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Set user status message
|
||||
|
||||
# Quick start
|
||||
|
||||
## Requirements
|
||||
|
||||
Only thing that you must have - installed docker. Please follow the original
|
||||
instruction <a href="https://docs.docker.com/get-docker/" target="_blank" rel="noopener">how to install docker -></a>.
|
||||
|
||||
When you are ready - come back and follows the below steps to send the first text message to WhatsApp via HTTP API!
|
||||
|
||||
{{< details "Why Docker?" >}}
|
||||
Docker makes it easy to ship all-in-one solution with the runtime and dependencies. You don't have to worry about
|
||||
language-specific libraries or chrome installation.
|
||||
|
||||
Also Docker makes installation and update processes so simple, just one command!
|
||||
{{< /details >}}
|
||||
|
||||
## Send your first message
|
||||
|
||||
Let's go over steps that allow you to send your first text message via WhatsApp HTTP API!
|
||||
|
||||
### 1. Download image
|
||||
|
||||
Assuming you have installed [Docker](https://docs.docker.com/get-docker/), let's download the image.
|
||||
|
||||
 For Core version the command is
|
||||
|
||||
```bash
|
||||
docker pull devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
 For Plus version, we use login to get the image before:
|
||||
|
||||
```bash
|
||||
docker login -u devlikeapro -p {PASSWORD}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
docker logout
|
||||
```
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
### 2. Run WhatsApp HTTP API
|
||||
|
||||
Run WhatsApp HTTP API:
|
||||
|
||||
```bash
|
||||
docker run -it --rm -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
Open the link in your browser [http://localhost:3000/](http://localhost:3000/) and you'll see API documentation
|
||||
(Swagger).
|
||||
|
||||

|
||||
|
||||
### 3. Start a new session
|
||||
|
||||
To start a new session you should have your mobile phone with installed WhatsApp application close to you.
|
||||
|
||||
Please go and read how what we'll need to a bit
|
||||
later:
|
||||
<a href="https://faq.whatsapp.com/381777293328336/?helpref=hc_fnav" target="_blank">
|
||||
How to log in - the instruction on WhatsApp site
|
||||
</a>
|
||||
|
||||
When your ready - find `POST /api/session/start`, click on **Try it out**, then **Execute** a bit below.
|
||||
|
||||
|
||||
The example payload:
|
||||
```json
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
By using the request with `name` values you can start multiple session (WhatsApp accounts) inside the single docker container in Plus
|
||||
 version and only one account in Core  version.
|
||||
|
||||
Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
|
||||
|
||||
### 4. Get and scan QR
|
||||
|
||||
Find `GET /api/screenshot` and execute it, it shows you QR code.
|
||||
|
||||

|
||||
|
||||
**Scan the QR with your cell phone's WhatsApp app.**
|
||||
|
||||

|
||||
|
||||
### 5. Get the screenshot
|
||||
|
||||
Execute `GET /api/screenshot` after a few seconds after scanning the QR - it'll show you the screenshot of you Whatsapp
|
||||
instance. If you can get the actual screenshot - then you're ready to start sending messages!
|
||||
|
||||

|
||||
|
||||
### 6. Send a text message
|
||||
|
||||
Let's send a text message - find `POST /api/sendText` in [swagger](http://localhost:3000/) and change `chatId` this
|
||||
way: use a phone international phone number without `+` symbol and add `@c.us` at the end.
|
||||
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
|
||||
The example payload:
|
||||
```json
|
||||
{
|
||||
"chatId": "12132132130@c.us",
|
||||
"text": "Hi there!",
|
||||
"session": "default"
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
Also, you can use `curl` and send POST request like this:
|
||||
|
||||
```bash
|
||||
# Phone without +
|
||||
export PHONE=12132132130
|
||||
curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP API\" }" -H "Content-Type: application/json" -X POST http://localhost:3000/api/sendText
|
||||
```
|
||||
|
||||
## What is next?
|
||||
[Go and read the full documentation!](https://waha.devlike.pro/docs/overview/introduction/)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
version: '3'
|
||||
services:
|
||||
whatsapp-http-api:
|
||||
image: devlikeapro/whatsapp-http-api
|
||||
container_name: whatsapp-http-api
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
- WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
- WHATSAPP_HOOK_EVENTS=message
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
restart: always
|
||||
|
||||
whatsapp-http-api-plus:
|
||||
image: devlikeapro/whatsapp-http-api-plus
|
||||
container_name: whatsapp-http-api-plus
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
volumes:
|
||||
- "./.sessions:/app/.sessions"
|
||||
environment:
|
||||
# Environment variables from https://waha.devlike.pro/docs/how-to/config/
|
||||
- WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
- WHATSAPP_HOOK_URL=https://httpbin.org/post
|
||||
- WHATSAPP_HOOK_EVENTS=message
|
||||
- WHATSAPP_DEFAULT_ENGINE=WEBJS
|
||||
- WHATSAPP_API_KEY=321
|
||||
- WHATSAPP_SWAGGER_USERNAME=admin
|
||||
- WHATSAPP_SWAGGER_PASSWORD=123
|
||||
- WHATSAPP_API_HOSTNAME=localhost
|
||||
restart: always
|
||||
@@ -1,4 +1,4 @@
|
||||
baseurl = "https://doks-child-theme.netlify.app/"
|
||||
baseurl = "https://waha.devlike.pro/"
|
||||
canonifyURLs = false
|
||||
disableAliases = true
|
||||
disableHugoGeneratorInject = true
|
||||
|
||||
@@ -71,10 +71,10 @@ footer = 'This project is not affiliated, associated, authorized, endorsed by, o
|
||||
copyRight = "Copyright (c) 2021-2023 Dev Like A Pro"
|
||||
|
||||
# Alert
|
||||
alert = false
|
||||
alertDismissable = true
|
||||
alert = true
|
||||
alertDismissable = false
|
||||
# alertText = "Introducing the Doks child theme, several DX + UX updates, and more! <a class=\"alert-link stretched-link\" href=\"https://getdoks.org/blog/doks-v0.2/\" target=\"_blank\" rel=\"noopener\">Check out Doks v0.2</a>"
|
||||
alertText = "Introducing the Doks child theme, several DX + UX updates, and more! <a class=\"alert-link stretched-link\" href=\"https://getdoks.org/blog/doks-v0.2/\">Check out Doks v0.2</a>"
|
||||
alertText = "Enjoy Core version or want to get WAHA Plus verison? <a class=\"alert-link stretched-link\" href=\"https://waha.devlike.pro/docs/how-to/plus-version/\">Support the project -></a>"
|
||||
|
||||
# Edit Page
|
||||
# repoHost [Github | Gitea | GitLab | Bitbucket | BitbucketServer ] is used for building the edit link based on git hoster
|
||||
|
||||
@@ -4,7 +4,7 @@ description: "Examples for different languages."
|
||||
lead: "Examples for different languages."
|
||||
date: 2020-10-06T08:49:15+00:00
|
||||
lastmod: 2020-10-06T08:49:15+00:00
|
||||
draft: true
|
||||
draft: false
|
||||
images: []
|
||||
weight: 900
|
||||
---
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Python"
|
||||
description: "WhatsApp HTTP API Python example."
|
||||
lead: "WhatsApp HTTP API Python example."
|
||||
description: "WhatsApp HTTP API Python examples."
|
||||
lead: "WhatsApp HTTP API Python examples."
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
@@ -12,41 +12,7 @@ parent: "help"
|
||||
weight: 630
|
||||
toc: true
|
||||
---
|
||||
We've created a few examples on how to interact with WAHA WhatsApp HTTP API with Python.
|
||||
|
||||
To show how to receive messages we've created a simple "echo" server with two functions:
|
||||
You can find the examples in [examples folder on GitHub with detailed instructions in README.md](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples/python)
|
||||
|
||||
1. When we receive a text message - just send the text back
|
||||
2. When we receive a message with a file (an image, a voice message) - download it and send the path back 
|
||||
|
||||
In order to send you messages we use **Webhooks** and configure them via environments variables. So
|
||||
what you need to
|
||||
create "echo" server is HTTP server that will receive JSON POST request and then call back WhatsApp HTTP API
|
||||
via `POST /api/sendText` endpoint with JSON body.
|
||||
|
||||
## Python echo server
|
||||
|
||||
We use Python. Feel free to create your favorite language example and contribute to the project!
|
||||
|
||||
Run "echo" server in one terminal and leave it working:
|
||||
|
||||
```bash
|
||||
# if you haven't already
|
||||
git clone https://github.com/devlikeapro/whatsapp-http-api.git
|
||||
cd whatsapp-http-api
|
||||
python -mpip install -r examples/requirements.txt
|
||||
export FLASK_APP=examples/echo.py
|
||||
flask run
|
||||
```
|
||||
|
||||
Visit [http://localhost:5000](http://localhost:5000) and check that we are good to go further.
|
||||
|
||||
Let's start WhatsApp HTTP API and configure the "on message" webhook and point it on our "http://localhost:5000/message"
|
||||
endpoint:
|
||||
|
||||
```bash
|
||||
docker run -it --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/message -e WHATSAPP_HOOK_EVENTS=* devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
Now go ahead, open the second whatsapp and send to our WhatsApp HTTP API a text message! It must reply the same text.
|
||||
|
||||
 If you try to send an image the "echo server" will send a path to the downloaded file.
|
||||
@@ -5,7 +5,7 @@ lead: "Changelog"
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
images: []
|
||||
images: [ ]
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
@@ -13,25 +13,100 @@ weight: 600
|
||||
toc: true
|
||||
---
|
||||
|
||||
## 2021.6 - June 2023
|
||||
|
||||
Improvements on session management, restarting sessions and more:
|
||||
|
||||
- Added `WHATSAPP_RESTART_ALL_SESSIONS=True`: Set this variable to `True` to start all **STOPPED** sessions after
|
||||
container restarts. By default, this variable is set to `False`.
|
||||
- Please note that this will start all **STOPPED** sessions, not just the sessions that were working before the
|
||||
restart. You can maintain the session list by
|
||||
using `POST /api/session/stop` with the `logout: True` parameter or by calling `POST /api/session/logout` to remove
|
||||
**STOPPED** sessions. You can see all sessions, including **STOPPED** sessions, in the `GET /api/sessions/all=True`
|
||||
response.
|
||||
- `WHATSAPP_START_SESSION` now support more than one session! Separate session names by command, and it'll start them
|
||||
ALWAYS after container restart `WHATSAPP_START_SESSION=session1,session2`
|
||||
- `WHATSAPP_SWAGGER_CONFIG_ADVANCED=true` enables advanced configuration options for Swagger documentation - you can customize host, port and base URL for the requests.
|
||||
Disabled by default.
|
||||
- Added `?all=true` parameter to `GET /api/session?all=True` endpoint - it'll show you ALL session, included **STOPPED
|
||||
**, so you can know which one will be restarted if you set `WHATSAPP_RESTART_ALL_SESSIONS=True` environment variable.
|
||||
- Added `POST /api/sessions/logout` that allow you to logout from session - remove saved credentials.
|
||||
- Added `logout` boolean parameter to `POST /api/sessions/stop` request that allow you to stop the session AND logout at
|
||||
the same time.
|
||||
- Added [How to deploy page ->]({{< relref "/docs/how-to/deploy" >}}) with
|
||||
[docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml) example
|
||||
- Added `engine` field in webhook payload
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
|
||||
## 2021.5 - May 2023
|
||||
|
||||
- Added new [NOWEB engine]({{< relref "/docs/how-to/engines" >}}). **NOWEB** engine does not require a browser to work
|
||||
with
|
||||
WhatsApp Web, it does so directly using a WebSocket.
|
||||
- Less CPU and RAM usage!
|
||||
- Send Locations API works!
|
||||
- Send Link Preview API works!
|
||||
- ⚠ Read the article before using it [How to avoid blocking ->]({{< relref "/docs/help/how-to-avoid-blocking" >}}).
|
||||
|
||||
## 2021.4 - March 2023
|
||||
|
||||
- Add [Groups API]({{< relref "/docs/how-to/groups" >}})
|
||||
- Use Chromium by default instead of Chrome
|
||||
|
||||
## 2021.1 - January 2023
|
||||
|
||||
- Added [Contacts API]({{< relref "/docs/how-to/contacts" >}})
|
||||
- Get all contacts
|
||||
- Get a contact
|
||||
- Get contact "about" (status)
|
||||
- Get contact profile picture
|
||||
- Check number exists (is registered in WhatsApp) - works even if the number is not in the contact list
|
||||
- Block and unblock contact
|
||||
|
||||
## 2022.12 - December 2023
|
||||
|
||||
- Added `GET /messages/` endpoint to get chat messages [#31](https://github.com/devlikeapro/whatsapp-http-api/issues/31)
|
||||
|
||||
### Security 
|
||||
|
||||
- Added `WHATSAPP_SWAGGER_USERNAME` and `WHATSAPP_SWAGGER_PASSWORD` to hide and protect swagger panel.
|
||||
|
||||
## 2022.11
|
||||
### Engine  
|
||||
WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's payloads.
|
||||
|
||||
**Please test changes in test environment before update production!!**
|
||||
|
||||
### Requests  
|
||||
### Engine 
|
||||
|
||||
1. WAHA has changed its underlying engine from Venom to Whatsapp Web.JS. It might change the response and webhook's
|
||||
payloads.
|
||||
2. Optimize CPU and memory consumption.
|
||||
|
||||
### Requests 
|
||||
|
||||
- For all `/api/session/` requests use `name` field instead of `sessionName`.
|
||||
- For all "chatting" requests use `session` field instead of `sessionName`.
|
||||
|
||||
### Sessions 
|
||||
|
||||
Now you don't have to scan QR code each time you run WAHA, WAHA saves it for you! Available only in Plus version.
|
||||
|
||||
### Authentication 
|
||||
|
||||
Now you can authenticate all requests for WAHA - use `WHATSAPP_API_KEY=secret` environment variable to set "secret key".
|
||||
|
||||
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random secret key.
|
||||
If `WHATSAPP_API_KEY` is set - requests must have `X-Api-Key` header with `secret` value, where `secret` - any random
|
||||
secret key.
|
||||
|
||||
### Webhooks 
|
||||
|
||||
### Webhooks  
|
||||
#### Configuration
|
||||
|
||||
Instead of setting each webhook via environment variables - we use two environments variables:
|
||||
|
||||
@@ -5,23 +5,45 @@ lead: "Answers to frequently asked questions."
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
images: []
|
||||
images: [ ]
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 630
|
||||
toc: true
|
||||
---
|
||||
|
||||
## How to update?
|
||||
|
||||
## How to build the newest Docker image locally
|
||||
{{< alert icon="💡" text="Please test all new versions in the development environment before updating production!" />}}
|
||||
|
||||
If the changes haven't been released yet from `main` branch - you can build your own version from the git repository
|
||||
 For **Core** version the command is
|
||||
|
||||
```bash
|
||||
git clone https://github.com/devlikeapro/whatsapp-http-api.git
|
||||
cd whatsapp-http-api
|
||||
docker build . -t devlikeapro/whatsapp-http-api
|
||||
docker pull devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
 For **Plus** version, we use login to get the image before:
|
||||
|
||||
```bash
|
||||
docker login -u devlikeapro -p {PASSWORD}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
docker logout
|
||||
```
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
## How much CPU and Memory does WhatsApp HTTP API consume?
|
||||
|
||||
The benchmark has been made on **Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz**.
|
||||
It may differ from case to case, it depends on usage pattern - how many messages you get, how many send, etc.
|
||||
|
||||
| Accounts (sessions) in the container | CPU | Memory |
|
||||
|--------------------------------------|------|--------|
|
||||
| 1 | 30% | 400MB |
|
||||
| 10 | 270% | 1.5GB |
|
||||
|
||||
The nature of WhatsApp HTTP API and its underlying engines - is to run **real** WhatsApp Web version in Chrome and
|
||||
communicate with it to prevent blocking from WhatsApp. It's the reason why it's so demanding on resources.
|
||||
|
||||
If you're looking for less resource demanded engine - [have a look at **NOWEB** engine ->]({{< relref "/docs/how-to/engines" >}})
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "How to Avoid Blocking"
|
||||
description: "How to Avoid Blocking From Whatsapp"
|
||||
date: 2020-11-12T13:26:54+01:00
|
||||
lastmod: 2020-11-12T13:26:54+01:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 610
|
||||
toc: true
|
||||
---
|
||||
|
||||
How to Avoid Blocking in WhatsApp when developing bots.
|
||||
|
||||
It's important to keep in mind that WhatsApp has strict policies in place to prevent spamming and abuse of their
|
||||
platform.
|
||||
|
||||
If you're developing a bot for WhatsApp, it's crucial to follow these guidelines to avoid getting blocked.
|
||||
|
||||
## Guidelines to Follow
|
||||
|
||||
### 1. Only Reply to Messages
|
||||
|
||||
When developing a bot for WhatsApp, you **should never initiate a conversation**.
|
||||
Instead, your bot **should only reply** to messages that it receives.
|
||||
This will prevent your bot from being flagged as spam by WhatsApp's users and algorithms.
|
||||
|
||||
### 2. Avoid Spamming and Sending Unnecessary Content
|
||||
|
||||
Sending too many messages or sending content that the user did not request can also lead to your bot being blocked. Make
|
||||
sure to only send relevant and useful information to the user.
|
||||
Additionally, do not send too many messages at once, as this can also trigger spam filters.
|
||||
|
||||
### 3. Other Considerations
|
||||
|
||||
There are other guidelines to follow when developing a bot for WhatsApp, such as avoiding the use of banned words and
|
||||
not sharing sensitive or inappropriate content.
|
||||
Make sure to read WhatsApp's policies thoroughly to ensure that your bot complies with all of their rules.
|
||||
|
||||
## How to Process Messages
|
||||
|
||||
When processing messages in your bot, it's important to follow certain steps to avoid being flagged as spam.
|
||||
Here's a recommended process to follow:
|
||||
|
||||
1. **Send seen** before processing the message. This can be done by sending a `POST /api/sendSeen/` request to
|
||||
the WAHA API.
|
||||
|
||||
2. **Start typing** before sending a message and wait for a random interval depending on the size of the message. This can
|
||||
be done by sending a `POST /api/startTyping/` request.
|
||||
|
||||
3. **Stop typing** before sending the message. This can be done by sending a `POST /api/stopTyping/` request.
|
||||
|
||||
4. **Send the text message** using the `POST /api/sendText` request.
|
||||
|
||||
By following these steps, you can ensure that your bot processes messages in a way that's compliant with WhatsApp's
|
||||
guidelines and reduces the risk of being blocked.
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: "How to Update"
|
||||
description: "Regularly update the installed WAHA to keep your bot stable, usable, and secure."
|
||||
lead: "Regularly update the installed WAHA to keep your bot stable, usable, and secure."
|
||||
date: 2020-11-12T13:26:54+01:00
|
||||
lastmod: 2020-11-12T13:26:54+01:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 610
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< alert icon="💡" text="Please test all new versions in the development environment before updating production!" />}}
|
||||
|
||||
## Core
|
||||
 For Core version the command is
|
||||
|
||||
```bash
|
||||
docker pull devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
## Plus
|
||||
 For Plus version, we use login to get the image before:
|
||||
|
||||
```bash
|
||||
docker login -u devlikeapro -p {PASSWORD}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
docker logout
|
||||
```
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
@@ -1,36 +1,133 @@
|
||||
---
|
||||
title : "Configuration"
|
||||
title: "Configuration"
|
||||
description: "Configuration"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 400
|
||||
images: [ ]
|
||||
weight: 800
|
||||
---
|
||||
|
||||
You can configure WhatsApp HTTP API behaviour via environment variables, by adding `-e WHATSAPP_VARNAME=value` at the
|
||||
begging of the command line or by using [other options](https://docs.docker.com/engine/reference/commandline/run/)
|
||||
|
||||
```bash
|
||||
docker run -it -e WHATSAPP_HOOK_EVENTS=* -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
|
||||
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
It's not necessary to always run such a long command - you can save all data in
|
||||
[docker-compose.yaml](https://github.com/devlikeapro/whatsapp-http-api/blob/core/docker-compose.yaml)
|
||||
file as described on [How to deploy page ->]({{< relref "/docs/how-to/deploy" >}}).
|
||||
|
||||
## Environment variables
|
||||
|
||||
### Common
|
||||
The following environment variables can be used to configure the WAHA:
|
||||
|
||||
- `DEBUG=1` - show debug and verbose logs, set in any value
|
||||
- `WHATSAPP_API_PORT=3000` - listen port for HTTP server (default: `3000`)
|
||||
- `WHATSAPP_API_HOSTNAME=localhost` - Hostname for HTTP server (default: `localhost`)
|
||||
- `WHATSAPP_API_KEY=mysecret` - protect the api with a secret code. If you set it - add `X-Api-Key: mysecret` to all
|
||||
requests.
|
||||
- `WHATSAPP_START_SESSION=default` - start session with the name right after launching the app
|
||||
-
|
||||
- `DEBUG=1`: Set this variable to any value to enable debug and verbose logs.
|
||||
- `WHATSAPP_API_PORT=3000`: The port number that the HTTP server will listen on. The default value is `3000`.
|
||||
- `WHATSAPP_API_HOSTNAME=localhost`: The hostname for the HTTP server. The default value is `localhost`.
|
||||
- `WHATSAPP_API_KEY=mysecret`: If you set this variable, you must include the `X-Api-Key: mysecret` header in all
|
||||
requests to the API. This will protect the API with a secret code.
|
||||
- `WHATSAPP_SWAGGER_USERNAME=admin` and `WHATSAPP_SWAGGER_PASSWORD=admin`: These variables can be used to protect the
|
||||
Swagger panel with `admin / admin` credentials. This does not affect API access.
|
||||
- `WHATSAPP_SWAGGER_CONFIG_ADVANCED=true` - enables advanced configuration options for Swagger documentation - you can customize host, port and base URL for the requests.
|
||||
Disabled by default.
|
||||
- `WHATSAPP_RESTART_ALL_SESSIONS=True`: Set this variable to `True` to start all **STOPPED** sessions after container
|
||||
restarts. By default, this variable is set to `False`.
|
||||
- Please note that this will start all **STOPPED** sessions, not just the sessions that were working before the restart. You can maintain the session list by
|
||||
using `POST /api/session/stop` with the `logout: True` parameter or by calling `POST /api/session/logout` to remove
|
||||
**STOPPED** sessions. You can see all sessions, including **STOPPED** sessions, in the `GET /api/sessions/all=True`
|
||||
response.
|
||||
- `WHATSAPP_START_SESSION=session1,session2`: This variable can be used to start sessions with the specified names right
|
||||
after launching the API. Separate session names with a comma.
|
||||
|
||||
## File storage 
|
||||
- `WHATSAPP_FILES_MIMETYPES` - download only these mimetypes from messages (download all files be default). Mimetypes
|
||||
must be separated by a comma, without spaces: `audio,image/png,image/gif`. In order to choose type use prefix (
|
||||
like `audio,image`).
|
||||
- `WHATSAPP_FILES_LIFETIME`- to keep free space files will be removed after this time (default: `180`, in seconds)
|
||||
- `WHATSAPP_FILES_FOLDER` - folder where will be stored files from chats (images, voice messages) (
|
||||
default: `/tmp/whatsapp-files`)
|
||||
### Examples
|
||||
|
||||
#### Debug Mode
|
||||
|
||||
To enable debug mode, set the `DEBUG` environment variable to any value:
|
||||
|
||||
```
|
||||
DEBUG=1
|
||||
```
|
||||
|
||||
#### Protecting the API with a Secret Code
|
||||
|
||||
To protect the API with a secret code, set the `WHATSAPP_API_KEY` environment variable to your secret code:
|
||||
|
||||
```
|
||||
WHATSAPP_API_KEY=mysecret
|
||||
```
|
||||
|
||||
You must include the `X-Api-Key: mysecret` header in all requests to the API.
|
||||
|
||||
#### Starting Sessions Automatically
|
||||
|
||||
To start sessions automatically when the API is launched, set the `WHATSAPP_START_SESSION` environment variable to a
|
||||
comma-separated list of session names:
|
||||
|
||||
```
|
||||
WHATSAPP_START_SESSION=session1,session2
|
||||
```
|
||||
|
||||
#### Restarting All Sessions
|
||||
|
||||
To start all **STOPPED** sessions after container restarts, set the `WHATSAPP_RESTART_ALL_SESSIONS` environment variable
|
||||
to `True`:
|
||||
|
||||
```
|
||||
WHATSAPP_RESTART_ALL_SESSIONS=True
|
||||
```
|
||||
|
||||
#### Protecting the Swagger Panel
|
||||
|
||||
To protect the Swagger panel with `admin / admin` credentials, set the `WHATSAPP_SWAGGER_USERNAME`
|
||||
and `WHATSAPP_SWAGGER_PASSWORD` environment variables:
|
||||
|
||||
```
|
||||
WHATSAPP_SWAGGER_USERNAME=admin
|
||||
WHATSAPP_SWAGGER_PASSWORD=admin
|
||||
```
|
||||
|
||||
## File storage variables 
|
||||
|
||||
|
||||
The following environment variables can be used to configure the file storage options for the WAHA:
|
||||
|
||||
- `WHATSAPP_FILES_MIMETYPES`: This variable can be used to download only specific mimetypes from messages. By default,
|
||||
all files are downloaded. The mimetypes must be separated by a comma, without spaces. For
|
||||
example: `audio,image/png,image/gif`. To choose a specific type, use a prefix (like `audio,image`).
|
||||
- `WHATSAPP_FILES_LIFETIME`: This variable can be used to set the time (in seconds) after which files will be removed to
|
||||
free up space. The default value is `180`.
|
||||
- `WHATSAPP_FILES_FOLDER`: This variable can be used to set the folder where files from chats (images, voice messages)
|
||||
will be stored. The default value is `/tmp/whatsapp-files`.
|
||||
|
||||
### Examples
|
||||
|
||||
#### Downloading Specific Mimetypes
|
||||
|
||||
To download only specific mimetypes from messages, set the `WHATSAPP_FILES_MIMETYPES` environment variable to a
|
||||
comma-separated list of mimetypes:
|
||||
|
||||
```
|
||||
WHATSAPP_FILES_MIMETYPES=audio,image/png,image/gif
|
||||
```
|
||||
|
||||
#### Setting the File Lifetime
|
||||
|
||||
To set the time (in seconds) after which files will be removed to free up space, set the `WHATSAPP_FILES_LIFETIME`
|
||||
environment variable:
|
||||
|
||||
```
|
||||
WHATSAPP_FILES_LIFETIME=300
|
||||
```
|
||||
|
||||
#### Setting the File Storage Folder
|
||||
|
||||
To set the folder where files from chats (images, voice messages) will be stored, set the `WHATSAPP_FILES_FOLDER`
|
||||
environment variable:
|
||||
|
||||
```
|
||||
WHATSAPP_FILES_FOLDER=/home/user/whatsapp-files
|
||||
```
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title : "Contacts"
|
||||
description: "Contacts"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 130
|
||||
---
|
||||
|
||||
Methods for contacts.
|
||||
|
||||
{{< alert icon="👉" text="WhatsApp Web does not support adding contacts, so the API doesn't support it too." />}}
|
||||
|
||||
## Get all contacts
|
||||
|
||||
Get your contacts - `GET /api/contacts/all`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "11231231231@c.us",
|
||||
"number": "11231231231",
|
||||
"name": "Contact Name",
|
||||
"pushname": "Pushname",
|
||||
"shortName": "Shortname",
|
||||
"isMe": true,
|
||||
"isGroup": false,
|
||||
"isWAContact": true,
|
||||
"isMyContact": true,
|
||||
"isBlocked": false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Get contact
|
||||
|
||||
Get contact
|
||||
|
||||
- `GET /api/contacts?contactId=11231231231&session=default`
|
||||
- `GET /api/contacts?contactId=11231231231@c.us&session=default`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "11231231231@c.us",
|
||||
"number": "11231231231",
|
||||
"name": "Contact Name",
|
||||
"pushname": "Pushname",
|
||||
"shortName": "Shortname",
|
||||
"isMe": true,
|
||||
"isGroup": false,
|
||||
"isWAContact": true,
|
||||
"isMyContact": true,
|
||||
"isBlocked": false
|
||||
}
|
||||
```
|
||||
|
||||
## Check phone number exists
|
||||
|
||||
If you want to check if phone number is registered in WhatsApp (even if the number is not in your contact list) - use
|
||||
this endpoint for that.
|
||||
- `GET /api/contacts/check-exists?phone=11231231231&session=default`
|
||||
|
||||
```json
|
||||
{
|
||||
"numberExists": true
|
||||
}
|
||||
```
|
||||
|
||||
## Get "about" contact
|
||||
|
||||
- `GET /api/contacts/about?contactId=11231231231&session=default`
|
||||
|
||||
```json
|
||||
{
|
||||
"about": "Hi, I use WhatsApp!"
|
||||
}
|
||||
```
|
||||
|
||||
## Get contact profile picture
|
||||
|
||||
- `GET /api/contacts/profile-picture?contactId=11231231231&session=default`
|
||||
|
||||
```json
|
||||
{
|
||||
"profilePictureURL": "https://example.com/profile.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
## Block (unblock) contact
|
||||
- To block contact - `POST /api/contacts/block`
|
||||
- To unblock contact - `POST /api/contacts/unblock`
|
||||
|
||||
Request:
|
||||
```json
|
||||
{
|
||||
"contactId": "11231231231",
|
||||
"session": "default"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: "Deploy"
|
||||
description: "How to deploy WAHA"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 801
|
||||
---
|
||||
|
||||
This page provides useful information to assist you in deploying the project.
|
||||
It includes tips on how to avoid running long command lines, how to view logs, and other helpful advice.
|
||||
|
||||
## How to Use Docker Compose
|
||||
|
||||
Docker Compose is a tool that allows you to define and run multi-container Docker applications. It simplifies the
|
||||
process of managing multiple containers and their dependencies. In this topic, we will walk you through how to use
|
||||
Docker Compose with the WhatsApp HTTP API.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you start, you need to have Docker and Docker Compose installed on your machine. You can download Docker from the
|
||||
official website [here](https://www.docker.com/products/docker-desktop). Docker Compose is included with Docker Desktop
|
||||
for Windows and macOS, but for Linux, you need to install it separately. You can find the installation
|
||||
instructions [here](https://docs.docker.com/compose/install/).
|
||||
|
||||
### Getting Started
|
||||
|
||||
To get started, you need to clone the docker-compose file to your local machine.
|
||||
Open your terminal and run the following command:
|
||||
|
||||
```
|
||||
wget https://raw.githubusercontent.com/devlikeapro/whatsapp-http-api/core/docker-compose.yaml
|
||||
```
|
||||
|
||||
### Running the WhatsApp HTTP API Service
|
||||
|
||||
To start the `whatsapp-http-api` service, run the following command:
|
||||
|
||||
```
|
||||
docker-compose up -d whatsapp-http-api
|
||||
```
|
||||
|
||||
This will start the container in detached mode and map port 3000 to 3000 on the host machine.
|
||||
|
||||
To stop the service, run the command:
|
||||
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
This will stop and remove the container.
|
||||
|
||||
To restart the service, run the command:
|
||||
|
||||
```
|
||||
docker-compose restart whtasapp-http-api
|
||||
```
|
||||
|
||||
This will restart the container.
|
||||
|
||||
### Viewing the Logs
|
||||
|
||||
To view the logs for the `whatsapp-http-api` service, run the command:
|
||||
|
||||
```
|
||||
docker-compose logs -f whatsapp-http-api
|
||||
```
|
||||
|
||||
This will show the logs in real-time. To view the logs for the `whatsapp-http-api-plus` service,
|
||||
replace `whatsapp-http-api` with `whatsapp-http-api-plus`.
|
||||
|
||||
To view the logs for a specific period, run the command:
|
||||
|
||||
```
|
||||
docker-compose logs --since <time>
|
||||
```
|
||||
|
||||
Replace `<time>` with the time period you want to view the logs for. For example, `docker-compose logs --since 1h` will
|
||||
show the logs for the past hour.
|
||||
|
||||
### Using the WhatsApp HTTP API Services
|
||||
|
||||
There are two services provided in the `docker-compose.yml` file - `whatsapp-http-api` and `whatsapp-http-api-plus`.
|
||||
It is important to note that these services must not be run at the same time.
|
||||
By default, the `whatsapp-http-api` service will be used.
|
||||
|
||||
To use the `whatsapp-http-api-plus` service, you need to specify it in the command.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "Engines"
|
||||
description: "Engines"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: [ ]
|
||||
weight: 800
|
||||
---
|
||||
|
||||
Under the hood **WAHA** allows you to use two different engines. You can control what you want to run by settings
|
||||
`WHATSAPP_DEFAULT_ENGINE` environment variables.
|
||||
|
||||
```bash
|
||||
docker run -it -e "WHATSAPP_DEFAULT_ENGINE=WEBJS" devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
If you have any problems with one engine - try another!
|
||||
|
||||
{{< alert icon="👉" text="API responses and webhook payloads may differ significantly! We do our best to keep requests body stable between engines." />}}
|
||||
|
||||
All engines are available in Core version 
|
||||
|
||||
## WEBJS
|
||||
|
||||
A WhatsApp API client that connects through the WhatsApp Web browser app.
|
||||
It uses Puppeteer to run a real instance of Whatsapp Web to avoid getting blocked.
|
||||
|
||||
**WAHA** uses **WhatsApp WebJS** engine by default.
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
|
||||
- https://github.com/pedroslopez/whatsapp-web.js
|
||||
|
||||
## NOWEB
|
||||
|
||||
**NOWEB** engine does not require a browser to work with WhatsApp Web, it does so directly using a WebSocket.
|
||||
Not running Chromium saves you CPU and Memory, so you can run more instances on a single server!
|
||||
|
||||
️Working with the engine requires more attention, because it's easy to be blocked with it!
|
||||
|
||||
⚠ Read the article before using **NOWEB** engine [How to avoid blocking ->]({{< relref "/docs/help/how-to-avoid-blocking" >}}).
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=NOWEB`
|
||||
- https://github.com/WhiskeySockets/Baileys
|
||||
|
||||
## VENOM
|
||||
|
||||
It's a high-performance system developed with JavaScript to create a bot for WhatsApp
|
||||
|
||||
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
|
||||
- https://github.com/orkestral/venom
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title : "Groups"
|
||||
description: "Groups"
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 140
|
||||
---
|
||||
|
||||
Methods for groups. Please look at swagger for details.
|
||||
|
||||
- `{session}` - use the session name for Whatsapp instance that you created with `POST /api/session/start` endpoint
|
||||
- `{groupId}` - group id in format `123123123123@g.us`. You can get the id in a few ways:
|
||||
- By [handling incoming message webhook]({{< relref "/docs/how-to/receive-messages" >}}).
|
||||
- By getting all groups (see below).
|
||||
- By creating a new group and saving the id.
|
||||
|
||||
## Create a new group
|
||||
|
||||
`POST /api/{session}/groups`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Group name",
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Get all groups
|
||||
|
||||
`GET /api/{session}/groups`
|
||||
|
||||
## Get the group
|
||||
|
||||
`GET /api/{session}/groups/{groupId}`
|
||||
|
||||
## Delete the group
|
||||
|
||||
`DELETE /api/{session}/groups/{groupId}`
|
||||
|
||||
## Leave the group
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/leave`
|
||||
|
||||
## Set group subject
|
||||
|
||||
Updates the group subject.
|
||||
|
||||
Returns `true` if the subject was properly updated. This can return false if the user does not have the necessary
|
||||
permissions.
|
||||
|
||||
`PUT /api/{session}/groups/{groupId}/subject`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "Group name"
|
||||
}
|
||||
```
|
||||
|
||||
## Set group description
|
||||
|
||||
Updates the group description.
|
||||
|
||||
Returns `true` if the subject was properly updated. This can return false if the user does not have the necessary
|
||||
permissions.
|
||||
|
||||
`PUT /api/{session}/groups/{groupId}/description`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Group description"
|
||||
}
|
||||
```
|
||||
|
||||
## Participants
|
||||
|
||||
### Get participants
|
||||
|
||||
`GET /api/{session}/groups/{groupId}/participants`
|
||||
|
||||
### Add participants
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/participants/add`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Remove participants
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/participants/remove`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Admin
|
||||
|
||||
### Promote to admin
|
||||
|
||||
Promote participants to admin users.
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/admin/promote`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Demote to regular users
|
||||
|
||||
Demote participants by to regular users.
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/admin/demote`
|
||||
|
||||
```json
|
||||
{
|
||||
"participants": [
|
||||
{
|
||||
"id": "123123123123@c.us"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Invite code
|
||||
|
||||
### Get invite code
|
||||
|
||||
`GET /api/{session}/groups/{groupId}/invite-code`
|
||||
|
||||
Then you can put it in the url `https://chat.whatsapp.com/{inviteCode}` and send it to contacts.
|
||||
|
||||
### Revoke invite code
|
||||
|
||||
Invalidates the current group invite code and generates a new one.
|
||||
|
||||
`POST /api/{session}/groups/{groupId}/invite-code/revoke`
|
||||
|
||||
@@ -9,28 +9,18 @@ images: []
|
||||
weight: 900
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**WAHA** is distributed in two versions:
|
||||
|
||||
1. Core  the basic version that meet almost 80% people’s needs. 100% free and open source.
|
||||
2. Plus  the version with advanced messages, security, and reliability features.
|
||||
|
||||
Plus version - available through **donations** - either one time or subscription.
|
||||
**If you enjoy Core  version and would like to support the project** - you can subscribe on Core level.
|
||||
It's not expensive, but it supports the project a lot!
|
||||
- [Boosty ->](https://boosty.to/wa-http-api)
|
||||
- [Patreon ->](https://patreon.com/wa_http_api)
|
||||
|
||||
**It doesn't require monthly subscriptions, once installed on your server - it always works!**
|
||||
|
||||
{{< alert icon="💡" text="No licence checks or expiration on already installed instances!" />}}
|
||||
|
||||
## Donations
|
||||
We do not sell **WAHA**, it's not a purchase. It's a donation.
|
||||
|
||||
We treat our projects like art. You donate to the project and get a bit more from it as a thank-you. 😊
|
||||
|
||||
Treat **WAHA Plus** it as an additional episode, behind-the-scenes videos, our nude photos 😊
|
||||
|
||||
{{< alert icon="💡" text="WAHA does not have license checks and expiration - because art can not expire! Enjoy it till it works!" />}}
|
||||
|
||||
We donate most of the donations down to the stream to the underlying libraries and tools - JS-community, Node, other libraries authors, personal contributors.
|
||||
By donating to the **WAHA** you donate to the Open Source world!
|
||||
|
||||
## Features
|
||||
|
||||
@@ -42,8 +32,7 @@ The Plus additional features are:
|
||||
4. [**Security** →]({{< relref "/docs/how-to/security" >}})
|
||||
5. [**Saving sessions** →]({{< relref "/docs/how-to/sessions" >}})
|
||||
6. [**Support multiple sessions** →]({{< relref "/docs/how-to/sessions" >}})
|
||||
7. **Instant updates** - no one month delay in getting bugfixes or new features
|
||||
8. **Priority in support** - on both bugs and features
|
||||
7. **Priority in support** - on both bugs and features
|
||||
|
||||
## Differences
|
||||
|
||||
@@ -51,16 +40,35 @@ What is the difference between **WAHA Plus** and other SaaS solutions for WhatsA
|
||||
|
||||
| | WAHA Plus | Others |
|
||||
|--------------------|:-------------------------------------------------:|----------------------------------------------------------------------------------:|
|
||||
| **Sessions** | ✔️ No limits! | ➖ One account for $50/month. |
|
||||
| **Sessions** | ✔️ No limits on **accounts** or **servers**! | ➖ One account for $50/month. |
|
||||
| **Infrastructure** | ✔️ On-Premise - your server, your infrastructure! | ➖ Insecure cloud solution. |
|
||||
| **Security** | ✔️ No data leaks! | ➖ You have to give access to customers' data - phone number, name, your messages. |
|
||||
| **License Term** | ✔️ No license checks! | ➖ One month |
|
||||
| **License Term** | ✔️ No license checks! | ➖ One month |
|
||||
| **Message price** | ✔️ All messages are Free! | ➖ Some solutions have a price for messages - $1/100 messages. |
|
||||
| **Expenses** | ✔️ one-time $69 **donation**! | ➖ Monthly payment starts from $50 |
|
||||
| **Expenses** | ✔️ $19 **donation**! | ➖ Monthly payment starts from $50 |
|
||||
|
||||
## Donations
|
||||
|
||||
WAHA Plus version is available through **donations** (subscriptions).
|
||||
|
||||
**It doesn't require monthly subscriptions, once installed on your server - it always works!**
|
||||
(until WhatsApp made backward-incompatible changes, and you have to update the image)
|
||||
|
||||
{{< alert icon="💡" text="No licence checks or expiration on already installed instances!" />}}
|
||||
We do not sell **WAHA**, it's not a purchase. It's a donation.
|
||||
|
||||
We treat our projects like art. You donate to the project and get a bit more from it as a thank-you. 😊
|
||||
|
||||
Treat **WAHA Plus** it as an additional episode, behind-the-scenes videos, our nude photos 😊
|
||||
|
||||
{{< alert icon="💡" text="WAHA does not have license checks and expiration - because art can not expire! Enjoy it till it works!" />}}
|
||||
|
||||
We donate most of the donations down to the stream to the underlying libraries and tools - JS-community, Node, other libraries authors, personal contributors.
|
||||
By donating to the **WAHA** you donate to the Open Source world!
|
||||
|
||||
## Get Plus 
|
||||
|
||||
For $69 one-time donation you get:
|
||||
For $19 one-time donation you get:
|
||||
1. **Secret password** for Docker Hub to download Plus image: `devlikeapro/whatsapp-http-api-plus`.
|
||||
2. **All updates for one month** for the Plus image.
|
||||
3. **NO LICENSE CHECKS** on installed instances!
|
||||
@@ -71,9 +79,9 @@ For $69 one-time donation you get:
|
||||
### Boosty
|
||||
We use Boosty to get donations (Patreon analogue) - [https://boosty.to/wa-http-api](https://boosty.to/wa-http-api).
|
||||
|
||||
Go ahead and get your monthly password to get no license expiration WhatsApp HTTP API - either subscribe or use one time donation!
|
||||
Go ahead and get your monthly password to get no license expiration WhatsApp HTTP API!
|
||||
|
||||
{{< alert icon="💡" text="If you stay subscribed - the donation amount will never change for you and always be $69 to get monthly updates!" />}}
|
||||
{{< alert icon="💡" text="If you stay subscribed - the donation amount will never change for you and always be $19 to get monthly updates!" />}}
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -6,20 +6,29 @@ date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 300
|
||||
weight: 120
|
||||
---
|
||||
|
||||
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages or events from WhatsApp to your
|
||||
The project uses [Webhooks](https://en.wikipedia.org/wiki/Webhook) to send the messages and events from WhatsApp to your
|
||||
application.
|
||||
|
||||
## Webhooks  
|
||||
Webhooks are a way for two different applications to communicate with each other in real-time.
|
||||
When a certain event happens in one application, it sends a message to another application through a webhook URL.
|
||||
The receiving application can then take action based on the information received.
|
||||
|
||||
To receive incoming messages with webhooks,
|
||||
you first need to set up a webhook URL in your application and pass it to `WHATSAPP_HOOK_URL` environment variable.
|
||||
This URL is where WhatsApp will send incoming messages and other events -
|
||||
define which events you want to receive with `WHATSAPP_HOOK_EVENTS` environment variable.
|
||||
|
||||
## Webhooks 
|
||||
|
||||
You can configure where you want to receive events in environment variables:
|
||||
|
||||
- `WHATSAPP_HOOK_URL=https://httpbin.org/post` - to set up a URL for the webhook
|
||||
- `WHATSAPP_HOOK_EVENTS=message,message.any,state.change,group.join,group.leave` - specify events. DO NOT specify all of
|
||||
them, it's too heavy payload, choose the right for you.
|
||||
- `WHATSAPP_HOOK_EVENTS=*` - subscribe to all events. It's not recommended for production, but it's fine for
|
||||
- `WHATSAPP_HOOK_EVENTS=*` - subscribe to all events. It's not recommended for production, but it's fine for
|
||||
development.
|
||||
|
||||
On the URL that you set via `WHATSAPP_HOOK_URL` you receive JSON-data with following format:
|
||||
@@ -27,6 +36,8 @@ On the URL that you set via `WHATSAPP_HOOK_URL` you receive JSON-data with follo
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"engine": "WEBJS",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
@@ -37,12 +48,25 @@ Below the list of all events that WhatsApp HTTP API sends to your.
|
||||
|
||||
{{< alert icon="👉" text="If you want to look at a webhook body - use the url `https://httpbin.org/post`" />}}
|
||||
|
||||
Run the bellow command and see look at the logs - it prints body request for all events that happen in your WhatsApp!
|
||||
|
||||
```bash
|
||||
docker run -it -e WHATSAPP_HOOK_EVENTS=* -e WHATSAPP_HOOK_URL=https://httpbin.org/post devlikeapro/whatsapp-http-api
|
||||
docker run -it -e "WHATSAPP_HOOK_EVENTS=*" -e WHATSAPP_HOOK_URL=https://httpbin.org/post -p 3000:3000 devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
### Examples
|
||||
Here's few examples of how to handle webhook in different languages:
|
||||
1. [Python guide]({{< relref "/docs/examples/python" >}})
|
||||
|
||||
## Events  
|
||||
**Do you use another language?**
|
||||
|
||||
Please create a short guide how to handle webhook and send message after you finish your setup!
|
||||
You can create a pull request with your favorite language in the [GitHub, in examples folder](https://github.com/devlikeapro/whatsapp-http-api/tree/core/examples).
|
||||
|
||||
Just put the code and **README.md** file in the folder - we'll do the rest!
|
||||
|
||||
|
||||
## Events 
|
||||
|
||||
### message
|
||||
|
||||
@@ -51,6 +75,7 @@ Incoming message (text/audio/files)
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
@@ -113,6 +138,7 @@ Fired on all message creations, including your own. The payload is the same as f
|
||||
```json
|
||||
{
|
||||
"event": "message.any",
|
||||
"session": "default",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
@@ -122,6 +148,7 @@ Fired on all message creations, including your own. The payload is the same as f
|
||||
```json
|
||||
{
|
||||
"event": "message.ack",
|
||||
"session": "default",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
@@ -131,6 +158,7 @@ Fired on all message creations, including your own. The payload is the same as f
|
||||
```json
|
||||
{
|
||||
"event": "state.change",
|
||||
"session": "default",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
@@ -140,6 +168,7 @@ Fired on all message creations, including your own. The payload is the same as f
|
||||
```json
|
||||
{
|
||||
"event": "group.join",
|
||||
"session": "default",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
@@ -149,6 +178,7 @@ Fired on all message creations, including your own. The payload is the same as f
|
||||
```json
|
||||
{
|
||||
"event": "group.left",
|
||||
"session": "default",
|
||||
"payload": {}
|
||||
}
|
||||
```
|
||||
@@ -163,6 +193,7 @@ For example, you can get the webhook like this with `mediaUrl` value (we've skip
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
@@ -179,4 +210,13 @@ To configure the url you can use environment variables `WHATSAPP_API_HOSTNAME` a
|
||||
|
||||
## Webhook retries 
|
||||
|
||||
**WAHA** retries to reach your webhook URL 15 times and with 2 seconds.
|
||||
**WAHA** retries to reach your webhook URL 15 times and with 2 seconds delay between attempts.
|
||||
|
||||
## Get messages
|
||||
You can read messages from the history by using `GET /api/messages` endpoint.
|
||||
|
||||
```bash
|
||||
curl -X 'GET' \
|
||||
'http://localhost:3000/api/messages?chatId=11111111111%40c.us&limit=1000&session=default' \
|
||||
-H 'accept: application/json'
|
||||
```
|
||||
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 800
|
||||
weight: 700
|
||||
---
|
||||
{{< alert icon="💡" text="Do not expose WhatsApp HTTP API on public networks!" />}}
|
||||
|
||||
@@ -15,19 +15,35 @@ We do not recommend exposing the API on any public networks!
|
||||
Either protect the API with [Api Key](https://www.fortinet.com/resources/cyberglossary/api-key) or deny access by using
|
||||
firewalls.
|
||||
|
||||
## Swagger Security 
|
||||
If you want to hide the project Swagger panel under the password - run the following command to hide under `admin/admin`
|
||||
login and password.
|
||||
|
||||
```bash
|
||||
docker run -it -e WHATSAPP_SWAGGER_USERNAME=admin -eWHATSAPP_SWAGGER_PASSWORD=admin devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
Open http://localhost:3000/ and enter `admin / admin` in the inputs:
|
||||
|
||||

|
||||
|
||||
{{< alert icon="👉" text="Protecting Swagger under the password does not protect your API from other request! Use both techniques to protect your API and Swagger!" />}}
|
||||
|
||||
## API security 
|
||||
|
||||
You can protect the API by requiring Api Key in a request's headers.
|
||||
|
||||
{{< alert icon="👉" text="Api Key does not hide your Swagger documentation. Please have a look at the previous section to find how to hide Swagger under the password." />}}
|
||||
|
||||
### Set Api Key
|
||||
|
||||
Set `WHATSAPP_API_KEY=yoursecretkey` environment variable for that:
|
||||
|
||||
```bash
|
||||
docker run -it -e WHATSAPP_API_KEY=yoursecretkey devlikeapro/whatsapp-http-api
|
||||
docker run -it -e WHATSAPP_API_KEY=yoursecretkey devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
### Swagger
|
||||
### Use Api-Key in Swagger
|
||||
|
||||
After you set api key - to authorize on swagger use **Authorize** button at the top:
|
||||

|
||||
@@ -49,5 +65,3 @@ headers = {
|
||||
requests.get("http://localhost:3000/api/sessions", headers=headers)
|
||||
```
|
||||
|
||||
## Swagger Security 
|
||||
If you want to hide under a password the swagger UI - please create an issue on GitHub, we'll do it!
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 200
|
||||
weight: 110
|
||||
---
|
||||
On your swagger documentation [http://localhost:3000/](http://localhost:3000/)
|
||||
or in [the example swagger](/swagger) you can find:
|
||||
@@ -28,7 +28,7 @@ Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
- **Groups** use random number with `@g.us` at the end.
|
||||
|
||||
## Send text  
|
||||
## Send text 
|
||||
To send text message - use `POST /api/sendText` with example payload.
|
||||
```json
|
||||
{
|
||||
@@ -38,7 +38,7 @@ To send text message - use `POST /api/sendText` with example payload.
|
||||
}
|
||||
```
|
||||
|
||||
## Reply on message  
|
||||
## Reply on message 
|
||||
To reply on a message - use `POST /api/reply` with example payload.
|
||||
```json
|
||||
{
|
||||
@@ -52,10 +52,10 @@ To reply on a message - use `POST /api/reply` with example payload.
|
||||
### Reply files 
|
||||
WAHA does not support reply with files (images, voice, etc). If you're interested in it - please create an issue in GitHub.
|
||||
|
||||
## Add a reaction  
|
||||
## Add a reaction 
|
||||
Use `PUT /api/reaction` method to set reaction to a message.
|
||||
|
||||
{{< alert icon="👉" text="Reactoin API uses PUT, not POST request! Please make sure you send right request." />}}
|
||||
{{< alert icon="👉" text="Reaction API uses PUT, not POST request! Please make sure you send right request." />}}
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
weight: 800
|
||||
weight: 200
|
||||
---
|
||||
|
||||
## Saving session 
|
||||
@@ -26,7 +26,7 @@ to the container. WAHA stores authentication information in the directory and re
|
||||
|
||||
The full command would be:
|
||||
```bash
|
||||
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
### Remote storage 
|
||||
@@ -69,11 +69,26 @@ The response:
|
||||
|
||||
In order to stop a new session - call `POST /api/sessions/stop`
|
||||
|
||||
{{< alert icon="👉" text="The stop request does not log out the account. Manually remove the session storage." />}}
|
||||
{{< alert icon="👉" text="The stop request does not log out the account by default. Set 'logout' field to 'true'." />}}
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default"
|
||||
"name": "default",
|
||||
"logout": true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Logout
|
||||
|
||||
In order to log out the session - call `POST /api/sessions/logout`
|
||||
|
||||
{{< alert icon="👉" text="You must stop session first." />}}
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default",
|
||||
"logout": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -22,7 +22,22 @@ toc: true
|
||||
|
||||
{{< alert icon="👉" text="Run WhatsApp HTTP API in less than 5 minutes!" />}}
|
||||
|
||||
Go straight to the [Quick Start →]({{< relref "quick-start" >}})
|
||||
We will guide you through the necessary steps to successfully send your first text message using WhatsApp HTTP API in
|
||||
[Quick Start →]({{< relref "quick-start" >}}).
|
||||
|
||||
If you find it easier to follow a video tutorial rather than reading through text, we've got you covered!
|
||||
A 3-minute video tutorial is available to help you navigate WhatsApp HTTP API smoothly.
|
||||
|
||||
<div class="d-flex justify-content-center my-4">
|
||||
<iframe
|
||||
width="560" height="315"
|
||||
src="https://www.youtube.com/embed/RFerMyAUPRg"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Features
|
||||
WAHA is distributed in two versions:
|
||||
@@ -32,21 +47,21 @@ WAHA is distributed in two versions:
|
||||
Here's the available features:
|
||||
|
||||
### Messages
|
||||
- Send messages  
|
||||
- Receive messages  
|
||||
- Message replies  
|
||||
- Send location  
|
||||
- Send buttons  
|
||||
- Receive location  
|
||||
- React to messages  
|
||||
- Send messages 
|
||||
- Receive messages 
|
||||
- Message replies 
|
||||
- Send location 
|
||||
- Send buttons 
|
||||
- Receive location 
|
||||
- React to messages 
|
||||
- Send media (images/documents/files) 
|
||||
- Send voice messages 
|
||||
- Receive media (images/audio/video/documents) 
|
||||
- Receive messages - webhook retries 
|
||||
|
||||
### Sessions
|
||||
- Multi Device  
|
||||
- Get the screenshot  
|
||||
- Multi Device 
|
||||
- Get the screenshot 
|
||||
- Session saving (don't have to scan QR on every restart) 
|
||||
- Session saving on remote storage (database, redis) 
|
||||
- Single WhatsApp account running inside one container 
|
||||
@@ -54,26 +69,21 @@ Here's the available features:
|
||||
|
||||
### Security
|
||||
- API authentication 
|
||||
- Swagger panel authentication 
|
||||
- Swagger panel authentication 
|
||||
|
||||
### Updates
|
||||
- Bug fixes and updates come as soon as they are implemented. 
|
||||
- One month delay in bug fixes and updates. 
|
||||
- Bug fixes and updates come as soon as they are implemented. 
|
||||
|
||||
|
||||
### Groups 
|
||||
- Join groups by invite
|
||||
### Groups 
|
||||
- Create a group
|
||||
- Get invite for group
|
||||
- Modify group info (subject, description)
|
||||
- Modify group settings (send messages, edit info)
|
||||
- Add group participants
|
||||
- Kick group participants
|
||||
- Promote/demote group participants
|
||||
|
||||
### Contacts 
|
||||
- Mention users
|
||||
### Contacts 
|
||||
- Mute/unmute chats
|
||||
- Block/unblock contacts
|
||||
- Get contact info
|
||||
- Get profile pictures
|
||||
- Set user status message
|
||||
@@ -13,7 +13,8 @@ weight: 110
|
||||
toc: true
|
||||
---
|
||||
|
||||
On this page you're going to install WAHA, run it, authenticate the client using QR code and send the first text message to WhatsApp!
|
||||
On this page you're going to install WAHA, run it, authenticate the client using QR code and send the first text message
|
||||
to WhatsApp!
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -31,7 +32,21 @@ Also Docker makes installation and update processes so simple, just one command!
|
||||
|
||||
## Send your first message
|
||||
|
||||
Let's go over steps that allow you to send your first text message via WhatsApp HTTP API!
|
||||
We will guide you through the necessary steps to successfully send your first text message using WhatsApp HTTP API.
|
||||
|
||||
If you find it easier to follow a video tutorial rather than reading through text, we've got you covered!
|
||||
A 3-minute video tutorial is available to help you navigate WhatsApp HTTP API smoothly.
|
||||
|
||||
<div class="d-flex justify-content-center my-4">
|
||||
<iframe
|
||||
width="560" height="315"
|
||||
src="https://www.youtube.com/embed/RFerMyAUPRg"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
### 1. Download image
|
||||
|
||||
@@ -53,12 +68,34 @@ docker logout
|
||||
|
||||
Read more about how to get `PASSWORD` for [Plus Version →]({{< relref "plus-version" >}})
|
||||
|
||||
#### 1.1 Download image on ARM
|
||||
|
||||
If you're using ARM (like Apple Silicon, Apple M1, etc) - use following commands to download the image
|
||||
|
||||
 For Core version the command is
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/whatsapp-http-api:arm
|
||||
# Rename it, so you can use devlikeapro/whatsapp-http-api image in other place
|
||||
docker tag devlikeapro/whatsapp-http-api:arm devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
 For Plus version the command is:
|
||||
```bash
|
||||
docker login -u devlikeapro -p {PASSWORD}
|
||||
docker pull devlikeapro/whatsapp-http-api-plus:arm
|
||||
docker logout
|
||||
|
||||
# Rename it, so you can use devlikeapro/whatsapp-http-api image in other place
|
||||
docker tag devlikeapro/whatsapp-http-api-plus:arm devlikeapro/whatsapp-http-api
|
||||
```
|
||||
|
||||
### 2. Run WhatsApp HTTP API
|
||||
|
||||
Run WhatsApp HTTP API:
|
||||
|
||||
```bash
|
||||
docker run -it --rm -v `pwd`/tokens:/app/tokens -p 127.0.0.1:3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
docker run -it --rm -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
@@ -83,8 +120,8 @@ How to log in - the instruction on WhatsApp site
|
||||
|
||||
When your ready - find `POST /api/session/start`, click on **Try it out**, then **Execute** a bit below.
|
||||
|
||||
|
||||
The example payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "default"
|
||||
@@ -93,7 +130,8 @@ The example payload:
|
||||
|
||||

|
||||
|
||||
By using the request with `name` values you can start multiple session (WhatsApp accounts) inside the single docker container in Plus
|
||||
By using the request with `name` values you can start multiple session (WhatsApp accounts) inside the single docker
|
||||
container in Plus
|
||||
 version and only one account in Core  version.
|
||||
|
||||
Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
|
||||
@@ -123,6 +161,7 @@ way: use a phone international phone number without `+` symbol and add `@c.us` a
|
||||
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
|
||||
|
||||
The example payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"chatId": "12132132130@c.us",
|
||||
@@ -142,6 +181,7 @@ curl -d "{\"chatId\": \"${PHONE}@c.us\", \"text\": \"Hello from WhatsApp HTTP AP
|
||||
```
|
||||
|
||||
## What is next?
|
||||
|
||||
- Right now you've sent simple text message, but you can send more! [Send messages →]({{< relref "/docs/how-to/send-messages" >}})
|
||||
- Read how to receive and response to messages [Receive messages →]({{< relref "/docs/how-to/receive-messages" >}})
|
||||
- Make sure your [API is properly protected ->]({{< relref "/docs/how-to/security" >}})
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title : "Modern documentatie-thema"
|
||||
description: "Doks is een Hugo-thema waarmee je moderne documentatie-websites kunt bouwen die veilig, snel en klaar voor SEO zijn — standaard."
|
||||
lead: "Doks is een Hugo-thema waarmee je moderne documentatie-websites kunt bouwen die veilig, snel en klaar voor SEO zijn — standaard."
|
||||
date: 2020-10-06T08:47:36+00:00
|
||||
lastmod: 2020-10-06T08:47:36+00:00
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: "Contact"
|
||||
description: "Drop us an email."
|
||||
date: 2020-08-27T19:25:12+02:00
|
||||
lastmod: 2020-08-27T19:25:12+02:00
|
||||
draft: true
|
||||
images: []
|
||||
---
|
||||
|
||||
{{< email user="hello" domain="getdoks.org" >}}
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
title: "Contributors"
|
||||
description: "The Doks contributors."
|
||||
date: 2020-10-06T08:50:29+00:00
|
||||
lastmod: 2020-10-06T08:50:29+00:00
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
|
||||
The Doks contributors.
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: "Henk Verlinde"
|
||||
description: "Creator of Hyas."
|
||||
date: 2020-10-06T08:50:45+00:00
|
||||
lastmod: 2020-10-06T08:50:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
|
||||
Creator of Hyas.
|
||||
|
||||
[@HenkVerlinde](https://twitter.com/henkverlinde)
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title : "Docs"
|
||||
description: "Docs Doks."
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:23+00:00
|
||||
lastmod: 2020-10-06T08:48:23+00:00
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title: "Help"
|
||||
description: "Help Doks."
|
||||
lead: ""
|
||||
date: 2020-10-06T08:49:15+00:00
|
||||
lastmod: 2020-10-06T08:49:15+00:00
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
title: "FAQ"
|
||||
description: "Answers to frequently asked questions."
|
||||
lead: "Answers to frequently asked questions."
|
||||
date: 2020-10-06T08:49:31+00:00
|
||||
lastmod: 2020-10-06T08:49:31+00:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 630
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Hyas?
|
||||
|
||||
Doks is a [Hyas theme](https://gethyas.com/themes/) build by the creator of Hyas.
|
||||
|
||||
## Footer notice?
|
||||
|
||||
Please keep it in place.
|
||||
|
||||
## Keyboard shortcuts for search?
|
||||
|
||||
- focus: `Ctrl + /`
|
||||
- select: `↓` and `↑`
|
||||
- open: `Enter`
|
||||
- close: `Esc`
|
||||
|
||||
## Other documentation?
|
||||
|
||||
- [Netlify](https://docs.netlify.com/)
|
||||
- [Hugo](https://gohugo.io/documentation/)
|
||||
|
||||
## Can I get support?
|
||||
|
||||
Create a topic:
|
||||
|
||||
- [Netlify Community](https://community.netlify.com/)
|
||||
- [Hugo Forums](https://discourse.gohugo.io/)
|
||||
- [Doks Discussions](https://github.com/h-enk/doks/discussions)
|
||||
|
||||
## Contact the creator?
|
||||
|
||||
Send `h-enk` a message:
|
||||
|
||||
- [Netlify Community](https://community.netlify.com/)
|
||||
- [Hugo Forums](https://discourse.gohugo.io/)
|
||||
- [Doks Discussions](https://github.com/h-enk/doks/discussions)
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
title: "How to Update"
|
||||
description: "Regularly update the installed npm packages to keep your Doks website stable, usable, and secure."
|
||||
lead: "Regularly update the installed npm packages to keep your Doks website stable, usable, and secure."
|
||||
date: 2020-11-12T13:26:54+01:00
|
||||
lastmod: 2020-11-12T13:26:54+01:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 610
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< alert icon="💡" text="Learn more about <a href=\"https://docs.npmjs.com/about-semantic-versioning\">semantic versioning</a> and <a href=\"https://docs.npmjs.com/cli/v6/using-npm/semver#advanced-range-syntax\">advanced range syntax</a>." />}}
|
||||
|
||||
## Check for outdated packages
|
||||
|
||||
The [`npm outdated`](https://docs.npmjs.com/cli/v7/commands/npm-outdated) command will check the registry to see if any (or, specific) installed packages are currently outdated:
|
||||
|
||||
```bash
|
||||
npm outdated [[<@scope>/]<pkg> ...]
|
||||
```
|
||||
|
||||
## Update packages
|
||||
|
||||
The [`npm update`](https://docs.npmjs.com/cli/v7/commands/npm-update) command will update all the packages listed to the latest version (specified by the tag config), respecting semver:
|
||||
|
||||
```bash
|
||||
npm update [<pkg>...]
|
||||
```
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
title: "Troubleshooting"
|
||||
description: "Solutions to common problems."
|
||||
lead: "Solutions to common problems."
|
||||
date: 2020-11-12T15:22:20+01:00
|
||||
lastmod: 2020-11-12T15:22:20+01:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "help"
|
||||
weight: 620
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Problems updating npm packages
|
||||
|
||||
Delete the `./node_modules` folder, and run again:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Problems with cache
|
||||
|
||||
Delete the temporary directories:
|
||||
|
||||
```bash
|
||||
npm run clean
|
||||
```
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
title : "Overview"
|
||||
description: "Overview Doks."
|
||||
lead: ""
|
||||
date: 2020-10-06T08:48:45+00:00
|
||||
lastmod: 2020-10-06T08:48:45+00:00
|
||||
draft: false
|
||||
images: []
|
||||
---
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
title: "Commands"
|
||||
description: "Doks comes with commands for common tasks."
|
||||
lead: "Doks comes with commands for common tasks."
|
||||
date: 2020-10-13T15:21:01+02:00
|
||||
lastmod: 2020-10-13T15:21:01+02:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "overview"
|
||||
weight: 130
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< alert icon="💡" text="You can change the commands in the scripts section of `./package.json`." />}}
|
||||
|
||||
## create
|
||||
|
||||
Create new content for your site:
|
||||
|
||||
```bash
|
||||
npm run create [path] [flags]
|
||||
```
|
||||
|
||||
See also the Hugo docs: [hugo new](https://gohugo.io/commands/hugo_new/).
|
||||
|
||||
## lint
|
||||
|
||||
Check scripts, styles, and markdown for errors:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### scripts
|
||||
|
||||
Check scripts for errors:
|
||||
|
||||
```bash
|
||||
npm run lint:scripts [-- --fix]
|
||||
```
|
||||
|
||||
### styles
|
||||
|
||||
Check styles for errors:
|
||||
|
||||
```bash
|
||||
npm run lint:styles [-- --fix]
|
||||
```
|
||||
|
||||
### markdown
|
||||
|
||||
Check markdown for errors:
|
||||
|
||||
```bash
|
||||
npm run lint:markdown [-- --fix]
|
||||
```
|
||||
|
||||
## clean
|
||||
|
||||
Delete temporary directories:
|
||||
|
||||
```bash
|
||||
npm run clean
|
||||
```
|
||||
|
||||
## start
|
||||
|
||||
Start local development server:
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
## build
|
||||
|
||||
Build production website:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### functions
|
||||
|
||||
Build Lambda functions:
|
||||
|
||||
```bash
|
||||
npm run build:functions
|
||||
```
|
||||
|
||||
### preview
|
||||
|
||||
Build production website including draft and future content:
|
||||
|
||||
```bash
|
||||
npm run build:preview
|
||||
```
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
title: "Introduction"
|
||||
description: "Doks is a Hugo theme helping you build modern documentation websites that are secure, fast, and SEO-ready — by default."
|
||||
lead: "Doks is a Hugo theme helping you build modern documentation websites that are secure, fast, and SEO-ready — by default."
|
||||
date: 2020-10-06T08:48:57+00:00
|
||||
lastmod: 2020-10-06T08:48:57+00:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "overview"
|
||||
weight: 100
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Get started
|
||||
|
||||
There are two main ways to get started with Doks:
|
||||
|
||||
### Tutorial
|
||||
|
||||
{{< alert icon="👉" text="The Tutorial is intended for novice to intermediate users." />}}
|
||||
|
||||
Step-by-step instructions on how to start a new Doks project. [Tutorial →](https://getdoks.org/tutorial/introduction/)
|
||||
|
||||
### Quick Start
|
||||
|
||||
{{< alert icon="👉" text="The Quick Start is intended for intermediate to advanced users." />}}
|
||||
|
||||
One page summary of how to start a new Doks project. [Quick Start →]({{< relref "quick-start" >}})
|
||||
|
||||
## Go further
|
||||
|
||||
Recipes, Reference Guides, Extensions, and Showcase.
|
||||
|
||||
### Recipes
|
||||
|
||||
Get instructions on how to accomplish common tasks with Doks. [Recipes →](https://getdoks.org/docs/recipes/project-configuration/)
|
||||
|
||||
### Reference Guides
|
||||
|
||||
Learn how to customize Doks to fully make it your own. [Reference Guides →](https://getdoks.org/docs/reference-guides/security/)
|
||||
|
||||
### Extensions
|
||||
|
||||
Get instructions on how to add even more to Doks. [Extensions →](https://getdoks.org/docs/extensions/breadcrumb-navigation/)
|
||||
|
||||
### Showcase
|
||||
|
||||
See what others have build with Doks. [Showcase →](https://getdoks.org/showcase/electric-blocks/)
|
||||
|
||||
## Contributing
|
||||
|
||||
Find out how to contribute to Doks. [Contributing →](https://getdoks.org/docs/contributing/how-to-contribute/)
|
||||
|
||||
## Help
|
||||
|
||||
Get help on Doks. [Help →]({{< relref "how-to-update" >}})
|
||||
@@ -1,69 +0,0 @@
|
||||
---
|
||||
title: "Quick Start"
|
||||
description: "One page summary of how to start a new Doks project."
|
||||
lead: "One page summary of how to start a new Doks project."
|
||||
date: 2020-11-16T13:59:39+01:00
|
||||
lastmod: 2020-11-16T13:59:39+01:00
|
||||
draft: false
|
||||
images: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "overview"
|
||||
weight: 110
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
Doks uses npm to centralize dependency management, making it [easy to update]({{< relref "how-to-update" >}}) resources, build tooling, plugins, and build scripts:
|
||||
|
||||
- Download and install [Node.js](https://nodejs.org/) (it includes npm) for your platform.
|
||||
|
||||
## Start a new Doks project
|
||||
|
||||
Create a new site, change directories, install dependencies, and start development server.
|
||||
|
||||
### Create a new site
|
||||
|
||||
Doks is available as a child theme, and a starter theme:
|
||||
|
||||
- Use the Doks child theme, if you do __not__ plan to customize a lot, and/or need future Doks updates.
|
||||
- Use the Doks starter theme, if you plan to customize a lot, and/or do __not__ need future Doks updates.
|
||||
|
||||
Not quite sure? Use the Doks child theme.
|
||||
|
||||
#### Doks child theme
|
||||
|
||||
```bash
|
||||
git clone https://github.com/h-enk/doks-child-theme.git my-doks-site
|
||||
```
|
||||
|
||||
#### Doks starter theme
|
||||
|
||||
```bash
|
||||
git clone https://github.com/h-enk/doks.git my-doks-site
|
||||
```
|
||||
|
||||
### Change directories
|
||||
|
||||
```bash
|
||||
cd my-doks-site
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Start development server
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
Doks will start the Hugo development webserver accessible by default at `http://localhost:1313`. Saved changes will live reload in the browser.
|
||||
|
||||
## Other commands
|
||||
|
||||
Doks comes with commands for common tasks. [Commands →]({{< relref "commands" >}})
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
title: "Privacy Policy"
|
||||
description: "We do not use cookies and we do not collect any personal data."
|
||||
date: 2020-08-27T19:23:18+02:00
|
||||
lastmod: 2020-08-27T19:23:18+02:00
|
||||
draft: true
|
||||
images: []
|
||||
---
|
||||
|
||||
__TLDR__: We do not use cookies and we do not collect any personal data.
|
||||
|
||||
## Website visitors
|
||||
|
||||
- No personal information is collected.
|
||||
- No information is stored in the browser.
|
||||
- No information is shared with, sent to or sold to third-parties.
|
||||
- No information is shared with advertising companies.
|
||||
- No information is mined and harvested for personal and behavioral trends.
|
||||
- No information is monetized.
|
||||
|
||||
### Information we collect and what we use it for
|
||||
|
||||
We run [Plausible](https://plausible.io/) analytics on getdoks.org. The following information is collected:
|
||||
|
||||
- __Page URL__. We track the page URL of each page view on this website. We use this to understand which pages have been viewed and how many times a particular page has been viewed. For example: _https://getdoks.org/_.
|
||||
- __HTTP Referrer__. We use the referrer string to understand the number of visitors referred to this website from links on other sites. For example: _https://github.com/_.
|
||||
- __Browser__. We use this to understand what browsers people use when visiting this website. This is derived from the User-Agent HTTP header. The full User-Agent is discarded. For example: _Chrome_.
|
||||
- __Operating system__. We use this to understand what operating systems people use when visiting this website. We only use the brand of the operating system and don’t include the version number or any other details. This is derived from the User-Agent HTTP header. The full User-Agent is discarded. For example: _GNU/Linux_.
|
||||
- __Device type__. We use this to understand what devices people use when visiting this website. This is derived from window.innerWidth. The actual width of the browser in pixels is discarded. For example: _Desktop_.
|
||||
- __Visitor Country__. We look up the visitor’s country using the IP address. We do not track anything more granular than the country of origin and the IP address of the visitor is discarded. We never store IP addresses in our database or logs. For example: _Canada_.
|
||||
|
||||
## Contact us
|
||||
|
||||
[Contact us]({{< relref "contact/index.md" >}}) if you have any questions.
|
||||
|
||||
Effective Date: _27th August 2020_
|
||||
@@ -15,6 +15,18 @@
|
||||
<p class="meta"><a href="https://github.com/devlikeapro/whatsapp-http-api">Source code in GitHub.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex justify-content-center my-4">
|
||||
<iframe
|
||||
width="560" height="315"
|
||||
src="https://www.youtube.com/embed/RFerMyAUPRg"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
@@ -22,6 +34,14 @@
|
||||
<section class="section section-sm">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">It's free!</h2>
|
||||
<p>
|
||||
<b>WAHA Core</b> version is always free!
|
||||
<br>
|
||||
Even <b>WAHA Plus</b> version have no licence expiration!
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">Language agnostic</h2>
|
||||
<p>
|
||||
@@ -34,18 +54,7 @@
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">Well documented</h2>
|
||||
<p>Documentation has a lof examples and how to guide.</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">No blocking</h2>
|
||||
<p>
|
||||
Under the hood it runs a real instance of Whatsapp Web to avoid getting blocked.
|
||||
<br/>
|
||||
<small class="text-muted">
|
||||
WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered
|
||||
totally safe.
|
||||
</small>
|
||||
</p>
|
||||
<p>Documentation has a lof examples and how-to guides.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center text-center">
|
||||
@@ -67,11 +76,14 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<h2 class="h4">It's free!</h2>
|
||||
<h2 class="h4">No blocking</h2>
|
||||
<p>
|
||||
<img src="/images/versions/core.png"> version is always free!
|
||||
<br>
|
||||
Even <img src="/images/versions/plus.png"> version does not require monthly donations and have no licence expiration!
|
||||
Under the hood it runs a real instance of Whatsapp Web to avoid getting blocked.
|
||||
<br/>
|
||||
<small class="text-muted">
|
||||
WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered
|
||||
totally safe.
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
<!-- Custom head -->
|
||||
<!-- Yandex.Metrika counter -->
|
||||
<script type="text/javascript" >
|
||||
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||
m[i].l=1*new Date();
|
||||
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||
|
||||
ym(91185978, "init", {
|
||||
clickmap:true,
|
||||
trackLinks:true,
|
||||
accurateTrackBounce:true,
|
||||
webvisor:true
|
||||
});
|
||||
</script>
|
||||
<noscript><div><img src="https://mc.yandex.ru/watch/91185978" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||
<!-- /Yandex.Metrika counter -->
|
||||
@@ -1 +1 @@
|
||||
{"openapi":"3.0.0","paths":{"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"sessionName","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"sessionName","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLocation":{"post":{"operationId":"ChattingController_sendLocation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLocationRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLinkPreview":{"post":{"operationId":"ChattingController_sendLinkPreview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkPreviewRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendImage":{"post":{"operationId":"ChattingController_sendImage","summary":"Send an image. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageImageRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendFile":{"post":{"operationId":"ChattingController_sendFile","summary":"Send a file. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageFileRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendVoice":{"post":{"operationId":"ChattingController_sendVoice","summary":"Send an voice message. Either from an URL or base64 data - look at the request schemas for details.","parametersLine truncated
|
||||
{"openapi":"3.0.0","paths":{"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionDTO"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WAMessage"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendTextButtons":{"post":{"operationId":"ChattingController_sendTextButtons","summary":"Send a text message with buttons","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTextButtonsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLocation":{"post":{"operationId":"ChattingController_sendLocation","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLocationRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendLinkPreview":{"post":{"operationId":"ChattingController_sendLinkPreview","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageLinkPreviewRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendImage":{"post":{"operationId":"ChattingController_sendImage","summary":"Send an image. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageImageRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendFile":{"post":{"operationId":"ChattingController_sendFile","summary":"Send a file. Either from an URL or base64 data - look at the request schemas for details.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageFileRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendVoice":{"post":{"operationId":"ChattingController_sendVoice","summary":"Send an voice message. Either from an URL or basLine truncated
|
||||
@@ -0,0 +1,118 @@
|
||||
# WAHA (WhatsApp HTTP API) Python examples
|
||||
|
||||
## Installation
|
||||
|
||||
We assume that you have installed software:
|
||||
|
||||
1. Python 3
|
||||
2. Docker
|
||||
|
||||
### Download and start image
|
||||
|
||||
First of all, you must run WhatsApp HTTP API locally (which under the hood it runs real WhatsApp Web instance and expose
|
||||
HTTP API for interaction).
|
||||
|
||||
Here are the steps from [Quick Start](https://waha.devlike.pro/docs/overview/quick-start/):
|
||||
|
||||
Download and start WhatsApp HTTP API docker container
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/whatsapp-http-api
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
#### Download image - ARM
|
||||
|
||||
If you're using ARM (like Apple Silicon, Apple M1, etc) - use following commands to download the image
|
||||
|
||||
 For Core version the command is
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/whatsapp-http-api:arm
|
||||
# Rename it, so you can use devlikeapro/whatsapp-http-api image in other place
|
||||
docker tag devlikeapro/whatsapp-http-api:arm devlikeapro/whatsapp-http-api
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name whatsapp-http-api devlikeapro/whatsapp-http-api
|
||||
|
||||
# It prints logs and the last line must be
|
||||
# WhatsApp HTTP API is running on: http://[::1]:3000
|
||||
```
|
||||
|
||||
### Start session and scan QR
|
||||
|
||||
2. Open Swagger API in the browser http://localhost:3000/
|
||||
3. Start session and scan QR code in swagger
|
||||
1. Find `POST /api/session/start` request press **Try it out** and **Execute** it with `default` session name
|
||||
2. Find `GET /api/screenshot` and execute it - it must show QR code
|
||||
3. Scan QR code on your mobile WhatsApp application (that installed on your phone)
|
||||
4. Execute `GET /api/screenshot` once again - it must show the screenshot from WhatsApp Web.
|
||||
4. Send test text message - find `POST /api/sendText` and execute it with payload (change `12132132130` in the `chatId`
|
||||
to phone number that is registered in WhatsApp).
|
||||
|
||||
```json
|
||||
{
|
||||
"chatId": "12132132130@c.us",
|
||||
"text": "Hi there!",
|
||||
"session": "default"
|
||||
}
|
||||
```
|
||||
|
||||
If you see **Hi there!** message then you're ready to run bots!
|
||||
|
||||
## WhatsApp Echo Bot
|
||||
|
||||
The WhatsApp Echo Bot is a sample flask webhook server application that echoes back to you whatever you send it. It can
|
||||
serve
|
||||
as a basic reference for how to set up webhooks and reply to incoming messages.
|
||||
|
||||
```bash
|
||||
# Clone the git repository with example
|
||||
git clone https://github.com/devlikeapro/whatsapp-http-api.git
|
||||
# Open python example folder
|
||||
cd whatsapp-http-api/examples/python
|
||||
# Install requirements
|
||||
python -mpip install -r requirements.txt
|
||||
# Run the bot
|
||||
FLASK_APP=whatsapp_echo_bot.py flask run
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Echo Bot is ready!** then the bot is ready to receive message!
|
||||
|
||||
Send message to the WhatsApp (that you used to scan QR code) and it'll echo text back to you!
|
||||
|
||||
## WhatsApp Download Files Bot
|
||||
|
||||
The WhatsApp Download Image Bot downloads all files people send to your WhatsApp and log the path for the file.
|
||||
|
||||
**The bot works only with WAHA Plus version** available with donations.
|
||||
Visit [read more about difference between Core and Plus versions](https://waha.devlike.pro/docs/how-to/plus-version/).
|
||||
|
||||
Download **WAHA Plus** version:
|
||||
|
||||
```bash
|
||||
# Download the image
|
||||
docker pull devlikeapro/whatsapp-http-api-plus
|
||||
# Run the docker container
|
||||
docker run -it --rm --network=host -e WHATSAPP_HOOK_URL=http://localhost:5000/bot -e "WHATSAPP_HOOK_EVENTS=*" --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
|
||||
```
|
||||
|
||||
Run the WhatsApp Download Files bot:
|
||||
|
||||
```bash
|
||||
# Clone the git repository with example
|
||||
git clone https://github.com/devlikeapro/whatsapp-http-api.git
|
||||
# Open python example folder
|
||||
cd whatsapp-http-api/examples/python
|
||||
# Install requirements
|
||||
python -mpip install -r requirements.txt
|
||||
# Run the bot
|
||||
FLASK_APP=whatsapp_download_files_bot.py flask run
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:5000/bot - if you see **WhatsApp Download Files Bot!** then the bot is ready to receive message
|
||||
with files!
|
||||
@@ -1,44 +0,0 @@
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp HTTP API Echo server is ready!"
|
||||
|
||||
|
||||
@app.route("/message", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return "WhatsApp HTTP API Echo server is ready!"
|
||||
pprint(data)
|
||||
if data['event'] != "message":
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
payload = data["payload"]
|
||||
# The text
|
||||
text = payload["body"]
|
||||
# Number in format 791111111@c.us
|
||||
from_ = payload["from"]
|
||||
|
||||
if payload.get("mediaUrl", None):
|
||||
# Download file and set text to path
|
||||
client_url = payload["mediaUrl"]
|
||||
filename = client_url.split("/")[-1]
|
||||
path = "/tmp/" + filename
|
||||
r = requests.get(client_url)
|
||||
open(path, "wb").write(r.content)
|
||||
text = f"We have downloaded file here: {path}"
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText", json={"chatId": from_, "text": text}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return "OK"
|
||||
@@ -1,2 +1,3 @@
|
||||
flask
|
||||
requests
|
||||
flask~=1.0
|
||||
requests~=2.0
|
||||
markupsafe==2.0.1
|
||||
@@ -0,0 +1,84 @@
|
||||
from os.path import abspath
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def send_message(chat_id, text):
|
||||
"""
|
||||
Send message to chat_id.
|
||||
:param chat_id: Phone number + "@c.us" suffix - 1231231231@c.us
|
||||
:param text: Message for the recipient
|
||||
"""
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def send_seen(chat_id, message_id, participant):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendSeen",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
"messageId": message_id,
|
||||
"participant": participant,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp Download Files Bot is ready!"
|
||||
|
||||
|
||||
@app.route("/bot", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
if request.method == "GET":
|
||||
return "WhatsApp Download Files Bot is ready!"
|
||||
|
||||
data = request.get_json()
|
||||
pprint(data)
|
||||
if data["event"] != "message":
|
||||
# We can't process other event yet
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
payload = data["payload"]
|
||||
# Ignore messages without files
|
||||
if not payload.get("mediaUrl", None):
|
||||
return "No files in the message"
|
||||
|
||||
# Number in format 791111111@c.us
|
||||
chat_id = payload["from"]
|
||||
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
message_id = payload['id']
|
||||
# For groups - who sent the message
|
||||
participant = payload.get('participant')
|
||||
# IMPORTANT - Always send seen before sending new message
|
||||
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
|
||||
|
||||
# Download the file and download it to the current folder
|
||||
client_url = payload["mediaUrl"]
|
||||
filename = client_url.split("/")[-1]
|
||||
path = abspath("./" + filename)
|
||||
r = requests.get(client_url)
|
||||
with open(path, "wb") as f:
|
||||
f.write(r.content)
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
text = f"We have downloaded file here: {path}"
|
||||
print(text)
|
||||
send_message(chat_id=chat_id, text=text)
|
||||
|
||||
# Send OK back
|
||||
return "OK"
|
||||
@@ -0,0 +1,88 @@
|
||||
from pprint import pprint
|
||||
|
||||
import requests
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def send_message(chat_id, text):
|
||||
"""
|
||||
Send message to chat_id.
|
||||
:param chat_id: Phone number + "@c.us" suffix - 1231231231@c.us
|
||||
:param text: Message for the recipient
|
||||
"""
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendText",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
def reply(chat_id, message_id, text):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/reply",
|
||||
json={
|
||||
"chatId": chat_id,
|
||||
"text": text,
|
||||
"reply_to": message_id,
|
||||
"session": "default",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def send_seen(chat_id, message_id, participant):
|
||||
response = requests.post(
|
||||
"http://localhost:3000/api/sendSeen",
|
||||
json={
|
||||
"session": "default",
|
||||
"chatId": chat_id,
|
||||
"messageId": message_id,
|
||||
"participant": participant,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def whatsapp_echo():
|
||||
return "WhatsApp Echo Bot is ready!"
|
||||
|
||||
|
||||
@app.route("/bot", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
if request.method == "GET":
|
||||
return "WhatsApp Echo Bot is ready!"
|
||||
|
||||
data = request.get_json()
|
||||
pprint(data)
|
||||
if data["event"] != "message":
|
||||
# We can't process other event yet
|
||||
return f"Unknown event {data['event']}"
|
||||
|
||||
# Payload that we've got
|
||||
payload = data["payload"]
|
||||
# The text
|
||||
text = payload["body"]
|
||||
# Number in format 1231231231@c.us or @g.us for group
|
||||
chat_id = payload["from"]
|
||||
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
message_id = payload['id']
|
||||
# For groups - who sent the message
|
||||
participant = payload.get('participant')
|
||||
# IMPORTANT - Always send seen before sending new message
|
||||
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
|
||||
|
||||
# Send a text back via WhatsApp HTTP API
|
||||
send_message(chat_id=chat_id, text=text)
|
||||
# OR reply on the message
|
||||
# reply(chat_id=chat_id, message_id=message_id, text=text)
|
||||
|
||||
# Send OK back
|
||||
return "OK"
|
||||
Generated
-32495
File diff suppressed because it is too large.
Load diff
+12
-3
@@ -22,6 +22,8 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adiwajshing/baileys": "file:../WhiskeySockets-Baileys",
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@nestjs/common": "^9.0.9",
|
||||
"@nestjs/config": "^0.5.0",
|
||||
"@nestjs/core": "^9.0.9",
|
||||
@@ -29,11 +31,18 @@
|
||||
"@nestjs/platform-express": "^9.0.9",
|
||||
"@nestjs/serve-static": "^2.1.3",
|
||||
"@nestjs/swagger": "^6.0.5",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/ws": "^8.5.4",
|
||||
"class-validator": "^0.12.2",
|
||||
"del": "^6.0.0",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"file-type": "16.5.4",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.27",
|
||||
"passport": "^0.6.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"request": "^2.88.2",
|
||||
@@ -41,8 +50,8 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.1.0",
|
||||
"swagger-ui-express": "^4.1.4",
|
||||
"venom-bot": "^4.3.0",
|
||||
"whatsapp-web.js": "github:mrfelipemartins/whatsapp-web.js#fix-group-module"
|
||||
"venom-bot": "^5.0.1",
|
||||
"whatsapp-web.js": "https://github.com/pedroslopez/whatsapp-web.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.0.0",
|
||||
@@ -50,7 +59,7 @@
|
||||
"@nestjs/testing": "^9.0.9",
|
||||
"@types/express": "^4.17.3",
|
||||
"@types/jest": "26.0.10",
|
||||
"@types/node": "^13.9.1",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "3.9.1",
|
||||
"@typescript-eslint/parser": "3.9.1",
|
||||
|
||||
+129
-112
@@ -1,134 +1,151 @@
|
||||
import {Body, Controller, Get, Post, Put, Query} from '@nestjs/common';
|
||||
import {ApiOperation, ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextQuery,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest
|
||||
} from "../structures/chatting.dto";
|
||||
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
|
||||
import {SessionManager} from "../core/abc/manager.abc";
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextQuery,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
} from '../structures/chatting.dto';
|
||||
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api')
|
||||
@ApiTags('chatting')
|
||||
export class ChattingController {
|
||||
constructor(private manager: SessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({summary: 'Check number status'})
|
||||
async checkNumberStatus(@Query() request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.sessionName)
|
||||
return whatsapp.checkNumberStatus(request)
|
||||
}
|
||||
@Get('/checkNumberStatus')
|
||||
@ApiOperation({ summary: 'Check number status' })
|
||||
async checkNumberStatus(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendContactVCard(request)
|
||||
}
|
||||
@Post('/sendContactVcard')
|
||||
sendContactVcard(@Body() request: MessageContactVcardRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendContactVCard(request);
|
||||
}
|
||||
|
||||
@Get('/sendText')
|
||||
@ApiOperation({summary: 'Send a text message'})
|
||||
sendTextGet(
|
||||
@Query() query: MessageTextQuery,
|
||||
) {
|
||||
const whatsapp = this.manager.getSession(query.sessionName)
|
||||
const msg = new MessageTextRequest()
|
||||
msg.chatId = query.phone
|
||||
msg.text = query.text
|
||||
return whatsapp.sendText(new MessageTextRequest())
|
||||
}
|
||||
@Get('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message' })
|
||||
sendTextGet(@Query() query: MessageTextQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
const msg = new MessageTextRequest();
|
||||
msg.chatId = query.phone;
|
||||
msg.text = query.text;
|
||||
return whatsapp.sendText(msg);
|
||||
}
|
||||
|
||||
@Post('/sendText')
|
||||
@ApiOperation({summary: 'Send a text message'})
|
||||
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendText(request)
|
||||
}
|
||||
@Post('/sendText')
|
||||
@ApiOperation({ summary: 'Send a text message' })
|
||||
sendText(@Body() request: MessageTextRequest): Promise<WAMessage> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendText(request);
|
||||
}
|
||||
|
||||
@Post('/sendTextButtons')
|
||||
@ApiOperation({summary: 'Send a text message with buttons'})
|
||||
sendTextButtons(@Body() request: MessageTextButtonsRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendTextButtons(request)
|
||||
}
|
||||
@Post('/sendTextButtons')
|
||||
@ApiOperation({ summary: 'Send a text message with buttons' })
|
||||
sendTextButtons(@Body() request: MessageTextButtonsRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendTextButtons(request);
|
||||
}
|
||||
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() request: MessageLocationRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendLocation(request)
|
||||
}
|
||||
@Post('/sendLocation')
|
||||
sendLocation(@Body() request: MessageLocationRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLocation(request);
|
||||
}
|
||||
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendLinkPreview(request)
|
||||
}
|
||||
@Post('/sendLinkPreview')
|
||||
sendLinkPreview(@Body() request: MessageLinkPreviewRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendLinkPreview(request);
|
||||
}
|
||||
|
||||
@Post('/sendImage')
|
||||
@ApiOperation({summary: "Send an image. Either from an URL or base64 data - look at the request schemas for details."})
|
||||
sendImage(@Body() request: MessageImageRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendImage(request)
|
||||
}
|
||||
@Post('/sendImage')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an image. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendImage(@Body() request: MessageImageRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendImage(request);
|
||||
}
|
||||
|
||||
@Post('/sendFile')
|
||||
@ApiOperation({summary: "Send a file. Either from an URL or base64 data - look at the request schemas for details."})
|
||||
sendFile(@Body() request: MessageFileRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendFile(request)
|
||||
}
|
||||
@Post('/sendFile')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send a file. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendFile(@Body() request: MessageFileRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendFile(request);
|
||||
}
|
||||
|
||||
@Post('/sendVoice')
|
||||
@ApiOperation({summary: "Send an voice message. Either from an URL or base64 data - look at the request schemas for details."})
|
||||
sendVoice(@Body() request: MessageVoiceRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.sendVoice(request)
|
||||
}
|
||||
@Post('/sendVoice')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Send an voice message. Either from an URL or base64 data - look at the request schemas for details.',
|
||||
})
|
||||
sendVoice(@Body() request: MessageVoiceRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.sendVoice(request);
|
||||
}
|
||||
|
||||
@Post('/reply')
|
||||
@ApiOperation({summary: 'Reply to a text message'})
|
||||
reply(@Body() request: MessageReplyRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.reply(request)
|
||||
}
|
||||
@Post('/reply')
|
||||
@ApiOperation({ summary: 'Reply to a text message' })
|
||||
reply(@Body() request: MessageReplyRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.reply(request);
|
||||
}
|
||||
|
||||
@Post('/sendSeen')
|
||||
sendSeen(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session)
|
||||
return whatsapp.sendSeen(chat)
|
||||
}
|
||||
@Post('/sendSeen')
|
||||
sendSeen(@Body() chat: SendSeenRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
return whatsapp.sendSeen(chat);
|
||||
}
|
||||
|
||||
@Post('/startTyping')
|
||||
async startTyping(@Body() chat: ChatRequest) {
|
||||
// It's infinitive action
|
||||
const whatsapp = this.manager.getSession(chat.session)
|
||||
await whatsapp.startTyping(chat)
|
||||
return {result: true}
|
||||
}
|
||||
@Post('/startTyping')
|
||||
async startTyping(@Body() chat: ChatRequest) {
|
||||
// It's infinitive action
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
await whatsapp.startTyping(chat);
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
@Post('/stopTyping')
|
||||
async stopTyping(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session)
|
||||
await whatsapp.stopTyping(chat)
|
||||
return {result: true}
|
||||
}
|
||||
@Post('/stopTyping')
|
||||
async stopTyping(@Body() chat: ChatRequest) {
|
||||
const whatsapp = this.manager.getSession(chat.session);
|
||||
await whatsapp.stopTyping(chat);
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
@Put('/reaction')
|
||||
@ApiOperation({summary: 'React to a message with an emoji'})
|
||||
setReaction(@Body() request: MessageReactionRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session)
|
||||
return whatsapp.setReaction(request)
|
||||
}
|
||||
@Put('/reaction')
|
||||
@ApiOperation({ summary: 'React to a message with an emoji' })
|
||||
setReaction(@Body() request: MessageReactionRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.setReaction(request);
|
||||
}
|
||||
|
||||
@Get('/messages')
|
||||
@ApiOperation({ summary: 'Get messages in a chat' })
|
||||
getMessages(@Query() query: GetMessageQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getMessages(query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
import { SessionQuery } from '../structures/base.dto';
|
||||
import { CheckNumberStatusQuery } from '../structures/chatting.dto';
|
||||
import { WANumberExistResult } from '../structures/responses.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/contacts')
|
||||
@ApiTags('contacts')
|
||||
export class ContactsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Get contact basic info. The method always return result, even if the phone number is not registered in WhatsApp. For that - use /check-exists endpoint below.',
|
||||
})
|
||||
get(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContact(query);
|
||||
}
|
||||
|
||||
@Get('/all')
|
||||
@ApiOperation({ summary: 'Get all contacts' })
|
||||
getAll(@Query() query: SessionQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContacts();
|
||||
}
|
||||
|
||||
@Get('/check-exists')
|
||||
@ApiOperation({ summary: 'Check phone number is registered in WhatsApp.' })
|
||||
async checkExists(
|
||||
@Query() request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.checkNumberStatus(request);
|
||||
}
|
||||
|
||||
@Get('/about')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Gets the Contact\'s current "about" info. Returns null if you don\'t have permission to read their status.',
|
||||
})
|
||||
getAbout(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContactAbout(query);
|
||||
}
|
||||
|
||||
@Get('/profile-picture')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Returns the contact's profile picture URL, if privacy settings allow it.",
|
||||
})
|
||||
getProfilePicture(@Query() query: ContactQuery) {
|
||||
const whatsapp = this.manager.getSession(query.session);
|
||||
return whatsapp.getContactProfilePicture(query);
|
||||
}
|
||||
|
||||
@Post('/block')
|
||||
@ApiOperation({ summary: 'Block contact' })
|
||||
block(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.blockContact(request);
|
||||
}
|
||||
@Post('/unblock')
|
||||
@ApiOperation({ summary: 'Unblock contact' })
|
||||
unblock(@Body() request: ContactRequest) {
|
||||
const whatsapp = this.manager.getSession(request.session);
|
||||
return whatsapp.unblockContact(request);
|
||||
}
|
||||
}
|
||||
+35
-35
@@ -1,6 +1,12 @@
|
||||
import {ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus,} from '@nestjs/common';
|
||||
import {Request, Response} from 'express';
|
||||
import {VERSION} from "../version";
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { VERSION } from '../version';
|
||||
|
||||
/**
|
||||
* Serializer error to JSON.
|
||||
@@ -8,42 +14,36 @@ import {VERSION} from "../version";
|
||||
* Credits: https://stackoverflow.com/a/72707578/6753144
|
||||
*/
|
||||
export function serializeError(err: unknown) {
|
||||
const properties = Object.getOwnPropertyNames(err)
|
||||
// getOwnPropertyNames does not get 'name', like "ReferenceError"
|
||||
properties.push('name')
|
||||
return JSON.parse(JSON.stringify(err, properties))
|
||||
const properties = Object.getOwnPropertyNames(err);
|
||||
// getOwnPropertyNames does not get 'name', like "ReferenceError"
|
||||
properties.push('name');
|
||||
return JSON.parse(JSON.stringify(err, properties));
|
||||
}
|
||||
|
||||
@Catch()
|
||||
export class AllExceptionsFilter implements ExceptionFilter {
|
||||
catch(exception: HttpException | Error, host: ArgumentsHost): void {
|
||||
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
response
|
||||
.status(exception.getStatus())
|
||||
.json(exception.getResponse());
|
||||
return
|
||||
}
|
||||
|
||||
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR
|
||||
response
|
||||
.status(httpStatus)
|
||||
.json({
|
||||
statusCode: httpStatus,
|
||||
timestamp: new Date().toISOString(),
|
||||
exception: serializeError(exception),
|
||||
request: {
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
},
|
||||
version: VERSION,
|
||||
});
|
||||
catch(exception: HttpException | Error, host: ArgumentsHost): void {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
response.status(exception.getStatus()).json(exception.getResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
const httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
response.status(httpStatus).json({
|
||||
statusCode: httpStatus,
|
||||
timestamp: new Date().toISOString(),
|
||||
exception: serializeError(exception),
|
||||
request: {
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
},
|
||||
version: VERSION,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
DescriptionRequest,
|
||||
ParticipantsRequest,
|
||||
SubjectRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/:session/groups')
|
||||
@ApiTags('groups')
|
||||
export class GroupsController {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Create a new group.' })
|
||||
createGroup(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Body() request: CreateGroupRequest,
|
||||
) {
|
||||
return session.createGroup(request);
|
||||
}
|
||||
|
||||
@Get('')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get all groups.' })
|
||||
getGroups(@SessionParam session: WhatsappSession) {
|
||||
return session.getGroups();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get the group.' })
|
||||
getGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.getGroup(id);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Delete the group.' })
|
||||
deleteGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.deleteGroup(id);
|
||||
}
|
||||
|
||||
@Post(':id/leave')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Leave the group.' })
|
||||
leaveGroup(@SessionParam session: WhatsappSession, @Param('id') id: string) {
|
||||
return session.leaveGroup(id);
|
||||
}
|
||||
|
||||
@Put(':id/description')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group description.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
})
|
||||
@SessionApiParam
|
||||
setDescription(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: DescriptionRequest,
|
||||
) {
|
||||
return session.setDescription(id, request.description);
|
||||
}
|
||||
|
||||
@Put(':id/subject')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Updates the group subject.\n' +
|
||||
'Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.\n',
|
||||
})
|
||||
setSubject(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: SubjectRequest,
|
||||
) {
|
||||
return session.setSubject(id, request.subject);
|
||||
}
|
||||
|
||||
@Get(':id/invite-code')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Gets the invite code for a specific group.' })
|
||||
getInviteCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<string> {
|
||||
return session.getInviteCode(id);
|
||||
}
|
||||
|
||||
@Post(':id/invite-code/revoke')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Invalidates the current group invite code and generates a new one.',
|
||||
})
|
||||
revokeInviteCode(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
): Promise<string> {
|
||||
return session.revokeInviteCode(id);
|
||||
}
|
||||
|
||||
@Get(':id/participants/')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get a list of participants by in the group.' })
|
||||
getParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return session.getParticipants(id);
|
||||
}
|
||||
|
||||
@Post(':id/participants/add')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Adds a list of participants by ID to the group.' })
|
||||
addParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.addParticipants(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/participants/remove')
|
||||
@SessionApiParam
|
||||
@ApiOperation({
|
||||
summary: 'Removes a list of participants by ID to the group.',
|
||||
})
|
||||
removeParticipants(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.removeParticipants(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/admin/promote')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Promote participants to admin users.' })
|
||||
promoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.promoteParticipantsToAdmin(id, request);
|
||||
}
|
||||
|
||||
@Post(':id/admin/demote')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Demotes participants by to regular users.' })
|
||||
demoteToAdmin(
|
||||
@SessionParam session: WhatsappSession,
|
||||
@Param('id') id: string,
|
||||
@Body() request: ParticipantsRequest,
|
||||
) {
|
||||
return session.demoteParticipantsToUser(id, request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ApiParam } from '@nestjs/swagger';
|
||||
import { Injectable, Param, PipeTransform } from '@nestjs/common';
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
|
||||
/**
|
||||
* Get session name and return Whatsapp session back (if exists)
|
||||
* use it as
|
||||
@Param('session', SessionPipe) session: WhatsappSession,
|
||||
*/
|
||||
@Injectable()
|
||||
export class SessionPipe implements PipeTransform<WhatsappSession> {
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
async transform(value: any) {
|
||||
return this.manager.getSession(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for a method that uses SessionPipe above
|
||||
*/
|
||||
export const SessionApiParam = ApiParam({
|
||||
name: 'session',
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'WhatsApp session name',
|
||||
});
|
||||
|
||||
/**
|
||||
* Session param
|
||||
@SessionParam session: WhatsappSession,
|
||||
*/
|
||||
export const SessionParam = Param('session', SessionPipe);
|
||||
@@ -1,34 +1,28 @@
|
||||
import {Controller, Get, Query, Res} from '@nestjs/common';
|
||||
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import {Readable} from "stream";
|
||||
import {Response} from 'express';
|
||||
import {SessionQuery} from "../structures/chatting.dto";
|
||||
import {SessionManager} from "../core/abc/manager.abc";
|
||||
|
||||
import { Controller, Get, Query, Res } from '@nestjs/common';
|
||||
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { Readable } from 'stream';
|
||||
import { Response } from 'express';
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { SessionQuery } from '../structures/base.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api')
|
||||
@ApiTags('screenshot')
|
||||
export class ScreenshotController {
|
||||
constructor(private manager: SessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Get('/screenshot')
|
||||
async screenshot(
|
||||
@Res() res: Response,
|
||||
@Query() sessionQuery: SessionQuery,
|
||||
) {
|
||||
const whatsappService = this.manager.getSession(sessionQuery.sessionName)
|
||||
const buffer = await whatsappService.getScreenshot();
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
@Get('/screenshot')
|
||||
async screenshot(@Res() res: Response, @Query() sessionQuery: SessionQuery) {
|
||||
const whatsappService = this.manager.getSession(sessionQuery.session);
|
||||
const buffer = await whatsappService.getScreenshot();
|
||||
const stream = new Readable();
|
||||
stream.push(buffer);
|
||||
stream.push(null);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
stream.pipe(res)
|
||||
}
|
||||
res.set({
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': buffer.length,
|
||||
});
|
||||
stream.pipe(res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,52 @@
|
||||
import {Body, Controller, Get, Post} from '@nestjs/common';
|
||||
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../structures/sessions.dto";
|
||||
import {SessionManager} from "../core/abc/manager.abc";
|
||||
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
SessionLogoutRequest,
|
||||
SessionDTO,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
ListSessionsQuery,
|
||||
} from '../structures/sessions.dto';
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { parseBool } from '../helpers';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/sessions')
|
||||
@ApiTags('sessions')
|
||||
export class SessionsController {
|
||||
constructor(private manager: SessionManager) {
|
||||
}
|
||||
constructor(private manager: SessionManager) {}
|
||||
|
||||
@Post('/start/')
|
||||
start(@Body() request: SessionStartRequest): SessionDTO {
|
||||
return this.manager.start(request);
|
||||
}
|
||||
|
||||
@Post('/start/')
|
||||
start(@Body() request: SessionStartRequest): SessionDTO {
|
||||
return this.manager.start(request)
|
||||
@Post('/stop/')
|
||||
@ApiOperation({ summary: 'Stop session' })
|
||||
async stop(@Body() request: SessionStopRequest): Promise<void> {
|
||||
await this.manager.stop(request);
|
||||
if (request.logout) {
|
||||
await this.manager.logout(request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('/stop/')
|
||||
stop(@Body() request: SessionStopRequest): Promise<void> {
|
||||
return this.manager.stop(request)
|
||||
@Post('/logout/')
|
||||
@ApiOperation({ summary: 'Logout from session.' })
|
||||
clean(@Body() request: SessionLogoutRequest): Promise<void> {
|
||||
const session = this.manager.getSession(request.name, false);
|
||||
if (session) {
|
||||
throw new UnprocessableEntityException(
|
||||
`Can not clean running session, please stop it first. You can set 'clean' field to 'True' in stop request so it stops and cleans at the same time.`,
|
||||
);
|
||||
}
|
||||
return this.manager.logout(request);
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
list(): SessionDTO[] {
|
||||
return this.manager.getSessions()
|
||||
}
|
||||
@Get('/')
|
||||
list(@Query() query: ListSessionsQuery): SessionDTO[] {
|
||||
const all = parseBool(query.all);
|
||||
return this.manager.getSessions(all);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import {Controller, Get} from '@nestjs/common';
|
||||
import {ApiSecurity, ApiTags} from "@nestjs/swagger";
|
||||
import {VERSION} from "../version";
|
||||
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
|
||||
import { VERSION } from '../version';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/version')
|
||||
@ApiTags('other')
|
||||
export class VersionController {
|
||||
@Get('')
|
||||
async get() {
|
||||
return VERSION
|
||||
}
|
||||
@Get('')
|
||||
async get() {
|
||||
return VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
+92
-68
@@ -1,79 +1,103 @@
|
||||
import {Injectable} from "@nestjs/common";
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {WhatsappEngine} from "./structures/enums.dto";
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { WhatsappEngine } from './structures/enums.dto';
|
||||
import { parseBool } from './helpers';
|
||||
|
||||
@Injectable()
|
||||
export class WhatsappConfigService {
|
||||
public files_uri = '/api/files'
|
||||
public schema = "http"
|
||||
public files_uri = '/api/files';
|
||||
public schema = 'http';
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
constructor(private configService: ConfigService) {}
|
||||
|
||||
get filesURL(): string {
|
||||
return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`;
|
||||
}
|
||||
|
||||
get hostname(): string {
|
||||
return this.configService.get('WHATSAPP_API_HOSTNAME', 'localhost');
|
||||
}
|
||||
|
||||
get port(): string {
|
||||
return this.configService.get('WHATSAPP_API_PORT', '3000');
|
||||
}
|
||||
|
||||
get filesFolder(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_FILES_FOLDER',
|
||||
'/tmp/whatsapp-files',
|
||||
);
|
||||
}
|
||||
|
||||
get filesLifetime(): number {
|
||||
return this.configService.get<number>('WHATSAPP_FILES_LIFETIME', 180);
|
||||
}
|
||||
|
||||
get mimetypes(): string[] | null {
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', '');
|
||||
return types ? types.split(',') : null;
|
||||
}
|
||||
|
||||
get startSessions(): string[] {
|
||||
const value: string = this.configService.get('WHATSAPP_START_SESSION', '');
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
return value.split(',');
|
||||
}
|
||||
|
||||
get filesURL(): string {
|
||||
return `${this.schema}://${this.hostname}:${this.port}${this.files_uri}/`
|
||||
get shouldRestartAllSessions(): boolean {
|
||||
const value: string = this.configService.get(
|
||||
'WHATSAPP_RESTART_ALL_SESSIONS',
|
||||
'false',
|
||||
);
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
getWebhookUrl(): string | undefined {
|
||||
return this.get('WHATSAPP_HOOK_URL');
|
||||
}
|
||||
|
||||
getWebhookEvents(): string[] {
|
||||
const value = this.get('WHATSAPP_HOOK_EVENTS', '');
|
||||
return value ? value.split(',') : [];
|
||||
}
|
||||
|
||||
getDefaultEngineName(): WhatsappEngine {
|
||||
const value = this.get('WHATSAPP_DEFAULT_ENGINE', WhatsappEngine.WEBJS);
|
||||
if (value in WhatsappEngine) {
|
||||
return WhatsappEngine[value];
|
||||
}
|
||||
console.log(
|
||||
`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`,
|
||||
);
|
||||
}
|
||||
|
||||
get hostname(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_API_HOSTNAME',
|
||||
'localhost',
|
||||
)
|
||||
get(name: string, defaultValue = undefined): any {
|
||||
return this.configService.get(name, defaultValue);
|
||||
}
|
||||
|
||||
getApiKey(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_API_KEY', '');
|
||||
}
|
||||
|
||||
getSwaggerUsernamePassword(): [string, string] | undefined {
|
||||
const user = this.configService.get('WHATSAPP_SWAGGER_USERNAME', undefined);
|
||||
const password = this.configService.get(
|
||||
'WHATSAPP_SWAGGER_PASSWORD',
|
||||
undefined,
|
||||
);
|
||||
if (!user && !password) {
|
||||
console.log(
|
||||
'Please set up both WHATSAPP_SWAGGER_USERNAME and WHATSAPP_SWAGGER_PASSWORD ' +
|
||||
'to enable swagger authentication.',
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return [user, password];
|
||||
}
|
||||
|
||||
get port(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_API_PORT',
|
||||
'3000',
|
||||
)
|
||||
}
|
||||
|
||||
get filesFolder(): string {
|
||||
return this.configService.get(
|
||||
'WHATSAPP_FILES_FOLDER',
|
||||
'/tmp/whatsapp-files',
|
||||
)
|
||||
}
|
||||
|
||||
get filesLifetime(): number {
|
||||
return this.configService.get<number>(
|
||||
'WHATSAPP_FILES_LIFETIME',
|
||||
180,
|
||||
)
|
||||
}
|
||||
|
||||
get mimetypes(): string[] | null {
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', "")
|
||||
return types ? types.split(',') : null
|
||||
}
|
||||
|
||||
get startSession(): string | undefined {
|
||||
return this.configService.get('WHATSAPP_START_SESSION', undefined)
|
||||
}
|
||||
|
||||
getWebhookUrl(): string | undefined {
|
||||
return this.get('WHATSAPP_HOOK_URL')
|
||||
}
|
||||
|
||||
getWebhookEvents(): string[] {
|
||||
const value = this.get('WHATSAPP_HOOK_EVENTS', "")
|
||||
return value ? value.split(',') : []
|
||||
}
|
||||
|
||||
getDefaultEngineName(): WhatsappEngine {
|
||||
const value = this.get("WHATSAPP_DEFAULT_ENGINE", WhatsappEngine.WEBJS)
|
||||
if (value in WhatsappEngine) {
|
||||
return WhatsappEngine[value]
|
||||
}
|
||||
console.log(`Unknown WhatsApp default engine, using WEBJS. WHATSAPP_DEFAULT_ENGINE=${value}`)
|
||||
|
||||
}
|
||||
|
||||
get(name: string, defaultValue = undefined): any {
|
||||
return this.configService.get(name, defaultValue)
|
||||
}
|
||||
|
||||
getApiKey(): string | undefined {
|
||||
return this.configService.get("WHATSAPP_API_KEY", "")
|
||||
}
|
||||
getSwaggerAdvancedConfigEnabled(): boolean {
|
||||
return this.configService.get('WHATSAPP_SWAGGER_CONFIG_ADVANCED', false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export class QR {
|
||||
private base64: string;
|
||||
|
||||
save(base64) {
|
||||
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
|
||||
}
|
||||
|
||||
get(): Buffer {
|
||||
return Buffer.from(this.base64, 'base64');
|
||||
}
|
||||
}
|
||||
+24
-16
@@ -1,28 +1,36 @@
|
||||
import {OnApplicationShutdown} from "@nestjs/common";
|
||||
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../../structures/sessions.dto";
|
||||
import {WhatsappSession} from "./session.abc";
|
||||
import {WhatsappEngine} from "../../structures/enums.dto";
|
||||
import {MediaStorage} from "./storage.abc";
|
||||
import {WebhookConductor} from "./webhooks.abc";
|
||||
import { OnApplicationShutdown } from '@nestjs/common';
|
||||
import {
|
||||
SessionLogoutRequest,
|
||||
SessionDTO,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
import { WhatsappEngine } from '../../structures/enums.dto';
|
||||
import { MediaStorage } from './storage.abc';
|
||||
import { WebhookConductor } from './webhooks.abc';
|
||||
|
||||
export abstract class SessionManager implements OnApplicationShutdown {
|
||||
protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession;
|
||||
|
||||
abstract start(request: SessionStartRequest): SessionDTO
|
||||
protected abstract get EngineClass(): typeof WhatsappSession;
|
||||
|
||||
abstract stop(request: SessionStopRequest): Promise<void>
|
||||
protected abstract get WebhookConductorClass(): typeof WebhookConductor;
|
||||
|
||||
abstract getSession(name: string): WhatsappSession
|
||||
protected abstract get MediaStorageClass(): typeof MediaStorage;
|
||||
|
||||
abstract getSessions(): SessionDTO[]
|
||||
abstract onApplicationShutdown(signal?: string);
|
||||
|
||||
abstract onApplicationShutdown(signal ?: string)
|
||||
//
|
||||
// API Methods
|
||||
//
|
||||
abstract start(request: SessionStartRequest): SessionDTO;
|
||||
|
||||
protected abstract getEngine(engine: WhatsappEngine): typeof WhatsappSession
|
||||
abstract stop(request: SessionStopRequest): Promise<void>;
|
||||
|
||||
protected abstract get EngineClass(): typeof WhatsappSession
|
||||
abstract logout(request: SessionLogoutRequest): Promise<void>;
|
||||
|
||||
protected abstract get WebhookConductorClass(): typeof WebhookConductor
|
||||
abstract getSession(name: string, error?: boolean): WhatsappSession;
|
||||
|
||||
protected abstract get MediaStorageClass(): typeof MediaStorage
|
||||
abstract getSessions(all: boolean): SessionDTO[];
|
||||
}
|
||||
|
||||
+258
-94
@@ -1,100 +1,264 @@
|
||||
import {WAEvents, WhatsappStatus} from "../../structures/enums.dto";
|
||||
import {ConsoleLogger} from "@nestjs/common";
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest
|
||||
} from "../../structures/chatting.dto";
|
||||
import {MediaStorage} from "./storage.abc";
|
||||
import {MessageId} from "whatsapp-web.js";
|
||||
WAEvents,
|
||||
WhatsappEngine,
|
||||
WhatsappStatus,
|
||||
} from '../../structures/enums.dto';
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
} from '../../structures/chatting.dto';
|
||||
import { LocalSessionStorage, MediaStorage } from './storage.abc';
|
||||
import { MessageId } from 'whatsapp-web.js';
|
||||
import { ContactQuery, ContactRequest } from '../../structures/contacts.dto';
|
||||
import { NotImplementedByEngineError } from '../exceptions';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../../structures/groups.dto';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
public status: WhatsappStatus;
|
||||
|
||||
public constructor(public name: string, protected storage: MediaStorage, protected log: ConsoleLogger) {
|
||||
this.name = name
|
||||
this.status = WhatsappStatus.STARTING
|
||||
this.log = log
|
||||
}
|
||||
|
||||
|
||||
/** Start the session */
|
||||
abstract start()
|
||||
|
||||
/** Stop the session */
|
||||
abstract stop(): void
|
||||
|
||||
/** Subscribe the handler to specific hook */
|
||||
abstract subscribe(hook: WAEvents | string, handler: (message) => void)
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
abstract getScreenshot(): Promise<Buffer | string>
|
||||
|
||||
abstract checkNumberStatus(request: CheckNumberStatusQuery)
|
||||
|
||||
abstract sendText(request: MessageTextRequest)
|
||||
|
||||
abstract sendContactVCard(request: MessageContactVcardRequest)
|
||||
|
||||
abstract sendTextButtons(request: MessageTextButtonsRequest)
|
||||
|
||||
abstract sendLocation(request: MessageLocationRequest)
|
||||
|
||||
abstract sendLinkPreview(request: MessageLinkPreviewRequest)
|
||||
|
||||
abstract sendImage(request: MessageImageRequest)
|
||||
|
||||
abstract sendFile(request: MessageFileRequest)
|
||||
|
||||
abstract sendVoice(request: MessageVoiceRequest)
|
||||
|
||||
abstract reply(request: MessageReplyRequest)
|
||||
|
||||
abstract sendSeen(chat: ChatRequest)
|
||||
|
||||
abstract startTyping(chat: ChatRequest)
|
||||
|
||||
abstract stopTyping(chat: ChatRequest)
|
||||
|
||||
abstract setReaction(request: MessageReactionRequest)
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet
|
||||
* @param phone
|
||||
*/
|
||||
protected ensureSuffix(phone) {
|
||||
const suffix = "@c.us"
|
||||
if (phone.includes("@")) {
|
||||
return phone
|
||||
}
|
||||
return phone + suffix
|
||||
}
|
||||
|
||||
protected deserializeId(messageId: string): MessageId {
|
||||
const parts = messageId.split("_")
|
||||
return {
|
||||
"fromMe": parts[0] === "true",
|
||||
"remote": parts[1],
|
||||
"id": parts[2],
|
||||
"_serialized": messageId
|
||||
}
|
||||
}
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export function getBrowserExecutablePath() {
|
||||
if (fs.existsSync(CHROME_PATH)) {
|
||||
return CHROME_PATH;
|
||||
}
|
||||
return CHROMIUM_PATH;
|
||||
}
|
||||
export function ensureSuffix(phone) {
|
||||
const suffix = '@c.us';
|
||||
if (phone.includes('@')) {
|
||||
return phone;
|
||||
}
|
||||
return phone + suffix;
|
||||
}
|
||||
|
||||
export enum WAHAInternalEvent {
|
||||
engine_start = 'engine.start',
|
||||
}
|
||||
|
||||
export interface WhatsAppSessionConfig {
|
||||
name: string;
|
||||
storage: MediaStorage;
|
||||
log: ConsoleLogger;
|
||||
sessionStorage: LocalSessionStorage;
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
public status: WhatsappStatus;
|
||||
public events: EventEmitter;
|
||||
public engine: WhatsappEngine;
|
||||
|
||||
public name: string;
|
||||
protected storage: MediaStorage;
|
||||
protected log: ConsoleLogger;
|
||||
protected sessionStorage: LocalSessionStorage;
|
||||
|
||||
public constructor({ name, log, sessionStorage }: WhatsAppSessionConfig) {
|
||||
this.name = name;
|
||||
this.status = WhatsappStatus.STARTING;
|
||||
this.log = log;
|
||||
this.sessionStorage = sessionStorage;
|
||||
this.events = new EventEmitter();
|
||||
}
|
||||
|
||||
getBrowserExecutablePath() {
|
||||
return getBrowserExecutablePath();
|
||||
}
|
||||
|
||||
getBrowserArgsForPuppeteer() {
|
||||
// Run optimized version of Chrome
|
||||
// References:
|
||||
// https://github.com/pedroslopez/whatsapp-web.js/issues/1420
|
||||
// https://www.bannerbear.com/blog/ways-to-speed-up-puppeteer-screenshots/
|
||||
return [
|
||||
'--no-sandbox',
|
||||
'--disable-client-side-phishing-detection',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-component-update',
|
||||
'--disable-default-apps',
|
||||
'--disable-popup-blocking',
|
||||
'--disable-offer-store-unmasked-wallet-cards',
|
||||
'--disable-speech-api',
|
||||
'--hide-scrollbars',
|
||||
'--mute-audio',
|
||||
'--disable-extensions',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-accelerated-2d-canvas',
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
'--no-pings',
|
||||
'--password-store=basic',
|
||||
'--use-mock-keychain',
|
||||
'--no-zygote',
|
||||
'--single-process',
|
||||
'--disable-gpu',
|
||||
];
|
||||
}
|
||||
|
||||
/** Start the session */
|
||||
abstract start();
|
||||
|
||||
/** Stop the session */
|
||||
abstract stop(): void;
|
||||
|
||||
/** Subscribe the handler to specific hook */
|
||||
abstract subscribe(hook: WAEvents | string, handler: (message) => void);
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
abstract getScreenshot(): Promise<Buffer | string>;
|
||||
|
||||
abstract checkNumberStatus(request: CheckNumberStatusQuery);
|
||||
|
||||
abstract sendText(request: MessageTextRequest);
|
||||
|
||||
abstract sendContactVCard(request: MessageContactVcardRequest);
|
||||
|
||||
abstract sendTextButtons(request: MessageTextButtonsRequest);
|
||||
|
||||
abstract sendLocation(request: MessageLocationRequest);
|
||||
|
||||
abstract sendLinkPreview(request: MessageLinkPreviewRequest);
|
||||
|
||||
abstract sendImage(request: MessageImageRequest);
|
||||
|
||||
abstract sendFile(request: MessageFileRequest);
|
||||
|
||||
abstract sendVoice(request: MessageVoiceRequest);
|
||||
|
||||
abstract reply(request: MessageReplyRequest);
|
||||
|
||||
abstract sendSeen(chat: ChatRequest);
|
||||
|
||||
abstract startTyping(chat: ChatRequest);
|
||||
|
||||
abstract stopTyping(chat: ChatRequest);
|
||||
|
||||
abstract getMessages(query: GetMessageQuery);
|
||||
|
||||
abstract setReaction(request: MessageReactionRequest);
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
public getContact(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContacts() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getContactProfilePicture(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public blockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public unblockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public deleteGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public leaveGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setDescription(id, description) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public setSubject(id, description) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getInviteCode(id): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public revokeInviteCode(id): Promise<string> {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public getParticipants(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public addParticipants(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public removeParticipants(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add WhatsApp suffix (@c.us) to the phone number if it doesn't have it yet
|
||||
* @param phone
|
||||
*/
|
||||
protected ensureSuffix(phone) {
|
||||
return ensureSuffix(phone);
|
||||
}
|
||||
|
||||
protected deserializeId(messageId: string): MessageId {
|
||||
const parts = messageId.split('_');
|
||||
return {
|
||||
fromMe: parts[0] === 'true',
|
||||
remote: parts[1],
|
||||
id: parts[2],
|
||||
_serialized: messageId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,23 @@
|
||||
export abstract class MediaStorage {
|
||||
abstract save(messageId: string, mimetype: string, buffer: Buffer): Promise<string>
|
||||
abstract save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
export abstract class LocalSessionStorage {
|
||||
public sessionsFolder: string;
|
||||
|
||||
protected constructor(public engine: string) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
abstract init();
|
||||
|
||||
abstract getFolderPath(sessionName: string): string;
|
||||
|
||||
abstract clean(sessionName: string);
|
||||
|
||||
abstract getAll(): string[];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import {WhatsappSession} from "./session.abc";
|
||||
import { WhatsappSession } from './session.abc';
|
||||
|
||||
export abstract class WebhookConductor {
|
||||
abstract configure(session: WhatsappSession)
|
||||
abstract configure(session: WhatsappSession);
|
||||
}
|
||||
+49
-46
@@ -1,53 +1,56 @@
|
||||
import {ConsoleLogger, Module} from '@nestjs/common';
|
||||
import {ScreenshotController} from "../api/screenshot.controller";
|
||||
import {ConfigModule} from "@nestjs/config";
|
||||
import {WhatsappConfigService} from "../config.service";
|
||||
import {ServeStaticModule} from "@nestjs/serve-static";
|
||||
import {SessionsController} from "../api/sessions.controller";
|
||||
import {ChattingController} from "../api/chatting.controller";
|
||||
import {VersionController} from "../api/version.controller";
|
||||
import {PassportModule} from "@nestjs/passport";
|
||||
import {SessionManager} from "./abc/manager.abc";
|
||||
import {SessionManagerCore} from "./manager.core";
|
||||
import { ConsoleLogger, Module } from '@nestjs/common';
|
||||
import { ScreenshotController } from '../api/screenshot.controller';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
import { SessionsController } from '../api/sessions.controller';
|
||||
import { ChattingController } from '../api/chatting.controller';
|
||||
import { ContactsController } from '../api/contacts.controller';
|
||||
import { VersionController } from '../api/version.controller';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import { SessionManagerCore } from './manager.core';
|
||||
import { GroupsController } from '../api/groups.controller';
|
||||
|
||||
export const IMPORTS = [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ServeStaticModule.forRootAsync({
|
||||
imports: [],
|
||||
extraProviders: [WhatsappConfigService],
|
||||
inject: [WhatsappConfigService],
|
||||
useFactory: (config: WhatsappConfigService) => {
|
||||
return [{
|
||||
rootPath: config.filesFolder,
|
||||
serveRoot: config.files_uri,
|
||||
}]
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
ServeStaticModule.forRootAsync({
|
||||
imports: [],
|
||||
extraProviders: [WhatsappConfigService],
|
||||
inject: [WhatsappConfigService],
|
||||
useFactory: (config: WhatsappConfigService) => {
|
||||
return [
|
||||
{
|
||||
rootPath: config.filesFolder,
|
||||
serveRoot: config.files_uri,
|
||||
},
|
||||
}),
|
||||
PassportModule,
|
||||
]
|
||||
export const CONTROLLERS = [
|
||||
SessionsController,
|
||||
ChattingController,
|
||||
ScreenshotController,
|
||||
VersionController,
|
||||
]
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: SessionManager,
|
||||
useClass: SessionManagerCore,
|
||||
];
|
||||
},
|
||||
WhatsappConfigService,
|
||||
ConsoleLogger,
|
||||
]
|
||||
|
||||
}),
|
||||
PassportModule,
|
||||
];
|
||||
export const CONTROLLERS = [
|
||||
SessionsController,
|
||||
ChattingController,
|
||||
ContactsController,
|
||||
GroupsController,
|
||||
ScreenshotController,
|
||||
VersionController,
|
||||
];
|
||||
const PROVIDERS = [
|
||||
{
|
||||
provide: SessionManager,
|
||||
useClass: SessionManagerCore,
|
||||
},
|
||||
WhatsappConfigService,
|
||||
ConsoleLogger,
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: IMPORTS,
|
||||
controllers: CONTROLLERS,
|
||||
providers: PROVIDERS,
|
||||
imports: IMPORTS,
|
||||
controllers: CONTROLLERS,
|
||||
providers: PROVIDERS,
|
||||
})
|
||||
export class AppModuleCore {
|
||||
|
||||
}
|
||||
export class AppModuleCore {}
|
||||
+13
-12
@@ -1,20 +1,21 @@
|
||||
import {UnprocessableEntityException} from "@nestjs/common";
|
||||
import { UnprocessableEntityException } from '@nestjs/common';
|
||||
|
||||
export const DOCS_URL = "https://waha.devlike.pro/"
|
||||
export const DOCS_URL = 'https://waha.devlike.pro/';
|
||||
|
||||
export class NotImplementedByEngineError extends UnprocessableEntityException {
|
||||
constructor(msg = "") {
|
||||
let error = "The method is not implemented by the engine."
|
||||
if (msg) {
|
||||
error += ` ${msg}`
|
||||
}
|
||||
super(error);
|
||||
constructor(msg = '') {
|
||||
let error = 'The method is not implemented by the engine.';
|
||||
if (msg) {
|
||||
error += ` ${msg}`;
|
||||
}
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AvailableInPlusVersion extends UnprocessableEntityException {
|
||||
constructor() {
|
||||
super(`The feature is available only in Plus version. Check this out: ${DOCS_URL}`);
|
||||
}
|
||||
constructor() {
|
||||
super(
|
||||
`The feature is available only in Plus version. Check this out: ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+152
-100
@@ -1,116 +1,168 @@
|
||||
import {ConsoleLogger, Injectable, NotFoundException, UnprocessableEntityException} from "@nestjs/common";
|
||||
import {SessionManager} from "./abc/manager.abc";
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import {WhatsappEngine} from "../structures/enums.dto";
|
||||
import {SessionDTO, SessionStartRequest, SessionStopRequest} from "../structures/sessions.dto";
|
||||
import {WhatsappConfigService} from "../config.service";
|
||||
import {WhatsappSessionVenomCore} from "./session.venom.core";
|
||||
import {WhatsappSessionWebJSCore} from "./session.webjs.core";
|
||||
import {DOCS_URL} from "./exceptions";
|
||||
import {WebhookConductorCore} from "./webhooks.core";
|
||||
import {MediaStorageCore} from "./storage.core";
|
||||
import {
|
||||
ConsoleLogger,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
UnprocessableEntityException,
|
||||
} from '@nestjs/common';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import {
|
||||
WAHAInternalEvent,
|
||||
WhatsappSession,
|
||||
WhatsAppSessionConfig,
|
||||
} from './abc/session.abc';
|
||||
import { WhatsappEngine, WhatsappStatus } from '../structures/enums.dto';
|
||||
import {
|
||||
SessionLogoutRequest,
|
||||
SessionDTO,
|
||||
SessionStartRequest,
|
||||
SessionStopRequest,
|
||||
} from '../structures/sessions.dto';
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
import { WhatsappSessionVenomCore } from './session.venom.core';
|
||||
import { WhatsappSessionWebJSCore } from './session.webjs.core';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
import { WebhookConductorCore } from './webhooks.core';
|
||||
import { MediaStorageCore, SessionStorageCore } from './storage.core';
|
||||
import { WhatsappSessionNoWebCore } from './session.noweb.core';
|
||||
import { LocalSessionStorage } from './abc/storage.abc';
|
||||
|
||||
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
constructor() {
|
||||
super(`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`);
|
||||
}
|
||||
constructor() {
|
||||
super(
|
||||
`WAHA Core support only 'default' session. If you want to run more then one WhatsApp account - please get WAHA PLUS version. Check this out: ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SessionManagerCore extends SessionManager {
|
||||
private session: WhatsappSession;
|
||||
DEFAULT = "default"
|
||||
private session: WhatsappSession;
|
||||
DEFAULT = 'default';
|
||||
|
||||
// @ts-ignore
|
||||
protected MediaStorageClass = MediaStorageCore;
|
||||
// @ts-ignore
|
||||
protected WebhookConductorClass = WebhookConductorCore;
|
||||
protected readonly EngineClass: typeof WhatsappSession;
|
||||
protected sessionStorage: LocalSessionStorage;
|
||||
|
||||
constructor(
|
||||
private config: WhatsappConfigService,
|
||||
private log: ConsoleLogger,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.log.setContext('SessionManager');
|
||||
this.session = undefined;
|
||||
const engineName = this.config.getDefaultEngineName();
|
||||
this.EngineClass = this.getEngine(engineName);
|
||||
this.sessionStorage = new SessionStorageCore(engineName.toLowerCase());
|
||||
this.sessionStorage.init();
|
||||
|
||||
this.startPredefinedSessions();
|
||||
}
|
||||
|
||||
protected startPredefinedSessions() {
|
||||
const startSessions = this.config.startSessions;
|
||||
startSessions.forEach((sessionName) => {
|
||||
this.start({ name: sessionName });
|
||||
});
|
||||
}
|
||||
|
||||
protected getEngine(engine: WhatsappEngine): typeof WhatsappSession {
|
||||
if (engine === WhatsappEngine.WEBJS) {
|
||||
return WhatsappSessionWebJSCore;
|
||||
} else if (engine === WhatsappEngine.VENOM) {
|
||||
return WhatsappSessionVenomCore;
|
||||
} else if (engine === WhatsappEngine.NOWEB) {
|
||||
return WhatsappSessionNoWebCore;
|
||||
} else {
|
||||
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
private onlyDefault(name: string) {
|
||||
if (name !== this.DEFAULT) {
|
||||
throw new OnlyDefaultSessionIsAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal?: string) {
|
||||
if (!this.session) {
|
||||
return;
|
||||
}
|
||||
await this.stop({ name: this.DEFAULT, logout: false });
|
||||
}
|
||||
|
||||
//
|
||||
// API Methods
|
||||
//
|
||||
start(request: SessionStartRequest): SessionDTO {
|
||||
this.onlyDefault(request.name);
|
||||
|
||||
const name = request.name;
|
||||
this.log.log(`'${name}' - starting session...`);
|
||||
const log = new ConsoleLogger(`WhatsappSession - ${name}`);
|
||||
const storage = new this.MediaStorageClass();
|
||||
const webhookLog = new ConsoleLogger(`Webhook - ${name}`);
|
||||
const webhook = new this.WebhookConductorClass(
|
||||
webhookLog,
|
||||
this.config.getWebhookUrl(),
|
||||
this.config.getWebhookEvents(),
|
||||
);
|
||||
|
||||
const sessionConfig: WhatsAppSessionConfig = {
|
||||
name,
|
||||
storage,
|
||||
log,
|
||||
sessionStorage: this.sessionStorage,
|
||||
};
|
||||
// @ts-ignore
|
||||
protected MediaStorageClass = MediaStorageCore
|
||||
// @ts-ignore
|
||||
protected WebhookConductorClass = WebhookConductorCore
|
||||
protected readonly EngineClass: typeof WhatsappSession;
|
||||
const session = new this.EngineClass(sessionConfig);
|
||||
this.session = session;
|
||||
|
||||
session.events.on(WAHAInternalEvent.engine_start, () =>
|
||||
webhook.configure(session),
|
||||
);
|
||||
session.start();
|
||||
return { name: session.name, status: session.status };
|
||||
}
|
||||
|
||||
constructor(
|
||||
private config: WhatsappConfigService,
|
||||
private log: ConsoleLogger,
|
||||
) {
|
||||
super()
|
||||
async stop(request: SessionStopRequest): Promise<void> {
|
||||
this.onlyDefault(request.name);
|
||||
|
||||
this.log.setContext('SessionManager')
|
||||
this.session = undefined
|
||||
this.EngineClass = this.getEngine(this.config.getDefaultEngineName())
|
||||
const name = request.name;
|
||||
this.log.log(`Stopping ${name} session...`);
|
||||
const session = this.getSession(name);
|
||||
await session.stop();
|
||||
this.log.log(`"${name}" has been stopped.`);
|
||||
this.session = undefined;
|
||||
}
|
||||
|
||||
// Start session from the start
|
||||
if (config.startSession) {
|
||||
this.start({name: config.startSession})
|
||||
}
|
||||
async logout(request: SessionLogoutRequest) {
|
||||
this.sessionStorage.clean(request.name);
|
||||
}
|
||||
|
||||
getSession(name: string, error = true): WhatsappSession {
|
||||
this.onlyDefault(name);
|
||||
const session = this.session;
|
||||
if (!session) {
|
||||
if (error) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
protected getEngine(engine: WhatsappEngine): typeof WhatsappSession {
|
||||
if (engine === WhatsappEngine.WEBJS) {
|
||||
return WhatsappSessionWebJSCore
|
||||
} else if (engine === WhatsappEngine.VENOM) {
|
||||
return WhatsappSessionVenomCore
|
||||
} else {
|
||||
throw new NotFoundException(`Unknown whatsapp engine '${engine}'.`)
|
||||
}
|
||||
|
||||
getSessions(all: boolean): SessionDTO[] {
|
||||
if (!this.session) {
|
||||
return all
|
||||
? [{ name: this.DEFAULT, status: WhatsappStatus.STOPPED }]
|
||||
: [];
|
||||
}
|
||||
|
||||
private onlyDefault(name: string) {
|
||||
if (name !== this.DEFAULT) {
|
||||
throw new OnlyDefaultSessionIsAllowed()
|
||||
}
|
||||
}
|
||||
|
||||
getSession(name: string): WhatsappSession {
|
||||
this.onlyDefault(name)
|
||||
if (!this.session) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return this.session
|
||||
}
|
||||
|
||||
getSessions(): SessionDTO[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
async onApplicationShutdown(signal?: string) {
|
||||
if (!this.session){
|
||||
return
|
||||
}
|
||||
await this.stop({name: this.DEFAULT})
|
||||
}
|
||||
|
||||
start(request: SessionStartRequest): SessionDTO {
|
||||
this.onlyDefault(request.name)
|
||||
|
||||
const name = request.name
|
||||
this.log.log(`'${name}' - starting session...`)
|
||||
const log = new ConsoleLogger(`WhatsappSession - ${name}`)
|
||||
const storage = new this.MediaStorageClass()
|
||||
const webhookLog = new ConsoleLogger(`Webhook - ${name}`)
|
||||
const webhook = new this.WebhookConductorClass(
|
||||
webhookLog,
|
||||
this.config.getWebhookUrl(),
|
||||
this.config.getWebhookEvents()
|
||||
)
|
||||
|
||||
// @ts-ignore
|
||||
const session = new this.EngineClass(name, storage, log)
|
||||
this.session = session
|
||||
|
||||
session.start().then(() => webhook.configure(session))
|
||||
return {name: session.name, status: session.status}
|
||||
}
|
||||
|
||||
async stop(request: SessionStopRequest): Promise<void> {
|
||||
const name = request.name
|
||||
this.log.log(`Stopping ${name} session...`)
|
||||
const session = this.getSession(name)
|
||||
await session.stop()
|
||||
this.log.log(`"${name}" has been stopped.`)
|
||||
this.session = undefined
|
||||
}
|
||||
|
||||
return [{ name: this.session.name, status: this.session.status }];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
import {
|
||||
SECOND,
|
||||
WAEvents,
|
||||
WhatsappEngine,
|
||||
WhatsappStatus,
|
||||
} from '../structures/enums.dto';
|
||||
import makeWASocket, {
|
||||
DisconnectReason,
|
||||
isJidGroup,
|
||||
makeInMemoryStore,
|
||||
useMultiFileAuthState,
|
||||
} from '@adiwajshing/baileys';
|
||||
|
||||
import {
|
||||
ensureSuffix,
|
||||
WAHAInternalEvent,
|
||||
WhatsappSession,
|
||||
} from './abc/session.abc';
|
||||
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
|
||||
import {
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
} from '../structures/chatting.dto';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from './exceptions';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../structures/groups.dto';
|
||||
import { QR } from './QR';
|
||||
import { MediaStorage } from './abc/storage.abc';
|
||||
import { ConsoleLogger, UnprocessableEntityException } from '@nestjs/common';
|
||||
import { Message } from 'whatsapp-web.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const logger = require('pino')();
|
||||
|
||||
export const BaileysEvents = {
|
||||
CONNECTION_UPDATE: 'connection.update',
|
||||
CREDS_UPDATE: 'creds.update',
|
||||
MESSAGES_UPSERT: 'messages.upsert',
|
||||
GROUPS_UPSERT: 'groups.upsert',
|
||||
};
|
||||
|
||||
export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
engine = WhatsappEngine.NOWEB;
|
||||
|
||||
sock: any;
|
||||
store: any;
|
||||
private qr: QR;
|
||||
authFolder: string;
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
this.authFolder = this.getAuthFolder();
|
||||
}
|
||||
|
||||
start() {
|
||||
return this.buildClient();
|
||||
}
|
||||
|
||||
protected getAuthFolder() {
|
||||
const folder = this.sessionStorage.getFolderPath(this.name);
|
||||
return fs.mkdtempSync(folder);
|
||||
}
|
||||
|
||||
async makeSocket() {
|
||||
const { state, saveCreds } = await useMultiFileAuthState(this.authFolder);
|
||||
const sock: any = makeWASocket({
|
||||
auth: state,
|
||||
printQRInTerminal: true,
|
||||
browser: ['Linux', 'Chrome', '111.0.5563.64'],
|
||||
logger: logger,
|
||||
});
|
||||
sock.ev.on(BaileysEvents.CREDS_UPDATE, saveCreds);
|
||||
return sock;
|
||||
}
|
||||
|
||||
connectStore() {
|
||||
if (!this.store) {
|
||||
this.store = makeInMemoryStore({});
|
||||
}
|
||||
this.store.bind(this.sock.ev);
|
||||
}
|
||||
|
||||
async buildClient() {
|
||||
this.sock = await this.makeSocket();
|
||||
this.connectStore();
|
||||
this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, async (update) => {
|
||||
const { connection, lastDisconnect, qr } = update;
|
||||
if (connection === 'connecting') {
|
||||
this.qr.save('');
|
||||
this.status = WhatsappStatus.WORKING;
|
||||
return;
|
||||
} else if (connection === 'close') {
|
||||
const shouldReconnect =
|
||||
lastDisconnect.error?.output?.statusCode !==
|
||||
DisconnectReason.loggedOut;
|
||||
this.log.error(
|
||||
'connection closed due to ',
|
||||
lastDisconnect.error,
|
||||
', reconnecting ',
|
||||
shouldReconnect,
|
||||
);
|
||||
this.qr.save('');
|
||||
this.status = WhatsappStatus.FAILED;
|
||||
// reconnect if not logged out
|
||||
if (shouldReconnect) {
|
||||
setTimeout(() => this.buildClient(), 2 * SECOND);
|
||||
}
|
||||
}
|
||||
|
||||
// Save QR
|
||||
if (qr) {
|
||||
this.status = WhatsappStatus.SCAN_QR_CODE;
|
||||
QRCode.toDataURL(qr).then((url) => {
|
||||
this.qr.save(url);
|
||||
});
|
||||
}
|
||||
});
|
||||
this.events.emit(WAHAInternalEvent.engine_start);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.sock.ws.removeAllListeners();
|
||||
this.log.log('socket connection terminated');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WhatsappStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
);
|
||||
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get());
|
||||
} else if (this.status === WhatsappStatus.WORKING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`Can not get screenshot for non chrome based engine.`,
|
||||
);
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const phone = request.phone.split('@')[0];
|
||||
const [result] = await this.sock.onWhatsApp(phone);
|
||||
if (!result) {
|
||||
return { numberExists: false };
|
||||
}
|
||||
return { numberExists: result.exists };
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
const chatId = this.ensureSuffix(request.chatId);
|
||||
return this.sock.sendMessage(chatId, { text: request.text });
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
buttonId: button.id,
|
||||
buttonText: { displayText: button.text },
|
||||
type: 1,
|
||||
};
|
||||
});
|
||||
|
||||
const buttonMessage = {
|
||||
text: request.title,
|
||||
buttons: buttons,
|
||||
headerType: 1,
|
||||
};
|
||||
|
||||
return this.sock.sendMessage(request.chatId, buttonMessage);
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async reply(request: MessageReplyRequest) {
|
||||
const { id } = parseMessageId(request.reply_to);
|
||||
const message = await this.store.loadMessage(toJID(request.chatId), id);
|
||||
return await this.sock.sendMessage(
|
||||
request.chatId,
|
||||
{ text: request.text },
|
||||
{ quoted: message },
|
||||
);
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendVoice(request: MessageVoiceRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.sock.sendMessage(request.chatId, {
|
||||
location: {
|
||||
degreesLatitude: request.latitude,
|
||||
degreesLongitude: request.longitude,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
const text = `${request.title}\n${request.url}`;
|
||||
const chatId = this.ensureSuffix(request.chatId);
|
||||
return this.sock.sendMessage(chatId, { text: text });
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
const key = parseMessageId(request.messageId);
|
||||
const data = {
|
||||
remoteJid: key.remoteJid,
|
||||
id: key.id,
|
||||
participant: request.participant,
|
||||
};
|
||||
return this.sock.readMessages([data]);
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
return this.sock.sendPresenceUpdate('composing', request.chatId);
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
return this.sock.sendPresenceUpdate('paused', request.chatId);
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const key = parseMessageId(request.messageId);
|
||||
const reactionMessage = {
|
||||
react: {
|
||||
text: request.reaction,
|
||||
key: key,
|
||||
},
|
||||
};
|
||||
return this.sock.sendMessage(key.remoteJid, reactionMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
getContact(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
getContacts() {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async getContactProfilePicture(query: ContactQuery) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async blockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async unblockContact(request: ContactRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupCreate(request.name, participants);
|
||||
}
|
||||
|
||||
public async getGroups() {
|
||||
return await this.sock.groupFetchAllParticipating();
|
||||
}
|
||||
|
||||
public async getGroup(id) {
|
||||
const groups = await this.sock.groupFetchAllParticipating();
|
||||
return groups[id];
|
||||
}
|
||||
|
||||
public async deleteGroup(id) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
public async leaveGroup(id) {
|
||||
return this.sock.groupLeave(id);
|
||||
}
|
||||
|
||||
public async setDescription(id, description) {
|
||||
return this.sock.groupUpdateDescription(id, description);
|
||||
}
|
||||
|
||||
public async setSubject(id, subject) {
|
||||
return this.sock.groupUpdateSubject(id, subject);
|
||||
}
|
||||
|
||||
public async getInviteCode(id): Promise<string> {
|
||||
return this.sock.groupInviteCode(id);
|
||||
}
|
||||
|
||||
public async revokeInviteCode(id): Promise<string> {
|
||||
await this.sock.groupRevokeInvite(id);
|
||||
return this.sock.groupInviteCode(id);
|
||||
}
|
||||
|
||||
public async getParticipants(id) {
|
||||
const groups = await this.sock.groupFetchAllParticipating();
|
||||
return groups[id].participants;
|
||||
}
|
||||
|
||||
public async addParticipants(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'add');
|
||||
}
|
||||
|
||||
public async removeParticipants(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'remove');
|
||||
}
|
||||
|
||||
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'promote');
|
||||
}
|
||||
|
||||
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
const participants = request.participants.map(getId);
|
||||
return this.sock.groupParticipantsUpdate(id, participants, 'demote');
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
subscribe(event, handler) {
|
||||
if (event === WAEvents.MESSAGE) {
|
||||
return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
|
||||
this.handleIncomingMessages(messages, handler, false),
|
||||
);
|
||||
} else if (event === WAEvents.MESSAGE_ANY) {
|
||||
return this.sock.ev.on(BaileysEvents.MESSAGES_UPSERT, ({ messages }) =>
|
||||
this.handleIncomingMessages(messages, handler, true),
|
||||
);
|
||||
} else if (event === WAEvents.STATE_CHANGE) {
|
||||
return this.sock.ev.on(BaileysEvents.CONNECTION_UPDATE, handler);
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
return this.sock.ev.on(BaileysEvents.GROUPS_UPSERT, handler);
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(
|
||||
`Engine does not support webhook event: ${event}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleIncomingMessages(messages, handler, includeFromMe) {
|
||||
for (const message of messages) {
|
||||
// if there is no text or media message
|
||||
if (!message) return;
|
||||
if (!message.message) return;
|
||||
// Do not include my messages
|
||||
if (!includeFromMe && message.key.fromMe) {
|
||||
continue;
|
||||
}
|
||||
this.processIncomingMessage(message).then(handler);
|
||||
}
|
||||
}
|
||||
|
||||
private processIncomingMessage(message) {
|
||||
return this.downloadMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
protected toWAMessage(message): Promise<WAMessage> {
|
||||
const isGroupMessage = Boolean(message.key.participant);
|
||||
let participant: string;
|
||||
let to: string;
|
||||
if (isGroupMessage) {
|
||||
participant = message.key.participant;
|
||||
to = message.key.remoteJid;
|
||||
}
|
||||
const from = message.key.remoteJid;
|
||||
const id = buildMessageId(message.key);
|
||||
let body = message.message.conversation;
|
||||
if (!body) {
|
||||
// Some of the messages have no conversation, but instead have text in extendedTextMessage
|
||||
// https://github.com/devlikeapro/whatsapp-http-api/issues/90
|
||||
body = message.message.extendedTextMessage?.text;
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: id,
|
||||
timestamp: message.messageTimestamp,
|
||||
from: toCusFormat(from),
|
||||
fromMe: message.key.fromMe,
|
||||
body: body,
|
||||
to: toCusFormat(to),
|
||||
participant: toCusFormat(participant),
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: message.location,
|
||||
vCards: message.vCards,
|
||||
_data: message,
|
||||
});
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.key.id, '', undefined);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from 11111111111@s.whatsapp.net to 11111111111@c.us
|
||||
*/
|
||||
function toCusFormat(remoteJid) {
|
||||
if (isJidGroup(remoteJid)) {
|
||||
return remoteJid;
|
||||
}
|
||||
if (!remoteJid) {
|
||||
return;
|
||||
}
|
||||
const number = remoteJid.split('@')[0];
|
||||
return ensureSuffix(number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from 11111111111@c.us to 11111111111@s.whatsapp.net
|
||||
* @param chatId
|
||||
*/
|
||||
function toJID(chatId) {
|
||||
if (isJidGroup(chatId)) {
|
||||
return chatId;
|
||||
}
|
||||
const number = chatId.split('@')[0];
|
||||
return number + '@s.whatsapp.net';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build WAHA message id from engine one
|
||||
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
|
||||
* false_11111111111@c.us_AA
|
||||
*/
|
||||
function buildMessageId({ id, remoteJid, fromMe }) {
|
||||
const chatId = toCusFormat(remoteJid);
|
||||
return `${fromMe || false}_${chatId}_${id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse message id from WAHA to engine
|
||||
* false_11111111111@c.us_AAA
|
||||
* {id: "AAA", remoteJid: "11111111111@s.whatsapp.net", "fromMe": false}
|
||||
*/
|
||||
function parseMessageId(messageId) {
|
||||
const parts = messageId.split('_');
|
||||
if (parts.length != 3) {
|
||||
throw new Error(
|
||||
'Message id be in format false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
);
|
||||
}
|
||||
const fromMe = parts[0] == 'true';
|
||||
const chatId = parts[1];
|
||||
const remoteJid = toJID(chatId);
|
||||
const id = parts[2];
|
||||
return { fromMe: fromMe, id: id, remoteJid: remoteJid };
|
||||
}
|
||||
|
||||
function getId(object) {
|
||||
return object.id;
|
||||
}
|
||||
+254
-209
@@ -1,229 +1,274 @@
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
|
||||
import {
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest
|
||||
} from "../structures/chatting.dto";
|
||||
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
|
||||
import {create, Message, Whatsapp} from "venom-bot";
|
||||
import {WAEvents, WhatsappStatus} from "../structures/enums.dto";
|
||||
import {NotImplementedByEngineError} from "./exceptions";
|
||||
import {MediaStorage} from "./abc/storage.abc";
|
||||
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
|
||||
import {ConsoleLogger} from "@nestjs/common";
|
||||
|
||||
class QR {
|
||||
private base64: string;
|
||||
|
||||
save(base64) {
|
||||
this.base64 = base64.replace(/^data:image\/png;base64,/, '');
|
||||
}
|
||||
|
||||
get(): Buffer {
|
||||
return Buffer.from(this.base64, "base64")
|
||||
}
|
||||
|
||||
}
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
} from '../structures/chatting.dto';
|
||||
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
|
||||
import { create, Message, Whatsapp } from 'venom-bot';
|
||||
import {
|
||||
WAEvents,
|
||||
WhatsappEngine,
|
||||
WhatsappStatus,
|
||||
} from '../structures/enums.dto';
|
||||
import { NotImplementedByEngineError } from './exceptions';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import { QR } from './QR';
|
||||
|
||||
export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
whatsapp: Whatsapp;
|
||||
private qr: QR
|
||||
engine = WhatsappEngine.NOWEB;
|
||||
|
||||
public constructor(public name: string, protected storage: MediaStorage, protected log: ConsoleLogger) {
|
||||
super(name, storage, log);
|
||||
this.qr = new QR()
|
||||
whatsapp: Whatsapp;
|
||||
private qr: QR;
|
||||
|
||||
public constructor(config) {
|
||||
super(config);
|
||||
this.qr = new QR();
|
||||
}
|
||||
|
||||
protected buildClient() {
|
||||
return create(this.name, this.getCatchQR(), undefined, {
|
||||
headless: true,
|
||||
devtools: false,
|
||||
debug: false,
|
||||
logQR: true,
|
||||
browserArgs: this.getBrowserArgsForPuppeteer(),
|
||||
autoClose: 60000,
|
||||
puppeteerOptions: {},
|
||||
});
|
||||
}
|
||||
|
||||
protected getCatchQR() {
|
||||
return (base64Qrimg, asciiQR, attempts, urlCode) => {
|
||||
this.qr.save(base64Qrimg);
|
||||
this.status = WhatsappStatus.SCAN_QR_CODE;
|
||||
this.log.debug('Number of attempts to read the qrcode: ', attempts);
|
||||
this.log.log('Terminal qrcode:');
|
||||
// Log QR image in console without this.log to make it pretty
|
||||
console.log(asciiQR);
|
||||
};
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
this.whatsapp = await this.buildClient();
|
||||
} catch (error) {
|
||||
this.status = WhatsappStatus.FAILED;
|
||||
this.log.error(error);
|
||||
this.qr.save('');
|
||||
return;
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
this.whatsapp = await create('sessionName',
|
||||
(base64Qrimg, asciiQR, attempts, urlCode) => {
|
||||
this.qr.save(base64Qrimg)
|
||||
this.status = WhatsappStatus.SCAN_QR_CODE
|
||||
this.log.debug('Number of attempts to read the qrcode: ', attempts);
|
||||
this.log.log('Terminal qrcode:');
|
||||
// Log QR image in console without this.log to make it pretty
|
||||
console.log(asciiQR);
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
headless: true,
|
||||
devtools: false,
|
||||
useChrome: true,
|
||||
debug: false,
|
||||
logQR: true,
|
||||
browserArgs: ["--no-sandbox"],
|
||||
autoClose: 60000,
|
||||
createPathFileToken: true,
|
||||
puppeteerOptions: {},
|
||||
multidevice: false,
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
this.status = WhatsappStatus.FAILED
|
||||
this.log.error(error)
|
||||
this.qr.save("")
|
||||
return
|
||||
}
|
||||
this.status = WhatsappStatus.WORKING;
|
||||
this.events.emit(WAHAInternalEvent.engine_start);
|
||||
return this;
|
||||
}
|
||||
|
||||
this.status = WhatsappStatus.WORKING
|
||||
return this
|
||||
stop() {
|
||||
return this.whatsapp.close();
|
||||
}
|
||||
|
||||
subscribe(event: WAEvents | string, handler: (message) => void) {
|
||||
if (event === WAEvents.MESSAGE) {
|
||||
return this.whatsapp.onMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAEvents.MESSAGE_ANY) {
|
||||
return this.whatsapp.onAnyMessage((message: Message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAEvents.STATE_CHANGE) {
|
||||
return this.whatsapp.onStateChange(handler);
|
||||
} else if (event === WAEvents.MESSAGE_ACK) {
|
||||
return this.whatsapp.onAck(handler);
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
return this.whatsapp.onAddedToGroup(handler);
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(
|
||||
`Engine does not support webhook event: ${event}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stop() {
|
||||
return this.whatsapp.close()
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WhatsappStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session is starting, please try again after few seconds`,
|
||||
);
|
||||
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get());
|
||||
} else if (this.status === WhatsappStatus.WORKING) {
|
||||
return this.whatsapp.page.screenshot();
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(event: WAEvents | string, handler: (message) => void) {
|
||||
if (event === WAEvents.MESSAGE) {
|
||||
return this.whatsapp.onMessage((message: Message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.MESSAGE_ANY) {
|
||||
return this.whatsapp.onAnyMessage((message: Message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.STATE_CHANGE) {
|
||||
return this.whatsapp.onStateChange(handler)
|
||||
} else if (event === WAEvents.MESSAGE_ACK) {
|
||||
return this.whatsapp.onAck(handler)
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
return this.whatsapp.onAddedToGroup(handler)
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`)
|
||||
}
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
try {
|
||||
return await this.whatsapp.checkNumberStatus(
|
||||
this.ensureSuffix(request.phone),
|
||||
);
|
||||
} catch (error) {
|
||||
// Catch number doesn't exist error and return it as is
|
||||
if (error.status === 404 && !error.numberExists) {
|
||||
return error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
return this.whatsapp.sendContactVcard(
|
||||
request.chatId,
|
||||
request.contactsId,
|
||||
request.name,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WhatsappStatus.STARTING) {
|
||||
throw new UnprocessableEntityException(`The session is starting, please try again after few seconds`);
|
||||
} else if (this.status === WhatsappStatus.SCAN_QR_CODE) {
|
||||
return Promise.resolve(this.qr.get())
|
||||
} else if (this.status === WhatsappStatus.WORKING) {
|
||||
return this.whatsapp.page.screenshot()
|
||||
} else {
|
||||
throw new UnprocessableEntityException(`Unknown status - ${this.status}`);
|
||||
}
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendText(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
);
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
return this.whatsapp.reply(request.chatId, request.text, request.reply_to);
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async sendVoice(request) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
return this.whatsapp.sendLinkPreview(
|
||||
request.chatId,
|
||||
request.url,
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.whatsapp.sendLocation(
|
||||
request.chatId,
|
||||
String(request.latitude),
|
||||
String(request.longitude),
|
||||
request.title,
|
||||
);
|
||||
}
|
||||
|
||||
sendSeen(chat: ChatRequest) {
|
||||
return this.whatsapp.markMarkSeenMessage(chat.chatId);
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
buttonId: button.id,
|
||||
buttonText: {
|
||||
displayText: button.text,
|
||||
},
|
||||
};
|
||||
});
|
||||
return this.whatsapp.sendButtons(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.title,
|
||||
request.footer,
|
||||
buttons,
|
||||
);
|
||||
}
|
||||
|
||||
startTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.startTyping(chat.chatId, false);
|
||||
}
|
||||
|
||||
stopTyping(chat: ChatRequest) {
|
||||
return;
|
||||
// The method is not available yet in new venom-bot
|
||||
// return this.whatsapp.stopTyping(chat.chatId);
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
const messages = await this.whatsapp.getAllMessagesInChat(
|
||||
query.chatId,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
// Go over messages, download media, and convert to right format.
|
||||
const result = [];
|
||||
for (const [count, message] of messages.entries()) {
|
||||
if (count > query.limit) {
|
||||
// Have enough in the list, stop processing
|
||||
break;
|
||||
}
|
||||
result.push(await this.processIncomingMessage(message));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
|
||||
try {
|
||||
const result = await this.whatsapp.checkNumberStatus(this.ensureSuffix(request.phone))
|
||||
return {numberExists: result['numberExists']}
|
||||
} catch (error) {
|
||||
// We need to "touch" the error in order to get unhandled rejections
|
||||
// It logs out the session and stop the app
|
||||
console.log(typeof error)
|
||||
console.log(error)
|
||||
return {numberExists: false}
|
||||
}
|
||||
setReaction(request: MessageReactionRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected async downloadAndDecryptMedia(message: Message) {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id, '', undefined);
|
||||
return message;
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
return this.whatsapp.sendContactVcard(request.chatId, request.contactsId, request.name)
|
||||
}
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadAndDecryptMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendText(this.ensureSuffix(request.chatId), request.text)
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
return this.whatsapp.reply(request.chatId, request.text, request.reply_to)
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
async sendVoice(request) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
return this.whatsapp.sendLinkPreview(request.chatId, request.url, request.title)
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
return this.whatsapp.sendLocation(request.chatId, request.latitude, request.longitude, request.title)
|
||||
}
|
||||
|
||||
sendSeen(chat: ChatRequest) {
|
||||
return this.whatsapp.sendSeen(chat.chatId)
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
buttonId: button.id,
|
||||
buttonText: {
|
||||
displayText: button.body
|
||||
}
|
||||
}
|
||||
})
|
||||
return this.whatsapp.sendButtons(this.ensureSuffix(request.chatId), request.title, buttons, request.text)
|
||||
}
|
||||
|
||||
startTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.startTyping(chat.chatId)
|
||||
}
|
||||
|
||||
stopTyping(chat: ChatRequest) {
|
||||
return this.whatsapp.stopTyping(chat.chatId)
|
||||
}
|
||||
|
||||
setReaction(request: MessageReactionRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected async downloadAndDecryptMedia(message: Message) {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message
|
||||
}
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id, "", undefined)
|
||||
return message
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadAndDecryptMedia(message).then(this.toWAMessage)
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
vCards: undefined,
|
||||
_data: message,
|
||||
})
|
||||
}
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
vCards: undefined,
|
||||
_data: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
+406
-197
@@ -1,207 +1,416 @@
|
||||
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
|
||||
import {Buttons, Chat, Client, Events, Message} from "whatsapp-web.js";
|
||||
import {Message as MessageInstance} from "whatsapp-web.js/src/structures"
|
||||
import {WAEvents, WhatsappStatus} from "../structures/enums.dto";
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import {WAMessage, WANumberExistResult} from "../structures/responses.dto";
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import {
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest
|
||||
} from "../structures/chatting.dto";
|
||||
import {AvailableInPlusVersion, NotImplementedByEngineError} from "./exceptions";
|
||||
Buttons,
|
||||
Chat,
|
||||
Client,
|
||||
Contact,
|
||||
Events,
|
||||
GroupChat,
|
||||
Location,
|
||||
Message,
|
||||
} from 'whatsapp-web.js';
|
||||
import { Message as MessageInstance } from 'whatsapp-web.js/src/structures';
|
||||
import {
|
||||
WAEvents,
|
||||
WhatsappEngine,
|
||||
WhatsappStatus,
|
||||
} from '../structures/enums.dto';
|
||||
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
|
||||
import { WAMessage, WANumberExistResult } from '../structures/responses.dto';
|
||||
import {
|
||||
Button,
|
||||
ChatRequest,
|
||||
CheckNumberStatusQuery,
|
||||
GetMessageQuery,
|
||||
MessageContactVcardRequest,
|
||||
MessageFileRequest,
|
||||
MessageImageRequest,
|
||||
MessageLinkPreviewRequest,
|
||||
MessageLocationRequest,
|
||||
MessageReactionRequest,
|
||||
MessageReplyRequest,
|
||||
MessageTextButtonsRequest,
|
||||
MessageTextRequest,
|
||||
MessageVoiceRequest,
|
||||
SendSeenRequest,
|
||||
} from '../structures/chatting.dto';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from './exceptions';
|
||||
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
|
||||
import {
|
||||
CreateGroupRequest,
|
||||
ParticipantsRequest,
|
||||
} from '../structures/groups.dto';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const qrcode = require('qrcode-terminal');
|
||||
|
||||
|
||||
export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
whatsapp: Client;
|
||||
engine = WhatsappEngine.NOWEB;
|
||||
|
||||
protected buildClient() {
|
||||
return new Client({
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
executablePath: "/usr/bin/google-chrome-stable",
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
}
|
||||
});
|
||||
whatsapp: Client;
|
||||
|
||||
protected buildClient() {
|
||||
return new Client({
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
executablePath: this.getBrowserExecutablePath(),
|
||||
args: this.getBrowserArgsForPuppeteer(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.whatsapp = this.buildClient();
|
||||
|
||||
this.whatsapp.initialize().catch((error) => {
|
||||
this.status = WhatsappStatus.FAILED;
|
||||
this.log.error(error);
|
||||
return;
|
||||
});
|
||||
|
||||
// Connect events
|
||||
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
|
||||
qrcode.generate(qr, { small: true });
|
||||
this.status = WhatsappStatus.SCAN_QR_CODE;
|
||||
});
|
||||
|
||||
this.whatsapp.on(Events.AUTHENTICATED, () => {
|
||||
this.status = WhatsappStatus.WORKING;
|
||||
this.log.log(`Session '${this.name}' has been authenticated!`);
|
||||
});
|
||||
this.events.emit(WAHAInternalEvent.engine_start);
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.whatsapp.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WhatsappStatus.FAILED) {
|
||||
throw new UnprocessableEntityException(
|
||||
`The session under FAILED status. Please try to restart it.`,
|
||||
);
|
||||
}
|
||||
return await this.whatsapp.pupPage.screenshot();
|
||||
}
|
||||
|
||||
async checkNumberStatus(
|
||||
request: CheckNumberStatusQuery,
|
||||
): Promise<WANumberExistResult> {
|
||||
const exist = await this.whatsapp.isRegisteredUser(
|
||||
this.ensureSuffix(request.phone),
|
||||
);
|
||||
return { numberExists: exist };
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest) {
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
request.text,
|
||||
);
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const buttons = request.buttons.map((button: Button) => {
|
||||
return {
|
||||
id: button.id,
|
||||
body: button.text,
|
||||
};
|
||||
});
|
||||
const message = new Buttons('', buttons, request.title, request.footer);
|
||||
return this.whatsapp.sendMessage(
|
||||
this.ensureSuffix(request.chatId),
|
||||
message,
|
||||
);
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
const options = {
|
||||
quotedMessageId: request.reply_to,
|
||||
};
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options);
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
sendVoice(request: MessageVoiceRequest) {
|
||||
throw new AvailableInPlusVersion();
|
||||
}
|
||||
|
||||
async sendLocation(request: MessageLocationRequest) {
|
||||
const location = new Location(
|
||||
request.latitude,
|
||||
request.longitude,
|
||||
request.title,
|
||||
);
|
||||
return this.whatsapp.sendMessage(request.chatId, location);
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
throw new NotImplementedByEngineError();
|
||||
}
|
||||
|
||||
async sendSeen(request: SendSeenRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.sendSeen();
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.sendStateTyping();
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId);
|
||||
await chat.clearState();
|
||||
}
|
||||
|
||||
async getMessages(query: GetMessageQuery) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(
|
||||
this.ensureSuffix(query.chatId),
|
||||
);
|
||||
const messages = await chat.fetchMessages({
|
||||
limit: query.limit,
|
||||
});
|
||||
// Go over messages, download media, and convert to right format.
|
||||
const result = [];
|
||||
for (const message of messages) {
|
||||
result.push(await this.processIncomingMessage(message));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const messageId = this.deserializeId(request.messageId);
|
||||
// Recreate instance to react on it
|
||||
const message = new MessageInstance(this.whatsapp);
|
||||
message.id = messageId;
|
||||
message._data = { id: messageId };
|
||||
|
||||
return message.react(request.reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contacts methods
|
||||
*/
|
||||
getContact(query: ContactQuery) {
|
||||
return this.whatsapp
|
||||
.getContactById(this.ensureSuffix(query.contactId))
|
||||
.then(this.toWAContact);
|
||||
}
|
||||
|
||||
getContacts() {
|
||||
return this.whatsapp
|
||||
.getContacts()
|
||||
.then((contacts) => contacts.map(this.toWAContact));
|
||||
}
|
||||
|
||||
public async getContactAbout(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(query.contactId),
|
||||
);
|
||||
return { about: await contact.getAbout() };
|
||||
}
|
||||
|
||||
public async getContactProfilePicture(query: ContactQuery) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(query.contactId),
|
||||
);
|
||||
return { profilePictureURL: await contact.getProfilePicUrl() };
|
||||
}
|
||||
|
||||
public async blockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(request.contactId),
|
||||
);
|
||||
await contact.block();
|
||||
}
|
||||
|
||||
public async unblockContact(request: ContactRequest) {
|
||||
const contact = await this.whatsapp.getContactById(
|
||||
this.ensureSuffix(request.contactId),
|
||||
);
|
||||
await contact.unblock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group methods
|
||||
*/
|
||||
public createGroup(request: CreateGroupRequest) {
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return this.whatsapp.createGroup(request.name, participantIds);
|
||||
}
|
||||
|
||||
public getGroups() {
|
||||
return this.whatsapp
|
||||
.getChats()
|
||||
.then((chats) => chats.filter((chat) => chat.isGroup));
|
||||
}
|
||||
|
||||
public getGroup(id) {
|
||||
return this.whatsapp.getChatById(id);
|
||||
}
|
||||
|
||||
public async deleteGroup(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.delete();
|
||||
}
|
||||
|
||||
public async leaveGroup(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.leave();
|
||||
}
|
||||
|
||||
public async setDescription(id, description) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setDescription(description);
|
||||
}
|
||||
|
||||
public async setSubject(id, subject) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.setSubject(subject);
|
||||
}
|
||||
|
||||
public async getInviteCode(id): Promise<string> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.getInviteCode();
|
||||
}
|
||||
|
||||
public async revokeInviteCode(id): Promise<string> {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
await groupChat.revokeInvite();
|
||||
return groupChat.getInviteCode();
|
||||
}
|
||||
|
||||
public async getParticipants(id) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
return groupChat.participants;
|
||||
}
|
||||
|
||||
public async addParticipants(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.addParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async removeParticipants(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.removeParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async promoteParticipantsToAdmin(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.promoteParticipants(participantIds);
|
||||
}
|
||||
|
||||
public async demoteParticipantsToUser(id, request: ParticipantsRequest) {
|
||||
const groupChat = (await this.whatsapp.getChatById(id)) as GroupChat;
|
||||
const participantIds = request.participants.map(
|
||||
(participant) => participant.id,
|
||||
);
|
||||
return groupChat.demoteParticipants(participantIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
subscribe(event, handler) {
|
||||
if (event === WAEvents.MESSAGE) {
|
||||
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAEvents.MESSAGE_ANY) {
|
||||
this.whatsapp.on(Events.MESSAGE_CREATE, (message) =>
|
||||
this.processIncomingMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAEvents.STATE_CHANGE) {
|
||||
this.whatsapp.on(Events.STATE_CHANGED, handler);
|
||||
} else if (event === WAEvents.MESSAGE_ACK) {
|
||||
// We do not download media here
|
||||
this.whatsapp.on(Events.MESSAGE_ACK, (message) =>
|
||||
this.toWAMessage(message).then(handler),
|
||||
);
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
this.whatsapp.on(Events.GROUP_JOIN, handler);
|
||||
} else if (event === WAEvents.GROUP_LEAVE) {
|
||||
this.whatsapp.on(Events.GROUP_LEAVE, handler);
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(
|
||||
`Engine does not support webhook event: ${event}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id._serialized,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: message.location,
|
||||
vCards: message.vCards,
|
||||
_data: message.rawData,
|
||||
});
|
||||
}
|
||||
|
||||
protected toWAContact(contact: Contact) {
|
||||
// @ts-ignore
|
||||
contact.id = contact.id._serialized;
|
||||
return contact;
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.whatsapp = this.buildClient()
|
||||
|
||||
this.whatsapp.initialize().catch(error => {
|
||||
this.status = WhatsappStatus.FAILED
|
||||
this.log.error(error)
|
||||
return
|
||||
});
|
||||
|
||||
// Connect events
|
||||
this.whatsapp.on(Events.QR_RECEIVED, (qr) => {
|
||||
qrcode.generate(qr, {small: true});
|
||||
this.status = WhatsappStatus.SCAN_QR_CODE
|
||||
});
|
||||
|
||||
this.whatsapp.on(Events.AUTHENTICATED, () => {
|
||||
this.status = WhatsappStatus.WORKING
|
||||
this.log.log(`Session '${this.name}' has been authenticated!`)
|
||||
});
|
||||
return this
|
||||
}
|
||||
|
||||
stop() {
|
||||
return this.whatsapp.pupPage.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* START - Methods for API
|
||||
*/
|
||||
async getScreenshot(): Promise<Buffer | string> {
|
||||
if (this.status === WhatsappStatus.FAILED) {
|
||||
throw new UnprocessableEntityException(`The session under FAILED status. Please try to restart it.`);
|
||||
}
|
||||
return await this.whatsapp.pupPage.screenshot()
|
||||
}
|
||||
|
||||
checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
sendText(request: MessageTextRequest): Promise<WAMessage> {
|
||||
return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), request.text).then(this.toWAMessage)
|
||||
}
|
||||
|
||||
sendTextButtons(request: MessageTextButtonsRequest) {
|
||||
const message = new Buttons("", request.buttons, request.title, request.text)
|
||||
return this.whatsapp.sendMessage(this.ensureSuffix(request.chatId), message).then(this.toWAMessage)
|
||||
}
|
||||
|
||||
sendContactVCard(request: MessageContactVcardRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
reply(request: MessageReplyRequest) {
|
||||
const options = {
|
||||
quotedMessageId: request.reply_to,
|
||||
};
|
||||
return this.whatsapp.sendMessage(request.chatId, request.text, options).then(this.toWAMessage)
|
||||
}
|
||||
|
||||
sendImage(request: MessageImageRequest) {
|
||||
throw new AvailableInPlusVersion()
|
||||
}
|
||||
|
||||
sendFile(request: MessageFileRequest) {
|
||||
throw new AvailableInPlusVersion()
|
||||
}
|
||||
|
||||
sendVoice(request: MessageVoiceRequest) {
|
||||
throw new AvailableInPlusVersion()
|
||||
}
|
||||
|
||||
sendLinkPreview(request: MessageLinkPreviewRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
sendLocation(request: MessageLocationRequest) {
|
||||
throw new NotImplementedByEngineError()
|
||||
}
|
||||
|
||||
async sendSeen(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
|
||||
await chat.sendSeen()
|
||||
}
|
||||
|
||||
async startTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
|
||||
await chat.sendStateTyping()
|
||||
}
|
||||
|
||||
async stopTyping(request: ChatRequest) {
|
||||
const chat: Chat = await this.whatsapp.getChatById(request.chatId)
|
||||
await chat.clearState()
|
||||
}
|
||||
|
||||
async setReaction(request: MessageReactionRequest) {
|
||||
const messageId = this.deserializeId(request.messageId)
|
||||
console.log(messageId)
|
||||
|
||||
// Recreate instance to react on it
|
||||
const message = new MessageInstance(this.whatsapp)
|
||||
message.id = messageId
|
||||
message._data = {id: messageId}
|
||||
|
||||
return message.react(request.reaction)
|
||||
}
|
||||
|
||||
/**
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
subscribe(event, handler) {
|
||||
if (event === WAEvents.MESSAGE) {
|
||||
this.whatsapp.on(Events.MESSAGE_RECEIVED, (message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.MESSAGE_ANY) {
|
||||
this.whatsapp.on(Events.MESSAGE_CREATE, (message) => this.processIncomingMessage(message).then(handler))
|
||||
} else if (event === WAEvents.STATE_CHANGE) {
|
||||
this.whatsapp.on(Events.STATE_CHANGED, handler)
|
||||
} else if (event === WAEvents.MESSAGE_ACK) {
|
||||
// We do not download media here
|
||||
this.whatsapp.on(Events.MESSAGE_ACK, (message) => this.toWAMessage(message).then(handler))
|
||||
} else if (event === WAEvents.GROUP_JOIN) {
|
||||
this.whatsapp.on(Events.GROUP_JOIN, handler)
|
||||
} else if (event === WAEvents.GROUP_LEAVE) {
|
||||
this.whatsapp.on(Events.GROUP_LEAVE, handler)
|
||||
} else {
|
||||
throw new NotImplementedByEngineError(`Engine does not support webhook event: ${event}`)
|
||||
}
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadMedia(message).then(this.toWAMessage)
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve({
|
||||
id: message.id._serialized,
|
||||
timestamp: message.timestamp,
|
||||
from: message.from,
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: message.location,
|
||||
vCards: message.vCards,
|
||||
_data: message.rawData,
|
||||
})
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id._serialized, "", undefined)
|
||||
return message
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(
|
||||
message.id._serialized,
|
||||
'',
|
||||
undefined,
|
||||
);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,50 @@
|
||||
import {DOCS_URL} from "./exceptions";
|
||||
import {MediaStorage} from "./abc/storage.abc";
|
||||
import { DOCS_URL } from './exceptions';
|
||||
import { LocalSessionStorage, MediaStorage } from './abc/storage.abc';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
|
||||
export class MediaStorageCore implements MediaStorage {
|
||||
async save(messageId: string, mimetype: string, buffer: Buffer): Promise<string>{
|
||||
return Promise.resolve(`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`)
|
||||
}
|
||||
async save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string> {
|
||||
return Promise.resolve(
|
||||
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class SessionStorageCore extends LocalSessionStorage {
|
||||
public sessionsFolder: string;
|
||||
|
||||
constructor(engine: string) {
|
||||
super(engine);
|
||||
this.sessionsFolder = path.join(os.tmpdir(), 'waha-');
|
||||
}
|
||||
|
||||
get engineFolder() {
|
||||
return path.join(this.sessionsFolder, this.engine);
|
||||
}
|
||||
|
||||
init() {
|
||||
fs.mkdirSync(this.engineFolder, { recursive: true });
|
||||
}
|
||||
|
||||
getFolderPath(name: string): string {
|
||||
return path.join(this.engineFolder, name);
|
||||
}
|
||||
|
||||
clean(sessionName: string) {
|
||||
const folder = this.getFolderPath(sessionName);
|
||||
fs.rmSync(folder, { recursive: true });
|
||||
}
|
||||
|
||||
getAll(): string[] {
|
||||
return fs
|
||||
.readdirSync(this.engineFolder, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { VERSION } from '../version';
|
||||
import { WhatsappConfigService } from '../config.service';
|
||||
|
||||
export class SwaggerModuleCore {
|
||||
configure(app: INestApplication) {
|
||||
this.setUpAuth(app);
|
||||
const builder = new DocumentBuilder();
|
||||
|
||||
builder
|
||||
.setTitle('WAHA - WhatsApp HTTP API')
|
||||
.setDescription('WhatsApp HTTP API that you can configure in a click!')
|
||||
.setExternalDoc('Documentation', 'https://waha.devlike.pro/')
|
||||
.setVersion(VERSION.version)
|
||||
.addTag('sessions', 'Control WhatsApp sessions')
|
||||
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
|
||||
.addTag('chatting', 'Chat methods')
|
||||
.addTag(
|
||||
'contacts',
|
||||
`Contacts methods.<br>
|
||||
Use phone number (without +) or phone number and \`@c.us\` at the end as \`contactId\`.<br>
|
||||
'E.g: \`12312312310\` OR \`12312312310@c.us\`<br>`,
|
||||
)
|
||||
.addTag('groups', `Groups methods.<br>`)
|
||||
.addTag('other', 'Other methods')
|
||||
.addApiKey({
|
||||
type: 'apiKey',
|
||||
description: 'Your secret api key',
|
||||
name: 'X-Api-Key',
|
||||
});
|
||||
|
||||
const config = app.get(WhatsappConfigService);
|
||||
if (config.getSwaggerAdvancedConfigEnabled()) {
|
||||
builder.addServer('{protocol}://{host}:{port}/{baseUrl}', '', {
|
||||
protocol: {
|
||||
default: 'http',
|
||||
enum: ['http', 'https'],
|
||||
description: 'The protocol used to access the server.',
|
||||
},
|
||||
host: {
|
||||
default: config.hostname,
|
||||
description: 'The hostname or IP address of the server.',
|
||||
},
|
||||
port: {
|
||||
default: config.port,
|
||||
description:
|
||||
'The port number on which the server is listening for requests',
|
||||
},
|
||||
baseUrl: {
|
||||
default: '',
|
||||
description:
|
||||
'The base URL path for all API endpoints. This can be used to group related endpoints together under a common path.',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const options = builder.build();
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
SwaggerModule.setup('', app, document);
|
||||
}
|
||||
|
||||
protected setUpAuth(app: INestApplication) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
+72
-65
@@ -1,76 +1,83 @@
|
||||
import {WhatsappSession} from "./abc/session.abc";
|
||||
import {ConsoleLogger} from "@nestjs/common";
|
||||
import {WAEvents} from "../structures/enums.dto";
|
||||
import {WAWebhook} from "../structures/responses.dto";
|
||||
import {NotImplementedByEngineError} from "./exceptions";
|
||||
import {WebhookConductor} from "./abc/webhooks.abc";
|
||||
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
import { WAEvents } from '../structures/enums.dto';
|
||||
import { WAWebhook } from '../structures/responses.dto';
|
||||
import { NotImplementedByEngineError } from './exceptions';
|
||||
import { WebhookConductor } from './abc/webhooks.abc';
|
||||
import request = require('request');
|
||||
|
||||
|
||||
export class WebhookConductorCore implements WebhookConductor {
|
||||
constructor(protected log: ConsoleLogger, private readonly url, private readonly events: WAEvents[] | string[]) {
|
||||
this.url = url
|
||||
this.events = this.getSuitableEvents(events)
|
||||
this.log = log
|
||||
constructor(
|
||||
protected log: ConsoleLogger,
|
||||
private readonly url,
|
||||
private readonly events: WAEvents[] | string[],
|
||||
) {
|
||||
this.url = url;
|
||||
this.events = this.getSuitableEvents(events);
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
private getSuitableEvents(events: WAEvents[] | string[]) {
|
||||
const allEvents = Object.values(WAEvents);
|
||||
|
||||
// Enable all events if * in the events
|
||||
// @ts-ignore
|
||||
if (events.includes('*')) {
|
||||
return allEvents;
|
||||
}
|
||||
|
||||
private getSuitableEvents(events: WAEvents[] | string[]) {
|
||||
const allEvents = Object.values(WAEvents)
|
||||
// Get only known events, log and ignore others
|
||||
const rightEvents = [];
|
||||
for (const event of events) {
|
||||
// @ts-ignore
|
||||
if (!allEvents.includes(event)) {
|
||||
this.log.error(`Unknown event for webhook: '${event}'`);
|
||||
continue;
|
||||
}
|
||||
rightEvents.push(event);
|
||||
}
|
||||
return rightEvents;
|
||||
}
|
||||
|
||||
// Enable all events if * in the events
|
||||
// @ts-ignore
|
||||
if (events.includes("*")) {
|
||||
return allEvents
|
||||
public configure(session: WhatsappSession) {
|
||||
this.log.log('Configuring webhooks...');
|
||||
for (const event of this.events) {
|
||||
try {
|
||||
session.subscribe(event, (data: any) =>
|
||||
this.callWebhook(event, session, data, this.url),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof NotImplementedByEngineError) {
|
||||
this.log.error(error);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Get only known events, log and ignore others
|
||||
const rightEvents = []
|
||||
for (const event of events) {
|
||||
// @ts-ignore
|
||||
if (!allEvents.includes(event)) {
|
||||
this.log.error(`Unknown event for webhook: '${event}'`)
|
||||
continue
|
||||
}
|
||||
rightEvents.push(event)
|
||||
}
|
||||
return rightEvents
|
||||
}
|
||||
this.log.log(`Event '${event}' is enabled for url: ${this.url}`);
|
||||
}
|
||||
this.log.log('Webhooks were configured.');
|
||||
}
|
||||
|
||||
public configure(session: WhatsappSession) {
|
||||
this.log.log('Configuring webhooks...')
|
||||
for (const event of this.events) {
|
||||
try {
|
||||
session.subscribe(event, (data: any) => this.callWebhook(event, data, this.url))
|
||||
} catch (error) {
|
||||
if (error instanceof NotImplementedByEngineError) {
|
||||
this.log.error(error)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
this.log.log(`Event '${event}' is enabled for url: ${this.url}`)
|
||||
}
|
||||
this.log.log('Webhooks were configured.')
|
||||
}
|
||||
public callWebhook(event, session: WhatsappSession, data: any, url) {
|
||||
const json: WAWebhook = {
|
||||
event: event,
|
||||
session: session.name,
|
||||
payload: data,
|
||||
engine: session.engine,
|
||||
};
|
||||
this.log.log(`Sending POST to ${url}...`);
|
||||
this.log.debug(`POST DATA: ${JSON.stringify(json)}`);
|
||||
this.post(json, url);
|
||||
}
|
||||
|
||||
public callWebhook(event, data: any, url) {
|
||||
const json: WAWebhook = {event: event, payload: data}
|
||||
this.log.log(`Sending POST to ${url}...`)
|
||||
this.log.debug(`POST DATA: ${JSON.stringify(json)}`)
|
||||
this.post(json, url)
|
||||
}
|
||||
|
||||
protected post(json, url) {
|
||||
request.post(
|
||||
url,
|
||||
{json: json},
|
||||
(error, res, body) => {
|
||||
if (error) {
|
||||
this.log.error(error)
|
||||
return
|
||||
}
|
||||
this.log.log(`POST request was sent with status code: ${res.statusCode}`)
|
||||
this.log.verbose(`Response: ${JSON.stringify(body)}`)
|
||||
})
|
||||
}
|
||||
protected post(json, url) {
|
||||
request.post(url, { json: json }, (error, res, body) => {
|
||||
if (error) {
|
||||
this.log.error(error);
|
||||
return;
|
||||
}
|
||||
this.log.log(`POST request was sent with status code: ${res.statusCode}`);
|
||||
this.log.verbose(`Response: ${JSON.stringify(body)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export function parseBool(value: any): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (value.toLowerCase() === 'true' || value.toLowerCase() === '1') {
|
||||
return true;
|
||||
}
|
||||
if (value.toLowerCase() === 'false' || value.toLowerCase() === '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
console.error(
|
||||
'parseBool got an unexpected value:',
|
||||
value,
|
||||
'(accepted values : "true", "false")',
|
||||
);
|
||||
throw new Error(
|
||||
'Error: parseBool got unexpected value - use "true" or "false" values',
|
||||
);
|
||||
}
|
||||
+43
-44
@@ -1,54 +1,53 @@
|
||||
import {NestFactory} from '@nestjs/core';
|
||||
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
|
||||
import {WhatsappConfigService} from "./config.service";
|
||||
import {AllExceptionsFilter} from "./api/exception.filter";
|
||||
import {getWAHAVersion, VERSION, WAHAVersion} from "./version";
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { WhatsappConfigService } from './config.service';
|
||||
import { AllExceptionsFilter } from './api/exception.filter';
|
||||
import { getWAHAVersion, VERSION, WAHAVersion } from './version';
|
||||
import { AppModuleCore } from './core/app.module.core';
|
||||
import { SwaggerModuleCore } from './core/swagger.module.core';
|
||||
import { json, urlencoded } from 'express';
|
||||
|
||||
async function getAppModule() {
|
||||
const version = getWAHAVersion()
|
||||
console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`)
|
||||
async function loadModules(): Promise<
|
||||
[typeof AppModuleCore, typeof SwaggerModuleCore]
|
||||
> {
|
||||
const version = getWAHAVersion();
|
||||
console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`);
|
||||
|
||||
if (version === WAHAVersion.CORE) {
|
||||
const {AppModuleCore} = await import("./core/app.module.core")
|
||||
return AppModuleCore
|
||||
}
|
||||
// Ignore if it's core version - there's no plus module
|
||||
// @ts-ignore
|
||||
const {AppModulePlus} = await import("./plus/app.module.plus")
|
||||
return AppModulePlus
|
||||
if (version === WAHAVersion.CORE) {
|
||||
const { AppModuleCore } = await import('./core/app.module.core');
|
||||
const { SwaggerModuleCore } = await import('./core/swagger.module.core');
|
||||
return [AppModuleCore, SwaggerModuleCore];
|
||||
}
|
||||
// Ignore if it's core version - there's no plus module
|
||||
// @ts-ignore
|
||||
const { AppModulePlus } = await import('./plus/app.module.plus');
|
||||
// @ts-ignore
|
||||
const { SwaggerModulePlus } = await import('./plus/swagger.module.plus');
|
||||
return [AppModulePlus, SwaggerModulePlus];
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const AppModule = await getAppModule()
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: process.env.DEBUG != undefined ? ['log', 'debug', 'error', 'verbose', 'warn'] :
|
||||
['log', 'error', 'warn'],
|
||||
});
|
||||
const [AppModule, SwaggerModule] = await loadModules();
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger:
|
||||
process.env.DEBUG != undefined
|
||||
? ['log', 'debug', 'error', 'verbose', 'warn']
|
||||
: ['log', 'error', 'warn'],
|
||||
});
|
||||
|
||||
app.enableShutdownHooks();
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
const options = new DocumentBuilder()
|
||||
.setTitle('WAHA - WhatsApp HTTP API')
|
||||
.setDescription('WhatsApp HTTP API that you can configure in a click!')
|
||||
.setExternalDoc("Documentation", "https://waha.devlike.pro/")
|
||||
.setVersion(VERSION.version)
|
||||
.addTag('sessions', 'Control your WhatsApp sessions')
|
||||
.addTag('screenshot', 'Get screenshot of WhatsApp and show QR code')
|
||||
.addTag('chatting', 'Chat methods')
|
||||
.addTag('other', 'Other endpoints')
|
||||
.addApiKey({
|
||||
type: 'apiKey',
|
||||
description: 'Your secret api key',
|
||||
name: 'X-Api-Key'
|
||||
}
|
||||
)
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
SwaggerModule.setup('', app, document);
|
||||
app.enableShutdownHooks();
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
|
||||
const config = app.get(WhatsappConfigService);
|
||||
await app.listen(config.port);
|
||||
console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`);
|
||||
// Allow to send big body - for images and attachments
|
||||
app.use(json({ limit: '50mb' }));
|
||||
app.use(urlencoded({ limit: '50mb', extended: false }));
|
||||
|
||||
// Configure swagger
|
||||
const swagger = new SwaggerModule();
|
||||
swagger.configure(app);
|
||||
|
||||
const config = app.get(WhatsappConfigService);
|
||||
await app.listen(config.port);
|
||||
console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
@@ -0,0 +1,19 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export const WHATSAPP_DEFAULT_SESSION_NAME = 'default';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
export class SessionQuery {
|
||||
@IsNotEmpty()
|
||||
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
export class SessionBaseRequest {
|
||||
session = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
+145
-123
@@ -1,199 +1,221 @@
|
||||
import {IsNotEmpty, IsString} from "class-validator";
|
||||
import {ApiExtraModels, ApiProperty, getSchemaPath} from "@nestjs/swagger";
|
||||
|
||||
export const WHATSAPP_DEFAULT_SESSION_NAME = "default"
|
||||
|
||||
import { IsNotEmpty, IsString, IsOptional, IsNumber } from 'class-validator';
|
||||
import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
export class SessionQuery {
|
||||
@IsNotEmpty()
|
||||
sessionName: string = WHATSAPP_DEFAULT_SESSION_NAME;
|
||||
}
|
||||
|
||||
export class CheckNumberStatusQuery extends SessionQuery {
|
||||
@IsString()
|
||||
phone: string
|
||||
@IsString()
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export class MessageTextQuery extends SessionQuery {
|
||||
@IsString()
|
||||
phone: string
|
||||
@IsString()
|
||||
text = "Hi there!"
|
||||
@IsString()
|
||||
phone: string;
|
||||
@IsString()
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class ChatQuery extends SessionQuery {
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
export class GetMessageQuery extends ChatQuery {
|
||||
@IsNumber()
|
||||
limit: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
export class SessionBaseRequest {
|
||||
session = WHATSAPP_DEFAULT_SESSION_NAME
|
||||
}
|
||||
|
||||
export class ChatRequest extends SessionBaseRequest {
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
chatId: string
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
})
|
||||
chatId: string;
|
||||
}
|
||||
|
||||
export class MessageRequest extends SessionBaseRequest{
|
||||
@ApiProperty({
|
||||
example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA",
|
||||
})
|
||||
messageId: string
|
||||
export class SendSeenRequest extends ChatRequest {
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
required: false,
|
||||
description:
|
||||
"NOWEB engine only - it's important to mark ALL messages as seen",
|
||||
})
|
||||
messageId?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '11111111111@c.us',
|
||||
required: false,
|
||||
description:
|
||||
'NOWEB engine only - the ID of the user that sent the message (undefined for individual chats)',
|
||||
})
|
||||
participant?: string;
|
||||
}
|
||||
|
||||
export class MessageRequest extends SessionBaseRequest {
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
messageId: string;
|
||||
}
|
||||
|
||||
export class MessageContactVcardRequest extends ChatRequest {
|
||||
contactsId: string;
|
||||
name: string
|
||||
contactsId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class MessageTextRequest extends ChatRequest {
|
||||
text = "Hi there!"
|
||||
text = 'Hi there!';
|
||||
}
|
||||
|
||||
export class Button {
|
||||
id: string
|
||||
body: string
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class MessageTextButtonsRequest extends ChatRequest {
|
||||
title: string;
|
||||
text: string;
|
||||
buttons?: Array<Button>;
|
||||
title: string;
|
||||
footer?: string;
|
||||
buttons?: Array<Button>;
|
||||
}
|
||||
|
||||
export class MessageReplyRequest extends ChatRequest {
|
||||
text = "Reply text"
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
reply_to: string;
|
||||
text = 'Reply text';
|
||||
@ApiProperty({
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
reply_to: string;
|
||||
}
|
||||
|
||||
export class MessageLocationRequest extends ChatRequest {
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
title: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export class RemoteFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Value can be null",
|
||||
example: 'filename.jpg',
|
||||
})
|
||||
filename?: string | null
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Value can be null',
|
||||
example: 'filename.jpg',
|
||||
})
|
||||
filename?: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
|
||||
})
|
||||
url: string
|
||||
@ApiProperty({
|
||||
example:
|
||||
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.jpg',
|
||||
})
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class BinaryFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
example: 'image/jpeg',
|
||||
})
|
||||
mimetype: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Optional",
|
||||
example: 'filename.jpeg',
|
||||
})
|
||||
filename?: string | null
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Optional',
|
||||
example: 'filename.jpeg',
|
||||
})
|
||||
filename?: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Base64-encoded data of the file",
|
||||
example: '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',
|
||||
})
|
||||
data: string
|
||||
@ApiProperty({
|
||||
description: 'Base64-encoded data of the file',
|
||||
example:
|
||||
'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',
|
||||
})
|
||||
data: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(BinaryFile, RemoteFile)
|
||||
class FileRequest extends ChatRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{$ref: getSchemaPath(BinaryFile)},
|
||||
{$ref: getSchemaPath(RemoteFile)},
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(BinaryFile) },
|
||||
{ $ref: getSchemaPath(RemoteFile) },
|
||||
],
|
||||
})
|
||||
file: BinaryFile | RemoteFile;
|
||||
}
|
||||
|
||||
export class MessageImageRequest extends FileRequest {
|
||||
caption: string;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
export class MessageFileRequest extends FileRequest {
|
||||
caption: string;
|
||||
}
|
||||
|
||||
export class VoiceBinaryFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
})
|
||||
mimetype = "audio/ogg; codecs=opus"
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
})
|
||||
mimetype = 'audio/ogg; codecs=opus';
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Optional",
|
||||
})
|
||||
filename = "voice-message.mp3"
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Optional',
|
||||
})
|
||||
filename = 'voice-message.mp3';
|
||||
|
||||
@ApiProperty({
|
||||
description: "Base64-encoded data of the file",
|
||||
example: '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
Line truncated
|
||||
})
|
||||
data: string
|
||||
@ApiProperty({
|
||||
description: 'Base64-encoded data of the file',
|
||||
example:
|
||||
'SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//NkxAAW0D4AVN4YAAYAKqTsSE2Z0IlkQMKANEGBUWH7a99t8eFgGQqfrfJxAsocfid8uizPyg535NQgTSs/z6xwJlJc/XKOE8uQtJsTP7ym4u/ky78nQzJw+t+4TvEiw/UftU4TymUVBiqj2qVLlF8X+bWGBLTBZi2+uOml4gA4HEVKHj2Fw89os529yuxD//NkxCcUAGI0FOJGKNWEHAhP3y5/+LZc5OU6X9RI+9M5IZc/fOaJf5y6cTjSBfLxrzjCnoSUCNUgnzokmHoBlzC6CGSrXGjEOPedHzBCkYcOBBpTTNe9/8tGfO5M4inBdS7PsvVusm91LKv4Ycftpwq4YATDG98/QyhKd3V7mZn7wZb2yrt3Y0fiGYpP4aUa//NkxFokCoIwNOpMRV7E1Kqm9vJ123c1F9bGVSKnasqqcezN/4ar26Uz3P8M/AgXq/+6ctsqX+fW6UmfCJ79o9g/CqpFrrpE78sS2V8ApIpklEE02r9II4IDbdNNnFC4dREiIgJFHHcOZjvE9lBMgBQUZzqdOmMDc97ku5pyFwNDph6+LaOyjKaRA6x4x2sc//NkxEwZkcZIVNpGCBophiL5tglY4Lj3qeQceTdr6ur/R11nlO/6KuHORu5SCMucIAApqsZ1I2CBCFxtWZ1tzE3pcvJD87eocViSFHDFuRXMs+q62dVX4O2GtELkoUrIfEbh7506+CEA0VUxhsBLvWABIfGtS8OCcALHMrWskk2pbXJuOEheYHvtaFW9arRY//NkxGgc+ZY8COJGVJsJX1dZg8q3QVzqEQCJapAB18VGV0Kcf+J8F8mNnEbyRRigCGBwCm4AQGjWWcBwIbSN5OhOjQgmU8f6ry3stZNsXzEDM1r44o0zCIuyJMgAejNzadpOkuIVTwXA/BS9ep5F8IvMiz9tM16cuR2SKoggy2MZQtg8BNAidnuNVpsFLpx5//NkxHcgEjJFdOGGtCyrFeW67L62epUITeB47MPJE/N7DOhVOlzQ3shzlhFM2gkw2MBQ0zxwHeTFmAKsdmTYnkf6VQlp4GCOOLPWCLF3sYHocCoKThOYp41Wo93UtOhKLnDyelr+f407RfQ0loWe5+a43SqraakgBnwG4POuQSELhVgoHwVB0OoeCgYj442U//NkxHkjKbZIyuZQTO/HTN86uNWKDHSjaBJ/RdqIU9N6adPSJcjBpAjd5ZQMRpirXYsZphSRhl2BiwAD9ZAHGGWBEzHAU3Ws11wH6d2fdF/sqG9P8pbEqpa2MNUcah+aizKGY8v4+WWNtiSTbbMmyf3K+U5Rez8xte6f9ZqzAIjDxVp4AEmjlm3KWLyoJw1r//NkxG8kSXpQMu5MWDLiYSGiEC1DUiwsLihhGVbL4DpupqbFV9eMCtpMfeuq2rdVeZVjIMHTE8nDqsWwcQxmk3BgCBxhAbhgGDBVBEP6XAIhRMwmTmYYwZ247Uc+cECwUeim6NFyz5cgE0SixFn+fd5Sb0kClO5up2jHtvXhqfSgK48ZRY+xgCaKhmJRI9lr//NkxGAhIT5UFO4MTFtqa6x7kuCp0aq5TakV0uFEJq1b3N9XYzKlRXJPaSjSxK16nqUZU4nVxqUFGJXeenWhgstGlJWAiyYfWYQCQYElQINEwKcFLFqIG1Zg8UvWrXmn1urARzCgoBTosSTkRcfBUVbsDXM62KmDEAkBJIYuNDHnGAAaYAyAwGwK6dYB7GPQ//NkxF4e4R5YXOMGiLWuWmL28y2xduEEPX16UzKUPFHrolYu8zXv879xhWqbVph6jGInGdqQwcFDlqOMrjUxIck9jAYAaslNTOm3mOyEoAq0pPsjKCa8QSZV7WmomOiijP1smCamch4MWEIfTcdiD/8MysS3MIj1h6NBhMoXeaAARIGjNLI4WOMDQOLS4+kR//NkxGUgaXZYVOGGlIXFBZoDP07hI3oN61pXNfrczNjykgKtFEu64tDaAKkkkkkkkiuSIFcvY9AuCM1psOiZD1e2RL5UFGSfVFOxKiSZcOB/rRbEIby4QS/l7HwSQ50PdnO5vF8uaYLg9T7o/0LGAVoE+AnnUhEqHqN+2EiZOousyjbkKCoGzIXFQGBGBcEg//NkxGY11Dq1vnpNhsAoeI90gZTMogfZUtHJiDaiLhMLEIFjgi/rrIXF+zNMlMwtIAFwQc+pTaGwtePD6+tl7j0comzEIh1EyQYLbHpcQ5SF7kZd1NIWQyNQKRC9JnDD6UFiJIGrIKBzkPl2Uq4cmamQz7+7tOxl7FtEH0h06g4OqqDJTBgSqEvrWRlEECjC//NkxBEhKYKUANPSnD4CG3gCwLkHoBMqTKUaUkraqCzctWV+m1bkb8u0ouTmawYa28AxqoQtKHeXrBv5cEpBP6IK/CcgM8Ey2hPrVRHqMT4cYXQFwbZAcIhcmTBOaMnhVz81zINiN///8sMPC3/7n/+qticPkhrRgq5w0OPcxplVBAMcSK16xyfBu6V5E4VO//NkxA8fQe7SVnsE3sqlEuhK9fjDZLxzi6SLBUEewl/scsTkeqdE9uFRUHGoaSs6r+2lvfUOiQ9kXdOx7B+1x2jPQvKC7RPl2PzZ7uzXe1f/5SkZoy2lzQ2FA6cWKCptRB0u3/6Vf+0SoKiglKox7AQEgJh8ISADd6fuBIAUaKk9cslsqjVbKmbz0PPY5Q1+//NkxBUfIaK01sPSlh5ZFGEiMvdwnWn2oMTLJTM2J8t0RhfRR2tLEUkrDGLOos4rOIYcCqAiKoiuom7r2xebLJIlTx44fjk7d8n83YRe957vMH/cwSKL6Ey973mSpMgGTlosv1ecaUut/hE0ILvvGRox6nk1AL2mPfUBUZaSvgWZbhpRdzWxrQ2qsfS4Qmi6//NkxBsgWWKwFsPMnLLjPbZm/+Hp/Fw44c4jYeLAJohxuQaL0ZST7Y3rG5bVb0kahY09mDWHbJTQ7lcxyAsgLkCImGpSeLFqjKWO+szt1WrMpSDqwjc80JbaLzItpImQmNBnf/t1/SBQZHiEsLNJUhlwbQoEgDWNNauCKdiupuITWqBhmGTVEDhJolUxTqPG//NkxBwc4Wq81nsMlh7aUSRnfU3mOSHKcXHggNr2WWu5xRtopJoCBLrRrL1WX2xrcpnD6LkCyUmR0yUWoJjCR77Sw9mBXULCT54Yu0Xrdb/hVS0I3/mTSw7/w6GyoBSMHjLXpqqgkAv1krkkRYNGdtnWV3uRCZjY3Di3rqbqSNhlzSWI00OhwAAcQvlyZaGo//NkxCsdK+rM9njE+zCBnEkZT8iKUmbJBEcUCMSNAxxiWZoqSN//tlV5mb7aKrPeulvuyohyLP////rv03ns6IrqtNN6p3LrKhFRn0akUwsm8ZkkyhudZqIlO/a1dASkgCFg25A5g4wH3DYgeONWRWR5F0rSuv35UUCWGdFkme7Wioowdlp+tKTOglxQIdDs//NkxDkcgZ7U/lmEllYr/sU44cWEhi+sCpF31D0rAKFKjD0tGAkIsfWlkkkieDodcZLB37qXxcNOSQIHaGhVNV6qqhR1SlSSUm1lRiYWROH6A2xUkPSoFND1ytKPu+bBqK93TKimC1i0iELlCdh2brUzdCKYEJBmO22uLhva2uLZ3EQvGqNqW2+KiYpnIZqf//NkxEodEoLEXmIE3uFdKU1ft6UWWu/NtOV2MFJBARFv915Fwi//ijKZ5QPtahDwbPvMOOPta9UJJad1sibIEcAnXUzOin78wDRaiSuCjubzcE4XF3jy82iLCgZdHNdXe0NttCoCkSCqQUsqtTX1rrQ0VUezN/9/F8WRyxaMdBFmkPGnrCA4XUpoG/xKGlVK//NkxFgdUa6w3nmQkoUGufspBpZEMpRb92LyRGYOuPLBUNETxFC+eZYI1QBiWo7bv9cbC4RBNgU2ZOzJlF2I6hncW0XFxQq5joOD4qV1k0mFqK+c0awzlTSR0s1t3Fwl6uZIKmCg7bWIdsjgSDKRpnRGr8vcxrIbKzUVl7316SOkxjs9CJen///Npyf///f///NkxGUc4869vkoEv/MjM0zQT+PLHf03+FAppQiO26Si0wExQTgQZNKKhYVXL/KdqhblIpuWqJEkZJao2zpmnQJzwo/KzF2SsevxgrWgg8ogPCHJC+ZGYI2ADMGCNweE3kigIuDQFGDEH4qBwcUYUz+bXbe4AMoPu/7s24QUHaPJvozFL7kkmPLhgUeLCPpU//NkxHQdQYKAPsmGkBUB40UDw1eB2YYqrAq9/o+y1xKjtHtsXBr0nSf2Vwmm+QRJlXz1m1IVzyOFGEgDDGZVaz3eRW2U4RFBamZ93qsjBtD5znsr2aIZnVX2RKpDvajaEnvzn37TvfX9f/faT/t///8i6rX3INPKHWGrJkprEf6xqTGg6UAvtPBqpnPTuNw1//NkxIIcA758FspEWAuYStUOFnAwRayKry+iKyESNAqfXOu5qY5aIXsruZdM/Z6b1jIKxB4uh2+ai4eSFYcK4fVqaeiZnNyp575zQoX5/c34fn8L/0fInJ48l+F09KZX4/8eN+rndzx47njOiPmj3Nd6SOQRWBYTMnDxEcIuFllGECkpxosYIVIcSiVJ51w+//NkxJUf9DqIFMGGMBolGBvMSYphcBJDXSmhMFFUsV27MeGBQHxCBzaaMCtwETeeCALA0JwkK1nBt2pRB7HrBckAWrpF4kJn0LYWQwa0URuPUN5l7LLlrWrvMhMuPBVwCCIFDLUqLslhZQTOEZoFjKZSQq07QHBQoX0DkWRTrgWmFVJSeW5NkVpKKXrclWbD//NkxJgcYPqSNoGGDBXTKSmTqRYo2Geff0vxs/Ve9dGEwGFWSYiWdaxBoqdDLZ+WPRaFSICcLmgghZVzUDgKz1IZYOfD3YlyEq1PNCo0XEoCUwGh0Ep0TLyRYDDhRQUDKSXe
Line truncated
|
||||
})
|
||||
data: string;
|
||||
}
|
||||
|
||||
export class VoiceRemoteFile {
|
||||
@ApiProperty({
|
||||
description: "MIME type of the attachment.",
|
||||
})
|
||||
mimetype = "audio/ogg; codecs=opus"
|
||||
@ApiProperty({
|
||||
description: 'MIME type of the attachment.',
|
||||
})
|
||||
mimetype = 'audio/ogg; codecs=opus';
|
||||
|
||||
@ApiProperty({
|
||||
description: "Document file name. Optional",
|
||||
})
|
||||
filename = "voice-message.mp3"
|
||||
|
||||
@ApiProperty({
|
||||
example: 'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
|
||||
})
|
||||
url: string
|
||||
@ApiProperty({
|
||||
description: 'Document file name. Optional',
|
||||
})
|
||||
filename = 'voice-message.mp3';
|
||||
|
||||
@ApiProperty({
|
||||
example:
|
||||
'https://github.com/devlikeapro/whatsapp-http-api/raw/core/examples/dev.likeapro.mp3',
|
||||
})
|
||||
url: string;
|
||||
}
|
||||
|
||||
@ApiExtraModels(VoiceBinaryFile, VoiceRemoteFile)
|
||||
export class MessageVoiceRequest extends ChatRequest {
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{$ref: getSchemaPath(VoiceBinaryFile)},
|
||||
{$ref: getSchemaPath(VoiceRemoteFile)},
|
||||
],
|
||||
})
|
||||
file: VoiceBinaryFile | VoiceRemoteFile
|
||||
@ApiProperty({
|
||||
oneOf: [
|
||||
{ $ref: getSchemaPath(VoiceBinaryFile) },
|
||||
{ $ref: getSchemaPath(VoiceRemoteFile) },
|
||||
],
|
||||
})
|
||||
file: VoiceBinaryFile | VoiceRemoteFile;
|
||||
}
|
||||
|
||||
export class MessageLinkPreviewRequest extends ChatRequest {
|
||||
url: string;
|
||||
title: string;
|
||||
url: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export class MessageReactionRequest extends MessageRequest {
|
||||
@ApiProperty({
|
||||
description: "Emoji to react with. Send an empty string to remove the reaction",
|
||||
example: '👍',
|
||||
})
|
||||
reaction: string
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Emoji to react with. Send an empty string to remove the reaction',
|
||||
example: '👍',
|
||||
})
|
||||
reaction: string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { SessionBaseRequest, SessionQuery } from './base.dto';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
|
||||
export class ContactQuery extends SessionQuery {
|
||||
@IsString()
|
||||
contactId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
export class ContactRequest extends SessionBaseRequest {
|
||||
@IsString()
|
||||
contactId: string;
|
||||
}
|
||||
+14
-12
@@ -1,22 +1,24 @@
|
||||
export const SECOND = 1000;
|
||||
|
||||
export enum WAEvents {
|
||||
MESSAGE = "message",
|
||||
MESSAGE_ANY = "message.any",
|
||||
MESSAGE_ACK = "message.ack",
|
||||
STATE_CHANGE = "state.change",
|
||||
GROUP_JOIN = "group.join",
|
||||
GROUP_LEAVE = "group.leave",
|
||||
MESSAGE = 'message',
|
||||
MESSAGE_ANY = 'message.any',
|
||||
MESSAGE_ACK = 'message.ack',
|
||||
STATE_CHANGE = 'state.change',
|
||||
GROUP_JOIN = 'group.join',
|
||||
GROUP_LEAVE = 'group.leave',
|
||||
}
|
||||
|
||||
export enum WhatsappStatus {
|
||||
STARTING = "STARTING",
|
||||
SCAN_QR_CODE = "SCAN_QR_CODE",
|
||||
WORKING = "WORKING",
|
||||
FAILED = "FAILED",
|
||||
STOPPED = 'STOPPED',
|
||||
STARTING = 'STARTING',
|
||||
SCAN_QR_CODE = 'SCAN_QR_CODE',
|
||||
WORKING = 'WORKING',
|
||||
FAILED = 'FAILED',
|
||||
}
|
||||
|
||||
export enum WhatsappEngine {
|
||||
VENOM = "VENOM",
|
||||
WEBJS = "WEBJS"
|
||||
VENOM = 'VENOM',
|
||||
WEBJS = 'WEBJS',
|
||||
NOWEB = 'NOWEB',
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IsArray, IsString } from 'class-validator';
|
||||
|
||||
/**
|
||||
* Structures
|
||||
*/
|
||||
export class Participant {
|
||||
@IsString()
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
|
||||
export class ParticipantsRequest {
|
||||
@IsArray()
|
||||
participants: Array<Participant>;
|
||||
}
|
||||
|
||||
export class DescriptionRequest {
|
||||
@IsString()
|
||||
description: string;
|
||||
}
|
||||
export class SubjectRequest {
|
||||
@IsString()
|
||||
subject: string;
|
||||
}
|
||||
|
||||
export class CreateGroupRequest {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsArray()
|
||||
participants: Array<Participant>;
|
||||
}
|
||||
+110
-100
@@ -1,138 +1,148 @@
|
||||
import {ApiProperty} from "@nestjs/swagger";
|
||||
import {WAEvents} from "./enums.dto";
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAEvents, WhatsappEngine } from './enums.dto';
|
||||
|
||||
export class WANumberExistResult {
|
||||
numberExists: boolean
|
||||
numberExists: boolean;
|
||||
}
|
||||
|
||||
|
||||
export class WAGroupNotification {
|
||||
@ApiProperty({
|
||||
description: "ID that represents the groupNotification",
|
||||
})
|
||||
id: any
|
||||
@ApiProperty({
|
||||
description: 'ID that represents the groupNotification',
|
||||
})
|
||||
id: any;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Unix timestamp for when the groupNotification was created",
|
||||
})
|
||||
timestamp: number
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp for when the groupNotification was created',
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: "ID for the Chat that this groupNotification was sent for",
|
||||
})
|
||||
chatId: string
|
||||
@ApiProperty({
|
||||
description: 'ID for the Chat that this groupNotification was sent for',
|
||||
})
|
||||
chatId: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'ContactId for the user that produced the GroupNotification',
|
||||
})
|
||||
author: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "ContactId for the user that produced the GroupNotification",
|
||||
})
|
||||
author: string
|
||||
@ApiProperty({
|
||||
description: 'Extra content',
|
||||
})
|
||||
body: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Extra content",
|
||||
})
|
||||
body: string
|
||||
|
||||
@ApiProperty({
|
||||
description: "Contact IDs for the users that were affected by this GroupNotification",
|
||||
})
|
||||
recipientIds: string[]
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Contact IDs for the users that were affected by this GroupNotification',
|
||||
})
|
||||
recipientIds: string[];
|
||||
}
|
||||
|
||||
export enum WAMessageAck {
|
||||
ACK_ERROR = -1,
|
||||
ACK_PENDING = 0,
|
||||
ACK_SERVER = 1,
|
||||
ACK_DEVICE = 2,
|
||||
ACK_READ = 3,
|
||||
ACK_PLAYED = 4,
|
||||
ACK_ERROR = -1,
|
||||
ACK_PENDING = 0,
|
||||
ACK_SERVER = 1,
|
||||
ACK_DEVICE = 2,
|
||||
ACK_READ = 3,
|
||||
ACK_PLAYED = 4,
|
||||
}
|
||||
|
||||
export class WALocation {
|
||||
description?: string | null
|
||||
latitude: string
|
||||
longitude: string
|
||||
description?: string | null;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
}
|
||||
|
||||
|
||||
export class WAMessage {
|
||||
@ApiProperty({
|
||||
description: "Message ID",
|
||||
example: "false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA"
|
||||
})
|
||||
id: string
|
||||
@ApiProperty({
|
||||
description: 'Message ID',
|
||||
example: 'false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA',
|
||||
})
|
||||
id: string;
|
||||
|
||||
/** */
|
||||
@ApiProperty({
|
||||
description: "Unix timestamp for when the message was created",
|
||||
example: 1666943582
|
||||
})
|
||||
timestamp: number
|
||||
/** */
|
||||
@ApiProperty({
|
||||
description: 'Unix timestamp for when the message was created',
|
||||
example: 1666943582,
|
||||
})
|
||||
timestamp: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: "ID for the Chat that this message was sent to, except if the message was sent by the current user "
|
||||
})
|
||||
from: string
|
||||
@ApiProperty({
|
||||
description:
|
||||
'ID for the Chat that this message was sent to, except if the message was sent by the current user ',
|
||||
})
|
||||
from: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Indicates if the message was sent by the current user"
|
||||
})
|
||||
fromMe: boolean
|
||||
@ApiProperty({
|
||||
description: 'Indicates if the message was sent by the current user',
|
||||
})
|
||||
fromMe: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: `
|
||||
@ApiProperty({
|
||||
description: `
|
||||
* ID for who this message is for.
|
||||
* If the message is sent by the current user, it will be the Chat to which the message is being sent.
|
||||
* If the message is sent by another user, it will be the ID for the current user.
|
||||
`
|
||||
})
|
||||
to: string
|
||||
`,
|
||||
})
|
||||
to: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Message content"
|
||||
})
|
||||
body: string
|
||||
@ApiProperty({
|
||||
description: 'For groups - participant who sent the message',
|
||||
})
|
||||
participant: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Indicates if the message has media available for download"
|
||||
})
|
||||
hasMedia: boolean
|
||||
@ApiProperty({
|
||||
description: 'Message content',
|
||||
})
|
||||
body: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "The URL for the media in the message if any",
|
||||
example: "http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga"
|
||||
})
|
||||
mediaUrl: string
|
||||
@ApiProperty({
|
||||
description: 'Indicates if the message has media available for download',
|
||||
})
|
||||
hasMedia: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: "ACK status for the message"
|
||||
})
|
||||
ack: WAMessageAck
|
||||
@ApiProperty({
|
||||
description: 'The URL for the media in the message if any',
|
||||
example:
|
||||
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
|
||||
})
|
||||
mediaUrl: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "If the message was sent to a group, this field will contain the user that sent the message."
|
||||
})
|
||||
author?: string;
|
||||
@ApiProperty({
|
||||
description: 'ACK status for the message',
|
||||
})
|
||||
ack: WAMessageAck;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Location information contained in the message, if the message is type \"location\""
|
||||
})
|
||||
location?: WALocation
|
||||
@ApiProperty({
|
||||
description:
|
||||
'If the message was sent to a group, this field will contain the user that sent the message.',
|
||||
})
|
||||
author?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "List of vCards contained in the message."
|
||||
})
|
||||
vCards?: string[]
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Location information contained in the message, if the message is type "location"',
|
||||
})
|
||||
location?: WALocation;
|
||||
|
||||
/** Returns message in a raw format */
|
||||
@ApiProperty({
|
||||
description: "Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend."
|
||||
})
|
||||
_data?: any
|
||||
@ApiProperty({
|
||||
description: 'List of vCards contained in the message.',
|
||||
})
|
||||
vCards?: string[];
|
||||
|
||||
/** Returns message in a raw format */
|
||||
@ApiProperty({
|
||||
description:
|
||||
'Message in a raw format that we get from WhatsApp. May be changed anytime, use it with caution! It depends a lot on the underlying backend.',
|
||||
})
|
||||
_data?: any;
|
||||
}
|
||||
|
||||
export class WAWebhook {
|
||||
event: WAEvents
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
payload: WAMessage | WAGroupNotification | object
|
||||
event: WAEvents;
|
||||
session: string;
|
||||
engine: WhatsappEngine;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
payload: WAMessage | WAGroupNotification | object;
|
||||
}
|
||||
@@ -1,14 +1,40 @@
|
||||
import {WhatsappStatus} from "./enums.dto";
|
||||
import { WhatsappStatus } from './enums.dto';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* Queries
|
||||
*/
|
||||
export class ListSessionsQuery {
|
||||
@ApiProperty({
|
||||
example: false,
|
||||
required: false,
|
||||
description:
|
||||
'Return all sessions, including those that are in the STOPPED state.',
|
||||
})
|
||||
all: boolean;
|
||||
}
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
export class SessionStartRequest {
|
||||
name = "default"
|
||||
name = 'default';
|
||||
}
|
||||
|
||||
export class SessionStopRequest {
|
||||
name = "default"
|
||||
name = 'default';
|
||||
@ApiProperty({
|
||||
example: false,
|
||||
required: false,
|
||||
description: 'Stop and logout from the session.',
|
||||
})
|
||||
logout = false;
|
||||
}
|
||||
|
||||
export class SessionLogoutRequest {
|
||||
name = 'default';
|
||||
}
|
||||
|
||||
export class SessionDTO {
|
||||
name = "default"
|
||||
status: WhatsappStatus
|
||||
name = 'default';
|
||||
status: WhatsappStatus;
|
||||
}
|
||||
+22
-15
@@ -1,27 +1,34 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('fs')
|
||||
import { getBrowserExecutablePath } from './core/abc/session.abc';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('fs');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const path = require('path');
|
||||
|
||||
export enum WAHAVersion {
|
||||
PLUS = "PLUS",
|
||||
CORE = "CORE",
|
||||
PLUS = 'PLUS',
|
||||
CORE = 'CORE',
|
||||
}
|
||||
|
||||
export function getWAHAVersion(): WAHAVersion {
|
||||
// force core version if env variables set
|
||||
const waha_version = process.env.WAHA_VERSION;
|
||||
if (waha_version && waha_version === WAHAVersion.CORE) {
|
||||
return WAHAVersion.CORE
|
||||
}
|
||||
// force core version if env variables set
|
||||
const waha_version = process.env.WAHA_VERSION;
|
||||
if (waha_version && waha_version === WAHAVersion.CORE) {
|
||||
return WAHAVersion.CORE;
|
||||
}
|
||||
|
||||
// Check the plus directory exists
|
||||
const plusExists = fs.existsSync(`${__dirname}/plus`)
|
||||
if (plusExists) {
|
||||
return WAHAVersion.PLUS
|
||||
}
|
||||
// Check the plus directory exists
|
||||
const plusExists = fs.existsSync(`${__dirname}/plus`);
|
||||
if (plusExists) {
|
||||
return WAHAVersion.PLUS;
|
||||
}
|
||||
|
||||
return WAHAVersion.CORE
|
||||
return WAHAVersion.CORE;
|
||||
}
|
||||
|
||||
export const VERSION = {version: "2022.11.11", tier: getWAHAVersion()}
|
||||
export const VERSION = {
|
||||
version: '2023.6.1',
|
||||
tier: getWAHAVersion(),
|
||||
browser: getBrowserExecutablePath(),
|
||||
};
|
||||
-656
@@ -1,656 +0,0 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "WhatsApp HTTP API",
|
||||
"description": "WhatsApp HTTP API that you can configure in a click",
|
||||
"version": "1.0",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "device",
|
||||
"description": "Device information"
|
||||
},
|
||||
{
|
||||
"name": "chatting",
|
||||
"description": "Chat methods"
|
||||
}
|
||||
],
|
||||
"servers": [],
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-VENOM-TOKEN",
|
||||
"description": "Your secret key"
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"MessageContactVcard": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"contactsId": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId",
|
||||
"contactsId",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"MessageText": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId",
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"MessageLocation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"latitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"title"
|
||||
]
|
||||
},
|
||||
"MessageLinkPreview": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId",
|
||||
"url",
|
||||
"title"
|
||||
]
|
||||
},
|
||||
"MessageImage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"caption": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId",
|
||||
"path",
|
||||
"filename",
|
||||
"caption"
|
||||
]
|
||||
},
|
||||
"MessageFile": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"caption": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId",
|
||||
"path",
|
||||
"filename",
|
||||
"caption"
|
||||
]
|
||||
},
|
||||
"MessageReply": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
},
|
||||
"reply_to": {
|
||||
"type": "string",
|
||||
"example": "message.id"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId",
|
||||
"reply_to",
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"Chat": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chatId": {
|
||||
"type": "string",
|
||||
"example": "791231234567@c.us"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"chatId"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/api/sendContactVcard": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendContactVcard",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageContactVcard"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendText": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendText",
|
||||
"summary": "Send a text message",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageText"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendLocation": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendLocation",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageLocation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendLinkPreview": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendLinkPreview",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageLinkPreview"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendImage": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendImage",
|
||||
"summary": "NOT IMPLEMENTED YET",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageImage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendFile": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendFile",
|
||||
"summary": "NOT IMPLEMENTED YET",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageFile"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/reply": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_reply",
|
||||
"summary": "Reply to a text message",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MessageReply"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/sendSeen": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_sendSeen",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/startTyping": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_startTyping",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/stopTyping": {
|
||||
"post": {
|
||||
"operationId": "ChattingController_stopTyping",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"chatting"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/killServiceWorker": {
|
||||
"post": {
|
||||
"operationId": "DeviceController_killServiceWorker",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/restartService": {
|
||||
"post": {
|
||||
"operationId": "DeviceController_restartService",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getHostDevice": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getHostDevice",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getConnectionState": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getConnectionState",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getBatteryLevel": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getBatteryLevel",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/isConnected": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_isConnected",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/getWAVersion": {
|
||||
"get": {
|
||||
"operationId": "DeviceController_getWAVersion",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"device"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/screenshot": {
|
||||
"get": {
|
||||
"operationId": "ScreenshotController_screenshot",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"screenshot"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user