From fe4246036d44dac5bbbdebc1fb1c729e17e825f4 Mon Sep 17 00:00:00 2001 From: allburov Date: Sun, 18 Feb 2024 19:19:33 +0700 Subject: [PATCH] [core] Support MongoDB --- docker-compose.yaml | 12 ++ .../content/en/docs/how-to/config/index.md | 13 +- .../content/en/docs/how-to/sessions/index.md | 24 +-- .../content/en/docs/how-to/storages/index.md | 139 +++++++++++++++++ .../en/docs/how-to/storages/waha-mongodb.png | Bin 0 -> 73472 bytes .../content/en/docs/overview/changelog.md | 10 ++ package.json | 3 +- src/api/sessions.controller.ts | 2 +- src/config.service.ts | 4 + src/core/abc/DataStore.ts | 1 + src/core/abc/manager.abc.ts | 7 +- src/core/abc/session.abc.ts | 11 +- src/core/abc/storage.abc.ts | 35 ----- .../engines/noweb/NowebAuthFactoryCore.ts | 19 +++ .../{ => engines/noweb}/session.noweb.core.ts | 47 +++--- .../{ => engines/venom}/session.venom.core.ts | 14 +- .../{ => engines/webjs}/session.webjs.core.ts | 26 ++-- src/core/manager.core.ts | 21 +-- src/core/storage.core.ts | 97 ------------ src/core/storage/ISessionAuthRepository.ts | 7 + src/core/storage/ISessionConfigRepository.ts | 12 ++ .../storage/LocalSessionAuthRepository.ts | 32 ++++ .../storage/LocalSessionConfigRepository.ts | 56 +++++++ src/core/storage/LocalStore.ts | 20 +++ src/core/storage/LocalStoreCore.ts | 51 +++++++ yarn.lock | 142 +++++++++++++++++- 26 files changed, 587 insertions(+), 218 deletions(-) create mode 100644 docs/site/content/en/docs/how-to/storages/index.md create mode 100644 docs/site/content/en/docs/how-to/storages/waha-mongodb.png create mode 100644 src/core/abc/DataStore.ts delete mode 100644 src/core/abc/storage.abc.ts create mode 100644 src/core/engines/noweb/NowebAuthFactoryCore.ts rename src/core/{ => engines/noweb}/session.noweb.core.ts (95%) rename src/core/{ => engines/venom}/session.venom.core.ts (95%) rename src/core/{ => engines/webjs}/session.webjs.core.ts (96%) delete mode 100644 src/core/storage.core.ts create mode 100644 src/core/storage/ISessionAuthRepository.ts create mode 100644 src/core/storage/ISessionConfigRepository.ts create mode 100644 src/core/storage/LocalSessionAuthRepository.ts create mode 100644 src/core/storage/LocalSessionConfigRepository.ts create mode 100644 src/core/storage/LocalStore.ts create mode 100644 src/core/storage/LocalStoreCore.ts diff --git a/docker-compose.yaml b/docker-compose.yaml index 23fe3830..4fe7b0bd 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -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: {} diff --git a/docs/site/content/en/docs/how-to/config/index.md b/docs/site/content/en/docs/how-to/config/index.md index 189c1794..b5ed46ac 100644 --- a/docs/site/content/en/docs/how-to/config/index.md +++ b/docs/site/content/en/docs/how-to/config/index.md @@ -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 +Security is available in [WAHA Plus ![](/images/versions/plus.png)]({{< relref "/docs/how-to/plus-version" >}}) only. + - `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 +Files configuration is available in [WAHA Plus ![](/images/versions/plus.png)]({{< relref "/docs/how-to/plus-version" >}}) only. 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`. diff --git a/docs/site/content/en/docs/how-to/sessions/index.md b/docs/site/content/en/docs/how-to/sessions/index.md index 7f07e6be..3a098d6a 100644 --- a/docs/site/content/en/docs/how-to/sessions/index.md +++ b/docs/site/content/en/docs/how-to/sessions/index.md @@ -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 - diff --git a/docs/site/content/en/docs/how-to/storages/index.md b/docs/site/content/en/docs/how-to/storages/index.md new file mode 100644 index 00000000..9b257744 --- /dev/null +++ b/docs/site/content/en/docs/how-to/storages/index.md @@ -0,0 +1,139 @@ +--- +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. + +Storages are available in [WAHA Plus ![](/images/versions/plus.png)]({{< relref "/docs/how-to/plus-version" >}}) only. + +## 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 well tested solution 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. + +### 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: +```bash +docker run -e WHATSAPP_SESSIONS_MONGO_URL=mongodb://localhost:27017/ -p 3000:3000/tcp 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 ->]({{}}). + +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 ->]({{}}). + +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). diff --git a/docs/site/content/en/docs/how-to/storages/waha-mongodb.png b/docs/site/content/en/docs/how-to/storages/waha-mongodb.png new file mode 100644 index 0000000000000000000000000000000000000000..04a6c1d8523856ac69b242ab1313f53f9d54e98c GIT binary patch literal 73472 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 HoD!Mo%Z literal 0 HcmV?d00001 diff --git a/docs/site/content/en/docs/overview/changelog.md b/docs/site/content/en/docs/overview/changelog.md index 8f467e6e..f5467efc 100644 --- a/docs/site/content/en/docs/overview/changelog.md +++ b/docs/site/content/en/docs/overview/changelog.md @@ -19,6 +19,16 @@ 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) diff --git a/package.json b/package.json index e81997a4..28f2cb78 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/api/sessions.controller.ts b/src/api/sessions.controller.ts index 5b420eac..3cfcd320 100644 --- a/src/api/sessions.controller.ts +++ b/src/api/sessions.controller.ts @@ -29,7 +29,7 @@ class SessionsController { @Post('/start/') async start(@Body() request: SessionStartRequest): Promise { const result = await this.manager.start(request); - await this.manager.sessionStorage.configRepository.save( + await this.manager.sessionConfigRepository.save( request.name, request.config || null, ); diff --git a/src/config.service.ts b/src/config.service.ts index b47ae18a..4e414910 100644 --- a/src/config.service.ts +++ b/src/config.service.ts @@ -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) { diff --git a/src/core/abc/DataStore.ts b/src/core/abc/DataStore.ts new file mode 100644 index 00000000..23e5a723 --- /dev/null +++ b/src/core/abc/DataStore.ts @@ -0,0 +1 @@ +export abstract class DataStore {} diff --git a/src/core/abc/manager.abc.ts b/src/core/abc/manager.abc.ts index 239ec0f7..a1369d7e 100644 --- a/src/core/abc/manager.abc.ts +++ b/src/core/abc/manager.abc.ts @@ -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; diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index cd14f998..9c6e2fcf 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -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; } diff --git a/src/core/abc/storage.abc.ts b/src/core/abc/storage.abc.ts deleted file mode 100644 index 5c4c005a..00000000 --- a/src/core/abc/storage.abc.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { SessionConfig } from '../../structures/sessions.dto'; - -abstract class SessionConfigRepository { - abstract save( - sessionName: string, - config: SessionConfig | null, - ): Promise; - - abstract get(sessionName: string): Promise; -} - -abstract class SessionStorage { - protected constructor(public engine: string) { - this.engine = engine; - } - - abstract init(sessionName?: string): Promise; - - abstract clean(sessionName: string): Promise; - - abstract getAll(): Promise; - - // - // Repositories - // - abstract get configRepository(): SessionConfigRepository; -} - -abstract class LocalSessionStorage extends SessionStorage { - public sessionsFolder: string; - - abstract getFolderPath(sessionName: string): string; -} - -export { LocalSessionStorage, SessionConfigRepository }; diff --git a/src/core/engines/noweb/NowebAuthFactoryCore.ts b/src/core/engines/noweb/NowebAuthFactoryCore.ts new file mode 100644 index 00000000..df7c1c87 --- /dev/null +++ b/src/core/engines/noweb/NowebAuthFactoryCore.ts @@ -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 }; + } +} diff --git a/src/core/session.noweb.core.ts b/src/core/engines/noweb/session.noweb.core.ts similarity index 95% rename from src/core/session.noweb.core.ts rename to src/core/engines/noweb/session.noweb.core.ts index f35334dc..c799cedc 100644 --- a/src/core/session.noweb.core.ts +++ b/src/core/engines/noweb/session.noweb.core.ts @@ -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); diff --git a/src/core/session.venom.core.ts b/src/core/engines/venom/session.venom.core.ts similarity index 95% rename from src/core/session.venom.core.ts rename to src/core/engines/venom/session.venom.core.ts index fc52ab2d..4ab10414 100644 --- a/src/core/session.venom.core.ts +++ b/src/core/engines/venom/session.venom.core.ts @@ -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; diff --git a/src/core/session.webjs.core.ts b/src/core/engines/webjs/session.webjs.core.ts similarity index 96% rename from src/core/session.webjs.core.ts rename to src/core/engines/webjs/session.webjs.core.ts index 5bf6494e..c7a0ed76 100644 --- a/src/core/session.webjs.core.ts +++ b/src/core/engines/webjs/session.webjs.core.ts @@ -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,7 +98,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { async start() { this.status = WAHASessionStatus.STARTING; - this.whatsapp = this.buildClient(); + this.whatsapp = await this.buildClient(); this.whatsapp .initialize() .then(() => { diff --git a/src/core/manager.core.ts b/src/core/manager.core.ts index ea37bf86..47b2e8a3 100644 --- a/src/core/manager.core.ts +++ b/src/core/manager.core.ts @@ -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 { diff --git a/src/core/storage.core.ts b/src/core/storage.core.ts deleted file mode 100644 index d08eac24..00000000 --- a/src/core/storage.core.ts +++ /dev/null @@ -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 { - 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 { - 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); - } -} diff --git a/src/core/storage/ISessionAuthRepository.ts b/src/core/storage/ISessionAuthRepository.ts new file mode 100644 index 00000000..f9b2a4e3 --- /dev/null +++ b/src/core/storage/ISessionAuthRepository.ts @@ -0,0 +1,7 @@ +export abstract class ISessionAuthRepository { + abstract init(sessionName?: string): Promise; + + abstract clean(sessionName: string): Promise; + + abstract getAll(): Promise; +} diff --git a/src/core/storage/ISessionConfigRepository.ts b/src/core/storage/ISessionConfigRepository.ts new file mode 100644 index 00000000..f5cb3d19 --- /dev/null +++ b/src/core/storage/ISessionConfigRepository.ts @@ -0,0 +1,12 @@ +import { SessionConfig } from '../../structures/sessions.dto'; + +export abstract class ISessionConfigRepository { + abstract save( + sessionName: string, + config: SessionConfig | null, + ): Promise; + + abstract get(sessionName: string): Promise; + + abstract delete(sessionName: string): Promise; +} diff --git a/src/core/storage/LocalSessionAuthRepository.ts b/src/core/storage/LocalSessionAuthRepository.ts new file mode 100644 index 00000000..2a7ef56c --- /dev/null +++ b/src/core/storage/LocalSessionAuthRepository.ts @@ -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 { + await this.init(); + const content = await fs.readdir(this.store.getEngineDirectory(), { + withFileTypes: true, + }); + return content + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name); + } +} diff --git a/src/core/storage/LocalSessionConfigRepository.ts b/src/core/storage/LocalSessionConfigRepository.ts new file mode 100644 index 00000000..3c2c30fa --- /dev/null +++ b/src/core/storage/LocalSessionConfigRepository.ts @@ -0,0 +1,56 @@ +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; + } + + async get(sessionName: string): Promise { + 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.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 { + const filepath = this.getFilePath(sessionName); + await fs.unlink(filepath); + } +} diff --git a/src/core/storage/LocalStore.ts b/src/core/storage/LocalStore.ts new file mode 100644 index 00000000..eed83f19 --- /dev/null +++ b/src/core/storage/LocalStore.ts @@ -0,0 +1,20 @@ +import { DataStore } from '../abc/DataStore'; + +export abstract class LocalStore extends DataStore { + abstract init(sessionName?: string): Promise; + + /** + * 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; +} diff --git a/src/core/storage/LocalStoreCore.ts b/src/core/storage/LocalStoreCore.ts new file mode 100644 index 00000000..fd19eb98 --- /dev/null +++ b/src/core/storage/LocalStoreCore.ts @@ -0,0 +1,51 @@ +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 implements LocalStore { + protected readonly baseDirectory: string = path.join( + os.tmpdir(), + 'waha-sessions', + ); + + private readonly engine: string; + + constructor(engine: string) { + 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}`); + } +} diff --git a/yarn.lock b/yarn.lock index 8e7d727d..04448adb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -935,6 +935,15 @@ __metadata: languageName: node linkType: hard +"@mongodb-js/saslprep@npm:^1.1.0": + version: 1.1.4 + resolution: "@mongodb-js/saslprep@npm:1.1.4" + dependencies: + sparse-bitfield: ^3.0.3 + checksum: 208fd6f82136fd4332d0a6c667f8090b08f365dd7aa5880b8c485501caed7b058a99c231085c51ad7fa25f4a590d96c87af9a5b3fc0aea4de8c527657e33e548 + languageName: node + linkType: hard + "@nestjs/cli@npm:^9.0.0": version: 9.5.0 resolution: "@nestjs/cli@npm:9.5.0" @@ -1818,6 +1827,22 @@ __metadata: languageName: node linkType: hard +"@types/webidl-conversions@npm:*": + version: 7.0.3 + resolution: "@types/webidl-conversions@npm:7.0.3" + checksum: 535ead9de4d3d6c8e4f4fa14e9db780d2a31e8020debc062f337e1420a41c3265e223e4f4b628f97a11ecf3b96390962cd88a9ffe34f44e159dec583ff49aa34 + languageName: node + linkType: hard + +"@types/whatwg-url@npm:^11.0.2": + version: 11.0.4 + resolution: "@types/whatwg-url@npm:11.0.4" + dependencies: + "@types/webidl-conversions": "*" + checksum: 5acd60dbd49df34b9131ea75dc5e24b03e084cc4ed5a273ddd801fdaf21c896abbbb846bb3ba71ffd5c715120bac0454debe569f39b9177887fb7636d65cdae4 + languageName: node + linkType: hard + "@types/ws@npm:^8.5.4": version: 8.5.5 resolution: "@types/ws@npm:8.5.5" @@ -2355,6 +2380,13 @@ __metadata: languageName: node linkType: hard +"adm-zip@npm:^0.5.10": + version: 0.5.10 + resolution: "adm-zip@npm:0.5.10" + checksum: 07ed91cf6423bf5dca4ee63977bc7635e91b8d21829c00829d48dce4c6932e1b19e6cfcbe44f1931c956e68795ae97183fc775913883fa48ce88a1ac11fb2034 + languageName: node + linkType: hard + "agent-base@npm:6, agent-base@npm:^6.0.2": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -3257,6 +3289,13 @@ __metadata: languageName: node linkType: hard +"bson@npm:^6.2.0": + version: 6.2.0 + resolution: "bson@npm:6.2.0" + checksum: 950fccd2abd0ff5a1bd3637f4697631298f1538314994ab8c9e13f1c9851d0fd042b54fe8340e00151c2acee43917ea40e64b800ceeea811b00f2de3e900c77e + languageName: node + linkType: hard + "buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -8634,6 +8673,13 @@ __metadata: languageName: node linkType: hard +"memory-pager@npm:^1.0.2": + version: 1.5.0 + resolution: "memory-pager@npm:1.5.0" + checksum: d1a2e684583ef55c61cd3a49101da645b11ad57014dfc565e0b43baa9004b743f7e4ab81493d8fff2ab24e9950987cc3209c94bcc4fc8d7e30a475489a1f15e9 + languageName: node + linkType: hard + "merge-deep@npm:^3.0.1": version: 3.0.3 resolution: "merge-deep@npm:3.0.3" @@ -8970,6 +9016,50 @@ __metadata: languageName: node linkType: hard +"mongodb-connection-string-url@npm:^3.0.0": + version: 3.0.0 + resolution: "mongodb-connection-string-url@npm:3.0.0" + dependencies: + "@types/whatwg-url": ^11.0.2 + whatwg-url: ^13.0.0 + checksum: 9d4885377345b14e2ba2ee63cb3085364a01aeae3aca622bbd568e7761caa58fdc664528f19f125c762cff230c9349f99e98396dd2271a4260286cf241a8c477 + languageName: node + linkType: hard + +"mongodb@npm:^6.3.0": + version: 6.3.0 + resolution: "mongodb@npm:6.3.0" + dependencies: + "@mongodb-js/saslprep": ^1.1.0 + bson: ^6.2.0 + mongodb-connection-string-url: ^3.0.0 + peerDependencies: + "@aws-sdk/credential-providers": ^3.188.0 + "@mongodb-js/zstd": ^1.1.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: ">=6.0.0 <7" + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + "@aws-sdk/credential-providers": + optional: true + "@mongodb-js/zstd": + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + checksum: ebc5d9dbd1299321b6873e86eb4ea635316f97450644811db24ce2b01432b1c641def864facf2eab6f0c0c5c360c318108ea5555142f55177ca4c33991c6d7c4 + languageName: node + linkType: hard + "mpg123-decoder@npm:^0.4.8": version: 0.4.8 resolution: "mpg123-decoder@npm:0.4.8" @@ -10176,6 +10266,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^2.3.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: bb0a0ceedca4c3c57a9b981b90601579058903c62be23c5e8e843d2c2d4148a3ecf029d5133486fb0e1822b098ba8bba09e89d6b21742d02fa26bda6441a6fb2 + languageName: node + linkType: hard + "puppeteer-core@npm:20.9.0": version: 20.9.0 resolution: "puppeteer-core@npm:20.9.0" @@ -11449,6 +11546,15 @@ __metadata: languageName: node linkType: hard +"sparse-bitfield@npm:^3.0.3": + version: 3.0.3 + resolution: "sparse-bitfield@npm:3.0.3" + dependencies: + memory-pager: ^1.0.2 + checksum: 174da88dbbcc783d5dbd26921931cc83830280b8055fb05333786ebe6fc015b9601b24972b3d55920dd2d9f5fb120576fbfa2469b08e5222c9cadf3f05210aab + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -12196,6 +12302,15 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^4.1.1": + version: 4.1.1 + resolution: "tr46@npm:4.1.1" + dependencies: + punycode: ^2.3.0 + checksum: aeeb821ac2cd792e63ec84888b4fd6598ac6ed75d861579e21a5cf9d4ee78b2c6b94e7d45036f2ca2088bc85b9b46560ad23c4482979421063b24137349dbd96 + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -13015,6 +13130,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: f05588567a2a76428515333eff87200fae6c83c3948a7482ebb109562971e77ef6dc49749afa58abb993391227c5697b3ecca52018793e0cb4620a48f10bd21b + languageName: node + linkType: hard + "webpack-node-externals@npm:3.0.0": version: 3.0.0 resolution: "webpack-node-externals@npm:3.0.0" @@ -13104,6 +13226,7 @@ __metadata: 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 prettier: ^1.19.1 @@ -13122,15 +13245,16 @@ __metadata: tsconfig-paths: ^4.1.0 typescript: ^4.8.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" languageName: unknown linkType: soft -"whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js": +"whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js#main-fork": version: 1.23.1-alpha.4 - resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=ef2f725bce1bd1afd8491f9de0cd2ed06a6febc3" + resolution: "whatsapp-web.js@https://github.com/devlikeapro/whatsapp-web.js.git#commit=6ab7270e9a2c86a81efb8831b7817da10daffff2" dependencies: "@pedroslopez/moduleraid": ^5.0.2 + adm-zip: ^0.5.10 archiver: ^5.3.1 fluent-ffmpeg: 2.1.2 fs-extra: ^10.1.0 @@ -13146,7 +13270,7 @@ __metadata: optional: true unzipper: optional: true - checksum: 36062f9aa97e5d3f2ae50235a1535ae4b67550ea40f875794590cbaed6045b6b5ae56bd3edd79092419018f5509ff8dfb8f03cb894ed76315f9583d546a4f4be + checksum: 425fa37ba93bc42a32609dde9e102a11e2783750119972153593a887200afbdf555dc3b559c346274b72d92ceb00cc363cf714b6ff40c8c35103676234170837 languageName: node linkType: hard @@ -13166,6 +13290,16 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^13.0.0": + version: 13.0.0 + resolution: "whatwg-url@npm:13.0.0" + dependencies: + tr46: ^4.1.1 + webidl-conversions: ^7.0.0 + checksum: 7f69272a1bfd5f0d994988b9e234e35d21071a9bffe0d6fd4477d295552665c566b176ff8e0251a0a79c61c5a67a7a392e248aae5887d7e22bdff0125209e26b + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0"