diff --git a/prisma/mysql-migrations/1707735894523_add_wavoip_token_to_settings_table/migration.sql b/prisma/mysql-migrations/1707735894523_add_wavoip_token_to_settings_table/migration.sql new file mode 100644 index 0000000..62165ee --- /dev/null +++ b/prisma/mysql-migrations/1707735894523_add_wavoip_token_to_settings_table/migration.sql @@ -0,0 +1,9 @@ +/* + Warnings: + + - A unique constraint covering the columns `[remoteJid,instanceId]` on the table `Chat` will be added. If there are existing duplicate values, this will fail. + */ + + -- AlterTable + ALTER TABLE `Setting` + ADD COLUMN IF NOT EXISTS `wavoipToken` VARCHAR(100); \ No newline at end of file diff --git a/prisma/mysql-migrations/20250214181954_add_wavoip_token_column/migration.sql b/prisma/mysql-migrations/20250214181954_add_wavoip_token_column/migration.sql new file mode 100644 index 0000000..799114c --- /dev/null +++ b/prisma/mysql-migrations/20250214181954_add_wavoip_token_column/migration.sql @@ -0,0 +1,175 @@ +/* + Warnings: + + - You are about to alter the column `createdAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Chat` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Chatwoot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Contact` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Dify` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `DifySetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `EvolutionBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `EvolutionBotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Flowise` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `FlowiseSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `disconnectionAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Instance` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `IntegrationSession` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `IsOnWhatsapp` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Label` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Media` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `OpenaiBot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `OpenaiCreds` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `OpenaiSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Proxy` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Pusher` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Rabbitmq` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Session` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Setting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Sqs` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Template` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Typebot` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `TypebotSetting` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Webhook` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `createdAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - You are about to alter the column `updatedAt` on the `Websocket` table. The data in that column could be lost. The data in that column will be cast from `Timestamp(0)` to `Timestamp`. + - A unique constraint covering the columns `[instanceId,remoteJid]` on the table `Chat` will be added. If there are existing duplicate values, this will fail. + + */ + -- AlterTable + ALTER TABLE `Chat` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NULL; + + -- AlterTable + ALTER TABLE `Chatwoot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Contact` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NULL; + + -- AlterTable + ALTER TABLE `Dify` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `DifySetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `EvolutionBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `EvolutionBotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Flowise` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `FlowiseSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Instance` MODIFY `disconnectionAt` TIMESTAMP NULL, + MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NULL; + + -- AlterTable + ALTER TABLE `IntegrationSession` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `IsOnWhatsapp` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Label` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Media` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP; + + -- AlterTable + ALTER TABLE `OpenaiBot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `OpenaiCreds` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `OpenaiSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Proxy` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Pusher` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Rabbitmq` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Session` MODIFY `createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; + + -- AlterTable + ALTER TABLE `Setting` ADD COLUMN `wavoipToken` VARCHAR(100) NULL, + MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Sqs` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Template` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Typebot` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NULL; + + -- AlterTable + ALTER TABLE `TypebotSetting` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Webhook` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- AlterTable + ALTER TABLE `Websocket` MODIFY `createdAt` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP, + MODIFY `updatedAt` TIMESTAMP NOT NULL; + + -- CreateIndex + CREATE UNIQUE INDEX `Chat_instanceId_remoteJid_key` ON `Chat`(`instanceId`, `remoteJid`); \ No newline at end of file diff --git a/prisma/mysql-migrations/migration_lock.toml b/prisma/mysql-migrations/migration_lock.toml index e5a788a..8a21669 100644 --- a/prisma/mysql-migrations/migration_lock.toml +++ b/prisma/mysql-migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# It should be added in your version-control system (e.g., Git) provider = "mysql" \ No newline at end of file diff --git a/prisma/mysql-schema.prisma b/prisma/mysql-schema.prisma index a73ca06..3796a9c 100644 --- a/prisma/mysql-schema.prisma +++ b/prisma/mysql-schema.prisma @@ -99,7 +99,7 @@ model Instance { Template Template[] Dify Dify[] DifySetting DifySetting? - integrationSessions IntegrationSession[] + IntegrationSession IntegrationSession[] EvolutionBot EvolutionBot[] EvolutionBotSetting EvolutionBotSetting? Flowise Flowise[] diff --git a/prisma/postgresql-schema.prisma b/prisma/postgresql-schema.prisma index a9782ce..afd5003 100644 --- a/prisma/postgresql-schema.prisma +++ b/prisma/postgresql-schema.prisma @@ -99,7 +99,7 @@ model Instance { Template Template[] Dify Dify[] DifySetting DifySetting? - integrationSessions IntegrationSession[] + IntegrationSession IntegrationSession[] EvolutionBot EvolutionBot[] EvolutionBotSetting EvolutionBotSetting? Flowise Flowise[] diff --git a/src/api/controllers/business.controller.ts b/src/api/controllers/business.controller.ts new file mode 100644 index 0000000..3c7f166 --- /dev/null +++ b/src/api/controllers/business.controller.ts @@ -0,0 +1,15 @@ +import { getCatalogDto, getCollectionsDto } from '@api/dto/business.dto'; +import { InstanceDto } from '@api/dto/instance.dto'; +import { WAMonitoringService } from '@api/services/monitor.service'; + +export class BusinessController { + constructor(private readonly waMonitor: WAMonitoringService) {} + + public async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) { + return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data); + } + + public async fetchCollections({ instanceName }: InstanceDto, data: getCollectionsDto) { + return await this.waMonitor.waInstances[instanceName].fetchCollections(instanceName, data); + } +} diff --git a/src/api/controllers/chat.controller.ts b/src/api/controllers/chat.controller.ts index 8c922f1..207d8ba 100644 --- a/src/api/controllers/chat.controller.ts +++ b/src/api/controllers/chat.controller.ts @@ -3,8 +3,6 @@ import { BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, - getCatalogDto, - getCollectionsDto, MarkChatUnreadDto, NumberDto, PrivacySettingDto, @@ -111,12 +109,4 @@ export class ChatController { public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) { return await this.waMonitor.waInstances[instanceName].blockUser(data); } - - public async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) { - return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data); - } - - public async fetchCatalogCollections({ instanceName }: InstanceDto, data: getCollectionsDto) { - return await this.waMonitor.waInstances[instanceName].fetchCatalogCollections(instanceName, data); - } } diff --git a/src/api/controllers/sendMessage.controller.ts b/src/api/controllers/sendMessage.controller.ts index ac40562..18339ce 100644 --- a/src/api/controllers/sendMessage.controller.ts +++ b/src/api/controllers/sendMessage.controller.ts @@ -18,6 +18,14 @@ import { WAMonitoringService } from '@api/services/monitor.service'; import { BadRequestException } from '@exceptions'; import { isBase64, isURL } from 'class-validator'; +function isEmoji(str: string) { + if (str === '') return true; + + const emojiRegex = + /^[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F000}-\u{1F02F}\u{1F0A0}-\u{1F0FF}\u{1F100}-\u{1F64F}\u{1F680}-\u{1F6FF}]$/u; + return emojiRegex.test(str); +} + export class SendMessageController { constructor(private readonly waMonitor: WAMonitoringService) {} @@ -81,8 +89,8 @@ export class SendMessageController { } public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) { - if (!data.reaction.match(/[^()\w\sà-ú"-+]+/)) { - throw new BadRequestException('"reaction" must be an emoji'); + if (!isEmoji(data.reaction)) { + throw new BadRequestException('Reaction must be a single emoji or empty string'); } return await this.waMonitor.waInstances[instanceName].reactionMessage(data); } diff --git a/src/api/dto/business.dto.ts b/src/api/dto/business.dto.ts new file mode 100644 index 0000000..d29b3cf --- /dev/null +++ b/src/api/dto/business.dto.ts @@ -0,0 +1,14 @@ +export class NumberDto { + number: string; +} + +export class getCatalogDto { + number?: string; + limit?: number; + cursor?: string; +} + +export class getCollectionsDto { + number?: string; + limit?: number; +} diff --git a/src/api/dto/chat.dto.ts b/src/api/dto/chat.dto.ts index 1693165..00da7fd 100644 --- a/src/api/dto/chat.dto.ts +++ b/src/api/dto/chat.dto.ts @@ -126,14 +126,3 @@ export class BlockUserDto { number: string; status: 'block' | 'unblock'; } - -export class getCatalogDto { - number?: string; - limit?: number; - cursor?: string; -} - -export class getCollectionsDto { - number?: string; - limit?: number; -} diff --git a/src/api/dto/sendMessage.dto.ts b/src/api/dto/sendMessage.dto.ts index 1c9b115..ba9ecf5 100644 --- a/src/api/dto/sendMessage.dto.ts +++ b/src/api/dto/sendMessage.dto.ts @@ -44,6 +44,7 @@ export class Metadata { mentionsEveryOne?: boolean; mentioned?: string[]; encoding?: boolean; + notConvertSticker?: boolean; } export class SendTextDto extends Metadata { diff --git a/src/api/integrations/channel/meta/whatsapp.business.service.ts b/src/api/integrations/channel/meta/whatsapp.business.service.ts index 001ffbf..17e3095 100644 --- a/src/api/integrations/channel/meta/whatsapp.business.service.ts +++ b/src/api/integrations/channel/meta/whatsapp.business.service.ts @@ -200,6 +200,20 @@ 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, + name: message.location?.name, + address: message.location?.address, + }, + }; + message.context ? (content = { ...content, contextInfo: { stanzaId: message.context.id } }) : content; + return content; + } + private messageContactsJson(received: any) { const message = received.messages[0]; let content: any = {}; @@ -277,6 +291,9 @@ export class BusinessStartupService extends ChannelStartupService { case 'template': messageType = 'conversation'; break; + case 'location': + messageType = 'locationMessage'; + break; default: messageType = 'conversation'; break; @@ -432,6 +449,17 @@ 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: this.renderMessageType(received.messages[0].type), + messageTimestamp: parseInt(received.messages[0].timestamp) as number, + source: 'unknown', + instanceId: this.instanceId, + }; } else { messageRaw = { key, @@ -722,6 +750,7 @@ export class BusinessStartupService extends ChannelStartupService { } if (message['media']) { const isImage = message['mimetype']?.startsWith('image/'); + const isVideo = message['mimetype']?.startsWith('video/'); content = { messaging_product: 'whatsapp', @@ -731,7 +760,7 @@ export class BusinessStartupService extends ChannelStartupService { [message['mediaType']]: { [message['type']]: message['id'], preview_url: linkPreview, - ...(message['fileName'] && !isImage && { filename: message['fileName'] }), + ...(message['fileName'] && !isImage && !isVideo && { filename: message['fileName'] }), caption: message['caption'], }, }; @@ -882,8 +911,10 @@ export class BusinessStartupService extends ChannelStartupService { private async getIdMedia(mediaMessage: any) { const formData = new FormData(); + const media = mediaMessage.media || mediaMessage.audio; + if (!media) throw new Error('Media or audio not found'); - const fileStream = createReadStream(mediaMessage.media); + const fileStream = createReadStream(media); formData.append('file', fileStream, { filename: 'media', contentType: mediaMessage.mimetype }); formData.append('typeFile', mediaMessage.mimetype); @@ -983,7 +1014,7 @@ export class BusinessStartupService extends ChannelStartupService { const prepareMedia: any = { fileName: `${hash}.mp3`, mediaType: 'audio', - media: audio, + audio, }; if (isURL(audio)) { @@ -1005,15 +1036,7 @@ export class BusinessStartupService extends ChannelStartupService { public async audioWhatsapp(data: SendAudioDto, file?: any) { const mediaData: SendAudioDto = { ...data }; - if (file?.buffer) { - mediaData.audio = file.buffer.toString('base64'); - } else if (isURL(mediaData.audio)) { - // DO NOTHING - // mediaData.audio = mediaData.audio; - } else { - console.error('El archivo no tiene buffer o file es undefined'); - throw new Error('File or buffer is undefined'); - } + if (file) mediaData.audio = file.buffer.toString('base64'); const message = await this.processAudio(mediaData.audio, data.number); diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 42695d8..376642a 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -1,11 +1,10 @@ +import { getCollectionsDto } from '@api/dto/business.dto'; import { OfferCallDto } from '@api/dto/call.dto'; import { ArchiveChatDto, BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, - getCatalogDto, - getCollectionsDto, LastMessage, MarkChatUnreadDto, NumberBusiness, @@ -921,26 +920,47 @@ export class BaileysStartupService extends ChannelStartupService { if (received.message?.protocolMessage?.editedMessage || received.message?.editedMessage?.message) { if (editedMessage) { await this.sendDataWebhook(Events.MESSAGES_EDITED, editedMessage); + const oldMessage = await this.getMessage(editedMessage.key, true); + if ((oldMessage as any)?.id) { + await this.prismaRepository.message.update({ + where: { id: (oldMessage as any).id }, + data: { + message: editedMessage.editedMessage as any, + messageTimestamp: (editedMessage.timestampMs as Long.Long).toNumber(), + status: 'EDITED', + }, + }); + await this.prismaRepository.messageUpdate.create({ + data: { + fromMe: editedMessage.key.fromMe, + keyId: editedMessage.key.id, + remoteJid: editedMessage.key.remoteJid, + status: 'EDITED', + instanceId: this.instanceId, + messageId: (oldMessage as any).id, + }, + }); + } } } - if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') { - this.logger.info(`Recovering message lost messageId: ${received.key.id}`); + //if (received.messageStubParameters && received.messageStubParameters[0] === 'Message absent from node') { + // this.logger.info(`Recovering message lost messageId: ${received.key.id}`); - await this.baileysCache.set(received.key.id, { - message: received, - retry: 0, - }); + // await this.baileysCache.set(received.key.id, { + // message: received, + // retry: 0, + // }); - continue; - } + // continue; + //} - const retryCache = (await this.baileysCache.get(received.key.id)) || null; + // const retryCache = (await this.baileysCache.get(received.key.id)) || null; - if (retryCache) { - this.logger.info('Recovered message lost'); - await this.baileysCache.delete(received.key.id); - } + // if (retryCache) { + // this.logger.info('Recovered message lost'); + // await this.baileysCache.delete(received.key.id); + // } // Cache to avoid duplicate messages const messageKey = `${this.instance.id}_${received.key.id}`; @@ -2342,7 +2362,9 @@ export class BaileysStartupService extends ChannelStartupService { if (file) mediaData.sticker = file.buffer.toString('base64'); - const convert = await this.convertToWebP(data.sticker); + const convert = data?.notConvertSticker + ? Buffer.from(data.sticker, 'base64') + : await this.convertToWebP(data.sticker); const gifPlayback = data.sticker.includes('.gif'); const result = await this.sendMessageWithTyping( data.number, @@ -3340,6 +3362,16 @@ export class BaileysStartupService extends ChannelStartupService { } try { + const oldMessage: any = await this.getMessage(data.key, true); + if (!oldMessage) throw new NotFoundException('Message not found'); + if (oldMessage?.key?.remoteJid !== jid) { + throw new BadRequestException('RemoteJid does not match'); + } + if (oldMessage?.messageTimestamp > Date.now() + 900000) { + // 15 minutes in milliseconds + throw new BadRequestException('Message is older than 15 minutes'); + } + const response = await this.client.sendMessage(jid, { ...(options as any), edit: data.key, @@ -3364,14 +3396,17 @@ export class BaileysStartupService extends ChannelStartupService { new BadRequestException('You cannot edit deleted messages'); } - const updateMessage = this.prepareMessage({ ...response }); + if (oldMessage.messageType === 'conversation' || oldMessage.messageType === 'extendedTextMessage') { + oldMessage.message.conversation = data.text; + } else { + oldMessage.message[oldMessage.messageType].caption = data.text; + } message = await this.prismaRepository.message.update({ where: { id: message.id }, data: { - message: { - ...updateMessage?.message?.[updateMessage.messageType]?.editedMessage, - }, + message: oldMessage.message, status: 'EDITED', + messageTimestamp: Math.floor(Date.now() / 1000), }, }); const messageUpdate: any = { @@ -4022,11 +4057,11 @@ export class BaileysStartupService extends ChannelStartupService { return response; } - //Catalogs and collections - public async fetchCatalog(instanceName: string, data: getCatalogDto) { + //Business Controller + public async fetchCatalog(instanceName: string, data: getCollectionsDto) { const jid = data.number ? createJid(data.number) : this.client?.user?.id; const limit = data.limit || 10; - const cursor = data.cursor || null; + const cursor = null; const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); @@ -4037,15 +4072,35 @@ export class BaileysStartupService extends ChannelStartupService { try { const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); const business = await this.fetchBusinessProfile(info?.jid); - const catalog = await this.getCatalog({ jid: info?.jid, limit, cursor }); + + let catalog = await this.getCatalog({ jid: info?.jid, limit, cursor }); + let nextPageCursor = catalog.nextPageCursor; + let nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null; + let pagination = nextPageCursorJson?.pagination_cursor + ? JSON.parse(atob(nextPageCursorJson.pagination_cursor)) + : null; + let fetcherHasMore = pagination?.fetcher_has_more === true ? true : false; + + let productsCatalog = catalog.products || []; + let countLoops = 0; + while (fetcherHasMore && countLoops < 4) { + catalog = await this.getCatalog({ jid: info?.jid, limit, cursor: nextPageCursor }); + nextPageCursor = catalog.nextPageCursor; + nextPageCursorJson = nextPageCursor ? JSON.parse(atob(nextPageCursor)) : null; + pagination = nextPageCursorJson?.pagination_cursor + ? JSON.parse(atob(nextPageCursorJson.pagination_cursor)) + : null; + fetcherHasMore = pagination?.fetcher_has_more === true ? true : false; + productsCatalog = [...productsCatalog, ...catalog.products]; + countLoops++; + } return { wuid: info?.jid || jid, - name: info?.name, numberExists: info?.exists, isBusiness: business.isBusiness, - catalogLength: catalog?.products.length, - catalog: catalog?.products, + catalogLength: productsCatalog.length, + catalog: productsCatalog, }; } catch (error) { console.log(error); @@ -4080,9 +4135,9 @@ export class BaileysStartupService extends ChannelStartupService { } } - public async fetchCatalogCollections(instanceName: string, data: getCollectionsDto) { + public async fetchCollections(instanceName: string, data: getCollectionsDto) { const jid = data.number ? createJid(data.number) : this.client?.user?.id; - const limit = data.limit || 10; + const limit = data.limit <= 20 ? data.limit : 20; //(tem esse limite, não sei porque) const onWhatsapp = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); @@ -4093,18 +4148,17 @@ export class BaileysStartupService extends ChannelStartupService { try { const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); const business = await this.fetchBusinessProfile(info?.jid); - const catalogCollections = await this.getCollections(info?.jid, limit); + const collections = await this.getCollections(info?.jid, limit); return { wuid: info?.jid || jid, name: info?.name, numberExists: info?.exists, isBusiness: business.isBusiness, - catalogLength: catalogCollections?.length, - catalogCollections: catalogCollections, + collectionsLength: collections?.length, + collections: collections, }; } catch (error) { - console.log(error); return { wuid: jid, name: null, diff --git a/src/api/integrations/event/sqs/sqs.controller.ts b/src/api/integrations/event/sqs/sqs.controller.ts index 1d60fb7..9cb4ef5 100644 --- a/src/api/integrations/event/sqs/sqs.controller.ts +++ b/src/api/integrations/event/sqs/sqs.controller.ts @@ -1,10 +1,11 @@ import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; -import { SQS } from '@aws-sdk/client-sqs'; +import { CreateQueueCommand, DeleteQueueCommand, ListQueuesCommand, SQS } from '@aws-sdk/client-sqs'; import { configService, Log, Sqs } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { EmitData, EventController, EventControllerInterface } from '../event.controller'; +import { EventDto } from '../event.dto'; export class SqsController extends EventController implements EventControllerInterface { private sqs: SQS; @@ -45,6 +46,39 @@ export class SqsController extends EventController implements EventControllerInt return this.sqs; } + override async set(instanceName: string, data: EventDto): Promise { + if (!this.status) { + return; + } + + if (!data[this.name]?.enabled) { + data[this.name].events = []; + } else { + if (0 === data[this.name].events.length) { + data[this.name].events = EventController.events; + } + } + + await this.saveQueues(instanceName, data[this.name].events, data[this.name]?.enabled); + + const payload: any = { + where: { + instanceId: this.monitor.waInstances[instanceName].instanceId, + }, + update: { + enabled: data[this.name]?.enabled, + events: data[this.name].events, + }, + create: { + enabled: data[this.name]?.enabled, + events: data[this.name].events, + instanceId: this.monitor.waInstances[instanceName].instanceId, + }, + }; + console.log('*** payload: ', payload); + return this.prisma[this.name].upsert(payload); + } + public async emit({ instanceName, origin, @@ -121,70 +155,90 @@ export class SqsController extends EventController implements EventControllerInt } } - public async initQueues(instanceName: string, events: string[]) { - if (!events || !events.length) return; + private async saveQueues(instanceName: string, events: string[], enable: boolean) { + if (enable) { + const eventsFinded = await this.listQueuesByInstance(instanceName); + console.log('eventsFinded', eventsFinded); - const queues = events.map((event) => { - return `${event.replace(/_/g, '_').toLowerCase()}`; - }); + for (const event of events) { + const normalizedEvent = event.toLowerCase(); + if (eventsFinded.includes(normalizedEvent)) { + this.logger.info(`A queue para o evento "${normalizedEvent}" já existe. Ignorando criação.`); + continue; + } - queues.forEach((event) => { - const queueName = `${instanceName}_${event}.fifo`; + const queueName = `${instanceName}_${normalizedEvent}.fifo`; - this.sqs.createQueue( - { - QueueName: queueName, - Attributes: { - FifoQueue: 'true', - }, - }, - (err, data) => { - if (err) { - this.logger.error(`Error creating queue ${queueName}: ${err.message}`); - } else { - this.logger.info(`Queue ${queueName} created: ${data.QueueUrl}`); - } - }, - ); - }); + try { + const createCommand = new CreateQueueCommand({ + QueueName: queueName, + Attributes: { + FifoQueue: 'true', + }, + }); + const data = await this.sqs.send(createCommand); + this.logger.info(`Queue ${queueName} criada: ${data.QueueUrl}`); + } catch (err: any) { + this.logger.error(`Erro ao criar queue ${queueName}: ${err.message}`); + } + } + } } - public async removeQueues(instanceName: string, events: any) { - const eventsArray = Array.isArray(events) ? events.map((event) => String(event)) : []; - if (!events || !eventsArray.length) return; + private async listQueuesByInstance(instanceName: string) { + let existingQueues: string[] = []; + try { + const listCommand = new ListQueuesCommand({ + QueueNamePrefix: `${instanceName}_`, + }); + const listData = await this.sqs.send(listCommand); + if (listData.QueueUrls && listData.QueueUrls.length > 0) { + // Extrai o nome da fila a partir da URL + existingQueues = listData.QueueUrls.map((queueUrl) => { + const parts = queueUrl.split('/'); + return parts[parts.length - 1]; + }); + } + } catch (error: any) { + this.logger.error(`Erro ao listar filas para a instância ${instanceName}: ${error.message}`); + return; + } - const queues = eventsArray.map((event) => { - return `${event.replace(/_/g, '_').toLowerCase()}`; - }); + // Mapeia os eventos já existentes nas filas: remove o prefixo e o sufixo ".fifo" + return existingQueues + .map((queueName) => { + // Espera-se que o nome seja `${instanceName}_${event}.fifo` + if (queueName.startsWith(`${instanceName}_`) && queueName.endsWith('.fifo')) { + return queueName.substring(instanceName.length + 1, queueName.length - 5).toLowerCase(); + } + return ''; + }) + .filter((event) => event !== ''); + } - queues.forEach((event) => { - const queueName = `${instanceName}_${event}.fifo`; + private async removeQueuesByInstance(instanceName: string) { + try { + const listCommand = new ListQueuesCommand({ + QueueNamePrefix: `${instanceName}_`, + }); + const listData = await this.sqs.send(listCommand); - this.sqs.getQueueUrl( - { - QueueName: queueName, - }, - (err, data) => { - if (err) { - this.logger.error(`Error getting queue URL for ${queueName}: ${err.message}`); - } else { - const queueUrl = data.QueueUrl; - - this.sqs.deleteQueue( - { - QueueUrl: queueUrl, - }, - (deleteErr) => { - if (deleteErr) { - this.logger.error(`Error deleting queue ${queueName}: ${deleteErr.message}`); - } else { - this.logger.info(`Queue ${queueName} deleted`); - } - }, - ); - } - }, - ); - }); + if (!listData.QueueUrls || listData.QueueUrls.length === 0) { + this.logger.info(`No queues found for instance ${instanceName}`); + return; + } + + for (const queueUrl of listData.QueueUrls) { + try { + const deleteCommand = new DeleteQueueCommand({ QueueUrl: queueUrl }); + await this.sqs.send(deleteCommand); + this.logger.info(`Queue ${queueUrl} deleted`); + } catch (err: any) { + this.logger.error(`Error deleting queue ${queueUrl}: ${err.message}`); + } + } + } catch (err: any) { + this.logger.error(`Error listing queues for instance ${instanceName}: ${err.message}`); + } } } diff --git a/src/api/integrations/event/webhook/webhook.controller.ts b/src/api/integrations/event/webhook/webhook.controller.ts index ce709c3..7698d2d 100644 --- a/src/api/integrations/event/webhook/webhook.controller.ts +++ b/src/api/integrations/event/webhook/webhook.controller.ts @@ -6,7 +6,6 @@ import { configService, Log, Webhook } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { BadRequestException } from '@exceptions'; import axios, { AxiosInstance } from 'axios'; -import { isURL } from 'class-validator'; import { EmitData, EventController, EventControllerInterface } from '../event.controller'; @@ -18,7 +17,7 @@ export class WebhookController extends EventController implements EventControlle } override async set(instanceName: string, data: EventDto): Promise { - if (!isURL(data.webhook.url, { require_tld: false })) { + if (!/^(https?:\/\/)/.test(data.webhook.url)) { throw new BadRequestException('Invalid "url" property'); } @@ -78,6 +77,7 @@ export class WebhookController extends EventController implements EventControlle const we = event.replace(/[.-]/gm, '_').toUpperCase(); const transformedWe = we.replace(/_/gm, '-').toLowerCase(); const enabledLog = configService.get('LOG').LEVEL.includes('WEBHOOKS'); + const regex = /^(https?:\/\/)/; const webhookData = { event, @@ -111,7 +111,7 @@ export class WebhookController extends EventController implements EventControlle } try { - if (instance?.enabled && isURL(instance.url, { require_tld: false })) { + if (instance?.enabled && regex.test(instance.url)) { const httpService = axios.create({ baseURL, headers: webhookHeaders as Record | undefined, @@ -155,7 +155,7 @@ export class WebhookController extends EventController implements EventControlle } try { - if (isURL(globalURL)) { + if (regex.test(globalURL)) { const httpService = axios.create({ baseURL: globalURL }); await this.retryWebhookRequest( diff --git a/src/api/integrations/event/websocket/websocket.controller.ts b/src/api/integrations/event/websocket/websocket.controller.ts index f6d152f..76366f2 100644 --- a/src/api/integrations/event/websocket/websocket.controller.ts +++ b/src/api/integrations/event/websocket/websocket.controller.ts @@ -1,6 +1,6 @@ import { PrismaRepository } from '@api/repository/repository.service'; import { WAMonitoringService } from '@api/services/monitor.service'; -import { configService, Cors, Log, Websocket } from '@config/env.config'; +import { Auth, configService, Cors, Log, Websocket } from '@config/env.config'; import { Logger } from '@config/logger.config'; import { Server } from 'http'; import { Server as SocketIO } from 'socket.io'; @@ -24,8 +24,40 @@ export class WebsocketController extends EventController implements EventControl } this.socket = new SocketIO(httpServer, { - cors: { - origin: this.cors, + cors: { origin: this.cors }, + allowRequest: async (req, callback) => { + try { + const url = new URL(req.url || '', 'http://localhost'); + const params = new URLSearchParams(url.search); + + // Permite conexões internas do Socket.IO (EIO=4 é o Engine.IO v4) + if (params.has('EIO')) { + return callback(null, true); + } + + const apiKey = params.get('apikey') || (req.headers.apikey as string); + + if (!apiKey) { + this.logger.error('Connection rejected: apiKey not provided'); + return callback('apiKey is required', false); + } + + const instance = await this.prismaRepository.instance.findFirst({ where: { token: apiKey } }); + + if (!instance) { + const globalToken = configService.get('AUTHENTICATION').API_KEY.KEY; + if (apiKey !== globalToken) { + this.logger.error('Connection rejected: invalid global token'); + return callback('Invalid global token', false); + } + } + + callback(null, true); + } catch (error) { + this.logger.error('Authentication error:'); + this.logger.error(error); + callback('Authentication error', false); + } }, }); @@ -101,10 +133,7 @@ export class WebsocketController extends EventController implements EventControl this.socket.emit(event, message); if (logEnabled) { - this.logger.log({ - local: `${origin}.sendData-WebsocketGlobal`, - ...message, - }); + this.logger.log({ local: `${origin}.sendData-WebsocketGlobal`, ...message }); } } @@ -119,10 +148,7 @@ export class WebsocketController extends EventController implements EventControl this.socket.of(`/${instanceName}`).emit(event, message); if (logEnabled) { - this.logger.log({ - local: `${origin}.sendData-Websocket`, - ...message, - }); + this.logger.log({ local: `${origin}.sendData-Websocket`, ...message }); } } } catch (err) { diff --git a/src/api/routes/business.router.ts b/src/api/routes/business.router.ts new file mode 100644 index 0000000..1e510a4 --- /dev/null +++ b/src/api/routes/business.router.ts @@ -0,0 +1,37 @@ +import { RouterBroker } from '@api/abstract/abstract.router'; +import { NumberDto } from '@api/dto/chat.dto'; +import { businessController } from '@api/server.module'; +import { catalogSchema, collectionsSchema } from '@validate/validate.schema'; +import { RequestHandler, Router } from 'express'; + +import { HttpStatus } from './index.router'; + +export class BusinessRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('getCatalog'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: catalogSchema, + ClassRef: NumberDto, + execute: (instance, data) => businessController.fetchCatalog(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + + .post(this.routerPath('getCollections'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: collectionsSchema, + ClassRef: NumberDto, + execute: (instance, data) => businessController.fetchCollections(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router: Router = Router(); +} diff --git a/src/api/routes/chat.router.ts b/src/api/routes/chat.router.ts index aac9fe3..20126c1 100644 --- a/src/api/routes/chat.router.ts +++ b/src/api/routes/chat.router.ts @@ -22,8 +22,6 @@ import { Contact, Message, MessageUpdate } from '@prisma/client'; import { archiveChatSchema, blockUserSchema, - catalogSchema, - collectionsSchema, contactValidateSchema, deleteMessageSchema, markChatUnreadSchema, @@ -269,28 +267,6 @@ export class ChatRouter extends RouterBroker { }); return res.status(HttpStatus.CREATED).json(response); - }) - - .post(this.routerPath('fetchCatalog'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - request: req, - schema: catalogSchema, - ClassRef: NumberDto, - execute: (instance, data) => chatController.fetchCatalog(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - - .post(this.routerPath('fetchCollections'), ...guards, async (req, res) => { - const response = await this.dataValidate({ - request: req, - schema: collectionsSchema, - ClassRef: NumberDto, - execute: (instance, data) => chatController.fetchCatalogCollections(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); }); } diff --git a/src/api/routes/index.router.ts b/src/api/routes/index.router.ts index f671f1f..688f210 100644 --- a/src/api/routes/index.router.ts +++ b/src/api/routes/index.router.ts @@ -8,6 +8,7 @@ import { configService } from '@config/env.config'; import { Router } from 'express'; import fs from 'fs'; +import { BusinessRouter } from './business.router'; import { CallRouter } from './call.router'; import { ChatRouter } from './chat.router'; import { GroupRouter } from './group.router'; @@ -61,6 +62,7 @@ router .use('/instance', new InstanceRouter(configService, ...guards).router) .use('/message', new MessageRouter(...guards).router) .use('/call', new CallRouter(...guards).router) + .use('/business', new BusinessRouter(...guards).router) .use('/chat', new ChatRouter(...guards).router) .use('/group', new GroupRouter(...guards).router) .use('/template', new TemplateRouter(configService, ...guards).router) diff --git a/src/api/routes/instance.router.ts b/src/api/routes/instance.router.ts index dd990c3..3559893 100644 --- a/src/api/routes/instance.router.ts +++ b/src/api/routes/instance.router.ts @@ -15,7 +15,6 @@ export class InstanceRouter extends RouterBroker { super(); this.router .post('/create', ...guards, async (req, res) => { - console.log('create instance', req.body); const response = await this.dataValidate({ request: req, schema: instanceSchema, diff --git a/src/api/server.module.ts b/src/api/server.module.ts index ec32ded..a9cfc9c 100644 --- a/src/api/server.module.ts +++ b/src/api/server.module.ts @@ -3,6 +3,7 @@ import { configService, ProviderSession } from '@config/env.config'; import { eventEmitter } from '@config/event.config'; import { Logger } from '@config/logger.config'; +import { BusinessController } from './controllers/business.controller'; import { CallController } from './controllers/call.controller'; import { ChatController } from './controllers/chat.controller'; import { GroupController } from './controllers/group.controller'; @@ -74,6 +75,7 @@ export const instanceController = new InstanceController( export const sendMessageController = new SendMessageController(waMonitor); export const callController = new CallController(waMonitor); export const chatController = new ChatController(waMonitor); +export const businessController = new BusinessController(waMonitor); export const groupController = new GroupController(waMonitor); export const labelController = new LabelController(waMonitor); diff --git a/src/utils/getConversationMessage.ts b/src/utils/getConversationMessage.ts index b2522ab..723a50d 100644 --- a/src/utils/getConversationMessage.ts +++ b/src/utils/getConversationMessage.ts @@ -3,8 +3,8 @@ import { configService, S3 } from '@config/env.config'; const getTypeMessage = (msg: any) => { let mediaId: string; - if (configService.get('S3').ENABLE) mediaId = msg.message.mediaUrl; - else mediaId = msg.key.id; + if (configService.get('S3').ENABLE) mediaId = msg.message?.mediaUrl; + else mediaId = msg.key?.id; const types = { conversation: msg?.message?.conversation, @@ -15,7 +15,7 @@ const getTypeMessage = (msg: any) => { msg?.message?.viewOnceMessageV2?.message?.imageMessage?.url || msg?.message?.viewOnceMessageV2?.message?.videoMessage?.url || msg?.message?.viewOnceMessageV2?.message?.audioMessage?.url, - listResponseMessage: msg?.message?.listResponseMessage?.title, + listResponseMessage: msg?.message?.listResponseMessage?.title || msg?.listResponseMessage?.title, responseRowId: msg?.message?.listResponseMessage?.singleSelectReply?.selectedRowId, templateButtonReplyMessage: msg?.message?.templateButtonReplyMessage?.selectedId || msg?.message?.buttonsResponseMessage?.selectedButtonId, diff --git a/src/validate/business.schema.ts b/src/validate/business.schema.ts new file mode 100644 index 0000000..91ad17b --- /dev/null +++ b/src/validate/business.schema.ts @@ -0,0 +1,17 @@ +import { JSONSchema7 } from 'json-schema'; + +export const catalogSchema: JSONSchema7 = { + type: 'object', + properties: { + number: { type: 'string' }, + limit: { type: 'number' }, + }, +}; + +export const collectionsSchema: JSONSchema7 = { + type: 'object', + properties: { + number: { type: 'string' }, + limit: { type: 'number' }, + }, +}; diff --git a/src/validate/chat.schema.ts b/src/validate/chat.schema.ts index fd324c1..dba2799 100644 --- a/src/validate/chat.schema.ts +++ b/src/validate/chat.schema.ts @@ -315,21 +315,3 @@ export const profileSchema: JSONSchema7 = { isBusiness: { type: 'boolean' }, }, }; - -export const catalogSchema: JSONSchema7 = { - type: 'object', - properties: { - number: { type: 'string' }, - limit: { type: 'number' }, - cursor: { type: 'string' }, - }, -}; - -export const collectionsSchema: JSONSchema7 = { - type: 'object', - properties: { - number: { type: 'string' }, - limit: { type: 'number' }, - cursor: { type: 'string' }, - }, -}; diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 9486960..c0ab1c6 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -1,4 +1,5 @@ // Integrations Schema +export * from './business.schema'; export * from './chat.schema'; export * from './group.schema'; export * from './instance.schema';