Compare commits

...
13 Commits
Author SHA1 Message Date
allburov 39d0f2a060 [core] Fix doc about WEBJS
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
2024-02-18 20:22:45 +07:00
allburov 0b03abee91 [core] Fix delete file when it doesn't exist anymore 2024-02-18 20:22:41 +07:00
allburov b4fb69d0ee [core] Use extends instead of implements for LocalStoreCore.ts 2024-02-18 19:55:54 +07:00
allburov 5b9ed029b3 [core] NOWEB - fix stop session 2024-02-18 19:42:32 +07:00
allburov c29397371b [core] Use network host in mongo example 2024-02-18 19:37:45 +07:00
allburov fe47e27788 [core] Fix yarn file 2024-02-18 19:37:33 +07:00
allburov a14c702c79 [core] Bump to 2024.2.3 2024-02-18 19:25:12 +07:00
allburov fe4246036d [core] Support MongoDB 2024-02-18 19:21:19 +07:00
allburov 074063b814 [core] Fix set presence documentation 2024-02-12 11:34:15 +07:00
allburov 0271a8b721 [core] Fix changelog version
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
2024-02-10 15:09:16 +07:00
allburov 08617383be [core] Listen for page close event
fix #262
2024-02-10 15:08:03 +07:00
allburov 3840459b0a [core] add noweb engine performance 2024-02-09 15:11:36 +07:00
Sunil Sankar aee7ac28bc [core] Fix the image name (#260)
The image name is wrongly mentioned
2024-02-05 14:02:21 +07:00
30 changed files with 1757 additions and 1323 deletions

No files matched your search

+12
View File
@@ -30,3 +30,15 @@ services:
- WHATSAPP_SWAGGER_PASSWORD=123
- WHATSAPP_API_HOSTNAME=localhost
restart: always
mongodb:
image: mongo
container_name: mongodb
ports:
- '27017:27017/tcp'
volumes:
- mongodb_data:/data/db
restart: always
volumes:
mongodb_data: {}
@@ -38,6 +38,8 @@ The following environment variables can be used to configure the WAHA.
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.
- Also read more about [Sessions ->]({{< relref "/docs/how-to/sessions" >}})
- Also read more about [Session Storages on the dedicated page ->]({{< relref "/docs/how-to/storages#sessions" >}})
### Swagger
- `WHATSAPP_SWAGGER_CONFIG_ADVANCED=true` - enables advanced configuration options for Swagger documentation - you can customize host, port and base URL for the requests.
@@ -66,14 +68,18 @@ Read more about it on [**Session page** ->]({{< relref "/docs/how-to/sessions#co
Keep in mind that session's proxy configuration takes precedence over proxy configuration set by environment variables!
### Security ![](/images/versions/plus.png)
### Security
<b>Security is available in [WAHA Plus ![](/images/versions/plus.png)]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
- `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.
Read more about security settings for Swagger and API on [**Security page** ->]({{< relref "/docs/how-to/security" >}}).
### Files ![](/images/versions/plus.png)
### Files
<b>Files configuration is available in [WAHA Plus ![](/images/versions/plus.png)]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
The following environment variables can be used to configure the file storage options for the WAHA:
@@ -85,8 +91,11 @@ The following environment variables can be used to configure the file storage op
- Under the hood, it sets `WHATSAPP_FILES_MIMETYPES=mimetype/ignore-all-media` to ignore all media files.
- `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`.
- Set this variable to `0` to disable the file lifetime.
- `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`.
- The folder must be mounted to the host machine to keep the files between container restarts. [ Read more about how to persist files ->]({{< relref "/docs/how-to/storages#media" >}})
- Also read more about [Media Storages on the dedicated page ->]({{< relref "/docs/how-to/storages#media" >}})
💡 Even if WAHA doesn't process the message media because of `WHATSAPP_FILES_MIMETYPES` or `WHATSAPP_DOWNLOAD_MEDIA`
you'll get a webhook event with `hasMedia: True` field, but with no `media.url`.
@@ -78,7 +78,7 @@ For **x86** processors use following images:
For **ARM** processors use following images:
- `devlikeapro/whatsapp-http-api:arm` - **Chromium**
- `devlikeapro/whatsapp-http-api:arm-noweb`- **no browser installed**
- `devlikeapro/whatsapp-http-api:noweb-arm`- **no browser installed**
{{< alert icon="💡" text="Chrome version is not available in ARM" />}}
@@ -188,7 +188,7 @@ please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues
| | WEBJS | NOWEB | VENOM |
|---------------------------------------------------|:-----:|:-----:|:-----:|
| **Presence** | | | |
| `POST /api/{session}/presence` | ➖ | ✔️ | ➖ |
| `POST /api/{session}/presence` | ✔️ | ✔️ | ➖ |
| `GET /api/{session}/presence` | ➖ | ✔️ | ➖ |
| `GET /api/{session}/presence/{chatId}` | ➖ | ✔️ | ➖ |
| `POST /api/{session}/presence/{chatId}/subscribe` | ➖ | ✔️ | ➖ |
@@ -27,7 +27,8 @@ See the list of engines [**that support the feature ->**]({{< relref "/docs/how-
You can set your global or chat-related presence with `POST /api/{session}/presence` endpoint
Start typing to a chat (you can use `POST /startTyping` instead)
```json
```
POST /api/{session}/presence
{
"chatId": "111111111@c.us",
"presence": "typing"
@@ -35,7 +36,8 @@ Start typing to a chat (you can use `POST /startTyping` instead)
```
Clear "typing" state (you can use `POST /stopTyping` instead)
```json
```
POST /api/{session}/presence
{
"chatId": "111111111@c.us",
"presence": "paused"
@@ -43,7 +45,8 @@ Clear "typing" state (you can use `POST /stopTyping` instead)
```
Set global "online", all contacts will see it
```json
```
POST /api/{session}/presence
{
"presence": "online"
}
@@ -53,7 +56,8 @@ Set global "online", all contacts will see it
to the device.
If you would like to receive said notifications - you need to mark a session's presence as `offline`.
```json
```
POST /api/{session}/presence
{
"presence": "offline"
}
@@ -298,28 +298,8 @@ configure autostart options so when the docker container restarts - it restores
### Session persistent
#### File storage
If you want to save your session and do not scan QR code everytime when you launch WAHA - connect a local file storage
to the container. WAHA stores authentication information in the directory and reuses it after restart.
[Attach volume](https://docs.docker.com/storage/volumes/) part to the command:
```bash
-v `pwd`/.sessions:/app/.sessions
```
The full command would be:
```bash
docker run -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
```
#### Remote storage ![](/images/versions/soon.png)
If you're interested in using some "remote" storage (like Redis or other Databases) to save sessions - please create an
issue on GitHub.
For instances, it may be useful if you run WAHA in a cluster of servers and do not have shared file storage
If you want to save your session and do not scan QR code everytime when you launch WAHA -
[connect the session storage to the container ->]({{< relref "/docs/how-to/storages#sessions" >}})
### Autostart
If you don't want to call `POST /api/sessions/start` for every session each time when the container restart -
@@ -0,0 +1,141 @@
---
title: "Storages"
description: "Storages"
lead: ""
date: 2020-10-06T08:48:45+00:00
lastmod: 2020-10-06T08:48:45+00:00
draft: false
images: [ ]
weight: 700
---
There are several storages that are used by the WAHA, below you can find the list of them and the way to configure them.
1. [Sessions](#sessions) - used to store the session data, such as authentication information, configuration, and other data that is required to keep the session alive and connected to WhatsApp.
2. [Media](#media) - used to store the media files, such as images, videos, and other files that are received from the WhatsApp instance.
<b>Storages are available in [WAHA Plus ![](/images/versions/plus.png)]({{< relref "/docs/how-to/plus-version" >}}) only.</b>
## Sessions
The "Sessions" storage is used to store the session data, such as authentication information, configuration,
and other data that is required to keep the session alive and connected to WhatsApp.
If you want to save your session and do not scan QR code everytime when you launch WAHA -
connect the session storage to the container.
For the session storage, you can use the following options:
1. [Local (files)](#local-files) - the default option, stores the session data in the local storage using files.
2. [MongoDB](#mongodb) - stores the session data in the MongoDB database.
The following table shows the compatibility of the session storage with the engines:
| | WEBJS | NOWEB | VENOM |
|---------------|:-----:|:-----:|:-----:|
| Local (files) | ✔️ | ✔️ | ✔️ |
| MongoDB | ✔️ | ✔️ | ➖ |
## Sessions - Local (files)
By default, the WAHA uses the **local storage (files)** to store the session data.
{{< alert icon="💡" text="It's <b>well tested solution</b> even for production" />}}
In order to use the local storage and save the session data between the container restarts,
you need to mount the volume to the `/app/.sessions` directory using the `-v` option in `docker run` command:
```bash
-v /path/to/on/host/.sessions:/app/.sessions
```
The full command to run the WAHA with the local storage and save the session data
in the current directory and `.sessions` directory:
```bash
docker run -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp devlikeapro/whatsapp-http-api-plus
```
This is the only action you need to do to use the local storage - all session data will be available between the container restarts.
### How it works
In the host machine, the session data will be stored in the current directory in the `.sessions` directory.
Under the hood, the WAHA stores the session data in the following directory structure:
```
.sessions/{engine}/{sessionName}/...
```
when you "logout" the session using [POST /api/sessions/logout]({{< relref "/docs/how-to/sessions#logout">}}) or providing `logout: True` in [POST /api/sessions/stop]({{< relref "/docs/how-to/sessions#stop">}})
it removes the directory with the session data.
## Sessions - MongoDB
If you want to use the MongoDB to store the session data, you need to:
1. Start the MongoDB server (using docker or any other way). You can either start your own MongoDB server or use the one of cloud providers, like [MongoDB Atlas](https://www.mongodb.com/atlas/database).
2. Set `WHATSAPP_SESSIONS_MONGO_URL=mongodb://user:password@host:port/` environment variable to connect to the MongoDB server.
**We recommend using your own MongoDB server as close as possible to WAHA server** for the best performance and security reasons.
{{< alert icon="💡" text="For <b>WEBJS</b> engine it can take up to 1 minute to save credentials in Mongo Database" />}}
### Example
First, you need to start MongoDB server:
```bash
docker run -d -p 27017:27017 --name mongodb mongo
```
Then, you need to run the WAHA with the `WHATSAPP_SESSIONS_MONGO_URL` environment variable (please note using `--network host` option as well)
```bash
docker run -e WHATSAPP_SESSIONS_MONGO_URL=mongodb://localhost:27017/ --network host devlikeapro/whatsapp-http-api-plus
```
This is the only action you need to do to use the MongoDB storage -
all session authentication data will be stored in the MongoDB database.
### How it works
When you start a session, it stores the session data in the MongoDB database in two databases:
1. `waha_{engine}` - it saves the session configuration in `sessions` collection with `name: {sessionName}` field.
2. `waha_{engine}_{sessionName}` - it saves the WhatsApp authentication data and other session data. Each engine saves different data in this database.
When you "logout" the session using [POST /api/sessions/logout]({{< relref "/docs/how-to/sessions#logout">}}) or providing `logout: True` in [POST /api/sessions/stop]({{< relref "/docs/how-to/sessions#stop">}}) it removes those databases or elements from them.
For dealing and troubleshooting with the MongoDB, we recommend using [MongoDB Compass](https://www.mongodb.com/products/tools/compass).
![](waha-mongodb.png)
### MongoDB Atlas
If you use the [MongoDB Atlas](https://www.mongodb.com/atlas/database) you must grant
`Atlas Admin` role in **Security => Database Access** before you connect to the database.
For production please consider running the MongoDB server close to the WAHA server for the best performance and security reasons.
## Media
When your WhatsApp instance receives media files, it stores them in the media storage.
You can use the following options to store the media files:
1. [Local](#local) - the default option, stores the media files in the local storage using files.
2. [S3](#s3) - stores the media files in the S3 storage.
Read more about [available configuration options ->]({{<relref "/docs/how-to/config#files">}}).
The following table shows the compatibility of the storage with the engines:
| | WEBJS | NOWEB | VENOM |
|-------|:-----:|:-----:|:-----:|
| Local | ✔️ | ✔️ | ✔️ |
| S3 | 🕒 | 🕒 | ➖ |
## Media - Local
By default, the WAHA uses the **local file storage** to store the media files and those files has a short lifetime (180 seconds).
So it's your app responsibility to download and store them in a safe persistent place during this time.
### Save media files between the container restarts
If you want to use the local storage and **save the media files between the container restarts for a long time** - you need to:
1. Specify a dedicated folder to store the media files using `WHATSAPP_FILES_FOLDER=/app/.media` environment variable
2. Disable automatic media files cleanup using `WHATSAPP_FILES_LIFETIME=0` environment variable
3. Connect the volume to the specified folder using the `-v /path/to/files/on/host:/app/.media` option in `docker run` command to the folder specified in `WHATSAPP_FILES_FOLDER` environment variable.
Read more about [available configuration options ->]({{<relref "/docs/how-to/config#files">}}).
Here's all the steps in one command:
```bash
docker run -v /path/to/on/host/.media:/app/.media -e WHATSAPP_FILES_FOLDER=/app/.media -e WHATSAPP_FILES_LIFETIME=0 -p 3000:3000/tcp devlikeapro/whatsapp-http-api-plus
```
## Media - S3
If you're interested in using the S3 storage or any other cloud storage (like [self-hosted S3 - Minio](https://min.io/)),
please create an issue or vote for the S3 issue in [the GitHub repository](https://github.com/devlikeapro/whatsapp-http-api/issues?q=is%3Aissue+is%3Aopen+S3).
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

@@ -19,6 +19,19 @@ If you wish to get and read Release Notes - read them on one of our platforms, *
You even can **subscribe to get new updates** there!
## 2024.2
- Add support for [MongoDB as storage for Session data]({{< relref "/docs/how-to/storages" >}})
- Support persistent file storage for media files - [now you can save media files between container restarts]({{< relref "/docs/how-to/storages#media" >}})
- If you set `WHATSAPP_FILES_LIFETIME=0` environment variable - media files will be never deleted.
## 2024.1
- Implement [Patron Portal](https://portal.devlike.pro/) where you can get your personal API key and manage your perks.
- Read more on [Patreon ->](https://www.patreon.com/posts/waha-patron-97637416)
- Read more on [Boosty ->](https://boosty.to/wa-http-api/posts/8319079f-dac1-4179-b954-fcc559097c76)
## 2024.2
- Listen for browser disconnected and page close events in **WEBJS** engine [#262](https://github.com/devlikeapro/whatsapp-http-api/issues/262)
## 2023.12
December 2023
- Add `chatId` field to `GET /api/contacts/check-exists` to help get the right `chatId` for Brazilian numbers.
@@ -14,6 +14,7 @@ toc: true
---
## How much CPU and Memory does WhatsApp HTTP API consume?
### WEBJS
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.
@@ -26,4 +27,10 @@ It may differ from case to case, it depends on usage pattern - how many messages
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.
### NOWEB
If you're looking for less resource demanded engine - [have a look at **NOWEB** engine ->]({{< relref "/docs/how-to/engines" >}})
The quote from the user:
> The server has 2 CPU and 8GB of memory.
> Today we have 85 session in this instance.
+2 -1
View File
@@ -42,6 +42,7 @@
"link-preview-js": "^3.0.4",
"lodash": "^4.17.21",
"mime-types": "^2.1.27",
"mongodb": "^6.3.0",
"passport": "^0.6.0",
"passport-headerapikey": "^1.2.2",
"qrcode": "^1.5.1",
@@ -53,7 +54,7 @@
"rxjs": "^7.1.0",
"swagger-ui-express": "^4.1.4",
"venom-bot": "5.0.1",
"whatsapp-web.js": "https://github.com/pedroslopez/whatsapp-web.js"
"whatsapp-web.js": "https://github.com/devlikeapro/whatsapp-web.js#main-fork"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
+1 -1
View File
@@ -29,7 +29,7 @@ class SessionsController {
@Post('/start/')
async start(@Body() request: SessionStartRequest): Promise<SessionDTO> {
const result = await this.manager.start(request);
await this.manager.sessionStorage.configRepository.save(
await this.manager.sessionConfigRepository.save(
request.name,
request.config || null,
);
+4
View File
@@ -108,6 +108,10 @@ export class WhatsappConfigService {
return value ? value.split(',') : [];
}
getSessionMongoUrl(): string | undefined {
return this.configService.get('WHATSAPP_SESSIONS_MONGO_URL', undefined);
}
getDefaultEngineName(): WAHAEngine {
const value = getEngineName();
if (value in WAHAEngine) {
+1
View File
@@ -0,0 +1 @@
export abstract class DataStore {}
+5 -2
View File
@@ -8,12 +8,15 @@ import {
SessionStartRequest,
SessionStopRequest,
} from '../../structures/sessions.dto';
import { ISessionAuthRepository } from '../storage/ISessionAuthRepository';
import { ISessionConfigRepository } from '../storage/ISessionConfigRepository';
import { WhatsappSession } from './session.abc';
import { LocalSessionStorage } from './storage.abc';
import { WebhookConductor } from './webhooks.abc';
export abstract class SessionManager implements OnApplicationShutdown {
public sessionStorage: LocalSessionStorage;
public store: any;
public sessionAuthRepository: ISessionAuthRepository;
public sessionConfigRepository: ISessionConfigRepository;
protected abstract getEngine(engine: WAHAEngine): typeof WhatsappSession;
+5 -6
View File
@@ -3,7 +3,6 @@ import { EventEmitter } from 'events';
import * as fs from 'fs';
import { MessageId } from 'whatsapp-web.js';
import { OTPRequest, RequestCodeRequest } from '../../structures/auth.dto';
import {
ChatRequest,
CheckNumberStatusQuery,
@@ -47,8 +46,8 @@ import {
import { WASessionStatusBody } from '../../structures/webhooks.dto';
import { NotImplementedByEngineError } from '../exceptions';
import { QR } from '../QR';
import { DataStore } from './DataStore';
import { MediaManager } from './media.abc';
import { LocalSessionStorage } from './storage.abc';
const CHROME_PATH = '/usr/bin/google-chrome-stable';
const CHROMIUM_PATH = '/usr/bin/chromium';
@@ -77,7 +76,7 @@ export interface SessionParams {
name: string;
mediaManager: MediaManager;
log: ConsoleLogger;
sessionStorage: LocalSessionStorage;
sessionStore: DataStore;
proxyConfig?: ProxyConfig;
sessionConfig?: SessionConfig;
}
@@ -89,7 +88,7 @@ export abstract class WhatsappSession {
public name: string;
protected mediaManager: MediaManager;
protected log: ConsoleLogger;
protected sessionStorage: LocalSessionStorage;
protected sessionStore: DataStore;
protected proxyConfig?: ProxyConfig;
public sessionConfig?: SessionConfig;
@@ -98,7 +97,7 @@ export abstract class WhatsappSession {
public constructor({
name,
log,
sessionStorage,
sessionStore,
proxyConfig,
mediaManager,
sessionConfig,
@@ -107,7 +106,7 @@ export abstract class WhatsappSession {
this.name = name;
this.proxyConfig = proxyConfig;
this.log = log;
this.sessionStorage = sessionStorage;
this.sessionStore = sessionStore;
this.mediaManager = mediaManager;
this.sessionConfig = sessionConfig;
}
-35
View File
@@ -1,35 +0,0 @@
import { SessionConfig } from '../../structures/sessions.dto';
abstract class SessionConfigRepository {
abstract save(
sessionName: string,
config: SessionConfig | null,
): Promise<void>;
abstract get(sessionName: string): Promise<SessionConfig | null>;
}
abstract class SessionStorage {
protected constructor(public engine: string) {
this.engine = engine;
}
abstract init(sessionName?: string): Promise<void>;
abstract clean(sessionName: string): Promise<void>;
abstract getAll(): Promise<string[]>;
//
// Repositories
//
abstract get configRepository(): SessionConfigRepository;
}
abstract class LocalSessionStorage extends SessionStorage {
public sessionsFolder: string;
abstract getFolderPath(sessionName: string): string;
}
export { LocalSessionStorage, SessionConfigRepository };
@@ -0,0 +1,19 @@
import { useMultiFileAuthState } from '@adiwajshing/baileys';
import { Store } from 'whatsapp-web.js';
import { DataStore } from '../../abc/DataStore';
import { LocalStore } from '../../storage/LocalStore';
export class NowebAuthFactoryCore {
buildAuth(store: DataStore, name: string) {
if (store instanceof LocalStore) return this.buildLocalAuth(store, name);
throw new Error(`Unsupported store type '${store.constructor.name}'`);
}
protected async buildLocalAuth(store: LocalStore, name: string) {
await store.init(name);
const authFolder = store.getSessionDirectory(name);
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
return { state, saveCreds };
}
}
@@ -5,6 +5,7 @@ import makeWASocket, {
getKeyAuthor,
isJidGroup,
jidNormalizedUser,
makeCacheableSignalKeyStore,
makeInMemoryStore,
PresenceData,
proto,
@@ -19,7 +20,7 @@ import { Agent } from 'https';
import * as lodash from 'lodash';
import { PairingCodeResponse } from 'src/structures/auth.dto';
import { flipObject, splitAt } from '../helpers';
import { flipObject, splitAt } from '../../../helpers';
import {
ChatRequest,
CheckNumberStatusQuery,
@@ -35,8 +36,8 @@ import {
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
} from '../../../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../../../structures/contacts.dto';
import {
ACK_UNKNOWN,
SECOND,
@@ -45,35 +46,36 @@ import {
WAHAPresenceStatus,
WAHASessionStatus,
WAMessageAck,
} from '../structures/enums.dto';
} from '../../../structures/enums.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
} from '../structures/groups.dto';
} from '../../../structures/groups.dto';
import {
WAHAChatPresences,
WAHAPresenceData,
} from '../structures/presence.dto';
import { WAMessage } from '../structures/responses.dto';
import { MeInfo } from '../structures/sessions.dto';
import { BROADCAST_ID, TextStatus } from '../structures/status.dto';
} from '../../../structures/presence.dto';
import { WAMessage } from '../../../structures/responses.dto';
import { MeInfo } from '../../../structures/sessions.dto';
import { BROADCAST_ID, TextStatus } from '../../../structures/status.dto';
import {
PollVote,
PollVotePayload,
WAMessageAckBody,
} from '../structures/webhooks.dto';
import { IEngineMediaProcessor } from './abc/media.abc';
} from '../../../structures/webhooks.dto';
import { IEngineMediaProcessor } from '../../abc/media.abc';
import {
ensureSuffix,
WAHAInternalEvent,
WhatsappSession,
} from './abc/session.abc';
} from '../../abc/session.abc';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { createAgentProxy } from './helpers.proxy';
import { QR } from './QR';
} from '../../exceptions';
import { createAgentProxy } from '../../helpers.proxy';
import { QR } from '../../QR';
import { NowebAuthFactoryCore } from './NowebAuthFactoryCore';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const QRCode = require('qrcode');
@@ -101,6 +103,8 @@ const ToEnginePresenceStatus = flipObject(PresenceStatuses);
export class WhatsappSessionNoWebCore extends WhatsappSession {
engine = WAHAEngine.NOWEB;
authFactory = new NowebAuthFactoryCore();
get listenConnectionEventsFromTheStart() {
return true;
}
@@ -123,7 +127,11 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
return {
agent: agent,
fetchAgent: agent,
auth: state,
auth: {
creds: state.creds,
/** caching makes the store faster to send/recv messages */
keys: makeCacheableSignalKeyStore(state.keys, logger),
},
printQRInTerminal: true,
browser: Browsers.macOS('Chrome'),
logger: logger,
@@ -134,9 +142,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
async makeSocket() {
const authFolder = this.sessionStorage.getFolderPath(this.name);
await fs.mkdir(authFolder, { recursive: true });
const { state, saveCreds } = await useMultiFileAuthState(authFolder);
const { state, saveCreds } = await this.authFactory.buildAuth(
this.sessionStore,
this.name,
);
const agent = this.makeAgent();
const socketConfig = this.getSocketConfig(agent, state);
const sock: any = makeWASocket(socketConfig);
@@ -234,9 +243,9 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
}
async stop() {
this.sock.ev.removeAllListeners();
this.sock.ws.removeAllListeners();
this.sock.ws.close();
this.sock?.ev?.removeAllListeners();
this.sock?.ws?.removeAllListeners();
this.sock?.ws?.close();
this.status = WAHASessionStatus.STOPPED;
return;
}
@@ -15,17 +15,17 @@ import {
MessageReplyRequest,
MessageTextRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
} from '../../../structures/chatting.dto';
import {
WAHAEngine,
WAHAEvents,
WAHASessionStatus,
} from '../structures/enums.dto';
import { WAMessage } from '../structures/responses.dto';
import { IEngineMediaProcessor } from './abc/media.abc';
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
import { NotImplementedByEngineError } from './exceptions';
import { QR } from './QR';
} from '../../../structures/enums.dto';
import { WAMessage } from '../../../structures/responses.dto';
import { IEngineMediaProcessor } from '../../abc/media.abc';
import { WAHAInternalEvent, WhatsappSession } from '../../abc/session.abc';
import { NotImplementedByEngineError } from '../../exceptions';
import { QR } from '../../QR';
export class WhatsappSessionVenomCore extends WhatsappSession {
engine = WAHAEngine.VENOM;
@@ -24,8 +24,8 @@ import {
MessageVoiceRequest,
SendSeenRequest,
WANumberExistResult,
} from '../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../structures/contacts.dto';
} from '../../../structures/chatting.dto';
import { ContactQuery, ContactRequest } from '../../../structures/contacts.dto';
import {
ACK_UNKNOWN,
WAHAEngine,
@@ -33,22 +33,22 @@ import {
WAHAPresenceStatus,
WAHASessionStatus,
WAMessageAck,
} from '../structures/enums.dto';
} from '../../../structures/enums.dto';
import {
CreateGroupRequest,
ParticipantsRequest,
SettingsSecurityChangeInfo,
} from '../structures/groups.dto';
import { WAMessage } from '../structures/responses.dto';
import { MeInfo } from '../structures/sessions.dto';
import { WAMessageRevokedBody } from '../structures/webhooks.dto';
import { IEngineMediaProcessor } from './abc/media.abc';
import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc';
} from '../../../structures/groups.dto';
import { WAMessage } from '../../../structures/responses.dto';
import { MeInfo } from '../../../structures/sessions.dto';
import { WAMessageRevokedBody } from '../../../structures/webhooks.dto';
import { IEngineMediaProcessor } from '../../abc/media.abc';
import { WAHAInternalEvent, WhatsappSession } from '../../abc/session.abc';
import {
AvailableInPlusVersion,
NotImplementedByEngineError,
} from './exceptions';
import { QR } from './QR';
} from '../../exceptions';
import { QR } from '../../QR';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const QRCode = require('qrcode');
@@ -67,7 +67,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
this.qr = new QR();
}
protected buildClient() {
protected async buildClient() {
const clientOptions: ClientOptions = {
puppeteer: {
headless: true,
@@ -98,12 +98,26 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
async start() {
this.status = WAHASessionStatus.STARTING;
this.whatsapp = this.buildClient();
this.whatsapp.initialize().catch((error) => {
this.status = WAHASessionStatus.FAILED;
this.log.error(error);
return;
});
this.whatsapp = await this.buildClient();
this.whatsapp
.initialize()
.then(() => {
// Listen for browser disconnected event
this.whatsapp.pupBrowser.on('disconnected', () => {
this.status = WAHASessionStatus.FAILED;
this.log.error('The browser has been disconnected');
});
// Listen for page close event
this.whatsapp.pupPage.on('close', () => {
this.status = WAHASessionStatus.FAILED;
this.log.error('The WhatsApp Web page has been closed');
});
})
.catch((error) => {
this.status = WAHASessionStatus.FAILED;
this.log.error(error);
return;
});
if (this.isDebugEnabled()) {
this.listenEngineEventsInDebugMode();
}
+12 -9
View File
@@ -19,13 +19,15 @@ import {
import { WebhookConfig } from '../structures/webhooks.config.dto';
import { SessionManager } from './abc/manager.abc';
import { SessionParams, WhatsappSession } from './abc/session.abc';
import { WhatsappSessionNoWebCore } from './engines/noweb/session.noweb.core';
import { WhatsappSessionVenomCore } from './engines/venom/session.venom.core';
import { WhatsappSessionWebJSCore } from './engines/webjs/session.webjs.core';
import { DOCS_URL } from './exceptions';
import { getProxyConfig } from './helpers.proxy';
import { CoreMediaManager, MediaStorageCore } from './media.core';
import { WhatsappSessionNoWebCore } from './session.noweb.core';
import { WhatsappSessionVenomCore } from './session.venom.core';
import { WhatsappSessionWebJSCore } from './session.webjs.core';
import { SessionStorageCore } from './storage.core';
import { LocalSessionAuthRepository } from './storage/LocalSessionAuthRepository';
import { LocalSessionConfigRepository } from './storage/LocalSessionConfigRepository';
import { LocalStoreCore } from './storage/LocalStoreCore';
import { WebhookConductorCore } from './webhooks.core';
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
@@ -59,8 +61,9 @@ export class SessionManagerCore extends SessionManager {
this.session = undefined;
const engineName = this.config.getDefaultEngineName();
this.EngineClass = this.getEngine(engineName);
this.sessionStorage = new SessionStorageCore(engineName.toLowerCase());
this.store = new LocalStoreCore(engineName.toLowerCase());
this.sessionAuthRepository = new LocalSessionAuthRepository(this.store);
this.sessionConfigRepository = new LocalSessionConfigRepository(this.store);
this.startPredefinedSessions();
}
@@ -116,11 +119,11 @@ export class SessionManagerCore extends SessionManager {
name,
mediaManager,
log,
sessionStorage: this.sessionStorage,
sessionStore: this.store,
proxyConfig: proxyConfig,
sessionConfig: request.config,
};
await this.sessionStorage.init(name);
await this.sessionAuthRepository.init(name);
// @ts-ignore
const session = new this.EngineClass(sessionConfig);
this.session = session;
@@ -181,7 +184,7 @@ export class SessionManagerCore extends SessionManager {
}
async logout(request: SessionLogoutRequest) {
await this.sessionStorage.clean(request.name);
await this.sessionAuthRepository.clean(request.name);
}
getSession(name: string): WhatsappSession {
-97
View File
@@ -1,97 +0,0 @@
import * as crypto from 'crypto';
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { SessionConfig } from '../structures/sessions.dto';
import {
LocalSessionStorage,
SessionConfigRepository,
} from './abc/storage.abc';
class LocalSessionConfigRepository extends SessionConfigRepository {
FILENAME = '.waha.session.config.json';
constructor(private storage: LocalSessionStorage) {
super();
}
async get(sessionName: string): Promise<SessionConfig | null> {
const filepath = this.getFilePath(sessionName);
// Check file exists
try {
await fs.access(filepath, fs.constants.F_OK);
} catch (error) {
return null;
}
// Try to load config
let content;
try {
content = await fs.readFile(filepath, 'utf-8');
} catch (error) {
return null;
}
return JSON.parse(content);
}
async save(sessionName: string, config: SessionConfig | null) {
// Create folder if not exist
const folder = this.storage.getFolderPath(sessionName);
await fs.mkdir(folder, { recursive: true });
// Save config
const filepath = this.getFilePath(sessionName);
const content = JSON.stringify(config || null);
await fs.writeFile(filepath, content);
}
private getFilePath(sessionName): string {
const folder = this.storage.getFolderPath(sessionName);
return path.join(folder, this.FILENAME);
}
}
export class SessionStorageCore extends LocalSessionStorage {
public sessionsFolder: string;
constructor(engine: string) {
super(engine);
this.sessionsFolder = path.join(os.tmpdir(), 'waha-sessions');
}
get engineFolder() {
return path.join(this.sessionsFolder, this.engine);
}
async init(sessionName?: string) {
const folder = sessionName
? this.getFolderPath(sessionName)
: this.engineFolder;
await fs.mkdir(folder, { recursive: true });
}
getFolderPath(name: string): string {
const suffix = crypto.createHash('md5').update(name).digest('hex');
return path.join(this.engineFolder, `${name}-${suffix}`);
}
async clean(sessionName: string) {
const folder = this.getFolderPath(sessionName);
await fs.rm(folder, { recursive: true });
}
async getAll(): Promise<string[]> {
await this.init();
const content = await fs.readdir(this.engineFolder, {
withFileTypes: true,
});
return content
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
}
get configRepository(): LocalSessionConfigRepository {
return new LocalSessionConfigRepository(this);
}
}
@@ -0,0 +1,7 @@
export abstract class ISessionAuthRepository {
abstract init(sessionName?: string): Promise<void>;
abstract clean(sessionName: string): Promise<void>;
abstract getAll(): Promise<string[]>;
}
@@ -0,0 +1,12 @@
import { SessionConfig } from '../../structures/sessions.dto';
export abstract class ISessionConfigRepository {
abstract save(
sessionName: string,
config: SessionConfig | null,
): Promise<void>;
abstract get(sessionName: string): Promise<SessionConfig | null>;
abstract delete(sessionName: string): Promise<void>;
}
@@ -0,0 +1,32 @@
import * as fs from 'fs/promises';
import { ISessionAuthRepository } from './ISessionAuthRepository';
import { LocalStore } from './LocalStore';
export class LocalSessionAuthRepository extends ISessionAuthRepository {
private store: LocalStore;
constructor(store: LocalStore) {
super();
this.store = store;
}
async init(sessionName?: string) {
await this.store.init(sessionName);
}
async clean(sessionName: string) {
const folder = this.store.getSessionDirectory(sessionName);
await fs.rm(folder, { recursive: true });
}
async getAll(): Promise<string[]> {
await this.init();
const content = await fs.readdir(this.store.getEngineDirectory(), {
withFileTypes: true,
});
return content
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
}
}
@@ -0,0 +1,66 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import { SessionConfig } from '../../structures/sessions.dto';
import { ISessionConfigRepository } from './ISessionConfigRepository';
import { LocalStore } from './LocalStore';
export class LocalSessionConfigRepository extends ISessionConfigRepository {
FILENAME = '.waha.session.config.json';
private store: LocalStore;
constructor(store: LocalStore) {
super();
this.store = store;
}
private async fileExists(filepath: string) {
try {
await fs.access(filepath, fs.constants.F_OK);
} catch (error) {
return false;
}
return true;
}
async get(sessionName: string): Promise<SessionConfig | null> {
const filepath = this.getFilePath(sessionName);
// Check file exists
if (!(await this.fileExists(filepath))) {
return null;
}
// Try to load config
let content;
try {
content = await fs.readFile(filepath, 'utf-8');
} catch (error) {
return null;
}
return JSON.parse(content);
}
async save(sessionName: string, config: SessionConfig | null) {
// Create folder if not exist
const folder = this.store.getSessionDirectory(sessionName);
await fs.mkdir(folder, { recursive: true });
// Save config
const filepath = this.getFilePath(sessionName);
const content = JSON.stringify(config || null);
await fs.writeFile(filepath, content);
}
private getFilePath(sessionName): string {
const folder = this.store.getSessionDirectory(sessionName);
return path.join(folder, this.FILENAME);
}
async delete(sessionName: string): Promise<void> {
const filepath = this.getFilePath(sessionName);
if (!(await this.fileExists(filepath))) {
return;
}
await fs.unlink(filepath);
}
}
+20
View File
@@ -0,0 +1,20 @@
import { DataStore } from '../abc/DataStore';
export abstract class LocalStore extends DataStore {
abstract init(sessionName?: string): Promise<void>;
/**
* Get the directory where all the engines and sessions are stored
*/
abstract getBaseDirectory(): string;
/**
* Get the directory where the engine sessions are stored
*/
abstract getEngineDirectory(): string;
/**
* Get the directory where the session data is stored
*/
abstract getSessionDirectory(name: string): string;
}
+52
View File
@@ -0,0 +1,52 @@
import * as crypto from 'crypto';
import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { LocalStore } from './LocalStore';
export class LocalStoreCore extends LocalStore {
protected readonly baseDirectory: string = path.join(
os.tmpdir(),
'waha-sessions',
);
private readonly engine: string;
constructor(engine: string) {
super();
this.engine = engine;
}
async init(sessionName?: string) {
await fs.mkdir(this.getEngineDirectory(), { recursive: true });
if (sessionName) {
await fs.mkdir(this.getSessionDirectory(sessionName), {
recursive: true,
});
}
}
/**
* Get the directory where all the engines and sessions are stored
*/
getBaseDirectory() {
return path.join(this.baseDirectory);
}
/**
* Get the directory where the engine sessions are stored
*/
getEngineDirectory() {
return path.join(this.baseDirectory, this.engine);
}
getSessionDirectory(name: string): string {
return this.getDirectoryPath(name);
}
protected getDirectoryPath(name: string): string {
const suffix = crypto.createHash('md5').update(name).digest('hex');
return path.join(this.getEngineDirectory(), `${name}-${suffix}`);
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2023.12.3',
version: '2024.2.3',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
+1252 -1094
View File
File diff suppressed because it is too large. Load diff