Enhance settings and integrate Baileys controller for WhatsApp functionality

- Added `wavoipToken` field to `Setting` model in both MySQL and PostgreSQL schemas.
- Updated `package.json` and `package-lock.json` to include `mime-types` and `socket.io-client` dependencies.
- Introduced `BaileysController` and `BaileysRouter` for handling WhatsApp interactions.
- Refactored media type handling to use `mime-types` instead of `mime` across various services.
- Updated DTOs and validation schemas to accommodate the new `wavoipToken` field.
- Implemented voice call functionalities using the Wavoip service in the Baileys integration.
- Enhanced event handling in the WebSocket controller to support new features.
This commit is contained in:
Davidson Gomes
2025-01-16 11:58:33 -03:00
parent 616ae0a7eb
commit 540467293c
30 changed files with 748 additions and 36 deletions

View File

@@ -2,14 +2,16 @@ import { Router } from 'express';
import { EvolutionRouter } from './evolution/evolution.router';
import { MetaRouter } from './meta/meta.router';
import { BaileysRouter } from './whatsapp/baileys.router';
export class ChannelRouter {
public readonly router: Router;
constructor(configService: any) {
constructor(configService: any, ...guards: any[]) {
this.router = Router();
this.router.use('/', new EvolutionRouter(configService).router);
this.router.use('/', new MetaRouter(configService).router);
this.router.use('/baileys', new BaileysRouter(...guards).router);
}
}

View File

@@ -10,7 +10,7 @@ import { BadRequestException, InternalServerErrorException } from '@exceptions';
import { status } from '@utils/renderStatus';
import { isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import mime from 'mime';
import mimeTypes from 'mime-types';
import { v4 } from 'uuid';
export class EvolutionStartupService extends ChannelStartupService {
@@ -396,7 +396,7 @@ export class EvolutionStartupService extends ChannelStartupService {
mediaMessage.fileName = 'video.mp4';
}
let mimetype: string;
let mimetype: string | false;
const prepareMedia: any = {
caption: mediaMessage?.caption,
@@ -407,9 +407,9 @@ export class EvolutionStartupService extends ChannelStartupService {
};
if (isURL(mediaMessage.media)) {
mimetype = mime.getType(mediaMessage.media);
mimetype = mimeTypes.lookup(mediaMessage.media);
} else {
mimetype = mime.getType(mediaMessage.fileName);
mimetype = mimeTypes.lookup(mediaMessage.fileName);
}
prepareMedia.mimetype = mimetype;
@@ -449,7 +449,7 @@ export class EvolutionStartupService extends ChannelStartupService {
number = number.replace(/\D/g, '');
const hash = `${number}-${new Date().getTime()}`;
let mimetype: string;
let mimetype: string | false;
const prepareMedia: any = {
fileName: `${hash}.mp4`,
@@ -458,9 +458,9 @@ export class EvolutionStartupService extends ChannelStartupService {
};
if (isURL(audio)) {
mimetype = mime.getType(audio);
mimetype = mimeTypes.lookup(audio);
} else {
mimetype = mime.getType(prepareMedia.fileName);
mimetype = mimeTypes.lookup(prepareMedia.fileName);
}
prepareMedia.mimetype = mimetype;

View File

@@ -28,7 +28,7 @@ import { arrayUnique, isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import FormData from 'form-data';
import { createReadStream } from 'fs';
import mime from 'mime';
import mimeTypes from 'mime-types';
import { join } from 'path';
export class BusinessStartupService extends ChannelStartupService {
@@ -1017,7 +1017,7 @@ export class BusinessStartupService extends ChannelStartupService {
mediaMessage.fileName = 'video.mp4';
}
let mimetype: string;
let mimetype: string | false;
const prepareMedia: any = {
caption: mediaMessage?.caption,
@@ -1028,11 +1028,11 @@ export class BusinessStartupService extends ChannelStartupService {
};
if (isURL(mediaMessage.media)) {
mimetype = mime.getType(mediaMessage.media);
mimetype = mimeTypes.lookup(mediaMessage.media);
prepareMedia.id = mediaMessage.media;
prepareMedia.type = 'link';
} else {
mimetype = mime.getType(mediaMessage.fileName);
mimetype = mimeTypes.lookup(mediaMessage.fileName);
const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id;
prepareMedia.type = 'id';
@@ -1075,7 +1075,7 @@ export class BusinessStartupService extends ChannelStartupService {
number = number.replace(/\D/g, '');
const hash = `${number}-${new Date().getTime()}`;
let mimetype: string;
let mimetype: string | false;
const prepareMedia: any = {
fileName: `${hash}.mp3`,
@@ -1084,11 +1084,11 @@ export class BusinessStartupService extends ChannelStartupService {
};
if (isURL(audio)) {
mimetype = mime.getType(audio);
mimetype = mimeTypes.lookup(audio);
prepareMedia.id = audio;
prepareMedia.type = 'link';
} else {
mimetype = mime.getType(prepareMedia.fileName);
mimetype = mimeTypes.lookup(prepareMedia.fileName);
const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id;
prepareMedia.type = 'id';

View File

@@ -0,0 +1,60 @@
import { InstanceDto } from '@api/dto/instance.dto';
import { WAMonitoringService } from '@api/services/monitor.service';
export class BaileysController {
constructor(private readonly waMonitor: WAMonitoringService) {}
public async onWhatsapp({ instanceName }: InstanceDto, body: any) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysOnWhatsapp(body?.jid);
}
public async profilePictureUrl({ instanceName }: InstanceDto, body: any) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysProfilePictureUrl(body?.jid, body?.type, body?.timeoutMs);
}
public async assertSessions({ instanceName }: InstanceDto, body: any) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysAssertSessions(body?.jids, body?.force);
}
public async createParticipantNodes({ instanceName }: InstanceDto, body: any) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysCreateParticipantNodes(body?.jids, body?.message, body?.extraAttrs);
}
public async getUSyncDevices({ instanceName }: InstanceDto, body: any) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysGetUSyncDevices(body?.jids, body?.useCache, body?.ignoreZeroDevices);
}
public async generateMessageTag({ instanceName }: InstanceDto) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysGenerateMessageTag();
}
public async sendNode({ instanceName }: InstanceDto, body: any) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysSendNode(body?.stanza);
}
public async signalRepositoryDecryptMessage({ instanceName }: InstanceDto, body: any) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysSignalRepositoryDecryptMessage(body?.jid, body?.type, body?.ciphertext);
}
public async getAuthState({ instanceName }: InstanceDto) {
const instance = this.waMonitor.waInstances[instanceName];
return instance.baileysGetAuthState();
}
}

View File

@@ -0,0 +1,105 @@
import { RouterBroker } from '@api/abstract/abstract.router';
import { InstanceDto } from '@api/dto/instance.dto';
import { HttpStatus } from '@api/routes/index.router';
import { baileysController } from '@api/server.module';
import { instanceSchema } from '@validate/instance.schema';
import { RequestHandler, Router } from 'express';
export class BaileysRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('onWhatsapp'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.onWhatsapp(instance, req.body),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('profilePictureUrl'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.profilePictureUrl(instance, req.body),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('assertSessions'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.assertSessions(instance, req.body),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('createParticipantNodes'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.createParticipantNodes(instance, req.body),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('getUSyncDevices'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.getUSyncDevices(instance, req.body),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('generateMessageTag'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.generateMessageTag(instance),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('sendNode'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.sendNode(instance, req.body),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('signalRepositoryDecryptMessage'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.signalRepositoryDecryptMessage(instance, req.body),
});
res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('getAuthState'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => baileysController.getAuthState(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router: Router = Router();
}

View File

@@ -0,0 +1,78 @@
import { BinaryNode, Contact, JidWithDevice, proto, WAConnectionState } from 'baileys';
export interface ServerToClientEvents {
withAck: (d: string, callback: (e: number) => void) => void;
onWhatsApp: onWhatsAppType;
profilePictureUrl: ProfilePictureUrlType;
assertSessions: AssertSessionsType;
createParticipantNodes: CreateParticipantNodesType;
getUSyncDevices: GetUSyncDevicesType;
generateMessageTag: GenerateMessageTagType;
sendNode: SendNodeType;
'signalRepository:decryptMessage': SignalRepositoryDecryptMessageType;
}
export interface ClientToServerEvents {
init: (
me: Contact | undefined,
account: proto.IADVSignedDeviceIdentity | undefined,
status: WAConnectionState,
) => void;
'CB:call': (packet: any) => void;
'CB:ack,class:call': (packet: any) => void;
'connection.update:status': (
me: Contact | undefined,
account: proto.IADVSignedDeviceIdentity | undefined,
status: WAConnectionState,
) => void;
'connection.update:qr': (qr: string) => void;
}
export type onWhatsAppType = (jid: string, callback: onWhatsAppCallback) => void;
export type onWhatsAppCallback = (
response: {
exists: boolean;
jid: string;
}[],
) => void;
export type ProfilePictureUrlType = (
jid: string,
type: 'image' | 'preview',
timeoutMs: number | undefined,
callback: ProfilePictureUrlCallback,
) => void;
export type ProfilePictureUrlCallback = (response: string | undefined) => void;
export type AssertSessionsType = (jids: string[], force: boolean, callback: AssertSessionsCallback) => void;
export type AssertSessionsCallback = (response: boolean) => void;
export type CreateParticipantNodesType = (
jids: string[],
message: any,
extraAttrs: any,
callback: CreateParticipantNodesCallback,
) => void;
export type CreateParticipantNodesCallback = (nodes: any, shouldIncludeDeviceIdentity: boolean) => void;
export type GetUSyncDevicesType = (
jids: string[],
useCache: boolean,
ignoreZeroDevices: boolean,
callback: GetUSyncDevicesTypeCallback,
) => void;
export type GetUSyncDevicesTypeCallback = (jids: JidWithDevice[]) => void;
export type GenerateMessageTagType = (callback: GenerateMessageTagTypeCallback) => void;
export type GenerateMessageTagTypeCallback = (response: string) => void;
export type SendNodeType = (stanza: BinaryNode, callback: SendNodeTypeCallback) => void;
export type SendNodeTypeCallback = (response: boolean) => void;
export type SignalRepositoryDecryptMessageType = (
jid: string,
type: 'pkmsg' | 'msg',
ciphertext: Buffer,
callback: SignalRepositoryDecryptMessageCallback,
) => void;
export type SignalRepositoryDecryptMessageCallback = (response: any) => void;

View File

@@ -0,0 +1,181 @@
import { ConnectionState, WAConnectionState, WASocket } from 'baileys';
import { io, Socket } from 'socket.io-client';
import { ClientToServerEvents, ServerToClientEvents } from './transport.type';
let baileys_connection_state: WAConnectionState = 'close';
export const useVoiceCallsBaileys = async (
wavoip_token: string,
baileys_sock: WASocket,
status?: WAConnectionState,
logger?: boolean,
) => {
baileys_connection_state = status ?? 'close';
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io('https://devices.wavoip.com/baileys', {
transports: ['websocket'],
path: `/${wavoip_token}/websocket`,
});
socket.on('connect', () => {
if (logger) console.log('[*] - Wavoip connected', socket.id);
socket.emit(
'init',
baileys_sock.authState.creds.me,
baileys_sock.authState.creds.account,
baileys_connection_state,
);
});
socket.on('disconnect', () => {
if (logger) console.log('[*] - Wavoip disconnect');
});
socket.on('connect_error', (error) => {
if (socket.active) {
if (logger)
console.log(
'[*] - Wavoip connection error temporary failure, the socket will automatically try to reconnect',
error,
);
} else {
if (logger) console.log('[*] - Wavoip connection error', error.message);
}
});
socket.on('onWhatsApp', async (jid, callback) => {
try {
const response: any = await baileys_sock.onWhatsApp(jid);
callback(response);
if (logger) console.log('[*] Success on call onWhatsApp function', response, jid);
} catch (error) {
if (logger) console.error('[*] Error on call onWhatsApp function', error);
}
});
socket.on('profilePictureUrl', async (jid, type, timeoutMs, callback) => {
try {
const response = await baileys_sock.profilePictureUrl(jid, type, timeoutMs);
callback(response);
if (logger) console.log('[*] Success on call profilePictureUrl function', response);
} catch (error) {
if (logger) console.error('[*] Error on call profilePictureUrl function', error);
}
});
socket.on('assertSessions', async (jids, force, callback) => {
try {
const response = await baileys_sock.assertSessions(jids, force);
callback(response);
if (logger) console.log('[*] Success on call assertSessions function', response);
} catch (error) {
if (logger) console.error('[*] Error on call assertSessions function', error);
}
});
socket.on('createParticipantNodes', async (jids, message, extraAttrs, callback) => {
try {
const response = await baileys_sock.createParticipantNodes(jids, message, extraAttrs);
callback(response, true);
if (logger) console.log('[*] Success on call createParticipantNodes function', response);
} catch (error) {
if (logger) console.error('[*] Error on call createParticipantNodes function', error);
}
});
socket.on('getUSyncDevices', async (jids, useCache, ignoreZeroDevices, callback) => {
try {
const response = await baileys_sock.getUSyncDevices(jids, useCache, ignoreZeroDevices);
callback(response);
if (logger) console.log('[*] Success on call getUSyncDevices function', response);
} catch (error) {
if (logger) console.error('[*] Error on call getUSyncDevices function', error);
}
});
socket.on('generateMessageTag', async (callback) => {
try {
const response = await baileys_sock.generateMessageTag();
callback(response);
if (logger) console.log('[*] Success on call generateMessageTag function', response);
} catch (error) {
if (logger) console.error('[*] Error on call generateMessageTag function', error);
}
});
socket.on('sendNode', async (stanza, callback) => {
try {
console.log('sendNode', JSON.stringify(stanza));
const response = await baileys_sock.sendNode(stanza);
callback(true);
if (logger) console.log('[*] Success on call sendNode function', response);
} catch (error) {
if (logger) console.error('[*] Error on call sendNode function', error);
}
});
socket.on('signalRepository:decryptMessage', async (jid, type, ciphertext, callback) => {
try {
const response = await baileys_sock.signalRepository.decryptMessage({
jid: jid,
type: type,
ciphertext: ciphertext,
});
callback(response);
if (logger) console.log('[*] Success on call signalRepository:decryptMessage function', response);
} catch (error) {
if (logger) console.error('[*] Error on call signalRepository:decryptMessage function', error);
}
});
// we only use this connection data to inform the webphone that the device is connected and creeds account to generate e2e whatsapp key for make call packets
baileys_sock.ev.on('connection.update', (update: Partial<ConnectionState>) => {
const { connection } = update;
if (connection) {
baileys_connection_state = connection;
socket
.timeout(1000)
.emit(
'connection.update:status',
baileys_sock.authState.creds.me,
baileys_sock.authState.creds.account,
connection,
);
}
if (update.qr) {
socket.timeout(1000).emit('connection.update:qr', update.qr);
}
});
baileys_sock.ws.on('CB:call', (packet) => {
if (logger) console.log('[*] Signling received');
socket.volatile.timeout(1000).emit('CB:call', packet);
});
baileys_sock.ws.on('CB:ack,class:call', (packet) => {
if (logger) console.log('[*] Signling ack received');
socket.volatile.timeout(1000).emit('CB:ack,class:call', packet);
});
return socket;
};

View File

@@ -131,7 +131,7 @@ import ffmpeg from 'fluent-ffmpeg';
import FormData from 'form-data';
import { readFileSync } from 'fs';
import Long from 'long';
import mime from 'mime';
import mimeTypes from 'mime-types';
import NodeCache from 'node-cache';
import cron from 'node-cron';
import { release } from 'os';
@@ -143,6 +143,8 @@ import sharp from 'sharp';
import { PassThrough, Readable } from 'stream';
import { v4 } from 'uuid';
import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys';
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
// Adicione a função getVideoDuration no início do arquivo
@@ -673,8 +675,30 @@ export class BaileysStartupService extends ChannelStartupService {
this.client = makeWASocket(socketConfig);
if (this.localSettings.wavoipToken && this.localSettings.wavoipToken.length > 0) {
useVoiceCallsBaileys(this.localSettings.wavoipToken, this.client, this.connectionStatus.state as any, true);
}
this.eventHandler();
this.client.ws.on('CB:call', (packet) => {
console.log('CB:call', packet);
const payload = {
event: 'CB:call',
packet: packet,
};
this.sendDataWebhook(Events.CALL, payload, true, ['websocket']);
});
this.client.ws.on('CB:ack,class:call', (packet) => {
console.log('CB:ack,class:call', packet);
const payload = {
event: 'CB:ack,class:call',
packet: packet,
};
this.sendDataWebhook(Events.CALL, payload, true, ['websocket']);
});
this.phoneNumber = number;
return this.client;
@@ -1248,7 +1272,7 @@ export class BaileysStartupService extends ChannelStartupService {
);
const { buffer, mediaType, fileName, size } = media;
const mimetype = mime.getType(fileName).toString();
const mimetype = mimeTypes.lookup(fileName).toString();
const fullName = join(`${this.instance.id}`, received.key.remoteJid, mediaType, fileName);
await s3Service.uploadFile(fullName, buffer, size.fileLength?.low, {
'Content-Type': mimetype,
@@ -2210,7 +2234,7 @@ export class BaileysStartupService extends ChannelStartupService {
const { buffer, mediaType, fileName, size } = media;
const mimetype = mime.getType(fileName).toString();
const mimetype = mimeTypes.lookup(fileName).toString();
const fullName = join(
`${this.instance.id}`,
@@ -2532,12 +2556,12 @@ export class BaileysStartupService extends ChannelStartupService {
mediaMessage.fileName = 'video.mp4';
}
let mimetype: string;
let mimetype: string | false;
if (mediaMessage.mimetype) {
mimetype = mediaMessage.mimetype;
} else {
mimetype = mime.getType(mediaMessage.fileName);
mimetype = mimeTypes.lookup(mediaMessage.fileName);
if (!mimetype && isURL(mediaMessage.media)) {
let config: any = {
@@ -3590,7 +3614,7 @@ export class BaileysStartupService extends ChannelStartupService {
);
const typeMessage = getContentType(msg.message);
const ext = mime.getExtension(mediaMessage?.['mimetype']);
const ext = mimeTypes.extension(mediaMessage?.['mimetype']);
const fileName = mediaMessage?.['fileName'] || `${msg.key.id}.${ext}` || `${v4()}.${ext}`;
if (convertToMp4 && typeMessage === 'audioMessage') {
@@ -4395,4 +4419,85 @@ export class BaileysStartupService extends ChannelStartupService {
id,
);
}
public async baileysOnWhatsapp(jid: string) {
const response = await this.client.onWhatsApp(jid);
return response;
}
public async baileysProfilePictureUrl(jid: string, type: 'image' | 'preview', timeoutMs: number) {
const response = await this.client.profilePictureUrl(jid, type, timeoutMs);
return response;
}
public async baileysAssertSessions(jids: string[], force: boolean) {
const response = await this.client.assertSessions(jids, force);
return response;
}
public async baileysCreateParticipantNodes(jids: string[], message: proto.IMessage, extraAttrs: any) {
const response = await this.client.createParticipantNodes(jids, message, extraAttrs);
const convertedResponse = {
...response,
nodes: response.nodes.map((node: any) => ({
...node,
content: node.content?.map((c: any) => ({
...c,
content: c.content instanceof Uint8Array ? Buffer.from(c.content).toString('base64') : c.content,
})),
})),
};
return convertedResponse;
}
public async baileysSendNode(stanza: any) {
console.log('stanza', JSON.stringify(stanza));
const response = await this.client.sendNode(stanza);
return response;
}
public async baileysGetUSyncDevices(jids: string[], useCache: boolean, ignoreZeroDevices: boolean) {
const response = await this.client.getUSyncDevices(jids, useCache, ignoreZeroDevices);
return response;
}
public async baileysGenerateMessageTag() {
const response = await this.client.generateMessageTag();
return response;
}
public async baileysSignalRepositoryDecryptMessage(jid: string, type: 'pkmsg' | 'msg', ciphertext: string) {
try {
const ciphertextBuffer = Buffer.from(ciphertext, 'base64');
const response = await this.client.signalRepository.decryptMessage({
jid,
type,
ciphertext: ciphertextBuffer,
});
return response instanceof Uint8Array ? Buffer.from(response).toString('base64') : response;
} catch (error) {
this.logger.error('Error decrypting message:');
this.logger.error(error);
throw error;
}
}
public async baileysGetAuthState() {
const response = {
me: this.client.authState.creds.me,
account: this.client.authState.creds.account,
};
return response;
}
}

View File

@@ -28,7 +28,7 @@ import dayjs from 'dayjs';
import FormData from 'form-data';
import Jimp from 'jimp';
import Long from 'long';
import mime from 'mime';
import mimeTypes from 'mime-types';
import path from 'path';
import { Readable } from 'stream';
@@ -1066,7 +1066,7 @@ export class ChatwootService {
public async sendAttachment(waInstance: any, number: string, media: any, caption?: string, options?: Options) {
try {
const parsedMedia = path.parse(decodeURIComponent(media));
let mimeType = mime.getType(parsedMedia?.ext) || '';
let mimeType = mimeTypes.lookup(parsedMedia?.ext) || '';
let fileName = parsedMedia?.name + parsedMedia?.ext;
if (!mimeType) {
@@ -1958,7 +1958,7 @@ export class ChatwootService {
}
if (!nameFile) {
nameFile = `${Math.random().toString(36).substring(7)}.${mime.getExtension(downloadBase64.mimetype) || ''}`;
nameFile = `${Math.random().toString(36).substring(7)}.${mimeTypes.extension(downloadBase64.mimetype) || ''}`;
}
const fileData = Buffer.from(downloadBase64.base64, 'base64');
@@ -2057,8 +2057,8 @@ export class ChatwootService {
if (isAdsMessage) {
const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' });
const extension = mime.getExtension(imgBuffer.headers['content-type']);
const mimeType = extension && mime.getType(extension);
const extension = mimeTypes.extension(imgBuffer.headers['content-type']);
const mimeType = extension && mimeTypes.lookup(extension);
if (!mimeType) {
this.logger.warn('mimetype of Ads message not found');
@@ -2066,7 +2066,7 @@ export class ChatwootService {
}
const random = Math.random().toString(36).substring(7);
const nameFile = `${random}.${mime.getExtension(mimeType)}`;
const nameFile = `${random}.${mimeTypes.extension(mimeType)}`;
const fileData = Buffer.from(imgBuffer.data, 'binary');
const img = await Jimp.read(fileData);

View File

@@ -13,6 +13,7 @@ export type EmitData = {
sender: string;
apiKey?: string;
local?: boolean;
integration?: string[];
};
export interface EventControllerInterface {
@@ -23,7 +24,7 @@ export interface EventControllerInterface {
export class EventController {
public prismaRepository: PrismaRepository;
private waMonitor: WAMonitoringService;
protected waMonitor: WAMonitoringService;
private integrationStatus: boolean;
private integrationName: string;

View File

@@ -99,6 +99,7 @@ export class EventManager {
sender: string;
apiKey?: string;
local?: boolean;
integration?: string[];
}): Promise<void> {
await this.websocket.emit(eventData);
await this.rabbitmq.emit(eventData);

View File

@@ -120,7 +120,11 @@ export class PusherController extends EventController implements EventController
sender,
apiKey,
local,
integration,
}: EmitData): Promise<void> {
if (integration && !integration.includes('pusher')) {
return;
}
if (!this.status) {
return;
}

View File

@@ -73,7 +73,12 @@ export class RabbitmqController extends EventController implements EventControll
dateTime,
sender,
apiKey,
integration,
}: EmitData): Promise<void> {
if (integration && !integration.includes('rabbitmq')) {
return;
}
if (!this.status) {
return;
}

View File

@@ -54,7 +54,12 @@ export class SqsController extends EventController implements EventControllerInt
dateTime,
sender,
apiKey,
integration,
}: EmitData): Promise<void> {
if (integration && !integration.includes('sqs')) {
return;
}
if (!this.status) {
return;
}

View File

@@ -64,7 +64,12 @@ export class WebhookController extends EventController implements EventControlle
sender,
apiKey,
local,
integration,
}: EmitData): Promise<void> {
if (integration && !integration.includes('webhook')) {
return;
}
const instance = (await this.get(instanceName)) as wa.LocalWebHook;
const webhookConfig = configService.get<Webhook>('WEBHOOK');
@@ -85,7 +90,7 @@ export class WebhookController extends EventController implements EventControlle
apikey: apiKey,
};
if ((local && !instance) || !instance?.enabled) {
if (local && instance?.enabled) {
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
let baseURL: string;

View File

@@ -35,6 +35,16 @@ export class WebsocketController extends EventController implements EventControl
socket.on('disconnect', () => {
this.logger.info('User disconnected');
});
socket.on('sendNode', async (data) => {
try {
await this.waMonitor.waInstances[data.instanceId].baileysSendNode(data.stanza);
this.logger.info('Node sent successfully');
} catch (error) {
this.logger.error('Error sending node:');
this.logger.error(error);
}
});
});
this.logger.info('Socket.io initialized');
@@ -65,7 +75,12 @@ export class WebsocketController extends EventController implements EventControl
dateTime,
sender,
apiKey,
integration,
}: EmitData): Promise<void> {
if (integration && !integration.includes('websocket')) {
return;
}
if (!this.status) {
return;
}