Compare commits

...
19 Commits
Author SHA1 Message Date
allburov 6af547d2af [core] Quick fix for webjs engine.
Release / deploy-docker (push) Waiting to run
Fix #46
2023-01-15 14:52:17 +07:00
allburov d72b1822cb [core][2023.1] Add checkNumberStatus for webjs engine 2023-01-15 14:37:15 +07:00
allburov d34df18da8 [core][2023.1] Add contacts endpoints 2023-01-15 14:37:14 +07:00
allburov 27cb247760 [core][docs] Add contact api changelog 2023-01-09 09:31:00 +07:00
allburov 7a66d6fccd [core][docs] Add changelog.md 2023-01-09 09:31:00 +07:00
allburov f1cb372339 [core][docs] Add contacts page 2023-01-09 09:31:00 +07:00
allburov 46d65e6fc0 [core][docs] Reorder guides 2023-01-09 09:31:00 +07:00
allburov 6168298b5e [core][docs] Move engines doc to config 2023-01-09 09:31:00 +07:00
allburov d5fef413ca [core] Add swagger authentication to changelog 2022-12-19 12:35:36 +07:00
allburov 0802ee6106 [core][docs] Add GET /messages request. Fix #31 2022-12-19 12:32:55 +07:00
allburov 65d0f87eeb Use -plus docker image in security
Release / deploy-docker (push) Waiting to run
2022-12-11 17:01:47 +07:00
allburov 488868d80e Add WHATSAPP_SWAGGER_USERNAME, WHATSAPP_SWAGGER_PASSWORD to config.md 2022-12-11 16:58:12 +07:00
allburov 154965efcb [core] bump version to 2022.12.11 2022-12-11 16:52:36 +07:00
allburov f364f0b495 [core] Authentication for swagger panel (available in plus only) 2022-12-11 16:52:34 +07:00
allburov 82e2f5bcb9 [core] Authentication for swagger panel (available in plus only) 2022-12-11 16:52:28 +07:00
dependabot[bot] f7a2228925 Bump venom-bot from 4.3.6 to 4.3.7
Bumps [venom-bot](https://github.com/orkestral/venom) from 4.3.6 to 4.3.7.
- [Release notes](https://github.com/orkestral/venom/releases)
- [Changelog](https://github.com/orkestral/venom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/orkestral/venom/compare/v4.3.6...v4.3.7)

---
updated-dependencies:
- dependency-name: venom-bot
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-11 16:52:17 +07:00
allburov dadc9d4efd Up version 2022-12-11 16:52:11 +07:00
allburov df59867431 Up dependencies 2022-12-11 16:52:11 +07:00
dependabot[bot] a7c6e9dc00 Bump whatsapp-web.js from 1.18.3 to 1.18.4
Bumps [whatsapp-web.js](https://github.com/pedroslopez/whatsapp-web.js) from 1.18.3 to 1.18.4.
- [Release notes](https://github.com/pedroslopez/whatsapp-web.js/releases)
- [Commits](https://github.com/pedroslopez/whatsapp-web.js/compare/v1.18.3...v1.18.4)

---
updated-dependencies:
- dependency-name: whatsapp-web.js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-11 16:52:11 +07:00
27 changed files with 805 additions and 363 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# Changelog
You'll find changelog on [the changelog page](https://waha.devlike.pro/docs/help/changelog/)
+1
View File
@@ -56,6 +56,7 @@
- Multiple WhatsApp account running inside one container
## Security
- Swagger panel authentication
- API authentication
## Updates
@@ -12,6 +12,20 @@ menu:
weight: 600
toc: true
---
## 2021.1
- 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
- Added `GET /messages/` endpoint to get chat messages [#31](https://github.com/devlikeapro/whatsapp-http-api/issues/31)
### Security ![](/images/versions/plus.png)
- Added `WHATSAPP_SWAGGER_USERNAME` and `WHATSAPP_SWAGGER_PASSWORD` to hide and protect swagger panel.
## 2022.11
**Please test changes in test environment before update production!!**
@@ -6,7 +6,7 @@ date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: []
weight: 400
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/)
@@ -17,20 +17,42 @@ docker run -it -e WHATSAPP_HOOK_EVENTS=* -e WHATSAPP_HOOK_URL=https://httpbin.or
## Environment variables
### Common
- `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_SWAGGER_USERNAME=admin` + `WHATSAPP_SWAGGER_PASSWORD=admin` - protect the Swagger panel with `admin / admin`
credentials. It doesn't affect api access!
- `WHATSAPP_START_SESSION=default` - start session with the name right after launching the app
-
## File storage ![](/images/versions/plus.png)
- `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`)
## Engines
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.
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." />}}
### WhatsApp WebJS ![](/images/versions/core.png)
**WAHA** uses **WhatsApp WebJS** engine by default
- https://github.com/pedroslopez/whatsapp-web.js
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
### Venom ![](/images/versions/core.png)
- https://github.com/orkestral/venom
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
@@ -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"
}
```
@@ -1,27 +0,0 @@
---
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: 700
---
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.
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." />}}
## WhatsApp WebJS ![](/images/versions/core.png)
**WAHA** uses **WhatsApp WebJS** engine by default
- https://github.com/pedroslopez/whatsapp-web.js
- `WHATSAPP_DEFAULT_ENGINE=WEBJS`
## Venom ![](/images/versions/core.png)
- https://github.com/orkestral/venom
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
@@ -6,7 +6,7 @@ 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
@@ -193,3 +193,12 @@ To configure the url you can use environment variables `WHATSAPP_API_HOSTNAME` a
## Webhook retries ![](/images/versions/plus.png)
**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 ![](/images/versions/plus.png)
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:
![](swagger-basic-auth.png)
{{< 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 ![](/images/versions/plus.png)
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:
![](swagger-auth.png)
@@ -49,5 +65,3 @@ headers = {
requests.get("http://localhost:3000/api/sessions", headers=headers)
```
## Swagger Security ![](/images/versions/plus-soon.png)
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:
@@ -55,7 +55,7 @@ WAHA does not support reply with files (images, voice, etc). If you're intereste
## Add a reaction ![](/images/versions/core.png) ![](/images/versions/plus.png)
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 ![](/images/versions/plus.png)
+1 -1
View File
@@ -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":"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
{"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
+377 -278
View File
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -31,6 +31,7 @@
"@nestjs/swagger": "^6.0.5",
"class-validator": "^0.12.2",
"del": "^6.0.0",
"express-basic-auth": "^1.2.1",
"mime-types": "^2.1.27",
"passport": "^0.6.0",
"passport-headerapikey": "^1.2.2",
@@ -41,8 +42,8 @@
"rimraf": "^3.0.2",
"rxjs": "^7.1.0",
"swagger-ui-express": "^4.1.4",
"venom-bot": "^4.3.5",
"whatsapp-web.js": "^1.18.3"
"venom-bot": "^4.3.7",
"whatsapp-web.js": "github:wictornogueira/whatsapp-web.js#fix_legacyphonefeatures"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
+63
View File
@@ -0,0 +1,63 @@
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)
}
}
+1 -1
View File
@@ -2,8 +2,8 @@ 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 {SessionQuery} from "../structures/base.dto";
@ApiSecurity('api_key')
+11
View File
@@ -76,4 +76,15 @@ export class WhatsappConfigService {
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]
}
}
+31 -1
View File
@@ -16,6 +16,8 @@ import {
} from "../../structures/chatting.dto";
import {MediaStorage} from "./storage.abc";
import {MessageId} from "whatsapp-web.js";
import {ContactQuery, ContactRequest} from "../../structures/contacts.dto";
import {NotImplementedByEngineError} from "../exceptions";
export abstract class WhatsappSession {
public status: WhatsappStatus;
@@ -26,7 +28,7 @@ export abstract class WhatsappSession {
this.log = log
}
getBrowserExecutablePath(){
getBrowserExecutablePath() {
return "/usr/bin/google-chrome-stable"
}
@@ -103,6 +105,34 @@ export abstract class WhatsappSession {
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()
}
/**
* END - Methods for API
*/
+2
View File
@@ -5,6 +5,7 @@ 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";
@@ -30,6 +31,7 @@ export const IMPORTS = [
export const CONTROLLERS = [
SessionsController,
ChattingController,
ContactsController,
ScreenshotController,
VersionController,
]
+1
View File
@@ -20,6 +20,7 @@ import {NotImplementedByEngineError} from "./exceptions";
import {MediaStorage} from "./abc/storage.abc";
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
import {ConsoleLogger} from "@nestjs/common";
import {ContactQuery} from "../structures/contacts.dto";
class QR {
private base64: string;
+43 -4
View File
@@ -1,5 +1,5 @@
import {UnprocessableEntityException} from "@nestjs/common/exceptions/unprocessable-entity.exception";
import {Buttons, Chat, Client, Events, Message} from "whatsapp-web.js";
import {Buttons, Chat, Client, Contact, 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";
@@ -19,6 +19,7 @@ import {
MessageVoiceRequest
} from "../structures/chatting.dto";
import {AvailableInPlusVersion, NotImplementedByEngineError} from "./exceptions";
import {ContactQuery, ContactRequest} from "../structures/contacts.dto";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const qrcode = require('qrcode-terminal');
@@ -73,8 +74,9 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return await this.whatsapp.pupPage.screenshot()
}
checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
throw new NotImplementedByEngineError()
async checkNumberStatus(request: CheckNumberStatusQuery): Promise<WANumberExistResult> {
const exist = await this.whatsapp.isRegisteredUser(this.ensureSuffix(request.phone))
return {numberExists: exist}
}
sendText(request: MessageTextRequest) {
@@ -144,6 +146,38 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
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()
}
/**
* END - Methods for API
*/
@@ -192,6 +226,12 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
})
}
protected toWAContact(contact: Contact) {
// @ts-ignore
contact.id = contact.id._serialized
return contact
}
protected async downloadMedia(message: Message) {
if (!message.hasMedia) {
return message
@@ -202,6 +242,5 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
return message
}
}
+40
View File
@@ -0,0 +1,40 @@
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
import {INestApplication} from "@nestjs/common";
import {VERSION} from "../version";
export class SwaggerModuleCore {
configure(app: INestApplication) {
this.setUpAuth(app)
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 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('other', 'Other methods')
.addApiKey({
type: 'apiKey',
description: 'Your secret api key',
name: 'X-Api-Key'
}
)
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('', app, document);
}
protected setUpAuth(app: INestApplication) {
return undefined
}
}
+13 -23
View File
@@ -1,25 +1,29 @@
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 {AppModuleCore} from "./core/app.module.core";
import {SwaggerModuleCore} from "./core/swagger.module.core";
async function getAppModule() {
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
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")
return AppModulePlus
// @ts-ignore
const {SwaggerModulePlus} = await import("./plus/swagger.module.plus")
return [AppModulePlus, SwaggerModulePlus]
}
async function bootstrap() {
const AppModule = await getAppModule()
const [AppModule, SwaggerModule] = await loadModules()
const app = await NestFactory.create(AppModule, {
logger: process.env.DEBUG != undefined ? ['log', 'debug', 'error', 'verbose', 'warn'] :
['log', 'error', 'warn'],
@@ -27,24 +31,10 @@ async function bootstrap() {
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);
// Configure swagger
const swagger = new SwaggerModule()
swagger.configure(app)
const config = app.get(WhatsappConfigService);
await app.listen(config.port);
+19
View File
@@ -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
}
+1 -12
View File
@@ -1,17 +1,11 @@
import {IsNotEmpty, IsString} from "class-validator";
import {ApiExtraModels, ApiProperty, getSchemaPath} from "@nestjs/swagger";
export const WHATSAPP_DEFAULT_SESSION_NAME = "default"
import {SessionBaseRequest, SessionQuery} from "./base.dto";
/**
* Queries
*/
export class SessionQuery {
@IsNotEmpty()
session: string = WHATSAPP_DEFAULT_SESSION_NAME;
}
export class CheckNumberStatusQuery extends SessionQuery {
@IsString()
phone: string
@@ -28,11 +22,6 @@ export class MessageTextQuery extends SessionQuery {
/**
* Requests
*/
export class SessionBaseRequest {
session = WHATSAPP_DEFAULT_SESSION_NAME
}
export class ChatRequest extends SessionBaseRequest {
@ApiProperty({
example: '11111111111@c.us',
+22
View File
@@ -0,0 +1,22 @@
import {IsString} from "class-validator";
import {SessionBaseRequest, SessionQuery, WHATSAPP_DEFAULT_SESSION_NAME} from "./base.dto";
/**
* Queries
*/
export class ContactQuery extends SessionQuery {
@IsString()
contactId: string
}
/**
* Requests
*/
export class ContactRequest extends SessionBaseRequest {
@IsString()
contactId: string
}
+1 -1
View File
@@ -24,4 +24,4 @@ export function getWAHAVersion(): WAHAVersion {
return WAHAVersion.CORE
}
export const VERSION = {version: "2022.11.20", tier: getWAHAVersion()}
export const VERSION = {version: "2022.12.11", tier: getWAHAVersion()}