diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1be220..1bb71df6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# 1.4.0 (2023-07-24 17:03) + +### Features + +* 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 +* Added connection with pairing code in chatwoot with command /init:{NUMBER} +* Added encoding option in endpoint sendWhatsAppAudio + +### Fixed + +* 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 +* Now accepts all chatwoot inbox templates +* Command to create new instances set to /new_instance:{NAME}:{NUMBER} +* Fix in chatwoot set, sign msg can now be disabled + +### Integrations + +- Chatwoot: v2.18.0 - v3.0.0 (Beta) + # 1.3.2 (2023-07-21 17:19) ### Fixed @@ -10,19 +36,27 @@ * 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 * 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 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..18a070ab 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'), @@ -123,7 +124,7 @@ const optionsSchema: JSONSchema7 = { const numberDefinition: JSONSchema7Definition = { type: 'string', - pattern: '^\\d+[\\.@\\w-]+', + // pattern: '^\\d+[\\.@\\w-]+', description: 'Invalid format', }; @@ -398,7 +399,7 @@ export const contactMessageSchema: JSONSchema7 = { email: { type: 'string' }, url: { type: 'string' }, }, - required: ['fullName', 'wuid', 'phoneNumber'], + required: ['fullName', 'phoneNumber'], ...isNotEmpty('fullName'), }, minItems: 1, @@ -445,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', }, }, @@ -587,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', @@ -865,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/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/chatwoot.controller.ts b/src/whatsapp/controllers/chatwoot.controller.ts index de0aef7a..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'); } } @@ -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/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index 75911848..ef930c7b 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, @@ -105,25 +106,12 @@ export class InstanceController { if (qrcode) { this.logger.verbose('creating qrcode'); - await instance.connectToWhatsapp(); - await delay(2000); + await instance.connectToWhatsapp(number); + await delay(3000); getQrcode = instance.qrCode; } - this.logger.verbose('instance created'); - this.logger.verbose({ - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook, - webhook_by_events, - events: getEvents, - qrcode: getQrcode, - }); - - return { + const result = { instance: { instanceName: instance.instanceName, status: 'created', @@ -134,6 +122,11 @@ export class InstanceController { events: getEvents, qrcode: getQrcode, }; + + this.logger.verbose('instance created'); + this.logger.verbose(result); + + return result; } if (!chatwoot_account_id) { @@ -162,6 +155,7 @@ export class InstanceController { url: chatwoot_url, sign_msg: chatwoot_sign_msg || false, name_inbox: instance.instanceName, + number, }); this.chatwootService.initInstanceChatwoot( @@ -169,6 +163,7 @@ export class InstanceController { instance.instanceName, `${urlServer}/chatwoot/webhook/${instance.instanceName}`, qrcode, + number, ); } catch (error) { this.logger.log(error); @@ -189,13 +184,14 @@ 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}`, }, }; } - public async connectToWhatsapp({ instanceName }: InstanceDto) { + public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { try { this.logger.verbose( 'requested connectToWhatsapp from ' + instanceName + ' instance', @@ -206,17 +202,29 @@ export class InstanceController { this.logger.verbose('state: ' + state); - switch (state) { - case 'close': - this.logger.verbose('connecting'); - await instance.connectToWhatsapp(); - 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(number); + + 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/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/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/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/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/dto/sendMessage.dto.ts b/src/whatsapp/dto/sendMessage.dto.ts index 51a55cec..c2ddb3a2 100644 --- a/src/whatsapp/dto/sendMessage.dto.ts +++ b/src/whatsapp/dto/sendMessage.dto.ts @@ -15,6 +15,8 @@ export class Options { presence?: WAPresence; quoted?: Quoted; mentions?: Mentions; + linkPreview?: boolean; + encoding?: boolean; } class OptionsMessage { options: Options; 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/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/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/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/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/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index decb5822..c77162ad 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -154,6 +154,7 @@ export class ChatwootService { inboxName: string, webhookUrl: string, qrcode: boolean, + number: string, ) { this.logger.verbose('init instance chatwoot: ' + instance.instanceName); @@ -243,11 +244,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', }, }); @@ -428,10 +436,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 +464,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, @@ -936,13 +961,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( @@ -990,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; @@ -1003,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, @@ -1094,11 +1124,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 = { @@ -1173,6 +1199,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 = {}; @@ -1276,6 +1306,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); @@ -1288,18 +1328,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'); @@ -1539,7 +1569,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'); @@ -1549,4 +1588,49 @@ 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; + + const requestData = { + instanceName, + qrcode, + chatwoot_account_id: accountId, + chatwoot_token: chatwootToken, + chatwoot_url: chatwootUrl, + chatwoot_sign_msg: signMsg, + }; + + if (number) { + requestData['number'] = number; + } + + 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; + } + } } 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/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 3851f067..d475988a 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, @@ -124,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( @@ -142,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(); @@ -149,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) { @@ -238,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, @@ -340,6 +351,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; @@ -555,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) { @@ -566,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) { @@ -574,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, + }, }, ); } @@ -583,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, ), ); @@ -750,11 +818,12 @@ 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(); this.loadChatwoot(); + this.loadSettings(); this.instance.authState = await this.defineAuthState(); @@ -823,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); @@ -1037,13 +1115,14 @@ export class WAStartupService { type: MessageUpsertType; }, database: Database, + settings: SettingsRaw, ) => { this.logger.verbose('Event received: messages.upsert'); const received = messages[0]; if ( type !== 'notify' || - received.message?.protocolMessage || + // received.message?.protocolMessage || received.message?.pollUpdateMessage ) { this.logger.verbose('message rejected'); @@ -1054,6 +1133,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, @@ -1145,7 +1229,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', @@ -1156,6 +1244,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'); @@ -1255,9 +1347,32 @@ 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'); + 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'); + const msg = await this.client.sendMessage(call.from, { + text: settings.msg_call, + }); + + this.client.ev.emit('messages.upsert', { + messages: [msg], + type: 'notify', + }); + } + } if (events['connection.update']) { this.logger.verbose('Listening event: connection.update'); @@ -1278,37 +1393,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']) { @@ -1391,36 +1513,22 @@ export class WAStartupService { return number; } - const countryCode = number.substring(0, 2); + number = number + ?.replace(/\s/g, '') + .replace(/\+/g, '') + .replace(/\(/g, '') + .replace(/\)/g, '') + .split(/\:/)[0] + .split('@')[0]; - 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('-')) { + 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`; } + number = number.replace(/\D/g, ''); + this.logger.verbose('Jid created is whatsapp: ' + `${number}@s.whatsapp.net`); return `${number}@s.whatsapp.net`; } @@ -1444,6 +1552,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, @@ -1451,15 +1631,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 +1647,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', ); @@ -1480,6 +1659,8 @@ export class WAStartupService { this.logger.verbose('Sending presence update: paused'); } + const linkPreview = options?.linkPreview != false ? undefined : false; + let quoted: WAMessage; if (options?.quoted) { @@ -1527,7 +1708,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; }); @@ -1573,6 +1755,7 @@ export class WAStartupService { { text: message['conversation'], mentions, + linkPreview: linkPreview, } as unknown as AnyMessageContent, option as unknown as MiscMessageGenerationOptions, ); @@ -1993,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) { @@ -2125,6 +2327,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' + @@ -2170,8 +2377,8 @@ export class WAStartupService { const onWhatsapp: OnWhatsAppDto[] = []; for await (const number of data.numbers) { - const jid = this.createJid(number); - // const jid = `${number}@s.whatsapp.net`; + let jid = this.createJid(number); + if (isJidGroup(jid)) { const group = await this.findGroup({ groupJid: jid }, 'inner'); @@ -2179,6 +2386,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]; @@ -2460,29 +2668,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', diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index 6869545f..d0c5f80c 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -25,9 +25,15 @@ export enum Events { } export declare namespace wa { - export type QrCode = { count?: number; base64?: string; code?: string }; + 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; @@ -51,6 +57,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,