mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-26 18:38:39 -06:00
added: Integration with whatsapp business api
This commit is contained in:
parent
1a2546c52b
commit
1686ef58cf
@ -46,7 +46,7 @@
|
||||
"@figuro/chatwoot-sdk": "^1.1.16",
|
||||
"@hapi/boom": "^10.0.1",
|
||||
"@sentry/node": "^7.59.2",
|
||||
"@whiskeysockets/baileys": "^6.5.0",
|
||||
"@whiskeysockets/baileys": "github:PurpShell/Baileys#combined",
|
||||
"amqplib": "^0.10.3",
|
||||
"aws-sdk": "^2.1499.0",
|
||||
"axios": "^1.3.5",
|
||||
|
@ -120,6 +120,7 @@ export type EventsWebhook = {
|
||||
|
||||
export type ApiKey = { KEY: string };
|
||||
export type Jwt = { EXPIRIN_IN: number; SECRET: string };
|
||||
export type WABussiness = { ACESS_TOKEN: string, URL: string, VERSION: string, LANGUAGE: string };
|
||||
|
||||
export type Auth = {
|
||||
API_KEY: ApiKey;
|
||||
@ -161,6 +162,7 @@ export interface Env {
|
||||
TYPEBOT: Typebot;
|
||||
AUTHENTICATION: Auth;
|
||||
PRODUCTION?: Production;
|
||||
WABUSSINESS: WABussiness;
|
||||
}
|
||||
|
||||
export type Key = keyof Env;
|
||||
@ -337,6 +339,12 @@ export class ConfigService {
|
||||
SECRET: process.env.AUTHENTICATION_JWT_SECRET || 'L=0YWt]b2w[WF>#>:&E`',
|
||||
},
|
||||
},
|
||||
WABUSSINESS: {
|
||||
ACESS_TOKEN: process.env.ACESS_TOKEN,
|
||||
URL: process.env.URL,
|
||||
VERSION: process.env.VERSION,
|
||||
LANGUAGE: process.env.LANGUAGE
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ export class Logger {
|
||||
|
||||
function salvarLog(env: any, log: string): void {
|
||||
mkdir(env.LOG_PATH, { recursive: true }, (err) => { if (err) throw err; });
|
||||
let file = new Date().toLocaleDateString().replaceAll('/', '');
|
||||
let file = new Date().toLocaleDateString('pt-BR').replaceAll('/', '');
|
||||
file = env.LOG_PATH + '/' + file + '.txt';
|
||||
try {
|
||||
if (fs.existsSync(file)) {
|
||||
@ -176,9 +176,9 @@ function excluirArquivosAntigos(path: string, diasLimite: number): void {
|
||||
data.setDate(data.getDate() - diasLimite);
|
||||
fs.readdirSync(path).forEach((nomeArquivo) => {
|
||||
let Timerfile = new Date(parseInt(nomeArquivo.substring(8, 4)),
|
||||
parseInt(nomeArquivo.substring(4, 2)), parseInt(nomeArquivo.substring(2, 0)))
|
||||
1-parseInt(nomeArquivo.substring(4, 2)), parseInt(nomeArquivo.substring(2, 0)))
|
||||
|
||||
if (Timerfile.getTime() < data.getTime()) {
|
||||
if ((Timerfile.getTime() > 0) && (Timerfile.getTime() < data.getTime())) {
|
||||
fs.unlinkSync(path + '/' + nomeArquivo);
|
||||
console.log(`Arquivo ${nomeArquivo} excluído.`);
|
||||
}
|
||||
|
@ -5,10 +5,10 @@ const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
@ -389,6 +389,25 @@ export const listMessageSchema: JSONSchema7 = {
|
||||
required: ['number', 'listMessage'],
|
||||
};
|
||||
|
||||
export const templateMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { ...numberDefinition },
|
||||
options: { ...optionsSchema },
|
||||
templateMessage: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
language: { type: 'string' },
|
||||
},
|
||||
required: ['name', 'language'],
|
||||
...isNotEmpty('name', 'language'),
|
||||
},
|
||||
},
|
||||
required: ['templateMessage', 'number'],
|
||||
};
|
||||
|
||||
export const contactMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@ -601,6 +620,28 @@ export const profilePictureSchema: JSONSchema7 = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export const profileBusinessSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
about: { type: 'string' },
|
||||
address: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
vertical: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
profile_picture_handle: { type: 'string' },
|
||||
websites: {
|
||||
type: 'array',
|
||||
minItems: 1,
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const profileSchema: JSONSchema7 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
WhatsAppNumberDto,
|
||||
NumberBusiness,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ContactQuery } from '../repository/contact.repository';
|
||||
@ -117,4 +118,9 @@ export class ChatController {
|
||||
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].removeProfilePicture();
|
||||
}
|
||||
|
||||
public async setWhatsappBusinessProfile({ instanceName }: InstanceDto, data: NumberBusiness) {
|
||||
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].setWhatsappBusinessProfile(data);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { isURL } from 'class-validator';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ConfigService, HttpServer } from '../../config/env.config';
|
||||
import { ConfigService, HttpServer, WABussiness } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
@ -19,8 +19,8 @@ import { SqsService } from '../services/sqs.service';
|
||||
import { TypebotService } from '../services/typebot.service';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
import { WebsocketService } from '../services/websocket.service';
|
||||
import { WAStartupService } from '../services/whatsapp.service';
|
||||
import { Events, wa } from '../types/wa.types';
|
||||
import { Events, wa, Integration } from '../types/wa.types';
|
||||
import { WAStartupClass } from '../whatsapp.module';
|
||||
|
||||
export class InstanceController {
|
||||
constructor(
|
||||
@ -50,6 +50,7 @@ export class InstanceController {
|
||||
events,
|
||||
qrcode,
|
||||
number,
|
||||
integration,
|
||||
token,
|
||||
chatwoot_account_id,
|
||||
chatwoot_token,
|
||||
@ -85,9 +86,10 @@ export class InstanceController {
|
||||
await this.authService.checkDuplicateToken(token);
|
||||
|
||||
this.logger.verbose('creating instance');
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
const instance = new WAStartupClass[integration](this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
instance.instanceName = instanceName;
|
||||
|
||||
instance.instanceNumber = number;
|
||||
instance.instanceToken = token;
|
||||
const instanceId = v4();
|
||||
|
||||
instance.sendDataWebhook(Events.INSTANCE_CREATE, {
|
||||
@ -359,20 +361,30 @@ export class InstanceController {
|
||||
|
||||
this.settingsService.create(instance, settings);
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
let webhook_url = '', acess_token = '';
|
||||
if(integration === Integration.WHATSAPP_BUSINESS)
|
||||
{
|
||||
webhook_url = `${urlServer}/webhook/whatsapp/${encodeURIComponent(instance.instanceName)}`;
|
||||
acess_token = this.configService.get<WABussiness>('WABUSSINESS').ACESS_TOKEN;
|
||||
}
|
||||
|
||||
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
|
||||
let getQrcode: wa.QrCode;
|
||||
|
||||
|
||||
await this.waMonitor.saveInstance({integration, instanceName, token, number});
|
||||
if (qrcode) {
|
||||
this.logger.verbose('creating qrcode');
|
||||
await instance.connectToWhatsapp(number);
|
||||
await delay(5000);
|
||||
getQrcode = instance.qrCode;
|
||||
}
|
||||
|
||||
const result = {
|
||||
instance: {
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
integration: integration,
|
||||
status: 'created',
|
||||
},
|
||||
hash,
|
||||
@ -405,6 +417,7 @@ export class InstanceController {
|
||||
listening_from_me: typebot_listening_from_me,
|
||||
},
|
||||
settings,
|
||||
webhook_url: webhook_url,
|
||||
qrcode: getQrcode,
|
||||
proxy,
|
||||
};
|
||||
@ -423,6 +436,10 @@ export class InstanceController {
|
||||
throw new BadRequestException('token is required');
|
||||
}
|
||||
|
||||
if (!integration) {
|
||||
throw new BadRequestException('integration is required');
|
||||
}
|
||||
|
||||
if (!chatwoot_url) {
|
||||
throw new BadRequestException('url is required');
|
||||
}
|
||||
@ -443,8 +460,6 @@ export class InstanceController {
|
||||
throw new BadRequestException('conversation_pending is required');
|
||||
}
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
try {
|
||||
this.chatwootService.create(instance, {
|
||||
enabled: true,
|
||||
@ -617,7 +632,7 @@ export class InstanceController {
|
||||
await this.waMonitor.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName);
|
||||
|
||||
this.logger.verbose('close connection instance: ' + instanceName);
|
||||
this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
|
||||
this.waMonitor.waInstances[instanceName]?.closeClient();
|
||||
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
|
||||
} catch (error) {
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTextDto,
|
||||
SendTemplateDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
@ -86,6 +87,11 @@ export class SendMessageController {
|
||||
return await this.waMonitor.waInstances[instanceName].listMessage(data);
|
||||
}
|
||||
|
||||
public async sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) {
|
||||
logger.verbose('requested sendList from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].templateMessage(data);
|
||||
}
|
||||
|
||||
public async sendContact({ instanceName }: InstanceDto, data: SendContactDto) {
|
||||
logger.verbose('requested sendContact from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].contactMessage(data);
|
||||
|
@ -5,11 +5,19 @@ import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { WebhookDto } from '../dto/webhook.dto';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
import { ConfigService } from '../../config/env.config';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
const logger = new Logger('WebhookController');
|
||||
|
||||
export class WebhookController {
|
||||
constructor(private readonly webhookService: WebhookService) {}
|
||||
constructor(
|
||||
private readonly webhookService: WebhookService,
|
||||
private readonly waMonitor: WAMonitoringService,
|
||||
) {}
|
||||
|
||||
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
|
||||
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
|
||||
@ -61,4 +69,9 @@ export class WebhookController {
|
||||
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
|
||||
return this.webhookService.find(instance);
|
||||
}
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].connectToWhatsapp(data);
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,11 @@ export class NumberBusiness {
|
||||
message?: string;
|
||||
description?: string;
|
||||
email?: string;
|
||||
website?: string[];
|
||||
websites?: string[];
|
||||
address?: string;
|
||||
about?: string;
|
||||
vertical?: string;
|
||||
profilehandle?: string;
|
||||
}
|
||||
|
||||
export class ProfileNameDto {
|
||||
|
@ -3,6 +3,7 @@ export class InstanceDto {
|
||||
instanceId?: string;
|
||||
qrcode?: boolean;
|
||||
number?: string;
|
||||
integration?: string;
|
||||
token?: string;
|
||||
webhook?: string;
|
||||
webhook_by_events?: boolean;
|
||||
|
@ -134,6 +134,15 @@ export class SendListDto extends Metadata {
|
||||
listMessage: ListMessage;
|
||||
}
|
||||
|
||||
export class TemplateMessage {
|
||||
name: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
export class SendTemplateDto extends Metadata {
|
||||
templateMessage: TemplateMessage;
|
||||
}
|
||||
|
||||
export class ContactMessage {
|
||||
fullName: string;
|
||||
wuid: string;
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
profileStatusSchema,
|
||||
readMessageSchema,
|
||||
whatsappNumberSchema,
|
||||
profileBusinessSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
@ -29,6 +30,7 @@ import {
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
WhatsAppNumberDto,
|
||||
NumberBusiness,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ContactQuery } from '../repository/contact.repository';
|
||||
@ -213,6 +215,23 @@ export class ChatRouter extends RouterBroker {
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('setWhatsappBusinessProfile'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findStatusMessage');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<NumberBusiness>({
|
||||
request: req,
|
||||
schema: messageUpSchema,
|
||||
ClassRef: NumberBusiness,
|
||||
execute: (instance, data) => chatController.setWhatsappBusinessProfile(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('findChats'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findChats');
|
||||
logger.verbose('request body: ');
|
||||
@ -291,7 +310,7 @@ export class ChatRouter extends RouterBroker {
|
||||
|
||||
const response = await this.dataValidate<ProfilePictureDto>({
|
||||
request: req,
|
||||
schema: profilePictureSchema,
|
||||
schema: profileBusinessSchema,
|
||||
ClassRef: ProfilePictureDto,
|
||||
execute: (instance, data) => chatController.fetchBusinessProfile(instance, data),
|
||||
});
|
||||
|
@ -53,7 +53,7 @@ router
|
||||
.use('/message', new MessageRouter(...guards).router)
|
||||
.use('/chat', new ChatRouter(...guards).router)
|
||||
.use('/group', new GroupRouter(...guards).router)
|
||||
.use('/webhook', new WebhookRouter(...guards).router)
|
||||
.use('/webhook', new WebhookRouter(configService, ...guards).router)
|
||||
.use('/chatwoot', new ChatwootRouter(...guards).router)
|
||||
.use('/settings', new SettingsRouter(...guards).router)
|
||||
.use('/websocket', new WebsocketRouter(...guards).router)
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
statusMessageSchema,
|
||||
stickerMessageSchema,
|
||||
textMessageSchema,
|
||||
templateMessageSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
@ -27,6 +28,7 @@ import {
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTextDto,
|
||||
SendTemplateDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { sendMessageController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
@ -133,6 +135,22 @@ export class MessageRouter extends RouterBroker {
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendTemplate'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendTemplate');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendTemplateDto>({
|
||||
request: req,
|
||||
schema: templateMessageSchema,
|
||||
ClassRef: SendTemplateDto,
|
||||
execute: (instance, data) => sendMessageController.sendTemplate(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendContact'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendContact');
|
||||
logger.verbose('request body: ');
|
||||
|
@ -7,11 +7,12 @@ import { InstanceDto } from '../dto/instance.dto';
|
||||
import { WebhookDto } from '../dto/webhook.dto';
|
||||
import { webhookController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
import { WABussiness, ConfigService } from '../../config/env.config';
|
||||
|
||||
const logger = new Logger('WebhookRouter');
|
||||
|
||||
export class WebhookRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
@ -30,6 +31,32 @@ export class WebhookRouter extends RouterBroker {
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('whatsapp'), async (req, res) => {
|
||||
logger.verbose('request received in findChatwoot');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance, data) => webhookController.receiveWebhook(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('whatsapp'), async (req, res) => {
|
||||
logger.verbose('request received in webhook');
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
if (req.query['hub.verify_token'] === this.configService.get<WABussiness>('WABUSSINESS').ACESS_TOKEN)
|
||||
res.send(req.query['hub.challenge']);
|
||||
else
|
||||
res.send('Error, wrong validation token');
|
||||
logger.verbose('Error, wrong validation token');
|
||||
})
|
||||
.get(this.routerPath('find'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findWebhook');
|
||||
logger.verbose('request body: ');
|
||||
|
@ -6,12 +6,12 @@ import Jimp from 'jimp';
|
||||
import mimeTypes from 'mime-types';
|
||||
import path from 'path';
|
||||
|
||||
import { ConfigService, HttpServer } from '../../config/env.config';
|
||||
import { ConfigService, HttpServer, WABussiness } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ROOT_DIR } from '../../config/path.config';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto';
|
||||
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto, SendTemplateDto } from '../dto/sendMessage.dto';
|
||||
import { MessageRaw } from '../models';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { Events } from '../types/wa.types';
|
||||
@ -457,7 +457,7 @@ export class ChatwootService {
|
||||
if (!findParticipant.name || findParticipant.name === chatId) {
|
||||
await this.updateContact(instance, findParticipant.id, {
|
||||
name: body.pushName,
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
avatar_url: picture_url?.profilePictureUrl || null,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@ -467,7 +467,7 @@ export class ChatwootService {
|
||||
filterInbox.id,
|
||||
false,
|
||||
body.pushName,
|
||||
picture_url.profilePictureUrl || null,
|
||||
picture_url?.profilePictureUrl || null,
|
||||
body.key.participant,
|
||||
);
|
||||
}
|
||||
@ -483,7 +483,7 @@ export class ChatwootService {
|
||||
if (body.key.fromMe) {
|
||||
if (findContact) {
|
||||
contact = await this.updateContact(instance, findContact.id, {
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
avatar_url: picture_url?.profilePictureUrl || null,
|
||||
});
|
||||
} else {
|
||||
const jid = isGroup ? null : body.key.remoteJid;
|
||||
@ -493,7 +493,7 @@ export class ChatwootService {
|
||||
filterInbox.id,
|
||||
isGroup,
|
||||
nameContact,
|
||||
picture_url.profilePictureUrl || null,
|
||||
picture_url?.profilePictureUrl || null,
|
||||
jid,
|
||||
);
|
||||
}
|
||||
@ -502,11 +502,11 @@ export class ChatwootService {
|
||||
if (!findContact.name || findContact.name === chatId) {
|
||||
contact = await this.updateContact(instance, findContact.id, {
|
||||
name: nameContact,
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
avatar_url: picture_url?.profilePictureUrl || null,
|
||||
});
|
||||
} else {
|
||||
contact = await this.updateContact(instance, findContact.id, {
|
||||
avatar_url: picture_url.profilePictureUrl || null,
|
||||
avatar_url: picture_url?.profilePictureUrl || null,
|
||||
});
|
||||
}
|
||||
if (!contact) {
|
||||
@ -520,7 +520,7 @@ export class ChatwootService {
|
||||
filterInbox.id,
|
||||
isGroup,
|
||||
nameContact,
|
||||
picture_url.profilePictureUrl || null,
|
||||
picture_url?.profilePictureUrl || null,
|
||||
jid,
|
||||
);
|
||||
}
|
||||
@ -785,9 +785,9 @@ export class ChatwootService {
|
||||
const replyToIds = await this.getReplyToIds(messageBody, instance);
|
||||
|
||||
if (replyToIds.in_reply_to || replyToIds.in_reply_to_external_id) {
|
||||
data.append('content_attributes', {
|
||||
...replyToIds,
|
||||
});
|
||||
data.append('content_attributes',
|
||||
JSON.stringify(replyToIds),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1023,10 +1023,10 @@ export class ChatwootService {
|
||||
// Chatwoot to Whatsapp
|
||||
const messageReceived = body.content
|
||||
? body.content
|
||||
.replaceAll(/(?<!\*)\*((?!\s)([^\n*]+?)(?<!\s))\*(?!\*)/g, '_$1_') // Substitui * por _
|
||||
.replaceAll(/\*{2}((?!\s)([^\n*]+?)(?<!\s))\*{2}/g, '*$1*') // Substitui ** por *
|
||||
.replaceAll(/~{2}((?!\s)([^\n*]+?)(?<!\s))~{2}/g, '~$1~') // Substitui ~~ por ~
|
||||
.replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ```
|
||||
.replaceAll(/(?<!\*)\*((?!\s)([^\n*]+?)(?<!\s))\*(?!\*)/g, '_$1_') // Substitui * por _
|
||||
.replaceAll(/\*{2}((?!\s)([^\n*]+?)(?<!\s))\*{2}/g, '*$1*') // Substitui ** por *
|
||||
.replaceAll(/~{2}((?!\s)([^\n*]+?)(?<!\s))~{2}/g, '~$1~') // Substitui ~~ por ~
|
||||
.replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ```
|
||||
: body.content;
|
||||
|
||||
const senderName = body?.sender?.available_name || body?.sender?.name;
|
||||
@ -1044,7 +1044,7 @@ export class ChatwootService {
|
||||
limit: 1,
|
||||
});
|
||||
if (message.length && message[0].key?.id) {
|
||||
await waInstance?.client.sendMessage(message[0].key.remoteJid, { delete: message[0].key });
|
||||
await waInstance?.client?.sendMessage(message[0].key.remoteJid, { delete: message[0].key });
|
||||
}
|
||||
return { message: 'bot' };
|
||||
}
|
||||
@ -1123,6 +1123,36 @@ export class ChatwootService {
|
||||
|
||||
this.logger.verbose('Format message to send');
|
||||
let formatText: string;
|
||||
const regex = /^▶️.*◀️$/;
|
||||
if (regex.test(messageReceived)) {
|
||||
const data: SendTemplateDto = {
|
||||
number: chatId,
|
||||
templateMessage: {
|
||||
name: messageReceived.replace(/[^\x20-\x7E]/g, ''),
|
||||
language: this.configService.get<WABussiness>('WABUSSINESS').LANGUAGE,
|
||||
},
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: 'composing',
|
||||
quoted: await this.getQuotedMessage(body, instance),
|
||||
},
|
||||
};
|
||||
|
||||
const messageSent = await waInstance?.templateMessage(data, true);
|
||||
this.updateChatwootMessageId(
|
||||
{
|
||||
...messageSent,
|
||||
owner: instance.instanceName,
|
||||
},
|
||||
{
|
||||
messageId: body.id,
|
||||
inboxId: body.inbox?.id,
|
||||
conversationId: body.conversation?.id,
|
||||
},
|
||||
instance,
|
||||
);
|
||||
return
|
||||
}
|
||||
if (senderName === null || senderName === undefined) {
|
||||
formatText = messageReceived;
|
||||
} else {
|
||||
@ -1137,7 +1167,7 @@ export class ChatwootService {
|
||||
|
||||
for (const message of body.conversation.messages) {
|
||||
this.logger.verbose('check if message is media');
|
||||
if (message.attachments && message.attachments.length > 0) {
|
||||
if (message?.attachments && message?.attachments.length > 0) {
|
||||
this.logger.verbose('message is media');
|
||||
for (const attachment of message.attachments) {
|
||||
this.logger.verbose('send media to whatsapp');
|
||||
@ -1513,9 +1543,9 @@ export class ChatwootService {
|
||||
const originalMessage = await this.getConversationMessage(body.message);
|
||||
const bodyMessage = originalMessage
|
||||
? originalMessage
|
||||
.replaceAll(/\*((?!\s)([^\n*]+?)(?<!\s))\*/g, '**$1**')
|
||||
.replaceAll(/_((?!\s)([^\n_]+?)(?<!\s))_/g, '*$1*')
|
||||
.replaceAll(/~((?!\s)([^\n~]+?)(?<!\s))~/g, '~~$1~~')
|
||||
.replaceAll(/\*((?!\s)([^\n*]+?)(?<!\s))\*/g, '**$1**')
|
||||
.replaceAll(/_((?!\s)([^\n_]+?)(?<!\s))_/g, '*$1*')
|
||||
.replaceAll(/~((?!\s)([^\n~]+?)(?<!\s))~/g, '~~$1~~')
|
||||
: originalMessage;
|
||||
|
||||
this.logger.verbose('body message: ' + bodyMessage);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { execSync } from 'child_process';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { opendirSync, readdirSync, rmSync } from 'fs';
|
||||
import { opendirSync, readdirSync, rmSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { Db } from 'mongodb';
|
||||
import { join } from 'path';
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
} from '../models';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { WAStartupService } from './whatsapp.service';
|
||||
import { WAStartupClass } from '../whatsapp.module';
|
||||
|
||||
export class WAMonitoringService {
|
||||
constructor(
|
||||
@ -359,13 +360,21 @@ export class WAMonitoringService {
|
||||
}
|
||||
|
||||
private async setInstance(name: string) {
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
const path = join(INSTANCE_DIR, name);
|
||||
let values: any;
|
||||
if(this.db.ENABLED )
|
||||
values = await this.dbInstance.collection(name).findOne({ _id: 'integration' })
|
||||
else
|
||||
values = JSON.parse(readFileSync(path + '/integration.json', 'utf8'));
|
||||
const instance = new WAStartupClass[values.integration]
|
||||
(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
instance.instanceName = name;
|
||||
instance.instanceNumber = values.number;
|
||||
instance.instanceToken = values.token;
|
||||
this.waInstances[name] = instance;
|
||||
this.logger.verbose('Instance loaded: ' + name);
|
||||
await instance.connectToWhatsapp();
|
||||
this.logger.verbose('connectToWhatsapp: ' + name);
|
||||
|
||||
this.waInstances[name] = instance;
|
||||
}
|
||||
|
||||
private async loadInstancesFromRedis() {
|
||||
@ -473,4 +482,27 @@ export class WAMonitoringService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async saveInstance(data: any) {
|
||||
this.logger.verbose('Save instance');
|
||||
|
||||
try {
|
||||
let msgParsed = JSON.parse(JSON.stringify(data));
|
||||
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
await this.repository.dbServer.connect();
|
||||
await this.dbInstance.collection(data.instanceName).replaceOne({ _id: 'integration' }, msgParsed,
|
||||
{
|
||||
upsert: true,
|
||||
});
|
||||
} else {
|
||||
const path = join(INSTANCE_DIR, data.instanceName)
|
||||
if (!existsSync(path))
|
||||
mkdirSync(path, { recursive: true });
|
||||
writeFileSync(path + '/integration.json', JSON.stringify(msgParsed));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { Logger } from '../../config/logger.config';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { Session, TypebotDto } from '../dto/typebot.dto';
|
||||
import { MessageRaw } from '../models';
|
||||
import { Events } from '../types/wa.types';
|
||||
import { Events, Integration } from '../types/wa.types';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class TypebotService {
|
||||
@ -405,6 +405,7 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
async function processMessages(instance, messages, input, clientSideActions, eventEmitter) {
|
||||
let qtdMessages = 0, buttonText = '';
|
||||
for (const message of messages) {
|
||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||
|
||||
@ -463,20 +464,24 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: linkPreview,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
qtdMessages++;
|
||||
if (instance?.constructor.name == Integration.WABussinessService &&
|
||||
input?.type === 'choice input' && messages.length == qtdMessages) {
|
||||
buttonText = formattedText;
|
||||
} else {
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: linkPreview,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (message.type === 'image') {
|
||||
await instance.mediaMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
@ -522,27 +527,49 @@ export class TypebotService {
|
||||
|
||||
if (input) {
|
||||
if (input.type === 'choice input') {
|
||||
let formattedText = '';
|
||||
|
||||
const items = input.items;
|
||||
if (instance?.constructor.name == Integration.WABussinessService) {
|
||||
let buttons = [];
|
||||
for (const item of items) {
|
||||
buttons.push({
|
||||
buttonId: item.id,
|
||||
buttonText: item.content,
|
||||
});
|
||||
}
|
||||
await instance.buttonMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: 'composing',
|
||||
linkPreview: false,
|
||||
},
|
||||
buttonMessage: {
|
||||
title: buttonText,
|
||||
buttons,
|
||||
},
|
||||
});
|
||||
|
||||
for (const item of items) {
|
||||
formattedText += `▶️ ${item.content}\n`;
|
||||
} else {
|
||||
let formattedText = '';
|
||||
|
||||
for (const item of items) {
|
||||
formattedText += `▶️ ${item.content}\n`;
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: 'composing',
|
||||
linkPreview: false,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: 1200,
|
||||
presence: 'composing',
|
||||
linkPreview: false,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
eventEmitter.emit('typebot:end', {
|
||||
|
2813
src/whatsapp/services/whatsapp.baileys.service.ts
Normal file
2813
src/whatsapp/services/whatsapp.baileys.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
1098
src/whatsapp/services/whatsapp.business.service.ts
Normal file
1098
src/whatsapp/services/whatsapp.business.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -45,6 +45,9 @@ export declare namespace wa {
|
||||
wuid?: string;
|
||||
profileName?: string;
|
||||
profilePictureUrl?: string;
|
||||
integration?: string;
|
||||
number?: string;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
export type LocalWebHook = {
|
||||
@ -139,3 +142,9 @@ export const MessageSubtype = [
|
||||
'viewOnceMessage',
|
||||
'viewOnceMessageV2',
|
||||
];
|
||||
|
||||
export enum Integration {
|
||||
WHATSAPP_BUSINESS='WHATSAPP-BUSINESS',
|
||||
WHATSAPP_BAILEYS='WHATSAPP-BAILEYS',
|
||||
WABussinessService = 'WABussinessService',
|
||||
}
|
||||
|
@ -58,6 +58,11 @@ import { SqsService } from './services/sqs.service';
|
||||
import { TypebotService } from './services/typebot.service';
|
||||
import { WebhookService } from './services/webhook.service';
|
||||
import { WebsocketService } from './services/websocket.service';
|
||||
import { WABaileysService } from './services/whatsapp.baileys.service';
|
||||
import { WAStartupService } from './services/whatsapp.service';
|
||||
import { WABussinessService } from './services/whatsapp.business.service';
|
||||
import { ConfigService, } from '../config/env.config';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
|
||||
const logger = new Logger('WA MODULE');
|
||||
|
||||
@ -107,7 +112,7 @@ export const typebotController = new TypebotController(typebotService);
|
||||
|
||||
const webhookService = new WebhookService(waMonitor);
|
||||
|
||||
export const webhookController = new WebhookController(webhookService);
|
||||
export const webhookController = new WebhookController(webhookService, waMonitor);
|
||||
|
||||
const websocketService = new WebsocketService(waMonitor);
|
||||
|
||||
@ -157,4 +162,17 @@ export const sendMessageController = new SendMessageController(waMonitor);
|
||||
export const chatController = new ChatController(waMonitor);
|
||||
export const groupController = new GroupController(waMonitor);
|
||||
|
||||
|
||||
export const WAStartupClass: {
|
||||
[key: string]: new (
|
||||
configService: ConfigService,
|
||||
eventEmitter: EventEmitter2,
|
||||
repository: RepositoryBroker,
|
||||
cache: RedisCache,
|
||||
) => WAStartupService
|
||||
} = {
|
||||
'WHATSAPP-BUSINESS': WABussinessService,
|
||||
'WHATSAPP-BAILEYS': WABaileysService,
|
||||
};
|
||||
|
||||
logger.info('Module - ON');
|
||||
|
Loading…
Reference in New Issue
Block a user