From e59098cf611cf8379f1ba033b4a8695434c79527 Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Wed, 7 Feb 2024 08:44:54 -0300 Subject: [PATCH 01/13] Changed whatsapp number response to 200 Nothing is being created so the code 201 is not correct --- src/whatsapp/routers/chat.router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/whatsapp/routers/chat.router.ts b/src/whatsapp/routers/chat.router.ts index 77285d1b..e4161474 100644 --- a/src/whatsapp/routers/chat.router.ts +++ b/src/whatsapp/routers/chat.router.ts @@ -60,7 +60,7 @@ export class ChatRouter extends RouterBroker { execute: (instance, data) => chatController.whatsappNumber(instance, data), }); - return res.status(HttpStatus.CREATED).json(response); + return res.status(HttpStatus.OK).json(response); }) .put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => { logger.verbose('request received in markMessageAsRead'); From 35cdce0d52ec3caf2318bca0dc949d30229acb8c Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Wed, 7 Feb 2024 08:53:47 -0300 Subject: [PATCH 02/13] Check multiple numbers only once in the database --- src/whatsapp/repository/contact.repository.ts | 55 +++++++++++++++++++ src/whatsapp/services/whatsapp.service.ts | 18 ++---- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/whatsapp/repository/contact.repository.ts b/src/whatsapp/repository/contact.repository.ts index 074b12e9..d26ada35 100644 --- a/src/whatsapp/repository/contact.repository.ts +++ b/src/whatsapp/repository/contact.repository.ts @@ -11,6 +11,11 @@ export class ContactQuery { where: ContactRaw; } +export class ContactQueryMany { + owner: ContactRaw['owner']; + ids: ContactRaw['id'][]; +} + export class ContactRepository extends Repository { constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) { super(configService); @@ -169,4 +174,54 @@ export class ContactRepository extends Repository { return []; } } + + public async findManyById(query: ContactQueryMany): Promise { + try { + this.logger.verbose('finding contacts'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding contacts in db'); + return await this.contactModel.find({ + owner: query.owner, + id: { $in: query.ids }, + }); + } + + this.logger.verbose('finding contacts in store'); + const contacts: ContactRaw[] = []; + if (query.ids.length > 0) { + this.logger.verbose('finding contacts in store by id'); + query.ids.forEach((id) => { + contacts.push( + JSON.parse( + readFileSync(join(this.storePath, 'contacts', query.owner, id + '.json'), { + encoding: 'utf-8', + }), + ), + ); + }); + } else { + this.logger.verbose('finding contacts in store by owner'); + + const openDir = opendirSync(join(this.storePath, 'contacts', query.owner), { + encoding: 'utf-8', + }); + for await (const dirent of openDir) { + if (dirent.isFile()) { + contacts.push( + JSON.parse( + readFileSync(join(this.storePath, 'contacts', query.owner, dirent.name), { + encoding: 'utf-8', + }), + ), + ); + } + } + } + + this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts'); + return contacts; + } catch (error) { + return []; + } + } } diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index b9a89aae..22f7e7b2 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -3312,6 +3312,10 @@ export class WAStartupService { onWhatsapp.push(...groups); // USERS + const contacts: ContactRaw[] = await this.repository.contact.findManyById({ + owner: this.instance.name, + ids: jids.users.map(({ jid }) => (jid.startsWith('+') ? jid.substring(1) : jid)), + }); const verify = await this.client.onWhatsApp( ...jids.users.map(({ jid }) => (!jid.startsWith('+') ? `+${jid}` : jid)), ); @@ -3321,18 +3325,6 @@ export class WAStartupService { const isBrWithDigit = user.jid.startsWith('55') && user.jid.slice(4, 5) === '9' && user.jid.length === 28; const jid = isBrWithDigit ? user.jid.slice(0, 4) + user.jid.slice(5) : user.jid; - const query: ContactQuery = { - where: { - owner: this.instance.name, - id: user.jid.startsWith('+') ? user.jid.substring(1) : user.jid, - }, - }; - const contacts: ContactRaw[] = await this.repository.contact.find(query); - let firstContactFound; - if (contacts.length > 0) { - firstContactFound = contacts[0].pushName; - } - const numberVerified = verify.find((v) => { const mainJidSimilarity = levenshtein.get(user.jid, v.jid) / Math.max(user.jid.length, v.jid.length); const jidSimilarity = levenshtein.get(jid, v.jid) / Math.max(jid.length, v.jid.length); @@ -3341,7 +3333,7 @@ export class WAStartupService { return { exists: !!numberVerified?.exists, jid: numberVerified?.jid || user.jid, - name: firstContactFound, + name: contacts.find((c) => c.id === jid)?.pushName, number: user.number, }; }), From 34c53d352a922a82bd923312720610f37dd2ef2d Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Wed, 7 Feb 2024 15:16:37 -0300 Subject: [PATCH 03/13] Refactor code and remove commented out sections --- src/utils/i18n.ts | 4 ---- src/whatsapp/abstract/abstract.router.ts | 5 ----- src/whatsapp/controllers/settings.controller.ts | 3 --- src/whatsapp/routers/chatwoot.router.ts | 1 - src/whatsapp/routers/settings.router.ts | 1 - src/whatsapp/services/chatwoot.service.ts | 10 ---------- 6 files changed, 24 deletions(-) diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts index 65feab48..74a76724 100644 --- a/src/utils/i18n.ts +++ b/src/utils/i18n.ts @@ -4,8 +4,6 @@ import path from 'path'; import { ConfigService, Language } from '../config/env.config'; -// export class i18n { -// constructor(private readonly configService: ConfigService) { const languages = ['en', 'pt-BR']; const translationsPath = path.join(__dirname, 'translations'); const configService: ConfigService = new ConfigService(); @@ -31,6 +29,4 @@ i18next.init({ escapeValue: false, }, }); -// } -// } export default i18next; diff --git a/src/whatsapp/abstract/abstract.router.ts b/src/whatsapp/abstract/abstract.router.ts index 7c603880..18770ffa 100644 --- a/src/whatsapp/abstract/abstract.router.ts +++ b/src/whatsapp/abstract/abstract.router.ts @@ -21,7 +21,6 @@ const logger = new Logger('Validate'); export abstract class RouterBroker { constructor() {} public routerPath(path: string, param = true) { - // const route = param ? '/:instanceName/' + path : '/' + path; let route = '/' + path; param ? (route += '/:instanceName') : null; @@ -56,10 +55,6 @@ export abstract class RouterBroker { message = stack.replace('instance.', ''); } return message; - // return { - // property: property.replace('instance.', ''), - // message, - // }; }); logger.error(message); throw new BadRequestException(message); diff --git a/src/whatsapp/controllers/settings.controller.ts b/src/whatsapp/controllers/settings.controller.ts index 0f559d1b..15563647 100644 --- a/src/whatsapp/controllers/settings.controller.ts +++ b/src/whatsapp/controllers/settings.controller.ts @@ -1,7 +1,4 @@ -// import { isURL } from 'class-validator'; - import { Logger } from '../../config/logger.config'; -// import { BadRequestException } from '../../exceptions'; import { InstanceDto } from '../dto/instance.dto'; import { SettingsDto } from '../dto/settings.dto'; import { SettingsService } from '../services/settings.service'; diff --git a/src/whatsapp/routers/chatwoot.router.ts b/src/whatsapp/routers/chatwoot.router.ts index eb779587..c232e007 100644 --- a/src/whatsapp/routers/chatwoot.router.ts +++ b/src/whatsapp/routers/chatwoot.router.ts @@ -5,7 +5,6 @@ import { chatwootSchema, instanceNameSchema } from '../../validate/validate.sche import { RouterBroker } from '../abstract/abstract.router'; import { ChatwootDto } from '../dto/chatwoot.dto'; import { InstanceDto } from '../dto/instance.dto'; -// import { ChatwootService } from '../services/chatwoot.service'; import { chatwootController } from '../whatsapp.module'; import { HttpStatus } from './index.router'; diff --git a/src/whatsapp/routers/settings.router.ts b/src/whatsapp/routers/settings.router.ts index 6bd4d549..57e56b0d 100644 --- a/src/whatsapp/routers/settings.router.ts +++ b/src/whatsapp/routers/settings.router.ts @@ -5,7 +5,6 @@ import { instanceNameSchema, settingsSchema } from '../../validate/validate.sche import { RouterBroker } from '../abstract/abstract.router'; import { InstanceDto } from '../dto/instance.dto'; import { SettingsDto } from '../dto/settings.dto'; -// import { SettingsService } from '../services/settings.service'; import { settingsController } from '../whatsapp.module'; import { HttpStatus } from './index.router'; diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index 42043e65..b0b07ce5 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -51,11 +51,6 @@ export class ChatwootService { this.cache.set(cacheKey, provider); return provider; - // try { - // } catch (error) { - // this.logger.error('provider not found'); - // return null; - // } } private async clientCw(instance: InstanceDto) { @@ -389,10 +384,6 @@ export class ChatwootService { q: query, }); } else { - // contact = await client.contacts.filter({ - // accountId: this.provider.account_id, - // payload: this.getFilterPayload(query), - // }); // hotfix for: https://github.com/EvolutionAPI/evolution-api/pull/382. waiting fix: https://github.com/figurolatam/chatwoot-sdk/pull/7 contact = await chatwootRequest(this.getClientCwConfig(), { method: 'POST', @@ -1194,7 +1185,6 @@ export class ChatwootService { if (state !== 'open') { if (state === 'close') { this.logger.verbose('request cleaning up instance: ' + instance.instanceName); - // await this.waMonitor.cleaningUp(instance.instanceName); } this.logger.verbose('connect to whatsapp'); const number = command.split(':')[1]; From 704701e2516c56f4c9e34fc1a9ba6699379c4889 Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Wed, 7 Feb 2024 16:34:26 -0300 Subject: [PATCH 04/13] Add list method to AuthRepository and delete temp instances in WAMonitoringService --- src/whatsapp/repository/auth.repository.ts | 33 +++++++++++++++++++- src/whatsapp/services/monitor.service.ts | 36 +++++++++++++--------- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/whatsapp/repository/auth.repository.ts b/src/whatsapp/repository/auth.repository.ts index 7aa1a427..fa520a16 100644 --- a/src/whatsapp/repository/auth.repository.ts +++ b/src/whatsapp/repository/auth.repository.ts @@ -1,4 +1,4 @@ -import { readFileSync } from 'fs'; +import { opendirSync, readFileSync } from 'fs'; import { join } from 'path'; import { Auth, ConfigService } from '../../config/env.config'; @@ -64,6 +64,37 @@ export class AuthRepository extends Repository { } } + public async list(): Promise { + try { + if (this.dbSettings.ENABLED) { + this.logger.verbose('listing auth in db'); + return await this.authModel.find(); + } + + this.logger.verbose('listing auth in store'); + + const auths: AuthRaw[] = []; + const openDir = opendirSync(join(AUTH_DIR, this.auth.TYPE), { + encoding: 'utf-8', + }); + for await (const dirent of openDir) { + if (dirent.isFile()) { + auths.push( + JSON.parse( + readFileSync(join(AUTH_DIR, this.auth.TYPE, dirent.name), { + encoding: 'utf-8', + }), + ), + ); + } + } + + return auths; + } catch (error) { + return []; + } + } + public async findInstanceNameById(instanceId: string): Promise { try { this.logger.verbose('finding auth by instanceId'); diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index 3c3e8881..4be4e462 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -2,22 +2,19 @@ import { execSync } from 'child_process'; import EventEmitter2 from 'eventemitter2'; import { opendirSync, readdirSync, rmSync } from 'fs'; import { Db } from 'mongodb'; +import { Collection } from 'mongoose'; import { join } from 'path'; import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config'; import { NotFoundException } from '../../exceptions'; -import { dbserver } from '../../libs/db.connect'; import { RedisCache } from '../../libs/redis.client'; import { AuthModel, ChamaaiModel, - // ChatModel, ChatwootModel, - // ContactModel, - // MessageModel, - // MessageUpModel, + ContactModel, ProxyModel, RabbitmqModel, SettingsModel, @@ -41,7 +38,6 @@ export class WAMonitoringService { this.removeInstance(); this.noConnection(); - // this.delInstanceFiles(); Object.assign(this.db, configService.get('DATABASE')); Object.assign(this.redis, configService.get('REDIS')); @@ -56,8 +52,6 @@ export class WAMonitoringService { private dbInstance: Db; - private dbStore = dbserver; - private readonly logger = new Logger(WAMonitoringService.name); public readonly waInstances: Record = {}; @@ -326,11 +320,6 @@ export class WAMonitoringService { this.logger.verbose('cleaning store database instance: ' + instanceName); - // await ChatModel.deleteMany({ owner: instanceName }); - // await ContactModel.deleteMany({ owner: instanceName }); - // await MessageUpModel.deleteMany({ owner: instanceName }); - // await MessageModel.deleteMany({ owner: instanceName }); - await AuthModel.deleteMany({ _id: instanceName }); await WebhookModel.deleteMany({ _id: instanceName }); await ChatwootModel.deleteMany({ _id: instanceName }); @@ -340,6 +329,7 @@ export class WAMonitoringService { await TypebotModel.deleteMany({ _id: instanceName }); await WebsocketModel.deleteMany({ _id: instanceName }); await SettingsModel.deleteMany({ _id: instanceName }); + await ContactModel.deleteMany({ owner: instanceName }); return; } @@ -393,7 +383,7 @@ export class WAMonitoringService { this.logger.verbose('Database enabled'); await this.repository.dbServer.connect(); const collections: any[] = await this.dbInstance.collections(); - + await this.deleteTempInstances(collections); if (collections.length > 0) { this.logger.verbose('Reading collections and setting instances'); await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, '')))); @@ -482,4 +472,22 @@ export class WAMonitoringService { } }); } + + private async deleteTempInstances(collections: Collection[]) { + this.logger.verbose('Cleaning up temp instances'); + const auths = await this.repository.auth.list(); + if (auths.length === 0) { + this.logger.verbose('No temp instances found'); + return; + } + let tempInstances = 0; + auths.forEach((auth) => { + if (collections.find((coll) => coll.namespace.replace(/^[\w-]+\./, '') === auth._id)) { + return; + } + tempInstances++; + this.eventEmitter.emit('remove.instance', auth._id, 'inner'); + }); + this.logger.verbose('Temp instances removed: ' + tempInstances); + } } From 27900c214f88792a9ca367cd01e3fa91f465106f Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Wed, 7 Feb 2024 18:42:12 -0300 Subject: [PATCH 05/13] Extracted profile picture request from contacts.upsert function --- src/whatsapp/services/whatsapp.service.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 22f7e7b2..f5081939 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1657,7 +1657,7 @@ export class WAStartupService { ); this.logger.verbose('Verifying if contacts exists in database to insert'); - const contactsRaw: ContactRaw[] = []; + let contactsRaw: ContactRaw[] = []; for (const contact of contacts) { if (contactsRepository.has(contact.id)) { @@ -1667,7 +1667,7 @@ export class WAStartupService { contactsRaw.push({ id: contact.id, pushName: contact?.name || contact?.verifiedName || contact.id.split('@')[0], - profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl, + profilePictureUrl: null, owner: this.instance.name, }); } @@ -1682,6 +1682,23 @@ export class WAStartupService { this.chatwootService.addHistoryContacts({ instanceName: this.instance.name }, contactsRaw); chatwootImport.importHistoryContacts({ instanceName: this.instance.name }, this.localChatwoot); } + + // Update profile pictures + contactsRaw = []; + for await (const contact of contacts) { + contactsRaw.push({ + id: contact.id, + pushName: contact?.name || contact?.verifiedName || contact.id.split('@')[0], + profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl, + owner: this.instance.name, + }); + } + + this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE'); + this.sendDataWebhook(Events.CONTACTS_UPSERT, contactsRaw); + + this.logger.verbose('Updating contacts in database'); + this.repository.contact.update(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS); } catch (error) { this.logger.error(error); } From 23f1b4ac03d9eccb61262f6665f7186483ebc33a Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Thu, 8 Feb 2024 09:16:24 -0300 Subject: [PATCH 06/13] Implementation of Whatsapp labels management --- CHANGELOG.md | 1 + Docker/.env.example | 2 + .../evolution-api-all-services/.env.example | 2 + Dockerfile | 2 + src/config/env.config.ts | 8 ++ src/dev-env.yml | 2 + src/docs/swagger.yaml | 97 ++++++++++++++ src/validate/validate.schema.ts | 21 +++ .../controllers/instance.controller.ts | 8 ++ src/whatsapp/controllers/label.controller.ts | 20 +++ .../controllers/rabbitmq.controller.ts | 2 + src/whatsapp/controllers/sqs.controller.ts | 2 + .../controllers/webhook.controller.ts | 2 + .../controllers/websocket.controller.ts | 2 + src/whatsapp/dto/label.dto.ts | 121 ++++++++++++++++++ src/whatsapp/models/index.ts | 1 + src/whatsapp/models/label.model.ts | 29 +++++ src/whatsapp/repository/label.repository.ts | 111 ++++++++++++++++ src/whatsapp/repository/repository.manager.ts | 2 + src/whatsapp/routers/index.router.ts | 4 +- src/whatsapp/routers/label.router.ts | 53 ++++++++ src/whatsapp/services/monitor.service.ts | 3 + src/whatsapp/services/whatsapp.service.ts | 106 +++++++++++++++ src/whatsapp/types/wa.types.ts | 4 + src/whatsapp/whatsapp.module.ts | 6 + 25 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 src/whatsapp/controllers/label.controller.ts create mode 100644 src/whatsapp/dto/label.dto.ts create mode 100644 src/whatsapp/models/label.model.ts create mode 100644 src/whatsapp/repository/label.repository.ts create mode 100644 src/whatsapp/routers/label.router.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a9284df3..bd553925 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Join in Group by Invite Code * Read messages from whatsapp in chatwoot * Add support to use use redis in cacheservice +* Add support for labels ### Fixed diff --git a/Docker/.env.example b/Docker/.env.example index 2813117e..dbc82634 100644 --- a/Docker/.env.example +++ b/Docker/.env.example @@ -84,6 +84,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true WEBHOOK_EVENTS_GROUPS_UPDATE=true WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true WEBHOOK_EVENTS_CONNECTION_UPDATE=true +WEBHOOK_EVENTS_LABELS_EDIT=true +WEBHOOK_EVENTS_LABELS_ASSOCIATION=true WEBHOOK_EVENTS_CALL=true # This event fires every time a new token is requested via the refresh route WEBHOOK_EVENTS_NEW_JWT_TOKEN=false diff --git a/Docker/evolution-api-all-services/.env.example b/Docker/evolution-api-all-services/.env.example index 555ba7bc..ce71d917 100644 --- a/Docker/evolution-api-all-services/.env.example +++ b/Docker/evolution-api-all-services/.env.example @@ -73,6 +73,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true WEBHOOK_EVENTS_GROUPS_UPDATE=true WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true WEBHOOK_EVENTS_CONNECTION_UPDATE=true +WEBHOOK_EVENTS_LABELS_EDIT=true +WEBHOOK_EVENTS_LABELS_ASSOCIATION=true # This event fires every time a new token is requested via the refresh route WEBHOOK_EVENTS_NEW_JWT_TOKEN=false diff --git a/Dockerfile b/Dockerfile index 9bc23317..201dda47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -98,6 +98,8 @@ ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true +ENV WEBHOOK_EVENTS_LABELS_EDIT=true +ENV WEBHOOK_EVENTS_LABELS_ASSOCIATION=true ENV WEBHOOK_EVENTS_CALL=true ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 22ea9b21..4c5c9120 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -34,6 +34,7 @@ export type SaveData = { MESSAGE_UPDATE: boolean; CONTACTS: boolean; CHATS: boolean; + LABELS: boolean; }; export type StoreConf = { @@ -41,6 +42,7 @@ export type StoreConf = { MESSAGE_UP: boolean; CONTACTS: boolean; CHATS: boolean; + LABELS: boolean; }; export type CleanStoreConf = { @@ -103,6 +105,8 @@ export type EventsWebhook = { CHATS_DELETE: boolean; CHATS_UPSERT: boolean; CONNECTION_UPDATE: boolean; + LABELS_EDIT: boolean; + LABELS_ASSOCIATION: boolean; GROUPS_UPSERT: boolean; GROUP_UPDATE: boolean; GROUP_PARTICIPANTS_UPDATE: boolean; @@ -237,6 +241,7 @@ export class ConfigService { MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true', CONTACTS: process.env?.STORE_CONTACTS === 'true', CHATS: process.env?.STORE_CHATS === 'true', + LABELS: process.env?.STORE_LABELS === 'true', }, CLEAN_STORE: { CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL) @@ -259,6 +264,7 @@ export class ConfigService { MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true', CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true', CHATS: process.env?.DATABASE_SAVE_DATA_CHATS === 'true', + LABELS: process.env?.DATABASE_SAVE_DATA_LABELS === 'true', }, }, REDIS: { @@ -323,6 +329,8 @@ export class ConfigService { CHATS_UPSERT: process.env?.WEBHOOK_EVENTS_CHATS_UPSERT === 'true', CHATS_DELETE: process.env?.WEBHOOK_EVENTS_CHATS_DELETE === 'true', CONNECTION_UPDATE: process.env?.WEBHOOK_EVENTS_CONNECTION_UPDATE === 'true', + LABELS_EDIT: process.env?.WEBHOOK_EVENTS_LABELS_EDIT === 'true', + LABELS_ASSOCIATION: process.env?.WEBHOOK_EVENTS_LABELS_ASSOCIATION === 'true', GROUPS_UPSERT: process.env?.WEBHOOK_EVENTS_GROUPS_UPSERT === 'true', GROUP_UPDATE: process.env?.WEBHOOK_EVENTS_GROUPS_UPDATE === 'true', GROUP_PARTICIPANTS_UPDATE: process.env?.WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE === 'true', diff --git a/src/dev-env.yml b/src/dev-env.yml index 07e71453..8567e117 100644 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -127,6 +127,8 @@ WEBHOOK: GROUP_UPDATE: true GROUP_PARTICIPANTS_UPDATE: true CONNECTION_UPDATE: true + LABELS_EDIT: true + LABELS_ASSOCIATION: true CALL: true # This event fires every time a new token is requested via the refresh route NEW_JWT_TOKEN: false diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index b5b27c38..d18a5bf1 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -51,6 +51,7 @@ tags: - name: Send Message Controller - name: Chat Controller - name: Group Controller + - name: Label Controller - name: Profile Settings - name: JWT - name: Settings @@ -1856,6 +1857,8 @@ paths: "GROUP_UPDATE", "GROUP_PARTICIPANTS_UPDATE", "CONNECTION_UPDATE", + "LABELS_EDIT", + "LABELS_ASSOCIATION", "CALL", "NEW_JWT_TOKEN", ] @@ -1932,6 +1935,8 @@ paths: "GROUP_UPDATE", "GROUP_PARTICIPANTS_UPDATE", "CONNECTION_UPDATE", + "LABELS_EDIT", + "LABELS_ASSOCIATION", "CALL", "NEW_JWT_TOKEN", ] @@ -2008,6 +2013,8 @@ paths: "GROUP_UPDATE", "GROUP_PARTICIPANTS_UPDATE", "CONNECTION_UPDATE", + "LABELS_EDIT", + "LABELS_ASSOCIATION", "CALL", "NEW_JWT_TOKEN", ] @@ -2046,6 +2053,96 @@ paths: content: application/json: {} + /label/findLabels/{instanceName}: + get: + tags: + - Label Controller + summary: List all labels for an instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + color: + type: integer + name: + type: string + id: + type: string + predefinedId: + type: string + required: + - color + - name + - id + /label/handleLabel/{instanceName}: + put: + tags: + - Label Controller + summary: Change the label (add or remove) for an specific chat. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + requestBody: + content: + application/json: + schema: + type: object + properties: + number: + type: string + labelId: + type: string + action: + type: string + enum: + - add + - remove + required: + - number + - labelId + - action + example: + number: '553499999999' + labelId: '1' + action: add + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: object + properties: + numberJid: + type: string + labelId: + type: string + remove: + type: boolean + required: + - numberJid + - labelId + - remove + /settings/set/{instanceName}: post: tags: diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 2dcdc206..3d9e1294 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -53,6 +53,8 @@ export const instanceNameSchema: JSONSchema7 = { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -897,6 +899,8 @@ export const webhookSchema: JSONSchema7 = { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -977,6 +981,8 @@ export const websocketSchema: JSONSchema7 = { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -1020,6 +1026,8 @@ export const rabbitmqSchema: JSONSchema7 = { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -1063,6 +1071,8 @@ export const sqsSchema: JSONSchema7 = { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -1150,3 +1160,14 @@ export const chamaaiSchema: JSONSchema7 = { required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'], ...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'), }; + +export const handleLabelSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + labelId: { type: 'string' }, + action: { type: 'string', enum: ['add', 'remove'] }, + }, + required: ['number', 'labelId', 'action'], +}; diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 0e48c2bf..414a2c2d 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -151,6 +151,8 @@ export class InstanceController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -201,6 +203,8 @@ export class InstanceController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -248,6 +252,8 @@ export class InstanceController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', @@ -295,6 +301,8 @@ export class InstanceController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', diff --git a/src/whatsapp/controllers/label.controller.ts b/src/whatsapp/controllers/label.controller.ts new file mode 100644 index 00000000..6fd91d86 --- /dev/null +++ b/src/whatsapp/controllers/label.controller.ts @@ -0,0 +1,20 @@ +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { HandleLabelDto } from '../dto/label.dto'; +import { WAMonitoringService } from '../services/monitor.service'; + +const logger = new Logger('LabelController'); + +export class LabelController { + constructor(private readonly waMonitor: WAMonitoringService) {} + + public async fetchLabels({ instanceName }: InstanceDto) { + logger.verbose('requested fetchLabels from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchLabels(); + } + + public async handleLabel({ instanceName }: InstanceDto, data: HandleLabelDto) { + logger.verbose('requested chat label change from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].handleLabel(data); + } +} diff --git a/src/whatsapp/controllers/rabbitmq.controller.ts b/src/whatsapp/controllers/rabbitmq.controller.ts index 8d33ce84..527c5006 100644 --- a/src/whatsapp/controllers/rabbitmq.controller.ts +++ b/src/whatsapp/controllers/rabbitmq.controller.ts @@ -38,6 +38,8 @@ export class RabbitmqController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', diff --git a/src/whatsapp/controllers/sqs.controller.ts b/src/whatsapp/controllers/sqs.controller.ts index 063e29ed..8dfebc6c 100644 --- a/src/whatsapp/controllers/sqs.controller.ts +++ b/src/whatsapp/controllers/sqs.controller.ts @@ -38,6 +38,8 @@ export class SqsController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', diff --git a/src/whatsapp/controllers/webhook.controller.ts b/src/whatsapp/controllers/webhook.controller.ts index 8201f1b5..0b591cc3 100644 --- a/src/whatsapp/controllers/webhook.controller.ts +++ b/src/whatsapp/controllers/webhook.controller.ts @@ -46,6 +46,8 @@ export class WebhookController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', diff --git a/src/whatsapp/controllers/websocket.controller.ts b/src/whatsapp/controllers/websocket.controller.ts index 5771027a..15abde70 100644 --- a/src/whatsapp/controllers/websocket.controller.ts +++ b/src/whatsapp/controllers/websocket.controller.ts @@ -38,6 +38,8 @@ export class WebsocketController { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'LABELS_EDIT', + 'LABELS_ASSOCIATION', 'CALL', 'NEW_JWT_TOKEN', 'TYPEBOT_START', diff --git a/src/whatsapp/dto/label.dto.ts b/src/whatsapp/dto/label.dto.ts new file mode 100644 index 00000000..ab7e564c --- /dev/null +++ b/src/whatsapp/dto/label.dto.ts @@ -0,0 +1,121 @@ +import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; + +export class OnWhatsAppDto { + constructor( + public readonly jid: string, + public readonly exists: boolean, + public readonly number: string, + public readonly name?: string, + ) {} +} + +export class LabelDto { + id?: string; + name: string; + color: number; + predefinedId?: string; +} + +export class HandleLabelDto { + number: string; + labelId: string; + action: 'add' | 'remove'; +} + +export class WhatsAppNumberDto { + numbers: string[]; +} + +export class NumberDto { + number: string; +} + +export class NumberBusiness { + wid?: string; + jid?: string; + exists?: boolean; + isBusiness: boolean; + name?: string; + message?: string; + description?: string; + email?: string; + website?: string[]; + address?: string; +} + +export class ProfileNameDto { + name: string; +} + +export class ProfileStatusDto { + status: string; +} + +export class ProfilePictureDto { + number?: string; + // url or base64 + picture?: string; +} + +class Key { + id: string; + fromMe: boolean; + remoteJid: string; +} +export class ReadMessageDto { + read_messages: Key[]; +} + +export class LastMessage { + key: Key; + messageTimestamp?: number; +} + +export class ArchiveChatDto { + lastMessage?: LastMessage; + chat?: string; + archive: boolean; +} + +class PrivacySetting { + readreceipts: WAReadReceiptsValue; + profile: WAPrivacyValue; + status: WAPrivacyValue; + online: WAPrivacyOnlineValue; + last: WAPrivacyValue; + groupadd: WAPrivacyValue; +} + +export class PrivacySettingDto { + privacySettings: PrivacySetting; +} + +export class DeleteMessage { + id: string; + fromMe: boolean; + remoteJid: string; + participant?: string; +} +export class Options { + delay?: number; + presence?: WAPresence; +} +class OptionsMessage { + options: Options; +} +export class Metadata extends OptionsMessage { + number: string; +} + +export class SendPresenceDto extends Metadata { + options: { + presence: WAPresence; + delay: number; + }; +} + +export class UpdateMessageDto extends Metadata { + number: string; + key: proto.IMessageKey; + text: string; +} diff --git a/src/whatsapp/models/index.ts b/src/whatsapp/models/index.ts index 7903e5b5..4d21e9b8 100644 --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -3,6 +3,7 @@ export * from './chamaai.model'; export * from './chat.model'; export * from './chatwoot.model'; export * from './contact.model'; +export * from './label.model'; export * from './message.model'; export * from './proxy.model'; export * from './rabbitmq.model'; diff --git a/src/whatsapp/models/label.model.ts b/src/whatsapp/models/label.model.ts new file mode 100644 index 00000000..99c39909 --- /dev/null +++ b/src/whatsapp/models/label.model.ts @@ -0,0 +1,29 @@ +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class LabelRaw { + _id?: string; + id?: string; + owner: string; + name: string; + color: number; + predefinedId?: string; +} + +type LabelRawBoolean = { + [P in keyof T]?: 0 | 1; +}; +export type LabelRawSelect = LabelRawBoolean; + +const labelSchema = new Schema({ + _id: { type: String, _id: true }, + id: { type: String, required: true, minlength: 1 }, + owner: { type: String, required: true, minlength: 1 }, + name: { type: String, required: true, minlength: 1 }, + color: { type: Number, required: true, min: 0, max: 19 }, + predefinedId: { type: String }, +}); + +export const LabelModel = dbserver?.model(LabelRaw.name, labelSchema, 'labels'); +export type ILabelModel = typeof LabelModel; diff --git a/src/whatsapp/repository/label.repository.ts b/src/whatsapp/repository/label.repository.ts new file mode 100644 index 00000000..7bbb0e58 --- /dev/null +++ b/src/whatsapp/repository/label.repository.ts @@ -0,0 +1,111 @@ +import { opendirSync, readFileSync, rmSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ILabelModel, LabelRaw, LabelRawSelect } from '../models'; + +export class LabelQuery { + select?: LabelRawSelect; + where: Partial; +} + +export class LabelRepository extends Repository { + constructor(private readonly labelModel: ILabelModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('LabelRepository'); + + public async insert(data: LabelRaw, instanceName: string, saveDb = false): Promise { + this.logger.verbose('inserting labels'); + + try { + if (this.dbSettings.ENABLED && saveDb) { + this.logger.verbose('saving labels to db'); + const insert = await this.labelModel.findOneAndUpdate({ id: data.id }, data, { upsert: true }); + + this.logger.verbose(`label ${data.name} saved to db`); + return { insertCount: Number(!!insert._id) }; + } + + this.logger.verbose('saving label to store'); + + const store = this.configService.get('STORE'); + + if (store.LABELS) { + this.logger.verbose('saving label to store'); + this.writeStore({ + path: join(this.storePath, 'labels', instanceName), + fileName: data.id, + data, + }); + this.logger.verbose( + 'labels saved to store in path: ' + join(this.storePath, 'labels', instanceName) + '/' + data.id, + ); + + this.logger.verbose(`label ${data.name} saved to store`); + return { insertCount: 1 }; + } + + this.logger.verbose('labels not saved to store'); + return { insertCount: 0 }; + } catch (error) { + return error; + } finally { + data = undefined; + } + } + + public async find(query: LabelQuery): Promise { + try { + this.logger.verbose('finding labels'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding labels in db'); + return await this.labelModel.find({ owner: query.where.owner }).select(query.select ?? {}); + } + + this.logger.verbose('finding labels in store'); + + const labels: LabelRaw[] = []; + const openDir = opendirSync(join(this.storePath, 'labels', query.where.owner)); + for await (const dirent of openDir) { + if (dirent.isFile()) { + labels.push( + JSON.parse( + readFileSync(join(this.storePath, 'labels', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), + ), + ); + } + } + + this.logger.verbose('labels found in store: ' + labels.length + ' labels'); + return labels; + } catch (error) { + return []; + } + } + + public async delete(query: LabelQuery) { + try { + this.logger.verbose('deleting labels'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('deleting labels in db'); + return await this.labelModel.deleteOne({ ...query.where }); + } + + this.logger.verbose('deleting labels in store'); + rmSync(join(this.storePath, 'labels', query.where.owner, query.where.id + '.josn'), { + force: true, + recursive: true, + }); + + return { deleted: { labelId: query.where.id } }; + } catch (error) { + return { error: error?.toString() }; + } + } +} diff --git a/src/whatsapp/repository/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts index ab4da1e3..57b63e16 100644 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -9,6 +9,7 @@ import { ChamaaiRepository } from './chamaai.repository'; import { ChatRepository } from './chat.repository'; import { ChatwootRepository } from './chatwoot.repository'; import { ContactRepository } from './contact.repository'; +import { LabelRepository } from './label.repository'; import { MessageRepository } from './message.repository'; import { MessageUpRepository } from './messageUp.repository'; import { ProxyRepository } from './proxy.repository'; @@ -34,6 +35,7 @@ export class RepositoryBroker { public readonly proxy: ProxyRepository, public readonly chamaai: ChamaaiRepository, public readonly auth: AuthRepository, + public readonly labels: LabelRepository, private configService: ConfigService, dbServer?: MongoClient, ) { diff --git a/src/whatsapp/routers/index.router.ts b/src/whatsapp/routers/index.router.ts index 56b9301f..33cf94c9 100644 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -9,6 +9,7 @@ import { ChatRouter } from './chat.router'; import { ChatwootRouter } from './chatwoot.router'; import { GroupRouter } from './group.router'; import { InstanceRouter } from './instance.router'; +import { LabelRouter } from './label.router'; import { ProxyRouter } from './proxy.router'; import { RabbitmqRouter } from './rabbitmq.router'; import { MessageRouter } from './sendMessage.router'; @@ -61,6 +62,7 @@ router .use('/sqs', new SqsRouter(...guards).router) .use('/typebot', new TypebotRouter(...guards).router) .use('/proxy', new ProxyRouter(...guards).router) - .use('/chamaai', new ChamaaiRouter(...guards).router); + .use('/chamaai', new ChamaaiRouter(...guards).router) + .use('/label', new LabelRouter(...guards).router); export { HttpStatus, router }; diff --git a/src/whatsapp/routers/label.router.ts b/src/whatsapp/routers/label.router.ts new file mode 100644 index 00000000..a856ff6d --- /dev/null +++ b/src/whatsapp/routers/label.router.ts @@ -0,0 +1,53 @@ +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { handleLabelSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { HandleLabelDto, LabelDto } from '../dto/label.dto'; +import { labelController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('LabelRouter'); + +export class LabelRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .get(this.routerPath('findLabels'), ...guards, async (req, res) => { + logger.verbose('request received in findLabels'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: LabelDto, + execute: (instance) => labelController.fetchLabels(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .put(this.routerPath('handleLabel'), ...guards, async (req, res) => { + logger.verbose('request received in handleLabel'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: handleLabelSchema, + ClassRef: HandleLabelDto, + execute: (instance, data) => labelController.handleLabel(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index 3c3e8881..2beec2fa 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -15,6 +15,7 @@ import { ChamaaiModel, // ChatModel, ChatwootModel, + LabelModel, // ContactModel, // MessageModel, // MessageUpModel, @@ -320,6 +321,7 @@ export class WAMonitoringService { execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`); execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`); execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`); + execSync(`rm -rf ${join(STORE_DIR, 'labels', instanceName + '*')}`); return; } @@ -340,6 +342,7 @@ export class WAMonitoringService { await TypebotModel.deleteMany({ _id: instanceName }); await WebsocketModel.deleteMany({ _id: instanceName }); await SettingsModel.deleteMany({ _id: instanceName }); + await LabelModel.deleteMany({ owner: instanceName }); return; } diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 22f7e7b2..b2d3bccd 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -34,6 +34,8 @@ import makeWASocket, { WAMessageUpdate, WASocket, } from '@whiskeysockets/baileys'; +import { Label } from '@whiskeysockets/baileys/lib/Types/Label'; +import { LabelAssociation } from '@whiskeysockets/baileys/lib/Types/LabelAssociation'; import axios from 'axios'; import { exec, execSync } from 'child_process'; import { arrayUnique, isBase64, isURL } from 'class-validator'; @@ -105,6 +107,7 @@ import { GroupUpdateSettingDto, } from '../dto/group.dto'; import { InstanceDto } from '../dto/instance.dto'; +import { HandleLabelDto, LabelDto } from '../dto/label.dto'; import { ContactMessage, MediaMessage, @@ -2147,6 +2150,53 @@ export class WAStartupService { }, }; + private readonly labelHandle = { + [Events.LABELS_EDIT]: async (label: Label, database: Database) => { + this.logger.verbose('Event received: labels.edit'); + this.logger.verbose('Finding labels in database'); + const labelsRepository = await this.repository.labels.find({ + where: { owner: this.instance.name }, + }); + + const savedLabel = labelsRepository.find((l) => l.id === label.id); + if (label.deleted && savedLabel) { + this.logger.verbose('Sending data to webhook in event LABELS_EDIT'); + await this.repository.labels.delete({ + where: { owner: this.instance.name, id: label.id }, + }); + this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name }); + return; + } + + const labelName = label.name.replace(/[^\x20-\x7E]/g, ''); + if (!savedLabel || savedLabel.color !== label.color || savedLabel.name !== labelName) { + this.logger.verbose('Sending data to webhook in event LABELS_EDIT'); + await this.repository.labels.insert( + { + color: label.color, + name: labelName, + owner: this.instance.name, + id: label.id, + predefinedId: label.predefinedId, + }, + this.instance.name, + database.SAVE_DATA.LABELS, + ); + this.sendDataWebhook(Events.LABELS_EDIT, { ...label, instance: this.instance.name }); + } + }, + + [Events.LABELS_ASSOCIATION]: async (data: { association: LabelAssociation; type: 'remove' | 'add' }) => { + this.logger.verbose('Sending data to webhook in event LABELS_ASSOCIATION'); + this.sendDataWebhook(Events.LABELS_ASSOCIATION, { + instance: this.instance.name, + type: data.type, + jid: data.association.chatId, + labelId: data.association.labelId, + }); + }, + }; + private eventHandler() { this.logger.verbose('Initializing event handler'); this.client.ev.process(async (events) => { @@ -2282,6 +2332,19 @@ export class WAStartupService { this.logger.verbose('Listening event: contacts.update'); const payload = events['contacts.update']; this.contactHandle['contacts.update'](payload, database); + + if (events[Events.LABELS_ASSOCIATION]) { + this.logger.verbose('Listening event: labels.association'); + const payload = events[Events.LABELS_ASSOCIATION]; + this.labelHandle[Events.LABELS_ASSOCIATION](payload); + return; + } + + if (events[Events.LABELS_EDIT]) { + this.logger.verbose('Listening event: labels.edit'); + const payload = events[Events.LABELS_EDIT]; + this.labelHandle[Events.LABELS_EDIT](payload, database); + return; } } }); @@ -4005,4 +4068,47 @@ export class WAStartupService { throw new BadRequestException('Unable to leave the group', error.toString()); } } + + public async fetchLabels(): Promise { + this.logger.verbose('Fetching labels'); + const labels = await this.repository.labels.find({ + where: { + owner: this.instance.name, + }, + }); + + return labels.map((label) => ({ + color: label.color, + name: label.name, + id: label.id, + predefinedId: label.predefinedId, + })); + } + + public async handleLabel(data: HandleLabelDto) { + this.logger.verbose('Adding label'); + const whatsappContact = await this.whatsappNumber({ numbers: [data.number] }); + if (whatsappContact.length === 0) { + throw new NotFoundException('Number not found'); + } + const contact = whatsappContact[0]; + if (!contact.exists) { + throw new NotFoundException('Number is not on WhatsApp'); + } + + try { + if (data.action === 'add') { + await this.client.addChatLabel(contact.jid, data.labelId); + + return { numberJid: contact.jid, labelId: data.labelId, add: true }; + } + if (data.action === 'remove') { + await this.client.removeChatLabel(contact.jid, data.labelId); + + return { numberJid: contact.jid, labelId: data.labelId, remove: true }; + } + } catch (error) { + throw new BadRequestException(`Unable to ${data.action} label to chat`, error.toString()); + } + } } diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index e2cfa195..21f5ee31 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -28,6 +28,10 @@ export enum Events { TYPEBOT_START = 'typebot.start', TYPEBOT_CHANGE_STATUS = 'typebot.change-status', CHAMA_AI_ACTION = 'chama-ai.action', + LABELS_EDIT = 'labels.edit', + LABELS_ASSOCIATION = 'labels.association', + CREDS_UPDATE = 'creds.update', + MESSAGING_HISTORY_SET = 'messaging-history.set', } export declare namespace wa { diff --git a/src/whatsapp/whatsapp.module.ts b/src/whatsapp/whatsapp.module.ts index 3e52504f..49beadfe 100644 --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -9,6 +9,7 @@ import { ChatController } from './controllers/chat.controller'; import { ChatwootController } from './controllers/chatwoot.controller'; import { GroupController } from './controllers/group.controller'; import { InstanceController } from './controllers/instance.controller'; +import { LabelController } from './controllers/label.controller'; import { ProxyController } from './controllers/proxy.controller'; import { RabbitmqController } from './controllers/rabbitmq.controller'; import { SendMessageController } from './controllers/sendMessage.controller'; @@ -33,11 +34,13 @@ import { WebhookModel, WebsocketModel, } from './models'; +import { LabelModel } from './models/label.model'; import { AuthRepository } from './repository/auth.repository'; import { ChamaaiRepository } from './repository/chamaai.repository'; import { ChatRepository } from './repository/chat.repository'; import { ChatwootRepository } from './repository/chatwoot.repository'; import { ContactRepository } from './repository/contact.repository'; +import { LabelRepository } from './repository/label.repository'; import { MessageRepository } from './repository/message.repository'; import { MessageUpRepository } from './repository/messageUp.repository'; import { ProxyRepository } from './repository/proxy.repository'; @@ -77,6 +80,7 @@ const sqsRepository = new SqsRepository(SqsModel, configService); const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); const settingsRepository = new SettingsRepository(SettingsModel, configService); const authRepository = new AuthRepository(AuthModel, configService); +const labelRepository = new LabelRepository(LabelModel, configService); export const repository = new RepositoryBroker( messageRepository, @@ -93,6 +97,7 @@ export const repository = new RepositoryBroker( proxyRepository, chamaaiRepository, authRepository, + labelRepository, configService, dbserver?.getClient(), ); @@ -160,5 +165,6 @@ export const instanceController = new InstanceController( export const sendMessageController = new SendMessageController(waMonitor); export const chatController = new ChatController(waMonitor); export const groupController = new GroupController(waMonitor); +export const labelController = new LabelController(waMonitor); logger.info('Module - ON'); From c7bed04c8050fe3da9ef37bf001c212f7fa8fe9d Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Thu, 8 Feb 2024 13:05:06 -0300 Subject: [PATCH 07/13] Add labels property to ContactRaw model and update labels in LABELS_ASSOCIATION event --- src/whatsapp/models/contact.model.ts | 2 + src/whatsapp/services/whatsapp.service.ts | 51 +++++++++++++++++------ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/whatsapp/models/contact.model.ts b/src/whatsapp/models/contact.model.ts index a915bb19..b42dc7d5 100644 --- a/src/whatsapp/models/contact.model.ts +++ b/src/whatsapp/models/contact.model.ts @@ -8,6 +8,7 @@ export class ContactRaw { id?: string; profilePictureUrl?: string; owner: string; + labels?: string[]; } type ContactRawBoolean = { @@ -21,6 +22,7 @@ const contactSchema = new Schema({ id: { type: String, required: true, minlength: 1 }, profilePictureUrl: { type: String, minlength: 1 }, owner: { type: String, required: true, minlength: 1 }, + labels: { type: [String], default: [] }, }); export const ContactModel = dbserver?.model(ContactRaw.name, contactSchema, 'contacts'); diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index b2d3bccd..c7ee3cd5 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -2186,8 +2186,34 @@ export class WAStartupService { } }, - [Events.LABELS_ASSOCIATION]: async (data: { association: LabelAssociation; type: 'remove' | 'add' }) => { + [Events.LABELS_ASSOCIATION]: async ( + data: { association: LabelAssociation; type: 'remove' | 'add' }, + database: Database, + ) => { this.logger.verbose('Sending data to webhook in event LABELS_ASSOCIATION'); + + // Atualiza labels no contato + const contact = await this.repository.contact.find({ + where: { + owner: this.instance.name, + id: data.association.chatId, + }, + }); + if (contact.length > 0) { + let labels = [...contact[0].labels]; + if (data.type === 'remove') { + labels = labels.filter((label) => label !== data.association.labelId); + } else if (data.type === 'add') { + labels = [...labels, data.association.labelId]; + } + await this.repository.contact.update( + [{ ...contact[0], labels }], + this.instance.name, + database.SAVE_DATA.CONTACTS, + ); + } + + // Envia dados para o webhook this.sendDataWebhook(Events.LABELS_ASSOCIATION, { instance: this.instance.name, type: data.type, @@ -2333,18 +2359,19 @@ export class WAStartupService { const payload = events['contacts.update']; this.contactHandle['contacts.update'](payload, database); - if (events[Events.LABELS_ASSOCIATION]) { - this.logger.verbose('Listening event: labels.association'); - const payload = events[Events.LABELS_ASSOCIATION]; - this.labelHandle[Events.LABELS_ASSOCIATION](payload); - return; - } + if (events[Events.LABELS_ASSOCIATION]) { + this.logger.verbose('Listening event: labels.association'); + const payload = events[Events.LABELS_ASSOCIATION]; + this.labelHandle[Events.LABELS_ASSOCIATION](payload, database); + return; + } - if (events[Events.LABELS_EDIT]) { - this.logger.verbose('Listening event: labels.edit'); - const payload = events[Events.LABELS_EDIT]; - this.labelHandle[Events.LABELS_EDIT](payload, database); - return; + if (events[Events.LABELS_EDIT]) { + this.logger.verbose('Listening event: labels.edit'); + const payload = events[Events.LABELS_EDIT]; + this.labelHandle[Events.LABELS_EDIT](payload, database); + return; + } } } }); From e8d32066b47584c189846c2a7d7d41b5151244e2 Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Thu, 8 Feb 2024 13:31:56 -0300 Subject: [PATCH 08/13] Add 'add' field to the 'label' object in swagger.yaml and update event handling in whatsapp.service.ts --- src/docs/swagger.yaml | 3 ++- src/whatsapp/services/whatsapp.service.ts | 24 +++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index d18a5bf1..4a8f55ee 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -2138,10 +2138,11 @@ paths: type: string remove: type: boolean + add: + type: boolean required: - numberJid - labelId - - remove /settings/set/{instanceName}: post: diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 6c59b88a..77c8808a 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -2375,20 +2375,20 @@ export class WAStartupService { this.logger.verbose('Listening event: contacts.update'); const payload = events['contacts.update']; this.contactHandle['contacts.update'](payload, database); + } - if (events[Events.LABELS_ASSOCIATION]) { - this.logger.verbose('Listening event: labels.association'); - const payload = events[Events.LABELS_ASSOCIATION]; - this.labelHandle[Events.LABELS_ASSOCIATION](payload, database); - return; - } + if (events[Events.LABELS_ASSOCIATION]) { + this.logger.verbose('Listening event: labels.association'); + const payload = events[Events.LABELS_ASSOCIATION]; + this.labelHandle[Events.LABELS_ASSOCIATION](payload, database); + return; + } - if (events[Events.LABELS_EDIT]) { - this.logger.verbose('Listening event: labels.edit'); - const payload = events[Events.LABELS_EDIT]; - this.labelHandle[Events.LABELS_EDIT](payload, database); - return; - } + if (events[Events.LABELS_EDIT]) { + this.logger.verbose('Listening event: labels.edit'); + const payload = events[Events.LABELS_EDIT]; + this.labelHandle[Events.LABELS_EDIT](payload, database); + return; } } }); From b58fd78450e5d03c349e39f833f98059eca5433e Mon Sep 17 00:00:00 2001 From: jaison-x Date: Mon, 5 Feb 2024 11:32:22 -0300 Subject: [PATCH 09/13] fix(chatwoot): fix qrcode filename --- src/whatsapp/services/chatwoot.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index b0b07ce5..9a5fc966 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -2102,7 +2102,7 @@ export class ChatwootService { this.logger.verbose('qrcode success'); const fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64'); - const fileName = `${path.join(waInstance?.storePath, 'temp', `${`${instance}.png`}`)}`; + const fileName = `${path.join(waInstance?.storePath, 'temp', `${instance.instanceName}.png`)}`; this.logger.verbose('temp file name: ' + fileName); From a2622cb38ea24066940f2fe62e974ea7f076fdb1 Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Thu, 8 Feb 2024 13:33:53 -0300 Subject: [PATCH 10/13] Remove unused classes and imports from label dto --- src/whatsapp/dto/label.dto.ts | 109 ---------------------------------- 1 file changed, 109 deletions(-) diff --git a/src/whatsapp/dto/label.dto.ts b/src/whatsapp/dto/label.dto.ts index ab7e564c..23ff47bb 100644 --- a/src/whatsapp/dto/label.dto.ts +++ b/src/whatsapp/dto/label.dto.ts @@ -1,14 +1,3 @@ -import { proto, WAPresence, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; - -export class OnWhatsAppDto { - constructor( - public readonly jid: string, - public readonly exists: boolean, - public readonly number: string, - public readonly name?: string, - ) {} -} - export class LabelDto { id?: string; name: string; @@ -21,101 +10,3 @@ export class HandleLabelDto { labelId: string; action: 'add' | 'remove'; } - -export class WhatsAppNumberDto { - numbers: string[]; -} - -export class NumberDto { - number: string; -} - -export class NumberBusiness { - wid?: string; - jid?: string; - exists?: boolean; - isBusiness: boolean; - name?: string; - message?: string; - description?: string; - email?: string; - website?: string[]; - address?: string; -} - -export class ProfileNameDto { - name: string; -} - -export class ProfileStatusDto { - status: string; -} - -export class ProfilePictureDto { - number?: string; - // url or base64 - picture?: string; -} - -class Key { - id: string; - fromMe: boolean; - remoteJid: string; -} -export class ReadMessageDto { - read_messages: Key[]; -} - -export class LastMessage { - key: Key; - messageTimestamp?: number; -} - -export class ArchiveChatDto { - lastMessage?: LastMessage; - chat?: string; - archive: boolean; -} - -class PrivacySetting { - readreceipts: WAReadReceiptsValue; - profile: WAPrivacyValue; - status: WAPrivacyValue; - online: WAPrivacyOnlineValue; - last: WAPrivacyValue; - groupadd: WAPrivacyValue; -} - -export class PrivacySettingDto { - privacySettings: PrivacySetting; -} - -export class DeleteMessage { - id: string; - fromMe: boolean; - remoteJid: string; - participant?: string; -} -export class Options { - delay?: number; - presence?: WAPresence; -} -class OptionsMessage { - options: Options; -} -export class Metadata extends OptionsMessage { - number: string; -} - -export class SendPresenceDto extends Metadata { - options: { - presence: WAPresence; - delay: number; - }; -} - -export class UpdateMessageDto extends Metadata { - number: string; - key: proto.IMessageKey; - text: string; -} From d35d75537926bcd549d96a323f0bd2d35209d727 Mon Sep 17 00:00:00 2001 From: jaison-x Date: Mon, 5 Feb 2024 13:13:23 -0300 Subject: [PATCH 11/13] feat(chatwoot): command to clearcache from chatwoot inbox --- src/whatsapp/services/chatwoot.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index 9a5fc966..9998c6a7 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1195,6 +1195,12 @@ export class ChatwootService { } } + if (command === 'clearcache') { + this.logger.verbose('command clearcache found'); + waInstance.clearCacheChatwoot(); + await this.createBotMessage(instance, `✅ ${body.inbox.name} instance cache cleared.`, 'incoming'); + } + if (command === 'status') { this.logger.verbose('command status found'); From 945bcf5fad4a2be693fb614c27d2daa19a7961d6 Mon Sep 17 00:00:00 2001 From: jaison-x Date: Mon, 5 Feb 2024 13:13:23 -0300 Subject: [PATCH 12/13] feat(chatwoot): command to clearcache from chatwoot inbox --- src/whatsapp/services/chatwoot.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index b0b07ce5..2052cb6a 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1195,6 +1195,12 @@ export class ChatwootService { } } + if (command === 'clearcache') { + this.logger.verbose('command clearcache found'); + waInstance.clearCacheChatwoot(); + await this.createBotMessage(instance, `✅ ${body.inbox.name} instance cache cleared.`, 'incoming'); + } + if (command === 'status') { this.logger.verbose('command status found'); From 3df4e8d2ba4199760787591caaa0aa63ebbaa6d7 Mon Sep 17 00:00:00 2001 From: Judson Cairo Date: Thu, 8 Feb 2024 15:38:27 -0300 Subject: [PATCH 13/13] Changed label update to chat instead of contacts --- src/whatsapp/models/chat.model.ts | 2 + src/whatsapp/models/contact.model.ts | 2 - src/whatsapp/repository/chat.repository.ts | 59 ++++++++++++++++++++++ src/whatsapp/services/whatsapp.service.ts | 41 ++++++++------- 4 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/whatsapp/models/chat.model.ts b/src/whatsapp/models/chat.model.ts index 57a263fb..9e713a13 100644 --- a/src/whatsapp/models/chat.model.ts +++ b/src/whatsapp/models/chat.model.ts @@ -7,6 +7,7 @@ export class ChatRaw { id?: string; owner: string; lastMsgTimestamp?: number; + labels?: string[]; } type ChatRawBoolean = { @@ -18,6 +19,7 @@ const chatSchema = new Schema({ _id: { type: String, _id: true }, id: { type: String, required: true, minlength: 1 }, owner: { type: String, required: true, minlength: 1 }, + labels: { type: [String], default: [] }, }); export const ChatModel = dbserver?.model(ChatRaw.name, chatSchema, 'chats'); diff --git a/src/whatsapp/models/contact.model.ts b/src/whatsapp/models/contact.model.ts index b42dc7d5..a915bb19 100644 --- a/src/whatsapp/models/contact.model.ts +++ b/src/whatsapp/models/contact.model.ts @@ -8,7 +8,6 @@ export class ContactRaw { id?: string; profilePictureUrl?: string; owner: string; - labels?: string[]; } type ContactRawBoolean = { @@ -22,7 +21,6 @@ const contactSchema = new Schema({ id: { type: String, required: true, minlength: 1 }, profilePictureUrl: { type: String, minlength: 1 }, owner: { type: String, required: true, minlength: 1 }, - labels: { type: [String], default: [] }, }); export const ContactModel = dbserver?.model(ContactRaw.name, contactSchema, 'contacts'); diff --git a/src/whatsapp/repository/chat.repository.ts b/src/whatsapp/repository/chat.repository.ts index 99cb61e8..5f2f50ce 100644 --- a/src/whatsapp/repository/chat.repository.ts +++ b/src/whatsapp/repository/chat.repository.ts @@ -115,4 +115,63 @@ export class ChatRepository extends Repository { return { error: error?.toString() }; } } + + public async update(data: ChatRaw[], instanceName: string, saveDb = false): Promise { + try { + this.logger.verbose('updating chats'); + + if (data.length === 0) { + this.logger.verbose('no chats to update'); + return; + } + + if (this.dbSettings.ENABLED && saveDb) { + this.logger.verbose('updating chats in db'); + + const chats = data.map((chat) => { + return { + updateOne: { + filter: { id: chat.id }, + update: { ...chat }, + upsert: true, + }, + }; + }); + + const { nModified } = await this.chatModel.bulkWrite(chats); + + this.logger.verbose('chats updated in db: ' + nModified + ' chats'); + return { insertCount: nModified }; + } + + this.logger.verbose('updating chats in store'); + + const store = this.configService.get('STORE'); + + if (store.CONTACTS) { + this.logger.verbose('updating chats in store'); + data.forEach((chat) => { + this.writeStore({ + path: join(this.storePath, 'chats', instanceName), + fileName: chat.id, + data: chat, + }); + this.logger.verbose( + 'chats updated in store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id, + ); + }); + + this.logger.verbose('chats updated in store: ' + data.length + ' chats'); + + return { insertCount: data.length }; + } + + this.logger.verbose('chats not updated'); + return { insertCount: 0 }; + } catch (error) { + return error; + } finally { + data = undefined; + } + } } diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 77c8808a..9b4a11f1 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -2209,32 +2209,34 @@ export class WAStartupService { ) => { this.logger.verbose('Sending data to webhook in event LABELS_ASSOCIATION'); - // Atualiza labels no contato - const contact = await this.repository.contact.find({ - where: { - owner: this.instance.name, - id: data.association.chatId, - }, - }); - if (contact.length > 0) { - let labels = [...contact[0].labels]; - if (data.type === 'remove') { - labels = labels.filter((label) => label !== data.association.labelId); - } else if (data.type === 'add') { - labels = [...labels, data.association.labelId]; + // Atualiza labels nos chats + if (database.SAVE_DATA.CHATS) { + const chats = await this.repository.chat.find({ + where: { + owner: this.instance.name, + }, + }); + const chat = chats.find((c) => c.id === data.association.chatId); + if (chat) { + let labels = [...chat.labels]; + if (data.type === 'remove') { + labels = labels.filter((label) => label !== data.association.labelId); + } else if (data.type === 'add') { + labels = [...labels, data.association.labelId]; + } + await this.repository.chat.update( + [{ id: chat.id, owner: this.instance.name, labels }], + this.instance.name, + database.SAVE_DATA.CHATS, + ); } - await this.repository.contact.update( - [{ ...contact[0], labels }], - this.instance.name, - database.SAVE_DATA.CONTACTS, - ); } // Envia dados para o webhook this.sendDataWebhook(Events.LABELS_ASSOCIATION, { instance: this.instance.name, type: data.type, - jid: data.association.chatId, + chatId: data.association.chatId, labelId: data.association.labelId, }); }, @@ -2244,6 +2246,7 @@ export class WAStartupService { this.logger.verbose('Initializing event handler'); this.client.ev.process(async (events) => { if (!this.endSession) { + this.logger.verbose(`Event received: ${Object.keys(events).join(', ')}`); const database = this.configService.get('DATABASE'); const settings = await this.findSettings();