From 8f4d44a21212cf3d075b93c3cb2fae5a6acdaf88 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Fri, 21 Jul 2023 20:37:58 -0300 Subject: [PATCH 01/24] Added connection functionality via pairing code --- CHANGELOG.md | 6 +++++ package.json | 2 +- src/validate/validate.schema.ts | 1 + .../controllers/instance.controller.ts | 22 +++++++++++++++++-- src/whatsapp/dto/instance.dto.ts | 1 + src/whatsapp/routers/instance.router.ts | 3 ++- src/whatsapp/services/whatsapp.service.ts | 5 +++++ src/whatsapp/types/wa.types.ts | 6 ++++- 8 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1be220..8d56ec43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.4.0 (homolog) + +### Features + +* Added connection functionality via pairing code + # 1.3.2 (2023-07-21 17:19) ### Fixed diff --git a/package.json b/package.json index 7bc6f476..92745ead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "1.3.2", + "version": "1.4.0", "description": "Rest api for communication with WhatsApp", "main": "./dist/src/main.js", "scripts": { diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 2b9ce19a..ed7ab2e0 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -58,6 +58,7 @@ export const instanceNameSchema: JSONSchema7 = { }, }, qrcode: { type: 'boolean', enum: [true, false] }, + number: { type: 'string', pattern: '^\\d+[\\.@\\w-]+' }, token: { type: 'string' }, }, ...isNotEmpty('instanceName'), diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 75911848..b54858b2 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -34,6 +34,7 @@ export class InstanceController { webhook_by_events, events, qrcode, + number, token, chatwoot_account_id, chatwoot_token, @@ -102,10 +103,16 @@ export class InstanceController { if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { let getQrcode: wa.QrCode; + let getParingCode: string; if (qrcode) { this.logger.verbose('creating qrcode'); await instance.connectToWhatsapp(); + if (number) { + this.logger.verbose('creating number'); + await delay(5000); + getParingCode = await instance.client.requestPairingCode(number); + } await delay(2000); getQrcode = instance.qrCode; } @@ -120,6 +127,7 @@ export class InstanceController { webhook, webhook_by_events, events: getEvents, + pairingCode: getParingCode, qrcode: getQrcode, }); @@ -132,6 +140,7 @@ export class InstanceController { webhook, webhook_by_events, events: getEvents, + pairingCode: getParingCode, qrcode: getQrcode, }; } @@ -195,7 +204,7 @@ export class InstanceController { }; } - public async connectToWhatsapp({ instanceName }: InstanceDto) { + public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { try { this.logger.verbose( 'requested connectToWhatsapp from ' + instanceName + ' instance', @@ -210,8 +219,17 @@ export class InstanceController { case 'close': this.logger.verbose('connecting'); await instance.connectToWhatsapp(); + let pairingCode = null; + if (number) { + this.logger.verbose('creating pairing code'); + await delay(5000); + pairingCode = await instance.client.requestPairingCode(number); + } await delay(2000); - return instance.qrCode; + return { + pairingCode, + ...instance.qrCode, + }; case 'connecting': return instance.qrCode; default: diff --git a/src/whatsapp/dto/instance.dto.ts b/src/whatsapp/dto/instance.dto.ts index ce282e03..9e8a7ec3 100644 --- a/src/whatsapp/dto/instance.dto.ts +++ b/src/whatsapp/dto/instance.dto.ts @@ -4,6 +4,7 @@ export class InstanceDto { webhook_by_events?: boolean; events?: string[]; qrcode?: boolean; + number?: string; token?: string; chatwoot_account_id?: string; chatwoot_token?: string; diff --git a/src/whatsapp/routers/instance.router.ts b/src/whatsapp/routers/instance.router.ts index 850ffebd..a984a89c 100644 --- a/src/whatsapp/routers/instance.router.ts +++ b/src/whatsapp/routers/instance.router.ts @@ -60,7 +60,8 @@ export class InstanceRouter extends RouterBroker { request: req, schema: instanceNameSchema, ClassRef: InstanceDto, - execute: (instance) => instanceController.connectToWhatsapp(instance), + execute: (instance, data) => + instanceController.connectToWhatsapp(instance, data), }); return res.status(HttpStatus.OK).json(response); diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 3851f067..ab94d691 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -545,6 +545,11 @@ export class WAStartupService { return this.eventEmitter.emit('no.connection', this.instance.name); } + // pairing code + // await delay(5000); + // const code = await this.client.requestPairingCode('557499879409'); + // console.log(`Pairing code: ${code}`); + this.logger.verbose('Incrementing QR code count'); this.instance.qrcode.count++; diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index 6869545f..fc71de31 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -25,7 +25,11 @@ export enum Events { } export declare namespace wa { - export type QrCode = { count?: number; base64?: string; code?: string }; + export type QrCode = { + count?: number; + base64?: string; + code?: string; + }; export type Instance = { qrcode?: QrCode; authState?: { state: AuthenticationState; saveCreds: () => void }; From b681e339444859ba94f6d3d9432642873bb54a07 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Fri, 21 Jul 2023 20:38:10 -0300 Subject: [PATCH 02/24] Added connection functionality via pairing code --- src/whatsapp/routers/instance.router.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/whatsapp/routers/instance.router.ts b/src/whatsapp/routers/instance.router.ts index a984a89c..850ffebd 100644 --- a/src/whatsapp/routers/instance.router.ts +++ b/src/whatsapp/routers/instance.router.ts @@ -60,8 +60,7 @@ export class InstanceRouter extends RouterBroker { request: req, schema: instanceNameSchema, ClassRef: InstanceDto, - execute: (instance, data) => - instanceController.connectToWhatsapp(instance, data), + execute: (instance) => instanceController.connectToWhatsapp(instance), }); return res.status(HttpStatus.OK).json(response); From 16ed5821e277a4128026bcf8137448dfbc5db9da Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Fri, 21 Jul 2023 20:58:37 -0300 Subject: [PATCH 03/24] Added connection functionality via pairing code --- .../controllers/instance.controller.ts | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index b54858b2..9b938ab0 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -103,7 +103,7 @@ export class InstanceController { if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { let getQrcode: wa.QrCode; - let getParingCode: string; + let getPairingCode: string; if (qrcode) { this.logger.verbose('creating qrcode'); @@ -111,14 +111,13 @@ export class InstanceController { if (number) { this.logger.verbose('creating number'); await delay(5000); - getParingCode = await instance.client.requestPairingCode(number); + getPairingCode = await instance.client.requestPairingCode(number); } await delay(2000); getQrcode = instance.qrCode; } - this.logger.verbose('instance created'); - this.logger.verbose({ + const result = { instance: { instanceName: instance.instanceName, status: 'created', @@ -127,22 +126,18 @@ export class InstanceController { webhook, webhook_by_events, events: getEvents, - pairingCode: getParingCode, - qrcode: getQrcode, - }); - - return { - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook, - webhook_by_events, - events: getEvents, - pairingCode: getParingCode, - qrcode: getQrcode, }; + + if (getPairingCode) { + result['pairingCode'] = getPairingCode; + } else { + result['qrcode'] = getQrcode; + } + + this.logger.verbose('instance created'); + this.logger.verbose(result); + + return result; } if (!chatwoot_account_id) { @@ -225,11 +220,15 @@ export class InstanceController { await delay(5000); pairingCode = await instance.client.requestPairingCode(number); } + + if (pairingCode) { + return { + pairingCode, + }; + } + await delay(2000); - return { - pairingCode, - ...instance.qrCode, - }; + return instance.qrCode; case 'connecting': return instance.qrCode; default: From 90048afa9d1bb700380c3b28497241a8a386607a Mon Sep 17 00:00:00 2001 From: Alan Mosko Date: Sun, 23 Jul 2023 11:24:16 -0300 Subject: [PATCH 04/24] Add LinkPreview Option --- src/whatsapp/dto/sendMessage.dto.ts | 1 + src/whatsapp/services/whatsapp.service.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/whatsapp/dto/sendMessage.dto.ts b/src/whatsapp/dto/sendMessage.dto.ts index 51a55cec..0a20674c 100644 --- a/src/whatsapp/dto/sendMessage.dto.ts +++ b/src/whatsapp/dto/sendMessage.dto.ts @@ -15,6 +15,7 @@ export class Options { presence?: WAPresence; quoted?: Quoted; mentions?: Mentions; + linkPreview?: boolean; } class OptionsMessage { options: Options; diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 3851f067..dd8fa3a5 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1480,6 +1480,8 @@ export class WAStartupService { this.logger.verbose('Sending presence update: paused'); } + let linkPreview = (options?.linkPreview != false) ? undefined : false; + let quoted: WAMessage; if (options?.quoted) { @@ -1573,6 +1575,7 @@ export class WAStartupService { { text: message['conversation'], mentions, + linkPreview: linkPreview, } as unknown as AnyMessageContent, option as unknown as MiscMessageGenerationOptions, ); From 798eb90bed2873be514135512b75d14869777b09 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Sun, 23 Jul 2023 22:24:21 -0300 Subject: [PATCH 05/24] feat: Added fetch profile endpoint in chat controller and link preview option in send text message --- CHANGELOG.md | 5 + src/validate/validate.schema.ts | 11 +++ src/whatsapp/controllers/chat.controller.ts | 8 ++ .../controllers/instance.controller.ts | 57 ++++++----- src/whatsapp/dto/chat.dto.ts | 13 +++ src/whatsapp/routers/chat.router.ts | 18 ++++ src/whatsapp/services/whatsapp.service.ts | 99 ++++++++++++++++--- 7 files changed, 176 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d56ec43..cd9ec53d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ### Features * Added connection functionality via pairing code +* Added fetch profile endpoint in chat controller + +### Fixed + +* Added link preview option in send text message # 1.3.2 (2023-07-21 17:19) diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index ed7ab2e0..30763fcd 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -588,6 +588,17 @@ export const profilePictureSchema: JSONSchema7 = { }, }; +export const profileSchema: JSONSchema7 = { + type: 'object', + properties: { + wuid: { type: 'string' }, + name: { type: 'string' }, + picture: { type: 'string' }, + status: { type: 'string' }, + isBusiness: { type: 'boolean' }, + }, +}; + export const messageValidateSchema: JSONSchema7 = { $id: v4(), type: 'object', diff --git a/src/whatsapp/controllers/chat.controller.ts b/src/whatsapp/controllers/chat.controller.ts index 0a176059..454ddabf 100644 --- a/src/whatsapp/controllers/chat.controller.ts +++ b/src/whatsapp/controllers/chat.controller.ts @@ -48,6 +48,14 @@ export class ChatController { return await this.waMonitor.waInstances[instanceName].profilePicture(data.number); } + public async fetchProfile({ instanceName }: InstanceDto, data: NumberDto) { + logger.verbose('requested fetchProfile from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchProfile( + instanceName, + data.number, + ); + } + public async fetchContacts({ instanceName }: InstanceDto, query: ContactQuery) { logger.verbose('requested fetchContacts from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].fetchContacts(query); diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 9b938ab0..e7827f15 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -210,30 +210,41 @@ export class InstanceController { this.logger.verbose('state: ' + state); - switch (state) { - case 'close': - this.logger.verbose('connecting'); - await instance.connectToWhatsapp(); - let pairingCode = null; - if (number) { - this.logger.verbose('creating pairing code'); - await delay(5000); - pairingCode = await instance.client.requestPairingCode(number); - } - - if (pairingCode) { - return { - pairingCode, - }; - } - - await delay(2000); - return instance.qrCode; - case 'connecting': - return instance.qrCode; - default: - return await this.connectionState({ instanceName }); + if (state == 'open') { + return await this.connectionState({ instanceName }); } + + if (state == 'connecting') { + return instance.qrCode; + } + + if (state == 'close') { + this.logger.verbose('connecting'); + await instance.connectToWhatsapp(); + let pairingCode = null; + if (number) { + this.logger.verbose('creating pairing code'); + await delay(5000); + pairingCode = await instance.client.requestPairingCode(number); + } + + if (pairingCode) { + return { + pairingCode, + }; + } + + await delay(2000); + return instance.qrCode; + } + + return { + instance: { + instanceName: instanceName, + status: state, + }, + qrcode: instance?.qrCode, + }; } catch (error) { this.logger.error(error); } diff --git a/src/whatsapp/dto/chat.dto.ts b/src/whatsapp/dto/chat.dto.ts index 07757194..5af66a5e 100644 --- a/src/whatsapp/dto/chat.dto.ts +++ b/src/whatsapp/dto/chat.dto.ts @@ -26,6 +26,19 @@ 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; } diff --git a/src/whatsapp/routers/chat.router.ts b/src/whatsapp/routers/chat.router.ts index 50ead521..49e64117 100644 --- a/src/whatsapp/routers/chat.router.ts +++ b/src/whatsapp/routers/chat.router.ts @@ -8,6 +8,7 @@ import { privacySettingsSchema, profileNameSchema, profilePictureSchema, + profileSchema, profileStatusSchema, readMessageSchema, whatsappNumberSchema, @@ -129,6 +130,23 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) + .post(this.routerPath('fetchProfile'), ...guards, async (req, res) => { + logger.verbose('request received in fetchProfile'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profileSchema, + ClassRef: NumberDto, + execute: (instance, data) => chatController.fetchProfile(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) .post(this.routerPath('findContacts'), ...guards, async (req, res) => { logger.verbose('request received in findContacts'); logger.verbose('request body: '); diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 3f720dff..128183da 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -84,6 +84,7 @@ import { arrayUnique, isBase64, isURL } from 'class-validator'; import { ArchiveChatDto, DeleteMessage, + NumberBusiness, OnWhatsAppDto, PrivacySettingDto, ReadMessageDto, @@ -1449,6 +1450,78 @@ export class WAStartupService { } } + public async getStatus(number: string) { + const jid = this.createJid(number); + + this.logger.verbose('Getting profile status with jid:' + jid); + try { + this.logger.verbose('Getting status'); + return { + wuid: jid, + status: (await this.client.fetchStatus(jid))?.status, + }; + } catch (error) { + this.logger.verbose('Status not found'); + return { + wuid: jid, + status: null, + }; + } + } + + public async fetchProfile(instanceName: string, number?: string) { + const jid = number ? this.createJid(number) : this.client?.user?.id; + + this.logger.verbose('Getting profile with jid: ' + jid); + try { + this.logger.verbose('Getting profile info'); + const info = await waMonitor.instanceInfo(instanceName); + const business = await this.fetchBusinessProfile(jid); + + if (number) { + const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); + const picture = await this.profilePicture(jid); + const status = await this.getStatus(jid); + + return { + wuid: jid, + name: info?.name, + numberExists: info?.exists, + picture: picture?.profilePictureUrl, + status: status?.status, + isBusiness: business.isBusiness, + email: business?.email, + description: business?.description, + website: business?.website?.shift(), + }; + } else { + const info = await waMonitor.instanceInfo(instanceName); + + return { + wuid: jid, + name: info?.instance?.profileName, + numberExists: true, + picture: info?.instance?.profilePictureUrl, + status: info?.instance?.profileStatus, + isBusiness: business.isBusiness, + email: business?.email, + description: business?.description, + website: business?.website?.shift(), + }; + } + } catch (error) { + this.logger.verbose('Profile not found'); + return { + wuid: jid, + name: null, + picture: null, + status: null, + os: null, + isBusiness: false, + }; + } + } + private async sendMessageWithTyping( number: string, message: T, @@ -1485,7 +1558,9 @@ export class WAStartupService { this.logger.verbose('Sending presence update: paused'); } - let linkPreview = (options?.linkPreview != false) ? undefined : false; + const linkPreview = options?.linkPreview != false ? undefined : false; + + console.log('linkPreview', linkPreview); let quoted: WAMessage; @@ -2468,29 +2543,29 @@ export class WAStartupService { } } - public async fetchBusinessProfile(number: string) { + public async fetchBusinessProfile(number: string): Promise { this.logger.verbose('Fetching business profile'); try { - let jid; - - if (!number) { - jid = this.instance.wuid; - } else { - jid = this.createJid(number); - } + const jid = number ? this.createJid(number) : this.instance.wuid; const profile = await this.client.getBusinessProfile(jid); this.logger.verbose('Trying to get business profile'); if (!profile) { + const info = await this.whatsappNumber({ numbers: [jid] }); + return { - exists: false, - message: 'Business profile not found', + isBusiness: false, + message: 'Not is business profile', + ...info?.shift(), }; } this.logger.verbose('Business profile fetched'); - return profile; + return { + isBusiness: true, + ...profile, + }; } catch (error) { throw new InternalServerErrorException( 'Error updating profile name', From be699d24a1d31b17670293564b1c937d1255fc4b Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Sun, 23 Jul 2023 22:26:38 -0300 Subject: [PATCH 06/24] feat: Added fetch profile endpoint in chat controller and link preview option in send text message --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd9ec53d..fed20ae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ * Added link preview option in send text message +### Integrations + +- Chatwoot: v2.18.0 + # 1.3.2 (2023-07-21 17:19) ### Fixed @@ -21,12 +25,20 @@ * For compatibility reasons, container mode has been removed * Added docker-compose files example +### Integrations + +- Chatwoot: v2.18.0 + # 1.3.1 (2023-07-20 07:48) ### Fixed * Adjust in create store files +### Integrations + +- Chatwoot: v2.18.0 + # 1.3.0 (2023-07-19 11:33) ### Features From 73d9cd62a56d56c070c54ccf39134b6e63f9a6c5 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 09:42:29 -0300 Subject: [PATCH 07/24] feat: Created settings Controller --- CHANGELOG.md | 1 + src/validate/validate.schema.ts | 12 +++ .../controllers/settings.controller.ts | 29 +++++++ src/whatsapp/dto/settings.dto.ts | 5 ++ src/whatsapp/models/index.ts | 1 + src/whatsapp/models/settings.model.ts | 23 ++++++ src/whatsapp/repository/repository.manager.ts | 8 +- .../repository/settings.repository.ts | 75 +++++++++++++++++++ src/whatsapp/routers/index.router.ts | 4 +- src/whatsapp/routers/settings.router.ts | 52 +++++++++++++ src/whatsapp/services/settings.service.ts | 34 +++++++++ src/whatsapp/services/whatsapp.service.ts | 43 +++++++++++ src/whatsapp/types/wa.types.ts | 6 ++ src/whatsapp/whatsapp.module.ts | 10 +++ 14 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 src/whatsapp/controllers/settings.controller.ts create mode 100644 src/whatsapp/dto/settings.dto.ts create mode 100644 src/whatsapp/models/settings.model.ts create mode 100644 src/whatsapp/repository/settings.repository.ts create mode 100644 src/whatsapp/routers/settings.router.ts create mode 100644 src/whatsapp/services/settings.service.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fed20ae3..ea7401df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Added connection functionality via pairing code * Added fetch profile endpoint in chat controller +* Created settings controller ### Fixed diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 30763fcd..a2a8ef13 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -877,3 +877,15 @@ export const chatwootSchema: JSONSchema7 = { required: ['enabled', 'account_id', 'token', 'url', 'sign_msg'], ...isNotEmpty('account_id', 'token', 'url', 'sign_msg'), }; + +export const settingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + reject_call: { type: 'boolean', enum: [true, false] }, + msg_call: { type: 'string' }, + groups_ignore: { type: 'boolean', enum: [true, false] }, + }, + required: ['reject_call'], + ...isNotEmpty('reject_call'), +}; diff --git a/src/whatsapp/controllers/settings.controller.ts b/src/whatsapp/controllers/settings.controller.ts new file mode 100644 index 00000000..59031634 --- /dev/null +++ b/src/whatsapp/controllers/settings.controller.ts @@ -0,0 +1,29 @@ +import { isURL } from 'class-validator'; +import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { SettingsService } from '../services/settings.service'; +import { Logger } from '../../config/logger.config'; + +const logger = new Logger('SettingsController'); + +export class SettingsController { + constructor(private readonly settingsService: SettingsService) {} + + public async createSettings(instance: InstanceDto, data: SettingsDto) { + logger.verbose( + 'requested createSettings from ' + instance.instanceName + ' instance', + ); + + if (data.reject_call && data.msg_call.trim() == '') { + throw new BadRequestException('msg_call is required'); + } + + return this.settingsService.create(instance, data); + } + + public async findSettings(instance: InstanceDto) { + logger.verbose('requested findSettings from ' + instance.instanceName + ' instance'); + return this.settingsService.find(instance); + } +} diff --git a/src/whatsapp/dto/settings.dto.ts b/src/whatsapp/dto/settings.dto.ts new file mode 100644 index 00000000..20a6cba0 --- /dev/null +++ b/src/whatsapp/dto/settings.dto.ts @@ -0,0 +1,5 @@ +export class SettingsDto { + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; +} diff --git a/src/whatsapp/models/index.ts b/src/whatsapp/models/index.ts index e0b773f0..e6c6d8b4 100644 --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -4,3 +4,4 @@ export * from './message.model'; export * from './auth.model'; export * from './webhook.model'; export * from './chatwoot.model'; +export * from './settings.model'; diff --git a/src/whatsapp/models/settings.model.ts b/src/whatsapp/models/settings.model.ts new file mode 100644 index 00000000..b5eb7fe7 --- /dev/null +++ b/src/whatsapp/models/settings.model.ts @@ -0,0 +1,23 @@ +import { Schema } from 'mongoose'; +import { dbserver } from '../../db/db.connect'; + +export class SettingsRaw { + _id?: string; + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; +} + +const settingsSchema = new Schema({ + _id: { type: String, _id: true }, + reject_call: { type: Boolean, required: true }, + msg_call: { type: String, required: true }, + groups_ignore: { type: Boolean, required: true }, +}); + +export const SettingsModel = dbserver?.model( + SettingsRaw.name, + settingsSchema, + 'settings', +); +export type ISettingsModel = typeof SettingsModel; diff --git a/src/whatsapp/repository/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts index 6c2a3091..dde636c7 100644 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -5,10 +5,10 @@ import { MessageUpRepository } from './messageUp.repository'; import { MongoClient } from 'mongodb'; import { WebhookRepository } from './webhook.repository'; import { ChatwootRepository } from './chatwoot.repository'; +import { SettingsRepository } from './settings.repository'; import { AuthRepository } from './auth.repository'; import { Auth, ConfigService, Database } from '../../config/env.config'; -import { execSync } from 'child_process'; import { join } from 'path'; import fs from 'fs'; import { Logger } from '../../config/logger.config'; @@ -20,6 +20,7 @@ export class RepositoryBroker { public readonly messageUpdate: MessageUpRepository, public readonly webhook: WebhookRepository, public readonly chatwoot: ChatwootRepository, + public readonly settings: SettingsRepository, public readonly auth: AuthRepository, private configService: ConfigService, dbServer?: MongoClient, @@ -53,6 +54,7 @@ export class RepositoryBroker { const messageUpDir = join(storePath, 'message-up'); const webhookDir = join(storePath, 'webhook'); const chatwootDir = join(storePath, 'chatwoot'); + const settingsDir = join(storePath, 'settings'); const tempDir = join(storePath, 'temp'); if (!fs.existsSync(authDir)) { @@ -83,6 +85,10 @@ export class RepositoryBroker { this.logger.verbose('creating chatwoot dir: ' + chatwootDir); fs.mkdirSync(chatwootDir, { recursive: true }); } + if (!fs.existsSync(settingsDir)) { + this.logger.verbose('creating settings dir: ' + settingsDir); + fs.mkdirSync(settingsDir, { recursive: true }); + } if (!fs.existsSync(tempDir)) { this.logger.verbose('creating temp dir: ' + tempDir); fs.mkdirSync(tempDir, { recursive: true }); diff --git a/src/whatsapp/repository/settings.repository.ts b/src/whatsapp/repository/settings.repository.ts new file mode 100644 index 00000000..d253643d --- /dev/null +++ b/src/whatsapp/repository/settings.repository.ts @@ -0,0 +1,75 @@ +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ConfigService } from '../../config/env.config'; +import { join } from 'path'; +import { readFileSync } from 'fs'; +import { ISettingsModel, SettingsRaw } from '../models'; +import { Logger } from '../../config/logger.config'; + +export class SettingsRepository extends Repository { + constructor( + private readonly settingsModel: ISettingsModel, + private readonly configService: ConfigService, + ) { + super(configService); + } + + private readonly logger = new Logger('SettingsRepository'); + + public async create(data: SettingsRaw, instance: string): Promise { + try { + this.logger.verbose('creating settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving settings to db'); + const insert = await this.settingsModel.replaceOne( + { _id: instance }, + { ...data }, + { upsert: true }, + ); + + this.logger.verbose( + 'settings saved to db: ' + insert.modifiedCount + ' settings', + ); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving settings to store'); + + this.writeStore({ + path: join(this.storePath, 'settings'), + fileName: instance, + data, + }); + + this.logger.verbose( + 'settings saved to store in path: ' + + join(this.storePath, 'settings') + + '/' + + instance, + ); + + this.logger.verbose('settings created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding settings in db'); + return await this.settingsModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding settings in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'settings', instance + '.json'), { + encoding: 'utf-8', + }), + ) as SettingsRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/routers/index.router.ts b/src/whatsapp/routers/index.router.ts index 5d8a2c05..4cf7befb 100644 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -10,6 +10,7 @@ import { ViewsRouter } from './view.router'; import { WebhookRouter } from './webhook.router'; import { ChatwootRouter } from './chatwoot.router'; import fs from 'fs'; +import { SettingsRouter } from './settings.router'; enum HttpStatus { OK = 200, @@ -44,6 +45,7 @@ router .use('/chat', new ChatRouter(...guards).router) .use('/group', new GroupRouter(...guards).router) .use('/webhook', new WebhookRouter(...guards).router) - .use('/chatwoot', new ChatwootRouter(...guards).router); + .use('/chatwoot', new ChatwootRouter(...guards).router) + .use('/settings', new SettingsRouter(...guards).router); export { router, HttpStatus }; diff --git a/src/whatsapp/routers/settings.router.ts b/src/whatsapp/routers/settings.router.ts new file mode 100644 index 00000000..3ec3df83 --- /dev/null +++ b/src/whatsapp/routers/settings.router.ts @@ -0,0 +1,52 @@ +import { RequestHandler, Router } from 'express'; +import { instanceNameSchema, settingsSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { settingsController } from '../whatsapp.module'; +import { SettingsService } from '../services/settings.service'; +import { HttpStatus } from './index.router'; +import { Logger } from '../../config/logger.config'; + +const logger = new Logger('SettingsRouter'); + +export class SettingsRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setSettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: settingsSchema, + ClassRef: SettingsDto, + execute: (instance, data) => settingsController.createSettings(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findSettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => settingsController.findSettings(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/services/settings.service.ts b/src/whatsapp/services/settings.service.ts new file mode 100644 index 00000000..9a82046a --- /dev/null +++ b/src/whatsapp/services/settings.service.ts @@ -0,0 +1,34 @@ +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { WAMonitoringService } from './monitor.service'; +import { Logger } from '../../config/logger.config'; + +export class SettingsService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(SettingsService.name); + + public create(instance: InstanceDto, data: SettingsDto) { + this.logger.verbose('create settings: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setSettings(data); + + return { settings: { ...instance, settings: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find settings: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[ + instance.instanceName + ].findSettings(); + + if (Object.keys(result).length === 0) { + throw new Error('Settings not found'); + } + + return result; + } catch (error) { + return { reject_call: false, msg_call: '', groups_ignore: false }; + } + } +} diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 128183da..27019883 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -125,6 +125,7 @@ import { Log } from '../../config/env.config'; import ProxyAgent from 'proxy-agent'; import { ChatwootService } from './chatwoot.service'; import { waMonitor } from '../whatsapp.module'; +import { SettingsRaw } from '../models'; export class WAStartupService { constructor( @@ -143,6 +144,7 @@ export class WAStartupService { public client: WASocket; private readonly localWebhook: wa.LocalWebHook = {}; private readonly localChatwoot: wa.LocalChatwoot = {}; + private readonly localSettings: wa.LocalSettings = {}; private stateConnection: wa.StateConnection = { state: 'close' }; public readonly storePath = join(ROOT_DIR, 'store'); private readonly msgRetryCounterCache: CacheStore = new NodeCache(); @@ -341,6 +343,46 @@ export class WAStartupService { return data; } + private async loadSettings() { + this.logger.verbose('Loading settings'); + const data = await this.repository.settings.find(this.instanceName); + this.localSettings.reject_call = data?.reject_call; + this.logger.verbose(`Settings reject_call: ${this.localSettings.reject_call}`); + + this.localSettings.msg_call = data?.msg_call; + this.logger.verbose(`Settings msg_call: ${this.localSettings.msg_call}`); + + this.localSettings.groups_ignore = data?.groups_ignore; + this.logger.verbose(`Settings groups_ignore: ${this.localSettings.groups_ignore}`); + + this.logger.verbose('Settings loaded'); + } + + public async setSettings(data: SettingsRaw) { + this.logger.verbose('Setting settings'); + await this.repository.settings.create(data, this.instanceName); + this.logger.verbose(`Settings reject_call: ${data.reject_call}`); + this.logger.verbose(`Settings msg_call: ${data.msg_call}`); + this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); + Object.assign(this.localSettings, data); + this.logger.verbose('Settings set'); + } + + public async findSettings() { + this.logger.verbose('Finding settings'); + const data = await this.repository.settings.find(this.instanceName); + + if (!data) { + this.logger.verbose('Settings not found'); + throw new NotFoundException('Settings not found'); + } + + this.logger.verbose(`Settings url: ${data.reject_call}`); + this.logger.verbose(`Settings msg_call: ${data.msg_call}`); + this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); + return data; + } + public async sendDataWebhook(event: Events, data: T, local = true) { const webhookGlobal = this.configService.get('WEBHOOK'); const webhookLocal = this.localWebhook.events; @@ -761,6 +803,7 @@ export class WAStartupService { try { this.loadWebhook(); this.loadChatwoot(); + this.loadSettings(); this.instance.authState = await this.defineAuthState(); diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index fc71de31..4b699d7e 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -55,6 +55,12 @@ export declare namespace wa { sign_msg?: boolean; }; + export type LocalSettings = { + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + }; + export type StateConnection = { instance?: string; state?: WAConnectionState | 'refused'; diff --git a/src/whatsapp/whatsapp.module.ts b/src/whatsapp/whatsapp.module.ts index c91ee9c4..9f2fed00 100644 --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -25,6 +25,7 @@ import { MessageUpModel, ChatwootModel, WebhookModel, + SettingsModel, } from './models'; import { dbserver } from '../db/db.connect'; import { WebhookRepository } from './repository/webhook.repository'; @@ -34,6 +35,9 @@ import { WAStartupService } from './services/whatsapp.service'; import { delay } from '@whiskeysockets/baileys'; import { Events } from './types/wa.types'; import { RedisCache } from '../db/redis.client'; +import { SettingsRepository } from './repository/settings.repository'; +import { SettingsService } from './services/settings.service'; +import { SettingsController } from './controllers/settings.controller'; const logger = new Logger('WA MODULE'); @@ -43,6 +47,7 @@ const contactRepository = new ContactRepository(ContactModel, configService); const messageUpdateRepository = new MessageUpRepository(MessageUpModel, configService); const webhookRepository = new WebhookRepository(WebhookModel, configService); const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); +const settingsRepository = new SettingsRepository(SettingsModel, configService); const authRepository = new AuthRepository(AuthModel, configService); export const repository = new RepositoryBroker( @@ -52,6 +57,7 @@ export const repository = new RepositoryBroker( messageUpdateRepository, webhookRepository, chatwootRepository, + settingsRepository, authRepository, configService, dbserver?.getClient(), @@ -76,6 +82,10 @@ const chatwootService = new ChatwootService(waMonitor, configService); export const chatwootController = new ChatwootController(chatwootService, configService); +const settingsService = new SettingsService(waMonitor); + +export const settingsController = new SettingsController(settingsService); + export const instanceController = new InstanceController( waMonitor, configService, From 1fcbd4f9fd6f6f3024baf8bb7a3f3af7179dc56b Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 10:18:16 -0300 Subject: [PATCH 08/24] feat: Added reject call and send text message when receiving a call and Added setting to ignore group messages --- CHANGELOG.md | 4 +- src/whatsapp/services/whatsapp.service.ts | 76 +++++++++++++++++------ 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7401df..4fc020a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * Added connection functionality via pairing code * Added fetch profile endpoint in chat controller * Created settings controller +* Added reject call and send text message when receiving a call +* Added setting to ignore group messages ### Fixed @@ -12,7 +14,7 @@ ### Integrations -- Chatwoot: v2.18.0 +- Chatwoot: v2.18.0 - v3.0.0 (Beta) # 1.3.2 (2023-07-21 17:19) diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 27019883..d3bed078 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1086,6 +1086,7 @@ export class WAStartupService { type: MessageUpsertType; }, database: Database, + settings: SettingsRaw, ) => { this.logger.verbose('Event received: messages.upsert'); const received = messages[0]; @@ -1103,6 +1104,11 @@ export class WAStartupService { received.messageTimestamp = received.messageTimestamp?.toNumber(); } + if (settings.groups_ignore && received.key.remoteJid.includes('@g.us')) { + this.logger.verbose('group ignored'); + return; + } + const messageRaw: MessageRaw = { key: received.key, pushName: received.pushName, @@ -1194,7 +1200,11 @@ export class WAStartupService { ); }, - 'messages.update': async (args: WAMessageUpdate[], database: Database) => { + 'messages.update': async ( + args: WAMessageUpdate[], + database: Database, + settings: SettingsRaw, + ) => { this.logger.verbose('Event received: messages.update'); const status: Record = { 0: 'ERROR', @@ -1205,6 +1215,10 @@ export class WAStartupService { 5: 'PLAYED', }; for await (const { key, update } of args) { + if (settings.groups_ignore && key.remoteJid.includes('@g.us')) { + this.logger.verbose('group ignored'); + return; + } if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) { this.logger.verbose('Message update is valid'); @@ -1304,9 +1318,28 @@ export class WAStartupService { private eventHandler() { this.logger.verbose('Initializing event handler'); - this.client.ev.process((events) => { + this.client.ev.process(async (events) => { if (!this.endSession) { const database = this.configService.get('DATABASE'); + const settings = await this.findSettings(); + + if (events.call) { + this.logger.verbose('Listening event: call'); + console.log('events.call', events.call); + const call = events.call[0]; + + if (settings?.reject_call && call.status == 'offer') { + this.logger.verbose('Rejecting call'); + this.client.rejectCall(call.id, call.from); + } + + if (settings?.msg_call.trim().length > 0 && call.status == 'offer') { + this.logger.verbose('Sending message in call'); + this.client.sendMessage(call.from, { + text: settings.msg_call, + }); + } + } if (events['connection.update']) { this.logger.verbose('Listening event: connection.update'); @@ -1327,37 +1360,44 @@ export class WAStartupService { if (events['messages.upsert']) { this.logger.verbose('Listening event: messages.upsert'); const payload = events['messages.upsert']; - this.messageHandle['messages.upsert'](payload, database); + this.messageHandle['messages.upsert'](payload, database, settings); } if (events['messages.update']) { this.logger.verbose('Listening event: messages.update'); const payload = events['messages.update']; - this.messageHandle['messages.update'](payload, database); + this.messageHandle['messages.update'](payload, database, settings); } if (events['presence.update']) { this.logger.verbose('Listening event: presence.update'); const payload = events['presence.update']; + + if (settings.groups_ignore && payload.id.includes('@g.us')) { + this.logger.verbose('group ignored'); + return; + } this.sendDataWebhook(Events.PRESENCE_UPDATE, payload); } - if (events['groups.upsert']) { - this.logger.verbose('Listening event: groups.upsert'); - const payload = events['groups.upsert']; - this.groupHandler['groups.upsert'](payload); - } + if (!settings?.groups_ignore) { + if (events['groups.upsert']) { + this.logger.verbose('Listening event: groups.upsert'); + const payload = events['groups.upsert']; + this.groupHandler['groups.upsert'](payload); + } - if (events['groups.update']) { - this.logger.verbose('Listening event: groups.update'); - const payload = events['groups.update']; - this.groupHandler['groups.update'](payload); - } + if (events['groups.update']) { + this.logger.verbose('Listening event: groups.update'); + const payload = events['groups.update']; + this.groupHandler['groups.update'](payload); + } - if (events['group-participants.update']) { - this.logger.verbose('Listening event: group-participants.update'); - const payload = events['group-participants.update']; - this.groupHandler['group-participants.update'](payload); + if (events['group-participants.update']) { + this.logger.verbose('Listening event: group-participants.update'); + const payload = events['group-participants.update']; + this.groupHandler['group-participants.update'](payload); + } } if (events['chats.upsert']) { From fd82aa143c421993f6e372aadc2b9b0fa49ce1f4 Mon Sep 17 00:00:00 2001 From: Alan Mosko Date: Mon, 24 Jul 2023 11:10:32 -0300 Subject: [PATCH 09/24] wip --- src/validate/validate.schema.ts | 2 +- src/whatsapp/services/whatsapp.service.ts | 56 +++++++++-------------- 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 2b9ce19a..f3602582 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -123,7 +123,7 @@ const optionsSchema: JSONSchema7 = { const numberDefinition: JSONSchema7Definition = { type: 'string', - pattern: '^\\d+[\\.@\\w-]+', + // pattern: '^\\d+[\\.@\\w-]+', description: 'Invalid format', }; diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 3851f067..340cf5a6 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1380,7 +1380,7 @@ export class WAStartupService { private createJid(number: string): string { this.logger.verbose('Creating jid with number: ' + number); - + if (number.includes('@g.us') || number.includes('@s.whatsapp.net')) { this.logger.verbose('Number already contains @g.us or @s.whatsapp.net'); return number; @@ -1390,37 +1390,23 @@ export class WAStartupService { this.logger.verbose('Number already contains @broadcast'); return number; } - - const countryCode = number.substring(0, 2); - - if (Number(countryCode) === 55) { - const formattedBRNumber = this.formatBRNumber(number); - if (formattedBRNumber !== number) { - this.logger.verbose( - 'Jid created is whatsapp in format BR: ' + - `${formattedBRNumber}@s.whatsapp.net`, - ); - return `${formattedBRNumber}@s.whatsapp.net`; - } - } - - if (Number(countryCode) === 52 || Number(countryCode) === 54) { - const formattedMXARNumber = this.formatMXOrARNumber(number); - - if (formattedMXARNumber !== number) { - this.logger.verbose( - 'Jid created is whatsapp in format MXAR: ' + - `${formattedMXARNumber}@s.whatsapp.net`, - ); - return `${formattedMXARNumber}@s.whatsapp.net`; - } - } - - if (number.includes('-')) { + + number = number + ?.split(":")[0] + ?.split("@")[0] + ?.replace(' ', '') + ?.replace('+', '') + ?.replace('(', '') + ?.replace(')', ''); + + if (number.includes('-') && number.length >= 18) { this.logger.verbose('Jid created is group: ' + `${number}@g.us`); + number = number.replace(/[^\d-]/g, ''); return `${number}@g.us`; } - + + number = number.replace(/\D/g, ''); + this.logger.verbose('Jid created is whatsapp: ' + `${number}@s.whatsapp.net`); return `${number}@s.whatsapp.net`; } @@ -1451,15 +1437,14 @@ export class WAStartupService { ) { this.logger.verbose('Sending message with typing'); - const jid = this.createJid(number); - const numberWA = await this.whatsappNumber({ numbers: [jid] }); + const numberWA = await this.whatsappNumber({ numbers: [number] }); const isWA = numberWA[0]; if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) { throw new BadRequestException(isWA); } - const sender = isJidGroup(jid) ? jid : isWA.jid; + const sender = isWA.jid; try { if (options?.delay) { @@ -1468,7 +1453,7 @@ export class WAStartupService { await this.client.presenceSubscribe(sender); this.logger.verbose('Subscribing to presence'); - await this.client.sendPresenceUpdate(options?.presence ?? 'composing', jid); + await this.client.sendPresenceUpdate(options?.presence ?? 'composing', sender); this.logger.verbose( 'Sending presence update: ' + options?.presence ?? 'composing', ); @@ -1527,7 +1512,8 @@ export class WAStartupService { mentions = options.mentions.mentioned.map((mention) => { const jid = this.createJid(mention); if (isJidGroup(jid)) { - throw new BadRequestException('Mentions must be a number'); + return null; + // throw new BadRequestException('Mentions must be a number'); } return jid; }); @@ -2171,7 +2157,7 @@ export class WAStartupService { const onWhatsapp: OnWhatsAppDto[] = []; for await (const number of data.numbers) { const jid = this.createJid(number); - // const jid = `${number}@s.whatsapp.net`; + if (isJidGroup(jid)) { const group = await this.findGroup({ groupJid: jid }, 'inner'); From 28c2c7285c2aada7c9a7b344e7737810a153cfb2 Mon Sep 17 00:00:00 2001 From: Alan Mosko Date: Mon, 24 Jul 2023 11:15:58 -0300 Subject: [PATCH 10/24] Gerar Wuid no SendContact --- src/validate/validate.schema.ts | 2 +- src/whatsapp/services/whatsapp.service.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 2b9ce19a..59cb2bd6 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -398,7 +398,7 @@ export const contactMessageSchema: JSONSchema7 = { email: { type: 'string' }, url: { type: 'string' }, }, - required: ['fullName', 'wuid', 'phoneNumber'], + required: ['fullName', 'phoneNumber'], ...isNotEmpty('fullName'), }, minItems: 1, diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 3851f067..b8e1eedb 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -2125,6 +2125,11 @@ export class WAStartupService { result += `URL:${contact.url}\n`; } + if (!contact.wuid) { + this.logger.verbose('Wuid defined'); + contact.wuid = this.createJid(contact.phoneNumber); + } + result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + From f35b62ed12fdd6a0c184f042eb800e61d8959e83 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 11:18:08 -0300 Subject: [PATCH 11/24] fix: Adjusts in createJid --- src/whatsapp/services/whatsapp.service.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index c56cddb1..2db6e9d4 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1469,7 +1469,7 @@ export class WAStartupService { private createJid(number: string): string { this.logger.verbose('Creating jid with number: ' + number); - + if (number.includes('@g.us') || number.includes('@s.whatsapp.net')) { this.logger.verbose('Number already contains @g.us or @s.whatsapp.net'); return number; @@ -1479,23 +1479,23 @@ export class WAStartupService { this.logger.verbose('Number already contains @broadcast'); return number; } - + number = number - ?.split(":")[0] - ?.split("@")[0] + ?.split(':')[0] + ?.split('@')[0] ?.replace(' ', '') ?.replace('+', '') ?.replace('(', '') ?.replace(')', ''); - + if (number.includes('-') && number.length >= 18) { this.logger.verbose('Jid created is group: ' + `${number}@g.us`); number = number.replace(/[^\d-]/g, ''); return `${number}@g.us`; } - + number = number.replace(/\D/g, ''); - + this.logger.verbose('Jid created is whatsapp: ' + `${number}@s.whatsapp.net`); return `${number}@s.whatsapp.net`; } @@ -2323,7 +2323,7 @@ export class WAStartupService { const onWhatsapp: OnWhatsAppDto[] = []; for await (const number of data.numbers) { const jid = this.createJid(number); - + if (isJidGroup(jid)) { const group = await this.findGroup({ groupJid: jid }, 'inner'); From ffe15231702b471588e9fd50bf2aad7efb711b1e Mon Sep 17 00:00:00 2001 From: Alan Mosko Date: Mon, 24 Jul 2023 11:42:06 -0300 Subject: [PATCH 12/24] Update whatsapp.service.ts --- src/whatsapp/services/whatsapp.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 340cf5a6..4e20fca8 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -2156,7 +2156,7 @@ export class WAStartupService { const onWhatsapp: OnWhatsAppDto[] = []; for await (const number of data.numbers) { - const jid = this.createJid(number); + let jid = this.createJid(number); if (isJidGroup(jid)) { const group = await this.findGroup({ groupJid: jid }, 'inner'); @@ -2165,6 +2165,7 @@ export class WAStartupService { onWhatsapp.push(new OnWhatsAppDto(group.id, !!group?.id, group?.subject)); } else { + jid = (!jid.startsWith('+')) ? `+${jid}` : jid; const verify = await this.client.onWhatsApp(jid); const result = verify[0]; From 2f3d6f7e63b556d5dfcd2f2c69c57fa78c4a5570 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 11:48:10 -0300 Subject: [PATCH 13/24] fix: Fixed problem with fileSha256 appearing when sending a sticker in chatwoot --- CHANGELOG.md | 1 + src/validate/validate.schema.ts | 2 +- src/whatsapp/services/chatwoot.service.ts | 6 ++++++ src/whatsapp/services/whatsapp.service.ts | 7 ++++++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc020a5..d9c76124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixed * Added link preview option in send text message +* Fixed problem with fileSha256 appearing when sending a sticker in chatwoot ### Integrations diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index ff95074e..18a070ab 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -446,7 +446,7 @@ export const whatsappNumberSchema: JSONSchema7 = { uniqueItems: true, items: { type: 'string', - pattern: '^\\d+', + // pattern: '^\\d+', description: '"numbers" must be an array of numeric strings', }, }, diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index decb5822..0c022c22 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1173,6 +1173,10 @@ export class ChatwootService { const result = typeKey ? types[typeKey] : undefined; + if (typeKey === 'stickerMessage') { + return null; + } + if (typeKey === 'contactMessage') { const vCardData = result.split('\n'); const contactInfo = {}; @@ -1295,6 +1299,8 @@ export class ChatwootService { this.logger.verbose('get conversation message'); const bodyMessage = await this.getConversationMessage(body.message); + console.log('bodyMessage', bodyMessage, body.message); + if (!bodyMessage && !isMedia) { this.logger.warn('no body message found'); return; diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 445c9469..12d338a3 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1335,9 +1335,14 @@ export class WAStartupService { if (settings?.msg_call.trim().length > 0 && call.status == 'offer') { this.logger.verbose('Sending message in call'); - this.client.sendMessage(call.from, { + const msg = await this.client.sendMessage(call.from, { text: settings.msg_call, }); + + this.client.ev.emit('messages.upsert', { + messages: [msg], + type: 'notify', + }); } } From 95df402c4cc49adcf6a223520910818fb81958fd Mon Sep 17 00:00:00 2001 From: Alan Mosko Date: Mon, 24 Jul 2023 11:57:15 -0300 Subject: [PATCH 14/24] wip --- src/validate/validate.schema.ts | 2 +- src/whatsapp/services/whatsapp.service.ts | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index f3602582..50972dd0 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -445,7 +445,7 @@ export const whatsappNumberSchema: JSONSchema7 = { uniqueItems: true, items: { type: 'string', - pattern: '^\\d+', + // pattern: '^\\d+', description: '"numbers" must be an array of numeric strings', }, }, diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 4e20fca8..a0914a68 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1392,14 +1392,14 @@ export class WAStartupService { } number = number - ?.split(":")[0] - ?.split("@")[0] - ?.replace(' ', '') - ?.replace('+', '') - ?.replace('(', '') - ?.replace(')', ''); + ?.replace(/\s/g, '') + .replace(/\+/g, '') + .replace(/\(/g, '') + .replace(/\)/g, '') + .split(/\:/)[0] + .split('@')[0]; - if (number.includes('-') && number.length >= 18) { + if(number.includes('-') && number.length >= 24){ this.logger.verbose('Jid created is group: ' + `${number}@g.us`); number = number.replace(/[^\d-]/g, ''); return `${number}@g.us`; @@ -1407,6 +1407,12 @@ export class WAStartupService { number = number.replace(/\D/g, ''); + if (number.length >= 18) { + this.logger.verbose('Jid created is group: ' + `${number}@g.us`); + number = number.replace(/[^\d-]/g, ''); + return `${number}@g.us`; + } + this.logger.verbose('Jid created is whatsapp: ' + `${number}@s.whatsapp.net`); return `${number}@s.whatsapp.net`; } From 1aa837d220e638c7f0a91d97fcee8f490c5d819a Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 11:58:52 -0300 Subject: [PATCH 15/24] fix: Adjusts in chatwoot integration --- CHANGELOG.md | 2 ++ src/whatsapp/services/chatwoot.service.ts | 37 ++++++++++++++++------- src/whatsapp/services/whatsapp.service.ts | 2 +- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c76124..f2d0d4ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * Added link preview option in send text message * Fixed problem with fileSha256 appearing when sending a sticker in chatwoot +* Fixed issue where it was not possible to open a conversation when sent at first by me on my cell phone in chatwoot +* Now it only updates the contact name if it is the same as the phone number in chatwoot ### Integrations diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index 0c022c22..c077a983 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -428,10 +428,12 @@ export class ChatwootService { ); if (findParticipant) { - await this.updateContact(instance, findParticipant.id, { - name: body.pushName, - avatar_url: picture_url.profilePictureUrl || null, - }); + if (!findParticipant.name || findParticipant.name === chatId) { + await this.updateContact(instance, findParticipant.id, { + name: body.pushName, + avatar_url: picture_url.profilePictureUrl || null, + }); + } } else { await this.createContact( instance, @@ -454,13 +456,28 @@ export class ChatwootService { let contact: any; if (body.key.fromMe) { - contact = findContact; + if (findContact) { + contact = findContact; + } else { + contact = await this.createContact( + instance, + chatId, + filterInbox.id, + isGroup, + nameContact, + picture_url.profilePictureUrl || null, + ); + } } else { if (findContact) { - contact = await this.updateContact(instance, findContact.id, { - name: nameContact, - avatar_url: picture_url.profilePictureUrl || null, - }); + if (!findContact.name || findContact.name === chatId) { + contact = await this.updateContact(instance, findContact.id, { + name: nameContact, + avatar_url: picture_url.profilePictureUrl || null, + }); + } else { + contact = findContact; + } } else { contact = await this.createContact( instance, @@ -1299,8 +1316,6 @@ export class ChatwootService { this.logger.verbose('get conversation message'); const bodyMessage = await this.getConversationMessage(body.message); - console.log('bodyMessage', bodyMessage, body.message); - if (!bodyMessage && !isMedia) { this.logger.warn('no body message found'); return; diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 12d338a3..5caaa9d9 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1093,7 +1093,7 @@ export class WAStartupService { if ( type !== 'notify' || - received.message?.protocolMessage || + // received.message?.protocolMessage || received.message?.pollUpdateMessage ) { this.logger.verbose('message rejected'); From c9b24ff612dbdcaa95cead2509fb30ed1ac5c2a6 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 12:55:27 -0300 Subject: [PATCH 16/24] fix: adjusts for chatwoot v3 --- src/whatsapp/services/chatwoot.service.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index c077a983..b28eed63 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1297,6 +1297,16 @@ export class ChatwootService { return; } + this.logger.verbose('get conversation message'); + const bodyMessage = await this.getConversationMessage(body.message); + + const isMedia = this.isMediaMessage(body.message); + + if (!bodyMessage && !isMedia) { + this.logger.warn('no body message found'); + return; + } + this.logger.verbose('get conversation in chatwoot'); const getConversion = await this.createConversation(instance, body); @@ -1309,18 +1319,8 @@ export class ChatwootService { this.logger.verbose('message type: ' + messageType); - const isMedia = this.isMediaMessage(body.message); - this.logger.verbose('is media: ' + isMedia); - this.logger.verbose('get conversation message'); - const bodyMessage = await this.getConversationMessage(body.message); - - if (!bodyMessage && !isMedia) { - this.logger.warn('no body message found'); - return; - } - this.logger.verbose('check if is media'); if (isMedia) { this.logger.verbose('message is media'); From bcada5d553aa7c79f35fc825ea3dc9be2c4febca Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 13:03:40 -0300 Subject: [PATCH 17/24] fix: Now accepts all chatwoot inbox templates --- CHANGELOG.md | 1 + src/whatsapp/services/chatwoot.service.ts | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2d0d4ac..10f6b6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * Fixed problem with fileSha256 appearing when sending a sticker in chatwoot * Fixed issue where it was not possible to open a conversation when sent at first by me on my cell phone in chatwoot * Now it only updates the contact name if it is the same as the phone number in chatwoot +* Now accepts all chatwoot inbox templates ### Integrations diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index b28eed63..aa346f0a 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1111,11 +1111,7 @@ export class ChatwootService { } } - if ( - body.message_type === 'template' && - body.content_type === 'input_csat' && - body.event === 'message_created' - ) { + if (body.message_type === 'template' && body.event === 'message_created') { this.logger.verbose('check if is csat'); const data: SendTextDto = { From 7103a953056929c7a5ab215cdaccaadfb8d8b528 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 13:43:18 -0300 Subject: [PATCH 18/24] feat: Added connection with pairing code in chatwoot --- CHANGELOG.md | 1 + .../controllers/instance.controller.ts | 32 +++---------- src/whatsapp/dto/chatwoot.dto.ts | 1 + src/whatsapp/models/chatwoot.model.ts | 2 + src/whatsapp/services/chatwoot.service.ts | 27 +++++++++-- src/whatsapp/services/monitor.service.ts | 3 ++ src/whatsapp/services/whatsapp.service.ts | 47 +++++++++++++++---- src/whatsapp/types/wa.types.ts | 2 + 8 files changed, 76 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10f6b6bb..7f8fd11e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Created settings controller * Added reject call and send text message when receiving a call * Added setting to ignore group messages +* Added connection with pairing code in chatwoot ### Fixed diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index e7827f15..31db7918 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -103,16 +103,10 @@ export class InstanceController { if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { let getQrcode: wa.QrCode; - let getPairingCode: string; if (qrcode) { this.logger.verbose('creating qrcode'); - await instance.connectToWhatsapp(); - if (number) { - this.logger.verbose('creating number'); - await delay(5000); - getPairingCode = await instance.client.requestPairingCode(number); - } + await instance.connectToWhatsapp(number); await delay(2000); getQrcode = instance.qrCode; } @@ -126,14 +120,9 @@ export class InstanceController { webhook, webhook_by_events, events: getEvents, + qrcode: getQrcode, }; - if (getPairingCode) { - result['pairingCode'] = getPairingCode; - } else { - result['qrcode'] = getQrcode; - } - this.logger.verbose('instance created'); this.logger.verbose(result); @@ -166,6 +155,7 @@ export class InstanceController { url: chatwoot_url, sign_msg: chatwoot_sign_msg || false, name_inbox: instance.instanceName, + number, }); this.chatwootService.initInstanceChatwoot( @@ -173,6 +163,7 @@ export class InstanceController { instance.instanceName, `${urlServer}/chatwoot/webhook/${instance.instanceName}`, qrcode, + number, ); } catch (error) { this.logger.log(error); @@ -193,6 +184,7 @@ export class InstanceController { token: chatwoot_token, url: chatwoot_url, sign_msg: chatwoot_sign_msg || false, + number, name_inbox: instance.instanceName, webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`, }, @@ -220,19 +212,7 @@ export class InstanceController { if (state == 'close') { this.logger.verbose('connecting'); - await instance.connectToWhatsapp(); - let pairingCode = null; - if (number) { - this.logger.verbose('creating pairing code'); - await delay(5000); - pairingCode = await instance.client.requestPairingCode(number); - } - - if (pairingCode) { - return { - pairingCode, - }; - } + await instance.connectToWhatsapp(number); await delay(2000); return instance.qrCode; diff --git a/src/whatsapp/dto/chatwoot.dto.ts b/src/whatsapp/dto/chatwoot.dto.ts index e78b0676..a5026a46 100644 --- a/src/whatsapp/dto/chatwoot.dto.ts +++ b/src/whatsapp/dto/chatwoot.dto.ts @@ -5,4 +5,5 @@ export class ChatwootDto { url?: string; name_inbox?: string; sign_msg?: boolean; + number?: string; } diff --git a/src/whatsapp/models/chatwoot.model.ts b/src/whatsapp/models/chatwoot.model.ts index ca082309..54d9e051 100644 --- a/src/whatsapp/models/chatwoot.model.ts +++ b/src/whatsapp/models/chatwoot.model.ts @@ -9,6 +9,7 @@ export class ChatwootRaw { url?: string; name_inbox?: string; sign_msg?: boolean; + number?: string; } const chatwootSchema = new Schema({ @@ -19,6 +20,7 @@ const chatwootSchema = new Schema({ url: { type: String, required: true }, name_inbox: { type: String, required: true }, sign_msg: { type: Boolean, required: true }, + number: { type: String, required: true }, }); export const ChatwootModel = dbserver?.model( diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index aa346f0a..9fd3d6d6 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -13,6 +13,7 @@ import { SendAudioDto } from '../dto/sendMessage.dto'; import { SendMediaDto } from '../dto/sendMessage.dto'; import { ROOT_DIR } from '../../config/path.config'; import { ConfigService, HttpServer } from '../../config/env.config'; +import { delay } from '@whiskeysockets/baileys'; export class ChatwootService { private messageCacheFile: string; @@ -154,6 +155,7 @@ export class ChatwootService { inboxName: string, webhookUrl: string, qrcode: boolean, + number: string, ) { this.logger.verbose('init instance chatwoot: ' + instance.instanceName); @@ -243,11 +245,18 @@ export class ChatwootService { } this.logger.verbose('create message for init instance in chatwoot'); + + let contentMsg = '/init'; + + if (number) { + contentMsg = `/init:${number}`; + } + const message = await client.messages.create({ accountId: this.provider.account_id, conversationId: conversation.id, data: { - content: '/init', + content: contentMsg, message_type: 'outgoing', }, }); @@ -953,13 +962,14 @@ export class ChatwootService { const command = messageReceived.replace('/', ''); - if (command === 'init' || command === 'iniciar') { + if (command.includes('init') || command.includes('iniciar')) { this.logger.verbose('command init found'); const state = waInstance?.connectionStatus?.state; if (state !== 'open') { this.logger.verbose('connect to whatsapp'); - await waInstance.connectToWhatsapp(); + const number = command.split(':')[1]; + await waInstance.connectToWhatsapp(number); } else { this.logger.verbose('whatsapp already connected'); await this.createBotMessage( @@ -1556,7 +1566,16 @@ export class ChatwootService { fileName, ); - const msgQrCode = `⚡️ QRCode successfully generated!\n\nScan this QR code within the next 40 seconds:`; + let msgQrCode = `⚡️ QRCode successfully generated!\n\nScan this QR code within the next 40 seconds.`; + + if (body?.qrcode?.pairingCode) { + msgQrCode = + msgQrCode + + `\n\n*Pairing Code:* ${body.qrcode.pairingCode.substring( + 0, + 4, + )}-${body.qrcode.pairingCode.substring(4, 8)}`; + } this.logger.verbose('send message to chatwoot'); await this.createBotMessage(instance, msgQrCode, 'incoming'); diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index 6c31ad58..7ffa81e1 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -25,6 +25,7 @@ import { ContactModel, MessageModel, MessageUpModel, + SettingsModel, WebhookModel, } from '../models'; @@ -241,6 +242,7 @@ export class WAMonitoringService { execSync(`rm -rf ${join(STORE_DIR, 'auth', 'apikey', instanceName + '.json')}`); execSync(`rm -rf ${join(STORE_DIR, 'webhook', instanceName + '.json')}`); execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`); + execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`); return; } @@ -254,6 +256,7 @@ export class WAMonitoringService { await AuthModel.deleteMany({ _id: instanceName }); await WebhookModel.deleteMany({ _id: instanceName }); await ChatwootModel.deleteMany({ _id: instanceName }); + await SettingsModel.deleteMany({ _id: instanceName }); return; } diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 7adaf641..9cb9b43f 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -152,6 +152,8 @@ export class WAStartupService { private endSession = false; private logBaileys = this.configService.get('LOG').BAILEYS; + private phoneNumber: string; + private chatwootService = new ChatwootService(waMonitor, this.configService); public set instanceName(name: string) { @@ -241,6 +243,12 @@ export class WAStartupService { public get qrCode(): wa.QrCode { this.logger.verbose('Getting qrcode'); + if (this.instance.qrcode?.pairingCode) { + return { + pairingCode: this.instance.qrcode?.pairingCode, + }; + } + return { code: this.instance.qrcode?.code, base64: this.instance.qrcode?.base64, @@ -588,11 +596,6 @@ export class WAStartupService { return this.eventEmitter.emit('no.connection', this.instance.name); } - // pairing code - // await delay(5000); - // const code = await this.client.requestPairingCode('557499879409'); - // console.log(`Pairing code: ${code}`); - this.logger.verbose('Incrementing QR code count'); this.instance.qrcode.count++; @@ -603,6 +606,13 @@ export class WAStartupService { color: { light: '#ffffff', dark: '#198754' }, }; + if (this.phoneNumber) { + await delay(2000); + this.instance.qrcode.pairingCode = await this.client.requestPairingCode( + this.phoneNumber, + ); + } + this.logger.verbose('Generating QR code'); qrcode.toDataURL(qr, optsQrcode, (error, base64) => { if (error) { @@ -614,7 +624,12 @@ export class WAStartupService { this.instance.qrcode.code = qr; this.sendDataWebhook(Events.QRCODE_UPDATED, { - qrcode: { instance: this.instance.name, code: qr, base64 }, + qrcode: { + instance: this.instance.name, + pairingCode: this.instance.qrcode.pairingCode, + code: qr, + base64, + }, }); if (this.localChatwoot.enabled) { @@ -622,7 +637,12 @@ export class WAStartupService { Events.QRCODE_UPDATED, { instanceName: this.instance.name }, { - qrcode: { instance: this.instance.name, code: qr, base64 }, + qrcode: { + instance: this.instance.name, + pairingCode: this.instance.qrcode.pairingCode, + code: qr, + base64, + }, }, ); } @@ -631,7 +651,7 @@ export class WAStartupService { this.logger.verbose('Generating QR code in terminal'); qrcodeTerminal.generate(qr, { small: true }, (qrcode) => this.logger.log( - `\n{ instance: ${this.instance.name}, qrcodeCount: ${this.instance.qrcode.count} }\n` + + `\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` + qrcode, ), ); @@ -798,7 +818,7 @@ export class WAStartupService { return await useMultiFileAuthState(join(INSTANCE_DIR, this.instance.name)); } - public async connectToWhatsapp(): Promise { + public async connectToWhatsapp(number?: string): Promise { this.logger.verbose('Connecting to whatsapp'); try { this.loadWebhook(); @@ -872,6 +892,15 @@ export class WAStartupService { this.logger.verbose('Socket event handler initialized'); + this.phoneNumber = number; + + // if (number) { + // this.logger.verbose('creating pairing code'); + // await delay(5000); + // this.phoneNumber = number; + // this.instance.qrcode.pairingCode = await this.client.requestPairingCode(number); + // } + return this.client; } catch (error) { this.logger.error(error); diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index 4b699d7e..d0c5f80c 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -27,11 +27,13 @@ export enum Events { export declare namespace wa { export type QrCode = { count?: number; + pairingCode?: string; base64?: string; code?: string; }; export type Instance = { qrcode?: QrCode; + pairingCode?: string; authState?: { state: AuthenticationState; saveCreds: () => void }; name?: string; wuid?: string; From fff420b652120af7919c01b9a4c55445da2bca9b Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 15:05:03 -0300 Subject: [PATCH 19/24] fix: command to create new instances set to /new_instance:: --- CHANGELOG.md | 1 + .../controllers/chatwoot.controller.ts | 6 ++ src/whatsapp/services/chatwoot.service.ts | 56 ++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f8fd11e..57a8c4cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Fixed issue where it was not possible to open a conversation when sent at first by me on my cell phone in chatwoot * Now it only updates the contact name if it is the same as the phone number in chatwoot * Now accepts all chatwoot inbox templates +* Command to create new instances set to /new_instance:: ### Integrations diff --git a/src/whatsapp/controllers/chatwoot.controller.ts b/src/whatsapp/controllers/chatwoot.controller.ts index de0aef7a..13e8fcd4 100644 --- a/src/whatsapp/controllers/chatwoot.controller.ts +++ b/src/whatsapp/controllers/chatwoot.controller.ts @@ -94,4 +94,10 @@ export class ChatwootController { return chatwootService.receiveWebhook(instance, data); } + + public async newInstance(data: any) { + const chatwootService = new ChatwootService(waMonitor, this.configService); + + return chatwootService.newInstance(data); + } } diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index 9fd3d6d6..399261c2 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -13,7 +13,6 @@ import { SendAudioDto } from '../dto/sendMessage.dto'; import { SendMediaDto } from '../dto/sendMessage.dto'; import { ROOT_DIR } from '../../config/path.config'; import { ConfigService, HttpServer } from '../../config/env.config'; -import { delay } from '@whiskeysockets/baileys'; export class ChatwootService { private messageCacheFile: string; @@ -1017,7 +1016,7 @@ export class ChatwootService { await waInstance?.client?.ws?.close(); } - if (command.includes('#inbox_whatsapp')) { + if (command.includes('new_instance')) { const urlServer = this.configService.get('SERVER').URL; const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; @@ -1030,6 +1029,10 @@ export class ChatwootService { chatwoot_sign_msg: this.provider.sign_msg, }; + if (command.split(':')[2]) { + data['number'] = command.split(':')[2]; + } + const config = { method: 'post', maxBodyLength: Infinity, @@ -1585,4 +1588,53 @@ export class ChatwootService { this.logger.error(error); } } + + public async newInstance(data: any) { + try { + const instanceName = data.instanceName; + const qrcode = true; + const number = data.number; + const accountId = data.accountId; + const chatwootToken = data.token; + const chatwootUrl = data.url; + const signMsg = true; + const urlServer = this.configService.get('SERVER').URL; + const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; + + console.log('data: ', data); + + const requestData = { + instanceName, + qrcode, + chatwoot_account_id: accountId, + chatwoot_token: chatwootToken, + chatwoot_url: chatwootUrl, + chatwoot_sign_msg: signMsg, + }; + + if (number) { + requestData['number'] = number; + } + + console.log('requestData: ', requestData); + + const config = { + method: 'post', + maxBodyLength: Infinity, + url: `${urlServer}/instance/create`, + headers: { + 'Content-Type': 'application/json', + apikey: apiKey, + }, + data: requestData, + }; + + // await axios.request(config); + + return true; + } catch (error) { + this.logger.error(error); + return null; + } + } } From 4d00351db790b498f919f3083787a85ad7788ecb Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 15:05:26 -0300 Subject: [PATCH 20/24] fix: command to create new instances set to /new_instance:: --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57a8c4cc..3cecc446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Created settings controller * Added reject call and send text message when receiving a call * Added setting to ignore group messages -* Added connection with pairing code in chatwoot +* Added connection with pairing code in chatwoot with command /init: ### Fixed From 45c11a5a8e9474a580557d8a8aa50f200d34646c Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 15:12:13 -0300 Subject: [PATCH 21/24] fix: fix in chatwoot set, sign msg can now be disabled --- CHANGELOG.md | 1 + src/whatsapp/controllers/chatwoot.controller.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cecc446..7009fce9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * Now it only updates the contact name if it is the same as the phone number in chatwoot * Now accepts all chatwoot inbox templates * Command to create new instances set to /new_instance:: +* Fix in chatwoot set, sign msg can now be disabled ### Integrations diff --git a/src/whatsapp/controllers/chatwoot.controller.ts b/src/whatsapp/controllers/chatwoot.controller.ts index 13e8fcd4..d5e5e841 100644 --- a/src/whatsapp/controllers/chatwoot.controller.ts +++ b/src/whatsapp/controllers/chatwoot.controller.ts @@ -33,7 +33,7 @@ export class ChatwootController { throw new BadRequestException('token is required'); } - if (!data.sign_msg) { + if (data.sign_msg !== true && data.sign_msg !== false) { throw new BadRequestException('sign_msg is required'); } } From 68d980795ad3fc57a4b902945be094934d9095cd Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 15:13:46 -0300 Subject: [PATCH 22/24] fix: fix in chatwoot set, sign msg can now be disabled --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7009fce9..ed6500be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Created settings controller * Added reject call and send text message when receiving a call * Added setting to ignore group messages -* Added connection with pairing code in chatwoot with command /init: +* Added connection with pairing code in chatwoot with command /init:{NUMBER} ### Fixed @@ -16,7 +16,7 @@ * Fixed issue where it was not possible to open a conversation when sent at first by me on my cell phone in chatwoot * Now it only updates the contact name if it is the same as the phone number in chatwoot * Now accepts all chatwoot inbox templates -* Command to create new instances set to /new_instance:: +* Command to create new instances set to /new_instance:{NAME}:{NUMBER} * Fix in chatwoot set, sign msg can now be disabled ### Integrations @@ -55,7 +55,7 @@ * Added messages.delete event * Added restart instance endpoint -* Created automation for creating instances in the chatwoot bot with the command '#inbox_whatsapp:' +* Created automation for creating instances in the chatwoot bot with the command '#inbox_whatsapp:{INSTANCE_NAME} * Change Baileys version to: 6.4.0 * Send contact in chatwoot * Send contact array in chatwoot From 8d91e7cb1d04abb2cf01e703a59222f04805fc5e Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 16:21:29 -0300 Subject: [PATCH 23/24] feat: Added encoding option in endpoint sendWhatsAppAudio --- CHANGELOG.md | 1 + .../controllers/instance.controller.ts | 2 +- src/whatsapp/dto/sendMessage.dto.ts | 1 + src/whatsapp/services/chatwoot.service.ts | 4 -- src/whatsapp/services/whatsapp.service.ts | 58 ++++++++++++------- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6500be..beeb2888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Added reject call and send text message when receiving a call * Added setting to ignore group messages * Added connection with pairing code in chatwoot with command /init:{NUMBER} +* Added encoding option in endpoint sendWhatsAppAudio ### Fixed diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 31db7918..ef930c7b 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -107,7 +107,7 @@ export class InstanceController { if (qrcode) { this.logger.verbose('creating qrcode'); await instance.connectToWhatsapp(number); - await delay(2000); + await delay(3000); getQrcode = instance.qrCode; } diff --git a/src/whatsapp/dto/sendMessage.dto.ts b/src/whatsapp/dto/sendMessage.dto.ts index 0a20674c..c2ddb3a2 100644 --- a/src/whatsapp/dto/sendMessage.dto.ts +++ b/src/whatsapp/dto/sendMessage.dto.ts @@ -16,6 +16,7 @@ export class Options { quoted?: Quoted; mentions?: Mentions; linkPreview?: boolean; + encoding?: boolean; } class OptionsMessage { options: Options; diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index 399261c2..c77162ad 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1601,8 +1601,6 @@ export class ChatwootService { const urlServer = this.configService.get('SERVER').URL; const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; - console.log('data: ', data); - const requestData = { instanceName, qrcode, @@ -1616,8 +1614,6 @@ export class ChatwootService { requestData['number'] = number; } - console.log('requestData: ', requestData); - const config = { method: 'post', maxBodyLength: Infinity, diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 9cb9b43f..d475988a 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1354,7 +1354,6 @@ export class WAStartupService { if (events.call) { this.logger.verbose('Listening event: call'); - console.log('events.call', events.call); const call = events.call[0]; if (settings?.reject_call && call.status == 'offer') { @@ -1662,8 +1661,6 @@ export class WAStartupService { const linkPreview = options?.linkPreview != false ? undefined : false; - console.log('linkPreview', linkPreview); - let quoted: WAMessage; if (options?.quoted) { @@ -2179,26 +2176,45 @@ export class WAStartupService { public async audioWhatsapp(data: SendAudioDto) { this.logger.verbose('Sending audio whatsapp'); - const convert = await this.processAudio(data.audioMessage.audio, data.number); - if (typeof convert === 'string') { - const audio = fs.readFileSync(convert).toString('base64'); - const result = this.sendMessageWithTyping( - data.number, - { - audio: Buffer.from(audio, 'base64'), - ptt: true, - mimetype: 'audio/mp4', - }, - { presence: 'recording', delay: data?.options?.delay }, - ); - fs.unlinkSync(convert); - this.logger.verbose('Converted audio deleted'); - - return result; - } else { - throw new InternalServerErrorException(convert); + if (!data.options?.encoding && data.options?.encoding !== false) { + data.options.encoding = true; } + + if (data.options?.encoding) { + const convert = await this.processAudio(data.audioMessage.audio, data.number); + if (typeof convert === 'string') { + const audio = fs.readFileSync(convert).toString('base64'); + const result = this.sendMessageWithTyping( + data.number, + { + audio: Buffer.from(audio, 'base64'), + ptt: true, + mimetype: 'audio/mp4', + }, + { presence: 'recording', delay: data?.options?.delay }, + ); + + fs.unlinkSync(convert); + this.logger.verbose('Converted audio deleted'); + + return result; + } else { + throw new InternalServerErrorException(convert); + } + } + + return await this.sendMessageWithTyping( + data.number, + { + audio: isURL(data.audioMessage.audio) + ? { url: data.audioMessage.audio } + : Buffer.from(data.audioMessage.audio, 'base64'), + ptt: true, + mimetype: 'audio/ogg; codecs=opus', + }, + { presence: 'recording', delay: data?.options?.delay }, + ); } public async buttonMessage(data: SendButtonDto) { From 1c30728880874eac25f7373a1f8c702042b24682 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Mon, 24 Jul 2023 17:05:18 -0300 Subject: [PATCH 24/24] version: 1.4.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beeb2888..1bb71df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 1.4.0 (homolog) +# 1.4.0 (2023-07-24 17:03) ### Features