diff --git a/Dockerfile b/Dockerfile index b360e768..e4851b6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine AS builder +FROM node:20-alpine3.20 AS builder RUN apk update && \ apk add git wget curl bash openssl @@ -28,7 +28,7 @@ RUN ./Docker/scripts/generate_database.sh RUN npm run build -FROM node:20-alpine AS final +FROM node:20-alpine3.20 AS final RUN apk update && \ apk add tzdata bash openssl diff --git a/package.json b/package.json index 04ccf1ef..b4847cd2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "2.2.1", + "version": "2.2.40", "description": "Rest api for communication with WhatsApp", "main": "./dist/main.js", "type": "commonjs", @@ -57,7 +57,7 @@ "@sentry/node": "^8.47.0", "amqplib": "^0.10.5", "axios": "^1.7.9", - "baileys": "github:EvolutionAPI/Baileys", + "baileys": "github:WhiskeySockets/Baileys#f8a538eee293c38387c08b2ea97b22d9b61c9d55", "class-validator": "^0.14.1", "compression": "^1.7.5", "cors": "^2.8.5", diff --git a/prisma/postgresql-migrations/20241105170512_web_version/migration.sql b/prisma/postgresql-migrations/20241105170512_web_version/migration.sql new file mode 100644 index 00000000..97fb5cb4 --- /dev/null +++ b/prisma/postgresql-migrations/20241105170512_web_version/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Instance" ADD COLUMN "webVersion" VARCHAR(50); diff --git a/prisma/postgresql-migrations/20250128200807_added_index_remotejid_on_is_on_whatsapp_table/migration.sql b/prisma/postgresql-migrations/20250128200807_added_index_remotejid_on_is_on_whatsapp_table/migration.sql new file mode 100644 index 00000000..ea4ddaa3 --- /dev/null +++ b/prisma/postgresql-migrations/20250128200807_added_index_remotejid_on_is_on_whatsapp_table/migration.sql @@ -0,0 +1,2 @@ +-- CreateIndex +CREATE INDEX "IsOnWhatsapp_remoteJid_idx" ON "IsOnWhatsapp"("remoteJid"); diff --git a/prisma/postgresql-migrations/20250711181706_change_token_to_text/migration.sql b/prisma/postgresql-migrations/20250711181706_change_token_to_text/migration.sql new file mode 100644 index 00000000..16c4f7aa --- /dev/null +++ b/prisma/postgresql-migrations/20250711181706_change_token_to_text/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Instance" ALTER COLUMN "token" SET DATA TYPE TEXT; diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index a9782ce5..2425c625 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -70,8 +70,9 @@ model Instance { integration String? @db.VarChar(100) number String? @db.VarChar(100) businessId String? @db.VarChar(100) - token String? @db.VarChar(255) + token String? @db.Text clientName String? @db.VarChar(100) + webVersion String? @db.VarChar(50) disconnectionReasonCode Int? @db.Integer disconnectionObject Json? @db.JsonB disconnectionAt DateTime? @db.Timestamp @@ -626,4 +627,5 @@ model IsOnWhatsapp { jidOptions String createdAt DateTime @default(now()) @db.Timestamp updatedAt DateTime @updatedAt @db.Timestamp + @@index([remoteJid]) } diff --git a/src/api/abstract/abstract.router.ts b/src/api/abstract/abstract.router.ts index e8449a8c..afa4b24e 100644 --- a/src/api/abstract/abstract.router.ts +++ b/src/api/abstract/abstract.router.ts @@ -41,6 +41,10 @@ export abstract class RouterBroker { Object.assign(instance, body); } + if (request.originalUrl.includes('/webhook/meta')) { + Object.assign(instance, body); + } + Object.assign(ref, body); const v = schema ? validate(ref, schema) : { valid: true, errors: [] }; diff --git a/src/api/controllers/group.controller.ts b/src/api/controllers/group.controller.ts index ebe7c036..64eaa14b 100644 --- a/src/api/controllers/group.controller.ts +++ b/src/api/controllers/group.controller.ts @@ -35,7 +35,7 @@ export class GroupController { } public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) { - return await this.waMonitor.waInstances[instance.instanceName].findGroup(groupJid); + return await this.waMonitor.waInstances[instance.instanceName].getGroupMetadataCache(groupJid.groupJid); } public async fetchAllGroups(instance: InstanceDto, getPaticipants: GetParticipant) { diff --git a/src/api/controllers/instance.controller.ts b/src/api/controllers/instance.controller.ts index f7d5f97d..78c9239b 100644 --- a/src/api/controllers/instance.controller.ts +++ b/src/api/controllers/instance.controller.ts @@ -131,7 +131,8 @@ export class InstanceController { throw new BadRequestException('number is required'); } const urlServer = this.configService.get('SERVER').URL; - webhookWaBusiness = `${urlServer}/webhook/meta`; + const webHookTest = this.configService.get('WA_BUSINESS').WEBHOOK_TEST; + webhookWaBusiness = `${webHookTest ? webHookTest : urlServer}/webhook/meta`; accessTokenWaBusiness = this.configService.get('WA_BUSINESS').TOKEN_WEBHOOK; } @@ -285,6 +286,32 @@ export class InstanceController { return this.waMonitor.instanceInfo(instanceNames); } + public async getInstanceByName({ instanceName }: InstanceDto) { + const instanceByName = await this.prismaRepository.instance.findFirst({ + where: { + name: instanceName, + }, + }); + + if (!instanceByName) { + return null; + } + + const response = { + id: instanceByName.id, + name: instanceByName.name, + ownerJid: instanceByName.ownerJid, + connectionStatus: instanceByName.connectionStatus, + profileName: instanceByName.profileName, + } + return response; + } + + public async countInstances() { + const count = await this.prismaRepository.instance.count(); + return count; + } + public async setPresence({ instanceName }: InstanceDto, data: SetPresenceDto) { return await this.waMonitor.waInstances[instanceName].setPresence(data); } diff --git a/src/api/guards/auth.guard.ts b/src/api/guards/auth.guard.ts index 9ad20b61..c890a9dd 100644 --- a/src/api/guards/auth.guard.ts +++ b/src/api/guards/auth.guard.ts @@ -20,7 +20,7 @@ async function apikey(req: Request, _: Response, next: NextFunction) { return next(); } - if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { + if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances') || req.originalUrl.includes('/instance/countInstances') || req.originalUrl.includes('/instance/instanceByName')) && !key) { throw new ForbiddenException('Missing global api key', 'The global api key must be set'); } const param = req.params as unknown as InstanceDto; @@ -34,7 +34,7 @@ async function apikey(req: Request, _: Response, next: NextFunction) { return next(); } } else { - if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) { + if ((req.originalUrl.includes('/instance/fetchInstances') || req.originalUrl.includes('/instance/countInstances') || req.originalUrl.includes('/instance/instanceByName')) && db.SAVE_DATA.INSTANCE) { const instanceByKey = await prismaRepository.instance.findFirst({ where: { token: key }, }); diff --git a/src/api/guards/instance.guard.ts b/src/api/guards/instance.guard.ts index e692f362..32736cd8 100644 --- a/src/api/guards/instance.guard.ts +++ b/src/api/guards/instance.guard.ts @@ -23,7 +23,7 @@ async function getInstance(instanceName: string) { } export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) { - if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) { + if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances') || req.originalUrl.includes('/instance/countInstances') || req.originalUrl.includes('/instance/instanceByName')) { return next(); } diff --git a/src/api/integrations/channel/evolution/evolution.channel.service.ts b/src/api/integrations/channel/evolution/evolution.channel.service.ts index 7662fa24..a0c436c0 100644 --- a/src/api/integrations/channel/evolution/evolution.channel.service.ts +++ b/src/api/integrations/channel/evolution/evolution.channel.service.ts @@ -179,34 +179,34 @@ export class EvolutionStartupService extends ChannelStartupService { this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw); - const chat = await this.prismaRepository.chat.findFirst({ - where: { instanceId: this.instanceId, remoteJid: data.remoteJid }, - }); - - if (chat) { - const chatRaw: any = { - remoteJid: data.remoteJid, - instanceId: this.instanceId, - }; - - this.sendDataWebhook(Events.CHATS_UPDATE, chatRaw); - - await this.prismaRepository.chat.updateMany({ - where: { remoteJid: chat.remoteJid }, - data: chatRaw, - }); - } - - const chatRaw: any = { - remoteJid: data.remoteJid, - instanceId: this.instanceId, - }; - - this.sendDataWebhook(Events.CHATS_UPSERT, chatRaw); - - await this.prismaRepository.chat.create({ - data: chatRaw, - }); + // const chat = await this.prismaRepository.chat.findFirst({ + // where: { instanceId: this.instanceId, remoteJid: data.remoteJid }, + // }); + + // if (chat) { + // const chatRaw: any = { + // remoteJid: data.remoteJid, + // instanceId: this.instanceId, + // }; + + // this.sendDataWebhook(Events.CHATS_UPDATE, chatRaw); + + // await this.prismaRepository.chat.updateMany({ + // where: { remoteJid: chat.remoteJid }, + // data: chatRaw, + // }); + // } + + // const chatRaw: any = { + // remoteJid: data.remoteJid, + // instanceId: this.instanceId, + // }; + + // this.sendDataWebhook(Events.CHATS_UPSERT, chatRaw); + + // await this.prismaRepository.chat.create({ + // data: chatRaw, + // }); } protected async sendMessageWithTyping(number: string, message: any, options?: Options, file?: any) { diff --git a/src/api/integrations/channel/meta/meta.controller.ts b/src/api/integrations/channel/meta/meta.controller.ts index 558a22e9..be2c9613 100644 --- a/src/api/integrations/channel/meta/meta.controller.ts +++ b/src/api/integrations/channel/meta/meta.controller.ts @@ -15,6 +15,7 @@ export class MetaController extends ChannelController implements ChannelControll integrationEnabled: boolean; public async receiveWebhook(data: any) { + this.logger.info('VALOR DE DATA META: ' + JSON.stringify(data)) if (data.object === 'whatsapp_business_account') { if (data.entry[0]?.changes[0]?.field === 'message_template_status_update') { const template = await this.prismaRepository.template.findFirst({ diff --git a/src/api/integrations/channel/meta/meta.router.ts b/src/api/integrations/channel/meta/meta.router.ts index b0fc43ce..7127d9af 100644 --- a/src/api/integrations/channel/meta/meta.router.ts +++ b/src/api/integrations/channel/meta/meta.router.ts @@ -16,6 +16,17 @@ export class MetaRouter extends RouterBroker { const { body } = req; const response = await metaController.receiveWebhook(body); + return res.status(200).json(response); + }) + .get(this.routerPath('webhook/whatsapp', false), async (req, res) => { + if (req.query['hub.verify_token'] === configService.get('WA_BUSINESS').TOKEN_WEBHOOK) + res.send(req.query['hub.challenge']); + else res.send('Error, wrong validation token'); + }) + .post(this.routerPath('webhook/whatsapp', false), async (req, res) => { + const { body } = req; + const response = await metaController.receiveWebhook(body); + return res.status(200).json(response); }); } diff --git a/src/api/integrations/channel/meta/whatsapp.business.service.ts b/src/api/integrations/channel/meta/whatsapp.business.service.ts index 001ffbf3..14a3f3b0 100644 --- a/src/api/integrations/channel/meta/whatsapp.business.service.ts +++ b/src/api/integrations/channel/meta/whatsapp.business.service.ts @@ -26,7 +26,6 @@ import axios from 'axios'; import { arrayUnique, isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; import FormData from 'form-data'; -import { createReadStream } from 'fs'; import mimeTypes from 'mime-types'; import { join } from 'path'; @@ -68,7 +67,7 @@ export class BusinessStartupService extends ChannelStartupService { } private isMediaMessage(message: any) { - return message.document || message.image || message.audio || message.video; + return message.document || message.image || message.audio || message.video || message.sticker; } private async post(message: any, params: string) { @@ -185,17 +184,57 @@ export class BusinessStartupService extends ChannelStartupService { return content; } + private messageLocationJson(received: any) { + const message = received.messages[0]; + let content: any = { + locationMessage: { + degreesLatitude: message.location.latitude, + degreesLongitude: message.location.longitude, + address: message.location.address, + name: message.location.name, + }, + }; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; + return content; + } + private messageTextJson(received: any) { let content: any; const message = received.messages[0]; + const referral = message.referral; + + const externalAdReply = referral?.source_url + ? { + externalAdReply: { + sourceUrl: referral?.source_url, + title: referral?.headline, + thumbnailUrl: referral?.thumbnail_url + } + } + : undefined; + if (message.from === received.metadata.phone_number_id) { content = { - extendedTextMessage: { text: message.text.body }, + extendedTextMessage: { text: message?.text?.body }, }; - message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; + if (message.context?.id) { + content.contextInfo = { + stanzaId: message?.context?.id, + ...externalAdReply, + }; + } else if (externalAdReply) { + content.contextInfo = externalAdReply; + } } else { - content = { conversation: message.text.body }; - message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; + content = { conversation: message?.text?.body }; + if (message?.context?.id) { + content.contextInfo = { + stanzaId: message?.context?.id, + ...externalAdReply, + }; + } else if (externalAdReply) { + content.contextInfo = externalAdReply; + } } return content; } @@ -241,9 +280,10 @@ export class BusinessStartupService extends ChannelStartupService { vcard: vcard(message.contacts[0]), }; } else { + const contactsArray = Array.isArray(message.contacts) ? message.contacts : []; content.contactsArrayMessage = { displayName: `${message.length} contacts`, - contacts: message.map((contact) => { + contacts: contactsArray.map((contact) => { return { displayName: contact.name.formatted_name, vcard: vcard(contact), @@ -354,19 +394,19 @@ export class BusinessStartupService extends ChannelStartupService { 'Content-Type': mimetype, }); - const createdMessage = await this.prismaRepository.message.create({ - data: messageRaw, - }); + // const createdMessage = await this.prismaRepository.message.create({ + // data: messageRaw, + // }); - await this.prismaRepository.media.create({ - data: { - messageId: createdMessage.id, - instanceId: this.instanceId, - type: mediaType, - fileName: fullName, - mimetype, - }, - }); + // await this.prismaRepository.media.create({ + // data: { + // messageId: createdMessage.id, + // instanceId: this.instanceId, + // type: mediaType, + // fileName: fullName, + // mimetype, + // }, + // }); const mediaUrl = await s3Service.getObjectUrl(fullName); @@ -375,10 +415,6 @@ export class BusinessStartupService extends ChannelStartupService { } catch (error) { this.logger.error(['Error on upload file to minio', error?.message, error?.stack]); } - } else { - const buffer = await this.downloadMediaMessage(received?.messages[0]); - - messageRaw.message.base64 = buffer.toString('base64'); } } else if (received?.messages[0].interactive) { messageRaw = { @@ -432,6 +468,19 @@ export class BusinessStartupService extends ChannelStartupService { source: 'unknown', instanceId: this.instanceId, }; + } else if (received?.messages[0].location) { + messageRaw = { + key, + pushName, + message: { + ...this.messageLocationJson(received), + }, + contextInfo: this.messageLocationJson(received)?.contextInfo, + messageType: 'locationMessage', + messageTimestamp: parseInt(received.messages[0].timestamp) as number, + source: 'unknown', + instanceId: this.instanceId, + }; } else { messageRaw = { key, @@ -441,6 +490,7 @@ export class BusinessStartupService extends ChannelStartupService { messageType: this.renderMessageType(received.messages[0].type), messageTimestamp: parseInt(received.messages[0].timestamp) as number, source: 'unknown', + errors: received.messages[0].errors || [], instanceId: this.instanceId, }; } @@ -449,11 +499,11 @@ export class BusinessStartupService extends ChannelStartupService { this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); - if (!this.isMediaMessage(received?.messages[0])) { - await this.prismaRepository.message.create({ - data: messageRaw, - }); - } + // if (!this.isMediaMessage(received?.messages[0])) { + // await this.prismaRepository.message.create({ + // data: messageRaw, + // }); + // } const contact = await this.prismaRepository.contact.findFirst({ where: { instanceId: this.instanceId, remoteJid: key.remoteJid }, @@ -504,59 +554,45 @@ export class BusinessStartupService extends ChannelStartupService { return; } if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) { - const findMessage = await this.prismaRepository.message.findFirst({ - where: { - instanceId: this.instanceId, - key: { - path: ['id'], - equals: key.id, - }, - }, - }); - if (!findMessage) { - return; - } + if (item.status === 'read' && key.fromMe) return; if (item.message === null && item.status === undefined) { this.sendDataWebhook(Events.MESSAGES_DELETE, key); - const message: any = { - messageId: findMessage.id, - keyId: key.id, - remoteJid: key.remoteJid, - fromMe: key.fromMe, - participant: key?.remoteJid, - status: 'DELETED', - instanceId: this.instanceId, - }; + // const message: any = { + // messageId: item.id, + // keyId: key.id, + // remoteJid: key.remoteJid, + // fromMe: key.fromMe, + // participant: key?.remoteJid, + // status: 'DELETED', + // instanceId: this.instanceId, + // }; - await this.prismaRepository.messageUpdate.create({ - data: message, - }); + // await this.prismaRepository.messageUpdate.create({ + // data: message, + // }); return; } const message: any = { - messageId: findMessage.id, + messageId: item.id, keyId: key.id, remoteJid: key.remoteJid, fromMe: key.fromMe, participant: key?.remoteJid, status: item.status.toUpperCase(), + errors: item.errors, instanceId: this.instanceId, }; this.sendDataWebhook(Events.MESSAGES_UPDATE, message); - await this.prismaRepository.messageUpdate.create({ - data: message, - }); - - if (findMessage.webhookUrl) { - await axios.post(findMessage.webhookUrl, message); - } + // await this.prismaRepository.messageUpdate.create({ + // data: message, + // }); } } } @@ -648,14 +684,22 @@ export class BusinessStartupService extends ChannelStartupService { let webhookUrl: any; const linkPreview = options?.linkPreview != false ? undefined : false; if (options?.quoted) { - const m = options?.quoted; - + let m = options.quoted; + + if (typeof m === 'string') { + try { + m = JSON.parse(m); + } catch (error) { + console.error("Erro ao fazer parse do quoted:", error); + throw 'Invalid quoted format'; + } + } const msg = m?.key; if (!msg) { - throw 'Message not found'; + throw "Message not found"; } - + quoted = msg; } if (options?.webhookUrl) { @@ -721,8 +765,6 @@ export class BusinessStartupService extends ChannelStartupService { return await this.post(content, 'messages'); } if (message['media']) { - const isImage = message['mimetype']?.startsWith('image/'); - content = { messaging_product: 'whatsapp', recipient_type: 'individual', @@ -731,8 +773,8 @@ export class BusinessStartupService extends ChannelStartupService { [message['mediaType']]: { [message['type']]: message['id'], preview_url: linkPreview, - ...(message['fileName'] && !isImage && { filename: message['fileName'] }), caption: message['caption'], + ...(message['mediaType'] === "document" && message['fileName'] && {filename: message.fileName}), }, }; quoted ? (content.context = { message_id: quoted.id }) : content; @@ -848,11 +890,11 @@ export class BusinessStartupService extends ChannelStartupService { this.logger.log(messageRaw); - this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); + //this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); - await this.prismaRepository.message.create({ - data: messageRaw, - }); + // await this.prismaRepository.message.create({ + // data: messageRaw, + // }); return messageRaw; } catch (error) { @@ -882,27 +924,30 @@ export class BusinessStartupService extends ChannelStartupService { private async getIdMedia(mediaMessage: any) { const formData = new FormData(); - - const fileStream = createReadStream(mediaMessage.media); - - formData.append('file', fileStream, { filename: 'media', contentType: mediaMessage.mimetype }); - formData.append('typeFile', mediaMessage.mimetype); + const buffer = mediaMessage.media; formData.append('messaging_product', 'whatsapp'); + formData.append('type', mediaMessage.mimetype); + formData.append('file', buffer, { filename: mediaMessage.fileName, contentType: mediaMessage.mimetype }); - // const fileBuffer = await fs.readFile(mediaMessage.media); - - // const fileBlob = new Blob([fileBuffer], { type: mediaMessage.mimetype }); - // formData.append('file', fileBlob); - // formData.append('typeFile', mediaMessage.mimetype); - // formData.append('messaging_product', 'whatsapp'); + const headers = { "Content-Type": "multipart/form-data", Authorization: `Bearer ${this.token}` }; + let urlServer = this.configService.get('WA_BUSINESS').URL; + const version = this.configService.get('WA_BUSINESS').VERSION; - const headers = { Authorization: `Bearer ${this.token}` }; - const res = await axios.post( - process.env.API_URL + '/' + process.env.VERSION + '/' + this.number + '/media', - formData, - { headers }, - ); - return res.data.id; + let res: any; + try { + res = await axios.post( + urlServer + '/' + version + '/' + this.number + '/media', + formData, + { headers }, + ); + this.logger.log(`Media uploaded successfully: ${res?.data}`); + + return res?.data?.id; + } catch (error) { + if (error.response) { + this.logger.info(JSON.stringify(error.response.data)); + } + } } protected async prepareMediaMessage(mediaMessage: MediaMessage) { @@ -937,11 +982,11 @@ export class BusinessStartupService extends ChannelStartupService { prepareMedia.type = 'link'; } else { mimetype = mimeTypes.lookup(mediaMessage.fileName); + prepareMedia.mimetype = mimetype; const id = await this.getIdMedia(prepareMedia); prepareMedia.id = id; prepareMedia.type = 'id'; } - prepareMedia.mimetype = mimetype; return prepareMedia; @@ -954,7 +999,7 @@ export class BusinessStartupService extends ChannelStartupService { public async mediaMessage(data: SendMediaDto, file?: any) { const mediaData: SendMediaDto = { ...data }; - if (file) mediaData.media = file.buffer.toString('base64'); + if (file) mediaData.media = file.buffer; const message = await this.prepareMediaMessage(mediaData); @@ -981,7 +1026,7 @@ export class BusinessStartupService extends ChannelStartupService { let mimetype: string | false; const prepareMedia: any = { - fileName: `${hash}.mp3`, + fileName: `${hash}.ogg`, mediaType: 'audio', media: audio, }; @@ -992,6 +1037,7 @@ export class BusinessStartupService extends ChannelStartupService { prepareMedia.type = 'link'; } else { mimetype = mimeTypes.lookup(prepareMedia.fileName); + prepareMedia.mimetype = mimetype; const id = await this.getIdMedia(prepareMedia); prepareMedia.id = id; prepareMedia.type = 'id'; @@ -1006,10 +1052,7 @@ export class BusinessStartupService extends ChannelStartupService { const mediaData: SendAudioDto = { ...data }; if (file?.buffer) { - mediaData.audio = file.buffer.toString('base64'); - } else if (isURL(mediaData.audio)) { - // DO NOTHING - // mediaData.audio = mediaData.audio; + mediaData.audio = file.buffer; } else { console.error('El archivo no tiene buffer o file es undefined'); throw new Error('File or buffer is undefined'); diff --git a/src/api/integrations/channel/whatsapp/baileys.controller.ts b/src/api/integrations/channel/whatsapp/baileys.controller.ts index ee547338..7c5dd4ad 100644 --- a/src/api/integrations/channel/whatsapp/baileys.controller.ts +++ b/src/api/integrations/channel/whatsapp/baileys.controller.ts @@ -40,6 +40,12 @@ export class BaileysController { return instance.baileysGenerateMessageTag(); } + public async downloadMediaMessage({ instanceName }: InstanceDto, body: any) { + const instance = this.waMonitor.waInstances[instanceName]; + + return instance.baileysDownloadMediaMessage(body); + } + public async sendNode({ instanceName }: InstanceDto, body: any) { const instance = this.waMonitor.waInstances[instanceName]; diff --git a/src/api/integrations/channel/whatsapp/baileys.router.ts b/src/api/integrations/channel/whatsapp/baileys.router.ts index 04a1d565..72e6a8b6 100644 --- a/src/api/integrations/channel/whatsapp/baileys.router.ts +++ b/src/api/integrations/channel/whatsapp/baileys.router.ts @@ -69,6 +69,16 @@ export class BaileysRouter extends RouterBroker { res.status(HttpStatus.OK).json(response); }) + .post(this.routerPath('downloadMediaMessage'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: instanceSchema, + ClassRef: InstanceDto, + execute: (instance) => baileysController.downloadMediaMessage(instance, req.body), + }); + + res.status(HttpStatus.OK).json(response); + }) .post(this.routerPath('sendNode'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index ddd93603..ded3cb89 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -36,6 +36,7 @@ import { KeyType, MediaMessage, Options, + Quoted, SendAudioDto, SendButtonsDto, SendContactDto, @@ -99,9 +100,9 @@ import makeWASocket, { getContentType, getDevice, GroupMetadata, - isJidBroadcast, isJidGroup, isJidNewsletter, + isJidStatusBroadcast, isJidUser, makeCacheableSignalKeyStore, MessageUpsertType, @@ -139,6 +140,11 @@ import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys'; const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine()); +interface IMessageKeyWithExtras extends proto.IMessageKey { + senderPn?: string | null; + senderLid?: string | null; +} + export class BaileysStartupService extends ChannelStartupService { constructor( public readonly configService: ConfigService, @@ -156,7 +162,10 @@ export class BaileysStartupService extends ChannelStartupService { private authStateProvider: AuthStateProvider; private readonly msgRetryCounterCache: CacheStore = new NodeCache(); - private readonly userDevicesCache: CacheStore = new NodeCache(); + private readonly userDevicesCache: CacheStore = new NodeCache({ + stdTTL: 5 * 60, // 5 minutes, + useClones: false + }); private endSession = false; private logBaileys = this.configService.get('LOG').BAILEYS; @@ -471,9 +480,21 @@ export class BaileysStartupService extends ChannelStartupService { } else { const baileysVersion = await fetchLatestBaileysVersion(); version = baileysVersion.version; - log = `Baileys version: ${version}`; + log = `Latest Baileys version: ${version}`; } + const integrationData = await this.prismaRepository.instance.findUnique({ + where: { id: this.instance.id }, + select: { + webVersion: true, + }, + }); + const webVersion = integrationData.webVersion + ? integrationData.webVersion.split('.').map(Number) + : version + + this.logger.info(`Using WhatsApp webVersion: ${webVersion}`); + this.logger.info(log); this.logger.info(`Group Ignore: ${this.localSettings.groupsIgnore}`); @@ -519,7 +540,7 @@ export class BaileysStartupService extends ChannelStartupService { const socketConfig: UserFacingSocketConfig = { ...options, - version, + version: webVersion, logger: P({ level: this.logBaileys }), printQRInTerminal: false, auth: { @@ -528,7 +549,7 @@ export class BaileysStartupService extends ChannelStartupService { }, msgRetryCounterCache: this.msgRetryCounterCache, generateHighQualityLinkPreview: true, - getMessage: async (key) => (await this.getMessage(key)) as Promise, + // getMessage: async (key) => (await this.getMessage(key)) as Promise, ...browserOptions, markOnlineOnConnect: this.localSettings.alwaysOnline, retryRequestDelayMs: 350, @@ -540,10 +561,10 @@ export class BaileysStartupService extends ChannelStartupService { emitOwnEvents: false, shouldIgnoreJid: (jid) => { const isGroupJid = this.localSettings.groupsIgnore && isJidGroup(jid); - const isBroadcast = !this.localSettings.readStatus && isJidBroadcast(jid); + const isBroadcast = !this.localSettings.readStatus && isJidStatusBroadcast(jid); const isNewsletter = isJidNewsletter(jid); - return isGroupJid || isBroadcast || isNewsletter; + return isGroupJid || isBroadcast || isNewsletter; }, syncFullHistory: this.localSettings.syncFullHistory, cachedGroupMetadata: this.getGroupMetadataCache, @@ -579,7 +600,7 @@ export class BaileysStartupService extends ChannelStartupService { this.eventHandler(); this.client.ws.on('CB:call', (packet) => { - console.log('CB:call', packet); + // console.log('CB:call', packet); const payload = { event: 'CB:call', packet: packet, @@ -588,7 +609,7 @@ export class BaileysStartupService extends ChannelStartupService { }); this.client.ws.on('CB:ack,class:call', (packet) => { - console.log('CB:ack,class:call', packet); + // console.log('CB:ack,class:call', packet); const payload = { event: 'CB:ack,class:call', packet: packet, @@ -694,6 +715,7 @@ export class BaileysStartupService extends ChannelStartupService { private readonly contactHandle = { 'contacts.upsert': async (contacts: Contact[]) => { try { + this.logger.info(`VALOR DE CONTACT EM contacts.upsert: ${contacts}`) const contactsRaw: any = contacts.map((contact) => ({ remoteJid: contact.id, pushName: contact?.name || contact?.verifiedName || contact.id.split('@')[0], @@ -716,35 +738,35 @@ export class BaileysStartupService extends ChannelStartupService { } } - const updatedContacts = await Promise.all( - contacts.map(async (contact) => ({ - remoteJid: contact.id, - pushName: contact?.name || contact?.verifiedName || contact.id.split('@')[0], - profilePicUrl: (await this.profilePicture(contact.id)).profilePictureUrl, - instanceId: this.instanceId, - })), - ); - - if (updatedContacts.length > 0) { - const usersContacts = updatedContacts.filter((c) => c.remoteJid.includes('@s.whatsapp')); - if (usersContacts) { - await saveOnWhatsappCache(usersContacts.map((c) => ({ remoteJid: c.remoteJid }))); - } - - this.sendDataWebhook(Events.CONTACTS_UPDATE, updatedContacts); - await Promise.all( - updatedContacts.map(async (contact) => { - const update = this.prismaRepository.contact.updateMany({ - where: { remoteJid: contact.remoteJid, instanceId: this.instanceId }, - data: { - profilePicUrl: contact.profilePicUrl, - }, - }); - - return update; - }), - ); - } + // const updatedContacts = await Promise.all( + // contacts.map(async (contact) => ({ + // remoteJid: contact.id, + // pushName: contact?.name || contact?.verifiedName || contact.id.split('@')[0], + // profilePicUrl: (await this.profilePicture(contact.id)).profilePictureUrl, + // instanceId: this.instanceId, + // })), + // ); + + // if (updatedContacts.length > 0) { + // const usersContacts = updatedContacts.filter((c) => c.remoteJid.includes('@s.whatsapp')); + // if (usersContacts) { + // await saveOnWhatsappCache(usersContacts.map((c) => ({ remoteJid: c.remoteJid }))); + // } + + // this.sendDataWebhook(Events.CONTACTS_UPDATE, updatedContacts); + // await Promise.all( + // updatedContacts.map(async (contact) => { + // const update = this.prismaRepository.contact.updateMany({ + // where: { remoteJid: contact.remoteJid, instanceId: this.instanceId }, + // data: { + // profilePicUrl: contact.profilePicUrl, + // }, + // }); + + // return update; + // }), + // ); + // } } catch (error) { console.error(error); this.logger.error(`Error: ${error.message}`); @@ -809,35 +831,7 @@ export class BaileysStartupService extends ChannelStartupService { `recv ${chats.length} chats, ${contacts.length} contacts, ${messages.length} msgs (is latest: ${isLatest}, progress: ${progress}%), type: ${syncType}`, ); - const chatsRaw: { remoteJid: string; instanceId: string; name?: string }[] = []; - const chatsRepository = new Set( - ( - await this.prismaRepository.chat.findMany({ - where: { instanceId: this.instanceId }, - }) - ).map((chat) => chat.remoteJid), - ); - - for (const chat of chats) { - if (chatsRepository?.has(chat.id)) { - continue; - } - - chatsRaw.push({ - remoteJid: chat.id, - instanceId: this.instanceId, - name: chat.name, - }); - } - - this.sendDataWebhook(Events.CHATS_SET, chatsRaw); - - if (this.configService.get('DATABASE').SAVE_DATA.HISTORIC) { - await this.prismaRepository.chat.createMany({ - data: chatsRaw, - skipDuplicates: true, - }); - } + this.logger.info(`VALOR DE CONTACTS EM HISTORY: ${JSON.stringify(contacts)}`) const messagesRaw: any[] = []; @@ -853,23 +847,14 @@ export class BaileysStartupService extends ChannelStartupService { messagesRaw.push(this.prepareMessage(m)); } - this.sendDataWebhook(Events.MESSAGES_SET, [...messagesRaw]); - - if (this.configService.get('DATABASE').SAVE_DATA.HISTORIC) { - await this.prismaRepository.message.createMany({ - data: messagesRaw, - skipDuplicates: true, - }); - } + const c = contacts + .filter((c) => !!c.notify || !!c.name) + .map((c) => ({ + id: c.id, + name: c.name ?? c.notify, + })); - await this.contactHandle['contacts.upsert']( - contacts - .filter((c) => !!c.notify || !!c.name) - .map((c) => ({ - id: c.id, - name: c.name ?? c.notify, - })), - ); + this.sendDataWebhook(Events.MESSAGES_SET, undefined, true, undefined, { contacts: c, messages: messagesRaw }); contacts = undefined; messages = undefined; @@ -893,22 +878,25 @@ export class BaileysStartupService extends ChannelStartupService { ) => { try { for (const received of messages) { - if (received.message?.conversation || received.message?.extendedTextMessage?.text) { - const text = received.message?.conversation || received.message?.extendedTextMessage?.text; - if (text == 'requestPlaceholder' && !requestId) { - const messageId = await this.client.requestPlaceholderResend(received.key); + // if (received.message?.conversation || received.message?.extendedTextMessage?.text) { + // const text = received.message?.conversation || received.message?.extendedTextMessage?.text; + // if (text == 'requestPlaceholder' && !requestId) { + // const messageId = await this.client.requestPlaceholderResend(received.key); + // console.log('requested placeholder resync, id=', messageId); + // } else if (requestId) { + // console.log('Message received from phone, id=', requestId, received); + // } - console.log('requested placeholder resync, id=', messageId); - } else if (requestId) { - console.log('Message received from phone, id=', requestId, received); - } + // if (text == 'onDemandHistSync') { + // const messageId = await this.client.fetchMessageHistory(50, received.key, received.messageTimestamp!); + // console.log('requested on-demand sync, id=', messageId); + // } + // } - if (text == 'onDemandHistSync') { - const messageId = await this.client.fetchMessageHistory(50, received.key, received.messageTimestamp!); - console.log('requested on-demand sync, id=', messageId); - } - } + this.logger.info("VALOR DA MENSAGEM: " + JSON.stringify(received)) + + received.key.remoteJid = this.normalizeLidKey(received?.key); if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) { const editedMessage = @@ -918,6 +906,16 @@ export class BaileysStartupService extends ChannelStartupService { } } + let groupInfo; + const { remoteJid } = received.key; + if (isJidGroup(remoteJid)) { + const groupMetaData = await this.getGroupMetadataCache(remoteJid); + groupInfo = { + id: groupMetaData.id, + subject: groupMetaData.subject + } + } + if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') { this.logger.info(`Recovering message lost messageId: ${received.key.id}`); @@ -926,6 +924,40 @@ export class BaileysStartupService extends ChannelStartupService { retry: 0, }); + const messageRaw = { + key: received.key, + pushName: received.pushName, + messageType: 'ciphertext', + message: {}, + messageTimestamp: received.messageTimestamp as number, + owner: this.instance.name, + instanceId: this.instanceId, + source: getDevice(received.key.id), + groupInfo + }; + + this.logger.verbose('Sending data ciphertext to webhook in event MESSAGES_UPSERT'); + this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); + + continue; + } + + if (received.messageStubParameters && received.messageStubParameters[0] === 'No SenderKeyRecord found for decryption') { + const messageRaw = { + key: received.key, + pushName: received.pushName, + messageType: 'NoSenderKeyRecord', + message: {}, + messageTimestamp: received.messageTimestamp as number, + owner: this.instance.name, + instanceId: this.instanceId, + source: getDevice(received.key.id), + groupInfo + }; + + this.logger.verbose('Sending data NoSenderKeyRecord to webhook in event MESSAGES_UPSERT'); + this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); + continue; } @@ -938,11 +970,15 @@ export class BaileysStartupService extends ChannelStartupService { if ( (type !== 'notify' && type !== 'append') || - received.message?.protocolMessage || received.message?.pollUpdateMessage || !received?.message ) { - continue; + return; + } else if(received.message?.protocolMessage){ + if(!received.message?.protocolMessage?.editedMessage){ + this.logger.verbose('message rejected'); + return; + } } if (Long.isLong(received.messageTimestamp)) { @@ -952,40 +988,47 @@ export class BaileysStartupService extends ChannelStartupService { if (settings?.groupsIgnore && received.key.remoteJid.includes('@g.us')) { continue; } - const existingChat = await this.prismaRepository.chat.findFirst({ - where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid }, - select: { id: true, name: true }, - }); - - if ( - existingChat && - received.pushName && - existingChat.name !== received.pushName && - received.pushName.trim().length > 0 + // const existingChat = await this.prismaRepository.chat.findFirst({ + // where: { instanceId: this.instanceId, remoteJid: received.key.remoteJid }, + // select: { id: true, name: true }, + // }); + + // if ( + // existingChat && + // received.pushName && + // existingChat.name !== received.pushName && + // received.pushName.trim().length > 0 + // ) { + // this.sendDataWebhook(Events.CHATS_UPSERT, [{ ...existingChat, name: received.pushName }]); + // if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { + // try { + // await this.prismaRepository.chat.update({ + // where: { id: existingChat.id }, + // data: { name: received.pushName }, + // }); + // } catch (error) { + // console.log(`Chat insert record ignored: ${received.key.remoteJid} - ${this.instanceId}`); + // } + // } + // } + + // Resposta de texto em algum status + if (received.message?.extendedTextMessage?.contextInfo?.remoteJid === 'status@broadcast' && + received.message?.extendedTextMessage?.contextInfo?.quotedMessage ) { - this.sendDataWebhook(Events.CHATS_UPSERT, [{ ...existingChat, name: received.pushName }]); - if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { - try { - await this.prismaRepository.chat.update({ - where: { id: existingChat.id }, - data: { name: received.pushName }, - }); - } catch (error) { - console.log(`Chat insert record ignored: ${received.key.remoteJid} - ${this.instanceId}`); - } + const caption = received.message.extendedTextMessage.text; + received.message = received.message.extendedTextMessage.contextInfo.quotedMessage; + if (received.message.imageMessage) { + received.message.imageMessage.caption = caption; + } + if (received.message.videoMessage) { + received.message.videoMessage.caption = caption; } } const messageRaw = this.prepareMessage(received); - const isMedia = - received?.message?.imageMessage || - received?.message?.videoMessage || - received?.message?.stickerMessage || - received?.message?.documentMessage || - received?.message?.documentWithCaptionMessage || - received?.message?.ptvMessage || - received?.message?.audioMessage; + const isMedia = this.isMedia(received.message); if (this.localSettings.readMessages && received.key.id !== 'status@broadcast') { await this.client.readMessages([received.key]); @@ -1000,64 +1043,64 @@ export class BaileysStartupService extends ChannelStartupService { data: messageRaw, }); - if (received.key.fromMe === false) { - if (msg.status === status[3]) { - this.logger.log(`Update not read messages ${received.key.remoteJid}`); - - await this.updateChatUnreadMessages(received.key.remoteJid); - } else if (msg.status === status[4]) { - this.logger.log(`Update readed messages ${received.key.remoteJid} - ${msg.messageTimestamp}`); - - await this.updateMessagesReadedByTimestamp(received.key.remoteJid, msg.messageTimestamp); - } - } else { - // is send message by me - this.logger.log(`Update readed messages ${received.key.remoteJid} - ${msg.messageTimestamp}`); - - await this.updateMessagesReadedByTimestamp(received.key.remoteJid, msg.messageTimestamp); - } - - if (isMedia) { - if (this.configService.get('S3').ENABLE) { - try { - const message: any = received; - const media = await this.getBase64FromMediaMessage( - { - message, - }, - true, - ); - - const { buffer, mediaType, fileName, size } = media; - 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, - }); - - await this.prismaRepository.media.create({ - data: { - messageId: msg.id, - instanceId: this.instanceId, - type: mediaType, - fileName: fullName, - mimetype, - }, - }); - - const mediaUrl = await s3Service.getObjectUrl(fullName); - - messageRaw.message.mediaUrl = mediaUrl; - - await this.prismaRepository.message.update({ - where: { id: msg.id }, - data: messageRaw, - }); - } catch (error) { - this.logger.error(['Error on upload file to minio', error?.message, error?.stack]); - } - } - } + // if (received.key.fromMe === false) { + // if (msg.status === status[3]) { + // this.logger.log(`Update not read messages ${received.key.remoteJid}`); + + // await this.updateChatUnreadMessages(received.key.remoteJid); + // } else if (msg.status === status[4]) { + // this.logger.log(`Update readed messages ${received.key.remoteJid} - ${msg.messageTimestamp}`); + + // await this.updateMessagesReadedByTimestamp(received.key.remoteJid, msg.messageTimestamp); + // } + // } else { + // // is send message by me + // this.logger.log(`Update readed messages ${received.key.remoteJid} - ${msg.messageTimestamp}`); + + // await this.updateMessagesReadedByTimestamp(received.key.remoteJid, msg.messageTimestamp); + // } + + // if (isMedia) { + // if (this.configService.get('S3').ENABLE) { + // try { + // const message: any = received; + // const media = await this.getBase64FromMediaMessage( + // { + // message, + // }, + // true, + // ); + + // const { buffer, mediaType, fileName, size } = media; + // 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, + // }); + + // await this.prismaRepository.media.create({ + // data: { + // messageId: msg.id, + // instanceId: this.instanceId, + // type: mediaType, + // fileName: fullName, + // mimetype, + // }, + // }); + + // const mediaUrl = await s3Service.getObjectUrl(fullName); + + // messageRaw.message.mediaUrl = mediaUrl; + + // await this.prismaRepository.message.update({ + // where: { id: msg.id }, + // data: messageRaw, + // }); + // } catch (error) { + // this.logger.error(['Error on upload file to minio', error?.message, error?.stack]); + // } + // } + // } } if (this.localWebhook.enabled) { @@ -1080,13 +1123,14 @@ export class BaileysStartupService extends ChannelStartupService { } } + messageRaw.groupInfo = groupInfo; this.logger.log(messageRaw); this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); - const contact = await this.prismaRepository.contact.findFirst({ - where: { remoteJid: received.key.remoteJid, instanceId: this.instanceId }, - }); + // const contact = await this.prismaRepository.contact.findFirst({ + // where: { remoteJid: received.key.remoteJid, instanceId: this.instanceId }, + // }); const contactRaw: { remoteJid: string; pushName: string; profilePicUrl?: string; instanceId: string } = { remoteJid: received.key.remoteJid, @@ -1099,32 +1143,32 @@ export class BaileysStartupService extends ChannelStartupService { continue; } - if (contact) { - this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw); - - if (this.configService.get('DATABASE').SAVE_DATA.CONTACTS) - await this.prismaRepository.contact.upsert({ - where: { remoteJid_instanceId: { remoteJid: contactRaw.remoteJid, instanceId: contactRaw.instanceId } }, - create: contactRaw, - update: contactRaw, - }); - - continue; - } - - this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw); - - if (this.configService.get('DATABASE').SAVE_DATA.CONTACTS) - await this.prismaRepository.contact.upsert({ - where: { - remoteJid_instanceId: { - remoteJid: contactRaw.remoteJid, - instanceId: contactRaw.instanceId, - }, - }, - update: contactRaw, - create: contactRaw, - }); + // if (contact) { + // this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw); + + // if (this.configService.get('DATABASE').SAVE_DATA.CONTACTS) + // await this.prismaRepository.contact.upsert({ + // where: { remoteJid_instanceId: { remoteJid: contactRaw.remoteJid, instanceId: contactRaw.instanceId } }, + // create: contactRaw, + // update: contactRaw, + // }); + + // continue; + // } + + // this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw); + + // if (this.configService.get('DATABASE').SAVE_DATA.CONTACTS) + // await this.prismaRepository.contact.upsert({ + // where: { + // remoteJid_instanceId: { + // remoteJid: contactRaw.remoteJid, + // instanceId: contactRaw.instanceId, + // }, + // }, + // update: contactRaw, + // create: contactRaw, + // }); if (contactRaw.remoteJid.includes('@s.whatsapp')) { await saveOnWhatsappCache([{ remoteJid: contactRaw.remoteJid }]); @@ -1138,7 +1182,7 @@ export class BaileysStartupService extends ChannelStartupService { 'messages.update': async (args: WAMessageUpdate[], settings: any) => { this.logger.log(`Update messages ${JSON.stringify(args, undefined, 2)}`); - const readChatToUpdate: Record = {}; // {remoteJid: true} + //const readChatToUpdate: Record = {}; // {remoteJid: true} for await (const { key, update } of args) { if (settings?.groupsIgnore && key.remoteJid?.includes('@g.us')) { @@ -1146,116 +1190,116 @@ export class BaileysStartupService extends ChannelStartupService { } if (key.remoteJid !== 'status@broadcast') { - let pollUpdates: any; - - if (update.pollUpdates) { - const pollCreation = await this.getMessage(key); - - if (pollCreation) { - pollUpdates = getAggregateVotesInPollMessage({ - message: pollCreation as proto.IMessage, - pollUpdates: update.pollUpdates, - }); - } - } - - const findMessage = await this.prismaRepository.message.findFirst({ - where: { - instanceId: this.instanceId, - key: { - path: ['id'], - equals: key.id, - }, - }, - }); - - if (!findMessage) { - continue; - } + // let pollUpdates: any; + + // if (update.pollUpdates) { + // const pollCreation = await this.getMessage(key); + + // if (pollCreation) { + // pollUpdates = getAggregateVotesInPollMessage({ + // message: pollCreation as proto.IMessage, + // pollUpdates: update.pollUpdates, + // }); + // } + // } + + // const findMessage = await this.prismaRepository.message.findFirst({ + // where: { + // instanceId: this.instanceId, + // key: { + // path: ['id'], + // equals: key.id, + // }, + // }, + // }); + + // if (!findMessage) { + // continue; + // } if (update.message === null && update.status === undefined) { this.sendDataWebhook(Events.MESSAGES_DELETE, key); - const message: any = { - messageId: findMessage.id, - keyId: key.id, - remoteJid: key.remoteJid, - fromMe: key.fromMe, - participant: key?.remoteJid, - status: 'DELETED', - instanceId: this.instanceId, - }; - - if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) - await this.prismaRepository.messageUpdate.create({ - data: message, - }); + // const message: any = { + // messageId: findMessage.id, + // keyId: key.id, + // remoteJid: key.remoteJid, + // fromMe: key.fromMe, + // participant: key?.remoteJid, + // status: 'DELETED', + // instanceId: this.instanceId, + // }; + + // if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) + // await this.prismaRepository.messageUpdate.create({ + // data: message, + // }); continue; - } else if (update.status !== undefined && status[update.status] !== findMessage.status) { - if (!key.fromMe && key.remoteJid) { - readChatToUpdate[key.remoteJid] = true; - - if (status[update.status] === status[4]) { - this.logger.log(`Update as read ${key.remoteJid} - ${findMessage.messageTimestamp}`); - this.updateMessagesReadedByTimestamp(key.remoteJid, findMessage.messageTimestamp); - } - } - - await this.prismaRepository.message.update({ - where: { id: findMessage.id }, - data: { status: status[update.status] }, - }); - } + } //else if (update.status !== undefined && status[update.status] !== findMessage.status) { + // if (!key.fromMe && key.remoteJid) { + // readChatToUpdate[key.remoteJid] = true; + + // if (status[update.status] === status[4]) { + // this.logger.log(`Update as read ${key.remoteJid} - ${findMessage.messageTimestamp}`); + // this.updateMessagesReadedByTimestamp(key.remoteJid, findMessage.messageTimestamp); + // } + // } + + // await this.prismaRepository.message.update({ + // where: { id: findMessage.id }, + // data: { status: status[update.status] }, + // }); + //} const message: any = { - messageId: findMessage.id, + messageId: key.id, keyId: key.id, remoteJid: key.remoteJid, fromMe: key.fromMe, participant: key?.remoteJid, status: status[update.status], - pollUpdates, + // pollUpdates, instanceId: this.instanceId, }; this.sendDataWebhook(Events.MESSAGES_UPDATE, message); - if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) - await this.prismaRepository.messageUpdate.create({ - data: message, - }); - - const existingChat = await this.prismaRepository.chat.findFirst({ - where: { instanceId: this.instanceId, remoteJid: message.remoteJid }, - }); - - if (existingChat) { - const chatToInsert = { - remoteJid: message.remoteJid, - instanceId: this.instanceId, - name: message.pushName || '', - unreadMessages: 0, - }; - - this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]); - if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { - try { - await this.prismaRepository.chat.update({ - where: { - id: existingChat.id, - }, - data: chatToInsert, - }); - } catch (error) { - console.log(`Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`); - } - } - } + // if (this.configService.get('DATABASE').SAVE_DATA.MESSAGE_UPDATE) + // await this.prismaRepository.messageUpdate.create({ + // data: message, + // }); + + // const existingChat = await this.prismaRepository.chat.findFirst({ + // where: { instanceId: this.instanceId, remoteJid: message.remoteJid }, + // }); + + // if (existingChat) { + // const chatToInsert = { + // remoteJid: message.remoteJid, + // instanceId: this.instanceId, + // name: message.pushName || '', + // unreadMessages: 0, + // }; + + // this.sendDataWebhook(Events.CHATS_UPSERT, [chatToInsert]); + // if (this.configService.get('DATABASE').SAVE_DATA.CHATS) { + // try { + // await this.prismaRepository.chat.update({ + // where: { + // id: existingChat.id, + // }, + // data: chatToInsert, + // }); + // } catch (error) { + // console.log(`Chat insert record ignored: ${chatToInsert.remoteJid} - ${chatToInsert.instanceId}`); + // } + // } + // } } } - await Promise.all(Object.keys(readChatToUpdate).map((remoteJid) => this.updateChatUnreadMessages(remoteJid))); + //await Promise.all(Object.keys(readChatToUpdate).map((remoteJid) => this.updateChatUnreadMessages(remoteJid))); }, }; @@ -1404,22 +1448,22 @@ export class BaileysStartupService extends ChannelStartupService { this.messageHandle['messages.update'](payload, settings); } - if (events['message-receipt.update']) { - const payload = events['message-receipt.update'] as MessageUserReceiptUpdate[]; - const remotesJidMap: Record = {}; + // if (events['message-receipt.update']) { + // const payload = events['message-receipt.update'] as MessageUserReceiptUpdate[]; + // const remotesJidMap: Record = {}; - for (const event of payload) { - if (typeof event.key.remoteJid === 'string' && typeof event.receipt.readTimestamp === 'number') { - remotesJidMap[event.key.remoteJid] = event.receipt.readTimestamp; - } - } + // for (const event of payload) { + // if (typeof event.key.remoteJid === 'string' && typeof event.receipt.readTimestamp === 'number') { + // remotesJidMap[event.key.remoteJid] = event.receipt.readTimestamp; + // } + // } - await Promise.all( - Object.keys(remotesJidMap).map(async (remoteJid) => - this.updateMessagesReadedByTimestamp(remoteJid, remotesJidMap[remoteJid]), - ), - ); - } + // await Promise.all( + // Object.keys(remotesJidMap).map(async (remoteJid) => + // this.updateMessagesReadedByTimestamp(remoteJid, remotesJidMap[remoteJid]), + // ), + // ); + // } if (events['presence.update']) { const payload = events['presence.update']; @@ -1448,42 +1492,42 @@ export class BaileysStartupService extends ChannelStartupService { } } - if (events['chats.upsert']) { - const payload = events['chats.upsert']; - this.chatHandle['chats.upsert'](payload); - } - - if (events['chats.update']) { - const payload = events['chats.update']; - this.chatHandle['chats.update'](payload); - } - - if (events['chats.delete']) { - const payload = events['chats.delete']; - this.chatHandle['chats.delete'](payload); - } - - if (events['contacts.upsert']) { - const payload = events['contacts.upsert']; - this.contactHandle['contacts.upsert'](payload); - } - - if (events['contacts.update']) { - const payload = events['contacts.update']; - this.contactHandle['contacts.update'](payload); - } - - if (events[Events.LABELS_ASSOCIATION]) { - const payload = events[Events.LABELS_ASSOCIATION]; - this.labelHandle[Events.LABELS_ASSOCIATION](payload, database); - return; - } - - if (events[Events.LABELS_EDIT]) { - const payload = events[Events.LABELS_EDIT]; - this.labelHandle[Events.LABELS_EDIT](payload); - return; - } + // if (events['chats.upsert']) { + // const payload = events['chats.upsert']; + // this.chatHandle['chats.upsert'](payload); + // } + + // if (events['chats.update']) { + // const payload = events['chats.update']; + // this.chatHandle['chats.update'](payload); + // } + + // if (events['chats.delete']) { + // const payload = events['chats.delete']; + // this.chatHandle['chats.delete'](payload); + // } + + // if (events['contacts.upsert']) { + // const payload = events['contacts.upsert']; + // this.contactHandle['contacts.upsert'](payload); + // } + + // if (events['contacts.update']) { + // const payload = events['contacts.update']; + // this.contactHandle['contacts.update'](payload); + // } + + // if (events[Events.LABELS_ASSOCIATION]) { + // const payload = events[Events.LABELS_ASSOCIATION]; + // this.labelHandle[Events.LABELS_ASSOCIATION](payload, database); + // return; + // } + + // if (events[Events.LABELS_EDIT]) { + // const payload = events[Events.LABELS_EDIT]; + // this.labelHandle[Events.LABELS_EDIT](payload); + // return; + // } } }); } @@ -1582,10 +1626,10 @@ export class BaileysStartupService extends ChannelStartupService { const jid = createJid(number); try { - const call = await this.client.offerCall(jid, isVideo); - setTimeout(() => this.client.terminateCall(call.id, call.to), callDuration * 1000); + //const call = await this.client.offerCall(jid, isVideo); + //setTimeout(() => this.client.terminateCall(call.id, call.to), callDuration * 1000); - return call; + //return call; } catch (error) { return error; } @@ -1772,7 +1816,7 @@ export class BaileysStartupService extends ChannelStartupService { const sender = isWA.jid.toLowerCase(); - this.logger.verbose(`Sending message to ${sender}`); + this.logger.log(`Sending message to ${sender}`); try { if (options?.delay) { @@ -1958,9 +2002,20 @@ export class BaileysStartupService extends ChannelStartupService { } } + let groupInfo; + const { remoteJid } = messageSent.key; + if (isJidGroup(remoteJid)) { + const groupMetaData = await this.getGroupMetadataCache(remoteJid); + groupInfo = { + id: groupMetaData.id, + subject: groupMetaData.subject + } + } + + messageRaw.groupInfo = groupInfo; this.logger.log(messageRaw); - this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); + // this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); return messageRaw; } catch (error) { @@ -2189,7 +2244,7 @@ export class BaileysStartupService extends ChannelStartupService { const prepareMedia = await prepareWAMessageMedia( { - [type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'), + [type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : mediaMessage.media, } as any, { upload: this.client.waUploadToServer }, ); @@ -2335,8 +2390,9 @@ export class BaileysStartupService extends ChannelStartupService { public async mediaMessage(data: SendMediaDto, file?: any) { const mediaData: SendMediaDto = { ...data }; + data.quoted = this.parseQuoted(data.quoted); - if (file) mediaData.media = file.buffer.toString('base64'); + if (file) mediaData.media = file.buffer; const generate = await this.prepareMediaMessage(mediaData); @@ -2387,9 +2443,10 @@ export class BaileysStartupService extends ChannelStartupService { public async audioWhatsapp(data: SendAudioDto, file?: any) { const mediaData: SendAudioDto = { ...data }; + data.quoted = this.parseQuoted(data.quoted); if (file?.buffer) { - mediaData.audio = file.buffer.toString('base64'); + // mediaData.audio: Buffer = file.buffer; } else if (!isURL(data.audio) && !isBase64(data.audio)) { console.error('Invalid file or audio source'); throw new BadRequestException('File buffer, URL, or base64 audio is required'); @@ -2402,11 +2459,11 @@ export class BaileysStartupService extends ChannelStartupService { return await this.sendMessageWithTyping( data.number, { - audio: isURL(data.audio) ? { url: data.audio } : Buffer.from(data.audio, 'base64'), + audio: isURL(data.audio) ? { url: data.audio } : file?.buffer, ptt: true, mimetype: 'audio/ogg; codecs=opus', }, - { presence: 'recording', delay: data?.delay }, + { presence: 'recording', delay: data?.delay, quoted: data?.quoted }, ); } @@ -2981,40 +3038,31 @@ export class BaileysStartupService extends ChannelStartupService { if (response) { const messageId = response.message?.protocolMessage?.key?.id; if (messageId) { - const isLogicalDeleted = configService.get('DATABASE').DELETE_DATA.LOGICAL_MESSAGE_DELETE; - let message = await this.prismaRepository.message.findUnique({ - where: { id: messageId }, - }); - if (isLogicalDeleted) { - if (!message) return response; - const existingKey = typeof message?.key === 'object' && message.key !== null ? message.key : {}; - message = await this.prismaRepository.message.update({ - where: { id: messageId }, - data: { - key: { - ...existingKey, - deleted: true, - }, - }, - }); - } else { - await this.prismaRepository.message.deleteMany({ - where: { - id: messageId, - }, - }); - } + // const isLogicalDeleted = configService.get('DATABASE').DELETE_DATA.LOGICAL_MESSAGE_DELETE; + // let message = await this.prismaRepository.message.findUnique({ + // where: { id: messageId }, + // }); + // if (isLogicalDeleted) { + // if (!message) return response; + // const existingKey = typeof message?.key === 'object' && message.key !== null ? message.key : {}; + // message = await this.prismaRepository.message.update({ + // where: { id: messageId }, + // data: { + // key: { + // ...existingKey, + // deleted: true, + // }, + // }, + // }); + // } else { + // await this.prismaRepository.message.deleteMany({ + // where: { + // id: messageId, + // }, + // }); + // } this.sendDataWebhook(Events.MESSAGES_DELETE, { - id: message.id, - instanceId: message.instanceId, - key: message.key, - messageType: message.messageType, - status: message.status, - source: message.source, - messageTimestamp: message.messageTimestamp, - pushName: message.pushName, - participant: message.participant, - message: message.message, + id: messageId, }); } } @@ -3093,6 +3141,44 @@ export class BaileysStartupService extends ChannelStartupService { } } + public isMedia = (message: proto.IMessage): boolean => { + type MediaTypes = 'audioMessage' | 'imageMessage' | 'videoMessage' | 'documentMessage' | 'stickerMessage'; + const mediaTypes: MediaTypes[] = [ + 'audioMessage', + 'imageMessage', + 'videoMessage', + 'documentMessage', + 'stickerMessage', + ]; + + for (const type of mediaTypes) { + if (message[type]) { + return true; + } + } + + const nestedPaths = [ + message.ephemeralMessage?.message, + message.ephemeralMessage?.message?.viewOnceMessage?.message, + message.ephemeralMessage?.message?.viewOnceMessageV2?.message, + message.viewOnceMessage?.message, + message.viewOnceMessageV2?.message, + message.documentWithCaptionMessage?.message, + ]; + + for (const nested of nestedPaths) { + if (nested) { + for (const type of mediaTypes) { + if (nested[type]) { + return true; + } + } + } + } + + return false; + } + public async fetchPrivacySettings() { const privacy = await this.client.fetchPrivacySettings(); @@ -3285,7 +3371,10 @@ export class BaileysStartupService extends ChannelStartupService { public async updateMessage(data: UpdateMessageDto) { const jid = createJid(data.number); - const options = await this.formatUpdateMessage(data); + // const options = await this.formatUpdateMessage(data); + const options = { + text: data.text, + } if (!options) { this.logger.error('Message not compatible'); @@ -3687,10 +3776,66 @@ export class BaileysStartupService extends ChannelStartupService { throw new Error('Method not available in the Baileys service'); } + private parseQuoted = (quoted: Quoted) => { + if (typeof quoted === 'string') { + try { + return JSON.parse(quoted); + } catch (error) { + console.error('Failed to parse quoted field:', error); + throw new BadRequestException('Invalid quoted format'); + } + } + return quoted; + } + private prepareMessage(message: proto.IWebMessageInfo): any { const contentType = getContentType(message.message); const contentMsg = message?.message[contentType] as any; + // const teste = { + // messageContextInfo: { + // deviceListMetadata: { + // senderKeyHash: "01JLmXAKQXesWw==", + // senderTimestamp: "1745146541", + // recipientKeyHash: "MqF4zcbEFrI3lw==", + // recipientTimestamp: "1745572827" + // }, + // deviceListMetadataVersion: 2 + // }, + // ephemeralMessage: { + // message: { + // documentWithCaptionMessage: { + // message: { + // documentMessage: { + // url: "https://mmg.whatsapp.net/v/t62.7119-24/34722357_2213164112419645_2947918714698327654_n.enc?ccb=11-4&oh=01_Q5Aa1QGXkA2FdYMVuYn-mxcn4X3gyVqtOBWH22kp62HdG_P5Ug&oe=68331230&_nc_sid=5e03e0&mms3=true", + // mimetype: "application/pdf", + // title: "Pedido 124040.pdf", + // fileSha256: "CERdYOygRTw1fkxfJ2v7R3YbdfJadzxrzmTZ0Mal99U=", + // fileLength: "74798", + // pageCount: 1, + // mediaKey: "qpkol332m0Kc8Qe5/VQQ/a56bM8ZrDjbx2G9frFvbBE=", + // fileName: "Pedido 124040.pdf", + // fileEncSha256: "/z7vSGbT3QKAJ/HsjFm24BWiN4lH/75TwjFVMqbMA7k=", + // directPath: "/v/t62.7119-24/34722357_2213164112419645_2947918714698327654_n.enc?ccb=11-4&oh=01_Q5Aa1QGXkA2FdYMVuYn-mxcn4X3gyVqtOBWH22kp62HdG_P5Ug&oe=68331230&_nc_sid=5e03e0", + // mediaKeyTimestamp: "1745593130", + // jpegThumbnail: "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAaABIDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQIBQn/xAAkEAACAQUAAQQDAQAAAAAAAAABAgMABAUREiEGFSMkCCIyM//EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwD1CsEyi3N4L6YSQCb63wBG4I6O2DnoAtyNqh/Q7DbDGPOZu/xmVxFlb2djLBlJJ7YPPdSxyC5WFpYkREhdWVljlLOzpwEGg5bQ6NhKs0DOslzIBNMnVxCYmBWRlIClV2o1pW1plCsCwPRrIB8EboJMXNk5cZaS5m0trXIPBG13BazvcQxTFR2kcrJG0iBtgOUQsACVXegqylApWMx689cHLQxn1nneD+RrYPn3GbXt3Mv09df4eB8X8eB4rZlApSlB/9k=", + // contextInfo: { + // forwardingScore: 1, + // isForwarded: true, + // expiration: 7776000, + // ephemeralSettingTimestamp: "1730124480", + // disappearingMode: { + // initiator: "CHANGED_IN_CHAT" + // } + // }, + // caption: "Pedido 124040.pdf" + // } + // } + // } + // } + // } + // } + const messageRaw = { key: message.key, pushName: message.pushName, @@ -3700,7 +3845,7 @@ export class BaileysStartupService extends ChannelStartupService { messageType: contentType || 'unknown', messageTimestamp: message.messageTimestamp as number, instanceId: this.instanceId, - source: getDevice(message.key.id), + source: getDevice(message.key.id), }; if (!messageRaw.status && message.key.fromMe === false) { @@ -3735,57 +3880,57 @@ export class BaileysStartupService extends ChannelStartupService { return messageRaw; } - private async updateMessagesReadedByTimestamp(remoteJid: string, timestamp?: number): Promise { - if (timestamp === undefined || timestamp === null) return 0; - - const result = await this.prismaRepository.message.updateMany({ - where: { - AND: [ - { key: { path: ['remoteJid'], equals: remoteJid } }, - { key: { path: ['fromMe'], equals: false } }, - { messageTimestamp: { lte: timestamp } }, - { - OR: [{ status: null }, { status: status[3] }], - }, - ], - }, - data: { status: status[4] }, - }); - - if (result) { - if (result.count > 0) { - this.updateChatUnreadMessages(remoteJid); - } - - return result.count; - } - - return 0; - } - - private async updateChatUnreadMessages(remoteJid: string): Promise { - const [chat, unreadMessages] = await Promise.all([ - this.prismaRepository.chat.findFirst({ where: { remoteJid } }), - this.prismaRepository.message.count({ - where: { - AND: [ - { key: { path: ['remoteJid'], equals: remoteJid } }, - { key: { path: ['fromMe'], equals: false } }, - { status: { equals: status[3] } }, - ], - }, - }), - ]); - - if (chat && chat.unreadMessages !== unreadMessages) { - await this.prismaRepository.chat.update({ - where: { id: chat.id }, - data: { unreadMessages }, - }); - } - - return unreadMessages; - } + // private async updateMessagesReadedByTimestamp(remoteJid: string, timestamp?: number): Promise { + // if (timestamp === undefined || timestamp === null) return 0; + + // const result = await this.prismaRepository.message.updateMany({ + // where: { + // AND: [ + // { key: { path: ['remoteJid'], equals: remoteJid } }, + // { key: { path: ['fromMe'], equals: false } }, + // { messageTimestamp: { lte: timestamp } }, + // { + // OR: [{ status: null }, { status: status[3] }], + // }, + // ], + // }, + // data: { status: status[4] }, + // }); + + // if (result) { + // if (result.count > 0) { + // this.updateChatUnreadMessages(remoteJid); + // } + + // return result.count; + // } + + // return 0; + // } + + // private async updateChatUnreadMessages(remoteJid: string): Promise { + // const [chat, unreadMessages] = await Promise.all([ + // this.prismaRepository.chat.findFirst({ where: { remoteJid } }), + // this.prismaRepository.message.count({ + // where: { + // AND: [ + // { key: { path: ['remoteJid'], equals: remoteJid } }, + // { key: { path: ['fromMe'], equals: false } }, + // { status: { equals: status[3] } }, + // ], + // }, + // }), + // ]); + + // if (chat && chat.unreadMessages !== unreadMessages) { + // await this.prismaRepository.chat.update({ + // where: { id: chat.id }, + // data: { unreadMessages }, + // }); + // } + + // return unreadMessages; + // } private async addLabel(labelId: string, instanceId: string, chatId: string) { const id = cuid(); @@ -3889,6 +4034,20 @@ export class BaileysStartupService extends ChannelStartupService { return response; } + public async baileysDownloadMediaMessage(message: proto.IWebMessageInfo) { + const buffer = await downloadMediaMessage( + message, + 'buffer', + {}, + { + logger: P({ level: 'error' }) as any, + reuploadRequest: this.client.updateMediaMessage, + }, + ); + + return buffer.toString('base64');; + } + public async baileysSignalRepositoryDecryptMessage(jid: string, type: 'pkmsg' | 'msg', ciphertext: string) { try { const ciphertextBuffer = Buffer.from(ciphertext, 'base64'); @@ -3915,4 +4074,13 @@ export class BaileysStartupService extends ChannelStartupService { return response; } + + public normalizeLidKey(key: proto.IMessageKey): string | undefined { + const extendedKey = key as IMessageKeyWithExtras; + if (extendedKey.remoteJid?.includes('@lid') && extendedKey.senderPn) { + this.logger.info("NOVO VALOR JID " + extendedKey.senderPn); + return extendedKey.senderPn; + } + return extendedKey.remoteJid; + } } diff --git a/src/api/integrations/event/event.controller.ts b/src/api/integrations/event/event.controller.ts index 2e6a2330..4117d580 100644 --- a/src/api/integrations/event/event.controller.ts +++ b/src/api/integrations/event/event.controller.ts @@ -14,6 +14,7 @@ export type EmitData = { apiKey?: string; local?: boolean; integration?: string[]; + historySetData?: wa.HistorySetData; }; export interface EventControllerInterface { diff --git a/src/api/integrations/event/event.manager.ts b/src/api/integrations/event/event.manager.ts index 9df96f9f..e9076c0f 100644 --- a/src/api/integrations/event/event.manager.ts +++ b/src/api/integrations/event/event.manager.ts @@ -5,6 +5,7 @@ import { WebhookController } from '@api/integrations/event/webhook/webhook.contr import { WebsocketController } from '@api/integrations/event/websocket/websocket.controller'; import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; +import { wa } from '@api/types/wa.types'; import { Server } from 'http'; export class EventManager { @@ -100,6 +101,7 @@ export class EventManager { apiKey?: string; local?: boolean; integration?: string[]; + historySetData?: wa.HistorySetData; }): Promise { await this.websocket.emit(eventData); await this.rabbitmq.emit(eventData); diff --git a/src/api/integrations/event/webhook/webhook.controller.ts b/src/api/integrations/event/webhook/webhook.controller.ts index ce709c3d..fb8ea5ed 100644 --- a/src/api/integrations/event/webhook/webhook.controller.ts +++ b/src/api/integrations/event/webhook/webhook.controller.ts @@ -65,6 +65,7 @@ export class WebhookController extends EventController implements EventControlle apiKey, local, integration, + historySetData }: EmitData): Promise { if (integration && !integration.includes('webhook')) { return; @@ -83,6 +84,7 @@ export class WebhookController extends EventController implements EventControlle event, instance: instanceName, data, + historySetData, destination: instance?.url || `${webhookConfig.GLOBAL.URL}/${transformedWe}`, date_time: dateTime, sender, diff --git a/src/api/routes/index.router.ts b/src/api/routes/index.router.ts index f671f1f6..cf488113 100644 --- a/src/api/routes/index.router.ts +++ b/src/api/routes/index.router.ts @@ -1,6 +1,5 @@ import { authGuard } from '@api/guards/auth.guard'; import { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard'; -import Telemetry from '@api/guards/telemetry.guard'; import { ChannelRouter } from '@api/integrations/channel/channel.router'; import { EventRouter } from '@api/integrations/event/event.router'; import { StorageRouter } from '@api/integrations/storage/storage.router'; @@ -32,13 +31,9 @@ const router: Router = Router(); const serverConfig = configService.get('SERVER'); const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']]; -const telemetry = new Telemetry(); - const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); router - .use((req, res, next) => telemetry.collectTelemetry(req, res, next)) - .get('/', (req, res) => { res.status(HttpStatus.OK).json({ status: HttpStatus.OK, diff --git a/src/api/routes/instance.router.ts b/src/api/routes/instance.router.ts index dd990c3b..bd62fac3 100644 --- a/src/api/routes/instance.router.ts +++ b/src/api/routes/instance.router.ts @@ -67,6 +67,26 @@ export class InstanceRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) + .get(this.routerPath('countInstances', false), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: InstanceDto, + execute: () => instanceController.countInstances(), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('instanceByName', false), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: InstanceDto, + execute: (instance) => instanceController.getInstanceByName(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) .post(this.routerPath('setPresence'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, diff --git a/src/api/services/channel.service.ts b/src/api/services/channel.service.ts index 29bed428..c9c59be2 100644 --- a/src/api/services/channel.service.ts +++ b/src/api/services/channel.service.ts @@ -257,7 +257,7 @@ export class ChannelStartupService { return data; } - public async sendDataWebhook(event: Events, data: T, local = true, integration?: string[]) { + public async sendDataWebhook(event: Events, data: T, local = true, integration?: string[], historySetData?: wa.HistorySetData) { const serverUrl = this.configService.get('SERVER').URL; const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds const localISOTime = new Date(Date.now() - tzoffset).toISOString(); @@ -278,6 +278,7 @@ export class ChannelStartupService { apiKey: expose && instanceApikey ? instanceApikey : null, local, integration, + historySetData }); } diff --git a/src/api/types/wa.types.ts b/src/api/types/wa.types.ts index 0aad0696..df649aad 100644 --- a/src/api/types/wa.types.ts +++ b/src/api/types/wa.types.ts @@ -130,6 +130,16 @@ export declare namespace wa { }; export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED'; + + export type HistorySetData = { + contacts: HistorySetContact[]; + messages: any[]; + } + + export type HistorySetContact = { + id: string; + name: string; + } } export const TypeMediaMessage = [ diff --git a/src/config/env.config.ts b/src/config/env.config.ts index a46fb2aa..8dd3bc0a 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -117,6 +117,7 @@ export type WaBusiness = { URL: string; VERSION: string; LANGUAGE: string; + WEBHOOK_TEST: string; }; export type EventsWebhook = { @@ -443,6 +444,7 @@ export class ConfigService { URL: process.env.WA_BUSINESS_URL || 'https://graph.facebook.com', VERSION: process.env.WA_BUSINESS_VERSION || 'v18.0', LANGUAGE: process.env.WA_BUSINESS_LANGUAGE || 'en', + WEBHOOK_TEST: process.env.WA_BUSINESS_WEBHOOK_TEST || '' }, LOG: { LEVEL: diff --git a/src/utils/createJid.ts b/src/utils/createJid.ts index a680e821..3c83bf16 100644 --- a/src/utils/createJid.ts +++ b/src/utils/createJid.ts @@ -51,7 +51,7 @@ export function createJid(number: string): string { .split(':')[0] .split('@')[0]; - if (number.includes('-') && number.length >= 24) { + if (number.includes('-') && number.length >= 23) { number = number.replace(/[^\d-]/g, ''); return `${number}@g.us`; } diff --git a/src/validate/message.schema.ts b/src/validate/message.schema.ts index d514c619..5ee6f752 100644 --- a/src/validate/message.schema.ts +++ b/src/validate/message.schema.ts @@ -81,7 +81,6 @@ export const textMessageSchema: JSONSchema7 = { mentioned: { type: 'array', minItems: 1, - uniqueItems: true, items: { type: 'string', pattern: '^\\d+',