diff --git a/Docker/.env.example b/Docker/.env.example index 55c4a765..e2b99a1e 100644 --- a/Docker/.env.example +++ b/Docker/.env.example @@ -92,4 +92,7 @@ AUTHENTICATION_JWT_SECRET='L0YWtjb2w554WFqPG' AUTHENTICATION_INSTANCE_MODE=server # container or server # if you are using container mode, set the container name and the webhook url to default instance AUTHENTICATION_INSTANCE_NAME=evolution -AUTHENTICATION_INSTANCE_WEBHOOK_URL='' \ No newline at end of file +AUTHENTICATION_INSTANCE_WEBHOOK_URL='' +AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1 +AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456 +AUTHENTICATION_INSTANCE_CHATWOOT_URL='' \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c9ef576d..726064e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -88,6 +88,9 @@ ENV AUTHENTICATION_JWT_SECRET="L=0YWt]b2w[WF>#>:&E`" ENV AUTHENTICATION_INSTANCE_NAME=$AUTHENTICATION_INSTANCE_NAME ENV AUTHENTICATION_INSTANCE_WEBHOOK_URL=$AUTHENTICATION_INSTANCE_WEBHOOK_URL +ENV AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=$AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID +ENV AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=$AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN +ENV AUTHENTICATION_INSTANCE_CHATWOOT_URL=$AUTHENTICATION_INSTANCE_CHATWOOT_URL ENV AUTHENTICATION_INSTANCE_MODE=$AUTHENTICATION_INSTANCE_MODE RUN npm install diff --git a/package.json b/package.json index f11b1229..1faf7fb9 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "@adiwajshing/keyed-db": "^0.2.4", "@ffmpeg-installer/ffmpeg": "^1.1.0", + "@figuro/chatwoot-sdk": "^1.1.14", "@hapi/boom": "^10.0.1", "@whiskeysockets/baileys": "github:vphelipe/WhiskeySockets-Baileys#master", "axios": "^1.3.5", diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 6ad188b1..d13eec07 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -98,6 +98,9 @@ export type Instance = { NAME: string; WEBHOOK_URL: string; MODE: string; + CHATWOOT_ACCOUNT_ID: string; + CHATWOOT_TOKEN: string; + CHATWOOT_URL: string; }; export type Auth = { API_KEY: ApiKey; @@ -275,6 +278,9 @@ export class ConfigService { NAME: process.env.AUTHENTICATION_INSTANCE_NAME, WEBHOOK_URL: process.env.AUTHENTICATION_INSTANCE_WEBHOOK_URL, MODE: process.env.AUTHENTICATION_INSTANCE_MODE, + CHATWOOT_ACCOUNT_ID: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID, + CHATWOOT_TOKEN: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN, + CHATWOOT_URL: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_URL, }, }, }; diff --git a/src/dev-env.yml b/src/dev-env.yml index e60e7436..55273971 100644 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -11,7 +11,7 @@ SERVER: CORS: ORIGIN: - - '*' + - "*" # - yourdomain.com METHODS: - POST @@ -63,7 +63,7 @@ CLEAN_STORE: DATABASE: ENABLED: false CONNECTION: - URI: 'mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true' + URI: "mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true" DB_PREFIX_NAME: evolution # Choose the data you want to save in the application's database or store SAVE_DATA: @@ -75,8 +75,8 @@ DATABASE: REDIS: ENABLED: false - URI: 'redis://localhost:6379' - PREFIX_KEY: 'evolution' + URI: "redis://localhost:6379" + PREFIX_KEY: "evolution" # Global Webhook Settings # Each instance's Webhook URL and events will be requested at the time it is created @@ -88,7 +88,7 @@ WEBHOOK: # With this option activated, you work with a url per webhook event, respecting the global url and the name of each event WEBHOOK_BY_EVENTS: false # Automatically maps webhook paths - # Set the events you want to hear + # Set the events you want to hear EVENTS: APPLICATION_STARTUP: false QRCODE_UPDATED: true @@ -113,7 +113,7 @@ WEBHOOK: CONFIG_SESSION_PHONE: # Name that will be displayed on smartphone connection - CLIENT: 'Evolution API' + CLIENT: "Evolution API" NAME: chrome # chrome | firefox | edge | opera | safari # Set qrcode display limit @@ -137,8 +137,11 @@ AUTHENTICATION: SECRET: L=0YWt]b2w[WF>#>:&E` # Set the instance name and webhook url to create an instance in init the application INSTANCE: - # With this option activated, you work with a url per webhook event, respecting the local url and the name of each event + # With this option activated, you work with a url per webhook event, respecting the local url and the name of each event MODE: server # container or server # if you are using container mode, set the container name and the webhook url to default instance NAME: evolution WEBHOOK_URL: + CHATWOOT_ACCOUNT_ID: 1 + CHATWOOT_TOKEN: 123456 + CHATWOOT_URL: diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 9427b1d7..9b75fd08 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -857,3 +857,16 @@ export const webhookSchema: JSONSchema7 = { required: ['url', 'enabled'], ...isNotEmpty('url'), }; + +export const chatwootSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + account_id: { type: 'string' }, + token: { type: 'string' }, + url: { type: 'string' }, + }, + required: ['enabled', 'account_id', 'token', 'url'], + ...isNotEmpty('account_id', 'token', 'url'), +}; diff --git a/src/whatsapp/controllers/chatwoot.controller.ts b/src/whatsapp/controllers/chatwoot.controller.ts new file mode 100644 index 00000000..e5887d26 --- /dev/null +++ b/src/whatsapp/controllers/chatwoot.controller.ts @@ -0,0 +1,55 @@ +import { isURL } from 'class-validator'; +import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { ChatwootDto } from '../dto/chatwoot.dto'; +import { ChatwootService } from '../services/chatwoot.service'; +import { Logger } from '../../config/logger.config'; + +const logger = new Logger('ChatwootController'); + +export class ChatwootController { + constructor(private readonly chatwootService: ChatwootService) {} + + public async createChatwoot(instance: InstanceDto, data: ChatwootDto) { + logger.verbose( + 'requested createChatwoot from ' + instance.instanceName + ' instance', + ); + + if (data.enabled) { + if (!isURL(data.url, { require_tld: false })) { + throw new BadRequestException('url is not valid'); + } + + if (!data.account_id) { + throw new BadRequestException('account_id is required'); + } + + if (!data.token) { + throw new BadRequestException('token is required'); + } + } + + if (!data.enabled) { + logger.verbose('chatwoot disabled'); + data.account_id = ''; + data.token = ''; + data.url = ''; + } + + data.name_inbox = instance.instanceName; + + return this.chatwootService.create(instance, data); + } + + public async findChatwoot(instance: InstanceDto) { + logger.verbose('requested findChatwoot from ' + instance.instanceName + ' instance'); + return this.chatwootService.find(instance); + } + + public async receiveWebhook(instance: InstanceDto, data: any) { + logger.verbose( + 'requested receiveWebhook from ' + instance.instanceName + ' instance', + ); + return this.chatwootService.receiveWebhook(instance, data); + } +} diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index c2abbffa..9ffd7e47 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -8,6 +8,7 @@ import { AuthService, OldToken } from '../services/auth.service'; import { WAMonitoringService } from '../services/monitor.service'; import { WAStartupService } from '../services/whatsapp.service'; import { WebhookService } from '../services/webhook.service'; +import { ChatwootService } from '../services/chatwoot.service'; import { Logger } from '../../config/logger.config'; import { wa } from '../types/wa.types'; import { RedisCache } from '../../db/redis.client'; @@ -20,6 +21,7 @@ export class InstanceController { private readonly eventEmitter: EventEmitter2, private readonly authService: AuthService, private readonly webhookService: WebhookService, + private readonly chatwootService: ChatwootService, private readonly cache: RedisCache, ) {} @@ -32,6 +34,9 @@ export class InstanceController { events, qrcode, token, + chatwoot_account_id, + chatwoot_token, + chatwoot_url, }: InstanceDto) { this.logger.verbose('requested createInstance from ' + instanceName + ' instance'); @@ -73,34 +78,70 @@ export class InstanceController { this.logger.verbose('hash: ' + hash + ' generated'); - let getEvents: string[]; + if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { + let getEvents: string[]; - if (webhook) { - this.logger.verbose('creating webhook'); - try { - this.webhookService.create(instance, { - enabled: true, - url: webhook, - events, - webhook_by_events, - }); + if (webhook) { + this.logger.verbose('creating webhook'); + try { + this.webhookService.create(instance, { + enabled: true, + url: webhook, + events, + webhook_by_events, + }); - getEvents = (await this.webhookService.find(instance)).events; - } catch (error) { - this.logger.log(error); + getEvents = (await this.webhookService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } } + + this.logger.verbose('instance created'); + this.logger.verbose({ + instance: { + instanceName: instance.instanceName, + status: 'created', + }, + hash, + webhook, + events: getEvents, + }); + + return { + instance: { + instanceName: instance.instanceName, + status: 'created', + }, + hash, + webhook, + events: getEvents, + }; } - this.logger.verbose('instance created'); - this.logger.verbose({ - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook, - events: getEvents, - }); + if (!chatwoot_account_id) { + throw new BadRequestException('account_id is required'); + } + + if (!chatwoot_token) { + throw new BadRequestException('token is required'); + } + + if (!chatwoot_url) { + throw new BadRequestException('url is required'); + } + + try { + this.chatwootService.create(instance, { + enabled: true, + account_id: chatwoot_account_id, + token: chatwoot_token, + url: chatwoot_url, + name_inbox: instance.instanceName, + }); + } catch (error) { + this.logger.log(error); + } return { instance: { @@ -108,8 +149,13 @@ export class InstanceController { status: 'created', }, hash, - webhook, - events: getEvents, + chatwoot: { + enabled: true, + account_id: chatwoot_account_id, + token: chatwoot_token, + url: chatwoot_url, + name_inbox: instance.instanceName, + }, }; } else { this.logger.verbose('server mode'); @@ -141,45 +187,83 @@ export class InstanceController { this.logger.verbose('hash: ' + hash + ' generated'); - let getEvents: string[]; + if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { + let getEvents: string[]; - if (webhook) { - this.logger.verbose('creating webhook'); - try { - this.webhookService.create(instance, { - enabled: true, - url: webhook, - events, - webhook_by_events, - }); + if (webhook) { + this.logger.verbose('creating webhook'); + try { + this.webhookService.create(instance, { + enabled: true, + url: webhook, + events, + webhook_by_events, + }); - getEvents = (await this.webhookService.find(instance)).events; - } catch (error) { - this.logger.log(error); + getEvents = (await this.webhookService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } } + + let getQrcode: wa.QrCode; + + if (qrcode) { + this.logger.verbose('creating qrcode'); + await instance.connectToWhatsapp(); + await delay(2000); + 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 { + instance: { + instanceName: instance.instanceName, + status: 'created', + }, + hash, + webhook, + webhook_by_events, + events: getEvents, + qrcode: getQrcode, + }; } - let getQrcode: wa.QrCode; - - if (qrcode) { - this.logger.verbose('creating qrcode'); - await instance.connectToWhatsapp(); - await delay(2000); - getQrcode = instance.qrCode; + if (!chatwoot_account_id) { + throw new BadRequestException('account_id is required'); } - this.logger.verbose('instance created'); - this.logger.verbose({ - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook, - webhook_by_events, - events: getEvents, - qrcode: getQrcode, - }); + if (!chatwoot_token) { + throw new BadRequestException('token is required'); + } + + if (!chatwoot_url) { + throw new BadRequestException('url is required'); + } + + try { + this.chatwootService.create(instance, { + enabled: true, + account_id: chatwoot_account_id, + token: chatwoot_token, + url: chatwoot_url, + name_inbox: instance.instanceName, + }); + } catch (error) { + this.logger.log(error); + } return { instance: { @@ -187,10 +271,13 @@ export class InstanceController { status: 'created', }, hash, - webhook, - webhook_by_events, - events: getEvents, - qrcode: getQrcode, + chatwoot: { + enabled: true, + account_id: chatwoot_account_id, + token: chatwoot_token, + url: chatwoot_url, + name_inbox: instance.instanceName, + }, }; } } diff --git a/src/whatsapp/dto/chatwoot.dto.ts b/src/whatsapp/dto/chatwoot.dto.ts new file mode 100644 index 00000000..a65bbf71 --- /dev/null +++ b/src/whatsapp/dto/chatwoot.dto.ts @@ -0,0 +1,7 @@ +export class ChatwootDto { + enabled?: boolean; + account_id?: string; + token?: string; + url?: string; + name_inbox?: string; +} diff --git a/src/whatsapp/dto/instance.dto.ts b/src/whatsapp/dto/instance.dto.ts index 8a3902e9..479a1dae 100644 --- a/src/whatsapp/dto/instance.dto.ts +++ b/src/whatsapp/dto/instance.dto.ts @@ -5,4 +5,7 @@ export class InstanceDto { events?: string[]; qrcode?: boolean; token?: string; + chatwoot_account_id?: string; + chatwoot_token?: string; + chatwoot_url?: string; } diff --git a/src/whatsapp/models/chatwoot.model.ts b/src/whatsapp/models/chatwoot.model.ts new file mode 100644 index 00000000..1ecdcf82 --- /dev/null +++ b/src/whatsapp/models/chatwoot.model.ts @@ -0,0 +1,27 @@ +import { Schema } from 'mongoose'; +import { dbserver } from '../../db/db.connect'; + +export class ChatwootRaw { + _id?: string; + enabled?: boolean; + account_id?: string; + token?: string; + url?: string; + name_inbox?: string; +} + +const chatwootSchema = new Schema({ + _id: { type: String, _id: true }, + enabled: { type: Boolean, required: true }, + account_id: { type: String, required: true }, + token: { type: String, required: true }, + url: { type: String, required: true }, + name_inbox: { type: String, required: true }, +}); + +export const ChatwootModel = dbserver?.model( + ChatwootRaw.name, + chatwootSchema, + 'chatwoot', +); +export type IChatwootModel = typeof ChatwootModel; diff --git a/src/whatsapp/models/index.ts b/src/whatsapp/models/index.ts index 11f760d9..e0b773f0 100644 --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -3,3 +3,4 @@ export * from './contact.model'; export * from './message.model'; export * from './auth.model'; export * from './webhook.model'; +export * from './chatwoot.model'; diff --git a/src/whatsapp/repository/chatwoot.repository.ts b/src/whatsapp/repository/chatwoot.repository.ts new file mode 100644 index 00000000..3d24022a --- /dev/null +++ b/src/whatsapp/repository/chatwoot.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 { IChatwootModel, ChatwootRaw } from '../models'; +import { Logger } from '../../config/logger.config'; + +export class ChatwootRepository extends Repository { + constructor( + private readonly chatwootModel: IChatwootModel, + private readonly configService: ConfigService, + ) { + super(configService); + } + + private readonly logger = new Logger('ChatwootRepository'); + + public async create(data: ChatwootRaw, instance: string): Promise { + try { + this.logger.verbose('creating chatwoot'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving chatwoot to db'); + const insert = await this.chatwootModel.replaceOne( + { _id: instance }, + { ...data }, + { upsert: true }, + ); + + this.logger.verbose( + 'chatwoot saved to db: ' + insert.modifiedCount + ' chatwoot', + ); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving chatwoot to store'); + + this.writeStore({ + path: join(this.storePath, 'chatwoot'), + fileName: instance, + data, + }); + + this.logger.verbose( + 'chatwoot saved to store in path: ' + + join(this.storePath, 'chatwoot') + + '/' + + instance, + ); + + this.logger.verbose('chatwoot created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding chatwoot'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding chatwoot in db'); + return await this.chatwootModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding chatwoot in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'chatwoot', instance + '.json'), { + encoding: 'utf-8', + }), + ) as ChatwootRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/repository/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts index bd41e09e..9740b436 100644 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -4,6 +4,7 @@ import { ContactRepository } from './contact.repository'; import { MessageUpRepository } from './messageUp.repository'; import { MongoClient } from 'mongodb'; import { WebhookRepository } from './webhook.repository'; +import { ChatwootRepository } from './chatwoot.repository'; import { AuthRepository } from './auth.repository'; import { Auth, ConfigService, Database } from '../../config/env.config'; import { execSync } from 'child_process'; @@ -17,6 +18,7 @@ export class RepositoryBroker { public readonly contact: ContactRepository, public readonly messageUpdate: MessageUpRepository, public readonly webhook: WebhookRepository, + public readonly chatwoot: ChatwootRepository, public readonly auth: AuthRepository, private configService: ConfigService, dbServer?: MongoClient, @@ -64,6 +66,9 @@ export class RepositoryBroker { this.logger.verbose('creating webhook path: ' + join(storePath, 'webhook')); execSync(`mkdir -p ${join(storePath, 'webhook')}`); + this.logger.verbose('creating chatwoot path: ' + join(storePath, 'chatwoot')); + execSync(`mkdir -p ${join(storePath, 'chatwoot')}`); + this.logger.verbose('creating temp path: ' + join(storePath, 'temp')); execSync(`mkdir -p ${join(storePath, 'temp')}`); } diff --git a/src/whatsapp/routers/chatwoot.router.ts b/src/whatsapp/routers/chatwoot.router.ts new file mode 100644 index 00000000..3d87f137 --- /dev/null +++ b/src/whatsapp/routers/chatwoot.router.ts @@ -0,0 +1,68 @@ +import { RequestHandler, Router } from 'express'; +import { instanceNameSchema, chatwootSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { ChatwootDto } from '../dto/chatwoot.dto'; +import { chatwootController } from '../whatsapp.module'; +import { ChatwootService } from '../services/chatwoot.service'; +import { HttpStatus } from './index.router'; +import { Logger } from '../../config/logger.config'; + +const logger = new Logger('ChatwootRouter'); + +export class ChatwootRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setChatwoot'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: chatwootSchema, + ClassRef: ChatwootDto, + execute: (instance, data) => chatwootController.createChatwoot(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, 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({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => chatwootController.findChatwoot(instance), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('webhook'), 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({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance, data) => chatwootController.receiveWebhook(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/index.router.ts b/src/whatsapp/routers/index.router.ts index 80f23c41..c25d320e 100644 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -8,6 +8,7 @@ import { InstanceRouter } from './instance.router'; import { MessageRouter } from './sendMessage.router'; import { ViewsRouter } from './view.router'; import { WebhookRouter } from './webhook.router'; +import { ChatwootRouter } from './chatwoot.router'; enum HttpStatus { OK = 200, @@ -32,6 +33,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(...guards).router) + .use('/chatwoot', new ChatwootRouter(...guards).router); export { router, HttpStatus }; diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts new file mode 100644 index 00000000..0f3c806f --- /dev/null +++ b/src/whatsapp/services/chatwoot.service.ts @@ -0,0 +1,722 @@ +import { InstanceDto } from '../dto/instance.dto'; +import path from 'path'; +import { ChatwootDto } from '../dto/chatwoot.dto'; +import { WAMonitoringService } from './monitor.service'; +import { Logger } from '../../config/logger.config'; +import ChatwootClient from '@figuro/chatwoot-sdk'; +import { createReadStream, unlinkSync, writeFileSync } from 'fs'; +import axios from 'axios'; +import FormData from 'form-data'; +import { SendTextDto } from '../dto/sendMessage.dto'; +import mimeTypes from 'mime-types'; +import { SendAudioDto } from '../dto/sendMessage.dto'; +import { SendMediaDto } from '../dto/sendMessage.dto'; + +export class ChatwootService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(ChatwootService.name); + + private provider: any; + + private async getProvider(instance: InstanceDto) { + const provider = await this.waMonitor.waInstances[ + instance.instanceName + ].findChatwoot(); + + return provider; + } + + private async clientCw(instance: InstanceDto) { + const provider = await this.getProvider(instance); + + if (!provider) { + throw new Error('provider not found'); + } + + this.provider = provider; + + const client = new ChatwootClient({ + config: { + basePath: provider.url, + with_credentials: true, + credentials: 'include', + token: provider.token, + }, + }); + + return client; + } + + public create(instance: InstanceDto, data: ChatwootDto) { + this.logger.verbose('create chatwoot: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setChatwoot(data); + + return { chatwoot: { ...instance, chatwoot: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find chatwoot: ' + instance.instanceName); + return await this.waMonitor.waInstances[instance.instanceName].findChatwoot(); + } catch (error) { + return { enabled: null, url: '' }; + } + } + + public async getContact(instance: InstanceDto, id: number) { + const client = await this.clientCw(instance); + + if (!client) { + throw new Error('client not found'); + } + + if (!id) { + throw new Error('id is required'); + } + + const contact = await client.contact.getContactable({ + accountId: this.provider.account_id, + id, + }); + + return contact; + } + + public async createContact( + instance: InstanceDto, + phoneNumber: string, + inboxId: number, + name?: string, + ) { + const client = await this.clientCw(instance); + + if (!client) { + throw new Error('client not found'); + } + + const contact = await client.contacts.create({ + accountId: this.provider.account_id, + data: { + inbox_id: inboxId, + name: name || phoneNumber, + phone_number: `+${phoneNumber}`, + }, + }); + + return contact; + } + + public async updateContact(instance: InstanceDto, id: number, data: any) { + const client = await this.clientCw(instance); + + if (!client) { + throw new Error('client not found'); + } + + if (!id) { + throw new Error('id is required'); + } + + const contact = await client.contacts.update({ + accountId: this.provider.account_id, + id, + data, + }); + + return contact; + } + + public async findContact(instance: InstanceDto, phoneNumber: string) { + const client = await this.clientCw(instance); + + if (!client) { + throw new Error('client not found'); + } + + const contact = await client.contacts.search({ + accountId: this.provider.account_id, + q: `+${phoneNumber}`, + }); + + return contact.payload.find((contact) => contact.phone_number === `+${phoneNumber}`); + } + + public async createConversation(instance: InstanceDto, body: any) { + const client = await this.clientCw(instance); + + if (!client) { + throw new Error('client not found'); + } + + const chatId = body.key.remoteJid.split('@')[0]; + const nameContact = !body.key.fromMe ? body.pushName : chatId; + + const filterInbox = await this.getInbox(instance); + + const contact = + (await this.findContact(instance, chatId)) || + ((await this.createContact(instance, chatId, filterInbox.id, nameContact)) as any); + + const contactId = contact.id || contact.payload.contact.id; + + if (!body.key.fromMe && contact.name === chatId && nameContact !== chatId) { + await this.updateContact(instance, contactId, { + name: nameContact, + }); + } + + const contactConversations = (await client.contacts.listConversations({ + accountId: this.provider.account_id, + id: contactId, + })) as any; + + if (contactConversations) { + const conversation = contactConversations.payload.find( + (conversation) => + conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id, + ); + if (conversation) { + return conversation.id; + } + } + + const conversation = await client.conversations.create({ + accountId: this.provider.account_id, + data: { + contact_id: `${contactId}`, + inbox_id: `${filterInbox.id}`, + }, + }); + + return conversation.id; + } + + public async getInbox(instance: InstanceDto) { + const client = await this.clientCw(instance); + + if (!client) { + throw new Error('client not found'); + } + + const inbox = (await client.inboxes.list({ + accountId: this.provider.account_id, + })) as any; + + const findByName = inbox.payload.find( + (inbox) => inbox.name === instance.instanceName, + ); + return findByName; + } + + public async createMessage( + instance: InstanceDto, + conversationId: number, + content: string, + messageType: 'incoming' | 'outgoing' | undefined, + attachments?: { + content: unknown; + encoding: string; + filename: string; + }[], + ) { + const client = await this.clientCw(instance); + + const message = await client.messages.create({ + accountId: this.provider.account_id, + conversationId: conversationId, + data: { + content: content, + message_type: messageType, + attachments: attachments, + }, + }); + + return message; + } + + public async createBotMessage( + instance: InstanceDto, + content: string, + messageType: 'incoming' | 'outgoing' | undefined, + attachments?: { + content: unknown; + encoding: string; + filename: string; + }[], + ) { + const client = await this.clientCw(instance); + + const contact = await this.findContact(instance, '123456'); + + const filterInbox = await this.getInbox(instance); + + const findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: filterInbox.id, + }); + + const conversation = findConversation.data.payload.find( + (conversation) => + conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', + ); + + const message = await client.messages.create({ + accountId: this.provider.account_id, + conversationId: conversation.id, + data: { + content: content, + message_type: messageType, + attachments: attachments, + }, + }); + + return message; + } + + private async sendData( + conversationId: number, + file: string, + messageType: 'incoming' | 'outgoing' | undefined, + content?: string, + ) { + const data = new FormData(); + + if (content) { + data.append('content', content); + } + + data.append('message_type', messageType); + + data.append('attachments[]', createReadStream(file)); + + const config = { + method: 'post', + maxBodyLength: Infinity, + url: `${this.provider.url}/api/v1/accounts/${this.provider.account_id}/conversations/${conversationId}/messages`, + headers: { + api_access_token: this.provider.token, + ...data.getHeaders(), + }, + data: data, + }; + + try { + const { data } = await axios.request(config); + unlinkSync(file); + return data; + } catch (error) { + console.log(error); + } + } + + public async createBotQr( + instance: InstanceDto, + content: string, + messageType: 'incoming' | 'outgoing' | undefined, + file?: string, + ) { + const client = await this.clientCw(instance); + + const contact = await this.findContact(instance, '123456'); + + const filterInbox = await this.getInbox(instance); + + const findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: filterInbox.id, + }); + const conversation = findConversation.data.payload.find( + (conversation) => + conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', + ); + + const data = new FormData(); + + if (content) { + data.append('content', content); + } + + data.append('message_type', messageType); + + if (file) { + data.append('attachments[]', createReadStream(file)); + } + + const config = { + method: 'post', + maxBodyLength: Infinity, + url: `${this.provider.url}/api/v1/accounts/${this.provider.account_id}/conversations/${conversation.id}/messages`, + headers: { + api_access_token: this.provider.token, + ...data.getHeaders(), + }, + data: data, + }; + + try { + const { data } = await axios.request(config); + unlinkSync(file); + return data; + } catch (error) { + console.log(error); + } + } + + public async sendAttachment( + waInstance: any, + number: string, + media: any, + caption?: string, + ) { + try { + const parts = media.split('/'); + const fileName = decodeURIComponent(parts[parts.length - 1]); + + const mimeType = mimeTypes.lookup(fileName).toString(); + + let type = 'document'; + + switch (mimeType.split('/')[0]) { + case 'image': + type = 'image'; + break; + case 'video': + type = 'video'; + break; + case 'audio': + type = 'audio'; + break; + default: + type = 'document'; + break; + } + + if (type === 'audio') { + const data: SendAudioDto = { + number: number, + audioMessage: { + audio: media, + }, + options: { + delay: 1200, + presence: 'recording', + }, + }; + + await waInstance?.audioWhatsapp(data); + + return; + } + + const data: SendMediaDto = { + number: number, + mediaMessage: { + mediatype: type as any, + fileName: fileName, + media: media, + }, + options: { + delay: 1200, + presence: 'composing', + }, + }; + + if (caption && type !== 'audio') { + data.mediaMessage.caption = caption; + } + + await waInstance?.mediaMessage(data); + + return; + } catch (error) { + throw new Error(error); + } + } + + public async receiveWebhook(instance: InstanceDto, body: any) { + try { + if (!body?.conversation || body.private) return { message: 'bot' }; + + const chatId = body.conversation.meta.sender.phone_number.replace('+', ''); + const messageReceived = body.content; + const senderName = body?.sender?.name; + const accountId = body.account.id as number; + const waInstance = this.waMonitor.waInstances[instance.instanceName]; + + if (chatId === '123456' && body.message_type === 'outgoing') { + const command = messageReceived.replace('/', ''); + + if (command === 'iniciar') { + const state = waInstance?.connectionStatus?.state; + + if (state !== 'open') { + await waInstance.connectToWhatsapp(); + } else { + await this.createBotMessage( + instance, + `🚨 Instância ${body.inbox.name} já está conectada.`, + 'incoming', + ); + } + } + + if (command === 'status') { + const state = waInstance?.connectionStatus?.state; + + if (!state) { + await this.createBotMessage( + instance, + `⚠️ Instância ${body.inbox.name} não existe.`, + 'incoming', + ); + } + + if (state) { + await this.createBotMessage( + instance, + `⚠️ Status da instância ${body.inbox.name}: *${state}*`, + 'incoming', + ); + } + } + + if (command === 'desconectar') { + const msgLogout = `🚨 Desconectando Whatsapp da caixa de entrada *${body.inbox.name}*: `; + + await this.createBotMessage(instance, msgLogout, 'incoming'); + await waInstance?.client?.logout('Log out instance: ' + instance.instanceName); + await waInstance?.client?.ws?.close(); + } + } + + if ( + body.message_type === 'outgoing' && + body?.conversation?.messages?.length && + chatId !== '123456' + ) { + // if (IMPORT_MESSAGES_SENT && messages_sent.includes(body.id)) { + // console.log(`🚨 Não importar mensagens enviadas, ficaria duplicado.`); + + // const indexMessage = messages_sent.indexOf(body.id); + // messages_sent.splice(indexMessage, 1); + + // return { message: 'bot' }; + // } + + let formatText: string; + if (senderName === null || senderName === undefined) { + formatText = messageReceived; + } else { + // formatText = TOSIGN ? `*${senderName}*: ${messageReceived}` : messageReceived; + formatText = `*${senderName}*: ${messageReceived}`; + } + + for (const message of body.conversation.messages) { + if (message.attachments && message.attachments.length > 0) { + for (const attachment of message.attachments) { + console.log(attachment); + if (!messageReceived) { + formatText = null; + } + + await this.sendAttachment( + waInstance, + chatId, + attachment.data_url, + formatText, + ); + } + } else { + const data: SendTextDto = { + number: chatId, + textMessage: { + text: formatText, + }, + options: { + delay: 1200, + presence: 'composing', + }, + }; + + await waInstance?.textMessage(data); + } + } + } + + return { message: 'bot' }; + } catch (error) { + console.log(error); + + return { message: 'bot' }; + } + } + + private isMediaMessage(message: any) { + const media = [ + 'imageMessage', + 'documentMessage', + 'audioMessage', + 'videoMessage', + 'stickerMessage', + ]; + + const messageKeys = Object.keys(message); + return messageKeys.some((key) => media.includes(key)); + } + + private getTypeMessage(msg: any) { + const types = { + conversation: msg.conversation, + imageMessage: msg.imageMessage?.caption, + videoMessage: msg.videoMessage?.caption, + extendedTextMessage: msg.extendedTextMessage?.text, + messageContextInfo: msg.messageContextInfo?.stanzaId, + stickerMessage: msg.stickerMessage?.fileSha256.toString('base64'), + documentMessage: msg.documentMessage?.caption, + audioMessage: msg.audioMessage?.caption, + }; + + return types; + } + + private getMessageContent(types: any) { + const typeKey = Object.keys(types).find((key) => types[key] !== undefined); + return typeKey ? types[typeKey] : undefined; + } + + private getConversationMessage(msg: any) { + const types = this.getTypeMessage(msg); + + const messageContent = this.getMessageContent(types); + + return messageContent; + } + + public async eventWhatsapp(event: string, instance: InstanceDto, body: any) { + try { + const client = await this.clientCw(instance); + + if (!client) { + throw new Error('client not found'); + } + + const waInstance = this.waMonitor.waInstances[instance.instanceName]; + + if (event === 'messages.upsert') { + // if (body.key.fromMe && !IMPORT_MESSAGES_SENT) { + // return; + // } + + if (body.key.remoteJid === 'status@broadcast') { + console.log(`🚨 Ignorando status do whatsapp.`); + return; + } + + const getConversion = await this.createConversation(instance, body); + const messageType = body.key.fromMe ? 'outgoing' : 'incoming'; + + if (!getConversion) { + console.log('🚨 Erro ao criar conversa'); + return; + } + + const isMedia = this.isMediaMessage(body.message); + + const bodyMessage = await this.getConversationMessage(body.message); + + if (isMedia) { + const downloadBase64 = await waInstance?.getBase64FromMediaMessage({ + message: { + ...body, + }, + }); + + const random = Math.random().toString(36).substring(7); + const nameFile = `${random}.${mimeTypes.extension(downloadBase64.mimetype)}`; + + const fileData = Buffer.from(downloadBase64.base64, 'base64'); + + const fileName = `${path.join(waInstance?.storePath, 'temp', `${nameFile}`)}`; + + writeFileSync(fileName, fileData, 'utf8'); + + return await this.sendData(getConversion, fileName, messageType, bodyMessage); + } + + const send = await this.createMessage( + instance, + getConversion, + bodyMessage, + messageType, + ); + + return send; + } + + if (event === 'status.instance') { + const data = body; + const inbox = await this.getInbox(instance); + const msgStatus = `⚡️ Status da instância ${inbox.name}: ${data.status}`; + await this.createBotMessage(instance, msgStatus, 'incoming'); + } + + if (event === 'connection.update') { + if (body.state === 'open') { + const msgConnection = `🚀 Conexão realizada com sucesso!`; + await this.createBotMessage(instance, msgConnection, 'incoming'); + } + } + + if (event === 'contacts.update') { + const data = body; + + if (data.length) { + for (const item of data) { + const number = item.id.split('@')[0]; + const photo = item.profilePictureUrl || null; + const find = await this.findContact(instance, number); + + if (find) { + await this.updateContact(instance, find.id, { + avatar_url: photo, + }); + } + } + } + } + + if (event === 'qrcode.updated') { + if (body.statusCode === 500) { + const erroQRcode = `🚨 Limite de geração de QRCode atingido, para gerar um novo QRCode, envie a mensagem /iniciar novamente.`; + return await this.createBotMessage(instance, erroQRcode, 'incoming'); + } else { + const fileData = Buffer.from( + body?.qrcode.base64.replace('data:image/png;base64,', ''), + 'base64', + ); + + const fileName = `${path.join( + waInstance?.storePath, + 'temp', + `${`${instance}.png`}`, + )}`; + + writeFileSync(fileName, fileData, 'utf8'); + + await this.createBotQr( + instance, + 'QRCode gerado com sucesso!', + 'incoming', + fileName, + ); + + const msgQrCode = `⚡️ QRCode gerado com sucesso!\n\nDigitalize este código QR nos próximos 40 segundos:`; + await this.createBotMessage(instance, msgQrCode, 'incoming'); + } + } + } catch (error) { + console.log(error); + } + } +} diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index e2486e69..0f756d18 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -114,6 +114,7 @@ import { MessageUpQuery } from '../repository/messageUp.repository'; import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db'; import Long from 'long'; import { WebhookRaw } from '../models/webhook.model'; +import { ChatwootRaw } from '../models/chatwoot.model'; import { dbserver } from '../../db/db.connect'; import NodeCache from 'node-cache'; import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; @@ -121,6 +122,8 @@ import sharp from 'sharp'; import { RedisCache } from '../../db/redis.client'; import { Log } from '../../config/env.config'; import ProxyAgent from 'proxy-agent'; +import { ChatwootService } from './chatwoot.service'; +import { waMonitor } from '../whatsapp.module'; export class WAStartupService { constructor( @@ -138,13 +141,16 @@ export class WAStartupService { private readonly instance: wa.Instance = {}; public client: WASocket; private readonly localWebhook: wa.LocalWebHook = {}; + private readonly localChatwoot: wa.LocalChatwoot = {}; private stateConnection: wa.StateConnection = { state: 'close' }; - private readonly storePath = join(ROOT_DIR, 'store'); + public readonly storePath = join(ROOT_DIR, 'store'); private readonly msgRetryCounterCache: CacheStore = new NodeCache(); private readonly userDevicesCache: CacheStore = new NodeCache(); private endSession = false; private logBaileys = this.configService.get('LOG').BAILEYS; + private chatwootService = new ChatwootService(waMonitor); + public set instanceName(name: string) { this.logger.verbose(`Initializing instance '${name}'`); if (!name) { @@ -159,6 +165,17 @@ export class WAStartupService { instance: this.instance.name, status: 'created', }); + + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.STATUS_INSTANCE, + { instanceName: this.instance.name }, + { + instance: this.instance.name, + status: 'created', + }, + ); + } } public get instanceName() { @@ -268,6 +285,52 @@ export class WAStartupService { return data; } + private async loadChatwoot() { + this.logger.verbose('Loading chatwoot'); + const data = await this.repository.chatwoot.find(this.instanceName); + this.localChatwoot.enabled = data?.enabled; + this.logger.verbose(`Chatwoot enabled: ${this.localChatwoot.enabled}`); + + this.localChatwoot.account_id = data?.account_id; + this.logger.verbose(`Chatwoot account id: ${this.localChatwoot.account_id}`); + + this.localChatwoot.token = data?.token; + this.logger.verbose(`Chatwoot token: ${this.localChatwoot.token}`); + + this.localChatwoot.url = data?.url; + this.logger.verbose(`Chatwoot url: ${this.localChatwoot.url}`); + + this.logger.verbose('Chatwoot loaded'); + } + + public async setChatwoot(data: ChatwootRaw) { + this.logger.verbose('Setting chatwoot'); + await this.repository.chatwoot.create(data, this.instanceName); + this.logger.verbose(`Chatwoot account id: ${data.account_id}`); + this.logger.verbose(`Chatwoot token: ${data.token}`); + this.logger.verbose(`Chatwoot url: ${data.url}`); + this.logger.verbose(`Chatwoot inbox name: ${data.name_inbox}`); + + Object.assign(this.localChatwoot, data); + this.logger.verbose('Chatwoot set'); + } + + public async findChatwoot() { + this.logger.verbose('Finding chatwoot'); + const data = await this.repository.chatwoot.find(this.instanceName); + + if (!data) { + this.logger.verbose('Chatwoot not found'); + throw new NotFoundException('Chatwoot not found'); + } + + this.logger.verbose(`Chatwoot account id: ${data.account_id}`); + this.logger.verbose(`Chatwoot token: ${data.token}`); + this.logger.verbose(`Chatwoot url: ${data.url}`); + this.logger.verbose(`Chatwoot inbox name: ${data.name_inbox}`); + return data; + } + public async sendDataWebhook(event: Events, data: T, local = true) { const webhookGlobal = this.configService.get('WEBHOOK'); const webhookLocal = this.localWebhook.events; @@ -399,6 +462,17 @@ export class WAStartupService { statusCode: DisconnectReason.badSession, }); + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.QRCODE_UPDATED, + { instanceName: this.instance.name }, + { + message: 'QR code limit reached, please login again', + statusCode: DisconnectReason.badSession, + }, + ); + } + this.logger.verbose('Sending data to webhook in event CONNECTION_UPDATE'); this.sendDataWebhook(Events.CONNECTION_UPDATE, { instance: this.instance.name, @@ -412,6 +486,17 @@ export class WAStartupService { status: 'removed', }); + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.STATUS_INSTANCE, + { instanceName: this.instance.name }, + { + instance: this.instance.name, + status: 'removed', + }, + ); + } + this.logger.verbose('endSession defined as true'); this.endSession = true; @@ -442,6 +527,16 @@ export class WAStartupService { this.sendDataWebhook(Events.QRCODE_UPDATED, { qrcode: { instance: this.instance.name, code: qr, base64 }, }); + + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.QRCODE_UPDATED, + { instanceName: this.instance.name }, + { + qrcode: { instance: this.instance.name, code: qr, base64 }, + }, + ); + } }); this.logger.verbose('Generating QR code in terminal'); @@ -482,6 +577,17 @@ export class WAStartupService { status: 'removed', }); + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.STATUS_INSTANCE, + { instanceName: this.instance.name }, + { + instance: this.instance.name, + status: 'removed', + }, + ); + } + this.logger.verbose('Emittin event logout.instance'); this.eventEmitter.emit('logout.instance', this.instance.name, 'inner'); this.client?.ws?.close(); @@ -596,6 +702,7 @@ export class WAStartupService { this.logger.verbose('Connecting to whatsapp'); try { this.loadWebhook(); + this.loadChatwoot(); this.instance.authState = await this.defineAuthState(); @@ -787,6 +894,14 @@ export class WAStartupService { this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE'); await this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw); + if (this.localChatwoot.enabled) { + await this.chatwootService.eventWhatsapp( + Events.CONTACTS_UPDATE, + { instanceName: this.instance.name }, + contactsRaw, + ); + } + this.logger.verbose('Updating contacts in database'); await this.repository.contact.update( contactsRaw, @@ -910,6 +1025,14 @@ export class WAStartupService { this.logger.verbose('Sending data to webhook in event MESSAGES_UPSERT'); await this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); + if (this.localChatwoot.enabled) { + await this.chatwootService.eventWhatsapp( + Events.MESSAGES_UPSERT, + { instanceName: this.instance.name }, + messageRaw, + ); + } + this.logger.verbose('Inserting message in database'); await this.repository.message.insert( [messageRaw], @@ -948,6 +1071,14 @@ export class WAStartupService { this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE'); await 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], diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index 1686f031..e08aef78 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -41,6 +41,14 @@ export declare namespace wa { webhook_by_events?: boolean; }; + export type LocalChatwoot = { + enabled?: boolean; + account_id?: string; + token?: string; + url?: string; + name_inbox?: string; + }; + export type StateConnection = { instance?: string; state?: WAConnectionState | 'refused'; diff --git a/src/whatsapp/whatsapp.module.ts b/src/whatsapp/whatsapp.module.ts index f05b8323..a03ca18d 100644 --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -14,6 +14,8 @@ import { GroupController } from './controllers/group.controller'; import { ViewsController } from './controllers/views.controller'; import { WebhookService } from './services/webhook.service'; import { WebhookController } from './controllers/webhook.controller'; +import { ChatwootService } from './services/chatwoot.service'; +import { ChatwootController } from './controllers/chatwoot.controller'; import { RepositoryBroker } from './repository/repository.manager'; import { AuthModel, @@ -21,10 +23,12 @@ import { ContactModel, MessageModel, MessageUpModel, + ChatwootModel, + WebhookModel, } from './models'; import { dbserver } from '../db/db.connect'; import { WebhookRepository } from './repository/webhook.repository'; -import { WebhookModel } from './models/webhook.model'; +import { ChatwootRepository } from './repository/chatwoot.repository'; import { AuthRepository } from './repository/auth.repository'; import { WAStartupService } from './services/whatsapp.service'; import { delay } from '@whiskeysockets/baileys'; @@ -38,6 +42,7 @@ const chatRepository = new ChatRepository(ChatModel, configService); 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 authRepository = new AuthRepository(AuthModel, configService); export const repository = new RepositoryBroker( @@ -46,6 +51,7 @@ export const repository = new RepositoryBroker( contactRepository, messageUpdateRepository, webhookRepository, + chatwootRepository, authRepository, configService, dbserver?.getClient(), @@ -66,6 +72,10 @@ const webhookService = new WebhookService(waMonitor); export const webhookController = new WebhookController(webhookService); +const chatwootService = new ChatwootService(waMonitor); + +export const chatwootController = new ChatwootController(chatwootService); + export const instanceController = new InstanceController( waMonitor, configService, @@ -73,6 +83,7 @@ export const instanceController = new InstanceController( eventEmitter, authService, webhookService, + chatwootService, cache, ); export const viewsController = new ViewsController(waMonitor, configService);