diff --git a/src/utils/translations/en.json b/src/utils/translations/en.json index d8566c72..2eeeccb1 100644 --- a/src/utils/translations/en.json +++ b/src/utils/translations/en.json @@ -2,5 +2,24 @@ "qrgeneratedsuccesfully": "QRCode successfully generated!", "scanqr": "Scan this QR code within the next 40 seconds.", "qrlimitreached": "QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.", - "numbernotinwhatsapp": "The message was not sent as the contact is not a valid Whatsapp number." + "numbernotinwhatsapp": "The message was not sent as the contact is not a valid Whatsapp number.", + "cw.inbox.connected": "🚀 Connection successfully established!", + "cw.inbox.disconnect": "🚨 Disconnecting WhatsApp from inbox *{{inboxName}}*.", + "cw.inbox.alreadyConnected": "🚨 {{inboxName}} instance is connected.", + "cw.inbox.clearCache": "✅ {{inboxName}} instance cache cleared.", + "cw.inbox.notFound": "⚠️ {{inboxName}} instance not found.", + "cw.inbox.status": "⚠️ {{inboxName}} instance status: *{{state}}*.", + "cw.import.startImport": "💬 Starting to import messages. Please wait...", + "cw.import.importingMessages": "💬 Importing messages. More one moment...", + "cw.import.messagesImported": "💬 {{totalMessagesImported}} messages imported. Refresh page to see the new messages.", + "cw.import.messagesException": "💬 Something went wrong in importing messages.", + "cw.locationMessage.location": "Location", + "cw.locationMessage.latitude": "Latitude", + "cw.locationMessage.longitude": "Longitude", + "cw.locationMessage.locationName": "Name", + "cw.locationMessage.locationAddress": "Address", + "cw.locationMessage.locationUrl": "URL", + "cw.contactMessage.contact": "Contact", + "cw.contactMessage.name": "Name", + "cw.contactMessage.number": "Number" } \ No newline at end of file diff --git a/src/utils/translations/pt-BR.json b/src/utils/translations/pt-BR.json index a9668848..52bfcd69 100644 --- a/src/utils/translations/pt-BR.json +++ b/src/utils/translations/pt-BR.json @@ -1,6 +1,25 @@ { "qrgeneratedsuccesfully": "QRCode gerado com sucesso!", - "scanqr": "Escanei o QRCode com o Whatsapp nos próximos 40 segundos.", + "scanqr": "Escaneie o QRCode com o WhatsApp nos próximos 40 segundos.", "qrlimitreached": "Limite de geração de QRCode atingido! Para gerar um novo QRCode, envie o texto 'init' nesta conversa.", - "numbernotinwhatsapp": "A mensagem não foi enviada, pois o contato não é um número válido do Whatsapp." + "numbernotinwhatsapp": "A mensagem não foi enviada, pois o contato não é um número válido do WhatsApp.", + "cw.inbox.connected": "🚀 Conectado com sucesso!", + "cw.inbox.disconnect": "🚨 Instância *{{inboxName}}* desconectada do WhatsApp.", + "cw.inbox.alreadyConnected": "🚨 Instância *{{inboxName}}* já está conectada.", + "cw.inbox.clearCache": "✅ Instância *{{inboxName}}* cache removido.", + "cw.inbox.notFound": "⚠️ Instância *{{inboxName}}* não encontrada.", + "cw.inbox.status": "⚠️ Status da instância {{inboxName}}: *{{state}}*.", + "cw.import.startImport": "💬 Iniciando importação de mensagens. Por favor, aguarde...", + "cw.import.importingMessages": "💬 Importando mensagens. Mais um momento...", + "cw.import.messagesImported": "💬 {{totalMessagesImported}} mensagens importadas. Atualize a página para ver as novas mensagens.", + "cw.import.messagesException": "💬 Não foi possível importar as mensagens.", + "cw.locationMessage.location": "Localização", + "cw.locationMessage.latitude": "Latitude", + "cw.locationMessage.longitude": "Longitude", + "cw.locationMessage.locationName": "Nome", + "cw.locationMessage.locationAddress": "Endereço", + "cw.locationMessage.locationUrl": "URL", + "cw.contactMessage.contact": "Contato", + "cw.contactMessage.name": "Nome", + "cw.contactMessage.number": "Número" } \ No newline at end of file diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 01592b1e..faaf2fa1 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -539,6 +539,17 @@ export const privacySettingsSchema: JSONSchema7 = { required: ['privacySettings'], }; +export const blockUserSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { type: 'string' }, + status: { type: 'string', enum: ['block', 'unblock'] }, + }, + required: ['number', 'status'], + ...isNotEmpty('number', 'status'), +}; + export const archiveChatSchema: JSONSchema7 = { $id: v4(), type: 'object', diff --git a/src/whatsapp/controllers/chat.controller.ts b/src/whatsapp/controllers/chat.controller.ts index f9d77fce..1c16260d 100644 --- a/src/whatsapp/controllers/chat.controller.ts +++ b/src/whatsapp/controllers/chat.controller.ts @@ -1,6 +1,7 @@ import { Logger } from '../../config/logger.config'; import { ArchiveChatDto, + BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, NumberDto, @@ -123,4 +124,9 @@ export class ChatController { logger.verbose('requested updateMessage from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].updateMessage(data); } + + public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) { + logger.verbose('requested blockUser from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].blockUser(data); + } } diff --git a/src/whatsapp/dto/chat.dto.ts b/src/whatsapp/dto/chat.dto.ts index 31f3dfe3..24f04847 100644 --- a/src/whatsapp/dto/chat.dto.ts +++ b/src/whatsapp/dto/chat.dto.ts @@ -115,3 +115,8 @@ export class UpdateMessageDto extends Metadata { key: proto.IMessageKey; text: string; } + +export class BlockUserDto { + number: string; + status: 'block' | 'unblock'; +} diff --git a/src/whatsapp/routers/chat.router.ts b/src/whatsapp/routers/chat.router.ts index e4161474..a0ce3216 100644 --- a/src/whatsapp/routers/chat.router.ts +++ b/src/whatsapp/routers/chat.router.ts @@ -3,6 +3,7 @@ import { RequestHandler, Router } from 'express'; import { Logger } from '../../config/logger.config'; import { archiveChatSchema, + blockUserSchema, contactValidateSchema, deleteMessageSchema, messageUpSchema, @@ -20,6 +21,7 @@ import { import { RouterBroker } from '../abstract/abstract.router'; import { ArchiveChatDto, + BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, NumberDto, @@ -384,6 +386,23 @@ export class ChatRouter extends RouterBroker { }); return res.status(HttpStatus.OK).json(response); + }) + .put(this.routerPath('updateBlockStatus'), ...guards, async (req, res) => { + logger.verbose('request received in updateBlockStatus'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: blockUserSchema, + ClassRef: BlockUserDto, + execute: (instance, data) => chatController.blockUser(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); }); } diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index 9998c6a7..3f50f06d 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1191,14 +1191,26 @@ export class ChatwootService { await waInstance.connectToWhatsapp(number); } else { this.logger.verbose('whatsapp already connected'); - await this.createBotMessage(instance, `🚨 ${body.inbox.name} instance is connected.`, 'incoming'); + await this.createBotMessage( + instance, + i18next.t('cw.inbox.alreadyConnected', { + inboxName: body.inbox.name, + }), + 'incoming', + ); } } if (command === 'clearcache') { this.logger.verbose('command clearcache found'); waInstance.clearCacheChatwoot(); - await this.createBotMessage(instance, `✅ ${body.inbox.name} instance cache cleared.`, 'incoming'); + await this.createBotMessage( + instance, + i18next.t('cw.inbox.clearCache', { + inboxName: body.inbox.name, + }), + 'incoming', + ); } if (command === 'status') { @@ -1208,19 +1220,34 @@ export class ChatwootService { if (!state) { this.logger.verbose('state not found'); - await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance not found.`, 'incoming'); + await this.createBotMessage( + instance, + i18next.t('cw.inbox.notFound', { + inboxName: body.inbox.name, + }), + 'incoming', + ); } if (state) { this.logger.verbose('state: ' + state + ' found'); - await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance status: *${state}*`, 'incoming'); + await this.createBotMessage( + instance, + i18next.t('cw.inbox.status', { + inboxName: body.inbox.name, + state: state, + }), + 'incoming', + ); } } if (command === 'disconnect' || command === 'desconectar') { this.logger.verbose('command disconnect found'); - const msgLogout = `🚨 Disconnecting Whatsapp from inbox *${body.inbox.name}*: `; + const msgLogout = i18next.t('cw.inbox.disconnect', { + inboxName: body.inbox.name, + }); this.logger.verbose('send message to chatwoot'); await this.createBotMessage(instance, msgLogout, 'incoming'); @@ -1509,27 +1536,17 @@ export class ChatwootService { const latitude = result.degreesLatitude; const longitude = result.degreesLongitude; - const locationName = result?.name || 'Unknown'; - const locationAddress = result?.address || 'Unknown'; + const locationName = result?.name; + const locationAddress = result?.address; const formattedLocation = - '*Localização:*\n\n' + - '_Latitude:_ ' + - latitude + - '\n' + - '_Longitude:_ ' + - longitude + - '\n' + - '_Nome:_ ' + - locationName + - '\n' + - '_Endereço:_ ' + - locationAddress + - '\n' + - '_Url:_ https://www.google.com/maps/search/?api=1&query=' + - latitude + - ',' + - longitude; + `*${i18next.t('cw.locationMessage.location')}:*\n\n` + + `_${i18next.t('cw.locationMessage.latitude')}:_ ${latitude} \n` + + `_${i18next.t('cw.locationMessage.longitude')}:_ ${longitude} \n` + + (locationName ? `_${i18next.t('cw.locationMessage.locationName')}:_ ${locationName}\n` : '') + + (locationAddress ? `_${i18next.t('cw.locationMessage.locationAddress')}:_ ${locationAddress} \n` : '') + + `_${i18next.t('cw.locationMessage.locationUrl')}:_ ` + + `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`; this.logger.verbose('message content: ' + formattedLocation); @@ -1547,17 +1564,19 @@ export class ChatwootService { } }); - let formattedContact = '*Contact:*\n\n' + '_Name:_ ' + contactInfo['FN']; + let formattedContact = + `*${i18next.t('cw.contactMessage.contact')}:*\n\n` + + `_${i18next.t('cw.contactMessage.name')}:_ ${contactInfo['FN']}`; let numberCount = 1; Object.keys(contactInfo).forEach((key) => { if (key.startsWith('item') && key.includes('TEL')) { const phoneNumber = contactInfo[key]; - formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber; + formattedContact += `\n_${i18next.t('cw.contactMessage.number')} (${numberCount}):_ ${phoneNumber}`; numberCount++; } else if (key.includes('TEL')) { const phoneNumber = contactInfo[key]; - formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber; + formattedContact += `\n_${i18next.t('cw.contactMessage.number')} (${numberCount}):_ ${phoneNumber}`; numberCount++; } }); @@ -1578,17 +1597,19 @@ export class ChatwootService { } }); - let formattedContact = '*Contact:*\n\n' + '_Name:_ ' + contact.displayName; + let formattedContact = `*${i18next.t('cw.contactMessage.contact')}:*\n\n_${i18next.t( + 'cw.contactMessage.name', + )}:_ ${contact.displayName}`; let numberCount = 1; Object.keys(contactInfo).forEach((key) => { if (key.startsWith('item') && key.includes('TEL')) { const phoneNumber = contactInfo[key]; - formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber; + formattedContact += `\n_${i18next.t('cw.contactMessage.number')} (${numberCount}):_ ${phoneNumber}`; numberCount++; } else if (key.includes('TEL')) { const phoneNumber = contactInfo[key]; - formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber; + formattedContact += `\n_${i18next.t('cw.contactMessage.number')} (${numberCount}):_ ${phoneNumber}`; numberCount++; } }); @@ -2074,7 +2095,10 @@ export class ChatwootService { return; } - const msgStatus = `⚡️ Instance status ${inbox.name}: ${data.status}`; + const msgStatus = i18next.t('cw.inbox.status', { + inboxName: inbox.name, + state: data.status, + }); this.logger.verbose('send message to chatwoot'); await this.createBotMessage(instance, msgStatus, 'incoming'); @@ -2086,7 +2110,7 @@ export class ChatwootService { if (body.status === 'open') { // if we have qrcode count then we understand that a new connection was established if (this.waMonitor.waInstances[instance.instanceName].qrCode.count > 0) { - const msgConnection = `🚀 Connection successfully established!`; + const msgConnection = i18next.t('cw.inbox.connected'); this.logger.verbose('send message to chatwoot'); await this.createBotMessage(instance, msgConnection, 'incoming'); this.waMonitor.waInstances[instance.instanceName].qrCode.count = 0; @@ -2147,7 +2171,7 @@ export class ChatwootService { return; } - this.createBotMessage(instance, `💬 Starting to import messages. Please wait...`, 'incoming'); + this.createBotMessage(instance, i18next.t('cw.import.startImport'), 'incoming'); } public isImportHistoryAvailable() { @@ -2180,7 +2204,7 @@ export class ChatwootService { return; } - this.createBotMessage(instance, '💬 Importing messages. More one moment...', 'incoming'); + this.createBotMessage(instance, i18next.t('cw.import.importingMessages'), 'incoming'); const totalMessagesImported = await chatwootImport.importHistoryMessages( instance, @@ -2191,10 +2215,10 @@ export class ChatwootService { this.updateContactAvatarInRecentConversations(instance); const msg = Number.isInteger(totalMessagesImported) - ? `${totalMessagesImported} messages imported. Refresh page to see the new messages` - : `Something went wrong in importing messages`; + ? i18next.t('cw.import.messagesImported', { totalMessagesImported }) + : i18next.t('cw.import.messagesException'); - this.createBotMessage(instance, `💬 ${msg}`, 'incoming'); + this.createBotMessage(instance, msg, 'incoming'); return totalMessagesImported; } diff --git a/src/whatsapp/services/whatsapp.baileys.service.ts b/src/whatsapp/services/whatsapp.baileys.service.ts index 9989fd3d..1ed58471 100644 --- a/src/whatsapp/services/whatsapp.baileys.service.ts +++ b/src/whatsapp/services/whatsapp.baileys.service.ts @@ -64,6 +64,7 @@ import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-d import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; import { ArchiveChatDto, + BlockUserDto, DeleteMessage, getBase64FromMediaMessageDto, LastMessage, @@ -2817,6 +2818,29 @@ export class BaileysStartupService extends WAStartupService { } } + public async blockUser(data: BlockUserDto) { + this.logger.verbose('Blocking user: ' + data.number); + try { + const { number } = data; + + this.logger.verbose(`Check if number "${number}" is WhatsApp`); + const isWA = (await this.whatsappNumber({ numbers: [number] }))?.shift(); + + this.logger.verbose(`Exists: "${isWA.exists}" | jid: ${isWA.jid}`); + if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) { + throw new BadRequestException(isWA); + } + + const sender = isWA.jid; + + await this.client.updateBlockStatus(sender, data.status); + + return { block: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error blocking user', error.toString()); + } + } + public async updateMessage(data: UpdateMessageDto) { try { const jid = this.createJid(data.number); diff --git a/src/whatsapp/services/whatsapp.business.service.ts b/src/whatsapp/services/whatsapp.business.service.ts index 2b37f362..73b1c299 100644 --- a/src/whatsapp/services/whatsapp.business.service.ts +++ b/src/whatsapp/services/whatsapp.business.service.ts @@ -1159,6 +1159,9 @@ export class BusinessStartupService extends WAStartupService { public async removeProfilePicture() { throw new BadRequestException('Method not available on WhatsApp Business API'); } + public async blockUser() { + throw new BadRequestException('Method not available on WhatsApp Business API'); + } public async updateMessage() { throw new BadRequestException('Method not available on WhatsApp Business API'); } diff --git a/tsconfig.json b/tsconfig.json index 0c89eea1..156bb77c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,5 +18,9 @@ "incremental": true, "noImplicitAny": false }, - "exclude": ["node_modules", "./test", "./dist", "./prisma"] + "exclude": ["node_modules", "./test", "./dist", "./prisma"], + "include": [ + "src/**/*", + "src/**/*.json" + ] } \ No newline at end of file