evolution-api/src/whatsapp/services/whatsapp.business.service.ts
2024-02-18 08:43:40 -03:00

1180 lines
40 KiB
TypeScript

import axios from 'axios';
import { arrayUnique, isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
import fs from 'fs/promises';
import { getMIMEType } from 'node-mime-types';
import { ConfigService, Database, WaBusiness } from '../../config/env.config';
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
import { RedisCache } from '../../libs/redis.client';
import { NumberBusiness } from '../dto/chat.dto';
import {
ContactMessage,
MediaMessage,
Options,
SendButtonDto,
SendContactDto,
SendListDto,
SendLocationDto,
SendMediaDto,
SendReactionDto,
SendTemplateDto,
SendTextDto,
} from '../dto/sendMessage.dto';
import { ContactRaw, MessageRaw, MessageUpdateRaw, SettingsRaw } from '../models';
import { RepositoryBroker } from '../repository/repository.manager';
import { Events, wa } from '../types/wa.types';
import { CacheService } from './cache.service';
import { WAStartupService } from './whatsapp.service';
export class BusinessStartupService extends WAStartupService {
constructor(
public readonly configService: ConfigService,
public readonly eventEmitter: EventEmitter2,
public readonly repository: RepositoryBroker,
public readonly cache: RedisCache,
public readonly chatwootCache: CacheService,
) {
super(configService, eventEmitter, repository, chatwootCache);
this.logger.verbose('BusinessStartupService initialized');
this.cleanStore();
}
public stateConnection: wa.StateConnection = { state: 'open' };
private phoneNumber: string;
public get connectionStatus() {
this.logger.verbose('Getting connection status');
return this.stateConnection;
}
public async closeClient() {
this.stateConnection = { state: 'close' };
}
public get qrCode(): wa.QrCode {
this.logger.verbose('Getting qrcode');
return {
pairingCode: this.instance.qrcode?.pairingCode,
code: this.instance.qrcode?.code,
base64: this.instance.qrcode?.base64,
count: this.instance.qrcode?.count,
};
}
public async logoutInstance() {
this.logger.verbose('Logging out instance');
await this.closeClient();
}
private async post(message: any, params: string) {
try {
const integration = await this.findIntegration();
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
urlServer = `${urlServer}/${version}/${integration.number}/${params}`;
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${integration.token}` };
const result = await axios.post(urlServer, message, { headers });
return result.data;
} catch (e) {
this.logger.error(e);
return e.response.data;
}
}
public async profilePicture(number: string) {
const jid = this.createJid(number);
this.logger.verbose('Getting profile picture with jid: ' + jid);
try {
this.logger.verbose('Getting profile picture url');
return {
wuid: jid,
profilePictureUrl: await this.client.profilePictureUrl(jid, 'image'),
};
} catch (error) {
this.logger.verbose('Profile picture not found');
return {
wuid: jid,
profilePictureUrl: null,
};
}
}
public async getProfileName() {
return null;
}
public async profilePictureUrl() {
return null;
}
public async getProfileStatus() {
return null;
}
public async setWhatsappBusinessProfile(data: NumberBusiness): Promise<any> {
this.logger.verbose('set profile');
const content = {
messaging_product: 'whatsapp',
about: data.about,
address: data.address,
description: data.description,
vertical: data.vertical,
email: data.email,
websites: data.websites,
profile_picture_handle: data.profilehandle,
};
return await this.post(content, 'whatsapp_business_profile');
}
public async connectToWhatsapp(data?: any): Promise<any> {
if (!data) return;
const content = data.entry[0].changes[0].value;
try {
this.loadWebhook();
this.loadChatwoot();
this.loadWebsocket();
this.loadRabbitmq();
this.loadSqs();
this.loadTypebot();
this.loadChamaai();
this.logger.verbose('Creating socket');
this.logger.verbose('Socket created');
this.eventHandler(content);
this.logger.verbose('Socket event handler initialized');
this.phoneNumber = this.createJid(
content.messages ? content.messages[0].from : content.statuses[0]?.recipient_id,
);
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString());
}
}
private async downloadMediaMessage(message: any) {
try {
const integration = await this.findIntegration();
const id = message[message.type].id;
let urlServer = this.configService.get<WaBusiness>('WA_BUSINESS').URL;
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
urlServer = `${urlServer}/${version}/${id}`;
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${integration.token}` };
let result = await axios.get(urlServer, { headers });
result = await axios.get(result.data.url, { headers, responseType: 'arraybuffer' });
return result.data;
} catch (e) {
this.logger.error(e);
}
}
private messageMediaJson(received: any) {
const message = received.messages[0];
let content: any = message.type + 'Message';
content = { [content]: message[message.type] };
message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content;
return content;
}
private messageInteractiveJson(received: any) {
const message = received.messages[0];
const content: any = { conversation: message.interactive[message.interactive.type].title };
message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content;
return content;
}
private messageTextJson(received: any) {
let content: any;
const message = received.messages[0];
if (message.from === received.metadata.phone_number_id) {
content = { extendedTextMessage: { text: message.text.body } };
message.context ? (content.extendedTextMessage.contextInfo = { stanzaId: message.context.id }) : content;
} else {
content = { conversation: message.text.body };
message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content;
}
return content;
}
private messageContactsJson(received: any) {
const message = received.messages[0];
const content: any = {};
const vcard = (contact: any) => {
this.logger.verbose('Creating vcard');
let result =
'BEGIN:VCARD\n' +
'VERSION:3.0\n' +
`N:${contact.name.formatted_name}\n` +
`FN:${contact.name.formatted_name}\n`;
if (contact.org) {
this.logger.verbose('Organization defined');
result += `ORG:${contact.org.company};\n`;
}
if (contact.emails) {
this.logger.verbose('Email defined');
result += `EMAIL:${contact.emails[0].email}\n`;
}
if (contact.urls) {
this.logger.verbose('Url defined');
result += `URL:${contact.urls[0].url}\n`;
}
if (!contact.phones[0]?.wa_id) {
this.logger.verbose('Wuid defined');
contact.phones[0].wa_id = this.createJid(contact.phones[0].phone);
}
result +=
`item1.TEL;waid=${contact.phones[0]?.wa_id}:${contact.phones[0].phone}\n` +
'item1.X-ABLabel:Celular\n' +
'END:VCARD';
this.logger.verbose('Vcard created');
return result;
};
if (message.contacts.length === 1) {
content.contactMessage = {
displayName: message.contacts[0].name.formatted_name,
vcard: vcard(message.contacts[0]),
};
} else {
content.contactsArrayMessage = {
displayName: `${message.length} contacts`,
contacts: message.map((contact) => {
return {
displayName: contact.name.formatted_name,
vcard: vcard(contact),
};
}),
};
}
message.context ? (content.extendedTextMessage = { contextInfo: { stanzaId: message.context.id } }) : content;
return content;
}
private async renderMessageType(type: string) {
let messageType: string;
switch (type) {
case 'image':
messageType = 'imageMessage';
break;
case 'video':
messageType = 'videoMessage';
break;
case 'audio':
messageType = 'audioMessage';
break;
case 'document':
messageType = 'documentMessage';
break;
default:
messageType = 'imageMessage';
break;
}
return messageType;
}
protected async messageHandle(received: any, database: Database, settings: SettingsRaw) {
try {
let messageRaw: MessageRaw;
let pushName: any;
if (received.contacts) pushName = received.contacts[0].profile.name;
if (received.messages) {
const key = {
id: received.messages[0].id,
remoteJid: this.phoneNumber,
fromMe: received.messages[0].from === received.metadata.phone_number_id,
};
if (
received?.messages[0].document ||
received?.messages[0].image ||
received?.messages[0].audio ||
received?.messages[0].video
) {
const buffer = await this.downloadMediaMessage(received?.messages[0]);
messageRaw = {
key,
pushName,
message: {
...this.messageMediaJson(received),
base64: buffer ? buffer.toString('base64') : undefined,
},
messageType: await this.renderMessageType(received.messages[0].type),
messageTimestamp: received.messages[0].timestamp as number,
owner: this.instance.name,
// source: getDevice(received.key.id),
};
} else if (received?.messages[0].interactive) {
messageRaw = {
key,
pushName,
message: {
...this.messageInteractiveJson(received),
},
messageType: 'conversation',
messageTimestamp: received.messages[0].timestamp as number,
owner: this.instance.name,
// source: getDevice(received.key.id),
};
} else if (received?.messages[0].contacts) {
messageRaw = {
key,
pushName,
message: {
...this.messageContactsJson(received),
},
messageType: 'conversation',
messageTimestamp: received.messages[0].timestamp as number,
owner: this.instance.name,
// source: getDevice(received.key.id),
};
} else {
messageRaw = {
key,
pushName,
message: this.messageTextJson(received),
messageType: received.messages[0].type,
messageTimestamp: received.messages[0].timestamp as number,
owner: this.instance.name,
//source: getDevice(received.key.id),
};
}
if (this.localSettings.read_messages && received.key.id !== 'status@broadcast') {
// await this.client.readMessages([received.key]);
}
if (this.localSettings.read_status && received.key.id === 'status@broadcast') {
// await this.client.readMessages([received.key]);
}
this.logger.log(messageRaw);
this.logger.verbose('Sending data to webhook in event MESSAGES_UPSERT');
this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw);
if (this.localChatwoot.enabled) {
const chatwootSentMessage = await this.chatwootService.eventWhatsapp(
Events.MESSAGES_UPSERT,
{ instanceName: this.instance.name },
messageRaw,
);
if (chatwootSentMessage?.id) {
messageRaw.chatwoot = {
messageId: chatwootSentMessage.id,
inboxId: chatwootSentMessage.inbox_id,
conversationId: chatwootSentMessage.conversation_id,
};
}
}
const typebotSessionRemoteJid = this.localTypebot.sessions?.find(
(session) => session.remoteJid === key.remoteJid,
);
if (this.localTypebot.enabled || typebotSessionRemoteJid) {
if (!(this.localTypebot.listening_from_me === false && key.fromMe === true)) {
if (messageRaw.messageType !== 'reactionMessage')
await this.typebotService.sendTypebot(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
messageRaw,
);
}
}
if (this.localChamaai.enabled && messageRaw.key.fromMe === false && received?.message.type === 'notify') {
await this.chamaaiService.sendChamaai(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
messageRaw,
);
}
this.logger.verbose('Inserting message in database');
await this.repository.message.insert([messageRaw], this.instance.name, database.SAVE_DATA.NEW_MESSAGE);
this.logger.verbose('Verifying contact from message');
const contact = await this.repository.contact.find({
where: { owner: this.instance.name, id: key.remoteJid },
});
const contactRaw: ContactRaw = {
id: received.contacts[0].profile.phone,
pushName,
//profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl,
owner: this.instance.name,
};
if (contactRaw.id === 'status@broadcast') {
this.logger.verbose('Contact is status@broadcast');
return;
}
if (contact?.length) {
this.logger.verbose('Contact found in database');
const contactRaw: ContactRaw = {
id: received.contacts[0].profile.phone,
pushName,
//profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl,
owner: this.instance.name,
};
this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE');
this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw);
if (this.localChatwoot.enabled) {
await this.chatwootService.eventWhatsapp(
Events.CONTACTS_UPDATE,
{ instanceName: this.instance.name },
contactRaw,
);
}
this.logger.verbose('Updating contact in database');
await this.repository.contact.update([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS);
return;
}
this.logger.verbose('Contact not found in database');
this.logger.verbose('Sending data to webhook in event CONTACTS_UPSERT');
this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw);
this.logger.verbose('Inserting contact in database');
this.repository.contact.insert([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS);
}
this.logger.verbose('Event received: messages.update');
if (received.statuses) {
for await (const item of received.statuses) {
const key = {
id: item.id,
remoteJid: this.phoneNumber,
fromMe: this.phoneNumber === received.metadata.phone_number_id,
};
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');
if (item.status === 'read' && !key.fromMe) return;
if (item.message === null && item.status === undefined) {
this.logger.verbose('Message deleted');
this.logger.verbose('Sending data to webhook in event MESSAGE_DELETE');
this.sendDataWebhook(Events.MESSAGES_DELETE, key);
const message: MessageUpdateRaw = {
...key,
status: 'DELETED',
datetime: Date.now(),
owner: this.instance.name,
};
this.logger.verbose(message);
this.logger.verbose('Inserting message in database');
await this.repository.messageUpdate.insert(
[message],
this.instance.name,
database.SAVE_DATA.MESSAGE_UPDATE,
);
if (this.localChatwoot.enabled) {
this.chatwootService.eventWhatsapp(
Events.MESSAGES_DELETE,
{ instanceName: this.instance.name },
{ key: key },
);
}
return;
}
const message: MessageUpdateRaw = {
...key,
status: item.status.toUpperCase(),
datetime: Date.now(),
owner: this.instance.name,
};
this.logger.verbose(message);
this.logger.verbose('Sending data to webhook in event MESSAGES_UPDATE');
this.sendDataWebhook(Events.MESSAGES_UPDATE, message);
this.logger.verbose('Inserting message in database');
this.repository.messageUpdate.insert([message], this.instance.name, database.SAVE_DATA.MESSAGE_UPDATE);
}
}
}
} catch (error) {
this.logger.error(error);
}
}
protected async eventHandler(content: any) {
this.logger.verbose('Initializing event handler');
const database = this.configService.get<Database>('DATABASE');
const settings = await this.findSettings();
this.logger.verbose('Listening event: messages.statuses');
this.messageHandle(content, database, settings);
}
protected async sendMessageWithTyping(number: string, message: any, options?: Options, isChatwoot = false) {
this.logger.verbose('Sending message with typing');
try {
let quoted: any;
const linkPreview = options?.linkPreview != false ? undefined : false;
if (options?.quoted) {
const m = options?.quoted;
const msg = m?.key;
if (!msg) {
throw 'Message not found';
}
quoted = msg;
this.logger.verbose('Quoted message');
}
let content: any;
const messageSent = await (async () => {
if (message['reactionMessage']) {
this.logger.verbose('Sending reaction');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
type: 'reaction',
to: number.replace(/\D/g, ''),
reaction: {
message_id: message['reactionMessage']['key']['id'],
emoji: message['reactionMessage']['text'],
},
context: { message_id: quoted.id },
};
quoted ? (content.context = { message_id: quoted.id }) : content;
return await this.post(content, 'messages');
}
if (message['locationMessage']) {
this.logger.verbose('Sending message');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
type: 'location',
to: number.replace(/\D/g, ''),
location: {
longitude: message['locationMessage']['degreesLongitude'],
latitude: message['locationMessage']['degreesLatitude'],
name: message['locationMessage']['name'],
address: message['locationMessage']['address'],
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
return await this.post(content, 'messages');
}
if (message['contacts']) {
this.logger.verbose('Sending message');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
type: 'contacts',
to: number.replace(/\D/g, ''),
contacts: message['contacts'],
};
quoted ? (content.context = { message_id: quoted.id }) : content;
message = message['message'];
return await this.post(content, 'messages');
}
if (message['conversation']) {
this.logger.verbose('Sending message');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
type: 'text',
to: number.replace(/\D/g, ''),
text: {
body: message['conversation'],
preview_url: linkPreview,
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
return await this.post(content, 'messages');
}
if (message['media']) {
this.logger.verbose('Sending message');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
type: message['mediaType'],
to: number.replace(/\D/g, ''),
[message['mediaType']]: {
[message['type']]: message['id'],
preview_url: linkPreview,
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
return await this.post(content, 'messages');
}
if (message['buttons']) {
this.logger.verbose('Sending message');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: number.replace(/\D/g, ''),
type: 'interactive',
interactive: {
type: 'button',
body: {
text: message['text'] || 'Select',
},
action: {
buttons: message['buttons'],
},
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
let formattedText = '';
for (const item of message['buttons']) {
formattedText += `▶️ ${item.reply?.title}\n`;
}
message = { conversation: `${message['text'] || 'Select'}\n` + formattedText };
return await this.post(content, 'messages');
}
if (message['sections']) {
this.logger.verbose('Sending message');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: number.replace(/\D/g, ''),
type: 'interactive',
interactive: {
type: 'list',
header: {
type: 'text',
text: message['title'],
},
body: {
text: message['text'],
},
footer: {
text: message['footerText'],
},
action: {
button: message['buttonText'],
sections: message['sections'],
},
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
let formattedText = '';
for (const section of message['sections']) {
formattedText += `${section?.title}\n`;
for (const row of section.rows) {
formattedText += `${row?.title}\n`;
}
}
message = { conversation: `${message['title']}\n` + formattedText };
return await this.post(content, 'messages');
}
if (message['template']) {
this.logger.verbose('Sending message');
content = {
messaging_product: 'whatsapp',
recipient_type: 'individual',
to: number.replace(/\D/g, ''),
type: 'template',
template: {
name: message['template']['name'],
language: {
code: message['template']['language'] || 'en_US',
},
components: message['template']['components'],
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
message = { conversation: `▶️${message['template']['name']}◀️` };
return await this.post(content, 'messages');
}
})();
const messageRaw: MessageRaw = {
key: { fromMe: true, id: messageSent?.messages[0]?.id, remoteJid: this.createJid(number) },
//pushName: messageSent.pushName,
message,
messageType: content.type,
messageTimestamp: (messageSent?.messages[0]?.timestamp as number) || Math.round(new Date().getTime() / 1000),
owner: this.instance.name,
//ource: getDevice(messageSent.key.id),
};
this.logger.log(messageRaw);
this.logger.verbose('Sending data to webhook in event SEND_MESSAGE');
this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw);
if (this.localChatwoot.enabled && !isChatwoot) {
this.chatwootService.eventWhatsapp(Events.SEND_MESSAGE, { instanceName: this.instance.name }, messageRaw);
}
this.logger.verbose('Inserting message in database');
await this.repository.message.insert(
[messageRaw],
this.instance.name,
this.configService.get<Database>('DATABASE').SAVE_DATA.NEW_MESSAGE,
);
return messageRaw;
} catch (error) {
this.logger.error(error);
console.log(error.data);
throw new BadRequestException(error.toString());
}
}
// Send Message Controller
public async textMessage(data: SendTextDto, isChatwoot = false) {
this.logger.verbose('Sending text message');
const res = await this.sendMessageWithTyping(
data.number,
{
conversation: data.textMessage.text,
},
data?.options,
isChatwoot,
);
return res;
}
private async getIdMedia(mediaMessage: any) {
const integration = await this.findIntegration();
const formData = new FormData();
const arquivoBuffer = await fs.readFile(mediaMessage.media);
const arquivoBlob = new Blob([arquivoBuffer], { type: mediaMessage.mimetype });
formData.append('file', arquivoBlob);
formData.append('typeFile', mediaMessage.mimetype);
formData.append('messaging_product', 'whatsapp');
const headers = { Authorization: `Bearer ${integration.token}` };
const res = await axios.post(
process.env.API_URL + '/' + process.env.VERSION + '/' + integration.number + '/media',
formData,
{ headers },
);
return res.data.id;
}
protected async prepareMediaMessage(mediaMessage: MediaMessage) {
try {
this.logger.verbose('Preparing media message');
const mediaType = mediaMessage.mediatype + 'Message';
this.logger.verbose('Media type: ' + mediaType);
if (mediaMessage.mediatype === 'document' && !mediaMessage.fileName) {
this.logger.verbose('If media type is document and file name is not defined then');
const regex = new RegExp(/.*\/(.+?)\./);
const arrayMatch = regex.exec(mediaMessage.media);
mediaMessage.fileName = arrayMatch[1];
this.logger.verbose('File name: ' + mediaMessage.fileName);
}
if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) {
mediaMessage.fileName = 'image.png';
}
if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) {
mediaMessage.fileName = 'video.mp4';
}
let mimetype: string;
const prepareMedia: any = {
caption: mediaMessage?.caption,
fileName: mediaMessage.fileName,
mediaType: mediaMessage.mediatype,
media: mediaMessage.media,
gifPlayback: false,
};
if (mediaMessage.mimetype) {
mimetype = mediaMessage.mimetype;
} else {
if (isURL(mediaMessage.media)) {
prepareMedia.id = mediaMessage.media;
prepareMedia.type = 'link';
} else {
mimetype = getMIMEType(mediaMessage.fileName);
const id = await this.getIdMedia(prepareMedia);
prepareMedia.id = id;
prepareMedia.type = 'id';
}
}
prepareMedia.mimetype = mimetype;
this.logger.verbose('Generating wa message from content');
return prepareMedia;
} catch (error) {
this.logger.error(error);
throw new InternalServerErrorException(error?.toString() || error);
}
}
public async mediaMessage(data: SendMediaDto, isChatwoot = false) {
this.logger.verbose('Sending media message');
const message = await this.prepareMediaMessage(data.mediaMessage);
return await this.sendMessageWithTyping(data.number, { ...message }, data?.options, isChatwoot);
}
public async buttonMessage(data: SendButtonDto) {
this.logger.verbose('Sending button message');
const embeddedMedia: any = {};
let mediatype = 'TEXT';
if (data.buttonMessage?.mediaMessage) {
mediatype = data.buttonMessage.mediaMessage?.mediatype.toUpperCase() ?? 'TEXT';
embeddedMedia.mediaKey = mediatype.toLowerCase() + 'Message';
const generate = await this.prepareMediaMessage(data.buttonMessage.mediaMessage);
embeddedMedia.message = generate.message[embeddedMedia.mediaKey];
embeddedMedia.contentText = `*${data.buttonMessage.title}*\n\n${data.buttonMessage.description}`;
}
const btnItems = {
text: data.buttonMessage.buttons.map((btn) => btn.buttonText),
ids: data.buttonMessage.buttons.map((btn) => btn.buttonId),
};
if (!arrayUnique(btnItems.text) || !arrayUnique(btnItems.ids)) {
throw new BadRequestException('Button texts cannot be repeated', 'Button IDs cannot be repeated.');
}
return await this.sendMessageWithTyping(
data.number,
{
text: !embeddedMedia?.mediaKey ? data.buttonMessage.title : undefined,
buttons: data.buttonMessage.buttons.map((button) => {
return {
type: 'reply',
reply: {
title: button.buttonText,
id: button.buttonId,
},
};
}),
[embeddedMedia?.mediaKey]: embeddedMedia?.message,
},
data?.options,
);
}
public async locationMessage(data: SendLocationDto) {
this.logger.verbose('Sending location message');
return await this.sendMessageWithTyping(
data.number,
{
locationMessage: {
degreesLatitude: data.locationMessage.latitude,
degreesLongitude: data.locationMessage.longitude,
name: data.locationMessage?.name,
address: data.locationMessage?.address,
},
},
data?.options,
);
}
public async listMessage(data: SendListDto) {
this.logger.verbose('Sending list message');
const sectionsItems = {
title: data.listMessage.sections.map((list) => list.title),
};
if (!arrayUnique(sectionsItems.title)) {
throw new BadRequestException('Section tiles cannot be repeated');
}
return await this.sendMessageWithTyping(
data.number,
{
title: data.listMessage.title,
text: data.listMessage.description,
footerText: data.listMessage?.footerText,
buttonText: data.listMessage?.buttonText,
sections: data.listMessage.sections.map((section) => {
return {
title: section.title,
rows: section.rows.map((row) => {
return {
title: row.title,
description: row.description,
id: row.rowId,
};
}),
};
}),
},
data?.options,
);
}
public async templateMessage(data: SendTemplateDto, isChatwoot = false) {
this.logger.verbose('Sending text message');
const res = await this.sendMessageWithTyping(
data.number,
{
template: {
name: data.templateMessage.name,
language: data.templateMessage.language,
components: data.templateMessage.components,
},
},
data?.options,
isChatwoot,
);
return res;
}
public async contactMessage(data: SendContactDto) {
this.logger.verbose('Sending contact message');
const message: any = {};
const vcard = (contact: ContactMessage) => {
this.logger.verbose('Creating vcard');
let result = 'BEGIN:VCARD\n' + 'VERSION:3.0\n' + `N:${contact.fullName}\n` + `FN:${contact.fullName}\n`;
if (contact.organization) {
this.logger.verbose('Organization defined');
result += `ORG:${contact.organization};\n`;
}
if (contact.email) {
this.logger.verbose('Email defined');
result += `EMAIL:${contact.email}\n`;
}
if (contact.url) {
this.logger.verbose('Url defined');
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' + 'END:VCARD';
this.logger.verbose('Vcard created');
return result;
};
if (data.contactMessage.length === 1) {
message.contactMessage = {
displayName: data.contactMessage[0].fullName,
vcard: vcard(data.contactMessage[0]),
};
} else {
message.contactsArrayMessage = {
displayName: `${data.contactMessage.length} contacts`,
contacts: data.contactMessage.map((contact) => {
return {
displayName: contact.fullName,
vcard: vcard(contact),
};
}),
};
}
return await this.sendMessageWithTyping(
data.number,
{
contacts: data.contactMessage.map((contact) => {
return {
name: { formatted_name: contact.fullName, first_name: contact.fullName },
phones: [{ phone: contact.phoneNumber }],
urls: [{ url: contact.url }],
emails: [{ email: contact.email }],
org: { company: contact.organization },
};
}),
message,
},
data?.options,
);
}
public async reactionMessage(data: SendReactionDto) {
this.logger.verbose('Sending reaction message');
return await this.sendMessageWithTyping(data.reactionMessage.key.remoteJid, {
reactionMessage: {
key: data.reactionMessage.key,
text: data.reactionMessage.reaction,
},
});
}
public async getBase64FromMediaMessage(data: any) {
try {
const msg = data.message;
this.logger.verbose('Getting base64 from media message');
const messageType = msg.messageType + 'Message';
const mediaMessage = msg.message[messageType];
this.logger.verbose('Media message downloaded');
return {
mediaType: msg.messageType,
fileName: mediaMessage?.fileName,
caption: mediaMessage?.caption,
size: {
fileLength: mediaMessage?.fileLength,
height: mediaMessage?.fileLength,
width: mediaMessage?.width,
},
mimetype: mediaMessage?.mime_type,
base64: msg.message.base64,
};
} catch (error) {
this.logger.error(error);
throw new BadRequestException(error.toString());
}
}
// methods not available on WhatsApp Business API
public async mediaSticker() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async audioWhatsapp() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async pollMessage() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async statusMessage() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async reloadConnection() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async whatsappNumber() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async markMessageAsRead() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async archiveChat() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async deleteMessage() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async fetchProfile() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async sendPresence() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async fetchPrivacySettings() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updatePrivacySettings() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async fetchBusinessProfile() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateProfileName() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateProfileStatus() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateProfilePicture() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async removeProfilePicture() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateMessage() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async createGroup() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateGroupPicture() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateGroupSubject() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateGroupDescription() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async findGroup() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async fetchAllGroups() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async inviteCode() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async inviteInfo() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async sendInvite() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async acceptInviteCode() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async revokeInviteCode() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async findParticipants() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateGParticipant() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async updateGSetting() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async toggleEphemeral() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async leaveGroup() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async fetchLabels() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
public async handleLabel() {
throw new BadRequestException('Method not available on WhatsApp Business API');
}
}