[core] Media improvements
- Disable downloading files - Add 'media' field
This commit is contained in:
1 parent
7904516c97
commit
7b7bd02503
20 files changed
+335
-132
No files matched your search
@@ -72,14 +72,34 @@ Keep in mind that session's proxy configuration takes precedence over proxy conf
|
||||
|
||||
The following environment variables can be used to configure the file storage options for the WAHA:
|
||||
|
||||
- `WHATSAPP_FILES_MIMETYPES`: This variable can be used to download only specific mimetypes from messages. By default,
|
||||
all files are downloaded. The mimetypes must be separated by a comma, without spaces. For
|
||||
example: `audio,image/png,image/gif`. To choose a specific type, use a prefix (like `audio,image`).
|
||||
- `WHATSAPP_FILES_MIMETYPES`: This variable can be used to download only specific mimetypes from messages.
|
||||
By default, all files are downloaded. The mimetypes must be separated by a comma, without spaces.
|
||||
For example: `audio,image/png,image/gif`. To choose a specific type, use a prefix (like `audio,image`). See usage below.
|
||||
- `WHATSAPP_DOWNLOAD_MEDIA=true` - this variable can be used to **completely** disable downloading media files. By default, all files are downloaded.
|
||||
Set this variable to `WHATSAPP_DOWNLOAD_MEDIA=false` to disable downloading media files.
|
||||
- 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`.
|
||||
- `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`.
|
||||
|
||||
💡 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`.
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"hasMedia": true,
|
||||
"media": {
|
||||
"url": null,
|
||||
"mimetype": "video/mp4",
|
||||
"filename": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
#### Debug Mode
|
||||
@@ -138,6 +158,13 @@ comma-separated list of mimetypes:
|
||||
WHATSAPP_FILES_MIMETYPES=audio,image/png,image/gif
|
||||
```
|
||||
|
||||
#### Disable Downloading Media Files
|
||||
To disable downloading media files, set the `WHATSAPP_DOWNLOAD_MEDIA` environment variable to `false`:
|
||||
|
||||
```
|
||||
WHATSAPP_DOWNLOAD_MEDIA=false
|
||||
```
|
||||
|
||||
#### Setting the File Lifetime
|
||||
|
||||
To set the time (in seconds) after which files will be removed to free up space, set the `WHATSAPP_FILES_LIFETIME`
|
||||
|
||||
@@ -155,9 +155,9 @@ Receive events when server or recipient gets the message, read or played it.
|
||||
## Files 
|
||||
|
||||
When people send you media - images, voice messages, and documents - WAHA saves it in the file storage.
|
||||
In your application you must download it and use it as you want to. You can find the URL in `mediaUrl` field.
|
||||
In your application you must download it and use it as you want to. You can find the URL in `media.url` field.
|
||||
|
||||
For example, you can get the webhook like this with `mediaUrl` value (we've skipped other fields):
|
||||
For example, you can get the webhook like this with `media` value (we've skipped other fields):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -167,7 +167,12 @@ For example, you can get the webhook like this with `mediaUrl` value (we've skip
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
"from": "11111111111@c.us",
|
||||
"mediaUrl": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.jpg"
|
||||
"hasMedia": true,
|
||||
"media": {
|
||||
"url": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.jpg",
|
||||
"mimetype": "image/jpeg",
|
||||
"filename": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -182,8 +187,11 @@ For documents (file attachments) there's also `filename` field with the original
|
||||
"id": "true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
"timestamp": 1667561485,
|
||||
"from": "11111111111@c.us",
|
||||
"mediaUrl": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.pdf",
|
||||
"filename": "some-file.pdf"
|
||||
"media": {
|
||||
"url": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.pdf",
|
||||
"filename": "some-file.pdf",
|
||||
"mimetype": "application/pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -21,6 +21,25 @@ You even can **subscribe to get new updates** there!
|
||||
|
||||
## 2023.11
|
||||
November 2023
|
||||
- Add dedicated `media` field in webhook payload - now you can know which media is attached to the message.
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"session": "default",
|
||||
"payload": {
|
||||
"hasMedia": true,
|
||||
"media": {
|
||||
"url": "http://localhost:3000/api/files/true_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.pdf",
|
||||
"mimetype": "application/pdf",
|
||||
"filename": "file.pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
- Add `WHATSAPP_DOWNLOAD_MEDIA` environment variable to control media download. Available only in Plus version.
|
||||
- `WHATSAPP_DOWNLOAD_MEDIA=True` - download media
|
||||
- `WHATSAPP_DOWNLOAD_MEDIA=False` - do not download media at all.
|
||||
- Add `filename` field for document (file attachments) - the original filename of the document.
|
||||
- Add `/api/sendVideo` endpoint to **NOWEB** and **WEBJS** engines. Available only in Plus version.
|
||||
- Add security settings for edit group info `GET /api/{session}/groups/{groupId}/settings/security/info-admin-only` - updates the group settings to only allow admins to edit group info (title, description, photo).
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||
import { UnprocessableEntityException } from '@nestjs/common/exceptions/unprocessable-entity.exception';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiOperation,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
} from '@nestjs/swagger';
|
||||
|
||||
import { SessionManager } from '../core/abc/manager.abc';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { parseBool } from '../helpers';
|
||||
import {
|
||||
ListSessionsQuery,
|
||||
@@ -19,9 +19,6 @@ import {
|
||||
SessionStopRequest,
|
||||
} from '../structures/sessions.dto';
|
||||
import { SessionApiParam, SessionParam } from './helpers';
|
||||
import { WhatsappSession } from '../core/abc/session.abc';
|
||||
import { WAHAChatPresences } from '../structures/presence.dto';
|
||||
import { WAHAWebhook } from '../structures/webhooks.dto';
|
||||
|
||||
@ApiSecurity('api_key')
|
||||
@Controller('api/sessions')
|
||||
@@ -34,7 +31,7 @@ class SessionsController {
|
||||
const result = await this.manager.start(request);
|
||||
await this.manager.sessionStorage.configRepository.save(
|
||||
request.name,
|
||||
request.config,
|
||||
request.config || null,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
@@ -71,7 +68,7 @@ class SessionController {
|
||||
@Get('me')
|
||||
@SessionApiParam
|
||||
@ApiOperation({ summary: 'Get information about the authenticated account' })
|
||||
getMe(@SessionParam session: WhatsappSession): Promise<MeInfo> {
|
||||
getMe(@SessionParam session: WhatsappSession): Promise<MeInfo | null> {
|
||||
return session.getSessionMeInfo();
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -36,9 +36,17 @@ export class WhatsappConfigService {
|
||||
return this.configService.get<number>('WHATSAPP_FILES_LIFETIME', 180);
|
||||
}
|
||||
|
||||
get mimetypes(): string[] | null {
|
||||
get mimetypes(): string[] {
|
||||
if (!this.shouldDownloadMedia) {
|
||||
return ['mimetype/ignore-all-media'];
|
||||
}
|
||||
const types = this.configService.get('WHATSAPP_FILES_MIMETYPES', '');
|
||||
return types ? types.split(',') : null;
|
||||
return types ? types.split(',') : [];
|
||||
}
|
||||
|
||||
get shouldDownloadMedia(): boolean {
|
||||
const value = this.configService.get('WHATSAPP_DOWNLOAD_MEDIA', 'true');
|
||||
return parseBool(value);
|
||||
}
|
||||
|
||||
get startSessions(): string[] {
|
||||
@@ -111,7 +119,7 @@ export class WhatsappConfigService {
|
||||
return WAHAEngine.WEBJS;
|
||||
}
|
||||
|
||||
get(name: string, defaultValue = undefined): any {
|
||||
get(name: string, defaultValue: any = undefined): any {
|
||||
return this.configService.get(name, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
SessionStopRequest,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { WhatsappSession } from './session.abc';
|
||||
import { LocalSessionStorage, MediaStorage } from './storage.abc';
|
||||
import { LocalSessionStorage } from './storage.abc';
|
||||
import { WebhookConductor } from './webhooks.abc';
|
||||
|
||||
export abstract class SessionManager implements OnApplicationShutdown {
|
||||
@@ -21,8 +21,6 @@ export abstract class SessionManager implements OnApplicationShutdown {
|
||||
|
||||
protected abstract get WebhookConductorClass(): typeof WebhookConductor;
|
||||
|
||||
protected abstract get MediaStorageClass(): typeof MediaStorage;
|
||||
|
||||
abstract onApplicationShutdown(signal?: string);
|
||||
|
||||
//
|
||||
@@ -34,7 +32,7 @@ export abstract class SessionManager implements OnApplicationShutdown {
|
||||
|
||||
abstract logout(request: SessionLogoutRequest): Promise<void>;
|
||||
|
||||
abstract getSession(name: string, error?: boolean): WhatsappSession;
|
||||
abstract getSession(name: string): WhatsappSession;
|
||||
|
||||
abstract getSessions(all: boolean): Promise<SessionInfo[]>;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
abstract class MediaStorage {
|
||||
abstract save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
interface IEngineMediaProcessor<Message> {
|
||||
hasMedia(message: Message): boolean;
|
||||
|
||||
getFilename(message: Message): string | null;
|
||||
|
||||
getMimetype(message: Message): string;
|
||||
|
||||
getMessageId(message: Message): string;
|
||||
|
||||
getMediaBuffer(message: Message): Promise<Buffer | null>;
|
||||
}
|
||||
|
||||
interface MediaManager {
|
||||
processMedia<Message>(
|
||||
processor: IEngineMediaProcessor<Message>,
|
||||
message: Message,
|
||||
): Promise<Message>;
|
||||
}
|
||||
|
||||
export { IEngineMediaProcessor, MediaManager, MediaStorage };
|
||||
@@ -3,6 +3,7 @@ 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,
|
||||
@@ -37,9 +38,6 @@ import {
|
||||
ProxyConfig,
|
||||
SessionConfig,
|
||||
} from '../../structures/sessions.dto';
|
||||
import { NotImplementedByEngineError } from '../exceptions';
|
||||
import { LocalSessionStorage, MediaStorage } from './storage.abc';
|
||||
import { OTPRequest, RequestCodeRequest } from '../../structures/auth.dto';
|
||||
import {
|
||||
ImageStatus,
|
||||
TextStatus,
|
||||
@@ -47,6 +45,9 @@ import {
|
||||
VoiceStatus,
|
||||
} from '../../structures/status.dto';
|
||||
import { WASessionStatusBody } from '../../structures/webhooks.dto';
|
||||
import { NotImplementedByEngineError } from '../exceptions';
|
||||
import { MediaManager } from './media.abc';
|
||||
import { LocalSessionStorage } from './storage.abc';
|
||||
|
||||
const CHROME_PATH = '/usr/bin/google-chrome-stable';
|
||||
const CHROMIUM_PATH = '/usr/bin/chromium';
|
||||
@@ -73,11 +74,11 @@ export enum WAHAInternalEvent {
|
||||
|
||||
export interface SessionParams {
|
||||
name: string;
|
||||
storage: MediaStorage;
|
||||
mediaManager: MediaManager;
|
||||
log: ConsoleLogger;
|
||||
sessionStorage: LocalSessionStorage;
|
||||
proxyConfig?: ProxyConfig;
|
||||
sessionConfig: SessionConfig;
|
||||
sessionConfig?: SessionConfig;
|
||||
}
|
||||
|
||||
export abstract class WhatsappSession {
|
||||
@@ -85,11 +86,11 @@ export abstract class WhatsappSession {
|
||||
public engine: WAHAEngine;
|
||||
|
||||
public name: string;
|
||||
protected storage: MediaStorage;
|
||||
protected mediaManager: MediaManager;
|
||||
protected log: ConsoleLogger;
|
||||
protected sessionStorage: LocalSessionStorage;
|
||||
protected proxyConfig?: ProxyConfig;
|
||||
public sessionConfig: SessionConfig;
|
||||
public sessionConfig?: SessionConfig;
|
||||
|
||||
private _status: WAHASessionStatus;
|
||||
|
||||
@@ -98,7 +99,7 @@ export abstract class WhatsappSession {
|
||||
log,
|
||||
sessionStorage,
|
||||
proxyConfig,
|
||||
storage,
|
||||
mediaManager,
|
||||
sessionConfig,
|
||||
}: SessionParams) {
|
||||
this.events = new EventEmitter();
|
||||
@@ -106,7 +107,7 @@ export abstract class WhatsappSession {
|
||||
this.proxyConfig = proxyConfig;
|
||||
this.log = log;
|
||||
this.sessionStorage = sessionStorage;
|
||||
this.storage = storage;
|
||||
this.mediaManager = mediaManager;
|
||||
this.sessionConfig = sessionConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { SessionConfig } from '../../structures/sessions.dto';
|
||||
|
||||
abstract class MediaStorage {
|
||||
abstract save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
abstract class SessionConfigRepository {
|
||||
abstract save(sessionName: string, config: SessionConfig): Promise<void>;
|
||||
abstract save(
|
||||
sessionName: string,
|
||||
config: SessionConfig | null,
|
||||
): Promise<void>;
|
||||
|
||||
abstract get(sessionName: string): Promise<SessionConfig>;
|
||||
abstract get(sessionName: string): Promise<SessionConfig | null>;
|
||||
}
|
||||
|
||||
abstract class SessionStorage {
|
||||
@@ -37,4 +32,4 @@ abstract class LocalSessionStorage extends SessionStorage {
|
||||
abstract getFolderPath(sessionName: string): string;
|
||||
}
|
||||
|
||||
export { LocalSessionStorage, MediaStorage, SessionConfigRepository };
|
||||
export { LocalSessionStorage, SessionConfigRepository };
|
||||
@@ -9,7 +9,7 @@ import { WhatsappSession } from './abc/session.abc';
|
||||
export function getProxyConfig(
|
||||
config: WhatsappConfigService,
|
||||
sessions: Record<string, WhatsappSession>,
|
||||
sessionName?: string,
|
||||
sessionName: string,
|
||||
): ProxyConfig | undefined {
|
||||
// Single proxy server configuration
|
||||
if (typeof config.proxyServer === 'string') {
|
||||
@@ -23,7 +23,7 @@ export function getProxyConfig(
|
||||
// Multiple proxy server configuration
|
||||
if (Array.isArray(config.proxyServer)) {
|
||||
const prefix = config.proxyServerIndexPrefix;
|
||||
let indexToUse: number = undefined;
|
||||
let indexToUse: number | undefined = undefined;
|
||||
if (prefix && sessionName.includes(prefix)) {
|
||||
// match numbers at the end of the string
|
||||
const matches = sessionName.match(/\d+$/);
|
||||
@@ -66,8 +66,8 @@ function getAuthenticatedUrl(
|
||||
export function createAgentProxy(proxyConfig: ProxyConfig): Agent | undefined {
|
||||
const url = getAuthenticatedUrl(
|
||||
proxyConfig.server,
|
||||
proxyConfig.username,
|
||||
proxyConfig.password,
|
||||
proxyConfig.username || '',
|
||||
proxyConfig.password || '',
|
||||
);
|
||||
return new HttpsProxyAgent(url);
|
||||
}
|
||||
+19
-19
@@ -18,17 +18,14 @@ import {
|
||||
} from '../structures/sessions.dto';
|
||||
import { WebhookConfig } from '../structures/webhooks.config.dto';
|
||||
import { SessionManager } from './abc/manager.abc';
|
||||
import {
|
||||
SessionParams,
|
||||
WAHAInternalEvent,
|
||||
WhatsappSession,
|
||||
} from './abc/session.abc';
|
||||
import { SessionParams, WhatsappSession } from './abc/session.abc';
|
||||
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 { MediaStorageCore, SessionStorageCore } from './storage.core';
|
||||
import { SessionStorageCore } from './storage.core';
|
||||
import { WebhookConductorCore } from './webhooks.core';
|
||||
|
||||
export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException {
|
||||
@@ -45,11 +42,9 @@ export function buildLogger(name) {
|
||||
|
||||
@Injectable()
|
||||
export class SessionManagerCore extends SessionManager {
|
||||
private session: WhatsappSession;
|
||||
private session?: WhatsappSession;
|
||||
DEFAULT = 'default';
|
||||
|
||||
// @ts-ignore
|
||||
protected MediaStorageClass = MediaStorageCore;
|
||||
// @ts-ignore
|
||||
protected WebhookConductorClass = WebhookConductorCore;
|
||||
protected readonly EngineClass: typeof WhatsappSession;
|
||||
@@ -110,13 +105,16 @@ export class SessionManagerCore extends SessionManager {
|
||||
const name = request.name;
|
||||
this.log.log(`'${name}' - starting session...`);
|
||||
const log = buildLogger(`WhatsappSession - ${name}`);
|
||||
const storage = new this.MediaStorageClass();
|
||||
const mediaManager = new CoreMediaManager(
|
||||
new MediaStorageCore(),
|
||||
this.config.mimetypes,
|
||||
);
|
||||
const webhookLog = buildLogger(`Webhook - ${name}`);
|
||||
const webhook = new this.WebhookConductorClass(webhookLog);
|
||||
const proxyConfig = this.getProxyConfig(request);
|
||||
const sessionConfig: SessionParams = {
|
||||
name,
|
||||
storage,
|
||||
mediaManager,
|
||||
log,
|
||||
sessionStorage: this.sessionStorage,
|
||||
proxyConfig: proxyConfig,
|
||||
@@ -149,7 +147,9 @@ export class SessionManagerCore extends SessionManager {
|
||||
webhooks = webhooks.concat(request.config.webhooks);
|
||||
}
|
||||
const globalWebhookConfig = this.config.getWebhookConfig();
|
||||
webhooks.push(globalWebhookConfig);
|
||||
if (globalWebhookConfig) {
|
||||
webhooks.push(globalWebhookConfig);
|
||||
}
|
||||
return webhooks;
|
||||
}
|
||||
|
||||
@@ -162,6 +162,9 @@ export class SessionManagerCore extends SessionManager {
|
||||
if (request.config?.proxy) {
|
||||
return request.config.proxy;
|
||||
}
|
||||
if (!this.session) {
|
||||
return undefined;
|
||||
}
|
||||
const sessions = { [request.name]: this.session };
|
||||
return getProxyConfig(this.config, sessions, request.name);
|
||||
}
|
||||
@@ -181,16 +184,13 @@ export class SessionManagerCore extends SessionManager {
|
||||
await this.sessionStorage.clean(request.name);
|
||||
}
|
||||
|
||||
getSession(name: string, error = true): WhatsappSession {
|
||||
getSession(name: string): WhatsappSession {
|
||||
this.onlyDefault(name);
|
||||
const session = this.session;
|
||||
if (!session) {
|
||||
if (error) {
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
throw new NotFoundException(
|
||||
`We didn't find a session with name '${name}'. Please start it first by using POST /sessions/start request`,
|
||||
);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
IEngineMediaProcessor,
|
||||
MediaManager,
|
||||
MediaStorage,
|
||||
} from './abc/media.abc';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
import { WAMedia } from '../structures/responses.dto';
|
||||
import * as process from 'process';
|
||||
|
||||
export class MediaStorageCore implements MediaStorage {
|
||||
async save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string> {
|
||||
return Promise.resolve(
|
||||
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreMediaManager implements MediaManager {
|
||||
constructor(private storage: MediaStorage, private mimetypes: string[]) {}
|
||||
|
||||
async processMedia<Message>(
|
||||
processor: IEngineMediaProcessor<Message>,
|
||||
message: Message,
|
||||
): Promise<Message> {
|
||||
if (!processor.hasMedia(message)) {
|
||||
return message;
|
||||
}
|
||||
const mimetype = '';
|
||||
const filename = processor.getFilename(message);
|
||||
const url = await this.storage.save('', mimetype, Buffer.from(''));
|
||||
const media: WAMedia = {
|
||||
mimetype: mimetype,
|
||||
filename: filename,
|
||||
url: url,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
message.media = media;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import * as fs from 'fs/promises';
|
||||
import { Agent } from 'https';
|
||||
import * as lodash from 'lodash';
|
||||
import { PairingCodeResponse } from 'src/structures/auth.dto';
|
||||
import { Message } from 'whatsapp-web.js';
|
||||
|
||||
import { flipObject, splitAt } from '../helpers';
|
||||
import {
|
||||
@@ -55,12 +54,14 @@ import {
|
||||
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';
|
||||
import {
|
||||
PollVote,
|
||||
PollVotePayload,
|
||||
WAMessageAckBody,
|
||||
} from '../structures/webhooks.dto';
|
||||
import { IEngineMediaProcessor } from './abc/media.abc';
|
||||
import {
|
||||
ensureSuffix,
|
||||
WAHAInternalEvent,
|
||||
@@ -72,7 +73,7 @@ import {
|
||||
} from './exceptions';
|
||||
import { createAgentProxy } from './helpers.proxy';
|
||||
import { QR } from './QR';
|
||||
import { MeInfo } from '../structures/sessions.dto';
|
||||
import * as Buffer from 'buffer';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -659,11 +660,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
body: body,
|
||||
to: toCusFormat(fromToParticipant.to),
|
||||
participant: toCusFormat(fromToParticipant.participant),
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
filename: message.message?.documentMessage?.fileName,
|
||||
// Media
|
||||
hasMedia: Boolean(message.media),
|
||||
media: message.media,
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
// @ts-ignore
|
||||
@@ -812,14 +812,39 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
|
||||
return { id: chatId, presences: presences };
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
}
|
||||
protected downloadMedia(message) {
|
||||
const processor = new EngineMediaProcessor(this);
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.key.id, '', undefined);
|
||||
return message;
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<any> {
|
||||
constructor(public session: WhatsappSessionNoWebCore) {}
|
||||
|
||||
hasMedia(message: any): boolean {
|
||||
const messageType = Object.keys(message.message)[0];
|
||||
const hasMedia =
|
||||
messageType === 'imageMessage' ||
|
||||
messageType == 'audioMessage' ||
|
||||
messageType == 'documentMessage' ||
|
||||
messageType == 'videoMessage';
|
||||
return hasMedia;
|
||||
}
|
||||
|
||||
getMessageId(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: any): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: any): string | null {
|
||||
return message.message?.documentMessage?.fileName || null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ import {
|
||||
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';
|
||||
import * as Buffer from 'buffer';
|
||||
|
||||
export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
engine = WAHAEngine.VENOM;
|
||||
@@ -246,17 +248,13 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
* END - Methods for API
|
||||
*/
|
||||
|
||||
protected async downloadAndDecryptMedia(message: Message) {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(message.id, '', undefined);
|
||||
return message;
|
||||
protected downloadMedia(message: Message) {
|
||||
const processor = new EngineMediaProcessor(this);
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
|
||||
private processIncomingMessage(message: Message) {
|
||||
return this.downloadAndDecryptMedia(message).then(this.toWAMessage);
|
||||
return this.downloadMedia(message).then(this.toWAMessage);
|
||||
}
|
||||
|
||||
protected toWAMessage(message: Message): Promise<WAMessage> {
|
||||
@@ -268,10 +266,13 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
hasMedia: Boolean(message.media),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
media: message.media,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
ack: message.ack,
|
||||
location: undefined,
|
||||
@@ -280,3 +281,29 @@ export class WhatsappSessionVenomCore extends WhatsappSession {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<Message> {
|
||||
constructor(public session: WhatsappSessionVenomCore) {}
|
||||
|
||||
hasMedia(message: any): boolean {
|
||||
if (!message.isMMS || !message.isMedia) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
getMessageId(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: any): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: any): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: Message): string | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -40,14 +40,15 @@ import {
|
||||
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';
|
||||
import {
|
||||
AvailableInPlusVersion,
|
||||
NotImplementedByEngineError,
|
||||
} from './exceptions';
|
||||
import { QR } from './QR';
|
||||
import { MeInfo } from '../structures/sessions.dto';
|
||||
import { WAMessageRevokedBody } from '../structures/webhooks.dto';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const QRCode = require('qrcode');
|
||||
@@ -541,12 +542,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
fromMe: message.fromMe,
|
||||
to: message.to,
|
||||
body: message.body,
|
||||
// Media
|
||||
// @ts-ignore
|
||||
hasMedia: Boolean(message.mediaUrl),
|
||||
hasMedia: Boolean(message.media),
|
||||
// @ts-ignore
|
||||
mediaUrl: message.mediaUrl,
|
||||
media: message.media,
|
||||
// @ts-ignore
|
||||
mediaUrl: message.media?.url,
|
||||
// @ts-ignore
|
||||
filename: message.rawData?.filename,
|
||||
ack: message.ack,
|
||||
ackName: WAMessageAck[message.ack] || ACK_UNKNOWN,
|
||||
location: message.location,
|
||||
@@ -561,17 +564,35 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
|
||||
return contact;
|
||||
}
|
||||
|
||||
protected async downloadMedia(message: Message) {
|
||||
protected downloadMedia(message: Message) {
|
||||
const processor = new EngineMediaProcessor();
|
||||
return this.mediaManager.processMedia(processor, message);
|
||||
}
|
||||
}
|
||||
|
||||
export class EngineMediaProcessor implements IEngineMediaProcessor<Message> {
|
||||
hasMedia(message: Message): boolean {
|
||||
if (!message.hasMedia) {
|
||||
return message;
|
||||
return false;
|
||||
}
|
||||
// Can't get media for revoked messages
|
||||
return message.type !== 'revoked';
|
||||
}
|
||||
|
||||
getMessageId(message: Message): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMimetype(message: Message): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
getMediaBuffer(message: Message): Promise<Buffer | null> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getFilename(message: Message): string | null {
|
||||
// @ts-ignore
|
||||
message.mediaUrl = await this.storage.save(
|
||||
message.id._serialized,
|
||||
'',
|
||||
undefined,
|
||||
);
|
||||
return message;
|
||||
return message.rawData?.filename || null;
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,8 @@ import * as path from 'path';
|
||||
import { SessionConfig } from '../structures/sessions.dto';
|
||||
import {
|
||||
LocalSessionStorage,
|
||||
MediaStorage,
|
||||
SessionConfigRepository,
|
||||
} from './abc/storage.abc';
|
||||
import { DOCS_URL } from './exceptions';
|
||||
|
||||
export class MediaStorageCore implements MediaStorage {
|
||||
async save(
|
||||
messageId: string,
|
||||
mimetype: string,
|
||||
buffer: Buffer,
|
||||
): Promise<string> {
|
||||
return Promise.resolve(
|
||||
`Media attachment's available only in WAHA Plus version. ${DOCS_URL}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LocalSessionConfigRepository extends SessionConfigRepository {
|
||||
FILENAME = '.waha.session.config.json';
|
||||
@@ -30,13 +16,13 @@ class LocalSessionConfigRepository extends SessionConfigRepository {
|
||||
super();
|
||||
}
|
||||
|
||||
async get(sessionName: string): Promise<SessionConfig> {
|
||||
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 undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to load config
|
||||
@@ -44,13 +30,13 @@ class LocalSessionConfigRepository extends SessionConfigRepository {
|
||||
try {
|
||||
content = await fs.readFile(filepath, 'utf-8');
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async save(sessionName: string, config: SessionConfig) {
|
||||
async save(sessionName: string, config: SessionConfig | null) {
|
||||
// Create folder if not exist
|
||||
const folder = this.storage.getFolderPath(sessionName);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
|
||||
@@ -131,7 +131,7 @@ export class SwaggerModuleCore {
|
||||
return document;
|
||||
}
|
||||
|
||||
protected setUpAuth(app: INestApplication) {
|
||||
return undefined;
|
||||
protected setUpAuth(app: INestApplication): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { WAHAEvents } from './enums.dto';
|
||||
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger';
|
||||
|
||||
function ChatIdProperty(options = undefined) {
|
||||
function ChatIdProperty(options: ApiPropertyOptions | null = null) {
|
||||
options = options || {};
|
||||
if (!options.example) {
|
||||
options.example = '11111111111@c.us';
|
||||
|
||||
@@ -9,6 +9,27 @@ export class WALocation {
|
||||
longitude: string;
|
||||
}
|
||||
|
||||
export class WAMedia {
|
||||
@ApiProperty({
|
||||
description: 'The URL for the media in the message if any',
|
||||
example:
|
||||
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
|
||||
})
|
||||
url: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'mimetype for the media in the message if any',
|
||||
example: 'audio/jpeg',
|
||||
})
|
||||
mimetype: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'The original filename in mediaUrl in the message if any',
|
||||
example: 'example.pdf',
|
||||
})
|
||||
filename: string | null;
|
||||
}
|
||||
|
||||
export class WAMessage {
|
||||
@MessageIdProperty()
|
||||
id: string;
|
||||
@@ -55,19 +76,17 @@ export class WAMessage {
|
||||
})
|
||||
hasMedia: boolean;
|
||||
|
||||
media: WAMedia | null = null;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'The URL for the media in the message if any',
|
||||
description:
|
||||
'Use `media.url` instead! The URL for the media in the message if any',
|
||||
deprecated: true,
|
||||
example:
|
||||
'http://localhost:3000/api/files/false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAA.oga',
|
||||
})
|
||||
mediaUrl: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'The original filename in mediaUrl in the message if any',
|
||||
example: 'example.pdf',
|
||||
})
|
||||
filename: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'ACK status for the message',
|
||||
})
|
||||
|
||||
@@ -65,7 +65,7 @@ export class SessionLogoutRequest {
|
||||
export class SessionDTO {
|
||||
name = 'default';
|
||||
status: WAHASessionStatus;
|
||||
config: SessionConfig;
|
||||
config?: SessionConfig;
|
||||
}
|
||||
|
||||
export class MeInfo {
|
||||
|
||||
Reference in new issue
Block a user