Merge branch 'develop' of https://github.com/EvolutionAPI/evolution-api into EvolutionAPI-develop

This commit is contained in:
raimartinsb 2023-09-11 08:08:18 -03:00
commit c5d2d7782a
41 changed files with 1217 additions and 210 deletions

View File

@ -1,3 +1,17 @@
# 1.5.1 (homolog)
### Feature
* Added listening_from_me option in Set Typebot
* Added variables options in Start Typebot
* Added webhooks for typebot events
* Added ChamaAI integration
* Added webhook to send errors
### Fixed
* Fix looping connection messages in chatwoot
# 1.5.0 (2023-08-18 12:47)
### Feature

View File

@ -81,11 +81,19 @@ WEBHOOK_EVENTS_CONNECTION_UPDATE=true
WEBHOOK_EVENTS_CALL=true
# This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
# This events is used with Typebot
WEBHOOK_EVENTS_TYPEBOT_START=false
WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
# This event is used with Chama AI
WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
# This event is used to send errors
WEBHOOK_EVENTS_ERRORS=false
WEBHOOK_EVENTS_ERRORS_WEBHOOK=
# Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
# Browser Name = chrome | firefox | edge | opera | safari
CONFIG_SESSION_PHONE_NAME=chrome
# Browser Name = Chrome | Firefox | Edge | Opera | Safari
CONFIG_SESSION_PHONE_NAME=Chrome
# Set qrcode display limit
QRCODE_LIMIT=30

View File

@ -0,0 +1,22 @@
version: '3.3'
services:
api:
container_name: evolution_api
image: davidsongomes/evolution-api
restart: always
ports:
- 8080:8080
volumes:
- evolution_instances:/evolution/instances
- evolution_store:/evolution/store
env_file:
- .env
command: ['node', './dist/src/main.js']
expose:
- 8080
volumes:
evolution_instances:
evolution_store:

View File

@ -0,0 +1,109 @@
# Server URL - Set your application url
SERVER_URL='http://localhost:8080'
# Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com'
CORS_ORIGIN='*'
CORS_METHODS='POST,GET,PUT,DELETE'
CORS_CREDENTIALS=true
# Determine the logs to be displayed
LOG_LEVEL='ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS'
LOG_COLOR=true
# Log Baileys - "fatal" | "error" | "warn" | "info" | "debug" | "trace"
LOG_BAILEYS=error
# Determine how long the instance should be deleted from memory in case of no connection.
# Default time: 5 minutes
# If you don't even want an expiration, enter the value false
DEL_INSTANCE=false
# Temporary data storage
STORE_MESSAGES=true
STORE_MESSAGE_UP=true
STORE_CONTACTS=true
STORE_CHATS=true
# Set Store Interval in Seconds (7200 = 2h)
CLEAN_STORE_CLEANING_INTERVAL=7200
CLEAN_STORE_MESSAGES=true
CLEAN_STORE_MESSAGE_UP=true
CLEAN_STORE_CONTACTS=true
CLEAN_STORE_CHATS=true
# Permanent data storage
DATABASE_ENABLED=true
DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true
DATABASE_CONNECTION_DB_PREFIX_NAME=evolution
# Choose the data you want to save in the application's database or store
DATABASE_SAVE_DATA_INSTANCE=false
DATABASE_SAVE_DATA_NEW_MESSAGE=false
DATABASE_SAVE_MESSAGE_UPDATE=false
DATABASE_SAVE_DATA_CONTACTS=false
DATABASE_SAVE_DATA_CHATS=false
REDIS_ENABLED=true
REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evolution
# Global Webhook Settings
# Each instance's Webhook URL and events will be requested at the time it is created
## Define a global webhook that will listen for enabled events from all instances
WEBHOOK_GLOBAL_URL=''
WEBHOOK_GLOBAL_ENABLED=false
# With this option activated, you work with a url per webhook event, respecting the global url and the name of each event
WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS=false
## Set the events you want to hear
WEBHOOK_EVENTS_APPLICATION_STARTUP=false
WEBHOOK_EVENTS_QRCODE_UPDATED=true
WEBHOOK_EVENTS_MESSAGES_SET=true
WEBHOOK_EVENTS_MESSAGES_UPSERT=true
WEBHOOK_EVENTS_MESSAGES_UPDATE=true
WEBHOOK_EVENTS_MESSAGES_DELETE=true
WEBHOOK_EVENTS_SEND_MESSAGE=true
WEBHOOK_EVENTS_CONTACTS_SET=true
WEBHOOK_EVENTS_CONTACTS_UPSERT=true
WEBHOOK_EVENTS_CONTACTS_UPDATE=true
WEBHOOK_EVENTS_PRESENCE_UPDATE=true
WEBHOOK_EVENTS_CHATS_SET=true
WEBHOOK_EVENTS_CHATS_UPSERT=true
WEBHOOK_EVENTS_CHATS_UPDATE=true
WEBHOOK_EVENTS_CHATS_DELETE=true
WEBHOOK_EVENTS_GROUPS_UPSERT=true
WEBHOOK_EVENTS_GROUPS_UPDATE=true
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
# This event fires every time a new token is requested via the refresh route
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
# Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_CLIENT='Evolution API'
# Browser Name = chrome | firefox | edge | opera | safari
CONFIG_SESSION_PHONE_NAME=chrome
# Set qrcode display limit
QRCODE_LIMIT=30
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
# jwt or 'apikey'
AUTHENTICATION_TYPE='apikey'
## Define a global apikey to access all instances.
### OBS: This key must be inserted in the request header to create an instance.
AUTHENTICATION_API_KEY='B6D711FCDE4D4FD5936544120E713976'
AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
## Set the secret key to encrypt and decrypt your token and its expiration time
# seconds - 3600s ===1h | zero (0) - never expires
AUTHENTICATION_JWT_EXPIRIN_IN=0
AUTHENTICATION_JWT_SECRET='L0YWtjb2w554WFqPG'
# Set the instance name and webhook url to create an instance in init the application
# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event
# container or server
AUTHENTICATION_INSTANCE_MODE=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=''
AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1
AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456
AUTHENTICATION_INSTANCE_CHATWOOT_URL=''

View File

@ -0,0 +1,91 @@
version: '3.3'
services:
mongodb:
container_name: mongodb
image: mongo
restart: on-failure
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
- PUID=1000
- PGID=1000
volumes:
- evolution_mongodb_data:/data/db
- evolution_mongodb_configdb:/data/configdb
expose:
- 27017
mongo-express:
container_name: mongodb-express
image: mongo-express
restart: on-failure
ports:
- 8081:8081
depends_on:
- mongodb
environment:
ME_CONFIG_BASICAUTH_USERNAME: root
ME_CONFIG_BASICAUTH_PASSWORD: root
ME_CONFIG_MONGODB_SERVER: mongodb
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: root
links:
- mongodb
redis:
container_name: redis
image: redis:latest
restart: on-failure
ports:
- 6379:6379
command: >
redis-server
--port 6379
--appendonly yes
volumes:
- evolution_redis:/data
rebrow:
container_name: rebrow
image: marian/rebrow
restart: on-failure
depends_on:
- redis
ports:
- 5001:5001
links:
- redis
api:
container_name: evolution_api
image: davidsongomes/evolution-api
restart: always
depends_on:
- mongodb
- redis
ports:
- 8080:8080
volumes:
- evolution_instances:/evolution/instances
- evolution_store:/evolution/store
env_file:
- .env
command: ['node', './dist/src/main.js']
expose:
- 8080
volumes:
evolution_mongodb_data:
evolution_mongodb_configdb:
evolution_redis:
evolution_instances:
evolution_store:
networks:
evolution-net:
external: true

View File

@ -1,6 +1,6 @@
FROM node:16.18-alpine
LABEL version="1.5.0" description="Api to control whatsapp features through http requests."
LABEL version="1.5.1" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@agenciadgcode.com"
@ -84,8 +84,16 @@ ENV WEBHOOK_EVENTS_CALL=true
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
ENV WEBHOOK_EVENTS_TYPEBOT_START=false
ENV WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
ENV WEBHOOK_EVENTS_CHAMA_AI_ACTION=false
ENV WEBHOOK_EVENTS_ERRORS=false
ENV WEBHOOK_EVENTS_ERRORS_WEBHOOK=
ENV CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI
ENV CONFIG_SESSION_PHONE_NAME=chrome
ENV CONFIG_SESSION_PHONE_NAME=Chrome
ENV QRCODE_LIMIT=30
ENV QRCODE_COLOR=#198754

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.5.0",
"version": "1.5.1",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@ -46,7 +46,7 @@
"@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"@whiskeysockets/baileys": "github:EvolutionAPI/Baileys",
"@whiskeysockets/baileys": "^6.4.1",
"amqplib": "^0.10.3",
"axios": "^1.3.5",
"class-validator": "^0.13.2",
@ -69,6 +69,7 @@
"mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"node-mime-types": "^1.1.0",
"node-windows": "^1.0.0-beta.8",
"pino": "^8.11.0",
"proxy-agent": "^6.3.0",
"qrcode": "^1.5.1",
@ -76,6 +77,7 @@
"redis": "^4.6.5",
"sharp": "^0.30.7",
"socket.io": "^4.7.1",
"socks-proxy-agent": "^8.0.1",
"uuid": "^9.0.0"
},
"devDependencies": {
@ -86,6 +88,7 @@
"@types/jsonwebtoken": "^8.5.9",
"@types/mime-types": "^2.1.1",
"@types/node": "^18.15.11",
"@types/node-windows": "^0.1.2",
"@types/qrcode": "^1.5.0",
"@types/qrcode-terminal": "^0.12.0",
"@types/uuid": "^8.3.4",

View File

@ -96,6 +96,11 @@ export type EventsWebhook = {
GROUP_PARTICIPANTS_UPDATE: boolean;
CALL: boolean;
NEW_JWT_TOKEN: boolean;
TYPEBOT_START: boolean;
TYPEBOT_CHANGE_STATUS: boolean;
CHAMA_AI_ACTION: boolean;
ERRORS: boolean;
ERRORS_WEBHOOK: string;
};
export type ApiKey = { KEY: string };
@ -171,17 +176,17 @@ export class ConfigService {
return {
SERVER: {
TYPE: process.env.SERVER_TYPE as 'http' | 'https',
PORT: Number.parseInt(process.env.SERVER_PORT),
PORT: Number.parseInt(process.env.SERVER_PORT) || 8080,
URL: process.env.SERVER_URL,
},
CORS: {
ORIGIN: process.env.CORS_ORIGIN.split(','),
METHODS: process.env.CORS_METHODS.split(',') as HttpMethods[],
ORIGIN: process.env.CORS_ORIGIN.split(',') || ['*'],
METHODS: (process.env.CORS_METHODS.split(',') as HttpMethods[]) || ['POST', 'GET', 'PUT', 'DELETE'],
CREDENTIALS: process.env?.CORS_CREDENTIALS === 'true',
},
SSL_CONF: {
PRIVKEY: process.env?.SSL_CONF_PRIVKEY,
FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN,
PRIVKEY: process.env?.SSL_CONF_PRIVKEY || '',
FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN || '',
},
STORE: {
MESSAGES: process.env?.STORE_MESSAGES === 'true',
@ -200,8 +205,8 @@ export class ConfigService {
},
DATABASE: {
CONNECTION: {
URI: process.env.DATABASE_CONNECTION_URI,
DB_PREFIX_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_NAME,
URI: process.env.DATABASE_CONNECTION_URI || '',
DB_PREFIX_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_NAME || 'evolution',
},
ENABLED: process.env?.DATABASE_ENABLED === 'true',
SAVE_DATA: {
@ -214,25 +219,36 @@ export class ConfigService {
},
REDIS: {
ENABLED: process.env?.REDIS_ENABLED === 'true',
URI: process.env.REDIS_URI,
PREFIX_KEY: process.env.REDIS_PREFIX_KEY,
URI: process.env.REDIS_URI || '',
PREFIX_KEY: process.env.REDIS_PREFIX_KEY || 'evolution',
},
RABBITMQ: {
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
URI: process.env.RABBITMQ_URI,
URI: process.env.RABBITMQ_URI || '',
},
WEBSOCKET: {
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
},
LOG: {
LEVEL: process.env?.LOG_LEVEL.split(',') as LogLevel[],
LEVEL: (process.env?.LOG_LEVEL.split(',') as LogLevel[]) || [
'ERROR',
'WARN',
'DEBUG',
'INFO',
'LOG',
'VERBOSE',
'DARK',
'WEBHOOKS',
],
COLOR: process.env?.LOG_COLOR === 'true',
BAILEYS: (process.env?.LOG_BAILEYS as LogBaileys) || 'error',
},
DEL_INSTANCE: process.env?.DEL_INSTANCE === 'true' ? 5 : Number.parseInt(process.env.DEL_INSTANCE) || false,
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
? process.env.DEL_INSTANCE === 'true'
: Number.parseInt(process.env.DEL_INSTANCE) || false,
WEBHOOK: {
GLOBAL: {
URL: process.env?.WEBHOOK_GLOBAL_URL,
URL: process.env?.WEBHOOK_GLOBAL_URL || '',
ENABLED: process.env?.WEBHOOK_GLOBAL_ENABLED === 'true',
WEBHOOK_BY_EVENTS: process.env?.WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS === 'true',
},
@ -258,27 +274,32 @@ export class ConfigService {
GROUP_PARTICIPANTS_UPDATE: process.env?.WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE === 'true',
CALL: process.env?.WEBHOOK_EVENTS_CALL === 'true',
NEW_JWT_TOKEN: process.env?.WEBHOOK_EVENTS_NEW_JWT_TOKEN === 'true',
TYPEBOT_START: process.env?.WEBHOOK_EVENTS_TYPEBOT_START === 'true',
TYPEBOT_CHANGE_STATUS: process.env?.WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS === 'true',
CHAMA_AI_ACTION: process.env?.WEBHOOK_EVENTS_CHAMA_AI_ACTION === 'true',
ERRORS: process.env?.WEBHOOK_EVENTS_ERRORS === 'true',
ERRORS_WEBHOOK: process.env?.WEBHOOK_EVENTS_ERRORS_WEBHOOK || '',
},
},
CONFIG_SESSION_PHONE: {
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'chrome',
NAME: process.env?.CONFIG_SESSION_PHONE_NAME || 'Chrome',
},
QRCODE: {
LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30,
COLOR: process.env.QRCODE_COLOR || '#198754',
},
AUTHENTICATION: {
TYPE: process.env.AUTHENTICATION_TYPE as 'jwt',
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
API_KEY: {
KEY: process.env.AUTHENTICATION_API_KEY,
KEY: process.env.AUTHENTICATION_API_KEY || 'BQYHJGJHJ',
},
EXPOSE_IN_FETCH_INSTANCES: process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true',
JWT: {
EXPIRIN_IN: Number.isInteger(process.env?.AUTHENTICATION_JWT_EXPIRIN_IN)
? Number.parseInt(process.env.AUTHENTICATION_JWT_EXPIRIN_IN)
: 3600,
SECRET: process.env.AUTHENTICATION_JWT_SECRET,
SECRET: process.env.AUTHENTICATION_JWT_SECRET || 'L=0YWt]b2w[WF>#>:&E`',
},
},
CHATWOOT: {

View File

@ -8,6 +8,7 @@ export function onUnexpectedError() {
stderr: process.stderr.fd,
error,
});
process.exit(1);
});
process.on('unhandledRejection', (error, origin) => {
@ -17,5 +18,6 @@ export function onUnexpectedError() {
stderr: process.stderr.fd,
error,
});
process.exit(1);
});
}

View File

@ -120,16 +120,24 @@ WEBHOOK:
CALL: true
# This event fires every time a new token is requested via the refresh route
NEW_JWT_TOKEN: false
# This events is used with Typebot
TYPEBOT_START: false
TYPEBOT_CHANGE_STATUS: false
# This event is used with Chama AI
CHAMA_AI_ACTION: false
# This event is used to send errors to the webhook
ERRORS: false
ERRORS_WEBHOOK: <url>
CONFIG_SESSION_PHONE:
# Name that will be displayed on smartphone connection
CLIENT: "Evolution API"
NAME: chrome # chrome | firefox | edge | opera | safari
NAME: Chrome # Chrome | Firefox | Edge | Opera | Safari
# Set qrcode display limit
QRCODE:
LIMIT: 30
COLOR: '#198754'
COLOR: "#198754"
# Defines an authentication type for the api
# We recommend using the apikey because it will allow you to use a custom token,

View File

@ -41,3 +41,33 @@ export const initAMQP = () => {
export const getAMQP = (): amqp.Channel | null => {
return amqpChannel;
};
export const initQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
const queues = events.map((event) => {
return `${event.replace(/_/g, '.').toLowerCase()}`;
});
queues.forEach((event) => {
const amqp = getAMQP();
const exchangeName = instanceName ?? 'evolution_exchange';
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queueName = `${instanceName}.${event}`;
amqp.assertQueue(queueName, {
durable: true,
autoDelete: false,
arguments: {
'x-queue-type': 'quorum',
},
});
amqp.bindQueue(queueName, exchangeName, event);
});
};

View File

@ -5,6 +5,10 @@ import { Redis } from '../config/env.config';
import { Logger } from '../config/logger.config';
export class RedisCache {
async disconnect() {
await this.client.disconnect();
this.statusConnection = false;
}
constructor() {
this.logger.verbose('instance created');
process.on('beforeExit', async () => {

View File

@ -1,11 +1,12 @@
import 'express-async-errors';
import axios from 'axios';
import compression from 'compression';
import cors from 'cors';
import express, { json, NextFunction, Request, Response, urlencoded } from 'express';
import { join } from 'path';
import { configService, Cors, HttpServer, Rabbitmq } from './config/env.config';
import { configService, Cors, HttpServer, Rabbitmq, Webhook } from './config/env.config';
import { onUnexpectedError } from './config/error.config';
import { Logger } from './config/logger.config';
import { ROOT_DIR } from './config/path.config';
@ -47,11 +48,46 @@ function bootstrap() {
app.set('views', join(ROOT_DIR, 'views'));
app.use(express.static(join(ROOT_DIR, 'public')));
app.use('/store', express.static(join(ROOT_DIR, 'store')));
app.use('/', router);
app.use(
(err: Error, req: Request, res: Response, next: NextFunction) => {
if (err) {
const webhook = configService.get<Webhook>('WEBHOOK');
if (webhook.EVENTS.ERRORS_WEBHOOK && webhook.EVENTS.ERRORS_WEBHOOK != '' && webhook.EVENTS.ERRORS) {
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
const localISOTime = new Date(Date.now() - tzoffset).toISOString();
const now = localISOTime;
const errorData = {
event: 'error',
data: {
error: err['error'] || 'Internal Server Error',
message: err['message'] || 'Internal Server Error',
status: err['status'] || 500,
response: {
message: err['message'] || 'Internal Server Error',
},
},
date_time: now,
};
logger.error(errorData);
const baseURL = webhook.EVENTS.ERRORS_WEBHOOK;
const httpService = axios.create({ baseURL });
httpService.post('', errorData);
}
if (err['message'].includes('No sessions')) {
console.log(err['message']);
process.exit(1);
}
return res.status(err['status'] || 500).json({
status: err['status'] || 500,
error: err['error'] || 'Internal Server Error',

View File

@ -55,6 +55,9 @@ export const instanceNameSchema: JSONSchema7 = {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
@ -856,6 +859,9 @@ export const webhookSchema: JSONSchema7 = {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
@ -927,6 +933,9 @@ export const websocketSchema: JSONSchema7 = {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
@ -967,6 +976,9 @@ export const rabbitmqSchema: JSONSchema7 = {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
],
},
},
@ -985,9 +997,10 @@ export const typebotSchema: JSONSchema7 = {
expire: { type: 'integer' },
delay_message: { type: 'integer' },
unknown_message: { type: 'string' },
listening_from_me: { type: 'boolean', enum: [true, false] },
},
required: ['enabled', 'url', 'typebot', 'expire'],
...isNotEmpty('enabled', 'url', 'typebot', 'expire'),
required: ['enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'],
...isNotEmpty('enabled', 'url', 'typebot', 'expire', 'delay_message', 'unknown_message', 'listening_from_me'),
};
export const typebotStatusSchema: JSONSchema7 = {
@ -1023,3 +1036,17 @@ export const proxySchema: JSONSchema7 = {
required: ['enabled', 'proxy'],
...isNotEmpty('enabled', 'proxy'),
};
export const chamaaiSchema: JSONSchema7 = {
$id: v4(),
type: 'object',
properties: {
enabled: { type: 'boolean', enum: [true, false] },
url: { type: 'string' },
token: { type: 'string' },
waNumber: { type: 'string' },
answerByAudio: { type: 'boolean', enum: [true, false] },
},
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
};

View File

@ -0,0 +1,29 @@
import { Logger } from '../../config/logger.config';
import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ChamaaiService } from '../services/chamaai.service';
const logger = new Logger('ChamaaiController');
export class ChamaaiController {
constructor(private readonly chamaaiService: ChamaaiService) {}
public async createChamaai(instance: InstanceDto, data: ChamaaiDto) {
logger.verbose('requested createChamaai from ' + instance.instanceName + ' instance');
if (!data.enabled) {
logger.verbose('chamaai disabled');
data.url = '';
data.token = '';
data.waNumber = '';
data.answerByAudio = false;
}
return this.chamaaiService.create(instance, data);
}
public async findChamaai(instance: InstanceDto) {
logger.verbose('requested findChamaai from ' + instance.instanceName + ' instance');
return this.chamaaiService.find(instance);
}
}

View File

@ -67,6 +67,7 @@ export class InstanceController {
typebot_keyword_finish,
typebot_delay_message,
typebot_unknown_message,
typebot_listening_from_me,
}: InstanceDto) {
try {
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
@ -126,6 +127,9 @@ export class InstanceController {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = events;
@ -172,9 +176,12 @@ export class InstanceController {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = events;
newEvents = websocket_events;
}
this.websocketService.create(instance, {
enabled: true,
@ -216,9 +223,12 @@ export class InstanceController {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
} else {
newEvents = events;
newEvents = rabbitmq_events;
}
this.rabbitmqService.create(instance, {
enabled: true,
@ -247,6 +257,7 @@ export class InstanceController {
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
listening_from_me: typebot_listening_from_me,
});
} catch (error) {
this.logger.log(error);
@ -304,6 +315,7 @@ export class InstanceController {
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
listening_from_me: typebot_listening_from_me,
},
settings,
qrcode: getQrcode,
@ -396,6 +408,7 @@ export class InstanceController {
keyword_finish: typebot_keyword_finish,
delay_message: typebot_delay_message,
unknown_message: typebot_unknown_message,
listening_from_me: typebot_listening_from_me,
},
settings,
chatwoot: {
@ -482,12 +495,13 @@ export class InstanceController {
}
public async fetchInstances({ instanceName }: InstanceDto) {
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
if (instanceName) {
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
this.logger.verbose('instanceName: ' + instanceName);
return this.waMonitor.instanceInfo(instanceName);
}
this.logger.verbose('requested fetchInstances (all instances)');
return this.waMonitor.instanceInfo();
}

View File

@ -40,6 +40,9 @@ export class RabbitmqController {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
}

View File

@ -48,6 +48,9 @@ export class WebhookController {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
}

View File

@ -40,6 +40,9 @@ export class WebsocketController {
'CONNECTION_UPDATE',
'CALL',
'NEW_JWT_TOKEN',
'TYPEBOT_START',
'TYPEBOT_CHANGE_STATUS',
'CHAMA_AI_ACTION',
];
}

View File

@ -0,0 +1,7 @@
export class ChamaaiDto {
enabled: boolean;
url: string;
token: string;
waNumber: string;
answerByAudio: boolean;
}

View File

@ -28,6 +28,7 @@ export class InstanceDto {
typebot_keyword_finish?: string;
typebot_delay_message?: number;
typebot_unknown_message?: string;
typebot_listening_from_me?: boolean;
proxy_enabled?: boolean;
proxy_proxy?: string;
}

View File

@ -14,5 +14,6 @@ export class TypebotDto {
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
listening_from_me?: boolean;
sessions?: Session[];
}

View File

@ -55,7 +55,7 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) {
}
}
async function apikey(req: Request, res: Response, next: NextFunction) {
async function apikey(req: Request, _: Response, next: NextFunction) {
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
const key = req.get('apikey');

View File

@ -4,31 +4,40 @@ import { join } from 'path';
import { configService, Database, Redis } from '../../config/env.config';
import { INSTANCE_DIR } from '../../config/path.config';
import { BadRequestException, ForbiddenException, NotFoundException } from '../../exceptions';
import {
BadRequestException,
ForbiddenException,
InternalServerErrorException,
NotFoundException,
} from '../../exceptions';
import { dbserver } from '../../libs/db.connect';
import { InstanceDto } from '../dto/instance.dto';
import { cache, waMonitor } from '../whatsapp.module';
async function getInstance(instanceName: string) {
const db = configService.get<Database>('DATABASE');
const redisConf = configService.get<Redis>('REDIS');
try {
const db = configService.get<Database>('DATABASE');
const redisConf = configService.get<Redis>('REDIS');
const exists = !!waMonitor.waInstances[instanceName];
const exists = !!waMonitor.waInstances[instanceName];
if (redisConf.ENABLED) {
const keyExists = await cache.keyExists();
return exists || keyExists;
if (redisConf.ENABLED) {
const keyExists = await cache.keyExists();
return exists || keyExists;
}
if (db.ENABLED) {
const collection = dbserver
.getClient()
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
.collection(instanceName);
return exists || (await collection.find({}).toArray()).length > 0;
}
return exists || existsSync(join(INSTANCE_DIR, instanceName));
} catch (error) {
throw new InternalServerErrorException(error?.toString());
}
if (db.ENABLED) {
const collection = dbserver
.getClient()
.db(db.CONNECTION.DB_PREFIX_NAME + '-instances')
.collection(instanceName);
return exists || (await collection.find({}).toArray()).length > 0;
}
return exists || existsSync(join(INSTANCE_DIR, instanceName));
}
export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {

View File

@ -0,0 +1,24 @@
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
export class ChamaaiRaw {
_id?: string;
enabled?: boolean;
url?: string;
token?: string;
waNumber?: string;
answerByAudio?: boolean;
}
const chamaaiSchema = new Schema<ChamaaiRaw>({
_id: { type: String, _id: true },
enabled: { type: Boolean, required: true },
url: { type: String, required: true },
token: { type: String, required: true },
waNumber: { type: String, required: true },
answerByAudio: { type: Boolean, required: true },
});
export const ChamaaiModel = dbserver?.model(ChamaaiRaw.name, chamaaiSchema, 'chamaai');
export type IChamaaiModel = typeof ChamaaiModel;

View File

@ -1,4 +1,5 @@
export * from './auth.model';
export * from './chamaai.model';
export * from './chat.model';
export * from './chatwoot.model';
export * from './contact.model';

View File

@ -19,6 +19,7 @@ export class TypebotRaw {
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
listening_from_me?: boolean;
sessions?: Session[];
}
@ -31,6 +32,7 @@ const typebotSchema = new Schema<TypebotRaw>({
keyword_finish: { type: String, required: true },
delay_message: { type: Number, required: true },
unknown_message: { type: String, required: true },
listening_from_me: { type: Boolean, required: true },
sessions: [
{
remoteJid: { type: String, required: true },

View File

@ -0,0 +1,62 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { ConfigService } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { IInsert, Repository } from '../abstract/abstract.repository';
import { ChamaaiRaw, IChamaaiModel } from '../models';
export class ChamaaiRepository extends Repository {
constructor(private readonly chamaaiModel: IChamaaiModel, private readonly configService: ConfigService) {
super(configService);
}
private readonly logger = new Logger('ChamaaiRepository');
public async create(data: ChamaaiRaw, instance: string): Promise<IInsert> {
try {
this.logger.verbose('creating chamaai');
if (this.dbSettings.ENABLED) {
this.logger.verbose('saving chamaai to db');
const insert = await this.chamaaiModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
this.logger.verbose('chamaai saved to db: ' + insert.modifiedCount + ' chamaai');
return { insertCount: insert.modifiedCount };
}
this.logger.verbose('saving chamaai to store');
this.writeStore<ChamaaiRaw>({
path: join(this.storePath, 'chamaai'),
fileName: instance,
data,
});
this.logger.verbose('chamaai saved to store in path: ' + join(this.storePath, 'chamaai') + '/' + instance);
this.logger.verbose('chamaai created');
return { insertCount: 1 };
} catch (error) {
return error;
}
}
public async find(instance: string): Promise<ChamaaiRaw> {
try {
this.logger.verbose('finding chamaai');
if (this.dbSettings.ENABLED) {
this.logger.verbose('finding chamaai in db');
return await this.chamaaiModel.findOne({ _id: instance });
}
this.logger.verbose('finding chamaai in store');
return JSON.parse(
readFileSync(join(this.storePath, 'chamaai', instance + '.json'), {
encoding: 'utf-8',
}),
) as ChamaaiRaw;
} catch (error) {
return {};
}
}
}

View File

@ -5,6 +5,7 @@ import { join } from 'path';
import { Auth, ConfigService, Database } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { AuthRepository } from './auth.repository';
import { ChamaaiRepository } from './chamaai.repository';
import { ChatRepository } from './chat.repository';
import { ChatwootRepository } from './chatwoot.repository';
import { ContactRepository } from './contact.repository';
@ -29,6 +30,7 @@ export class RepositoryBroker {
public readonly rabbitmq: RabbitmqRepository,
public readonly typebot: TypebotRepository,
public readonly proxy: ProxyRepository,
public readonly chamaai: ChamaaiRepository,
public readonly auth: AuthRepository,
private configService: ConfigService,
dbServer?: MongoClient,
@ -63,6 +65,7 @@ export class RepositoryBroker {
const rabbitmqDir = join(storePath, 'rabbitmq');
const typebotDir = join(storePath, 'typebot');
const proxyDir = join(storePath, 'proxy');
const chamaaiDir = join(storePath, 'chamaai');
const tempDir = join(storePath, 'temp');
if (!fs.existsSync(authDir)) {
@ -113,6 +116,10 @@ export class RepositoryBroker {
this.logger.verbose('creating proxy dir: ' + proxyDir);
fs.mkdirSync(proxyDir, { recursive: true });
}
if (!fs.existsSync(chamaaiDir)) {
this.logger.verbose('creating chamaai dir: ' + chamaaiDir);
fs.mkdirSync(chamaaiDir, { recursive: true });
}
if (!fs.existsSync(tempDir)) {
this.logger.verbose('creating temp dir: ' + tempDir);
fs.mkdirSync(tempDir, { recursive: true });

View File

@ -0,0 +1,52 @@
import { RequestHandler, Router } from 'express';
import { Logger } from '../../config/logger.config';
import { chamaaiSchema, instanceNameSchema } from '../../validate/validate.schema';
import { RouterBroker } from '../abstract/abstract.router';
import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { chamaaiController } from '../whatsapp.module';
import { HttpStatus } from './index.router';
const logger = new Logger('ChamaaiRouter');
export class ChamaaiRouter extends RouterBroker {
constructor(...guards: RequestHandler[]) {
super();
this.router
.post(this.routerPath('set'), ...guards, async (req, res) => {
logger.verbose('request received in setChamaai');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<ChamaaiDto>({
request: req,
schema: chamaaiSchema,
ClassRef: ChamaaiDto,
execute: (instance, data) => chamaaiController.createChamaai(instance, data),
});
res.status(HttpStatus.CREATED).json(response);
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
logger.verbose('request received in findChamaai');
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) => chamaaiController.findChamaai(instance),
});
res.status(HttpStatus.OK).json(response);
});
}
public readonly router = Router();
}

View File

@ -4,6 +4,7 @@ import fs from 'fs';
import { Auth, configService } from '../../config/env.config';
import { authGuard } from '../guards/auth.guard';
import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard';
import { ChamaaiRouter } from './chamaai.router';
import { ChatRouter } from './chat.router';
import { ChatwootRouter } from './chatwoot.router';
import { GroupRouter } from './group.router';
@ -52,6 +53,7 @@ router
.use('/websocket', new WebsocketRouter(...guards).router)
.use('/rabbitmq', new RabbitmqRouter(...guards).router)
.use('/typebot', new TypebotRouter(...guards).router)
.use('/proxy', new ProxyRouter(...guards).router);
.use('/proxy', new ProxyRouter(...guards).router)
.use('/chamaai', new ChamaaiRouter(...guards).router);
export { HttpStatus, router };

View File

@ -0,0 +1,230 @@
import axios from 'axios';
import { writeFileSync } from 'fs';
import path from 'path';
import { ConfigService, HttpServer } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { ChamaaiDto } from '../dto/chamaai.dto';
import { InstanceDto } from '../dto/instance.dto';
import { ChamaaiRaw } from '../models';
import { Events } from '../types/wa.types';
import { WAMonitoringService } from './monitor.service';
export class ChamaaiService {
constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {}
private readonly logger = new Logger(ChamaaiService.name);
public create(instance: InstanceDto, data: ChamaaiDto) {
this.logger.verbose('create chamaai: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setChamaai(data);
return { chamaai: { ...instance, chamaai: data } };
}
public async find(instance: InstanceDto): Promise<ChamaaiRaw> {
try {
this.logger.verbose('find chamaai: ' + instance.instanceName);
const result = await this.waMonitor.waInstances[instance.instanceName].findChamaai();
if (Object.keys(result).length === 0) {
throw new Error('Chamaai not found');
}
return result;
} catch (error) {
return { enabled: false, url: '', token: '', waNumber: '', answerByAudio: false };
}
}
private getTypeMessage(msg: any) {
this.logger.verbose('get type message');
const types = {
conversation: msg.conversation,
extendedTextMessage: msg.extendedTextMessage?.text,
};
this.logger.verbose('type message: ' + types);
return types;
}
private getMessageContent(types: any) {
this.logger.verbose('get message content');
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
const result = typeKey ? types[typeKey] : undefined;
this.logger.verbose('message content: ' + result);
return result;
}
private getConversationMessage(msg: any) {
this.logger.verbose('get conversation message');
const types = this.getTypeMessage(msg);
const messageContent = this.getMessageContent(types);
this.logger.verbose('conversation message: ' + messageContent);
return messageContent;
}
private calculateTypingTime(text: string) {
const wordsPerMinute = 100;
const wordCount = text.split(' ').length;
const typingTimeInMinutes = wordCount / wordsPerMinute;
const typingTimeInMilliseconds = typingTimeInMinutes * 60;
return typingTimeInMilliseconds;
}
private convertToMilliseconds(count: number) {
const averageCharactersPerSecond = 15;
const characterCount = count;
const speakingTimeInSeconds = characterCount / averageCharactersPerSecond;
return speakingTimeInSeconds;
}
private getRegexPatterns() {
const patternsToCheck = [
'.*atend.*humano.*',
'.*falar.*com.*um.*humano.*',
'.*fala.*humano.*',
'.*atend.*humano.*',
'.*fala.*atend.*',
'.*preciso.*ajuda.*',
'.*quero.*suporte.*',
'.*preciso.*assiste.*',
'.*ajuda.*atend.*',
'.*chama.*atendente.*',
'.*suporte.*urgente.*',
'.*atend.*por.*favor.*',
'.*quero.*falar.*com.*alguém.*',
'.*falar.*com.*um.*humano.*',
'.*transfer.*humano.*',
'.*transfer.*atend.*',
'.*equipe.*humano.*',
'.*suporte.*humano.*',
];
const regexPatterns = patternsToCheck.map((pattern) => new RegExp(pattern, 'iu'));
return regexPatterns;
}
public async sendChamaai(instance: InstanceDto, remoteJid: string, msg: any) {
const content = this.getConversationMessage(msg.message);
const msgType = msg.messageType;
const find = await this.find(instance);
const url = find.url;
const token = find.token;
const waNumber = find.waNumber;
const answerByAudio = find.answerByAudio;
if (!content && msgType !== 'audioMessage') {
return;
}
let data;
let endpoint;
if (msgType === 'audioMessage') {
const downloadBase64 = await this.waMonitor.waInstances[instance.instanceName].getBase64FromMediaMessage({
message: {
...msg,
},
});
const random = Math.random().toString(36).substring(7);
const nameFile = `${random}.ogg`;
const fileData = Buffer.from(downloadBase64.base64, 'base64');
const fileName = `${path.join(
this.waMonitor.waInstances[instance.instanceName].storePath,
'temp',
`${nameFile}`,
)}`;
writeFileSync(fileName, fileData, 'utf8');
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const url = `${urlServer}/store/temp/${nameFile}`;
data = {
waNumber: waNumber,
audioUrl: url,
queryNumber: remoteJid.split('@')[0],
answerByAudio: answerByAudio,
};
endpoint = 'processMessageAudio';
} else {
data = {
waNumber: waNumber,
question: content,
queryNumber: remoteJid.split('@')[0],
answerByAudio: answerByAudio,
};
endpoint = 'processMessageText';
}
const request = await axios.post(`${url}/${endpoint}`, data, {
headers: {
Authorization: `${token}`,
},
});
const answer = request.data?.answer;
const type = request.data?.type;
const characterCount = request.data?.characterCount;
if (answer) {
if (type === 'text') {
this.waMonitor.waInstances[instance.instanceName].textMessage({
number: remoteJid.split('@')[0],
options: {
delay: this.calculateTypingTime(answer) * 1000 || 1000,
presence: 'composing',
linkPreview: false,
quoted: {
key: msg.key,
message: msg.message,
},
},
textMessage: {
text: answer,
},
});
}
if (type === 'audio') {
this.waMonitor.waInstances[instance.instanceName].audioWhatsapp({
number: remoteJid.split('@')[0],
options: {
delay: characterCount ? this.convertToMilliseconds(characterCount) * 1000 || 1000 : 1000,
presence: 'recording',
encoding: true,
},
audioMessage: {
audio: answer,
},
});
}
if (this.getRegexPatterns().some((pattern) => pattern.test(answer))) {
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.CHAMA_AI_ACTION, {
remoteJid: remoteJid,
message: msg,
answer: answer,
action: 'transfer',
});
}
}
}
}

View File

@ -460,7 +460,9 @@ export class ChatwootService {
let contact: any;
if (body.key.fromMe) {
if (findContact) {
contact = findContact;
contact = await this.updateContact(instance, findContact.id, {
avatar_url: picture_url.profilePictureUrl || null,
});
} else {
const jid = isGroup ? null : body.key.remoteJid;
contact = await this.createContact(
@ -481,7 +483,9 @@ export class ChatwootService {
avatar_url: picture_url.profilePictureUrl || null,
});
} else {
contact = findContact;
contact = await this.updateContact(instance, findContact.id, {
avatar_url: picture_url.profilePictureUrl || null,
});
}
} else {
const jid = isGroup ? null : body.key.remoteJid;
@ -994,6 +998,9 @@ export class ChatwootService {
public async receiveWebhook(instance: InstanceDto, body: any) {
try {
// espera 500ms para evitar duplicidade de mensagens
await new Promise((resolve) => setTimeout(resolve, 500));
this.logger.verbose('receive webhook to chatwoot instance: ' + instance.instanceName);
const client = await this.clientCw(instance);
@ -1567,16 +1574,16 @@ export class ChatwootService {
await this.createBotMessage(instance, msgStatus, 'incoming');
}
if (event === 'connection.update') {
this.logger.verbose('event connection.update');
// if (event === 'connection.update') {
// this.logger.verbose('event connection.update');
if (body.status === 'open') {
const msgConnection = `🚀 Connection successfully established!`;
// if (body.status === 'open') {
// const msgConnection = `🚀 Connection successfully established!`;
this.logger.verbose('send message to chatwoot');
await this.createBotMessage(instance, msgConnection, 'incoming');
}
}
// this.logger.verbose('send message to chatwoot');
// await this.createBotMessage(instance, msgConnection, 'incoming');
// }
// }
if (event === 'qrcode.updated') {
this.logger.verbose('event qrcode.updated');

View File

@ -7,7 +7,6 @@ import { join } from 'path';
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
import { Logger } from '../../config/logger.config';
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
import { NotFoundException } from '../../exceptions';
import { dbserver } from '../../libs/db.connect';
import { RedisCache } from '../../libs/redis.client';
import {
@ -76,77 +75,57 @@ export class WAMonitoringService {
public async instanceInfo(instanceName?: string) {
this.logger.verbose('get instance info');
if (instanceName && !this.waInstances[instanceName]) {
throw new NotFoundException(`Instance "${instanceName}" not found`);
}
const instances: any[] = [];
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
for await (const [key, value] of Object.entries(this.waInstances)) {
if (value) {
this.logger.verbose('get instance info: ' + key);
let chatwoot: any;
const instances: any[] = await Promise.all(
Object.entries(this.waInstances).map(async ([key, value]) => {
const status = value?.connectionStatus?.state || 'unknown';
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
const findChatwoot = await this.waInstances[key].findChatwoot();
if (findChatwoot && findChatwoot.enabled) {
chatwoot = {
...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
};
if (status === 'unknown') {
return null;
}
if (value.connectionStatus.state === 'open') {
if (status === 'open') {
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
const instanceData = {
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
profileStatus: (await value.getProfileStatus()) || '',
status: value.connectionStatus.state,
},
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
instances.push(instanceData);
} else {
this.logger.verbose('instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state);
const instanceData = {
instance: {
instanceName: key,
status: value.connectionStatus.state,
},
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey;
instanceData.instance['chatwoot'] = chatwoot;
}
instances.push(instanceData);
}
}
}
const instanceData: any = {
instance: {
instanceName: key,
owner: value.wuid,
profileName: (await value.getProfileName()) || 'not loaded',
profilePictureUrl: value.profilePictureUrl,
profileStatus: (await value.getProfileStatus()) || '',
status: status,
},
};
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
instanceData.instance.serverUrl = urlServer;
instanceData.instance.apikey = (await this.repository.auth.find(key))?.apikey;
const findChatwoot = await this.waInstances[key].findChatwoot();
if (findChatwoot && findChatwoot.enabled) {
instanceData.instance.chatwoot = {
...findChatwoot,
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
};
}
}
return instanceData;
}),
).then((results) => results.filter((instance) => instance !== null));
this.logger.verbose('return instance info: ' + instances.length);
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
if (instanceName) {
const instance = instances.find((i) => i.instance.instanceName === instanceName);
return instance || [];
}
return instances;
}
private delInstanceFiles() {
@ -161,8 +140,7 @@ export class WAMonitoringService {
});
this.logger.verbose('instance files deleted: ' + name);
});
// } else if (this.redis.ENABLED) {
} else {
} else if (!this.redis.ENABLED) {
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) {
if (dirent.isDirectory()) {
@ -220,6 +198,11 @@ export class WAMonitoringService {
execSync(`rm -rf ${join(STORE_DIR, 'auth', 'apikey', instanceName + '.json')}`);
execSync(`rm -rf ${join(STORE_DIR, 'webhook', instanceName + '.json')}`);
execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'chamaai', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'proxy', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'rabbitmq', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`);
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
return;
@ -240,68 +223,85 @@ export class WAMonitoringService {
}
public async loadInstance() {
this.logger.verbose('load instances');
const set = async (name: string) => {
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = name;
this.logger.verbose('instance loaded: ' + name);
await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name);
this.waInstances[name] = instance;
};
this.logger.verbose('Loading instances');
try {
if (this.redis.ENABLED) {
this.logger.verbose('redis enabled');
await this.cache.connect(this.redis as Redis);
const keys = await this.cache.instanceKeys();
if (keys?.length > 0) {
this.logger.verbose('reading instance keys and setting instances');
keys.forEach(async (k) => await set(k.split(':')[1]));
} else {
this.logger.verbose('no instance keys found');
}
return;
}
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
this.logger.verbose('database enabled');
await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) {
this.logger.verbose('reading collections and setting instances');
collections.forEach(async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')));
} else {
this.logger.verbose('no collections found');
}
return;
}
this.logger.verbose('store in files enabled');
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
for await (const dirent of dir) {
if (dirent.isDirectory()) {
this.logger.verbose('reading instance files and setting instances');
const files = readdirSync(join(INSTANCE_DIR, dirent.name), {
encoding: 'utf-8',
});
if (files.length === 0) {
rmSync(join(INSTANCE_DIR, dirent.name), { recursive: true, force: true });
break;
}
await set(dirent.name);
} else {
this.logger.verbose('no instance files found');
}
await this.loadInstancesFromRedis();
} else if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
await this.loadInstancesFromDatabase();
} else {
await this.loadInstancesFromFiles();
}
} catch (error) {
this.logger.error(error);
}
}
private async setInstance(name: string) {
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
instance.instanceName = name;
this.logger.verbose('Instance loaded: ' + name);
await instance.connectToWhatsapp();
this.logger.verbose('connectToWhatsapp: ' + name);
this.waInstances[name] = instance;
}
private async loadInstancesFromRedis() {
this.logger.verbose('Redis enabled');
await this.cache.connect(this.redis as Redis);
const keys = await this.cache.instanceKeys();
if (keys?.length > 0) {
this.logger.verbose('Reading instance keys and setting instances');
await Promise.all(keys.map((k) => this.setInstance(k.split(':')[1])));
} else {
this.logger.verbose('No instance keys found');
}
}
private async loadInstancesFromDatabase() {
this.logger.verbose('Database enabled');
await this.repository.dbServer.connect();
const collections: any[] = await this.dbInstance.collections();
if (collections.length > 0) {
this.logger.verbose('Reading collections and setting instances');
await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, ''))));
} else {
this.logger.verbose('No collections found');
}
}
private async loadInstancesFromFiles() {
this.logger.verbose('Store in files enabled');
const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' });
const instanceDirs = [];
for await (const dirent of dir) {
if (dirent.isDirectory()) {
instanceDirs.push(dirent.name);
} else {
this.logger.verbose('No instance files found');
}
}
await Promise.all(
instanceDirs.map(async (instanceName) => {
this.logger.verbose('Reading instance files and setting instances: ' + instanceName);
const files = readdirSync(join(INSTANCE_DIR, instanceName), { encoding: 'utf-8' });
if (files.length === 0) {
rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true });
} else {
await this.setInstance(instanceName);
}
}),
);
}
private removeInstance() {
this.eventEmitter.on('remove.instance', async (instanceName: string) => {
this.logger.verbose('remove instance: ' + instanceName);

View File

@ -1,4 +1,5 @@
import { Logger } from '../../config/logger.config';
import { initQueues } from '../../libs/amqp.server';
import { InstanceDto } from '../dto/instance.dto';
import { RabbitmqDto } from '../dto/rabbitmq.dto';
import { RabbitmqRaw } from '../models';
@ -13,6 +14,7 @@ export class RabbitmqService {
this.logger.verbose('create rabbitmq: ' + instance.instanceName);
this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data);
initQueues(instance.instanceName, data.events);
return { rabbitmq: { ...instance, rabbitmq: data } };
}

View File

@ -4,6 +4,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 { WAMonitoringService } from './monitor.service';
export class TypebotService {
@ -53,6 +54,7 @@ export class TypebotService {
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
listening_from_me: findData.listening_from_me,
sessions: findData.sessions,
};
@ -76,11 +78,20 @@ export class TypebotService {
keyword_finish: findData.keyword_finish,
delay_message: findData.delay_message,
unknown_message: findData.unknown_message,
listening_from_me: findData.listening_from_me,
sessions: findData.sessions,
};
this.create(instance, typebotData);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, {
remoteJid: remoteJid,
status: status,
url: findData.url,
typebot: findData.typebot,
session,
});
return { typebot: { ...instance, typebot: typebotData } };
}
@ -88,6 +99,15 @@ export class TypebotService {
const remoteJid = data.remoteJid;
const url = data.url;
const typebot = data.typebot;
const variables = data.variables;
const prefilledVariables = {
remoteJid: remoteJid,
};
variables.forEach((variable) => {
prefilledVariables[variable.name] = variable.value;
});
const id = Math.floor(Math.random() * 10000000000).toString();
@ -95,14 +115,9 @@ export class TypebotService {
sessionId: id,
startParams: {
typebot: data.typebot,
prefilledVariables: {
remoteJid: data.remoteJid,
pushName: data.pushName,
instanceName: instance.instanceName,
},
prefilledVariables: prefilledVariables,
},
};
console.log(reqData);
const request = await axios.post(data.url + '/api/v1/sendMessage', reqData);
@ -114,6 +129,14 @@ export class TypebotService {
request.data.clientSideActions,
);
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, {
remoteJid: remoteJid,
url: url,
typebot: typebot,
variables: variables,
sessionId: id,
});
return {
typebot: {
...instance,
@ -121,6 +144,7 @@ export class TypebotService {
url: url,
remoteJid: remoteJid,
typebot: typebot,
variables: variables,
},
},
};
@ -195,6 +219,7 @@ export class TypebotService {
keyword_finish: data.keyword_finish,
delay_message: data.delay_message,
unknown_message: data.unknown_message,
listening_from_me: data.listening_from_me,
sessions: data.sessions,
};
@ -354,13 +379,15 @@ export class TypebotService {
}
public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) {
const url = (await this.find(instance)).url;
const typebot = (await this.find(instance)).typebot;
const sessions = ((await this.find(instance)).sessions as Session[]) ?? [];
const expire = (await this.find(instance)).expire;
const keyword_finish = (await this.find(instance)).keyword_finish;
const delay_message = (await this.find(instance)).delay_message;
const unknown_message = (await this.find(instance)).unknown_message;
const findTypebot = await this.find(instance);
const url = findTypebot.url;
const typebot = findTypebot.typebot;
const sessions = (findTypebot.sessions as Session[]) ?? [];
const expire = findTypebot.expire;
const keyword_finish = findTypebot.keyword_finish;
const delay_message = findTypebot.delay_message;
const unknown_message = findTypebot.unknown_message;
const listening_from_me = findTypebot.listening_from_me;
const session = sessions.find((session) => session.remoteJid === remoteJid);
@ -381,6 +408,7 @@ export class TypebotService {
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
@ -404,6 +432,7 @@ export class TypebotService {
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions: sessions,
remoteJid: remoteJid,
pushName: msg.pushName,
@ -428,6 +457,7 @@ export class TypebotService {
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};
@ -462,6 +492,7 @@ export class TypebotService {
keyword_finish: keyword_finish,
delay_message: delay_message,
unknown_message: unknown_message,
listening_from_me: listening_from_me,
sessions,
};

View File

@ -113,7 +113,7 @@ import {
SendTextDto,
StatusMessage,
} from '../dto/sendMessage.dto';
import { ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, SettingsRaw, TypebotRaw } from '../models';
import { ChatRaw } from '../models/chat.model';
import { ChatwootRaw } from '../models/chatwoot.model';
import { ContactRaw } from '../models/contact.model';
@ -126,7 +126,9 @@ import { MessageUpQuery } from '../repository/messageUp.repository';
import { RepositoryBroker } from '../repository/repository.manager';
import { Events, MessageSubtype, TypeMediaMessage, wa } from '../types/wa.types';
import { waMonitor } from '../whatsapp.module';
import { ChamaaiService } from './chamaai.service';
import { ChatwootService } from './chatwoot.service';
//import { SocksProxyAgent } from './socks-proxy-agent';
import { TypebotService } from './typebot.service';
export class WAStartupService {
@ -151,6 +153,7 @@ export class WAStartupService {
private readonly localRabbitmq: wa.LocalRabbitmq = {};
public readonly localTypebot: wa.LocalTypebot = {};
private readonly localProxy: wa.LocalProxy = {};
private readonly localChamaai: wa.LocalChamaai = {};
public stateConnection: wa.StateConnection = { state: 'close' };
public readonly storePath = join(ROOT_DIR, 'store');
private readonly msgRetryCounterCache: CacheStore = new NodeCache();
@ -164,6 +167,8 @@ export class WAStartupService {
private typebotService = new TypebotService(waMonitor);
private chamaaiService = new ChamaaiService(waMonitor, this.configService);
public set instanceName(name: string) {
this.logger.verbose(`Initializing instance '${name}'`);
if (!name) {
@ -515,6 +520,9 @@ export class WAStartupService {
this.localTypebot.unknown_message = data?.unknown_message;
this.logger.verbose(`Typebot unknown_message: ${this.localTypebot.unknown_message}`);
this.localTypebot.listening_from_me = data?.listening_from_me;
this.logger.verbose(`Typebot listening_from_me: ${this.localTypebot.listening_from_me}`);
this.localTypebot.sessions = data?.sessions;
this.logger.verbose('Typebot loaded');
@ -528,6 +536,7 @@ export class WAStartupService {
this.logger.verbose(`Typebot keyword_finish: ${data.keyword_finish}`);
this.logger.verbose(`Typebot delay_message: ${data.delay_message}`);
this.logger.verbose(`Typebot unknown_message: ${data.unknown_message}`);
this.logger.verbose(`Typebot listening_from_me: ${data.listening_from_me}`);
Object.assign(this.localTypebot, data);
this.logger.verbose('Typebot set');
}
@ -579,6 +588,52 @@ export class WAStartupService {
return data;
}
private async loadChamaai() {
this.logger.verbose('Loading chamaai');
const data = await this.repository.chamaai.find(this.instanceName);
this.localChamaai.enabled = data?.enabled;
this.logger.verbose(`Chamaai enabled: ${this.localChamaai.enabled}`);
this.localChamaai.url = data?.url;
this.logger.verbose(`Chamaai url: ${this.localChamaai.url}`);
this.localChamaai.token = data?.token;
this.logger.verbose(`Chamaai token: ${this.localChamaai.token}`);
this.localChamaai.waNumber = data?.waNumber;
this.logger.verbose(`Chamaai waNumber: ${this.localChamaai.waNumber}`);
this.localChamaai.answerByAudio = data?.answerByAudio;
this.logger.verbose(`Chamaai answerByAudio: ${this.localChamaai.answerByAudio}`);
this.logger.verbose('Chamaai loaded');
}
public async setChamaai(data: ChamaaiRaw) {
this.logger.verbose('Setting chamaai');
await this.repository.chamaai.create(data, this.instanceName);
this.logger.verbose(`Chamaai url: ${data.url}`);
this.logger.verbose(`Chamaai token: ${data.token}`);
this.logger.verbose(`Chamaai waNumber: ${data.waNumber}`);
this.logger.verbose(`Chamaai answerByAudio: ${data.answerByAudio}`);
Object.assign(this.localChamaai, data);
this.logger.verbose('Chamaai set');
}
public async findChamaai() {
this.logger.verbose('Finding chamaai');
const data = await this.repository.chamaai.find(this.instanceName);
if (!data) {
this.logger.verbose('Chamaai not found');
throw new NotFoundException('Chamaai not found');
}
return data;
}
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
const webhookGlobal = this.configService.get<Webhook>('WEBHOOK');
const webhookLocal = this.localWebhook.events;
@ -633,6 +688,25 @@ export class WAStartupService {
}
amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message)));
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
const logData = {
local: WAStartupService.name + '.sendData-RabbitMQ',
event,
instance: this.instance.name,
data,
server_url: serverUrl,
apikey: (expose && instanceApikey) || null,
date_time: now,
sender: this.wuid,
};
if (expose && instanceApikey) {
logData['apikey'] = instanceApikey;
}
this.logger.log(logData);
}
}
}
}
@ -658,6 +732,25 @@ export class WAStartupService {
this.logger.verbose('Sending data to socket.io in channel: ' + this.instance.name);
io.of(`/${this.instance.name}`).emit(event, message);
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
const logData = {
local: WAStartupService.name + '.sendData-Websocket',
event,
instance: this.instance.name,
data,
server_url: serverUrl,
apikey: (expose && instanceApikey) || null,
date_time: now,
sender: this.wuid,
};
if (expose && instanceApikey) {
logData['apikey'] = instanceApikey;
}
this.logger.log(logData);
}
}
}
@ -696,7 +789,7 @@ export class WAStartupService {
}
try {
if (this.localWebhook.enabled && isURL(this.localWebhook.url)) {
if (this.localWebhook.enabled && isURL(this.localWebhook.url, { require_tld: false })) {
const httpService = axios.create({ baseURL });
const postData = {
event,
@ -1065,6 +1158,7 @@ export class WAStartupService {
this.loadRabbitmq();
this.loadTypebot();
this.loadProxy();
this.loadChamaai();
this.instance.authState = await this.defineAuthState();
@ -1338,7 +1432,12 @@ export class WAStartupService {
this.logger.verbose('Event received: messages.upsert');
const received = messages[0];
if (type !== 'notify' || received.message?.protocolMessage || received.message?.pollUpdateMessage) {
if (
type !== 'notify' ||
!received.message ||
received.message?.protocolMessage ||
received.message?.pollUpdateMessage
) {
this.logger.verbose('message rejected');
return;
}
@ -1383,8 +1482,18 @@ export class WAStartupService {
);
}
if (this.localTypebot.enabled && messageRaw.key.remoteJid.includes('@s.whatsapp.net')) {
await this.typebotService.sendTypebot(
if (this.localTypebot.enabled) {
if (!(this.localTypebot.listening_from_me === false && messageRaw.key.fromMe === true)) {
await this.typebotService.sendTypebot(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
messageRaw,
);
}
}
if (this.localChamaai.enabled && messageRaw.key.fromMe === false) {
await this.chamaaiService.sendChamaai(
{ instanceName: this.instance.name },
messageRaw.key.remoteJid,
messageRaw,
@ -1795,15 +1904,15 @@ export class WAStartupService {
this.logger.verbose('Getting profile with jid: ' + jid);
try {
this.logger.verbose('Getting profile info');
const business = await this.fetchBusinessProfile(jid);
if (number) {
const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift();
const picture = await this.profilePicture(jid);
const status = await this.getStatus(jid);
const picture = await this.profilePicture(info?.jid);
const status = await this.getStatus(info?.jid);
const business = await this.fetchBusinessProfile(info?.jid);
return {
wuid: jid,
wuid: info?.jid || jid,
name: info?.name,
numberExists: info?.exists,
picture: picture?.profilePictureUrl,
@ -1815,6 +1924,7 @@ export class WAStartupService {
};
} else {
const info = await waMonitor.instanceInfo(instanceName);
const business = await this.fetchBusinessProfile(jid);
return {
wuid: jid,
@ -2315,7 +2425,7 @@ export class WAStartupService {
return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options);
}
private async processAudio(audio: string, number: string) {
public async processAudio(audio: string, number: string) {
this.logger.verbose('Processing audio');
let tempAudioPath: string;
let outputAudio: string;
@ -2969,7 +3079,9 @@ export class WAStartupService {
public async createGroup(create: CreateGroupDto) {
this.logger.verbose('Creating group: ' + create.subject);
try {
const participants = create.participants.map((p) => this.createJid(p));
const participants = (await this.whatsappNumber({ numbers: create.participants }))
.filter((participant) => participant.exists)
.map((participant) => participant.jid);
const { id } = await this.client.groupCreate(create.subject, participants);
this.logger.verbose('Group created: ' + id);
@ -2979,7 +3091,7 @@ export class WAStartupService {
}
if (create?.promoteParticipants) {
this.logger.verbose('Prometing group participants: ' + create.description);
this.logger.verbose('Prometing group participants: ' + participants);
await this.updateGParticipant({
groupJid: id,
action: 'promote',
@ -2987,8 +3099,8 @@ export class WAStartupService {
});
}
const group = await this.client.groupMetadata(id);
this.logger.verbose('Getting group metadata');
const group = await this.client.groupMetadata(id);
return group;
} catch (error) {

View File

@ -23,6 +23,9 @@ export enum Events {
GROUPS_UPDATE = 'groups.update',
GROUP_PARTICIPANTS_UPDATE = 'group-participants.update',
CALL = 'call',
TYPEBOT_START = 'typebot.start',
TYPEBOT_CHANGE_STATUS = 'typebot.change-status',
CHAMA_AI_ACTION = 'chama-ai.action',
}
export declare namespace wa {
@ -94,6 +97,7 @@ export declare namespace wa {
keyword_finish?: string;
delay_message?: number;
unknown_message?: string;
listening_from_me?: boolean;
sessions?: Session[];
};
@ -102,6 +106,14 @@ export declare namespace wa {
proxy?: string;
};
export type LocalChamaai = {
enabled?: boolean;
url?: string;
token?: string;
waNumber?: string;
answerByAudio?: boolean;
};
export type StateConnection = {
instance?: string;
state?: WAConnectionState | 'refused';

View File

@ -3,6 +3,7 @@ import { eventEmitter } from '../config/event.config';
import { Logger } from '../config/logger.config';
import { dbserver } from '../libs/db.connect';
import { RedisCache } from '../libs/redis.client';
import { ChamaaiController } from './controllers/chamaai.controller';
import { ChatController } from './controllers/chat.controller';
import { ChatwootController } from './controllers/chatwoot.controller';
import { GroupController } from './controllers/group.controller';
@ -17,6 +18,7 @@ import { WebhookController } from './controllers/webhook.controller';
import { WebsocketController } from './controllers/websocket.controller';
import {
AuthModel,
ChamaaiModel,
ChatModel,
ChatwootModel,
ContactModel,
@ -30,6 +32,7 @@ import {
WebsocketModel,
} from './models';
import { AuthRepository } from './repository/auth.repository';
import { ChamaaiRepository } from './repository/chamaai.repository';
import { ChatRepository } from './repository/chat.repository';
import { ChatwootRepository } from './repository/chatwoot.repository';
import { ContactRepository } from './repository/contact.repository';
@ -43,6 +46,7 @@ import { TypebotRepository } from './repository/typebot.repository';
import { WebhookRepository } from './repository/webhook.repository';
import { WebsocketRepository } from './repository/websocket.repository';
import { AuthService } from './services/auth.service';
import { ChamaaiService } from './services/chamaai.service';
import { ChatwootService } from './services/chatwoot.service';
import { WAMonitoringService } from './services/monitor.service';
import { ProxyService } from './services/proxy.service';
@ -62,6 +66,7 @@ const typebotRepository = new TypebotRepository(TypebotModel, configService);
const webhookRepository = new WebhookRepository(WebhookModel, configService);
const websocketRepository = new WebsocketRepository(WebsocketModel, configService);
const proxyRepository = new ProxyRepository(ProxyModel, configService);
const chamaaiRepository = new ChamaaiRepository(ChamaaiModel, configService);
const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService);
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
const settingsRepository = new SettingsRepository(SettingsModel, configService);
@ -79,6 +84,7 @@ export const repository = new RepositoryBroker(
rabbitmqRepository,
typebotRepository,
proxyRepository,
chamaaiRepository,
authRepository,
configService,
dbserver?.getClient(),
@ -106,6 +112,10 @@ const proxyService = new ProxyService(waMonitor);
export const proxyController = new ProxyController(proxyService);
const chamaaiService = new ChamaaiService(waMonitor, configService);
export const chamaaiController = new ChamaaiController(chamaaiService);
const rabbitmqService = new RabbitmqService(waMonitor);
export const rabbitmqController = new RabbitmqController(rabbitmqService);