From f7a731a19393c5877ed0f2811a7888a464f53405 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Sat, 13 Jul 2024 13:55:42 -0300 Subject: [PATCH] chore: Add ignoreJid feature to typebot and improve comparison Adds a new feature to the typebot integration that allows ignoring specific remote JIDs. Also improves the comparison functionality. The main changes are in the typebot controller, DTO, route, and service. Additionally, the WhatsApp Business Service and event configuration files have been updated. The main files modified are: - typebot.controller.ts - typebot.dto.ts - typebot.router.ts - typebot.service.ts - typebot.schema.ts - whatsapp.business.service.ts - event.config.ts --- .../typebot/controllers/typebot.controller.ts | 8 +- .../integrations/typebot/dto/typebot.dto.ts | 5 + .../typebot/routes/typebot.router.ts | 13 +- .../typebot/services/typebot.service.ts | 115 ++++++++++++++++-- .../typebot/validate/typebot.schema.ts | 11 ++ .../channels/whatsapp.business.service.ts | 15 ++- src/config/event.config.ts | 1 + 7 files changed, 154 insertions(+), 14 deletions(-) diff --git a/src/api/integrations/typebot/controllers/typebot.controller.ts b/src/api/integrations/typebot/controllers/typebot.controller.ts index 707545ac..a73771c1 100644 --- a/src/api/integrations/typebot/controllers/typebot.controller.ts +++ b/src/api/integrations/typebot/controllers/typebot.controller.ts @@ -1,7 +1,7 @@ import { configService, Typebot } from '../../../../config/env.config'; import { BadRequestException } from '../../../../exceptions'; import { InstanceDto } from '../../../dto/instance.dto'; -import { TypebotDto } from '../dto/typebot.dto'; +import { TypebotDto, TypebotIgnoreJidDto } from '../dto/typebot.dto'; import { TypebotService } from '../services/typebot.service'; export class TypebotController { @@ -66,4 +66,10 @@ export class TypebotController { return this.typebotService.fetchSessions(instance, typebotId); } + + public async ignoreJid(instance: InstanceDto, data: TypebotIgnoreJidDto) { + if (!configService.get('TYPEBOT').ENABLED) throw new BadRequestException('Typebot is disabled'); + + return this.typebotService.ignoreJid(instance, data); + } } diff --git a/src/api/integrations/typebot/dto/typebot.dto.ts b/src/api/integrations/typebot/dto/typebot.dto.ts index c37a1e24..1682e90b 100644 --- a/src/api/integrations/typebot/dto/typebot.dto.ts +++ b/src/api/integrations/typebot/dto/typebot.dto.ts @@ -46,3 +46,8 @@ export class TypebotSettingDto { typebotIdFallback?: string; ignoreJids?: any; } + +export class TypebotIgnoreJidDto { + remoteJid?: string; + action?: string; +} diff --git a/src/api/integrations/typebot/routes/typebot.router.ts b/src/api/integrations/typebot/routes/typebot.router.ts index 9d9042f6..842d76dd 100644 --- a/src/api/integrations/typebot/routes/typebot.router.ts +++ b/src/api/integrations/typebot/routes/typebot.router.ts @@ -2,6 +2,7 @@ import { RequestHandler, Router } from 'express'; import { instanceSchema, + typebotIgnoreJidSchema, typebotSchema, typebotSettingSchema, typebotStartSchema, @@ -11,7 +12,7 @@ import { RouterBroker } from '../../../abstract/abstract.router'; import { InstanceDto } from '../../../dto/instance.dto'; import { HttpStatus } from '../../../routes/index.router'; import { typebotController } from '../../../server.module'; -import { TypebotDto, TypebotSettingDto } from '../dto/typebot.dto'; +import { TypebotDto, TypebotIgnoreJidDto, TypebotSettingDto } from '../dto/typebot.dto'; export class TypebotRouter extends RouterBroker { constructor(...guards: RequestHandler[]) { @@ -115,6 +116,16 @@ export class TypebotRouter extends RouterBroker { execute: (instance) => typebotController.fetchSessions(instance, req.params.typebotId), }); + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('ignoreJid'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: typebotIgnoreJidSchema, + ClassRef: TypebotIgnoreJidDto, + execute: (instance, data) => typebotController.ignoreJid(instance, data), + }); + res.status(HttpStatus.OK).json(response); }); } diff --git a/src/api/integrations/typebot/services/typebot.service.ts b/src/api/integrations/typebot/services/typebot.service.ts index caf48d8a..955264a5 100644 --- a/src/api/integrations/typebot/services/typebot.service.ts +++ b/src/api/integrations/typebot/services/typebot.service.ts @@ -7,7 +7,7 @@ import { InstanceDto } from '../../../dto/instance.dto'; import { PrismaRepository } from '../../../repository/repository.service'; import { WAMonitoringService } from '../../../services/monitor.service'; import { Events } from '../../../types/wa.types'; -import { TypebotDto } from '../dto/typebot.dto'; +import { TypebotDto, TypebotIgnoreJidDto } from '../dto/typebot.dto'; export class TypebotService { constructor( @@ -472,6 +472,54 @@ export class TypebotService { } } + public async ignoreJid(instance: InstanceDto, data: TypebotIgnoreJidDto) { + try { + const instanceId = await this.prismaRepository.instance + .findFirst({ + where: { + name: instance.instanceName, + }, + }) + .then((instance) => instance.id); + + const settings = await this.prismaRepository.typebotSetting.findFirst({ + where: { + instanceId: instanceId, + }, + }); + + if (!settings) { + throw new Error('Settings not found'); + } + + let ignoreJids: any = settings?.ignoreJids || []; + + if (data.action === 'add') { + if (ignoreJids.includes(data.remoteJid)) return { ignoreJids: ignoreJids }; + + ignoreJids.push(data.remoteJid); + } else { + ignoreJids = ignoreJids.filter((jid) => jid !== data.remoteJid); + } + + const updateSettings = await this.prismaRepository.typebotSetting.update({ + where: { + id: settings.id, + }, + data: { + ignoreJids: ignoreJids, + }, + }); + + return { + ignoreJids: updateSettings.ignoreJids, + }; + } catch (error) { + this.logger.error(error); + throw new Error('Error setting default settings'); + } + } + public async fetchSessions(instance: InstanceDto, typebotId?: string, remoteJid?: string) { try { const instanceId = await this.prismaRepository.instance @@ -581,6 +629,42 @@ export class TypebotService { let stopBotFromMe = data?.typebot?.stopBotFromMe; let keepOpen = data?.typebot?.keepOpen; + const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({ + where: { + instanceId: instance.instanceId, + }, + }); + + if (defaultSettingCheck?.ignoreJids) { + const ignoreJids: any = defaultSettingCheck.ignoreJids; + + let ignoreGroups = false; + let ignoreContacts = false; + + if (ignoreJids.includes('@g.us')) { + ignoreGroups = true; + } + + if (ignoreJids.includes('@s.whatsapp.net')) { + ignoreContacts = true; + } + + if (ignoreGroups && remoteJid.includes('@g.us')) { + this.logger.warn('Ignoring message from group: ' + remoteJid); + return; + } + + if (ignoreContacts && remoteJid.includes('@s.whatsapp.net')) { + this.logger.warn('Ignoring message from contact: ' + remoteJid); + return; + } + + if (ignoreJids.includes(remoteJid)) { + this.logger.warn('Ignoring message from jid: ' + remoteJid); + return; + } + } + const findTypebot = await this.prismaRepository.typebot.findFirst({ where: { url: url, @@ -601,12 +685,6 @@ export class TypebotService { !stopBotFromMe || !keepOpen ) { - const defaultSettingCheck = await this.prismaRepository.typebotSetting.findFirst({ - where: { - instanceId: instance.instanceId, - }, - }); - if (!expire) expire = defaultSettingCheck?.expire || 0; if (!keywordFinish) keywordFinish = defaultSettingCheck?.keywordFinish || '#SAIR'; if (!delayMessage) delayMessage = defaultSettingCheck?.delayMessage || 1000; @@ -1245,9 +1323,30 @@ export class TypebotService { }, }); - if (settings.ignoreJids) { + if (settings?.ignoreJids) { const ignoreJids: any = settings.ignoreJids; + let ignoreGroups = false; + let ignoreContacts = false; + + if (ignoreJids.includes('@g.us')) { + ignoreGroups = true; + } + + if (ignoreJids.includes('@s.whatsapp.net')) { + ignoreContacts = true; + } + + if (ignoreGroups && remoteJid.endsWith('@g.us')) { + this.logger.warn('Ignoring message from group: ' + remoteJid); + return; + } + + if (ignoreContacts && remoteJid.endsWith('@s.whatsapp.net')) { + this.logger.warn('Ignoring message from contact: ' + remoteJid); + return; + } + if (ignoreJids.includes(remoteJid)) { this.logger.warn('Ignoring message from jid: ' + remoteJid); return; diff --git a/src/api/integrations/typebot/validate/typebot.schema.ts b/src/api/integrations/typebot/validate/typebot.schema.ts index 5131dbcc..838d2da5 100644 --- a/src/api/integrations/typebot/validate/typebot.schema.ts +++ b/src/api/integrations/typebot/validate/typebot.schema.ts @@ -83,3 +83,14 @@ export const typebotSettingSchema: JSONSchema7 = { required: ['expire', 'keywordFinish', 'delayMessage', 'unknownMessage', 'listeningFromMe', 'stopBotFromMe'], ...isNotEmpty('expire', 'keywordFinish', 'delayMessage', 'unknownMessage', 'listeningFromMe', 'stopBotFromMe'), }; + +export const typebotIgnoreJidSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + remoteJid: { type: 'string' }, + action: { type: 'string', enum: ['add', 'remove'] }, + }, + required: ['remoteJid', 'action'], + ...isNotEmpty('remoteJid', 'action'), +}; diff --git a/src/api/services/channels/whatsapp.business.service.ts b/src/api/services/channels/whatsapp.business.service.ts index da61b72a..d835361b 100644 --- a/src/api/services/channels/whatsapp.business.service.ts +++ b/src/api/services/channels/whatsapp.business.service.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { arrayUnique, isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; import FormData from 'form-data'; -import fs from 'fs/promises'; +import { createReadStream } from 'fs'; import { getMIMEType } from 'node-mime-types'; import { Chatwoot, ConfigService, Database, Typebot, WaBusiness } from '../../../config/env.config'; @@ -885,12 +885,19 @@ export class BusinessStartupService extends ChannelStartupService { private async getIdMedia(mediaMessage: any) { const formData = new FormData(); - const fileBuffer = await fs.readFile(mediaMessage.media); + const fileStream = createReadStream(mediaMessage.media); - const fileBlob = new Blob([fileBuffer], { type: mediaMessage.mimetype }); - formData.append('file', fileBlob); + formData.append('file', fileStream, { filename: 'media', contentType: mediaMessage.mimetype }); formData.append('typeFile', mediaMessage.mimetype); formData.append('messaging_product', 'whatsapp'); + + // 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 = { Authorization: `Bearer ${this.token}` }; const res = await axios.post( process.env.API_URL + '/' + process.env.VERSION + '/' + this.number + '/media', diff --git a/src/config/event.config.ts b/src/config/event.config.ts index 8451ffdf..ff9b92f3 100644 --- a/src/config/event.config.ts +++ b/src/config/event.config.ts @@ -4,4 +4,5 @@ export const eventEmitter = new EventEmitter2({ delimiter: '.', newListener: false, ignoreErrors: false, + maxListeners: 50, });