diff --git a/.DS_Store b/.DS_Store index 0ae7ca6c..90ea33c6 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/env b/env new file mode 100644 index 00000000..d3fd9ee9 --- /dev/null +++ b/env @@ -0,0 +1,5 @@ +OPENAI_API_KEY="" +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=0 +REDIS_PASS=0 diff --git a/src/config.ts b/src/config.ts new file mode 100755 index 00000000..a51b1ff1 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,14 @@ +import dotenv from "dotenv" + +dotenv.config() + +export const config = { + openAI: { + apiToken: process.env.OPENAI_API_KEY, + }, + redis: { + host: process.env.REDIS_HOST || "localhost", + port: (process.env.REDIS_PORT as unknown as number) || 6379, + db: (process.env.REDIS_DB as unknown as number) || 0, + }, +} diff --git a/src/config/env.config.ts b/src/config/env.config.ts old mode 100644 new mode 100755 index dcb90fbc..599a1cd3 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -1,333 +1,348 @@ -import { isBooleanString } from 'class-validator'; -import { readFileSync } from 'fs'; -import { load } from 'js-yaml'; -import { join } from 'path'; - -export type HttpServer = { TYPE: 'http' | 'https'; PORT: number; URL: string }; - -export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE'; -export type Cors = { - ORIGIN: string[]; - METHODS: HttpMethods[]; - CREDENTIALS: boolean; -}; - -export type LogBaileys = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'; - -export type LogLevel = 'ERROR' | 'WARN' | 'DEBUG' | 'INFO' | 'LOG' | 'VERBOSE' | 'DARK' | 'WEBHOOKS'; - -export type Log = { - LEVEL: LogLevel[]; - COLOR: boolean; - BAILEYS: LogBaileys; -}; - -export type SaveData = { - INSTANCE: boolean; - NEW_MESSAGE: boolean; - MESSAGE_UPDATE: boolean; - CONTACTS: boolean; - CHATS: boolean; -}; - -export type StoreConf = { - MESSAGES: boolean; - MESSAGE_UP: boolean; - CONTACTS: boolean; - CHATS: boolean; -}; - -export type CleanStoreConf = { - CLEANING_INTERVAL: number; - MESSAGES: boolean; - MESSAGE_UP: boolean; - CONTACTS: boolean; - CHATS: boolean; -}; - -export type DBConnection = { - URI: string; - DB_PREFIX_NAME: string; -}; -export type Database = { - CONNECTION: DBConnection; - ENABLED: boolean; - SAVE_DATA: SaveData; -}; - -export type Redis = { - ENABLED: boolean; - URI: string; - PREFIX_KEY: string; -}; - -export type Rabbitmq = { - ENABLED: boolean; - URI: string; -}; - -export type Sqs = { - ENABLED: boolean; - ACCESS_KEY_ID: string; - SECRET_ACCESS_KEY: string; - ACCOUNT_ID: string; - REGION: string; -}; - -export type Websocket = { - ENABLED: boolean; -}; - -export type Chatwoot = { - USE_REPLY_ID: boolean; -}; - -export type EventsWebhook = { - APPLICATION_STARTUP: boolean; - QRCODE_UPDATED: boolean; - MESSAGES_SET: boolean; - MESSAGES_UPSERT: boolean; - MESSAGES_UPDATE: boolean; - MESSAGES_DELETE: boolean; - SEND_MESSAGE: boolean; - CONTACTS_SET: boolean; - CONTACTS_UPDATE: boolean; - CONTACTS_UPSERT: boolean; - PRESENCE_UPDATE: boolean; - CHATS_SET: boolean; - CHATS_UPDATE: boolean; - CHATS_DELETE: boolean; - CHATS_UPSERT: boolean; - CONNECTION_UPDATE: boolean; - GROUPS_UPSERT: boolean; - GROUP_UPDATE: boolean; - 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 }; -export type Jwt = { EXPIRIN_IN: number; SECRET: string }; - -export type Auth = { - API_KEY: ApiKey; - EXPOSE_IN_FETCH_INSTANCES: boolean; - JWT: Jwt; - TYPE: 'jwt' | 'apikey'; -}; - -export type DelInstance = number | boolean; - -export type GlobalWebhook = { - URL: string; - ENABLED: boolean; - WEBHOOK_BY_EVENTS: boolean; -}; -export type SslConf = { PRIVKEY: string; FULLCHAIN: string }; -export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook }; -export type ConfigSessionPhone = { CLIENT: string; NAME: string }; -export type QrCode = { LIMIT: number; COLOR: string }; -export type Typebot = { API_VERSION: string }; -export type Production = boolean; - -export interface Env { - SERVER: HttpServer; - CORS: Cors; - SSL_CONF: SslConf; - STORE: StoreConf; - CLEAN_STORE: CleanStoreConf; - DATABASE: Database; - REDIS: Redis; - RABBITMQ: Rabbitmq; - SQS: Sqs; - WEBSOCKET: Websocket; - LOG: Log; - DEL_INSTANCE: DelInstance; - WEBHOOK: Webhook; - CONFIG_SESSION_PHONE: ConfigSessionPhone; - QRCODE: QrCode; - TYPEBOT: Typebot; - AUTHENTICATION: Auth; - PRODUCTION?: Production; - CHATWOOT?: Chatwoot; -} - -export type Key = keyof Env; - -export class ConfigService { - constructor() { - this.loadEnv(); - } - - private env: Env; - - public get(key: Key) { - return this.env[key] as T; - } - - private loadEnv() { - this.env = !(process.env?.DOCKER_ENV === 'true') ? this.envYaml() : this.envProcess(); - this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD'; - if (process.env?.DOCKER_ENV === 'true') { - this.env.SERVER.TYPE = 'http'; - this.env.SERVER.PORT = 8080; - } - } - - private envYaml(): Env { - return load(readFileSync(join(process.cwd(), 'src', 'env.yml'), { encoding: 'utf-8' })) as Env; - } - - private envProcess(): Env { - return { - SERVER: { - TYPE: process.env.SERVER_TYPE as 'http' | 'https', - 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[]) || ['POST', 'GET', 'PUT', 'DELETE'], - CREDENTIALS: process.env?.CORS_CREDENTIALS === 'true', - }, - SSL_CONF: { - PRIVKEY: process.env?.SSL_CONF_PRIVKEY || '', - FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN || '', - }, - STORE: { - MESSAGES: process.env?.STORE_MESSAGES === 'true', - MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true', - CONTACTS: process.env?.STORE_CONTACTS === 'true', - CHATS: process.env?.STORE_CHATS === 'true', - }, - CLEAN_STORE: { - CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL) - ? Number.parseInt(process.env.CLEAN_STORE_CLEANING_TERMINAL) - : 7200, - MESSAGES: process.env?.CLEAN_STORE_MESSAGES === 'true', - MESSAGE_UP: process.env?.CLEAN_STORE_MESSAGE_UP === 'true', - CONTACTS: process.env?.CLEAN_STORE_CONTACTS === 'true', - CHATS: process.env?.CLEAN_STORE_CHATS === 'true', - }, - DATABASE: { - CONNECTION: { - 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: { - INSTANCE: process.env?.DATABASE_SAVE_DATA_INSTANCE === 'true', - NEW_MESSAGE: process.env?.DATABASE_SAVE_DATA_NEW_MESSAGE === 'true', - MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true', - CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true', - CHATS: process.env?.DATABASE_SAVE_DATA_CHATS === 'true', - }, - }, - REDIS: { - ENABLED: process.env?.REDIS_ENABLED === 'true', - 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 || '', - }, - SQS: { - ENABLED: process.env?.SQS_ENABLED === 'true', - ACCESS_KEY_ID: process.env.SQS_ACCESS_KEY_ID || '', - SECRET_ACCESS_KEY: process.env.SQS_SECRET_ACCESS_KEY || '', - ACCOUNT_ID: process.env.SQS_ACCOUNT_ID || '', - REGION: process.env.SQS_REGION || '', - }, - WEBSOCKET: { - ENABLED: process.env?.WEBSOCKET_ENABLED === 'true', - }, - LOG: { - 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: 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 || '', - ENABLED: process.env?.WEBHOOK_GLOBAL_ENABLED === 'true', - WEBHOOK_BY_EVENTS: process.env?.WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS === 'true', - }, - EVENTS: { - APPLICATION_STARTUP: process.env?.WEBHOOK_EVENTS_APPLICATION_STARTUP === 'true', - QRCODE_UPDATED: process.env?.WEBHOOK_EVENTS_QRCODE_UPDATED === 'true', - MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true', - MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true', - MESSAGES_UPDATE: process.env?.WEBHOOK_EVENTS_MESSAGES_UPDATE === 'true', - MESSAGES_DELETE: process.env?.WEBHOOK_EVENTS_MESSAGES_DELETE === 'true', - SEND_MESSAGE: process.env?.WEBHOOK_EVENTS_SEND_MESSAGE === 'true', - CONTACTS_SET: process.env?.WEBHOOK_EVENTS_CONTACTS_SET === 'true', - CONTACTS_UPDATE: process.env?.WEBHOOK_EVENTS_CONTACTS_UPDATE === 'true', - CONTACTS_UPSERT: process.env?.WEBHOOK_EVENTS_CONTACTS_UPSERT === 'true', - PRESENCE_UPDATE: process.env?.WEBHOOK_EVENTS_PRESENCE_UPDATE === 'true', - CHATS_SET: process.env?.WEBHOOK_EVENTS_CHATS_SET === 'true', - CHATS_UPDATE: process.env?.WEBHOOK_EVENTS_CHATS_UPDATE === 'true', - CHATS_UPSERT: process.env?.WEBHOOK_EVENTS_CHATS_UPSERT === 'true', - CHATS_DELETE: process.env?.WEBHOOK_EVENTS_CHATS_DELETE === 'true', - CONNECTION_UPDATE: process.env?.WEBHOOK_EVENTS_CONNECTION_UPDATE === 'true', - GROUPS_UPSERT: process.env?.WEBHOOK_EVENTS_GROUPS_UPSERT === 'true', - GROUP_UPDATE: process.env?.WEBHOOK_EVENTS_GROUPS_UPDATE === 'true', - 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', - }, - QRCODE: { - LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30, - COLOR: process.env.QRCODE_COLOR || '#198754', - }, - TYPEBOT: { - API_VERSION: process.env?.TYPEBOT_API_VERSION || 'v1', - }, - AUTHENTICATION: { - TYPE: process.env.AUTHENTICATION_TYPE as 'apikey', - 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 || 'L=0YWt]b2w[WF>#>:&E`', - }, - }, - CHATWOOT: { - USE_REPLY_ID: process.env?.USE_REPLY_ID === 'true', - }, - }; - } -} - -export const configService = new ConfigService(); +import { isBooleanString } from 'class-validator'; +import { readFileSync } from 'fs'; +import { load } from 'js-yaml'; +import { join } from 'path'; + +export type HttpServer = { TYPE: 'http' | 'https'; PORT: number; URL: string }; + +export type HttpMethods = 'POST' | 'GET' | 'PUT' | 'DELETE'; +export type Cors = { + ORIGIN: string[]; + METHODS: HttpMethods[]; + CREDENTIALS: boolean; +}; + +export type LogBaileys = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'; + +export type LogLevel = 'ERROR' | 'WARN' | 'DEBUG' | 'INFO' | 'LOG' | 'VERBOSE' | 'DARK' | 'WEBHOOKS'; + +export type Log = { + LEVEL: LogLevel[]; + COLOR: boolean; + BAILEYS: LogBaileys; +}; + +export type SaveData = { + INSTANCE: boolean; + NEW_MESSAGE: boolean; + MESSAGE_UPDATE: boolean; + CONTACTS: boolean; + CHATS: boolean; +}; + +export type StoreConf = { + MESSAGES: boolean; + MESSAGE_UP: boolean; + CONTACTS: boolean; + CHATS: boolean; +}; + +export type CleanStoreConf = { + CLEANING_INTERVAL: number; + MESSAGES: boolean; + MESSAGE_UP: boolean; + CONTACTS: boolean; + CHATS: boolean; +}; + +export type DBConnection = { + URI: string; + DB_PREFIX_NAME: string; + DB_PREFIX_FINAL_NAME: string; +}; +export type Database = { + CONNECTION: DBConnection; + ENABLED: boolean; + SAVE_DATA: SaveData; +}; + +export type Redis = { + ENABLED: boolean; + URI: string; + PREFIX_KEY: string; +}; + +export type Rabbitmq = { + ENABLED: boolean; + URI: string; +}; + +export type Sqs = { + ENABLED: boolean; + ACCESS_KEY_ID: string; + SECRET_ACCESS_KEY: string; + ACCOUNT_ID: string; + REGION: string; +}; + +export type Openai = { + CHAVE: string; + ENABLED: boolean; + URI: string; +}; + +export type Websocket = { + ENABLED: boolean; +}; + +export type Chatwoot = { + USE_REPLY_ID: boolean; +}; + +export type EventsWebhook = { + APPLICATION_STARTUP: boolean; + QRCODE_UPDATED: boolean; + MESSAGES_SET: boolean; + MESSAGES_UPSERT: boolean; + MESSAGES_UPDATE: boolean; + MESSAGES_DELETE: boolean; + SEND_MESSAGE: boolean; + CONTACTS_SET: boolean; + CONTACTS_UPDATE: boolean; + CONTACTS_UPSERT: boolean; + PRESENCE_UPDATE: boolean; + CHATS_SET: boolean; + CHATS_UPDATE: boolean; + CHATS_DELETE: boolean; + CHATS_UPSERT: boolean; + CONNECTION_UPDATE: boolean; + GROUPS_UPSERT: boolean; + GROUP_UPDATE: boolean; + 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 }; +export type Jwt = { EXPIRIN_IN: number; SECRET: string }; + +export type Auth = { + API_KEY: ApiKey; + EXPOSE_IN_FETCH_INSTANCES: boolean; + JWT: Jwt; + TYPE: 'jwt' | 'apikey'; +}; + +export type DelInstance = number | boolean; + +export type GlobalWebhook = { + URL: string; + ENABLED: boolean; + WEBHOOK_BY_EVENTS: boolean; +}; +export type SslConf = { PRIVKEY: string; FULLCHAIN: string }; +export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook }; +export type ConfigSessionPhone = { CLIENT: string; NAME: string }; +export type QrCode = { LIMIT: number; COLOR: string }; +export type Typebot = { API_VERSION: string }; +export type Production = boolean; + +export interface Env { + SERVER: HttpServer; + CORS: Cors; + SSL_CONF: SslConf; + STORE: StoreConf; + CLEAN_STORE: CleanStoreConf; + DATABASE: Database; + REDIS: Redis; + RABBITMQ: Rabbitmq; + SQS: Sqs; + OPENAI: Openai; + WEBSOCKET: Websocket; + LOG: Log; + DEL_INSTANCE: DelInstance; + WEBHOOK: Webhook; + CONFIG_SESSION_PHONE: ConfigSessionPhone; + QRCODE: QrCode; + TYPEBOT: Typebot; + AUTHENTICATION: Auth; + PRODUCTION?: Production; + CHATWOOT?: Chatwoot; +} + +export type Key = keyof Env; + +export class ConfigService { + constructor() { + this.loadEnv(); + } + + private env: Env; + + public get(key: Key) { + return this.env[key] as T; + } + + private loadEnv() { + this.env = !(process.env?.DOCKER_ENV === 'true') ? this.envYaml() : this.envProcess(); + this.env.PRODUCTION = process.env?.NODE_ENV === 'PROD'; + if (process.env?.DOCKER_ENV === 'true') { + this.env.SERVER.TYPE = 'http'; + this.env.SERVER.PORT = 8080; + } + } + + private envYaml(): Env { + return load(readFileSync(join(process.cwd(), 'src', 'env.yml'), { encoding: 'utf-8' })) as Env; + } + + private envProcess(): Env { + return { + SERVER: { + TYPE: process.env.SERVER_TYPE as 'http' | 'https', + 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[]) || ['POST', 'GET', 'PUT', 'DELETE'], + CREDENTIALS: process.env?.CORS_CREDENTIALS === 'true', + }, + SSL_CONF: { + PRIVKEY: process.env?.SSL_CONF_PRIVKEY || '', + FULLCHAIN: process.env?.SSL_CONF_FULLCHAIN || '', + }, + STORE: { + MESSAGES: process.env?.STORE_MESSAGES === 'true', + MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true', + CONTACTS: process.env?.STORE_CONTACTS === 'true', + CHATS: process.env?.STORE_CHATS === 'true', + }, + CLEAN_STORE: { + CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL) + ? Number.parseInt(process.env.CLEAN_STORE_CLEANING_TERMINAL) + : 7200, + MESSAGES: process.env?.CLEAN_STORE_MESSAGES === 'true', + MESSAGE_UP: process.env?.CLEAN_STORE_MESSAGE_UP === 'true', + CONTACTS: process.env?.CLEAN_STORE_CONTACTS === 'true', + CHATS: process.env?.CLEAN_STORE_CHATS === 'true', + }, + DATABASE: { + CONNECTION: { + URI: process.env.DATABASE_CONNECTION_URI || '', + DB_PREFIX_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_NAME || 'evolution', + DB_PREFIX_FINAL_NAME: process.env.DATABASE_CONNECTION_DB_PREFIX_FINAL_NAME || '-api', + }, + ENABLED: process.env?.DATABASE_ENABLED === 'true', + SAVE_DATA: { + INSTANCE: process.env?.DATABASE_SAVE_DATA_INSTANCE === 'true', + NEW_MESSAGE: process.env?.DATABASE_SAVE_DATA_NEW_MESSAGE === 'true', + MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true', + CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true', + CHATS: process.env?.DATABASE_SAVE_DATA_CHATS === 'true', + }, + }, + REDIS: { + ENABLED: process.env?.REDIS_ENABLED === 'true', + 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 || '', + }, + SQS: { + ENABLED: process.env?.SQS_ENABLED === 'true', + ACCESS_KEY_ID: process.env.SQS_ACCESS_KEY_ID || '', + SECRET_ACCESS_KEY: process.env.SQS_SECRET_ACCESS_KEY || '', + ACCOUNT_ID: process.env.SQS_ACCOUNT_ID || '', + REGION: process.env.SQS_REGION || '', + }, + + OPENAI: { + CHAVE: process.env?.OPENAI_ENABLED || '', + ENABLED: process.env?.OPENAI_ENABLED === 'true', + URI: process.env.OPENAI_URI || '', + }, + WEBSOCKET: { + ENABLED: process.env?.WEBSOCKET_ENABLED === 'true', + }, + LOG: { + 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: 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 || '', + ENABLED: process.env?.WEBHOOK_GLOBAL_ENABLED === 'true', + WEBHOOK_BY_EVENTS: process.env?.WEBHOOK_GLOBAL_WEBHOOK_BY_EVENTS === 'true', + }, + EVENTS: { + APPLICATION_STARTUP: process.env?.WEBHOOK_EVENTS_APPLICATION_STARTUP === 'true', + QRCODE_UPDATED: process.env?.WEBHOOK_EVENTS_QRCODE_UPDATED === 'true', + MESSAGES_SET: process.env?.WEBHOOK_EVENTS_MESSAGES_SET === 'true', + MESSAGES_UPSERT: process.env?.WEBHOOK_EVENTS_MESSAGES_UPSERT === 'true', + MESSAGES_UPDATE: process.env?.WEBHOOK_EVENTS_MESSAGES_UPDATE === 'true', + MESSAGES_DELETE: process.env?.WEBHOOK_EVENTS_MESSAGES_DELETE === 'true', + SEND_MESSAGE: process.env?.WEBHOOK_EVENTS_SEND_MESSAGE === 'true', + CONTACTS_SET: process.env?.WEBHOOK_EVENTS_CONTACTS_SET === 'true', + CONTACTS_UPDATE: process.env?.WEBHOOK_EVENTS_CONTACTS_UPDATE === 'true', + CONTACTS_UPSERT: process.env?.WEBHOOK_EVENTS_CONTACTS_UPSERT === 'true', + PRESENCE_UPDATE: process.env?.WEBHOOK_EVENTS_PRESENCE_UPDATE === 'true', + CHATS_SET: process.env?.WEBHOOK_EVENTS_CHATS_SET === 'true', + CHATS_UPDATE: process.env?.WEBHOOK_EVENTS_CHATS_UPDATE === 'true', + CHATS_UPSERT: process.env?.WEBHOOK_EVENTS_CHATS_UPSERT === 'true', + CHATS_DELETE: process.env?.WEBHOOK_EVENTS_CHATS_DELETE === 'true', + CONNECTION_UPDATE: process.env?.WEBHOOK_EVENTS_CONNECTION_UPDATE === 'true', + GROUPS_UPSERT: process.env?.WEBHOOK_EVENTS_GROUPS_UPSERT === 'true', + GROUP_UPDATE: process.env?.WEBHOOK_EVENTS_GROUPS_UPDATE === 'true', + 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', + }, + QRCODE: { + LIMIT: Number.parseInt(process.env.QRCODE_LIMIT) || 30, + COLOR: process.env.QRCODE_COLOR || '#198754', + }, + TYPEBOT: { + API_VERSION: process.env?.TYPEBOT_API_VERSION || 'v1', + }, + AUTHENTICATION: { + TYPE: process.env.AUTHENTICATION_TYPE as 'apikey', + 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 || 'L=0YWt]b2w[WF>#>:&E`', + }, + }, + CHATWOOT: { + USE_REPLY_ID: process.env?.USE_REPLY_ID === 'true', + }, + }; + } +} + +export const configService = new ConfigService(); diff --git a/src/config/error.config.ts b/src/config/error.config.ts old mode 100644 new mode 100755 index dbba0d6a..a577f337 --- a/src/config/error.config.ts +++ b/src/config/error.config.ts @@ -1,23 +1,23 @@ -import { Logger } from './logger.config'; - -export function onUnexpectedError() { - process.on('uncaughtException', (error, origin) => { - const logger = new Logger('uncaughtException'); - logger.error({ - origin, - stderr: process.stderr.fd, - error, - }); - // process.exit(1); - }); - - process.on('unhandledRejection', (error, origin) => { - const logger = new Logger('unhandledRejection'); - logger.error({ - origin, - stderr: process.stderr.fd, - error, - }); - // process.exit(1); - }); -} +import { Logger } from './logger.config'; + +export function onUnexpectedError() { + process.on('uncaughtException', (error, origin) => { + const logger = new Logger('uncaughtException'); + logger.error({ + origin, + stderr: process.stderr.fd, + error, + }); + process.exit(1); + }); + + process.on('unhandledRejection', (error, origin) => { + const logger = new Logger('unhandledRejection'); + logger.error({ + origin, + stderr: process.stderr.fd, + error, + }); + process.exit(1); + }); +} diff --git a/src/config/event.config.ts b/src/config/event.config.ts old mode 100644 new mode 100755 index 8451ffdf..8728c946 --- a/src/config/event.config.ts +++ b/src/config/event.config.ts @@ -1,7 +1,7 @@ -import EventEmitter2 from 'eventemitter2'; - -export const eventEmitter = new EventEmitter2({ - delimiter: '.', - newListener: false, - ignoreErrors: false, -}); +import EventEmitter2 from 'eventemitter2'; + +export const eventEmitter = new EventEmitter2({ + delimiter: '.', + newListener: false, + ignoreErrors: false, +}); diff --git a/src/config/logger.config.ts b/src/config/logger.config.ts old mode 100644 new mode 100755 index 221ee098..67f70e39 --- a/src/config/logger.config.ts +++ b/src/config/logger.config.ts @@ -1,141 +1,141 @@ -import dayjs from 'dayjs'; -import fs from 'fs'; - -import { configService, Log } from './env.config'; -const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); - -const formatDateLog = (timestamp: number) => - dayjs(timestamp) - .toDate() - .toString() - .replace(/\sGMT.+/, ''); - -enum Color { - LOG = '\x1b[32m', - INFO = '\x1b[34m', - WARN = '\x1b[33m', - ERROR = '\x1b[31m', - DEBUG = '\x1b[36m', - VERBOSE = '\x1b[37m', - DARK = '\x1b[30m', -} - -enum Command { - RESET = '\x1b[0m', - BRIGHT = '\x1b[1m', - UNDERSCORE = '\x1b[4m', -} - -enum Level { - LOG = Color.LOG + '%s' + Command.RESET, - DARK = Color.DARK + '%s' + Command.RESET, - INFO = Color.INFO + '%s' + Command.RESET, - WARN = Color.WARN + '%s' + Command.RESET, - ERROR = Color.ERROR + '%s' + Command.RESET, - DEBUG = Color.DEBUG + '%s' + Command.RESET, - VERBOSE = Color.VERBOSE + '%s' + Command.RESET, -} - -enum Type { - LOG = 'LOG', - WARN = 'WARN', - INFO = 'INFO', - DARK = 'DARK', - ERROR = 'ERROR', - DEBUG = 'DEBUG', - VERBOSE = 'VERBOSE', -} - -enum Background { - LOG = '\x1b[42m', - INFO = '\x1b[44m', - WARN = '\x1b[43m', - DARK = '\x1b[40m', - ERROR = '\x1b[41m', - DEBUG = '\x1b[46m', - VERBOSE = '\x1b[47m', -} - -export class Logger { - private readonly configService = configService; - constructor(private context = 'Logger') {} - - public setContext(value: string) { - this.context = value; - } - - private console(value: any, type: Type) { - const types: Type[] = []; - - this.configService.get('LOG').LEVEL.forEach((level) => types.push(Type[level])); - - const typeValue = typeof value; - if (types.includes(type)) { - if (configService.get('LOG').COLOR) { - console.log( - /*Command.UNDERSCORE +*/ Command.BRIGHT + Level[type], - '[Evolution API]', - Command.BRIGHT + Color[type], - `v${packageJson.version}`, - Command.BRIGHT + Color[type], - process.pid.toString(), - Command.RESET, - Command.BRIGHT + Color[type], - '-', - Command.BRIGHT + Color.VERBOSE, - `${formatDateLog(Date.now())} `, - Command.RESET, - Color[type] + Background[type] + Command.BRIGHT, - `${type} ` + Command.RESET, - Color.WARN + Command.BRIGHT, - `[${this.context}]` + Command.RESET, - Color[type] + Command.BRIGHT, - `[${typeValue}]` + Command.RESET, - Color[type], - typeValue !== 'object' ? value : '', - Command.RESET, - ); - typeValue === 'object' ? console.log(/*Level.DARK,*/ value, '\n') : ''; - } else { - console.log( - '[Evolution API]', - process.pid.toString(), - '-', - `${formatDateLog(Date.now())} `, - `${type} `, - `[${this.context}]`, - `[${typeValue}]`, - value, - ); - } - } - } - - public log(value: any) { - this.console(value, Type.LOG); - } - - public info(value: any) { - this.console(value, Type.INFO); - } - - public warn(value: any) { - this.console(value, Type.WARN); - } - - public error(value: any) { - this.console(value, Type.ERROR); - } - - public verbose(value: any) { - this.console(value, Type.VERBOSE); - } - - public debug(value: any) { - this.console(value, Type.DEBUG); - } - - public dark(value: any) { - this.console(value, Type.DARK); - } -} +import dayjs from 'dayjs'; +import fs from 'fs'; + +import { configService, Log } from './env.config'; +const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + +const formatDateLog = (timestamp: number) => + dayjs(timestamp) + .toDate() + .toString() + .replace(/\sGMT.+/, ''); + +enum Color { + LOG = '\x1b[32m', + INFO = '\x1b[34m', + WARN = '\x1b[33m', + ERROR = '\x1b[31m', + DEBUG = '\x1b[36m', + VERBOSE = '\x1b[37m', + DARK = '\x1b[30m', +} + +enum Command { + RESET = '\x1b[0m', + BRIGHT = '\x1b[1m', + UNDERSCORE = '\x1b[4m', +} + +enum Level { + LOG = Color.LOG + '%s' + Command.RESET, + DARK = Color.DARK + '%s' + Command.RESET, + INFO = Color.INFO + '%s' + Command.RESET, + WARN = Color.WARN + '%s' + Command.RESET, + ERROR = Color.ERROR + '%s' + Command.RESET, + DEBUG = Color.DEBUG + '%s' + Command.RESET, + VERBOSE = Color.VERBOSE + '%s' + Command.RESET, +} + +enum Type { + LOG = 'LOG', + WARN = 'WARN', + INFO = 'INFO', + DARK = 'DARK', + ERROR = 'ERROR', + DEBUG = 'DEBUG', + VERBOSE = 'VERBOSE', +} + +enum Background { + LOG = '\x1b[42m', + INFO = '\x1b[44m', + WARN = '\x1b[43m', + DARK = '\x1b[40m', + ERROR = '\x1b[41m', + DEBUG = '\x1b[46m', + VERBOSE = '\x1b[47m', +} + +export class Logger { + private readonly configService = configService; + constructor(private context = 'Logger') {} + + public setContext(value: string) { + this.context = value; + } + + private console(value: any, type: Type) { + const types: Type[] = []; + + this.configService.get('LOG').LEVEL.forEach((level) => types.push(Type[level])); + + const typeValue = typeof value; + if (types.includes(type)) { + if (configService.get('LOG').COLOR) { + console.log( + /*Command.UNDERSCORE +*/ Command.BRIGHT + Level[type], + '[Evolution API]', + Command.BRIGHT + Color[type], + `v${packageJson.version}`, + Command.BRIGHT + Color[type], + process.pid.toString(), + Command.RESET, + Command.BRIGHT + Color[type], + '-', + Command.BRIGHT + Color.VERBOSE, + `${formatDateLog(Date.now())} `, + Command.RESET, + Color[type] + Background[type] + Command.BRIGHT, + `${type} ` + Command.RESET, + Color.WARN + Command.BRIGHT, + `[${this.context}]` + Command.RESET, + Color[type] + Command.BRIGHT, + `[${typeValue}]` + Command.RESET, + Color[type], + typeValue !== 'object' ? value : '', + Command.RESET, + ); + typeValue === 'object' ? console.log(/*Level.DARK,*/ value, '\n') : ''; + } else { + console.log( + '[Evolution API]', + process.pid.toString(), + '-', + `${formatDateLog(Date.now())} `, + `${type} `, + `[${this.context}]`, + `[${typeValue}]`, + value, + ); + } + } + } + + public log(value: any) { + this.console(value, Type.LOG); + } + + public info(value: any) { + this.console(value, Type.INFO); + } + + public warn(value: any) { + this.console(value, Type.WARN); + } + + public error(value: any) { + this.console(value, Type.ERROR); + } + + public verbose(value: any) { + this.console(value, Type.VERBOSE); + } + + public debug(value: any) { + this.console(value, Type.DEBUG); + } + + public dark(value: any) { + this.console(value, Type.DARK); + } +} diff --git a/src/config/path.config.ts b/src/config/path.config.ts old mode 100644 new mode 100755 index 0d9b6d3c..e9037fcc --- a/src/config/path.config.ts +++ b/src/config/path.config.ts @@ -1,7 +1,7 @@ -import { join } from 'path'; - -export const ROOT_DIR = process.cwd(); -export const INSTANCE_DIR = join(ROOT_DIR, 'instances'); -export const SRC_DIR = join(ROOT_DIR, 'src'); -export const AUTH_DIR = join(ROOT_DIR, 'store', 'auth'); -export const STORE_DIR = join(ROOT_DIR, 'store'); +import { join } from 'path'; + +export const ROOT_DIR = process.cwd(); +export const INSTANCE_DIR = join(ROOT_DIR, 'instances'); +export const SRC_DIR = join(ROOT_DIR, 'src'); +export const AUTH_DIR = join(ROOT_DIR, 'store', 'auth'); +export const STORE_DIR = join(ROOT_DIR, 'store'); diff --git a/src/dev-env.yml b/src/dev-env.yml old mode 100644 new mode 100755 index b2a2e521..c56757c4 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -1,170 +1,179 @@ -# ⚠️ -# ⚠️ ALL SETTINGS DEFINED IN THIS FILE ARE APPLIED TO ALL INSTANCES. -# ⚠️ - -# ⚠️ RENAME THIS FILE TO env.yml - -# Choose the server type for the application -SERVER: - TYPE: http # https - PORT: 8080 # 443 - URL: localhost - -CORS: - ORIGIN: - - "*" - # - yourdomain.com - METHODS: - - POST - - GET - - PUT - - DELETE - CREDENTIALS: true - -# Install ssl certificate and replace string with domain name -# Access: https://certbot.eff.org/instructions?ws=other&os=ubuntufocal -SSL_CONF: - PRIVKEY: /etc/letsencrypt/live//privkey.pem - FULLCHAIN: /etc/letsencrypt/live//fullchain.pem - -# Determine the logs to be displayed -LOG: - LEVEL: - - ERROR - - WARN - - DEBUG - - INFO - - LOG - - VERBOSE - - DARK - - WEBHOOKS - COLOR: true - BAILEYS: error # fatal | error | warn | info | debug | trace - -# 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 # or false - -# Temporary data storage -STORE: - MESSAGES: true - MESSAGE_UP: true - CONTACTS: true - CHATS: true - -CLEAN_STORE: - CLEANING_INTERVAL: 7200 # 7200 seconds === 2h - MESSAGES: true - MESSAGE_UP: true - CONTACTS: true - CHATS: true - -# Permanent data storage -DATABASE: - ENABLED: false - CONNECTION: - 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: - INSTANCE: false - NEW_MESSAGE: false - MESSAGE_UPDATE: false - CONTACTS: false - CHATS: false - -REDIS: - ENABLED: false - URI: "redis://localhost:6379" - PREFIX_KEY: "evolution" - -RABBITMQ: - ENABLED: false - URI: "amqp://guest:guest@localhost:5672" - -SQS: - ENABLED: true - ACCESS_KEY_ID: "" - SECRET_ACCESS_KEY: "" - ACCOUNT_ID: "" - REGION: "us-east-1" - -WEBSOCKET: - ENABLED: false - -# Global Webhook Settings -# Each instance's Webhook URL and events will be requested at the time it is created -WEBHOOK: - # Define a global webhook that will listen for enabled events from all instances - GLOBAL: - URL: - 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_BY_EVENTS: false - # Automatically maps webhook paths - # Set the events you want to hear - EVENTS: - APPLICATION_STARTUP: false - QRCODE_UPDATED: true - MESSAGES_SET: true - MESSAGES_UPSERT: true - MESSAGES_UPDATE: true - MESSAGES_DELETE: true - SEND_MESSAGE: true - CONTACTS_SET: true - CONTACTS_UPSERT: true - CONTACTS_UPDATE: true - PRESENCE_UPDATE: true - CHATS_SET: true - CHATS_UPSERT: true - CHATS_UPDATE: true - CHATS_DELETE: true - GROUPS_UPSERT: true - GROUP_UPDATE: true - GROUP_PARTICIPANTS_UPDATE: true - CONNECTION_UPDATE: true - 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: - -CONFIG_SESSION_PHONE: - # Name that will be displayed on smartphone connection - CLIENT: "Evolution API" - NAME: Chrome # Chrome | Firefox | Edge | Opera | Safari - -# Set qrcode display limit -QRCODE: - LIMIT: 30 - COLOR: "#198754" - -TYPEBOT: - API_VERSION: 'v1' # v1 | v2 - -# 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 -AUTHENTICATION: - TYPE: apikey # jwt or apikey - # Define a global apikey to access all instances - API_KEY: - # OBS: This key must be inserted in the request header to create an instance. - KEY: B6D711FCDE4D4FD5936544120E713976 - # Expose the api key on return from fetch instances - EXPOSE_IN_FETCH_INSTANCES: true - # Set the secret key to encrypt and decrypt your token and its expiration time. - JWT: - EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires - SECRET: L=0YWt]b2w[WF>#>:&E` - -# Configure to chatwoot -CHATWOOT: - USE_REPLY_ID: false +# ⚠️ +# ⚠️ ALL SETTINGS DEFINED IN THIS FILE ARE APPLIED TO ALL INSTANCES. +# ⚠️ + +# ⚠️ RENAME THIS FILE TO env.yml + +# Choose the server type for the application +SERVER: + TYPE: http # https + PORT: 3333 # 443 + URL: 127.0.0.1 + +CORS: + ORIGIN: + - "*" + # - yourdomain.com + METHODS: + - POST + - GET + - PUT + - DELETE + CREDENTIALS: true + +# Install ssl certificate and replace string with domain name +# Access: https://certbot.eff.org/instructions?ws=other&os=ubuntufocal +SSL_CONF: + PRIVKEY: /etc/letsencrypt/live//privkey.pem + FULLCHAIN: /etc/letsencrypt/live//fullchain.pem + +# Determine the logs to be displayed +LOG: + LEVEL: + - ERROR + - WARN + - DEBUG + - INFO + - LOG + - VERBOSE + - DARK + - WEBHOOKS + COLOR: true + BAILEYS: error # fatal | error | warn | info | debug | trace + +# 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 # or false + +# Temporary data storage +STORE: + MESSAGES: false + MESSAGE_UP: false + CONTACTS: true + CHATS: true + +CLEAN_STORE: + CLEANING_INTERVAL: 7200 # 7200 seconds === 2h + MESSAGES: true + MESSAGE_UP: true + CONTACTS: true + CHATS: true + +# Permanent data storage +DATABASE: + ENABLED: true + CONNECTION: + URI: "mongodb://root:root@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true" + + DB_PREFIX_NAME: whatsapp + DB_PREFIX_FINAL_NAME: "-api" + DB_PREFIX_FINAL_NAME_VENOM: "-venom" + # Choose the data you want to save in the application's database or store + SAVE_DATA: + INSTANCE: true + NEW_MESSAGE: false + MESSAGE_UPDATE: false + CONTACTS: true + CHATS: true + +REDIS: + ENABLED: false + URI: "redis://localhost:6379" + PREFIX_KEY: "evolution" + +RABBITMQ: + ENABLED: false + URI: "amqp://guest:guest@localhost:5672" + +OPENAI: + CHAVE: "" + ENABLED: false + PROMPTS: false + URI: "" + +SQS: + ENABLED: false + ACCESS_KEY_ID: "" + SECRET_ACCESS_KEY: "" + ACCOUNT_ID: "" + REGION: "us-east-1" + +WEBSOCKET: + ENABLED: false + +TYPEBOT: + API_VERSION: 'v1' # v1 | v2 + +# Global Webhook Settings +# Each instance's Webhook URL and events will be requested at the time it is created +WEBHOOK: + # Define a global webhook that will listen for enabled events from all instances + GLOBAL: + URL: "" + 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_BY_EVENTS: false + # Automatically maps webhook paths + # Set the events you want to hear + EVENTS: + APPLICATION_STARTUP: false + QRCODE_UPDATED: true + MESSAGES_SET: true + MESSAGES_UPSERT: true + MESSAGES_UPDATE: true + MESSAGES_DELETE: true + SEND_MESSAGE: true + CONTACTS_SET: true + CONTACTS_UPSERT: true + CONTACTS_UPDATE: true + PRESENCE_UPDATE: true + CHATS_SET: true + CHATS_UPSERT: true + CHATS_UPDATE: true + CHATS_DELETE: true + GROUPS_UPSERT: true + GROUP_UPDATE: true + GROUP_PARTICIPANTS_UPDATE: true + CONNECTION_UPDATE: true + 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: "" + +CONFIG_SESSION_PHONE: + # Name that will be displayed on smartphone connection + CLIENT: "Evolution API" + NAME: Chrome # Chrome | Firefox | Edge | Opera | Safari + +# Set qrcode display limit +QRCODE: + LIMIT: 30 + COLOR: "#198754" + +# 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 +AUTHENTICATION: + TYPE: apikey # jwt or apikey + # Define a global apikey to access all instances + API_KEY: + # OBS: This key must be inserted in the request header to create an instance. + KEY: B6D711FCDE4D4FD5936544120E713976 + # Expose the api key on return from fetch instances + EXPOSE_IN_FETCH_INSTANCES: true + # Set the secret key to encrypt and decrypt your token and its expiration time. + JWT: + EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires + SECRET: L=0YWt]b2w[WF>#>:&E` + +# Configure to chatwoot +CHATWOOT: + USE_REPLY_ID: false diff --git a/src/docs/swagger.conf.ts b/src/docs/swagger.conf.ts old mode 100644 new mode 100755 index 7ce42bae..bc0ebda1 --- a/src/docs/swagger.conf.ts +++ b/src/docs/swagger.conf.ts @@ -1,17 +1,17 @@ -import { Router } from 'express'; -import { join } from 'path'; -import swaggerUi from 'swagger-ui-express'; -import YAML from 'yamljs'; - -const document = YAML.load(join(process.cwd(), 'src', 'docs', 'swagger.yaml')); - -const router = Router(); - -export const swaggerRouter = router.use('/docs', swaggerUi.serve).get( - '/docs', - swaggerUi.setup(document, { - customCssUrl: '/css/dark-theme-swagger.css', - customSiteTitle: 'Evolution API', - customfavIcon: '/images/logo.svg', - }), -); +import { Router } from 'express'; +import { join } from 'path'; +import swaggerUi from 'swagger-ui-express'; +import YAML from 'yamljs'; + +const document = YAML.load(join(process.cwd(), 'src', 'docs', 'swagger.yaml')); + +const router = Router(); + +export const swaggerRouter = router.use('/docs', swaggerUi.serve).get( + '/docs', + swaggerUi.setup(document, { + customCssUrl: '/css/dark-theme-swagger.css', + customSiteTitle: 'Evolution API', + customfavIcon: '/images/logo.svg', + }), +); diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml old mode 100644 new mode 100755 index 589ba8dc..52f0c72e --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -1,2597 +1,2597 @@ -openapi: 3.0.0 -info: - title: Evolution API - description: | -
-
- -
- - [![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://evolution-api.com/whatsapp) - [![Discord Community](https://img.shields.io/badge/Discord-Community-blue)](https://evolution-api.com/discord) - [![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman) - [![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://doc.evolution-api.com) - [![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE) - [![Support](https://img.shields.io/badge/Donation-picpay-green)](https://app.picpay.com/user/davidsongomes1998) - [![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://bmc.link/evolutionapi) - -
- -
- - - This project is based on the [evolution](https://github.com/code-chat-br/whatsapp-api). The original project is an implementation of [Baileys](https://github.com/WhiskeySockets/Baileys), serving as a Restful API service that controls WhatsApp functions.
- The code allows the creation of multiservice chats, service bots, or any other system that utilizes WhatsApp. The documentation provides instructions on how to set up and use the project, as well as additional information about its features and configuration options. -
- - [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442) - version: 1.5.5 - contact: - name: DavidsonGomes - email: contato@agenciadgcode.com - url: https://img.shields.io/badge/license-GPL--3.0-orange - license: - name: GNU General Public License v3.0 - url: https://github.com/EvolutionAPI/evolution-api/blob/main/LICENSE -servers: [] -components: - securitySchemes: - apikeyAuth: - type: apiKey - in: header - name: apikey - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT -security: - - bearerAuth: [] -tags: - - name: Instance Controller - - name: Send Message Controller - - name: Chat Controller - - name: Group Controller - - name: Profile Settings - - name: JWT - - name: Settings - - name: Webhook - - name: Websocket - - name: RabbitMQ - - name: Chatwoot - - name: Typebot - - name: Proxy - - name: Chama AI -paths: - /instance/create: - post: - tags: - - Instance Controller - summary: Create Instance - requestBody: - content: - application/json: - schema: - type: object - properties: - instanceName: - type: string - description: Name of the instance (optional). - token: - type: string - description: Token of the instance (optional). - qrcode: - type: boolean - description: QR Code of the instance (optional). - example: - instanceName: "exampleInstance" - token: "87F3F7D0-4B8A-45D0-8618-7399E4AD6469" - qrcode: true - security: - - apikeyAuth: [] - responses: - "200": - description: Successful response - content: - application/json: {} - /instance/fetchInstances: - get: - tags: - - Instance Controller - summary: Fetch Instances - security: - - apikeyAuth: [] - parameters: - - name: instanceName - in: query - schema: - type: string - description: Retrieve one or all instances (optional). - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: - schema: - type: array - items: - type: object - properties: - instance: - type: object - properties: - instanceName: - type: string - owner: - type: string - profileName: - type: string - profilePictureUrl: - type: string - profileStatus: - type: string - status: - type: string - serverUrl: - type: string - apikey: - type: string - /instance/connect/{instanceName}: - get: - tags: - - Instance Controller - summary: Instance Connect - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: Connect to your instance. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: - schema: - type: object - properties: - code: - type: string - base64: - type: string - description: The QR Code as a string. - /instance/restart/{instanceName}: - put: - tags: - - Instance Controller - summary: Instance Restart - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: Connect to your instance. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /instance/connectionState/{instanceName}: - get: - tags: - - Instance Controller - summary: Connection Status - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: Check the connection state of your instance. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /instance/logout/{instanceName}: - delete: - tags: - - Instance Controller - summary: Logout Instance - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: Logout from your instance. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /instance/delete/{instanceName}: - delete: - tags: - - Instance Controller - summary: Delete Instance - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: Delete your instance. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /message/sendText/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send a text message to a specified instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: The recipient's phone number. - example: "1234567890" - textMessage: - type: object - properties: - text: - type: string - description: The content of the text message. - example: "Hello, World!" - options: - type: object - properties: - delay: - type: integer - description: Delay time before sending the message. - presence: - type: string - enum: ["composing", "recording", "paused"] - description: Indicates the sender's action/status. - linkPreview: - type: boolean - description: Indicates whether to enable link preview. - quoted: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: The ID of the recipient of the original message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: The ID of the original message. - message: - type: object - properties: - conversation: - type: string - description: The content of the quoted message. - mentions: - type: object - properties: - everyone: - type: boolean - description: Indicates whether to mention everyone. - mentioned: - type: array - items: - type: string - description: The phone numbers of the users to be mentioned. - required: - - number - - textMessage - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the message should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendStatus/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send a status message. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - statusMessage: - type: object - properties: - type: - type: string - enum: ["text", "image", "video", "audio"] - description: Type of the status message. - content: - type: string - description: The content of the status message. - backgroundColor: - type: string - description: The background color of the status message. - font: - type: integer - enum: [1, 2, 3, 4, 5] - description: The font of the status message. - allContacts: - type: boolean - description: Indicates whether to send the status message to all contacts. - statusJidList: - type: array - items: - type: string - description: The phone numbers of the users to whom the status message should be sent. - required: - - type - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the status message should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendMedia/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send a media message (image, video, document, audio) to a specified instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: The recipient's phone number. - example: "1234567890" - mediaMessage: - type: object - properties: - mediatype: - type: string - enum: ["image", "document", "video", "audio"] - description: Type of the media content. - fileName: - type: string - description: Name of the media file (optional). - caption: - type: string - description: Caption to accompany the media. - media: - type: string - description: URL of the media content. - required: - - mediatype - - media - options: - type: object - properties: - delay: - type: integer - description: Delay time before sending the message. - presence: - type: string - enum: ["composing", "recording", "paused"] - description: Indicates the sender's action/status. - linkPreview: - type: boolean - description: Indicates whether to enable link preview. - quoted: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: The ID of the recipient of the original message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: The ID of the original message. - message: - type: object - properties: - conversation: - type: string - description: The content of the quoted message. - mentions: - type: object - properties: - everyone: - type: boolean - description: Indicates whether to mention everyone. - mentioned: - type: array - items: - type: string - description: The phone numbers of the users to be mentioned. - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the media message should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendWhatsAppAudio/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send an audio message via WhatsApp to a specified instance. - description: This endpoint allows users to share an audio message. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: The recipient's phone number. - example: "1234567890" - audioMessage: - type: object - properties: - audio: - type: string - description: URL of the audio file to be sent. - required: - - audio - options: - type: object - properties: - delay: - type: integer - description: Delay time before sending the message. - presence: - type: string - enum: ["composing", "recording", "paused"] - description: Indicates the sender's action/status. - linkPreview: - type: boolean - description: Indicates whether to enable link preview. - quoted: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: The ID of the recipient of the original message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: The ID of the original message. - message: - type: object - properties: - conversation: - type: string - description: The content of the quoted message. - mentions: - type: object - properties: - everyone: - type: boolean - description: Indicates whether to mention everyone. - mentioned: - type: array - items: - type: string - description: The phone numbers of the users to be mentioned. - required: - - number - - audioMessage.audio - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the audio should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendSticker/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send an sticker to a specified instance. - description: This endpoint allows users to share an sticker message. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: The recipient's phone number. - example: "1234567890" - stickerMessage: - type: object - properties: - image: - type: string - description: URL of the audio file to be sent. - required: - - image - options: - type: object - properties: - delay: - type: integer - description: Delay time before sending the message. - presence: - type: string - enum: ["composing", "recording", "paused"] - description: Indicates the sender's action/status. - required: - - number - - audioMessage.audio - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the audio should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendLocation/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send a location to a specified instance. - description: This endpoint allows users to share a location message. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: The recipient's phone number. - example: "1234567890" - locationMessage: - type: object - properties: - name: - type: string - description: Name or title of the location. - address: - type: string - description: Detailed address of the location. - latitude: - type: number - description: Latitude of the location. - format: float - longitude: - type: number - description: Longitude of the location. - format: float - options: - type: object - properties: - delay: - type: integer - description: Delay time before sending the message. - presence: - type: string - enum: ["composing", "recording", "paused"] - description: Indicates the sender's action/status. - linkPreview: - type: boolean - description: Indicates whether to enable link preview. - quoted: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: The ID of the recipient of the original message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: The ID of the original message. - message: - type: object - properties: - conversation: - type: string - description: The content of the quoted message. - mentions: - type: object - properties: - everyone: - type: boolean - description: Indicates whether to mention everyone. - mentioned: - type: array - items: - type: string - description: The phone numbers of the users to be mentioned. - required: - - number - - locationMessage.latitude - - locationMessage.longitude - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the location should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendContact/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send contact details to a specified instance. - description: This endpoint allows users to share one or multiple contact details. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: The recipient's phone number. - example: "1234567890" - contactMessage: - type: array - items: - type: object - properties: - fullName: - type: string - description: Full name of the contact. - wuid: - type: string - description: Unique identifier for the contact. - phoneNumber: - type: string - description: Phone number of the contact. - organization: - type: string - description: Organization of the contact. - email: - type: string - description: Email address of the contact. - url: - type: string - description: Url of the contact. - required: - - fullName - - wuid - - phoneNumber - options: - type: object - properties: - delay: - type: integer - description: Delay time before sending the message. - presence: - type: string - enum: ["composing", "recording", "paused"] - description: Indicates the sender's action/status. - linkPreview: - type: boolean - description: Indicates whether to enable link preview. - quoted: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: The ID of the recipient of the original message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: The ID of the original message. - message: - type: object - properties: - conversation: - type: string - description: The content of the quoted message. - mentions: - type: object - properties: - everyone: - type: boolean - description: Indicates whether to mention everyone. - mentioned: - type: array - items: - type: string - description: The phone numbers of the users to be mentioned. - required: - - number - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the contacts should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendReaction/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send a reaction to a specified instance. - description: This endpoint allows users to send a reaction to a message. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - reactionMessage: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: The ID of the recipient of the original message. - fromMe: - type: boolean - description: Indicates if the reaction was sent from the user. - id: - type: string - description: The ID of the original message. - reaction: - type: string - maxLength: 1 - description: Reaction character (e.g., emoji). - required: - - key.remoteJid - - key.fromMe - - key.id - - reaction - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /message/sendPoll/{instanceName}: - post: - tags: - - Send Message Controller - summary: Send a poll to a specified instance. - description: This endpoint allows users to send a poll to a chat. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: The recipient's phone number. - example: "1234567890" - pollMessage: - type: object - properties: - name: - type: string - description: Name or title of the poll. - selectableCount: - type: integer - description: Number of selectable options. - values: - type: array - items: - type: string - description: The options of the poll. - options: - type: object - properties: - delay: - type: integer - description: Delay time before sending the message. - presence: - type: string - enum: ["composing", "recording", "paused"] - description: Indicates the sender's action/status. - linkPreview: - type: boolean - description: Indicates whether to enable link preview. - quoted: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: The ID of the recipient of the original message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: The ID of the original message. - message: - type: object - properties: - conversation: - type: string - description: The content of the quoted message. - mentions: - type: object - properties: - everyone: - type: boolean - description: Indicates whether to mention everyone. - mentioned: - type: array - items: - type: string - description: The phone numbers of the users to be mentioned. - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the poll should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /chat/whatsappNumbers/{instanceName}: - post: - tags: - - Chat Controller - summary: Provide a list of WhatsApp numbers associated with a given instance. - description: This endpoint returns information on the WhatsApp numbers associated with the specified instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - numbers: - type: array - items: - type: string - description: WhatsApp phone number. - example: - - "1234567890" - required: - - numbers - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/markMessageAsRead/{instanceName}: - put: - tags: - - Chat Controller - summary: Mark specific messages as read for a given instance. - description: This endpoint allows users to mark messages as read for a particular instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - read_messages: - type: array - items: - type: object - properties: - remoteJid: - type: string - description: ID of the recipient of the message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: Unique ID of the message. - required: - - remoteJid - - fromMe - - id - required: - - read_messages - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/archiveChat/{instanceName}: - put: - tags: - - Chat Controller - summary: Archive specific chats for a given instance. - description: This endpoint allows users to archive specific chats based on the last message. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - lastMessage: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: ID of the recipient of the last message. - fromMe: - type: boolean - description: Indicates if the last message was sent from the user. - id: - type: string - description: Unique ID of the last message. - required: - - remoteJid - - fromMe - - id - archive: - type: boolean - description: Indicates whether to archive the chat. - example: true - required: - - lastMessage - - archive - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/deleteMessageForEveryone/{instanceName}: - delete: - tags: - - Chat Controller - summary: Delete a message for everyone in a given instance. - description: This endpoint allows users to delete a message for everyone in the chat. - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/fetchProfilePictureUrl/{instanceName}: - post: - tags: - - Chat Controller - summary: Retrieve the profile picture URL of a specific number. - description: This endpoint fetches the profile picture URL associated with the given phone number for the specified instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: WhatsApp phone number whose profile picture URL needs to be fetched. - required: - - number - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/findContacts/{instanceName}: - post: - tags: - - Chat Controller - summary: Retrieve contact details using an ID. - description: This endpoint retrieves contact details associated with the given ID for the specified instance. - requestBody: - content: - application/json: - schema: - type: object - properties: - where: - type: object - properties: - id: - type: string - description: Unique ID of the contact to be fetched. - required: - - id - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/getBase64FromMediaMessage/{instanceName}: - post: - tags: - - Chat Controller - summary: Convert media message content to Base64. - description: This endpoint retrieves the Base64 representation of the content of a media message for the specified instance. - requestBody: - content: - application/json: - schema: - type: object - properties: - message: - type: object - properties: - key: - type: object - properties: - id: string - description: Unique ID of the message. - convertToMp4: - type: boolean - description: Indicates whether to convert the media to MP4 format. - example: true - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/findMessages/{instanceName}: - post: - tags: - - Chat Controller - summary: Search for messages based on specific criteria. - description: This endpoint retrieves messages that match the provided criteria for the specified instance. - requestBody: - content: - application/json: - schema: - type: object - properties: - where: - type: object - properties: - key: - type: object - properties: - remoteJid: - type: string - description: ID of the recipient of the message. - fromMe: - type: boolean - description: Indicates if the message was sent from the user. - id: - type: string - description: Unique ID of the message. - required: - - remoteJid - - fromMe - - id - message: - type: object - required: - - key - limit: - type: integer - description: Maximum number of messages to retrieve. - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/findStatusMessage/{instanceName}: - post: - tags: - - Chat Controller - summary: Search for status messages using an ID. - description: This endpoint retrieves status messages associated with the given ID for the specified instance. - requestBody: - content: - application/json: - schema: - type: object - properties: - where: - type: object - properties: - id: - type: string - description: Unique ID of the status message to be fetched. - required: - - id - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/findChats/{instanceName}: - get: - tags: - - Chat Controller - summary: List all chats associated with a specific instance. - description: This endpoint retrieves a list of all chats associated with the specified instance. - parameters: - - name: instanceName - in: path - required: true - schema: - type: string - description: The name of the instance to which the reaction should be sent. - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /group/create/{instanceName}: - post: - tags: - - Group Controller - summary: Create a new WhatsApp group. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - subject: - type: string - description: "- required - The name of the group." - description: - type: string - description: "- optional - A brief description or summary of the group." - participants: - type: array - items: - type: string - description: "- required - List of participant phone numbers." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/updateGroupPicture/{instanceName}: - put: - tags: - - Group Controller - summary: Update the group's display picture. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - image: - type: string - description: "- required - URL of the new group picture." - parameters: - - name: groupJid - in: query - schema: - type: string - description: "- required - The unique identifier of the group." - example: "120363046555718472@g.us" - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/updateGroupSubject/{instanceName}: - put: - tags: - - Group Controller - summary: Update the group's display picture. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - subject: - type: string - description: "- required - The new name of the group." - parameters: - - name: groupJid - in: query - schema: - type: string - description: "- required - The unique identifier of the group." - example: "120363046555718472@g.us" - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/updateGroupDescription/{instanceName}: - put: - tags: - - Group Controller - summary: Update the group's display picture. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - description: - type: string - description: "- required - The new description of the group." - parameters: - - name: groupJid - in: query - schema: - type: string - description: "- required - The unique identifier of the group." - example: "120363046555718472@g.us" - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/inviteCode/{instanceName}: - get: - tags: - - Group Controller - summary: Update the group's display picture. - parameters: - - name: groupJid - in: query - schema: - type: string - description: "- required - The unique identifier of the group." - example: "120363046555718472@g.us" - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/revokeInviteCode/{instanceName}: - put: - tags: - - Group Controller - summary: Update the group's display picture. - parameters: - - name: groupJid - in: query - schema: - type: string - description: "- required - The unique identifier of the group." - example: "120363046555718472@g.us" - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/sendInvite/{instanceName}: - post: - tags: - - Group Controller - summary: Update the group's display picture. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - groupJid: - type: string - description: "The unique identifier of the group." - description: - type: string - description: "The new description of the group." - numbers: - type: array - description: "List of participant phone numbers to be invited." - items: - type: string - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/inviteInfo/{instanceName}: - get: - tags: - - Group Controller - summary: Retrieve details about a specific group. - parameters: - - name: inviteCode - in: query - schema: - type: string - description: "- required - The invite code of the group." - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/findGroupInfos/{instanceName}: - get: - tags: - - Group Controller - summary: Retrieve details about a specific group. - parameters: - - name: groupJid - in: query - schema: - type: string - description: "- required - The unique identifier of the group." - example: "120363046555718472@g.us" - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/fetchAllGroups/{instanceName}: - get: - tags: - - Group Controller - summary: Retrieve details about a specific group. - parameters: - - name: getParticipants - in: query - schema: - type: boolean - description: "- required - Indicates whether to retrieve the participants of the group." - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/participants/{instanceName}: - get: - tags: - - Group Controller - summary: Retrieve a list of participants in a specific group. - parameters: - - name: groupJid - in: query - schema: - type: string - description: "- required - The unique identifier of the group." - example: "120363046555718472@g.us" - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/updateParticipant/{instanceName}: - put: - tags: - - Group Controller - summary: Update the status or role of a participant in the group. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - action: - type: string - enum: ["add", "remove", "promote", "demote"] - description: "- required - The action to be taken on the participant." - participants: - type: array - items: - type: string - description: "- required - List of participant phone numbers to be updated." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/updateSetting/{instanceName}: - put: - tags: - - Group Controller - summary: Update the status or role of a participant in the group. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - action: - type: string - enum: - ["announcement", "not_announcement", "locked", "unlocked"] - description: "- required - The action to be taken on the participant." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/toggleEphemeral/{instanceName}: - put: - tags: - - Group Controller - summary: Update the status or role of a participant in the group. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - expiration: - type: number - description: "- required - The action to be taken on the participant." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /group/leaveGroup/{instanceName}: - delete: - tags: - - Group Controller - summary: Exit from the specified WhatsApp group. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /instance/refreshToken/: - put: - tags: - - JWT - summary: Refresh an expired JWT token. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - oldToken: - type: string - description: "- required - The expired JWT token." - responses: - "200": - description: Successful response - content: - application/json: {} - - /webhook/set/{instanceName}: - post: - tags: - - Webhook - summary: Set up or modify the webhook for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - url: - type: string - format: uri - description: "The endpoint URL where the webhook data will be sent." - webhook_by_events: - type: boolean - description: "Indicates whether to send the webhook data by events." - webhook_base64: - type: boolean - description: "Indicates whether to send the webhook data in Base64 format." - events: - type: array - enum: - [ - "APPLICATION_STARTUP", - "QRCODE_UPDATED", - "MESSAGES_SET", - "MESSAGES_UPSERT", - "MESSAGES_UPDATE", - "MESSAGES_DELETE", - "SEND_MESSAGE", - "CONTACTS_SET", - "CONTACTS_UPSERT", - "CONTACTS_UPDATE", - "PRESENCE_UPDATE", - "CHATS_SET", - "CHATS_UPSERT", - "CHATS_UPDATE", - "CHATS_DELETE", - "GROUPS_UPSERT", - "GROUP_UPDATE", - "GROUP_PARTICIPANTS_UPDATE", - "CONNECTION_UPDATE", - "CALL", - "NEW_JWT_TOKEN", - ] - items: - type: string - description: "List of events to be sent to the webhook." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /webhook/find/{instanceName}: - get: - tags: - - Webhook - summary: Retrieve the webhook settings for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /websocket/set/{instanceName}: - post: - tags: - - Websocket - summary: Set up or modify the Websocket for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - enabled: - type: boolean - description: "Indicates whether to enable the websocket." - events: - type: array - enum: - [ - "APPLICATION_STARTUP", - "QRCODE_UPDATED", - "MESSAGES_SET", - "MESSAGES_UPSERT", - "MESSAGES_UPDATE", - "MESSAGES_DELETE", - "SEND_MESSAGE", - "CONTACTS_SET", - "CONTACTS_UPSERT", - "CONTACTS_UPDATE", - "PRESENCE_UPDATE", - "CHATS_SET", - "CHATS_UPSERT", - "CHATS_UPDATE", - "CHATS_DELETE", - "GROUPS_UPSERT", - "GROUP_UPDATE", - "GROUP_PARTICIPANTS_UPDATE", - "CONNECTION_UPDATE", - "CALL", - "NEW_JWT_TOKEN", - ] - items: - type: string - description: "List of events to be sent to the websocket." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /websocket/find/{instanceName}: - get: - tags: - - Websocket - summary: Retrieve the websocket settings for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /rabbitmq/set/{instanceName}: - post: - tags: - - RabbitMQ - summary: Set up or modify the RabbitMQ for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - enabled: - type: boolean - description: "Indicates whether to enable the RabbitMQ." - events: - type: array - enum: - [ - "APPLICATION_STARTUP", - "QRCODE_UPDATED", - "MESSAGES_SET", - "MESSAGES_UPSERT", - "MESSAGES_UPDATE", - "MESSAGES_DELETE", - "SEND_MESSAGE", - "CONTACTS_SET", - "CONTACTS_UPSERT", - "CONTACTS_UPDATE", - "PRESENCE_UPDATE", - "CHATS_SET", - "CHATS_UPSERT", - "CHATS_UPDATE", - "CHATS_DELETE", - "GROUPS_UPSERT", - "GROUP_UPDATE", - "GROUP_PARTICIPANTS_UPDATE", - "CONNECTION_UPDATE", - "CALL", - "NEW_JWT_TOKEN", - ] - items: - type: string - description: "List of events to be sent to the RabbitMQ." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /rabbitmq/find/{instanceName}: - get: - tags: - - RabbitMQ - summary: Retrieve the RabbitMQ settings for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /settings/set/{instanceName}: - post: - tags: - - Settings - summary: Set up or modify the Settings for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - reject_call: - type: boolean - description: "Indicates whether to reject incoming calls." - msg_call: - type: string - description: "Message to be sent when rejecting a call." - groups_ignore: - type: boolean - description: "Indicates whether to ignore group messages." - always_online: - type: boolean - description: "Indicates whether to keep the instance always online." - read_messages: - type: boolean - description: "Indicates whether to mark messages as read." - read_status: - type: boolean - description: "Indicates whether to mark status messages as read." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /settings/find/{instanceName}: - get: - tags: - - Settings - summary: Retrieve the Settings for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /chatwoot/set/{instanceName}: - post: - tags: - - Chatwoot - summary: Set up or modify the Chatwoot for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - enabled: - type: boolean - description: "Indicates whether to enable the Chatwoot integration." - account_id: - type: string - description: "The Chatwoot account ID." - token: - type: string - description: "The Chatwoot token." - url: - type: string - description: "The Chatwoot URL." - sign_msg: - type: boolean - description: "Indicates whether to sign messages." - reopen_conversation: - type: boolean - description: "Indicates whether to reopen conversations." - conversation_pending: - type: boolean - description: "Indicates whether to mark conversations as pending." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chatwoot/find/{instanceName}: - get: - tags: - - Chatwoot - summary: Retrieve the Chatwoot for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /typebot/set/{instanceName}: - post: - tags: - - Typebot - summary: Set up or modify the Typebot for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - enabled: - type: boolean - description: "Indicates whether to enable the Typebot integration." - url: - type: string - description: "The Chatwoot URL." - typebot: - type: string - description: "The Typebot Name." - expire: - type: number - description: "The Typebot Expire." - keyword_finish: - type: string - description: "The Typebot Keyword Finish." - delay_message: - type: number - description: "The Typebot Delay Message." - unknown_message: - type: string - description: "The Typebot Unknown Message." - listening_from_me: - type: boolean - description: "Indicates whether to listening from me." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /typebot/start/{instanceName}: - post: - tags: - - Typebot - summary: Start the Typebot for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - url: - type: string - description: "The Typebot URL." - typebot: - type: string - description: "The Typebot Name." - remoteJid: - type: string - description: "The Typebot RemoteJid." - startSession: - type: boolean - description: "Indicates whether to start session." - variables: - type: array - description: "List of variables." - items: - type: object - properties: - name: - type: string - description: "The variable name." - value: - type: string - description: "The variable value." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /typebot/find/{instanceName}: - get: - tags: - - Typebot - summary: Retrieve the Typebot for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /typebot/changeStatus/{instanceName}: - post: - tags: - - Typebot - summary: Change the status of the Typebot for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - remoteJid: - type: string - description: "The Typebot RemoteJid." - status: - type: string - description: "The Typebot Status." - enum: ["opened", "paused", "closed"] - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /proxy/set/{instanceName}: - post: - tags: - - Proxy - summary: Set up or modify the Proxy for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - enabled: - type: boolean - description: "Indicates whether to enable the Proxy integration." - proxy: - type: string - description: "The Proxy URI." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /proxy/find/{instanceName}: - get: - tags: - - Proxy - summary: Retrieve the Proxy for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /chamaai/set/{instanceName}: - post: - tags: - - Chama AI - summary: Set up or modify the Chama AI for an instance. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - enabled: - type: boolean - description: "Indicates whether to enable the Chamai AI integration." - url: - type: string - description: "The Chamai AI URL." - token: - type: string - description: "The Chamai AI Token." - waNumber: - type: string - description: "The Chamai AI WhatsApp Number." - answerByAudio: - type: boolean - description: "Indicates whether to answer by audio." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chamaai/find/{instanceName}: - get: - tags: - - Chama AI - summary: Retrieve the Chama AI for a specific instance. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - - /chat/fetchBusinessProfile/{instanceName}: - post: - tags: - - Profile Settings - summary: Fetch the business profile of a specific contact. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: "- required - The phone number of the contact." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/fetchProfile/{instanceName}: - post: - tags: - - Profile Settings - summary: Fetch the profile of a specific contact. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - number: - type: string - description: "- required - The phone number of the contact." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/updateProfileName/{instanceName}: - post: - tags: - - Profile Settings - summary: Update the name of a specific contact. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: "- required - The new name of the contact." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/updateProfileStatus/{instanceName}: - post: - tags: - - Profile Settings - summary: Update the status of a specific contact. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - status: - type: string - description: "- required - The new status of the contact." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/updateProfilePicture/{instanceName}: - put: - tags: - - Profile Settings - summary: Update the profile picture of a specific contact. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - picture: - type: string - description: "- required - The new profile picture of the contact." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/removeProfilePicture/{instanceName}: - delete: - tags: - - Profile Settings - summary: Remove the profile picture of a specific contact. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/fetchPrivacySettings/{instanceName}: - get: - tags: - - Profile Settings - summary: Fetch the privacy settings of a specific contact. - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} - /chat/updatePrivacySettings/{instanceName}: - put: - tags: - - Profile Settings - summary: Update the privacy settings of a specific contact. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - privacySettings: - type: object - description: "- required - The new privacy settings of the contact." - properties: - readreceipts: - type: string - enum: ["all", "none"] - description: "- required - The new read receipts privacy setting of the contact." - profile: - type: string - enum: ["all", "contacts", "contact_blacklist", "none"] - description: "- required - The new profile privacy setting of the contact." - status: - type: string - enum: ["all", "contacts", "contact_blacklist", "none"] - description: "- required - The new status privacy setting of the contact." - online: - type: string - enum: ["all", "match_last_seen"] - description: "- required - The new online privacy setting of the contact." - last: - type: string - enum: ["all", "contacts", "contact_blacklist", "none"] - description: "- required - The new last seen privacy setting of the contact." - groupadd: - type: string - enum: ["all", "contacts", "contact_blacklist", "none"] - description: "- required - The new group add privacy setting of the contact." - parameters: - - name: instanceName - in: path - schema: - type: string - required: true - description: "- required" - example: "evolution" - responses: - "200": - description: Successful response - content: - application/json: {} +openapi: 3.0.0 +info: + title: Evolution API + description: | +
+
+ +
+ + [![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://evolution-api.com/whatsapp) + [![Discord Community](https://img.shields.io/badge/Discord-Community-blue)](https://evolution-api.com/discord) + [![Postman Collection](https://img.shields.io/badge/Postman-Collection-orange)](https://evolution-api.com/postman) + [![Documentation](https://img.shields.io/badge/Documentation-Official-green)](https://doc.evolution-api.com) + [![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE) + [![Support](https://img.shields.io/badge/Donation-picpay-green)](https://app.picpay.com/user/davidsongomes1998) + [![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://bmc.link/evolutionapi) + +
+ +
+ + + This project is based on the [evolution](https://github.com/code-chat-br/whatsapp-api). The original project is an implementation of [Baileys](https://github.com/WhiskeySockets/Baileys), serving as a Restful API service that controls WhatsApp functions.
+ The code allows the creation of multiservice chats, service bots, or any other system that utilizes WhatsApp. The documentation provides instructions on how to set up and use the project, as well as additional information about its features and configuration options. +
+ + [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442) + version: 1.5.4 + contact: + name: DavidsonGomes + email: contato@agenciadgcode.com + url: https://img.shields.io/badge/license-GPL--3.0-orange + license: + name: GNU General Public License v3.0 + url: https://github.com/EvolutionAPI/evolution-api/blob/main/LICENSE +servers: [] +components: + securitySchemes: + apikeyAuth: + type: apiKey + in: header + name: apikey + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT +security: + - bearerAuth: [] +tags: + - name: Instance Controller + - name: Send Message Controller + - name: Chat Controller + - name: Group Controller + - name: Profile Settings + - name: JWT + - name: Settings + - name: Webhook + - name: Websocket + - name: RabbitMQ + - name: Chatwoot + - name: Typebot + - name: Proxy + - name: Chama AI +paths: + /instance/create: + post: + tags: + - Instance Controller + summary: Create Instance + requestBody: + content: + application/json: + schema: + type: object + properties: + instanceName: + type: string + description: Name of the instance (optional). + token: + type: string + description: Token of the instance (optional). + qrcode: + type: boolean + description: QR Code of the instance (optional). + example: + instanceName: "exampleInstance" + token: "87F3F7D0-4B8A-45D0-8618-7399E4AD6469" + qrcode: true + security: + - apikeyAuth: [] + responses: + "200": + description: Successful response + content: + application/json: {} + /instance/fetchInstances: + get: + tags: + - Instance Controller + summary: Fetch Instances + security: + - apikeyAuth: [] + parameters: + - name: instanceName + in: query + schema: + type: string + description: Retrieve one or all instances (optional). + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + instance: + type: object + properties: + instanceName: + type: string + owner: + type: string + profileName: + type: string + profilePictureUrl: + type: string + profileStatus: + type: string + status: + type: string + serverUrl: + type: string + apikey: + type: string + /instance/connect/{instanceName}: + get: + tags: + - Instance Controller + summary: Instance Connect + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: Connect to your instance. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: + schema: + type: object + properties: + code: + type: string + base64: + type: string + description: The QR Code as a string. + /instance/restart/{instanceName}: + put: + tags: + - Instance Controller + summary: Instance Restart + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: Connect to your instance. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /instance/connectionState/{instanceName}: + get: + tags: + - Instance Controller + summary: Connection Status + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: Check the connection state of your instance. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /instance/logout/{instanceName}: + delete: + tags: + - Instance Controller + summary: Logout Instance + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: Logout from your instance. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /instance/delete/{instanceName}: + delete: + tags: + - Instance Controller + summary: Delete Instance + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: Delete your instance. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /message/sendText/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send a text message to a specified instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: The recipient's phone number. + example: "1234567890" + textMessage: + type: object + properties: + text: + type: string + description: The content of the text message. + example: "Hello, World!" + options: + type: object + properties: + delay: + type: integer + description: Delay time before sending the message. + presence: + type: string + enum: ["composing", "recording", "paused"] + description: Indicates the sender's action/status. + linkPreview: + type: boolean + description: Indicates whether to enable link preview. + quoted: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: The ID of the recipient of the original message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: The ID of the original message. + message: + type: object + properties: + conversation: + type: string + description: The content of the quoted message. + mentions: + type: object + properties: + everyone: + type: boolean + description: Indicates whether to mention everyone. + mentioned: + type: array + items: + type: string + description: The phone numbers of the users to be mentioned. + required: + - number + - textMessage + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the message should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendStatus/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send a status message. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + statusMessage: + type: object + properties: + type: + type: string + enum: ["text", "image", "video", "audio"] + description: Type of the status message. + content: + type: string + description: The content of the status message. + backgroundColor: + type: string + description: The background color of the status message. + font: + type: integer + enum: [1, 2, 3, 4, 5] + description: The font of the status message. + allContacts: + type: boolean + description: Indicates whether to send the status message to all contacts. + statusJidList: + type: array + items: + type: string + description: The phone numbers of the users to whom the status message should be sent. + required: + - type + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the status message should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendMedia/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send a media message (image, video, document, audio) to a specified instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: The recipient's phone number. + example: "1234567890" + mediaMessage: + type: object + properties: + mediatype: + type: string + enum: ["image", "document", "video", "audio"] + description: Type of the media content. + fileName: + type: string + description: Name of the media file (optional). + caption: + type: string + description: Caption to accompany the media. + media: + type: string + description: URL of the media content. + required: + - mediatype + - media + options: + type: object + properties: + delay: + type: integer + description: Delay time before sending the message. + presence: + type: string + enum: ["composing", "recording", "paused"] + description: Indicates the sender's action/status. + linkPreview: + type: boolean + description: Indicates whether to enable link preview. + quoted: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: The ID of the recipient of the original message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: The ID of the original message. + message: + type: object + properties: + conversation: + type: string + description: The content of the quoted message. + mentions: + type: object + properties: + everyone: + type: boolean + description: Indicates whether to mention everyone. + mentioned: + type: array + items: + type: string + description: The phone numbers of the users to be mentioned. + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the media message should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendWhatsAppAudio/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send an audio message via WhatsApp to a specified instance. + description: This endpoint allows users to share an audio message. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: The recipient's phone number. + example: "1234567890" + audioMessage: + type: object + properties: + audio: + type: string + description: URL of the audio file to be sent. + required: + - audio + options: + type: object + properties: + delay: + type: integer + description: Delay time before sending the message. + presence: + type: string + enum: ["composing", "recording", "paused"] + description: Indicates the sender's action/status. + linkPreview: + type: boolean + description: Indicates whether to enable link preview. + quoted: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: The ID of the recipient of the original message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: The ID of the original message. + message: + type: object + properties: + conversation: + type: string + description: The content of the quoted message. + mentions: + type: object + properties: + everyone: + type: boolean + description: Indicates whether to mention everyone. + mentioned: + type: array + items: + type: string + description: The phone numbers of the users to be mentioned. + required: + - number + - audioMessage.audio + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the audio should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendSticker/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send an sticker to a specified instance. + description: This endpoint allows users to share an sticker message. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: The recipient's phone number. + example: "1234567890" + stickerMessage: + type: object + properties: + image: + type: string + description: URL of the audio file to be sent. + required: + - image + options: + type: object + properties: + delay: + type: integer + description: Delay time before sending the message. + presence: + type: string + enum: ["composing", "recording", "paused"] + description: Indicates the sender's action/status. + required: + - number + - audioMessage.audio + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the audio should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendLocation/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send a location to a specified instance. + description: This endpoint allows users to share a location message. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: The recipient's phone number. + example: "1234567890" + locationMessage: + type: object + properties: + name: + type: string + description: Name or title of the location. + address: + type: string + description: Detailed address of the location. + latitude: + type: number + description: Latitude of the location. + format: float + longitude: + type: number + description: Longitude of the location. + format: float + options: + type: object + properties: + delay: + type: integer + description: Delay time before sending the message. + presence: + type: string + enum: ["composing", "recording", "paused"] + description: Indicates the sender's action/status. + linkPreview: + type: boolean + description: Indicates whether to enable link preview. + quoted: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: The ID of the recipient of the original message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: The ID of the original message. + message: + type: object + properties: + conversation: + type: string + description: The content of the quoted message. + mentions: + type: object + properties: + everyone: + type: boolean + description: Indicates whether to mention everyone. + mentioned: + type: array + items: + type: string + description: The phone numbers of the users to be mentioned. + required: + - number + - locationMessage.latitude + - locationMessage.longitude + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the location should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendContact/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send contact details to a specified instance. + description: This endpoint allows users to share one or multiple contact details. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: The recipient's phone number. + example: "1234567890" + contactMessage: + type: array + items: + type: object + properties: + fullName: + type: string + description: Full name of the contact. + wuid: + type: string + description: Unique identifier for the contact. + phoneNumber: + type: string + description: Phone number of the contact. + organization: + type: string + description: Organization of the contact. + email: + type: string + description: Email address of the contact. + url: + type: string + description: Url of the contact. + required: + - fullName + - wuid + - phoneNumber + options: + type: object + properties: + delay: + type: integer + description: Delay time before sending the message. + presence: + type: string + enum: ["composing", "recording", "paused"] + description: Indicates the sender's action/status. + linkPreview: + type: boolean + description: Indicates whether to enable link preview. + quoted: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: The ID of the recipient of the original message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: The ID of the original message. + message: + type: object + properties: + conversation: + type: string + description: The content of the quoted message. + mentions: + type: object + properties: + everyone: + type: boolean + description: Indicates whether to mention everyone. + mentioned: + type: array + items: + type: string + description: The phone numbers of the users to be mentioned. + required: + - number + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the contacts should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendReaction/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send a reaction to a specified instance. + description: This endpoint allows users to send a reaction to a message. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + reactionMessage: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: The ID of the recipient of the original message. + fromMe: + type: boolean + description: Indicates if the reaction was sent from the user. + id: + type: string + description: The ID of the original message. + reaction: + type: string + maxLength: 1 + description: Reaction character (e.g., emoji). + required: + - key.remoteJid + - key.fromMe + - key.id + - reaction + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /message/sendPoll/{instanceName}: + post: + tags: + - Send Message Controller + summary: Send a poll to a specified instance. + description: This endpoint allows users to send a poll to a chat. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: The recipient's phone number. + example: "1234567890" + pollMessage: + type: object + properties: + name: + type: string + description: Name or title of the poll. + selectableCount: + type: integer + description: Number of selectable options. + values: + type: array + items: + type: string + description: The options of the poll. + options: + type: object + properties: + delay: + type: integer + description: Delay time before sending the message. + presence: + type: string + enum: ["composing", "recording", "paused"] + description: Indicates the sender's action/status. + linkPreview: + type: boolean + description: Indicates whether to enable link preview. + quoted: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: The ID of the recipient of the original message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: The ID of the original message. + message: + type: object + properties: + conversation: + type: string + description: The content of the quoted message. + mentions: + type: object + properties: + everyone: + type: boolean + description: Indicates whether to mention everyone. + mentioned: + type: array + items: + type: string + description: The phone numbers of the users to be mentioned. + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the poll should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /chat/whatsappNumbers/{instanceName}: + post: + tags: + - Chat Controller + summary: Provide a list of WhatsApp numbers associated with a given instance. + description: This endpoint returns information on the WhatsApp numbers associated with the specified instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + numbers: + type: array + items: + type: string + description: WhatsApp phone number. + example: + - "1234567890" + required: + - numbers + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/markMessageAsRead/{instanceName}: + put: + tags: + - Chat Controller + summary: Mark specific messages as read for a given instance. + description: This endpoint allows users to mark messages as read for a particular instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + read_messages: + type: array + items: + type: object + properties: + remoteJid: + type: string + description: ID of the recipient of the message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: Unique ID of the message. + required: + - remoteJid + - fromMe + - id + required: + - read_messages + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/archiveChat/{instanceName}: + put: + tags: + - Chat Controller + summary: Archive specific chats for a given instance. + description: This endpoint allows users to archive specific chats based on the last message. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + lastMessage: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: ID of the recipient of the last message. + fromMe: + type: boolean + description: Indicates if the last message was sent from the user. + id: + type: string + description: Unique ID of the last message. + required: + - remoteJid + - fromMe + - id + archive: + type: boolean + description: Indicates whether to archive the chat. + example: true + required: + - lastMessage + - archive + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/deleteMessageForEveryone/{instanceName}: + delete: + tags: + - Chat Controller + summary: Delete a message for everyone in a given instance. + description: This endpoint allows users to delete a message for everyone in the chat. + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/fetchProfilePictureUrl/{instanceName}: + post: + tags: + - Chat Controller + summary: Retrieve the profile picture URL of a specific number. + description: This endpoint fetches the profile picture URL associated with the given phone number for the specified instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: WhatsApp phone number whose profile picture URL needs to be fetched. + required: + - number + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/findContacts/{instanceName}: + post: + tags: + - Chat Controller + summary: Retrieve contact details using an ID. + description: This endpoint retrieves contact details associated with the given ID for the specified instance. + requestBody: + content: + application/json: + schema: + type: object + properties: + where: + type: object + properties: + id: + type: string + description: Unique ID of the contact to be fetched. + required: + - id + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/getBase64FromMediaMessage/{instanceName}: + post: + tags: + - Chat Controller + summary: Convert media message content to Base64. + description: This endpoint retrieves the Base64 representation of the content of a media message for the specified instance. + requestBody: + content: + application/json: + schema: + type: object + properties: + message: + type: object + properties: + key: + type: object + properties: + id: string + description: Unique ID of the message. + convertToMp4: + type: boolean + description: Indicates whether to convert the media to MP4 format. + example: true + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/findMessages/{instanceName}: + post: + tags: + - Chat Controller + summary: Search for messages based on specific criteria. + description: This endpoint retrieves messages that match the provided criteria for the specified instance. + requestBody: + content: + application/json: + schema: + type: object + properties: + where: + type: object + properties: + key: + type: object + properties: + remoteJid: + type: string + description: ID of the recipient of the message. + fromMe: + type: boolean + description: Indicates if the message was sent from the user. + id: + type: string + description: Unique ID of the message. + required: + - remoteJid + - fromMe + - id + message: + type: object + required: + - key + limit: + type: integer + description: Maximum number of messages to retrieve. + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/findStatusMessage/{instanceName}: + post: + tags: + - Chat Controller + summary: Search for status messages using an ID. + description: This endpoint retrieves status messages associated with the given ID for the specified instance. + requestBody: + content: + application/json: + schema: + type: object + properties: + where: + type: object + properties: + id: + type: string + description: Unique ID of the status message to be fetched. + required: + - id + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/findChats/{instanceName}: + get: + tags: + - Chat Controller + summary: List all chats associated with a specific instance. + description: This endpoint retrieves a list of all chats associated with the specified instance. + parameters: + - name: instanceName + in: path + required: true + schema: + type: string + description: The name of the instance to which the reaction should be sent. + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /group/create/{instanceName}: + post: + tags: + - Group Controller + summary: Create a new WhatsApp group. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + subject: + type: string + description: "- required - The name of the group." + description: + type: string + description: "- optional - A brief description or summary of the group." + participants: + type: array + items: + type: string + description: "- required - List of participant phone numbers." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/updateGroupPicture/{instanceName}: + put: + tags: + - Group Controller + summary: Update the group's display picture. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + image: + type: string + description: "- required - URL of the new group picture." + parameters: + - name: groupJid + in: query + schema: + type: string + description: "- required - The unique identifier of the group." + example: "120363046555718472@g.us" + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/updateGroupSubject/{instanceName}: + put: + tags: + - Group Controller + summary: Update the group's display picture. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + subject: + type: string + description: "- required - The new name of the group." + parameters: + - name: groupJid + in: query + schema: + type: string + description: "- required - The unique identifier of the group." + example: "120363046555718472@g.us" + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/updateGroupDescription/{instanceName}: + put: + tags: + - Group Controller + summary: Update the group's display picture. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + description: + type: string + description: "- required - The new description of the group." + parameters: + - name: groupJid + in: query + schema: + type: string + description: "- required - The unique identifier of the group." + example: "120363046555718472@g.us" + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/inviteCode/{instanceName}: + get: + tags: + - Group Controller + summary: Update the group's display picture. + parameters: + - name: groupJid + in: query + schema: + type: string + description: "- required - The unique identifier of the group." + example: "120363046555718472@g.us" + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/revokeInviteCode/{instanceName}: + put: + tags: + - Group Controller + summary: Update the group's display picture. + parameters: + - name: groupJid + in: query + schema: + type: string + description: "- required - The unique identifier of the group." + example: "120363046555718472@g.us" + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/sendInvite/{instanceName}: + post: + tags: + - Group Controller + summary: Update the group's display picture. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + groupJid: + type: string + description: "The unique identifier of the group." + description: + type: string + description: "The new description of the group." + numbers: + type: array + description: "List of participant phone numbers to be invited." + items: + type: string + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/inviteInfo/{instanceName}: + get: + tags: + - Group Controller + summary: Retrieve details about a specific group. + parameters: + - name: inviteCode + in: query + schema: + type: string + description: "- required - The invite code of the group." + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/findGroupInfos/{instanceName}: + get: + tags: + - Group Controller + summary: Retrieve details about a specific group. + parameters: + - name: groupJid + in: query + schema: + type: string + description: "- required - The unique identifier of the group." + example: "120363046555718472@g.us" + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/fetchAllGroups/{instanceName}: + get: + tags: + - Group Controller + summary: Retrieve details about a specific group. + parameters: + - name: getParticipants + in: query + schema: + type: boolean + description: "- required - Indicates whether to retrieve the participants of the group." + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/participants/{instanceName}: + get: + tags: + - Group Controller + summary: Retrieve a list of participants in a specific group. + parameters: + - name: groupJid + in: query + schema: + type: string + description: "- required - The unique identifier of the group." + example: "120363046555718472@g.us" + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/updateParticipant/{instanceName}: + put: + tags: + - Group Controller + summary: Update the status or role of a participant in the group. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + action: + type: string + enum: ["add", "remove", "promote", "demote"] + description: "- required - The action to be taken on the participant." + participants: + type: array + items: + type: string + description: "- required - List of participant phone numbers to be updated." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/updateSetting/{instanceName}: + put: + tags: + - Group Controller + summary: Update the status or role of a participant in the group. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + action: + type: string + enum: + ["announcement", "not_announcement", "locked", "unlocked"] + description: "- required - The action to be taken on the participant." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/toggleEphemeral/{instanceName}: + put: + tags: + - Group Controller + summary: Update the status or role of a participant in the group. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + expiration: + type: number + description: "- required - The action to be taken on the participant." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /group/leaveGroup/{instanceName}: + delete: + tags: + - Group Controller + summary: Exit from the specified WhatsApp group. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /instance/refreshToken/: + put: + tags: + - JWT + summary: Refresh an expired JWT token. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + oldToken: + type: string + description: "- required - The expired JWT token." + responses: + "200": + description: Successful response + content: + application/json: {} + + /webhook/set/{instanceName}: + post: + tags: + - Webhook + summary: Set up or modify the webhook for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + url: + type: string + format: uri + description: "The endpoint URL where the webhook data will be sent." + webhook_by_events: + type: boolean + description: "Indicates whether to send the webhook data by events." + webhook_base64: + type: boolean + description: "Indicates whether to send the webhook data in Base64 format." + events: + type: array + enum: + [ + "APPLICATION_STARTUP", + "QRCODE_UPDATED", + "MESSAGES_SET", + "MESSAGES_UPSERT", + "MESSAGES_UPDATE", + "MESSAGES_DELETE", + "SEND_MESSAGE", + "CONTACTS_SET", + "CONTACTS_UPSERT", + "CONTACTS_UPDATE", + "PRESENCE_UPDATE", + "CHATS_SET", + "CHATS_UPSERT", + "CHATS_UPDATE", + "CHATS_DELETE", + "GROUPS_UPSERT", + "GROUP_UPDATE", + "GROUP_PARTICIPANTS_UPDATE", + "CONNECTION_UPDATE", + "CALL", + "NEW_JWT_TOKEN", + ] + items: + type: string + description: "List of events to be sent to the webhook." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /webhook/find/{instanceName}: + get: + tags: + - Webhook + summary: Retrieve the webhook settings for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /websocket/set/{instanceName}: + post: + tags: + - Websocket + summary: Set up or modify the Websocket for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + description: "Indicates whether to enable the websocket." + events: + type: array + enum: + [ + "APPLICATION_STARTUP", + "QRCODE_UPDATED", + "MESSAGES_SET", + "MESSAGES_UPSERT", + "MESSAGES_UPDATE", + "MESSAGES_DELETE", + "SEND_MESSAGE", + "CONTACTS_SET", + "CONTACTS_UPSERT", + "CONTACTS_UPDATE", + "PRESENCE_UPDATE", + "CHATS_SET", + "CHATS_UPSERT", + "CHATS_UPDATE", + "CHATS_DELETE", + "GROUPS_UPSERT", + "GROUP_UPDATE", + "GROUP_PARTICIPANTS_UPDATE", + "CONNECTION_UPDATE", + "CALL", + "NEW_JWT_TOKEN", + ] + items: + type: string + description: "List of events to be sent to the websocket." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /websocket/find/{instanceName}: + get: + tags: + - Websocket + summary: Retrieve the websocket settings for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /rabbitmq/set/{instanceName}: + post: + tags: + - RabbitMQ + summary: Set up or modify the RabbitMQ for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + description: "Indicates whether to enable the RabbitMQ." + events: + type: array + enum: + [ + "APPLICATION_STARTUP", + "QRCODE_UPDATED", + "MESSAGES_SET", + "MESSAGES_UPSERT", + "MESSAGES_UPDATE", + "MESSAGES_DELETE", + "SEND_MESSAGE", + "CONTACTS_SET", + "CONTACTS_UPSERT", + "CONTACTS_UPDATE", + "PRESENCE_UPDATE", + "CHATS_SET", + "CHATS_UPSERT", + "CHATS_UPDATE", + "CHATS_DELETE", + "GROUPS_UPSERT", + "GROUP_UPDATE", + "GROUP_PARTICIPANTS_UPDATE", + "CONNECTION_UPDATE", + "CALL", + "NEW_JWT_TOKEN", + ] + items: + type: string + description: "List of events to be sent to the RabbitMQ." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /rabbitmq/find/{instanceName}: + get: + tags: + - RabbitMQ + summary: Retrieve the RabbitMQ settings for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /settings/set/{instanceName}: + post: + tags: + - Settings + summary: Set up or modify the Settings for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + reject_call: + type: boolean + description: "Indicates whether to reject incoming calls." + msg_call: + type: string + description: "Message to be sent when rejecting a call." + groups_ignore: + type: boolean + description: "Indicates whether to ignore group messages." + always_online: + type: boolean + description: "Indicates whether to keep the instance always online." + read_messages: + type: boolean + description: "Indicates whether to mark messages as read." + read_status: + type: boolean + description: "Indicates whether to mark status messages as read." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /settings/find/{instanceName}: + get: + tags: + - Settings + summary: Retrieve the Settings for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /chatwoot/set/{instanceName}: + post: + tags: + - Chatwoot + summary: Set up or modify the Chatwoot for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + description: "Indicates whether to enable the Chatwoot integration." + account_id: + type: string + description: "The Chatwoot account ID." + token: + type: string + description: "The Chatwoot token." + url: + type: string + description: "The Chatwoot URL." + sign_msg: + type: boolean + description: "Indicates whether to sign messages." + reopen_conversation: + type: boolean + description: "Indicates whether to reopen conversations." + conversation_pending: + type: boolean + description: "Indicates whether to mark conversations as pending." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chatwoot/find/{instanceName}: + get: + tags: + - Chatwoot + summary: Retrieve the Chatwoot for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /typebot/set/{instanceName}: + post: + tags: + - Typebot + summary: Set up or modify the Typebot for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + description: "Indicates whether to enable the Typebot integration." + url: + type: string + description: "The Chatwoot URL." + typebot: + type: string + description: "The Typebot Name." + expire: + type: number + description: "The Typebot Expire." + keyword_finish: + type: string + description: "The Typebot Keyword Finish." + delay_message: + type: number + description: "The Typebot Delay Message." + unknown_message: + type: string + description: "The Typebot Unknown Message." + listening_from_me: + type: boolean + description: "Indicates whether to listening from me." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /typebot/start/{instanceName}: + post: + tags: + - Typebot + summary: Start the Typebot for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + url: + type: string + description: "The Typebot URL." + typebot: + type: string + description: "The Typebot Name." + remoteJid: + type: string + description: "The Typebot RemoteJid." + startSession: + type: boolean + description: "Indicates whether to start session." + variables: + type: array + description: "List of variables." + items: + type: object + properties: + name: + type: string + description: "The variable name." + value: + type: string + description: "The variable value." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /typebot/find/{instanceName}: + get: + tags: + - Typebot + summary: Retrieve the Typebot for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /typebot/changeStatus/{instanceName}: + post: + tags: + - Typebot + summary: Change the status of the Typebot for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + remoteJid: + type: string + description: "The Typebot RemoteJid." + status: + type: string + description: "The Typebot Status." + enum: ["opened", "paused", "closed"] + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /proxy/set/{instanceName}: + post: + tags: + - Proxy + summary: Set up or modify the Proxy for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + description: "Indicates whether to enable the Proxy integration." + proxy: + type: string + description: "The Proxy URI." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /proxy/find/{instanceName}: + get: + tags: + - Proxy + summary: Retrieve the Proxy for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /chamaai/set/{instanceName}: + post: + tags: + - Chama AI + summary: Set up or modify the Chama AI for an instance. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + enabled: + type: boolean + description: "Indicates whether to enable the Chamai AI integration." + url: + type: string + description: "The Chamai AI URL." + token: + type: string + description: "The Chamai AI Token." + waNumber: + type: string + description: "The Chamai AI WhatsApp Number." + answerByAudio: + type: boolean + description: "Indicates whether to answer by audio." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chamaai/find/{instanceName}: + get: + tags: + - Chama AI + summary: Retrieve the Chama AI for a specific instance. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + + /chat/fetchBusinessProfile/{instanceName}: + post: + tags: + - Profile Settings + summary: Fetch the business profile of a specific contact. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: "- required - The phone number of the contact." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/fetchProfile/{instanceName}: + post: + tags: + - Profile Settings + summary: Fetch the profile of a specific contact. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + number: + type: string + description: "- required - The phone number of the contact." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/updateProfileName/{instanceName}: + post: + tags: + - Profile Settings + summary: Update the name of a specific contact. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: "- required - The new name of the contact." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/updateProfileStatus/{instanceName}: + post: + tags: + - Profile Settings + summary: Update the status of a specific contact. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + type: string + description: "- required - The new status of the contact." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/updateProfilePicture/{instanceName}: + put: + tags: + - Profile Settings + summary: Update the profile picture of a specific contact. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + picture: + type: string + description: "- required - The new profile picture of the contact." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/removeProfilePicture/{instanceName}: + delete: + tags: + - Profile Settings + summary: Remove the profile picture of a specific contact. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/fetchPrivacySettings/{instanceName}: + get: + tags: + - Profile Settings + summary: Fetch the privacy settings of a specific contact. + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} + /chat/updatePrivacySettings/{instanceName}: + put: + tags: + - Profile Settings + summary: Update the privacy settings of a specific contact. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + privacySettings: + type: object + description: "- required - The new privacy settings of the contact." + properties: + readreceipts: + type: string + enum: ["all", "none"] + description: "- required - The new read receipts privacy setting of the contact." + profile: + type: string + enum: ["all", "contacts", "contact_blacklist", "none"] + description: "- required - The new profile privacy setting of the contact." + status: + type: string + enum: ["all", "contacts", "contact_blacklist", "none"] + description: "- required - The new status privacy setting of the contact." + online: + type: string + enum: ["all", "match_last_seen"] + description: "- required - The new online privacy setting of the contact." + last: + type: string + enum: ["all", "contacts", "contact_blacklist", "none"] + description: "- required - The new last seen privacy setting of the contact." + groupadd: + type: string + enum: ["all", "contacts", "contact_blacklist", "none"] + description: "- required - The new group add privacy setting of the contact." + parameters: + - name: instanceName + in: path + schema: + type: string + required: true + description: "- required" + example: "evolution" + responses: + "200": + description: Successful response + content: + application/json: {} diff --git a/src/exceptions/400.exception.ts b/src/exceptions/400.exception.ts old mode 100644 new mode 100755 index 833295c1..75462955 --- a/src/exceptions/400.exception.ts +++ b/src/exceptions/400.exception.ts @@ -1,11 +1,11 @@ -import { HttpStatus } from '../whatsapp/routers/index.router'; - -export class BadRequestException { - constructor(...objectError: any[]) { - throw { - status: HttpStatus.BAD_REQUEST, - error: 'Bad Request', - message: objectError.length > 0 ? objectError : undefined, - }; - } -} +import { HttpStatus } from '../whatsapp/routers/index.router'; + +export class BadRequestException { + constructor(...objectError: any[]) { + throw { + status: HttpStatus.BAD_REQUEST, + error: 'Bad Request', + message: objectError.length > 0 ? objectError : undefined, + }; + } +} diff --git a/src/exceptions/401.exception.ts b/src/exceptions/401.exception.ts old mode 100644 new mode 100755 index d5424c71..04dbed38 --- a/src/exceptions/401.exception.ts +++ b/src/exceptions/401.exception.ts @@ -1,11 +1,11 @@ -import { HttpStatus } from '../whatsapp/routers/index.router'; - -export class UnauthorizedException { - constructor(...objectError: any[]) { - throw { - status: HttpStatus.UNAUTHORIZED, - error: 'Unauthorized', - message: objectError.length > 0 ? objectError : 'Unauthorized', - }; - } -} +import { HttpStatus } from '../whatsapp/routers/index.router'; + +export class UnauthorizedException { + constructor(...objectError: any[]) { + throw { + status: HttpStatus.UNAUTHORIZED, + error: 'Unauthorized', + message: objectError.length > 0 ? objectError : 'Unauthorized', + }; + } +} diff --git a/src/exceptions/403.exception.ts b/src/exceptions/403.exception.ts old mode 100644 new mode 100755 index f53ca9a5..bb24c568 --- a/src/exceptions/403.exception.ts +++ b/src/exceptions/403.exception.ts @@ -1,11 +1,11 @@ -import { HttpStatus } from '../whatsapp/routers/index.router'; - -export class ForbiddenException { - constructor(...objectError: any[]) { - throw { - status: HttpStatus.FORBIDDEN, - error: 'Forbidden', - message: objectError.length > 0 ? objectError : undefined, - }; - } -} +import { HttpStatus } from '../whatsapp/routers/index.router'; + +export class ForbiddenException { + constructor(...objectError: any[]) { + throw { + status: HttpStatus.FORBIDDEN, + error: 'Forbidden', + message: objectError.length > 0 ? objectError : undefined, + }; + } +} diff --git a/src/exceptions/404.exception.ts b/src/exceptions/404.exception.ts old mode 100644 new mode 100755 index 1119d1a1..a749531d --- a/src/exceptions/404.exception.ts +++ b/src/exceptions/404.exception.ts @@ -1,11 +1,11 @@ -import { HttpStatus } from '../whatsapp/routers/index.router'; - -export class NotFoundException { - constructor(...objectError: any[]) { - throw { - status: HttpStatus.NOT_FOUND, - error: 'Not Found', - message: objectError.length > 0 ? objectError : undefined, - }; - } -} +import { HttpStatus } from '../whatsapp/routers/index.router'; + +export class NotFoundException { + constructor(...objectError: any[]) { + throw { + status: HttpStatus.NOT_FOUND, + error: 'Not Found', + message: objectError.length > 0 ? objectError : undefined, + }; + } +} diff --git a/src/exceptions/500.exception.ts b/src/exceptions/500.exception.ts old mode 100644 new mode 100755 index 2a41dfa5..4ad65056 --- a/src/exceptions/500.exception.ts +++ b/src/exceptions/500.exception.ts @@ -1,11 +1,11 @@ -import { HttpStatus } from '../whatsapp/routers/index.router'; - -export class InternalServerErrorException { - constructor(...objectError: any[]) { - throw { - status: HttpStatus.INTERNAL_SERVER_ERROR, - error: 'Internal Server Error', - message: objectError.length > 0 ? objectError : undefined, - }; - } -} +import { HttpStatus } from '../whatsapp/routers/index.router'; + +export class InternalServerErrorException { + constructor(...objectError: any[]) { + throw { + status: HttpStatus.INTERNAL_SERVER_ERROR, + error: 'Internal Server Error', + message: objectError.length > 0 ? objectError : undefined, + }; + } +} diff --git a/src/exceptions/index.ts b/src/exceptions/index.ts old mode 100644 new mode 100755 index 1657fc2f..edfd7f4b --- a/src/exceptions/index.ts +++ b/src/exceptions/index.ts @@ -1,5 +1,5 @@ -export * from './400.exception'; -export * from './401.exception'; -export * from './403.exception'; -export * from './404.exception'; -export * from './500.exception'; +export * from './400.exception'; +export * from './401.exception'; +export * from './403.exception'; +export * from './404.exception'; +export * from './500.exception'; diff --git a/src/libs/amqp.server.ts b/src/libs/amqp.server.ts old mode 100644 new mode 100755 index fc95b33c..d53b44ad --- a/src/libs/amqp.server.ts +++ b/src/libs/amqp.server.ts @@ -1,100 +1,100 @@ -import * as amqp from 'amqplib/callback_api'; - -import { configService, Rabbitmq } from '../config/env.config'; -import { Logger } from '../config/logger.config'; - -const logger = new Logger('AMQP'); - -let amqpChannel: amqp.Channel | null = null; - -export const initAMQP = () => { - return new Promise((resolve, reject) => { - const uri = configService.get('RABBITMQ').URI; - amqp.connect(uri, (error, connection) => { - if (error) { - reject(error); - return; - } - - connection.createChannel((channelError, channel) => { - if (channelError) { - reject(channelError); - return; - } - - const exchangeName = 'evolution_exchange'; - - channel.assertExchange(exchangeName, 'topic', { - durable: true, - autoDelete: false, - }); - - amqpChannel = channel; - - logger.info('AMQP initialized'); - resolve(); - }); - }); - }); -}; - -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); - }); -}; - -export const removeQueues = (instanceName: string, events: string[]) => { - if (!events || !events.length) return; - - const channel = getAMQP(); - - const queues = events.map((event) => { - return `${event.replace(/_/g, '.').toLowerCase()}`; - }); - - const exchangeName = instanceName ?? 'evolution_exchange'; - - queues.forEach((event) => { - const amqp = getAMQP(); - - amqp.assertExchange(exchangeName, 'topic', { - durable: true, - autoDelete: false, - }); - - const queueName = `${instanceName}.${event}`; - - amqp.deleteQueue(queueName); - }); - - channel.deleteExchange(exchangeName); -}; +import * as amqp from 'amqplib/callback_api'; + +import { configService, Rabbitmq, Openai } from '../config/env.config'; +import { Logger } from '../config/logger.config'; + +const logger = new Logger('AMQP'); + +let amqpChannel: amqp.Channel | null = null; + +export const initAMQP = () => { + return new Promise((resolve, reject) => { + const uri = configService.get('RABBITMQ').URI; + amqp.connect(uri, (error, connection) => { + if (error) { + reject(error); + return; + } + + connection.createChannel((channelError, channel) => { + if (channelError) { + reject(channelError); + return; + } + + const exchangeName = 'evolution_exchange'; + + channel.assertExchange(exchangeName, 'topic', { + durable: true, + autoDelete: false, + }); + + amqpChannel = channel; + + logger.info('AMQP initialized'); + resolve(); + }); + }); + }); +}; + +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); + }); +}; + +export const removeQueues = (instanceName: string, events: string[]) => { + if (!events || !events.length) return; + + const channel = getAMQP(); + + const queues = events.map((event) => { + return `${event.replace(/_/g, '.').toLowerCase()}`; + }); + + const exchangeName = instanceName ?? 'evolution_exchange'; + + queues.forEach((event) => { + const amqp = getAMQP(); + + amqp.assertExchange(exchangeName, 'topic', { + durable: true, + autoDelete: false, + }); + + const queueName = `${instanceName}.${event}`; + + amqp.deleteQueue(queueName); + }); + + channel.deleteExchange(exchangeName); +}; diff --git a/src/libs/db.connect.ts b/src/libs/db.connect.ts old mode 100644 new mode 100755 index b11610c7..ecb1e08a --- a/src/libs/db.connect.ts +++ b/src/libs/db.connect.ts @@ -1,25 +1,26 @@ -import mongoose from 'mongoose'; - -import { configService, Database } from '../config/env.config'; -import { Logger } from '../config/logger.config'; - -const logger = new Logger('MongoDB'); - -const db = configService.get('DATABASE'); -export const dbserver = (() => { - if (db.ENABLED) { - logger.verbose('connecting'); - const dbs = mongoose.createConnection(db.CONNECTION.URI, { - dbName: db.CONNECTION.DB_PREFIX_NAME + '-whatsapp-api', - }); - logger.verbose('connected in ' + db.CONNECTION.URI); - logger.info('ON - dbName: ' + dbs['$dbName']); - - process.on('beforeExit', () => { - logger.verbose('instance destroyed'); - dbserver.destroy(true, (error) => logger.error(error)); - }); - - return dbs; - } -})(); +import mongoose from 'mongoose'; + +import { configService, Database } from '../config/env.config'; +import { Logger } from '../config/logger.config'; + +const logger = new Logger('MongoDB'); + +const db = configService.get('DATABASE'); +export const dbserver = (() => { + if (db.ENABLED) { + logger.verbose('connecting'); + const dbs = mongoose.createConnection(db.CONNECTION.URI, { + + dbName: db.CONNECTION.DB_PREFIX_NAME + db.CONNECTION.DB_PREFIX_FINAL_NAME + '-config', + }); + logger.verbose('connected in ' + db.CONNECTION.URI); + logger.info('ON - dbName: ' + dbs['$dbName']); + + process.on('beforeExit', () => { + logger.verbose('instance destroyed'); + dbserver.destroy(true, (error) => logger.error(error)); + }); + + return dbs; + } +})(); diff --git a/src/libs/openai.ts b/src/libs/openai.ts new file mode 100755 index 00000000..5eb7c9b7 --- /dev/null +++ b/src/libs/openai.ts @@ -0,0 +1,34 @@ +import { Configuration, OpenAIApi } from "openai" + +import { config } from "../config" + +const configuration = new Configuration({ + apiKey: config.openAI.apiToken, +}) + +export const openai = new OpenAIApi(configuration) + + +export class OpenAIService { + constructor( + private readonly apikey: String, + ) { + } + + private WaOpenai: OpenAIApi; + + public SetOpenai() { + + const configuration = new Configuration({ + apiKey: config.openAI.apiToken, + }) + + this.WaOpenai = new OpenAIApi(configuration) + } + + public openai() { + + return this.WaOpenai; + } + +} diff --git a/src/libs/redis.client.ts b/src/libs/redis.client.ts old mode 100644 new mode 100755 index 1d74ff15..4164c129 --- a/src/libs/redis.client.ts +++ b/src/libs/redis.client.ts @@ -1,115 +1,132 @@ -import { createClient, RedisClientType } from '@redis/client'; -import { BufferJSON } from '@whiskeysockets/baileys'; - -import { Redis } from '../config/env.config'; -import { Logger } from '../config/logger.config'; - -export class RedisCache { - private readonly logger = new Logger(RedisCache.name); - private client: RedisClientType; - private statusConnection = false; - private instanceName: string; - private redisEnv: Redis; - - constructor() { - this.logger.verbose('RedisCache instance created'); - process.on('beforeExit', () => { - this.logger.verbose('RedisCache instance destroyed'); - this.disconnect(); - }); - } - - public set reference(reference: string) { - this.logger.verbose('set reference: ' + reference); - this.instanceName = reference; - } - - public async connect(redisEnv: Redis) { - this.logger.verbose('Connecting to Redis...'); - this.client = createClient({ url: redisEnv.URI }); - this.client.on('error', (err) => this.logger.error('Redis Client Error ' + err)); - - await this.client.connect(); - this.statusConnection = true; - this.redisEnv = redisEnv; - this.logger.verbose(`Connected to ${redisEnv.URI}`); - } - - public async disconnect() { - if (this.statusConnection) { - await this.client.disconnect(); - this.statusConnection = false; - this.logger.verbose('Redis client disconnected'); - } - } - - public async instanceKeys(): Promise { - const keys: string[] = []; - try { - this.logger.verbose('Fetching instance keys'); - for await (const key of this.client.scanIterator({ MATCH: `${this.redisEnv.PREFIX_KEY}:*` })) { - keys.push(key); - } - } catch (error) { - this.logger.error('Error fetching instance keys ' + error); - } - return keys; - } - - public async keyExists(key?: string) { - if (key) { - this.logger.verbose('keyExists: ' + key); - return !!(await this.instanceKeys()).find((i) => i === key); - } - this.logger.verbose('keyExists: ' + this.instanceName); - return !!(await this.instanceKeys()).find((i) => i === this.instanceName); - } - - public async writeData(field: string, data: any) { - try { - this.logger.verbose('writeData: ' + field); - const json = JSON.stringify(data, BufferJSON.replacer); - - return await this.client.hSet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field, json); - } catch (error) { - this.logger.error(error); - } - } - - public async readData(field: string) { - try { - this.logger.verbose('readData: ' + field); - const data = await this.client.hGet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); - - if (data) { - this.logger.verbose('readData: ' + field + ' success'); - return JSON.parse(data, BufferJSON.reviver); - } - - this.logger.verbose('readData: ' + field + ' not found'); - return null; - } catch (error) { - this.logger.error(error); - } - } - - public async removeData(field: string) { - try { - this.logger.verbose('removeData: ' + field); - return await this.client.hDel(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); - } catch (error) { - this.logger.error(error); - } - } - - public async delAll(hash?: string) { - try { - this.logger.verbose('instance delAll: ' + hash); - const result = await this.client.del(hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName); - - return result; - } catch (error) { - this.logger.error(error); - } - } -} +import { createClient, RedisClientType } from '@redis/client'; +import { BufferJSON } from '@whiskeysockets/baileys'; + +import { Redis } from '../config/env.config'; +import { Logger } from '../config/logger.config'; + +export class RedisCache { + + constructor() { + this.logger.verbose('RedisCache instance created'); + process.on('beforeExit', () => { + this.logger.verbose('RedisCache instance destroyed'); + this.disconnect(); + }); + } + + // constructor() { + // this.logger.verbose('instance created'); + // process.on('beforeExit', async () => { + // this.logger.verbose('instance destroyed'); + // if (this.statusConnection) { + // this.logger.verbose('instance disconnect'); + // await this.client.disconnect(); + // } + // }); + // } + + private statusConnection = false; + private instanceName: string; + private redisEnv: Redis; + + public set reference(reference: string) { + this.logger.verbose('set reference: ' + reference); + this.instanceName = reference; + } + + public async connect(redisEnv: Redis) { + this.logger.verbose('Connecting to Redis...'); + this.client = createClient({ url: redisEnv.URI }); + //this.logger.verbose('connected in ' + redisEnv.URI); + this.client.on('error', (err) => this.logger.error('Redis Client Error ' + err)); + await this.client.connect(); + this.statusConnection = true; + this.redisEnv = redisEnv; + this.logger.verbose(`Connected to ${redisEnv.URI}`); + } + + private readonly logger = new Logger(RedisCache.name); + private client: RedisClientType; + + + public async disconnect() { + if (this.statusConnection) { + await this.client.disconnect(); + this.statusConnection = false; + this.logger.verbose('Redis client disconnected'); + } + } + + public async instanceKeys(): Promise { + const keys: string[] = []; + try { + //this.logger.verbose('instance keys: ' + this.redisEnv.PREFIX_KEY + ':*'); + //return await this.client.sendCommand(['keys', this.redisEnv.PREFIX_KEY + ':*']); + this.logger.verbose('Fetching instance keys'); + for await (const key of this.client.scanIterator({ MATCH: `${this.redisEnv.PREFIX_KEY}:*` })) { + keys.push(key); + } + } catch (error) { + this.logger.error(error); + this.logger.error('Error fetching instance keys ' + error); + } + return keys; + } + + public async keyExists(key?: string) { + if (key) { + this.logger.verbose('keyExists: ' + key); + return !!(await this.instanceKeys()).find((i) => i === key); + } + this.logger.verbose('keyExists: ' + this.instanceName); + return !!(await this.instanceKeys()).find((i) => i === this.instanceName); + } + + public async writeData(field: string, data: any) { + try { + this.logger.verbose('writeData: ' + field); + const json = JSON.stringify(data, BufferJSON.replacer); + + return await this.client.hSet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field, json); + } catch (error) { + this.logger.error(error); + } + } + + public async readData(field: string) { + try { + this.logger.verbose('readData: ' + field); + const data = await this.client.hGet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); + + if (data) { + this.logger.verbose('readData: ' + field + ' success'); + return JSON.parse(data, BufferJSON.reviver); + } + + this.logger.verbose('readData: ' + field + ' not found'); + return null; + } catch (error) { + this.logger.error(error); + } + } + + public async removeData(field: string) { + try { + this.logger.verbose('removeData: ' + field); + return await this.client.hDel(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); + } catch (error) { + this.logger.error(error); + } + } + + public async delAll(hash?: string) { + try { + this.logger.verbose('instance delAll: ' + hash); + const result = await this.client.del(hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName); + + return result; + } catch (error) { + this.logger.error(error); + } + } +} diff --git a/src/libs/redis.ts b/src/libs/redis.ts new file mode 100755 index 00000000..8414321c --- /dev/null +++ b/src/libs/redis.ts @@ -0,0 +1,9 @@ +import { Redis } from "ioredis" + +import { config } from "../config" + +export const redis = new Redis({ + host: config.redis.host, + port: config.redis.port, + db: config.redis.db, +}) diff --git a/src/libs/socket.server.ts b/src/libs/socket.server.ts old mode 100644 new mode 100755 index ecf01ab1..c12dd384 --- a/src/libs/socket.server.ts +++ b/src/libs/socket.server.ts @@ -1,44 +1,44 @@ -import { Server } from 'http'; -import { Server as SocketIO } from 'socket.io'; - -import { configService, Cors, Websocket } from '../config/env.config'; -import { Logger } from '../config/logger.config'; - -const logger = new Logger('Socket'); - -let io: SocketIO; - -const cors = configService.get('CORS').ORIGIN; - -export const initIO = (httpServer: Server) => { - if (configService.get('WEBSOCKET')?.ENABLED) { - io = new SocketIO(httpServer, { - cors: { - origin: cors, - }, - }); - - io.on('connection', (socket) => { - logger.info('User connected'); - - socket.on('disconnect', () => { - logger.info('User disconnected'); - }); - }); - - logger.info('Socket.io initialized'); - return io; - } - return null; -}; - -export const getIO = (): SocketIO => { - logger.verbose('Getting Socket.io'); - - if (!io) { - logger.error('Socket.io not initialized'); - throw new Error('Socket.io not initialized'); - } - - return io; -}; +import { Server } from 'http'; +import { Server as SocketIO } from 'socket.io'; + +import { configService, Cors, Websocket } from '../config/env.config'; +import { Logger } from '../config/logger.config'; + +const logger = new Logger('Socket'); + +let io: SocketIO; + +const cors = configService.get('CORS').ORIGIN; + +export const initIO = (httpServer: Server) => { + if (configService.get('WEBSOCKET')?.ENABLED) { + io = new SocketIO(httpServer, { + cors: { + origin: cors, + }, + }); + + io.on('connection', (socket) => { + logger.info('User connected'); + + socket.on('disconnect', () => { + logger.info('User disconnected'); + }); + }); + + logger.info('Socket.io initialized'); + return io; + } + return null; +}; + +export const getIO = (): SocketIO => { + logger.verbose('Getting Socket.io'); + + if (!io) { + logger.error('Socket.io not initialized'); + throw new Error('Socket.io not initialized'); + } + + return io; +}; diff --git a/src/libs/sqs.server.ts b/src/libs/sqs.server.ts old mode 100644 new mode 100755 index 04184542..35a69afd --- a/src/libs/sqs.server.ts +++ b/src/libs/sqs.server.ts @@ -1,97 +1,97 @@ -import { SQS } from 'aws-sdk'; - -import { configService, Sqs } from '../config/env.config'; -import { Logger } from '../config/logger.config'; - -const logger = new Logger('SQS'); - -let sqs: SQS; - -export const initSQS = () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return new Promise((resolve, reject) => { - const awsConfig = configService.get('SQS'); - sqs = new SQS({ - accessKeyId: awsConfig.ACCESS_KEY_ID, - secretAccessKey: awsConfig.SECRET_ACCESS_KEY, - region: awsConfig.REGION, - }); - - logger.info('SQS initialized'); - resolve(); - }); -}; - -export const getSQS = (): SQS => { - return sqs; -}; - -export const initQueues = (instanceName: string, events: string[]) => { - if (!events || !events.length) return; - - const queues = events.map((event) => { - return `${event.replace(/_/g, '_').toLowerCase()}`; - }); - - const sqs = getSQS(); - - queues.forEach((event) => { - const queueName = `${instanceName}_${event}.fifo`; - - sqs.createQueue( - { - QueueName: queueName, - Attributes: { - FifoQueue: 'true', - }, - }, - (err, data) => { - if (err) { - logger.error(`Error creating queue ${queueName}: ${err.message}`); - } else { - logger.info(`Queue ${queueName} created: ${data.QueueUrl}`); - } - }, - ); - }); -}; - -export const removeQueues = (instanceName: string, events: string[]) => { - if (!events || !events.length) return; - - const sqs = getSQS(); - - const queues = events.map((event) => { - return `${event.replace(/_/g, '_').toLowerCase()}`; - }); - - queues.forEach((event) => { - const queueName = `${instanceName}_${event}.fifo`; - - sqs.getQueueUrl( - { - QueueName: queueName, - }, - (err, data) => { - if (err) { - logger.error(`Error getting queue URL for ${queueName}: ${err.message}`); - } else { - const queueUrl = data.QueueUrl; - - sqs.deleteQueue( - { - QueueUrl: queueUrl, - }, - (deleteErr) => { - if (deleteErr) { - logger.error(`Error deleting queue ${queueName}: ${deleteErr.message}`); - } else { - logger.info(`Queue ${queueName} deleted`); - } - }, - ); - } - }, - ); - }); -}; +import { SQS } from 'aws-sdk'; + +import { configService, Sqs } from '../config/env.config'; +import { Logger } from '../config/logger.config'; + +const logger = new Logger('SQS'); + +let sqs: SQS; + +export const initSQS = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + return new Promise((resolve, reject) => { + const awsConfig = configService.get('SQS'); + sqs = new SQS({ + accessKeyId: awsConfig.ACCESS_KEY_ID, + secretAccessKey: awsConfig.SECRET_ACCESS_KEY, + region: awsConfig.REGION, + }); + + logger.info('SQS initialized'); + resolve(); + }); +}; + +export const getSQS = (): SQS => { + return sqs; +}; + +export const initQueues = (instanceName: string, events: string[]) => { + if (!events || !events.length) return; + + const queues = events.map((event) => { + return `${event.replace(/_/g, '_').toLowerCase()}`; + }); + + const sqs = getSQS(); + + queues.forEach((event) => { + const queueName = `${instanceName}_${event}.fifo`; + + sqs.createQueue( + { + QueueName: queueName, + Attributes: { + FifoQueue: 'true', + }, + }, + (err, data) => { + if (err) { + logger.error(`Error creating queue ${queueName}: ${err.message}`); + } else { + logger.info(`Queue ${queueName} created: ${data.QueueUrl}`); + } + }, + ); + }); +}; + +export const removeQueues = (instanceName: string, events: string[]) => { + if (!events || !events.length) return; + + const sqs = getSQS(); + + const queues = events.map((event) => { + return `${event.replace(/_/g, '_').toLowerCase()}`; + }); + + queues.forEach((event) => { + const queueName = `${instanceName}_${event}.fifo`; + + sqs.getQueueUrl( + { + QueueName: queueName, + }, + (err, data) => { + if (err) { + logger.error(`Error getting queue URL for ${queueName}: ${err.message}`); + } else { + const queueUrl = data.QueueUrl; + + sqs.deleteQueue( + { + QueueUrl: queueUrl, + }, + (deleteErr) => { + if (deleteErr) { + logger.error(`Error deleting queue ${queueName}: ${deleteErr.message}`); + } else { + logger.info(`Queue ${queueName} deleted`); + } + }, + ); + } + }, + ); + }); +}; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts old mode 100644 new mode 100755 index 52bdd798..c7ee54b5 --- a/src/main.ts +++ b/src/main.ts @@ -1,137 +1,137 @@ -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 { Auth, configService, Cors, HttpServer, Rabbitmq, Sqs, Webhook } from './config/env.config'; -import { onUnexpectedError } from './config/error.config'; -import { Logger } from './config/logger.config'; -import { ROOT_DIR } from './config/path.config'; -import { swaggerRouter } from './docs/swagger.conf'; -import { initAMQP } from './libs/amqp.server'; -import { initIO } from './libs/socket.server'; -import { initSQS } from './libs/sqs.server'; -import { ServerUP } from './utils/server-up'; -import { HttpStatus, router } from './whatsapp/routers/index.router'; -import { waMonitor } from './whatsapp/whatsapp.module'; - -function initWA() { - waMonitor.loadInstance(); -} - -function bootstrap() { - const logger = new Logger('SERVER'); - const app = express(); - - app.use( - cors({ - origin(requestOrigin, callback) { - const { ORIGIN } = configService.get('CORS'); - if (ORIGIN.includes('*')) { - return callback(null, true); - } - if (ORIGIN.indexOf(requestOrigin) !== -1) { - return callback(null, true); - } - return callback(new Error('Not allowed by CORS')); - }, - methods: [...configService.get('CORS').METHODS], - credentials: configService.get('CORS').CREDENTIALS, - }), - urlencoded({ extended: true, limit: '136mb' }), - json({ limit: '136mb' }), - compression(), - ); - - app.set('view engine', 'hbs'); - 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(swaggerRouter); - - app.use( - (err: Error, req: Request, res: Response, next: NextFunction) => { - if (err) { - const webhook = configService.get('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 globalApiKey = configService.get('AUTHENTICATION').API_KEY.KEY; - const serverUrl = configService.get('SERVER').URL; - - 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, - api_key: globalApiKey, - server_url: serverUrl, - }; - - logger.error(errorData); - - const baseURL = webhook.EVENTS.ERRORS_WEBHOOK; - const httpService = axios.create({ baseURL }); - - httpService.post('', errorData); - } - - return res.status(err['status'] || 500).json({ - status: err['status'] || 500, - error: err['error'] || 'Internal Server Error', - response: { - message: err['message'] || 'Internal Server Error', - }, - }); - } - - next(); - }, - (req: Request, res: Response, next: NextFunction) => { - const { method, url } = req; - - res.status(HttpStatus.NOT_FOUND).json({ - status: HttpStatus.NOT_FOUND, - error: 'Not Found', - response: { - message: [`Cannot ${method.toUpperCase()} ${url}`], - }, - }); - - next(); - }, - ); - - const httpServer = configService.get('SERVER'); - - ServerUP.app = app; - const server = ServerUP[httpServer.TYPE]; - - server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT)); - - initWA(); - - initIO(server); - - if (configService.get('RABBITMQ')?.ENABLED) initAMQP(); - - if (configService.get('SQS')?.ENABLED) initSQS(); - - onUnexpectedError(); -} - -bootstrap(); +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 { Auth, configService, Cors, HttpServer, Rabbitmq, Sqs, Webhook } from './config/env.config'; +import { onUnexpectedError } from './config/error.config'; +import { Logger } from './config/logger.config'; +import { ROOT_DIR } from './config/path.config'; +import { swaggerRouter } from './docs/swagger.conf'; +import { initAMQP } from './libs/amqp.server'; +import { initIO } from './libs/socket.server'; +import { initSQS } from './libs/sqs.server'; +import { ServerUP } from './utils/server-up'; +import { HttpStatus, router } from './whatsapp/routers/index.router'; +import { waMonitor } from './whatsapp/whatsapp.module'; + +function initWA() { + waMonitor.loadInstance(); +} + +function bootstrap() { + const logger = new Logger('SERVER'); + const app = express(); + + app.use( + cors({ + origin(requestOrigin, callback) { + const { ORIGIN } = configService.get('CORS'); + if (ORIGIN.includes('*')) { + return callback(null, true); + } + if (ORIGIN.indexOf(requestOrigin) !== -1) { + return callback(null, true); + } + return callback(new Error('Not allowed by CORS')); + }, + methods: [...configService.get('CORS').METHODS], + credentials: configService.get('CORS').CREDENTIALS, + }), + urlencoded({ extended: true, limit: '136mb' }), + json({ limit: '136mb' }), + compression(), + ); + + app.set('view engine', 'hbs'); + 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(swaggerRouter); + + app.use( + (err: Error, req: Request, res: Response, next: NextFunction) => { + if (err) { + const webhook = configService.get('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 globalApiKey = configService.get('AUTHENTICATION').API_KEY.KEY; + const serverUrl = configService.get('SERVER').URL; + + 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, + api_key: globalApiKey, + server_url: serverUrl, + }; + + logger.error(errorData); + + const baseURL = webhook.EVENTS.ERRORS_WEBHOOK; + const httpService = axios.create({ baseURL }); + + httpService.post('', errorData); + } + + return res.status(err['status'] || 500).json({ + status: err['status'] || 500, + error: err['error'] || 'Internal Server Error', + response: { + message: err['message'] || 'Internal Server Error', + }, + }); + } + + next(); + }, + (req: Request, res: Response, next: NextFunction) => { + const { method, url } = req; + + res.status(HttpStatus.NOT_FOUND).json({ + status: HttpStatus.NOT_FOUND, + error: 'Not Found', + response: { + message: [`Cannot ${method.toUpperCase()} ${url}`], + }, + }); + + next(); + }, + ); + + const httpServer = configService.get('SERVER'); + + ServerUP.app = app; + const server = ServerUP[httpServer.TYPE]; + + server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT)); + + initWA(); + + initIO(server); + + if (configService.get('RABBITMQ')?.ENABLED) initAMQP(); + + if (configService.get('SQS')?.ENABLED) initSQS(); + + onUnexpectedError(); +} + +bootstrap(); diff --git a/src/prompts/contabilAgent.ts b/src/prompts/contabilAgent.ts new file mode 100755 index 00000000..ce0a71ae --- /dev/null +++ b/src/prompts/contabilAgent.ts @@ -0,0 +1,49 @@ +export const promptContabil = `Você é uma assistente virtual de atendimento de um escritorio de contabilidade chamado {{ storeName }}. Você deve ser educada, atenciosa, amigável, cordial e muito paciente. + + + +O roteiro de atendimento é: + +1. Saudação inicial: Cumprimente o cliente e agradeça por entrar em contato. +2. Coleta de informações: Solicite ao cliente seu nome para registro caso ainda não tenha registrado. Informe que os dados são apenas para controle de atendimento e não serão compartilhados com terceiros. +3. Pergunte o setor que deseja seguir, sendo o seguinte Financeiro, suporte ou novo cliente. + +4. Serviços de Contabilidade: +4.1 Apresente os principais serviços de contabilidade oferecidos pelo escritório. +4.2 Oferecemos uma ampla gama de serviços contábeis, incluindo ABERTURA DE EMPRESA, ABERTURA DE FILIAL, ASSESSORIA CONTÁBIL, ASSESSORIA FISCAL, BAIXA E REGULARIZAÇÃO DE EMPRESAS, CONSULTORIA CONTÁBIL, PLANEJAMENTO TRIBUTÁRIO, RECURSOS HUMANOS, REVISÃO TRIBUTÁRIA, Administração de Condomínios. Como posso ajudá-lo com seus desafios financeiros hoje? +5. Perguntas Frequentes (FAQ): +5.1 Forneça respostas para perguntas frequentes sobre impostos, contabilidade e serviços específicos. +5.2 Aqui estão algumas perguntas frequentes sobre nossos serviços: [ + Pergunta 1: Vou precisar falar com meu antigo contador ou escritório de contabilidade sobre a migração? + Resposta 1: Não. Fique tranquilo pois a Anexo Gestão Contábil cuida disso para você. Você só precisará enviar o seu Certificado Digital. Caso não tenha um e-CNPJ(A1), vamos te ajudar no processo de migração de outra forma, falando com o contador ou auxiliando na contratação de um Certificado Digital. Depois dessa etapa nós vamos fazer todo o seu processo com o seu antigo contador ou escritório de contabilidade. É simples, transparente e sem burocracia para você. + + Pergunta 2: Quanto tempo demora para mudar para a Anexo Gestão Contábil? + Resposta 2: O processo é rápido, prático e você não precisa sair de casa. Aproximadamente 5 dias úteis é o prazo após conseguirmos acessar a documentação via Certificado Digital ou com o contador antigo. +]. + +5. Agendamento de Consulta: +5.2 Permita que os usuários agendem uma consulta com um contador. +5.3 Pergunte sobre a data e hora preferidas. +5.3 Se você gostaria de agendar uma consulta com um de nossos contadores, por favor, informe-nos sobre a data e horário que funcionam melhor para você. + +6. Impostos: +6.1 Forneça informações sobre prazos de declaração de impostos, documentos necessários e dicas fiscais. +6.2 Os prazos para a declaração de impostos estão se aproximando. Aqui estão algumas dicas sobre como se preparar e os documentos necessários. + +7. Contato e Localização: +7.1 Fornecer informações de contato, incluindo número de telefone, endereço de e-mail e endereço físico do escritório. +7.2 Você pode nos contatar pelo telefone [número], enviar um e-mail para [e-mail] ou nos visitar no seguinte endereço [endereço]. + +8. Encaminhamento para um Contador: +8.1 Se o chatbot não puder responder a uma pergunta específica, ofereça a opção de ser encaminhado para um contador real. +8.2 Se você tiver uma pergunta mais complexa que eu não possa responder, gostaria de ser encaminhado para um dos nossos contadores?" + +9. Despedida: +9.1 Encerre a conversa de maneira cortês e ofereça informações de contato adicionais. +9.2 Obrigado por usar nossos serviços! Se precisar de assistência futura, estamos à disposição. Tenha um ótimo dia!" + +10. Feedback: +10.1 Solicite feedback aos usuários para melhorar o desempenho do chatbot. +10.2 Gostaríamos de ouvir sua opinião! Em uma escala de 1 a 5, quão útil você achou nosso chatbot hoje? + +` diff --git a/src/prompts/pizzaAgent.ts b/src/prompts/pizzaAgent.ts new file mode 100755 index 00000000..53c0c91b --- /dev/null +++ b/src/prompts/pizzaAgent.ts @@ -0,0 +1,94 @@ +export const promptPizza = `Você é uma assistente virtual de atendimento de uma pizzaria chamada {{ storeName }}. Você deve ser educada, atenciosa, amigável, cordial e muito paciente. + +Você não pode oferecer nenhum item ou sabor que não esteja em nosso cardápio. Siga estritamente as listas de opções. + +O código do pedido é: {{ orderCode }} + +O roteiro de atendimento é: + +1. Saudação inicial: Cumprimente o cliente e agradeça por entrar em contato. +2. Coleta de informações: Solicite ao cliente seu nome para registro caso ainda não tenha registrado. Informe que os dados são apenas para controle de pedidos e não serão compartilhados com terceiros. +3. Quantidade de pizzas: Pergunte ao cliente quantas pizzas ele deseja pedir. +4. Sabores: Envie a lista resumida apenas com os nomes de sabores salgados e doces e pergunte ao cliente quais sabores de pizza ele deseja pedir. +4.1 O cliente pode escolher a pizza fracionada em até 2 sabores na mesma pizza. +4.2 Se o cliente escolher mais de uma pizza, pergunte se ele deseja que os sabores sejam repetidos ou diferentes. +4.3 Se o cliente escolher sabores diferentes, pergunte quais são os sabores de cada pizza. +4.4 Se o cliente escolher sabores repetidos, pergunte quantas pizzas de cada sabor ele deseja. +4.5 Se o cliente estiver indeciso, ofereça sugestões de sabores ou se deseja receber o cardápio completo. +4.6 Se o sabor não estiver no cardápio, não deve prosseguir com o atendimento. Nesse caso informe que o sabor não está disponível e agradeça o cliente. +5. Tamanho: Pergunte ao cliente qual o tamanho das pizzas. +5.1 Se o cliente escolher mais de um tamanho, pergunte se ele deseja que os tamanhos sejam repetidos ou diferentes. +5.2 Se o cliente escolher tamanhos diferentes, pergunte qual o tamanho de cada pizza. +5.3 Se o cliente escolher tamanhos repetidos, pergunte quantas pizzas de cada tamanho ele deseja. +5.4 Se o cliente estiver indeciso, ofereça sugestões de tamanhos. Se for para 1 pessoa o tamanho pequeno é ideal, para 2 pessoas o tamanho médio é ideal e para 3 ou mais pessoas o tamanho grande é ideal. +6. Ingredientes adicionais: Pergunte ao cliente se ele deseja adicionar algum ingrediente extra. +6.1 Se o cliente escolher ingredientes extras, pergunte quais são os ingredientes adicionais de cada pizza. +6.2 Se o cliente estiver indeciso, ofereça sugestões de ingredientes extras. +7. Remover ingredientes: Pergunte ao cliente se ele deseja remover algum ingrediente, por exemplo, cebola. +7.1 Se o cliente escolher ingredientes para remover, pergunte quais são os ingredientes que ele deseja remover de cada pizza. +7.2 Não é possível remover ingredientes que não existam no cardápio. +8. Borda: Pergunte ao cliente se ele deseja borda recheada. +8.1 Se o cliente escolher borda recheada, pergunte qual o sabor da borda recheada. +8.2 Se o cliente estiver indeciso, ofereça sugestões de sabores de borda recheada. Uma dica é oferecer a borda como sobremesa com sabor de chocolate. +9. Bebidas: Pergunte ao cliente se ele deseja pedir alguma bebida. +9.1 Se o cliente escolher bebidas, pergunte quais são as bebidas que ele deseja pedir. +9.2 Se o cliente estiver indeciso, ofereça sugestões de bebidas. +10. Entrega: Pergunte ao cliente se ele deseja receber o pedido em casa ou se prefere retirar no balcão. +10.1 Se o cliente escolher entrega, pergunte qual o endereço de entrega. O endereço deverá conter Rua, Número, Bairro e CEP. +10.2 Os CEPs de 12.220-000 até 12.330-000 possuem uma taxa de entrega de R$ 10,00. +10.3 Se o cliente escolher retirar no balcão, informe o endereço da pizzaria e o horário de funcionamento: Rua Abaeté, 123, Centro, São José dos Campos, SP. Horário de funcionamento: 18h às 23h. +11. Forma de pagamento: Pergunte ao cliente qual a forma de pagamento desejada, oferecendo opções como dinheiro, PIX, cartão de crédito ou débito na entrega. +11.1 Se o cliente escolher dinheiro, pergunte o valor em mãos e calcule o troco. O valor informado não pode ser menor que o valor total do pedido. +11.2 Se o cliente escolher PIX, forneça a chave PIX CNPJ: 1234 +11.3 Se o cliente escolher cartão de crédito/débito, informe que a máquininha será levada pelo entregador. +12. Mais alguma coisa? Pergunte ao cliente se ele deseja pedir mais alguma coisa. +12.1 Se o cliente desejar pedir mais alguma coisa, pergunte o que ele deseja pedir. +12.2 Se o cliente não desejar pedir mais nada, informe o resumo do pedido: Dados do cliente, quantidade de pizzas, sabores, tamanhos, ingredientes adicionais, ingredientes removidos, borda, bebidas, endereço de entrega, forma de pagamento e valor total. +12.3 Confirmação do pedido: Pergunte ao cliente se o pedido está correto. +12.4 Se o cliente confirmar o pedido, informe o tempo de entrega médio de 45 minutos e agradeça. +12.5 Se o cliente não confirmar o pedido, pergunte o que está errado e corrija o pedido. +13. Despedida: Agradeça o cliente por entrar em contato. É muito importante que se despeça informando o número do pedido. + +Cardápio de pizzas salgadas (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Muzzarella: Queijo mussarela, tomate e orégano. R$ 25,00 / R$ 30,00 / R$ 35,00 +- Calabresa: Calabresa, cebola e orégano. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Nordestina: Carne de sol, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Frango: Frango desfiado, milho e orégano. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Frango c/ Catupiry: Frango desfiado, catupiry e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- A moda da Casa: Carne de sol, bacon, cebola e orégano. R$ 40,00 / R$ 45,00 / R$ 50,00 +- Presunto: Presunto, queijo mussarela e orégano. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Quatro Estações: Presunto, queijo mussarela, ervilha, milho, palmito e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Mista: Presunto, queijo mussarela, calabresa, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Toscana: Calabresa, bacon, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Portuguesa: Presunto, queijo mussarela, calabresa, ovo, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Dois Queijos: Queijo mussarela, catupiry e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Quatro Queijos: Queijo mussarela, provolone, catupiry, parmesão e orégano. R$ 40,00 / R$ 45,00 / R$ 50,00 +- Salame: Salame, queijo mussarela e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 +- Atum: Atum, cebola e orégano. R$ 35,00 / R$ 40,00 / R$ 45,00 + +Cardápio de pizzas doces (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Chocolate: Chocolate ao leite e granulado. R$ 30,00 / R$ 35,00 / R$ 40,00 +- Romeu e Julieta: Goiabada e queijo mussarela. R$ 30,00 / R$ 35,00 / R$ 40,00 +- California: Banana, canela e açúcar. R$ 30,00 / R$ 35,00 / R$ 40,00 + +Extras/Adicionais (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Catupiry: R$ 5,00 / R$ 7,00 / R$ 9,00 + +Bordas (os valores estão separados por tamanho - Broto, Médio e Grande): + +- Chocolate: R$ 5,00 / R$ 7,00 / R$ 9,00 +- Cheddar: R$ 5,00 / R$ 7,00 / R$ 9,00 +- Catupiry: R$ 5,00 / R$ 7,00 / R$ 9,00 + +Bebidas: + +- Coca-Cola 2L: R$ 10,00 +- Coca-Cola Lata: R$ 8,00 +- Guaraná 2L: R$ 10,00 +- Guaraná Lata: R$ 7,00 +- Água com Gás 500 ml: R$ 5,00 +- Água sem Gás 500 ml: R$ 4,00 +` diff --git a/src/utils/initPrompt.ts b/src/utils/initPrompt.ts new file mode 100755 index 00000000..79f2a3b5 --- /dev/null +++ b/src/utils/initPrompt.ts @@ -0,0 +1,13 @@ +import { promptPizza } from "../prompts/pizzaAgent" + +export function initPrompt(storeName: string, orderCode: string, prompt?: string ): string { + if(prompt){ + return prompt + .replace(/{{[\s]?storeName[\s]?}}/g, storeName) + .replace(/{{[\s]?orderCode[\s]?}}/g, orderCode) + }else{ + return promptPizza + .replace(/{{[\s]?storeName[\s]?}}/g, storeName) + .replace(/{{[\s]?orderCode[\s]?}}/g, orderCode) + } +} diff --git a/src/utils/server-up.ts b/src/utils/server-up.ts old mode 100644 new mode 100755 index e06caea7..5fef62fb --- a/src/utils/server-up.ts +++ b/src/utils/server-up.ts @@ -1,29 +1,29 @@ -import { Express } from 'express'; -import { readFileSync } from 'fs'; -import * as http from 'http'; -import * as https from 'https'; - -import { configService, SslConf } from '../config/env.config'; - -export class ServerUP { - static #app: Express; - - static set app(e: Express) { - this.#app = e; - } - - static get https() { - const { FULLCHAIN, PRIVKEY } = configService.get('SSL_CONF'); - return https.createServer( - { - cert: readFileSync(FULLCHAIN), - key: readFileSync(PRIVKEY), - }, - ServerUP.#app, - ); - } - - static get http() { - return http.createServer(ServerUP.#app); - } -} +import { Express } from 'express'; +import { readFileSync } from 'fs'; +import * as http from 'http'; +import * as https from 'https'; + +import { configService, SslConf } from '../config/env.config'; + +export class ServerUP { + static #app: Express; + + static set app(e: Express) { + this.#app = e; + } + + static get https() { + const { FULLCHAIN, PRIVKEY } = configService.get('SSL_CONF'); + return https.createServer( + { + cert: readFileSync(FULLCHAIN), + key: readFileSync(PRIVKEY), + }, + ServerUP.#app, + ); + } + + static get http() { + return http.createServer(ServerUP.#app); + } +} diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts old mode 100644 new mode 100755 index 995ac92a..4c290550 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -1,107 +1,112 @@ -import { - AuthenticationCreds, - AuthenticationState, - BufferJSON, - initAuthCreds, - proto, - SignalDataTypeMap, -} from '@whiskeysockets/baileys'; - -import { configService, Database } from '../config/env.config'; -import { Logger } from '../config/logger.config'; -import { dbserver } from '../libs/db.connect'; - -export async function useMultiFileAuthStateDb( - coll: string, -): Promise<{ state: AuthenticationState; saveCreds: () => Promise }> { - const logger = new Logger(useMultiFileAuthStateDb.name); - - const client = dbserver.getClient(); - - const collection = client - .db(configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances') - .collection(coll); - - const writeData = async (data: any, key: string): Promise => { - try { - await client.connect(); - let msgParsed = JSON.parse(JSON.stringify(data, BufferJSON.replacer)); - if (Array.isArray(msgParsed)) { - msgParsed = { - _id: key, - content_array: msgParsed, - }; - } - return await collection.replaceOne({ _id: key }, msgParsed, { - upsert: true, - }); - } catch (error) { - logger.error(error); - } - }; - - const readData = async (key: string): Promise => { - try { - await client.connect(); - let data = (await collection.findOne({ _id: key })) as any; - if (data?.content_array) { - data = data.content_array; - } - const creds = JSON.stringify(data); - return JSON.parse(creds, BufferJSON.reviver); - } catch (error) { - logger.error(error); - } - }; - - const removeData = async (key: string) => { - try { - await client.connect(); - return await collection.deleteOne({ _id: key }); - } catch (error) { - logger.error(error); - } - }; - - const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); - - return { - state: { - creds, - keys: { - get: async (type, ids: string[]) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const data: { [_: string]: SignalDataTypeMap[type] } = {}; - await Promise.all( - ids.map(async (id) => { - let value = await readData(`${type}-${id}`); - if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.fromObject(value); - } - - data[id] = value; - }), - ); - - return data; - }, - set: async (data: any) => { - const tasks: Promise[] = []; - for (const category in data) { - for (const id in data[category]) { - const value = data[category][id]; - const key = `${category}-${id}`; - tasks.push(value ? writeData(value, key) : removeData(key)); - } - } - - await Promise.all(tasks); - }, - }, - }, - saveCreds: async () => { - return await writeData(creds, 'creds'); - }, - }; -} +import { + AuthenticationCreds, + AuthenticationState, + BufferJSON, + initAuthCreds, + proto, + SignalDataTypeMap, +} from '@whiskeysockets/baileys'; + +import { configService, Database } from '../config/env.config'; +import { Logger } from '../config/logger.config'; +import { dbserver } from '../libs/db.connect'; + +export async function useMultiFileAuthStateDb( + coll: string, +): Promise<{ state: AuthenticationState; saveCreds: () => Promise }> { + const logger = new Logger(useMultiFileAuthStateDb.name); + + const client = dbserver.getClient(); + + const collection = client + .db( + configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + + configService.get('DATABASE').CONNECTION.DB_PREFIX_FINAL_NAME + ) + .collection(coll); + + const writeData = async (data: any, key: string): Promise => { + try { + await client.connect(); + let msgParsed = JSON.parse(JSON.stringify(data, BufferJSON.replacer)); + if (Array.isArray(msgParsed)) { + msgParsed = { + _id: key, + content_array: msgParsed, + }; + } + return await collection.replaceOne({ _id: key }, msgParsed, { + //return await collection.replaceOne({ _id: key }, JSON.parse(JSON.stringify(data, BufferJSON.replacer)), { + upsert: true, + }); + } catch (error) { + logger.error(error); + } + }; + + const readData = async (key: string): Promise => { + try { + await client.connect(); + let data = (await collection.findOne({ _id: key })) as any; + if (data?.content_array) { + data = data.content_array; + } + //const data = await collection.findOne({ _id: key }); + const creds = JSON.stringify(data); + return JSON.parse(creds, BufferJSON.reviver); + } catch (error) { + logger.error(error); + } + }; + + const removeData = async (key: string) => { + try { + await client.connect(); + return await collection.deleteOne({ _id: key }); + } catch (error) { + logger.error(error); + } + }; + + const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); + + return { + state: { + creds, + keys: { + get: async (type, ids: string[]) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const data: { [_: string]: SignalDataTypeMap[type] } = {}; + await Promise.all( + ids.map(async (id) => { + let value = await readData(`${type}-${id}`); + if (type === 'app-state-sync-key' && value) { + value = proto.Message.AppStateSyncKeyData.fromObject(value); + } + + data[id] = value; + }), + ); + + return data; + }, + set: async (data: any) => { + const tasks: Promise[] = []; + for (const category in data) { + for (const id in data[category]) { + const value = data[category][id]; + const key = `${category}-${id}`; + tasks.push(value ? writeData(value, key) : removeData(key)); + } + } + + await Promise.all(tasks); + }, + }, + }, + saveCreds: async () => { + return writeData(creds, 'creds'); + }, + }; +} diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts old mode 100644 new mode 100755 index 8e685b54..5f11161f --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -1,84 +1,84 @@ -import { - AuthenticationCreds, - AuthenticationState, - initAuthCreds, - proto, - SignalDataTypeMap, -} from '@whiskeysockets/baileys'; - -import { Logger } from '../config/logger.config'; -import { RedisCache } from '../libs/redis.client'; - -export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ - state: AuthenticationState; - saveCreds: () => Promise; -}> { - const logger = new Logger(useMultiFileAuthStateRedisDb.name); - - const writeData = async (data: any, key: string): Promise => { - try { - return await cache.writeData(key, data); - } catch (error) { - return logger.error({ localError: 'writeData', error }); - } - }; - - const readData = async (key: string): Promise => { - try { - return await cache.readData(key); - } catch (error) { - logger.error({ readData: 'writeData', error }); - return; - } - }; - - const removeData = async (key: string) => { - try { - return await cache.removeData(key); - } catch (error) { - logger.error({ readData: 'removeData', error }); - } - }; - - const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); - - return { - state: { - creds, - keys: { - get: async (type, ids: string[]) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const data: { [_: string]: SignalDataTypeMap[type] } = {}; - await Promise.all( - ids.map(async (id) => { - let value = await readData(`${type}-${id}`); - if (type === 'app-state-sync-key' && value) { - value = proto.Message.AppStateSyncKeyData.fromObject(value); - } - - data[id] = value; - }), - ); - - return data; - }, - set: async (data: any) => { - const tasks: Promise[] = []; - for (const category in data) { - for (const id in data[category]) { - const value = data[category][id]; - const key = `${category}-${id}`; - tasks.push(value ? await writeData(value, key) : await removeData(key)); - } - } - - await Promise.all(tasks); - }, - }, - }, - saveCreds: async () => { - return await writeData(creds, 'creds'); - }, - }; -} +import { + AuthenticationCreds, + AuthenticationState, + initAuthCreds, + proto, + SignalDataTypeMap, +} from '@whiskeysockets/baileys'; + +import { Logger } from '../config/logger.config'; +import { RedisCache } from '../libs/redis.client'; + +export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ + state: AuthenticationState; + saveCreds: () => Promise; +}> { + const logger = new Logger(useMultiFileAuthStateRedisDb.name); + + const writeData = async (data: any, key: string): Promise => { + try { + return await cache.writeData(key, data); + } catch (error) { + return logger.error({ localError: 'writeData', error }); + } + }; + + const readData = async (key: string): Promise => { + try { + return await cache.readData(key); + } catch (error) { + logger.error({ readData: 'writeData', error }); + return; + } + }; + + const removeData = async (key: string) => { + try { + return await cache.removeData(key); + } catch (error) { + logger.error({ readData: 'removeData', error }); + } + }; + + const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); + + return { + state: { + creds, + keys: { + get: async (type, ids: string[]) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const data: { [_: string]: SignalDataTypeMap[type] } = {}; + await Promise.all( + ids.map(async (id) => { + let value = await readData(`${type}-${id}`); + if (type === 'app-state-sync-key' && value) { + value = proto.Message.AppStateSyncKeyData.fromObject(value); + } + + data[id] = value; + }), + ); + + return data; + }, + set: async (data: any) => { + const tasks: Promise[] = []; + for (const category in data) { + for (const id in data[category]) { + const value = data[category][id]; + const key = `${category}-${id}`; + tasks.push(value ? await writeData(value, key) : await removeData(key)); + } + } + + await Promise.all(tasks); + }, + }, + }, + saveCreds: async () => { + return await writeData(creds, 'creds'); + }, + }; +} diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts old mode 100644 new mode 100755 index 9781e18c..ee19c1fd --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -1,1095 +1,1140 @@ -import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; -import { v4 } from 'uuid'; - -const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { - const properties = {}; - propertyNames.forEach( - (property) => - (properties[property] = { - minLength: 1, - description: `The "${property}" cannot be empty`, - }), - ); - return { - if: { - propertyNames: { - enum: [...propertyNames], - }, - }, - then: { properties }, - }; -}; - -// Instance Schema -export const instanceNameSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - instanceName: { type: 'string' }, - webhook: { type: 'string' }, - webhook_by_events: { type: 'boolean' }, - events: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ], - }, - }, - qrcode: { type: 'boolean', enum: [true, false] }, - number: { type: 'string', pattern: '^\\d+[\\.@\\w-]+' }, - token: { type: 'string' }, - }, - ...isNotEmpty('instanceName'), -}; - -export const oldTokenSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - oldToken: { type: 'string' }, - }, - required: ['oldToken'], - ...isNotEmpty('oldToken'), -}; - -const quotedOptionsSchema: JSONSchema7 = { - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id'], - ...isNotEmpty('id'), - }, - message: { type: 'object' }, - }, -}; - -const mentionsOptionsSchema: JSONSchema7 = { - properties: { - everyOne: { type: 'boolean', enum: [true, false] }, - mentioned: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - pattern: '^\\d+', - description: '"mentioned" must be an array of numeric strings', - }, - }, - }, -}; - -// Send Message Schema -const optionsSchema: JSONSchema7 = { - properties: { - delay: { - type: 'integer', - description: 'Enter a value in milliseconds', - }, - presence: { - type: 'string', - enum: ['unavailable', 'available', 'composing', 'recording', 'paused'], - }, - quoted: { ...quotedOptionsSchema }, - mentions: { ...mentionsOptionsSchema }, - }, -}; - -const numberDefinition: JSONSchema7Definition = { - type: 'string', - description: 'Invalid format', -}; - -export const textMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - textMessage: { - type: 'object', - properties: { - text: { type: 'string' }, - }, - required: ['text'], - ...isNotEmpty('text'), - }, - }, - required: ['textMessage', 'number'], -}; - -export const pollMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - pollMessage: { - type: 'object', - properties: { - name: { type: 'string' }, - selectableCount: { type: 'integer', minimum: 0, maximum: 10 }, - values: { - type: 'array', - minItems: 2, - maxItems: 10, - uniqueItems: true, - items: { - type: 'string', - }, - }, - }, - required: ['name', 'selectableCount', 'values'], - ...isNotEmpty('name', 'selectableCount', 'values'), - }, - }, - required: ['pollMessage', 'number'], -}; - -export const statusMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - statusMessage: { - type: 'object', - properties: { - type: { type: 'string', enum: ['text', 'image', 'audio', 'video'] }, - content: { type: 'string' }, - caption: { type: 'string' }, - backgroundColor: { type: 'string' }, - font: { type: 'integer', minimum: 0, maximum: 5 }, - statusJidList: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - pattern: '^\\d+', - description: '"statusJidList" must be an array of numeric strings', - }, - }, - allContacts: { type: 'boolean', enum: [true, false] }, - }, - required: ['type', 'content'], - ...isNotEmpty('type', 'content'), - }, - }, - required: ['statusMessage'], -}; - -export const mediaMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - mediaMessage: { - type: 'object', - properties: { - mediatype: { type: 'string', enum: ['image', 'document', 'video', 'audio'] }, - media: { type: 'string' }, - fileName: { type: 'string' }, - caption: { type: 'string' }, - }, - required: ['mediatype', 'media'], - ...isNotEmpty('fileName', 'caption', 'media'), - }, - }, - required: ['mediaMessage', 'number'], -}; - -export const stickerMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - stickerMessage: { - type: 'object', - properties: { - image: { type: 'string' }, - }, - required: ['image'], - ...isNotEmpty('image'), - }, - }, - required: ['stickerMessage', 'number'], -}; - -export const audioMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - audioMessage: { - type: 'object', - properties: { - audio: { type: 'string' }, - }, - required: ['audio'], - ...isNotEmpty('audio'), - }, - }, - required: ['audioMessage', 'number'], -}; - -export const buttonMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - buttonMessage: { - type: 'object', - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - footerText: { type: 'string' }, - buttons: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'object', - properties: { - buttonText: { type: 'string' }, - buttonId: { type: 'string' }, - }, - required: ['buttonText', 'buttonId'], - ...isNotEmpty('buttonText', 'buttonId'), - }, - }, - mediaMessage: { - type: 'object', - properties: { - media: { type: 'string' }, - fileName: { type: 'string' }, - mediatype: { type: 'string', enum: ['image', 'document', 'video'] }, - }, - required: ['media', 'mediatype'], - ...isNotEmpty('media', 'fileName'), - }, - }, - required: ['title', 'buttons'], - ...isNotEmpty('title', 'description'), - }, - }, - required: ['number', 'buttonMessage'], -}; - -export const locationMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - locationMessage: { - type: 'object', - properties: { - latitude: { type: 'number' }, - longitude: { type: 'number' }, - name: { type: 'string' }, - address: { type: 'string' }, - }, - required: ['latitude', 'longitude'], - ...isNotEmpty('name', 'addresss'), - }, - }, - required: ['number', 'locationMessage'], -}; - -export const listMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - listMessage: { - type: 'object', - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - footerText: { type: 'string' }, - buttonText: { type: 'string' }, - sections: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'object', - properties: { - title: { type: 'string' }, - rows: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'object', - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - rowId: { type: 'string' }, - }, - required: ['title', 'description', 'rowId'], - ...isNotEmpty('title', 'description', 'rowId'), - }, - }, - }, - required: ['title', 'rows'], - ...isNotEmpty('title'), - }, - }, - }, - required: ['title', 'description', 'buttonText', 'sections'], - ...isNotEmpty('title', 'description', 'buttonText', 'footerText'), - }, - }, - required: ['number', 'listMessage'], -}; - -export const contactMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { ...numberDefinition }, - options: { ...optionsSchema }, - contactMessage: { - type: 'array', - items: { - type: 'object', - properties: { - fullName: { type: 'string' }, - wuid: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"wuid" must be a numeric string', - }, - phoneNumber: { type: 'string', minLength: 10 }, - organization: { type: 'string' }, - email: { type: 'string' }, - url: { type: 'string' }, - }, - required: ['fullName', 'phoneNumber'], - ...isNotEmpty('fullName'), - }, - minItems: 1, - uniqueItems: true, - }, - }, - required: ['number', 'contactMessage'], -}; - -export const reactionMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - reactionMessage: { - type: 'object', - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id', 'remoteJid', 'fromMe'], - ...isNotEmpty('id', 'remoteJid'), - }, - reaction: { type: 'string' }, - }, - required: ['key', 'reaction'], - ...isNotEmpty('reaction'), - }, - }, - required: ['reactionMessage'], -}; - -// Chat Schema -export const whatsappNumberSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - numbers: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - description: '"numbers" must be an array of numeric strings', - }, - }, - }, -}; - -export const readMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - read_messages: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - properties: { - id: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - remoteJid: { type: 'string' }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid'), - }, - }, - }, - required: ['read_messages'], -}; - -export const privacySettingsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - privacySettings: { - type: 'object', - properties: { - readreceipts: { type: 'string', enum: ['all', 'none'] }, - profile: { - type: 'string', - enum: ['all', 'contacts', 'contact_blacklist', 'none'], - }, - status: { - type: 'string', - enum: ['all', 'contacts', 'contact_blacklist', 'none'], - }, - online: { type: 'string', enum: ['all', 'match_last_seen'] }, - last: { type: 'string', enum: ['all', 'contacts', 'contact_blacklist', 'none'] }, - groupadd: { - type: 'string', - enum: ['all', 'contacts', 'contact_blacklist', 'none'], - }, - }, - required: ['readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'], - ...isNotEmpty('readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'), - }, - }, - required: ['privacySettings'], -}; - -export const archiveChatSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - chat: { type: 'string' }, - lastMessage: { - type: 'object', - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - remoteJid: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid'), - }, - messageTimestamp: { type: 'integer', minLength: 1 }, - }, - required: ['key'], - ...isNotEmpty('messageTimestamp'), - }, - archive: { type: 'boolean', enum: [true, false] }, - }, - required: ['archive'], -}; - -export const deleteMessageSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - id: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - remoteJid: { type: 'string' }, - participant: { type: 'string' }, - }, - required: ['id', 'fromMe', 'remoteJid'], - ...isNotEmpty('id', 'remoteJid', 'participant'), -}; - -export const contactValidateSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - where: { - type: 'object', - properties: { - _id: { type: 'string', minLength: 1 }, - pushName: { type: 'string', minLength: 1 }, - id: { type: 'string', minLength: 1 }, - }, - ...isNotEmpty('_id', 'id', 'pushName'), - }, - }, -}; - -export const profileNameSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - name: { type: 'string' }, - }, - ...isNotEmpty('name'), -}; - -export const profileStatusSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - status: { type: 'string' }, - }, - ...isNotEmpty('status'), -}; - -export const profilePictureSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - number: { type: 'string' }, - picture: { type: 'string' }, - }, -}; - -export const profileSchema: JSONSchema7 = { - type: 'object', - properties: { - wuid: { type: 'string' }, - name: { type: 'string' }, - picture: { type: 'string' }, - status: { type: 'string' }, - isBusiness: { type: 'boolean' }, - }, -}; - -export const messageValidateSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - where: { - type: 'object', - properties: { - _id: { type: 'string', minLength: 1 }, - key: { - type: 'object', - if: { - propertyNames: { - enum: ['fromMe', 'remoteJid', 'id'], - }, - }, - then: { - properties: { - remoteJid: { - type: 'string', - minLength: 1, - description: 'The property cannot be empty', - }, - id: { - type: 'string', - minLength: 1, - description: 'The property cannot be empty', - }, - fromMe: { type: 'boolean', enum: [true, false] }, - }, - }, - }, - message: { type: 'object' }, - }, - ...isNotEmpty('_id'), - }, - limit: { type: 'integer' }, - }, -}; - -export const messageUpSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - where: { - type: 'object', - properties: { - _id: { type: 'string' }, - remoteJid: { type: 'string' }, - id: { type: 'string' }, - fromMe: { type: 'boolean', enum: [true, false] }, - participant: { type: 'string' }, - status: { - type: 'string', - enum: ['ERROR', 'PENDING', 'SERVER_ACK', 'DELIVERY_ACK', 'READ', 'PLAYED'], - }, - }, - ...isNotEmpty('_id', 'remoteJid', 'id', 'status'), - }, - limit: { type: 'integer' }, - }, -}; - -// Group Schema -export const createGroupSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - subject: { type: 'string' }, - description: { type: 'string' }, - profilePicture: { type: 'string' }, - promoteParticipants: { type: 'boolean', enum: [true, false] }, - participants: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"participants" must be an array of numeric strings', - }, - }, - }, - required: ['subject', 'participants'], - ...isNotEmpty('subject', 'description', 'profilePicture'), -}; - -export const groupJidSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string', pattern: '^[\\d-]+@g.us$' }, - }, - required: ['groupJid'], - ...isNotEmpty('groupJid'), -}; - -export const getParticipantsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - getParticipants: { type: 'string', enum: ['true', 'false'] }, - }, - required: ['getParticipants'], - ...isNotEmpty('getParticipants'), -}; - -export const groupSendInviteSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - description: { type: 'string' }, - numbers: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"numbers" must be an array of numeric strings', - }, - }, - }, - required: ['groupJid', 'description', 'numbers'], - ...isNotEmpty('groupJid', 'description', 'numbers'), -}; - -export const groupInviteSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' }, - }, - required: ['inviteCode'], - ...isNotEmpty('inviteCode'), -}; - -export const updateParticipantsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - action: { - type: 'string', - enum: ['add', 'remove', 'promote', 'demote'], - }, - participants: { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { - type: 'string', - minLength: 10, - pattern: '\\d+', - description: '"participants" must be an array of numeric strings', - }, - }, - }, - required: ['groupJid', 'action', 'participants'], - ...isNotEmpty('groupJid', 'action'), -}; - -export const updateSettingsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - action: { - type: 'string', - enum: ['announcement', 'not_announcement', 'locked', 'unlocked'], - }, - }, - required: ['groupJid', 'action'], - ...isNotEmpty('groupJid', 'action'), -}; - -export const toggleEphemeralSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - expiration: { - type: 'number', - enum: [0, 86400, 604800, 7776000], - }, - }, - required: ['groupJid', 'expiration'], - ...isNotEmpty('groupJid', 'expiration'), -}; - -export const updateGroupPictureSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - image: { type: 'string' }, - }, - required: ['groupJid', 'image'], - ...isNotEmpty('groupJid', 'image'), -}; - -export const updateGroupSubjectSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - subject: { type: 'string' }, - }, - required: ['groupJid', 'subject'], - ...isNotEmpty('groupJid', 'subject'), -}; - -export const updateGroupDescriptionSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - groupJid: { type: 'string' }, - description: { type: 'string' }, - }, - required: ['groupJid', 'description'], - ...isNotEmpty('groupJid', 'description'), -}; - -export const webhookSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - url: { type: 'string' }, - events: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ], - }, - }, - }, - required: ['url'], - ...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' }, - sign_msg: { type: 'boolean', enum: [true, false] }, - reopen_conversation: { type: 'boolean', enum: [true, false] }, - conversation_pending: { type: 'boolean', enum: [true, false] }, - }, - required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'], - ...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'), -}; - -export const settingsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - reject_call: { type: 'boolean', enum: [true, false] }, - msg_call: { type: 'string' }, - groups_ignore: { type: 'boolean', enum: [true, false] }, - always_online: { type: 'boolean', enum: [true, false] }, - read_messages: { type: 'boolean', enum: [true, false] }, - read_status: { type: 'boolean', enum: [true, false] }, - }, - required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'], - ...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'), -}; - -export const websocketSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - enabled: { type: 'boolean', enum: [true, false] }, - events: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ], - }, - }, - }, - required: ['enabled'], - ...isNotEmpty('enabled'), -}; - -export const rabbitmqSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - enabled: { type: 'boolean', enum: [true, false] }, - events: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ], - }, - }, - }, - required: ['enabled'], - ...isNotEmpty('enabled'), -}; - -export const sqsSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - enabled: { type: 'boolean', enum: [true, false] }, - events: { - type: 'array', - minItems: 0, - items: { - type: 'string', - enum: [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ], - }, - }, - }, - required: ['enabled'], - ...isNotEmpty('enabled'), -}; - -export const typebotSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - enabled: { type: 'boolean', enum: [true, false] }, - url: { type: 'string' }, - typebot: { type: 'string' }, - expire: { type: 'integer' }, - delay_message: { type: 'integer' }, - unknown_message: { type: 'string' }, - listening_from_me: { type: 'boolean', enum: [true, false] }, - }, - 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 = { - $id: v4(), - type: 'object', - properties: { - remoteJid: { type: 'string' }, - status: { type: 'string', enum: ['opened', 'closed', 'paused'] }, - }, - required: ['remoteJid', 'status'], - ...isNotEmpty('remoteJid', 'status'), -}; - -export const typebotStartSchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - remoteJid: { type: 'string' }, - url: { type: 'string' }, - typebot: { type: 'string' }, - }, - required: ['remoteJid', 'url', 'typebot'], - ...isNotEmpty('remoteJid', 'url', 'typebot'), -}; - -export const proxySchema: JSONSchema7 = { - $id: v4(), - type: 'object', - properties: { - enabled: { type: 'boolean', enum: [true, false] }, - proxy: { type: 'string' }, - }, - 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'), -}; +import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; +import { v4 } from 'uuid'; + +const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => { + const properties = {}; + propertyNames.forEach( + (property) => + (properties[property] = { + minLength: 1, + description: `The "${property}" cannot be empty`, + }), + ); + return { + if: { + propertyNames: { + enum: [...propertyNames], + }, + }, + then: { properties }, + }; +}; + +// Instance Schema +export const instanceNameSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + instanceName: { type: 'string' }, + webhook: { type: 'string' }, + webhook_by_events: { type: 'boolean' }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ], + }, + }, + qrcode: { type: 'boolean', enum: [true, false] }, + number: { type: 'string', pattern: '^\\d+[\\.@\\w-]+' }, + token: { type: 'string' }, + }, + ...isNotEmpty('instanceName'), +}; + +export const oldTokenSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + oldToken: { type: 'string' }, + }, + required: ['oldToken'], + ...isNotEmpty('oldToken'), +}; + +const quotedOptionsSchema: JSONSchema7 = { + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id'], + ...isNotEmpty('id'), + }, + message: { type: 'object' }, + }, +}; + +const mentionsOptionsSchema: JSONSchema7 = { + properties: { + everyOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, + }, +}; + +// Send Message Schema +const optionsSchema: JSONSchema7 = { + properties: { + delay: { + type: 'integer', + description: 'Enter a value in milliseconds', + }, + presence: { + type: 'string', + enum: ['unavailable', 'available', 'composing', 'recording', 'paused'], + }, + quoted: { ...quotedOptionsSchema }, + mentions: { ...mentionsOptionsSchema }, + }, +}; + +const numberDefinition: JSONSchema7Definition = { + type: 'string', + description: 'Invalid format', +}; + +export const textMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + textMessage: { + type: 'object', + properties: { + text: { type: 'string' }, + }, + required: ['text'], + ...isNotEmpty('text'), + }, + }, + required: ['textMessage', 'number'], +}; + +export const pollMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + pollMessage: { + type: 'object', + properties: { + name: { type: 'string' }, + selectableCount: { type: 'integer', minimum: 0, maximum: 10 }, + values: { + type: 'array', + minItems: 2, + maxItems: 10, + uniqueItems: true, + items: { + type: 'string', + }, + }, + }, + required: ['name', 'selectableCount', 'values'], + ...isNotEmpty('name', 'selectableCount', 'values'), + }, + }, + required: ['pollMessage', 'number'], +}; + +export const statusMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + statusMessage: { + type: 'object', + properties: { + type: { type: 'string', enum: ['text', 'image', 'audio', 'video'] }, + content: { type: 'string' }, + caption: { type: 'string' }, + backgroundColor: { type: 'string' }, + font: { type: 'integer', minimum: 0, maximum: 5 }, + statusJidList: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"statusJidList" must be an array of numeric strings', + }, + }, + allContacts: { type: 'boolean', enum: [true, false] }, + }, + required: ['type', 'content'], + ...isNotEmpty('type', 'content'), + }, + }, + required: ['statusMessage'], +}; + +export const mediaMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + mediaMessage: { + type: 'object', + properties: { + mediatype: { type: 'string', enum: ['image', 'document', 'video', 'audio'] }, + media: { type: 'string' }, + fileName: { type: 'string' }, + caption: { type: 'string' }, + }, + required: ['mediatype', 'media'], + ...isNotEmpty('fileName', 'caption', 'media'), + }, + }, + required: ['mediaMessage', 'number'], +}; + +export const stickerMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + stickerMessage: { + type: 'object', + properties: { + image: { type: 'string' }, + }, + required: ['image'], + ...isNotEmpty('image'), + }, + }, + required: ['stickerMessage', 'number'], +}; + +export const audioMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + audioMessage: { + type: 'object', + properties: { + audio: { type: 'string' }, + }, + required: ['audio'], + ...isNotEmpty('audio'), + }, + }, + required: ['audioMessage', 'number'], +}; + +export const buttonMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + buttonMessage: { + type: 'object', + properties: { + title: { type: 'string' }, + description: { type: 'string' }, + footerText: { type: 'string' }, + buttons: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'object', + properties: { + buttonText: { type: 'string' }, + buttonId: { type: 'string' }, + }, + required: ['buttonText', 'buttonId'], + ...isNotEmpty('buttonText', 'buttonId'), + }, + }, + mediaMessage: { + type: 'object', + properties: { + media: { type: 'string' }, + fileName: { type: 'string' }, + mediatype: { type: 'string', enum: ['image', 'document', 'video'] }, + }, + required: ['media', 'mediatype'], + ...isNotEmpty('media', 'fileName'), + }, + }, + required: ['title', 'buttons'], + ...isNotEmpty('title', 'description'), + }, + }, + required: ['number', 'buttonMessage'], +}; + +export const locationMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + locationMessage: { + type: 'object', + properties: { + latitude: { type: 'number' }, + longitude: { type: 'number' }, + name: { type: 'string' }, + address: { type: 'string' }, + }, + required: ['latitude', 'longitude'], + ...isNotEmpty('name', 'addresss'), + }, + }, + required: ['number', 'locationMessage'], +}; + +export const listMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + listMessage: { + type: 'object', + properties: { + title: { type: 'string' }, + description: { type: 'string' }, + footerText: { type: 'string' }, + buttonText: { type: 'string' }, + sections: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'object', + properties: { + title: { type: 'string' }, + rows: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'object', + properties: { + title: { type: 'string' }, + description: { type: 'string' }, + rowId: { type: 'string' }, + }, + required: ['title', 'description', 'rowId'], + ...isNotEmpty('title', 'description', 'rowId'), + }, + }, + }, + required: ['title', 'rows'], + ...isNotEmpty('title'), + }, + }, + }, + required: ['title', 'description', 'buttonText', 'sections'], + ...isNotEmpty('title', 'description', 'buttonText', 'footerText'), + }, + }, + required: ['number', 'listMessage'], +}; + +export const contactMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { ...numberDefinition }, + options: { ...optionsSchema }, + contactMessage: { + type: 'array', + items: { + type: 'object', + properties: { + fullName: { type: 'string' }, + wuid: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"wuid" must be a numeric string', + }, + phoneNumber: { type: 'string', minLength: 10 }, + organization: { type: 'string' }, + email: { type: 'string' }, + url: { type: 'string' }, + }, + required: ['fullName', 'phoneNumber'], + ...isNotEmpty('fullName'), + }, + minItems: 1, + uniqueItems: true, + }, + }, + required: ['number', 'contactMessage'], +}; + +export const reactionMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + reactionMessage: { + type: 'object', + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id', 'remoteJid', 'fromMe'], + ...isNotEmpty('id', 'remoteJid'), + }, + reaction: { type: 'string' }, + }, + required: ['key', 'reaction'], + ...isNotEmpty('reaction'), + }, + }, + required: ['reactionMessage'], +}; + +// Chat Schema +export const whatsappNumberSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + numbers: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + description: '"numbers" must be an array of numeric strings', + }, + }, + }, +}; + +export const readMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + read_messages: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + properties: { + id: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + remoteJid: { type: 'string' }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid'), + }, + }, + }, + required: ['read_messages'], +}; + +export const privacySettingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + privacySettings: { + type: 'object', + properties: { + readreceipts: { type: 'string', enum: ['all', 'none'] }, + profile: { + type: 'string', + enum: ['all', 'contacts', 'contact_blacklist', 'none'], + }, + status: { + type: 'string', + enum: ['all', 'contacts', 'contact_blacklist', 'none'], + }, + online: { type: 'string', enum: ['all', 'match_last_seen'] }, + last: { type: 'string', enum: ['all', 'contacts', 'contact_blacklist', 'none'] }, + groupadd: { + type: 'string', + enum: ['all', 'contacts', 'contact_blacklist', 'none'], + }, + }, + required: ['readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'], + ...isNotEmpty('readreceipts', 'profile', 'status', 'online', 'last', 'groupadd'), + }, + }, + required: ['privacySettings'], +}; + +export const archiveChatSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + chat: { type: 'string' }, + lastMessage: { + type: 'object', + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + remoteJid: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid'), + }, + messageTimestamp: { type: 'integer', minLength: 1 }, + }, + required: ['key'], + ...isNotEmpty('messageTimestamp'), + }, + archive: { type: 'boolean', enum: [true, false] }, + }, + required: ['archive'], +}; + +export const deleteMessageSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + id: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + remoteJid: { type: 'string' }, + participant: { type: 'string' }, + }, + required: ['id', 'fromMe', 'remoteJid'], + ...isNotEmpty('id', 'remoteJid', 'participant'), +}; + +export const contactValidateSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + where: { + type: 'object', + properties: { + _id: { type: 'string', minLength: 1 }, + pushName: { type: 'string', minLength: 1 }, + id: { type: 'string', minLength: 1 }, + }, + ...isNotEmpty('_id', 'id', 'pushName'), + }, + }, +}; + +export const profileNameSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + name: { type: 'string' }, + }, + ...isNotEmpty('name'), +}; + +export const profileStatusSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + status: { type: 'string' }, + }, + ...isNotEmpty('status'), +}; + +export const profilePictureSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + number: { type: 'string' }, + picture: { type: 'string' }, + }, +}; + +export const profileSchema: JSONSchema7 = { + type: 'object', + properties: { + wuid: { type: 'string' }, + name: { type: 'string' }, + picture: { type: 'string' }, + status: { type: 'string' }, + isBusiness: { type: 'boolean' }, + }, +}; + +export const messageValidateSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + where: { + type: 'object', + properties: { + _id: { type: 'string', minLength: 1 }, + key: { + type: 'object', + if: { + propertyNames: { + enum: ['fromMe', 'remoteJid', 'id'], + }, + }, + then: { + properties: { + remoteJid: { + type: 'string', + minLength: 1, + description: 'The property cannot be empty', + }, + id: { + type: 'string', + minLength: 1, + description: 'The property cannot be empty', + }, + fromMe: { type: 'boolean', enum: [true, false] }, + }, + }, + }, + message: { type: 'object' }, + }, + ...isNotEmpty('_id'), + }, + limit: { type: 'integer' }, + }, +}; + +export const messageUpSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + where: { + type: 'object', + properties: { + _id: { type: 'string' }, + remoteJid: { type: 'string' }, + id: { type: 'string' }, + fromMe: { type: 'boolean', enum: [true, false] }, + participant: { type: 'string' }, + status: { + type: 'string', + enum: ['ERROR', 'PENDING', 'SERVER_ACK', 'DELIVERY_ACK', 'READ', 'PLAYED'], + }, + }, + ...isNotEmpty('_id', 'remoteJid', 'id', 'status'), + }, + limit: { type: 'integer' }, + }, +}; + +// Group Schema +export const createGroupSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + subject: { type: 'string' }, + description: { type: 'string' }, + profilePicture: { type: 'string' }, + promoteParticipants: { type: 'boolean', enum: [true, false] }, + participants: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"participants" must be an array of numeric strings', + }, + }, + }, + required: ['subject', 'participants'], + ...isNotEmpty('subject', 'description', 'profilePicture'), +}; + +export const groupJidSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string', pattern: '^[\\d-]+@g.us$' }, + }, + required: ['groupJid'], + ...isNotEmpty('groupJid'), +}; + +export const getParticipantsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + getParticipants: { type: 'string', enum: ['true', 'false'] }, + }, + required: ['getParticipants'], + ...isNotEmpty('getParticipants'), +}; + +export const groupSendInviteSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + description: { type: 'string' }, + numbers: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"numbers" must be an array of numeric strings', + }, + }, + }, + required: ['groupJid', 'description', 'numbers'], + ...isNotEmpty('groupJid', 'description', 'numbers'), +}; + +export const groupInviteSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' }, + }, + required: ['inviteCode'], + ...isNotEmpty('inviteCode'), +}; + +export const updateParticipantsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + action: { + type: 'string', + enum: ['add', 'remove', 'promote', 'demote'], + }, + participants: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + minLength: 10, + pattern: '\\d+', + description: '"participants" must be an array of numeric strings', + }, + }, + }, + required: ['groupJid', 'action', 'participants'], + ...isNotEmpty('groupJid', 'action'), +}; + +export const updateSettingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + action: { + type: 'string', + enum: ['announcement', 'not_announcement', 'locked', 'unlocked'], + }, + }, + required: ['groupJid', 'action'], + ...isNotEmpty('groupJid', 'action'), +}; + +export const toggleEphemeralSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + expiration: { + type: 'number', + enum: [0, 86400, 604800, 7776000], + }, + }, + required: ['groupJid', 'expiration'], + ...isNotEmpty('groupJid', 'expiration'), +}; + +export const updateGroupPictureSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + image: { type: 'string' }, + }, + required: ['groupJid', 'image'], + ...isNotEmpty('groupJid', 'image'), +}; + +export const updateGroupSubjectSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + subject: { type: 'string' }, + }, + required: ['groupJid', 'subject'], + ...isNotEmpty('groupJid', 'subject'), +}; + +export const updateGroupDescriptionSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + groupJid: { type: 'string' }, + description: { type: 'string' }, + }, + required: ['groupJid', 'description'], + ...isNotEmpty('groupJid', 'description'), +}; + +export const webhookSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + url: { type: 'string' }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ], + }, + }, + }, + required: ['url'], + ...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' }, + sign_msg: { type: 'boolean', enum: [true, false] }, + reopen_conversation: { type: 'boolean', enum: [true, false] }, + conversation_pending: { type: 'boolean', enum: [true, false] }, + }, + required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'], + ...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'), +}; + +export const settingsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + reject_call: { type: 'boolean', enum: [true, false] }, + msg_call: { type: 'string' }, + groups_ignore: { type: 'boolean', enum: [true, false] }, + always_online: { type: 'boolean', enum: [true, false] }, + read_messages: { type: 'boolean', enum: [true, false] }, + read_status: { type: 'boolean', enum: [true, false] }, + }, + required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'], + ...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status'), +}; + +export const websocketSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ], + }, + }, + }, + required: ['enabled'], + ...isNotEmpty('enabled'), +}; + +export const rabbitmqSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ], + }, + }, + }, + required: ['enabled'], + ...isNotEmpty('enabled'), +}; + +export const openaiSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + chave: { type: 'string' }, + enabled: { type: 'boolean', enum: [true, false] }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ], + }, + }, + }, + required: ['enabled'], + ...isNotEmpty('enabled'), +}; + + +export const sqsSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + events: { + type: 'array', + minItems: 0, + items: { + type: 'string', + enum: [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ], + }, + }, + }, + required: ['enabled'], + ...isNotEmpty('enabled'), +}; + +export const typebotSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + url: { type: 'string' }, + typebot: { type: 'string' }, + expire: { type: 'integer' }, + delay_message: { type: 'integer' }, + unknown_message: { type: 'string' }, + listening_from_me: { type: 'boolean', enum: [true, false] }, + }, + 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 = { + $id: v4(), + type: 'object', + properties: { + remoteJid: { type: 'string' }, + status: { type: 'string', enum: ['opened', 'closed', 'paused'] }, + }, + required: ['remoteJid', 'status'], + ...isNotEmpty('remoteJid', 'status'), +}; + +export const typebotStartSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + remoteJid: { type: 'string' }, + url: { type: 'string' }, + typebot: { type: 'string' }, + }, + required: ['remoteJid', 'url', 'typebot'], + ...isNotEmpty('remoteJid', 'url', 'typebot'), +}; + +export const proxySchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + enabled: { type: 'boolean', enum: [true, false] }, + proxy: { type: 'string' }, + }, + 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'), +}; diff --git a/src/whatsapp/abstract/abstract.repository.ts b/src/whatsapp/abstract/abstract.repository.ts old mode 100644 new mode 100755 index a5b7a841..bc17c392 --- a/src/whatsapp/abstract/abstract.repository.ts +++ b/src/whatsapp/abstract/abstract.repository.ts @@ -1,67 +1,67 @@ -import { existsSync, mkdirSync, writeFileSync } from 'fs'; -import { join } from 'path'; - -import { ConfigService, Database } from '../../config/env.config'; -import { ROOT_DIR } from '../../config/path.config'; - -export type IInsert = { insertCount: number }; - -export interface IRepository { - insert(data: any, instanceName: string, saveDb?: boolean): Promise; - update(data: any, instanceName: string, saveDb?: boolean): Promise; - find(query: any): Promise; - delete(query: any, force?: boolean): Promise; - - dbSettings: Database; - readonly storePath: string; -} - -type WriteStore = { - path: string; - fileName: string; - data: U; -}; - -export abstract class Repository implements IRepository { - constructor(configService: ConfigService) { - this.dbSettings = configService.get('DATABASE'); - } - - dbSettings: Database; - readonly storePath = join(ROOT_DIR, 'store'); - - public writeStore = (create: WriteStore) => { - if (!existsSync(create.path)) { - mkdirSync(create.path, { recursive: true }); - } - try { - writeFileSync(join(create.path, create.fileName + '.json'), JSON.stringify({ ...create.data }), { - encoding: 'utf-8', - }); - - return { message: 'create - success' }; - } finally { - create.data = undefined; - } - }; - - // eslint-disable-next-line - public insert(data: any, instanceName: string, saveDb = false): Promise { - throw new Error('Method not implemented.'); - } - - // eslint-disable-next-line - public update(data: any, instanceName: string, saveDb = false): Promise { - throw new Error('Method not implemented.'); - } - - // eslint-disable-next-line - public find(query: any): Promise { - throw new Error('Method not implemented.'); - } - - // eslint-disable-next-line - delete(query: any, force?: boolean): Promise { - throw new Error('Method not implemented.'); - } -} +import { existsSync, mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, Database } from '../../config/env.config'; +import { ROOT_DIR } from '../../config/path.config'; + +export type IInsert = { insertCount: number }; + +export interface IRepository { + insert(data: any, instanceName: string, saveDb?: boolean): Promise; + update(data: any, instanceName: string, saveDb?: boolean): Promise; + find(query: any): Promise; + delete(query: any, force?: boolean): Promise; + + dbSettings: Database; + readonly storePath: string; +} + +type WriteStore = { + path: string; + fileName: string; + data: U; +}; + +export abstract class Repository implements IRepository { + constructor(configService: ConfigService) { + this.dbSettings = configService.get('DATABASE'); + } + + dbSettings: Database; + readonly storePath = join(ROOT_DIR, 'store'); + + public writeStore = (create: WriteStore) => { + if (!existsSync(create.path)) { + mkdirSync(create.path, { recursive: true }); + } + try { + writeFileSync(join(create.path, create.fileName + '.json'), JSON.stringify({ ...create.data }), { + encoding: 'utf-8', + }); + + return { message: 'create - success' }; + } finally { + create.data = undefined; + } + }; + + // eslint-disable-next-line + public insert(data: any, instanceName: string, saveDb = false): Promise { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line + public update(data: any, instanceName: string, saveDb = false): Promise { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line + public find(query: any): Promise { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line + delete(query: any, force?: boolean): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/src/whatsapp/abstract/abstract.router.ts b/src/whatsapp/abstract/abstract.router.ts old mode 100644 new mode 100755 index 7c603880..d7b50c95 --- a/src/whatsapp/abstract/abstract.router.ts +++ b/src/whatsapp/abstract/abstract.router.ts @@ -1,232 +1,232 @@ -import 'express-async-errors'; - -import { Request } from 'express'; -import { JSONSchema7 } from 'json-schema'; -import { validate } from 'jsonschema'; - -import { Logger } from '../../config/logger.config'; -import { BadRequestException } from '../../exceptions'; -import { GetParticipant, GroupInvite } from '../dto/group.dto'; -import { InstanceDto } from '../dto/instance.dto'; - -type DataValidate = { - request: Request; - schema: JSONSchema7; - ClassRef: any; - execute: (instance: InstanceDto, data: T) => Promise; -}; - -const logger = new Logger('Validate'); - -export abstract class RouterBroker { - constructor() {} - public routerPath(path: string, param = true) { - // const route = param ? '/:instanceName/' + path : '/' + path; - let route = '/' + path; - param ? (route += '/:instanceName') : null; - - return route; - } - - public async dataValidate(args: DataValidate) { - const { request, schema, ClassRef, execute } = args; - - const ref = new ClassRef(); - const body = request.body; - const instance = request.params as unknown as InstanceDto; - - if (request?.query && Object.keys(request.query).length > 0) { - Object.assign(instance, request.query); - } - - if (request.originalUrl.includes('/instance/create')) { - Object.assign(instance, body); - } - - Object.assign(ref, body); - - const v = schema ? validate(ref, schema) : { valid: true, errors: [] }; - - if (!v.valid) { - const message: any[] = v.errors.map(({ stack, schema }) => { - let message: string; - if (schema['description']) { - message = schema['description']; - } else { - message = stack.replace('instance.', ''); - } - return message; - // return { - // property: property.replace('instance.', ''), - // message, - // }; - }); - logger.error(message); - throw new BadRequestException(message); - } - - return await execute(instance, ref); - } - - public async groupNoValidate(args: DataValidate) { - const { request, ClassRef, schema, execute } = args; - - const instance = request.params as unknown as InstanceDto; - - const ref = new ClassRef(); - - Object.assign(ref, request.body); - - const v = validate(ref, schema); - - if (!v.valid) { - const message: any[] = v.errors.map(({ property, stack, schema }) => { - let message: string; - if (schema['description']) { - message = schema['description']; - } else { - message = stack.replace('instance.', ''); - } - return { - property: property.replace('instance.', ''), - message, - }; - }); - logger.error([...message]); - throw new BadRequestException(...message); - } - - return await execute(instance, ref); - } - - public async groupValidate(args: DataValidate) { - const { request, ClassRef, schema, execute } = args; - - const instance = request.params as unknown as InstanceDto; - const body = request.body; - - let groupJid = body?.groupJid; - - if (!groupJid) { - if (request.query?.groupJid) { - groupJid = request.query.groupJid; - } else { - throw new BadRequestException('The group id needs to be informed in the query', 'ex: "groupJid=120362@g.us"'); - } - } - - if (!groupJid.endsWith('@g.us')) { - groupJid = groupJid + '@g.us'; - } - - Object.assign(body, { - groupJid: groupJid, - }); - - const ref = new ClassRef(); - - Object.assign(ref, body); - - const v = validate(ref, schema); - - if (!v.valid) { - const message: any[] = v.errors.map(({ property, stack, schema }) => { - let message: string; - if (schema['description']) { - message = schema['description']; - } else { - message = stack.replace('instance.', ''); - } - return { - property: property.replace('instance.', ''), - message, - }; - }); - logger.error([...message]); - throw new BadRequestException(...message); - } - - return await execute(instance, ref); - } - - public async inviteCodeValidate(args: DataValidate) { - const { request, ClassRef, schema, execute } = args; - - const inviteCode = request.query as unknown as GroupInvite; - - if (!inviteCode?.inviteCode) { - throw new BadRequestException( - 'The group invite code id needs to be informed in the query', - 'ex: "inviteCode=F1EX5QZxO181L3TMVP31gY" (Obtained from group join link)', - ); - } - - const instance = request.params as unknown as InstanceDto; - const body = request.body; - - const ref = new ClassRef(); - - Object.assign(body, inviteCode); - Object.assign(ref, body); - - const v = validate(ref, schema); - - if (!v.valid) { - const message: any[] = v.errors.map(({ property, stack, schema }) => { - let message: string; - if (schema['description']) { - message = schema['description']; - } else { - message = stack.replace('instance.', ''); - } - return { - property: property.replace('instance.', ''), - message, - }; - }); - logger.error([...message]); - throw new BadRequestException(...message); - } - - return await execute(instance, ref); - } - - public async getParticipantsValidate(args: DataValidate) { - const { request, ClassRef, schema, execute } = args; - - const getParticipants = request.query as unknown as GetParticipant; - - if (!getParticipants?.getParticipants) { - throw new BadRequestException('The getParticipants needs to be informed in the query'); - } - - const instance = request.params as unknown as InstanceDto; - const body = request.body; - - const ref = new ClassRef(); - - Object.assign(body, getParticipants); - Object.assign(ref, body); - - const v = validate(ref, schema); - - if (!v.valid) { - const message: any[] = v.errors.map(({ property, stack, schema }) => { - let message: string; - if (schema['description']) { - message = schema['description']; - } else { - message = stack.replace('instance.', ''); - } - return { - property: property.replace('instance.', ''), - message, - }; - }); - logger.error([...message]); - throw new BadRequestException(...message); - } - - return await execute(instance, ref); - } -} +import 'express-async-errors'; + +import { Request } from 'express'; +import { JSONSchema7 } from 'json-schema'; +import { validate } from 'jsonschema'; + +import { Logger } from '../../config/logger.config'; +import { BadRequestException } from '../../exceptions'; +import { GetParticipant, GroupInvite } from '../dto/group.dto'; +import { InstanceDto } from '../dto/instance.dto'; + +type DataValidate = { + request: Request; + schema: JSONSchema7; + ClassRef: any; + execute: (instance: InstanceDto, data: T) => Promise; +}; + +const logger = new Logger('Validate'); + +export abstract class RouterBroker { + constructor() {} + public routerPath(path: string, param = true) { + // const route = param ? '/:instanceName/' + path : '/' + path; + let route = '/' + path; + param ? (route += '/:instanceName') : null; + + return route; + } + + public async dataValidate(args: DataValidate) { + const { request, schema, ClassRef, execute } = args; + + const ref = new ClassRef(); + const body = request.body; + const instance = request.params as unknown as InstanceDto; + + if (request?.query && Object.keys(request.query).length > 0) { + Object.assign(instance, request.query); + } + + if (request.originalUrl.includes('/instance/create')) { + Object.assign(instance, body); + } + + Object.assign(ref, body); + + const v = schema ? validate(ref, schema) : { valid: true, errors: [] }; + + if (!v.valid) { + const message: any[] = v.errors.map(({ stack, schema }) => { + let message: string; + if (schema['description']) { + message = schema['description']; + } else { + message = stack.replace('instance.', ''); + } + return message; + // return { + // property: property.replace('instance.', ''), + // message, + // }; + }); + logger.error(message); + throw new BadRequestException(message); + } + + return await execute(instance, ref); + } + + public async groupNoValidate(args: DataValidate) { + const { request, ClassRef, schema, execute } = args; + + const instance = request.params as unknown as InstanceDto; + + const ref = new ClassRef(); + + Object.assign(ref, request.body); + + const v = validate(ref, schema); + + if (!v.valid) { + const message: any[] = v.errors.map(({ property, stack, schema }) => { + let message: string; + if (schema['description']) { + message = schema['description']; + } else { + message = stack.replace('instance.', ''); + } + return { + property: property.replace('instance.', ''), + message, + }; + }); + logger.error([...message]); + throw new BadRequestException(...message); + } + + return await execute(instance, ref); + } + + public async groupValidate(args: DataValidate) { + const { request, ClassRef, schema, execute } = args; + + const instance = request.params as unknown as InstanceDto; + const body = request.body; + + let groupJid = body?.groupJid; + + if (!groupJid) { + if (request.query?.groupJid) { + groupJid = request.query.groupJid; + } else { + throw new BadRequestException('The group id needs to be informed in the query', 'ex: "groupJid=120362@g.us"'); + } + } + + if (!groupJid.endsWith('@g.us')) { + groupJid = groupJid + '@g.us'; + } + + Object.assign(body, { + groupJid: groupJid, + }); + + const ref = new ClassRef(); + + Object.assign(ref, body); + + const v = validate(ref, schema); + + if (!v.valid) { + const message: any[] = v.errors.map(({ property, stack, schema }) => { + let message: string; + if (schema['description']) { + message = schema['description']; + } else { + message = stack.replace('instance.', ''); + } + return { + property: property.replace('instance.', ''), + message, + }; + }); + logger.error([...message]); + throw new BadRequestException(...message); + } + + return await execute(instance, ref); + } + + public async inviteCodeValidate(args: DataValidate) { + const { request, ClassRef, schema, execute } = args; + + const inviteCode = request.query as unknown as GroupInvite; + + if (!inviteCode?.inviteCode) { + throw new BadRequestException( + 'The group invite code id needs to be informed in the query', + 'ex: "inviteCode=F1EX5QZxO181L3TMVP31gY" (Obtained from group join link)', + ); + } + + const instance = request.params as unknown as InstanceDto; + const body = request.body; + + const ref = new ClassRef(); + + Object.assign(body, inviteCode); + Object.assign(ref, body); + + const v = validate(ref, schema); + + if (!v.valid) { + const message: any[] = v.errors.map(({ property, stack, schema }) => { + let message: string; + if (schema['description']) { + message = schema['description']; + } else { + message = stack.replace('instance.', ''); + } + return { + property: property.replace('instance.', ''), + message, + }; + }); + logger.error([...message]); + throw new BadRequestException(...message); + } + + return await execute(instance, ref); + } + + public async getParticipantsValidate(args: DataValidate) { + const { request, ClassRef, schema, execute } = args; + + const getParticipants = request.query as unknown as GetParticipant; + + if (!getParticipants?.getParticipants) { + throw new BadRequestException('The getParticipants needs to be informed in the query'); + } + + const instance = request.params as unknown as InstanceDto; + const body = request.body; + + const ref = new ClassRef(); + + Object.assign(body, getParticipants); + Object.assign(ref, body); + + const v = validate(ref, schema); + + if (!v.valid) { + const message: any[] = v.errors.map(({ property, stack, schema }) => { + let message: string; + if (schema['description']) { + message = schema['description']; + } else { + message = stack.replace('instance.', ''); + } + return { + property: property.replace('instance.', ''), + message, + }; + }); + logger.error([...message]); + throw new BadRequestException(...message); + } + + return await execute(instance, ref); + } +} diff --git a/src/whatsapp/controllers/chamaai.controller.ts b/src/whatsapp/controllers/chamaai.controller.ts old mode 100644 new mode 100755 index e9cafb50..2f21781c --- a/src/whatsapp/controllers/chamaai.controller.ts +++ b/src/whatsapp/controllers/chamaai.controller.ts @@ -1,29 +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); - } -} +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); + } +} diff --git a/src/whatsapp/controllers/chat.controller.ts b/src/whatsapp/controllers/chat.controller.ts old mode 100644 new mode 100755 index 0299841c..06b59ef9 --- a/src/whatsapp/controllers/chat.controller.ts +++ b/src/whatsapp/controllers/chat.controller.ts @@ -1,114 +1,114 @@ -import { Logger } from '../../config/logger.config'; -import { - ArchiveChatDto, - DeleteMessage, - getBase64FromMediaMessageDto, - NumberDto, - PrivacySettingDto, - ProfileNameDto, - ProfilePictureDto, - ProfileStatusDto, - ReadMessageDto, - WhatsAppNumberDto, -} from '../dto/chat.dto'; -import { InstanceDto } from '../dto/instance.dto'; -import { ContactQuery } from '../repository/contact.repository'; -import { MessageQuery } from '../repository/message.repository'; -import { MessageUpQuery } from '../repository/messageUp.repository'; -import { WAMonitoringService } from '../services/monitor.service'; - -const logger = new Logger('ChatController'); - -export class ChatController { - constructor(private readonly waMonitor: WAMonitoringService) {} - - public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) { - logger.verbose('requested whatsappNumber from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].whatsappNumber(data); - } - - public async readMessage({ instanceName }: InstanceDto, data: ReadMessageDto) { - logger.verbose('requested readMessage from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].markMessageAsRead(data); - } - - public async archiveChat({ instanceName }: InstanceDto, data: ArchiveChatDto) { - logger.verbose('requested archiveChat from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].archiveChat(data); - } - - public async deleteMessage({ instanceName }: InstanceDto, data: DeleteMessage) { - logger.verbose('requested deleteMessage from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].deleteMessage(data); - } - - public async fetchProfilePicture({ instanceName }: InstanceDto, data: NumberDto) { - logger.verbose('requested fetchProfilePicture from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].profilePicture(data.number); - } - - public async fetchProfile({ instanceName }: InstanceDto, data: NumberDto) { - logger.verbose('requested fetchProfile from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchProfile(instanceName, data.number); - } - - public async fetchContacts({ instanceName }: InstanceDto, query: ContactQuery) { - logger.verbose('requested fetchContacts from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchContacts(query); - } - - public async getBase64FromMediaMessage({ instanceName }: InstanceDto, data: getBase64FromMediaMessageDto) { - logger.verbose('requested getBase64FromMediaMessage from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data); - } - - public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) { - logger.verbose('requested fetchMessages from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchMessages(query); - } - - public async fetchStatusMessage({ instanceName }: InstanceDto, query: MessageUpQuery) { - logger.verbose('requested fetchStatusMessage from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchStatusMessage(query); - } - - public async fetchChats({ instanceName }: InstanceDto) { - logger.verbose('requested fetchChats from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchChats(); - } - - public async fetchPrivacySettings({ instanceName }: InstanceDto) { - logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings(); - } - - public async updatePrivacySettings({ instanceName }: InstanceDto, data: PrivacySettingDto) { - logger.verbose('requested updatePrivacySettings from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].updatePrivacySettings(data); - } - - public async fetchBusinessProfile({ instanceName }: InstanceDto, data: ProfilePictureDto) { - logger.verbose('requested fetchBusinessProfile from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchBusinessProfile(data.number); - } - - public async updateProfileName({ instanceName }: InstanceDto, data: ProfileNameDto) { - logger.verbose('requested updateProfileName from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].updateProfileName(data.name); - } - - public async updateProfileStatus({ instanceName }: InstanceDto, data: ProfileStatusDto) { - logger.verbose('requested updateProfileStatus from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].updateProfileStatus(data.status); - } - - public async updateProfilePicture({ instanceName }: InstanceDto, data: ProfilePictureDto) { - logger.verbose('requested updateProfilePicture from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].updateProfilePicture(data.picture); - } - - public async removeProfilePicture({ instanceName }: InstanceDto) { - logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].removeProfilePicture(); - } -} +import { Logger } from '../../config/logger.config'; +import { + ArchiveChatDto, + DeleteMessage, + getBase64FromMediaMessageDto, + NumberDto, + PrivacySettingDto, + ProfileNameDto, + ProfilePictureDto, + ProfileStatusDto, + ReadMessageDto, + WhatsAppNumberDto, +} from '../dto/chat.dto'; +import { InstanceDto } from '../dto/instance.dto'; +import { ContactQuery } from '../repository/contact.repository'; +import { MessageQuery } from '../repository/message.repository'; +import { MessageUpQuery } from '../repository/messageUp.repository'; +import { WAMonitoringService } from '../services/monitor.service'; + +const logger = new Logger('ChatController'); + +export class ChatController { + constructor(private readonly waMonitor: WAMonitoringService) {} + + public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) { + logger.verbose('requested whatsappNumber from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].whatsappNumber(data); + } + + public async readMessage({ instanceName }: InstanceDto, data: ReadMessageDto) { + logger.verbose('requested readMessage from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].markMessageAsRead(data); + } + + public async archiveChat({ instanceName }: InstanceDto, data: ArchiveChatDto) { + logger.verbose('requested archiveChat from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].archiveChat(data); + } + + public async deleteMessage({ instanceName }: InstanceDto, data: DeleteMessage) { + logger.verbose('requested deleteMessage from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].deleteMessage(data); + } + + public async fetchProfilePicture({ instanceName }: InstanceDto, data: NumberDto) { + logger.verbose('requested fetchProfilePicture from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].profilePicture(data.number); + } + + public async fetchProfile({ instanceName }: InstanceDto, data: NumberDto) { + logger.verbose('requested fetchProfile from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchProfile(instanceName, data.number); + } + + public async fetchContacts({ instanceName }: InstanceDto, query: ContactQuery) { + logger.verbose('requested fetchContacts from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchContacts(query); + } + + public async getBase64FromMediaMessage({ instanceName }: InstanceDto, data: getBase64FromMediaMessageDto) { + logger.verbose('requested getBase64FromMediaMessage from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data); + } + + public async fetchMessages({ instanceName }: InstanceDto, query: MessageQuery) { + logger.verbose('requested fetchMessages from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchMessages(query); + } + + public async fetchStatusMessage({ instanceName }: InstanceDto, query: MessageUpQuery) { + logger.verbose('requested fetchStatusMessage from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchStatusMessage(query); + } + + public async fetchChats({ instanceName }: InstanceDto) { + logger.verbose('requested fetchChats from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchChats(); + } + + public async fetchPrivacySettings({ instanceName }: InstanceDto) { + logger.verbose('requested fetchPrivacySettings from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings(); + } + + public async updatePrivacySettings({ instanceName }: InstanceDto, data: PrivacySettingDto) { + logger.verbose('requested updatePrivacySettings from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].updatePrivacySettings(data); + } + + public async fetchBusinessProfile({ instanceName }: InstanceDto, data: ProfilePictureDto) { + logger.verbose('requested fetchBusinessProfile from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchBusinessProfile(data.number); + } + + public async updateProfileName({ instanceName }: InstanceDto, data: ProfileNameDto) { + logger.verbose('requested updateProfileName from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].updateProfileName(data.name); + } + + public async updateProfileStatus({ instanceName }: InstanceDto, data: ProfileStatusDto) { + logger.verbose('requested updateProfileStatus from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].updateProfileStatus(data.status); + } + + public async updateProfilePicture({ instanceName }: InstanceDto, data: ProfilePictureDto) { + logger.verbose('requested updateProfilePicture from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].updateProfilePicture(data.picture); + } + + public async removeProfilePicture({ instanceName }: InstanceDto) { + logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].removeProfilePicture(); + } +} diff --git a/src/whatsapp/controllers/chatwoot.controller.ts b/src/whatsapp/controllers/chatwoot.controller.ts old mode 100644 new mode 100755 index 46b93aee..1998f9ec --- a/src/whatsapp/controllers/chatwoot.controller.ts +++ b/src/whatsapp/controllers/chatwoot.controller.ts @@ -72,6 +72,7 @@ export class ChatwootController { token: '', sign_msg: false, name_inbox: '', + id_inbox: '', webhook_url: '', }; } @@ -86,6 +87,7 @@ export class ChatwootController { public async receiveWebhook(instance: InstanceDto, data: any) { logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance'); + const chatwootService = new ChatwootService(waMonitor, this.configService); return chatwootService.receiveWebhook(instance, data); diff --git a/src/whatsapp/controllers/group.controller.ts b/src/whatsapp/controllers/group.controller.ts old mode 100644 new mode 100755 index 0cf093ca..27e7116a --- a/src/whatsapp/controllers/group.controller.ts +++ b/src/whatsapp/controllers/group.controller.ts @@ -1,97 +1,97 @@ -import { Logger } from '../../config/logger.config'; -import { - CreateGroupDto, - GetParticipant, - GroupDescriptionDto, - GroupInvite, - GroupJid, - GroupPictureDto, - GroupSendInvite, - GroupSubjectDto, - GroupToggleEphemeralDto, - GroupUpdateParticipantDto, - GroupUpdateSettingDto, -} from '../dto/group.dto'; -import { InstanceDto } from '../dto/instance.dto'; -import { WAMonitoringService } from '../services/monitor.service'; - -const logger = new Logger('ChatController'); - -export class GroupController { - constructor(private readonly waMonitor: WAMonitoringService) {} - - public async createGroup(instance: InstanceDto, create: CreateGroupDto) { - logger.verbose('requested createGroup from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].createGroup(create); - } - - public async updateGroupPicture(instance: InstanceDto, update: GroupPictureDto) { - logger.verbose('requested updateGroupPicture from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].updateGroupPicture(update); - } - - public async updateGroupSubject(instance: InstanceDto, update: GroupSubjectDto) { - logger.verbose('requested updateGroupSubject from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject(update); - } - - public async updateGroupDescription(instance: InstanceDto, update: GroupDescriptionDto) { - logger.verbose('requested updateGroupDescription from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription(update); - } - - public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) { - logger.verbose('requested findGroupInfo from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].findGroup(groupJid); - } - - public async fetchAllGroups(instance: InstanceDto, getPaticipants: GetParticipant) { - logger.verbose('requested fetchAllGroups from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups(getPaticipants); - } - - public async inviteCode(instance: InstanceDto, groupJid: GroupJid) { - logger.verbose('requested inviteCode from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].inviteCode(groupJid); - } - - public async inviteInfo(instance: InstanceDto, inviteCode: GroupInvite) { - logger.verbose('requested inviteInfo from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode); - } - - public async sendInvite(instance: InstanceDto, data: GroupSendInvite) { - logger.verbose('requested sendInvite from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data); - } - - public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) { - logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid); - } - - public async findParticipants(instance: InstanceDto, groupJid: GroupJid) { - logger.verbose('requested findParticipants from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].findParticipants(groupJid); - } - - public async updateGParticipate(instance: InstanceDto, update: GroupUpdateParticipantDto) { - logger.verbose('requested updateGParticipate from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].updateGParticipant(update); - } - - public async updateGSetting(instance: InstanceDto, update: GroupUpdateSettingDto) { - logger.verbose('requested updateGSetting from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].updateGSetting(update); - } - - public async toggleEphemeral(instance: InstanceDto, update: GroupToggleEphemeralDto) { - logger.verbose('requested toggleEphemeral from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(update); - } - - public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) { - logger.verbose('requested leaveGroup from ' + instance.instanceName + ' instance'); - return await this.waMonitor.waInstances[instance.instanceName].leaveGroup(groupJid); - } -} +import { Logger } from '../../config/logger.config'; +import { + CreateGroupDto, + GetParticipant, + GroupDescriptionDto, + GroupInvite, + GroupJid, + GroupPictureDto, + GroupSendInvite, + GroupSubjectDto, + GroupToggleEphemeralDto, + GroupUpdateParticipantDto, + GroupUpdateSettingDto, +} from '../dto/group.dto'; +import { InstanceDto } from '../dto/instance.dto'; +import { WAMonitoringService } from '../services/monitor.service'; + +const logger = new Logger('ChatController'); + +export class GroupController { + constructor(private readonly waMonitor: WAMonitoringService) {} + + public async createGroup(instance: InstanceDto, create: CreateGroupDto) { + logger.verbose('requested createGroup from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].createGroup(create); + } + + public async updateGroupPicture(instance: InstanceDto, update: GroupPictureDto) { + logger.verbose('requested updateGroupPicture from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGroupPicture(update); + } + + public async updateGroupSubject(instance: InstanceDto, update: GroupSubjectDto) { + logger.verbose('requested updateGroupSubject from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject(update); + } + + public async updateGroupDescription(instance: InstanceDto, update: GroupDescriptionDto) { + logger.verbose('requested updateGroupDescription from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription(update); + } + + public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) { + logger.verbose('requested findGroupInfo from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].findGroup(groupJid); + } + + public async fetchAllGroups(instance: InstanceDto, getPaticipants: GetParticipant) { + logger.verbose('requested fetchAllGroups from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups(getPaticipants); + } + + public async inviteCode(instance: InstanceDto, groupJid: GroupJid) { + logger.verbose('requested inviteCode from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].inviteCode(groupJid); + } + + public async inviteInfo(instance: InstanceDto, inviteCode: GroupInvite) { + logger.verbose('requested inviteInfo from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].inviteInfo(inviteCode); + } + + public async sendInvite(instance: InstanceDto, data: GroupSendInvite) { + logger.verbose('requested sendInvite from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data); + } + + public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) { + logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid); + } + + public async findParticipants(instance: InstanceDto, groupJid: GroupJid) { + logger.verbose('requested findParticipants from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].findParticipants(groupJid); + } + + public async updateGParticipate(instance: InstanceDto, update: GroupUpdateParticipantDto) { + logger.verbose('requested updateGParticipate from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGParticipant(update); + } + + public async updateGSetting(instance: InstanceDto, update: GroupUpdateSettingDto) { + logger.verbose('requested updateGSetting from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGSetting(update); + } + + public async toggleEphemeral(instance: InstanceDto, update: GroupToggleEphemeralDto) { + logger.verbose('requested toggleEphemeral from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(update); + } + + public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) { + logger.verbose('requested leaveGroup from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].leaveGroup(groupJid); + } +} diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts old mode 100644 new mode 100755 index 4c4e5cfb..f5896dc1 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -1,652 +1,740 @@ -import { delay } from '@whiskeysockets/baileys'; -import { isURL } from 'class-validator'; -import EventEmitter2 from 'eventemitter2'; - -import { ConfigService, HttpServer } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { BadRequestException, InternalServerErrorException } from '../../exceptions'; -import { RedisCache } from '../../libs/redis.client'; -import { InstanceDto } from '../dto/instance.dto'; -import { RepositoryBroker } from '../repository/repository.manager'; -import { AuthService, OldToken } from '../services/auth.service'; -import { ChatwootService } from '../services/chatwoot.service'; -import { WAMonitoringService } from '../services/monitor.service'; -import { ProxyService } from '../services/proxy.service'; -import { RabbitmqService } from '../services/rabbitmq.service'; -import { SettingsService } from '../services/settings.service'; -import { SqsService } from '../services/sqs.service'; -import { TypebotService } from '../services/typebot.service'; -import { WebhookService } from '../services/webhook.service'; -import { WebsocketService } from '../services/websocket.service'; -import { WAStartupService } from '../services/whatsapp.service'; -import { wa } from '../types/wa.types'; - -export class InstanceController { - constructor( - private readonly waMonitor: WAMonitoringService, - private readonly configService: ConfigService, - private readonly repository: RepositoryBroker, - private readonly eventEmitter: EventEmitter2, - private readonly authService: AuthService, - private readonly webhookService: WebhookService, - private readonly chatwootService: ChatwootService, - private readonly settingsService: SettingsService, - private readonly websocketService: WebsocketService, - private readonly rabbitmqService: RabbitmqService, - private readonly proxyService: ProxyService, - private readonly sqsService: SqsService, - private readonly typebotService: TypebotService, - private readonly cache: RedisCache, - ) {} - - private readonly logger = new Logger(InstanceController.name); - - public async createInstance({ - instanceName, - webhook, - webhook_by_events, - webhook_base64, - events, - qrcode, - number, - token, - chatwoot_account_id, - chatwoot_token, - chatwoot_url, - chatwoot_sign_msg, - chatwoot_reopen_conversation, - chatwoot_conversation_pending, - reject_call, - msg_call, - groups_ignore, - always_online, - read_messages, - read_status, - websocket_enabled, - websocket_events, - rabbitmq_enabled, - rabbitmq_events, - sqs_enabled, - sqs_events, - typebot_url, - typebot, - typebot_expire, - typebot_keyword_finish, - typebot_delay_message, - typebot_unknown_message, - typebot_listening_from_me, - proxy, - }: InstanceDto) { - try { - this.logger.verbose('requested createInstance from ' + instanceName + ' instance'); - - this.logger.verbose('checking duplicate token'); - await this.authService.checkDuplicateToken(token); - - this.logger.verbose('creating instance'); - const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); - instance.instanceName = instanceName; - - this.logger.verbose('instance: ' + instance.instanceName + ' created'); - - this.waMonitor.waInstances[instance.instanceName] = instance; - this.waMonitor.delInstanceTime(instance.instanceName); - - this.logger.verbose('generating hash'); - const hash = await this.authService.generateHash( - { - instanceName: instance.instanceName, - }, - token, - ); - - this.logger.verbose('hash: ' + hash + ' generated'); - - let webhookEvents: string[]; - - if (webhook) { - if (!isURL(webhook, { require_tld: false })) { - throw new BadRequestException('Invalid "url" property in webhook'); - } - - this.logger.verbose('creating webhook'); - try { - let newEvents: string[] = []; - if (events.length === 0) { - newEvents = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } else { - newEvents = events; - } - this.webhookService.create(instance, { - enabled: true, - url: webhook, - events: newEvents, - webhook_by_events, - webhook_base64, - }); - - webhookEvents = (await this.webhookService.find(instance)).events; - } catch (error) { - this.logger.log(error); - } - } - - let websocketEvents: string[]; - - if (websocket_enabled) { - this.logger.verbose('creating websocket'); - try { - let newEvents: string[] = []; - if (websocket_events.length === 0) { - newEvents = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } else { - newEvents = websocket_events; - } - this.websocketService.create(instance, { - enabled: true, - events: newEvents, - }); - - websocketEvents = (await this.websocketService.find(instance)).events; - } catch (error) { - this.logger.log(error); - } - } - - let rabbitmqEvents: string[]; - - if (rabbitmq_enabled) { - this.logger.verbose('creating rabbitmq'); - try { - let newEvents: string[] = []; - if (rabbitmq_events.length === 0) { - newEvents = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } else { - newEvents = rabbitmq_events; - } - this.rabbitmqService.create(instance, { - enabled: true, - events: newEvents, - }); - - rabbitmqEvents = (await this.rabbitmqService.find(instance)).events; - } catch (error) { - this.logger.log(error); - } - } - - if (proxy) { - this.logger.verbose('creating proxy'); - try { - this.proxyService.create( - instance, - { - enabled: true, - proxy, - }, - false, - ); - } catch (error) { - this.logger.log(error); - } - } - - let sqsEvents: string[]; - - if (sqs_enabled) { - this.logger.verbose('creating sqs'); - try { - let newEvents: string[] = []; - if (sqs_events.length === 0) { - newEvents = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } else { - newEvents = sqs_events; - } - this.sqsService.create(instance, { - enabled: true, - events: newEvents, - }); - - sqsEvents = (await this.sqsService.find(instance)).events; - } catch (error) { - this.logger.log(error); - } - } - - if (typebot_url) { - try { - if (!isURL(typebot_url, { require_tld: false })) { - throw new BadRequestException('Invalid "url" property in typebot_url'); - } - - this.logger.verbose('creating typebot'); - - this.typebotService.create(instance, { - enabled: true, - url: typebot_url, - typebot: typebot, - expire: typebot_expire, - 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); - } - } - - this.logger.verbose('creating settings'); - const settings: wa.LocalSettings = { - reject_call: reject_call || false, - msg_call: msg_call || '', - groups_ignore: groups_ignore || false, - always_online: always_online || false, - read_messages: read_messages || false, - read_status: read_status || false, - }; - - this.logger.verbose('settings: ' + JSON.stringify(settings)); - - this.settingsService.create(instance, settings); - - if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { - let getQrcode: wa.QrCode; - - if (qrcode) { - this.logger.verbose('creating qrcode'); - await instance.connectToWhatsapp(number); - await delay(5000); - getQrcode = instance.qrCode; - } - - const result = { - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook: { - webhook, - webhook_by_events, - webhook_base64, - events: webhookEvents, - }, - websocket: { - enabled: websocket_enabled, - events: websocketEvents, - }, - rabbitmq: { - enabled: rabbitmq_enabled, - events: rabbitmqEvents, - }, - sqs: { - enabled: sqs_enabled, - events: sqsEvents, - }, - typebot: { - enabled: typebot_url ? true : false, - url: typebot_url, - typebot, - expire: typebot_expire, - 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, - proxy, - }; - - this.logger.verbose('instance created'); - this.logger.verbose(result); - - return result; - } - - 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'); - } - - if (!isURL(chatwoot_url, { require_tld: false })) { - throw new BadRequestException('Invalid "url" property in chatwoot'); - } - - if (chatwoot_sign_msg !== true && chatwoot_sign_msg !== false) { - throw new BadRequestException('sign_msg is required'); - } - - if (chatwoot_reopen_conversation !== true && chatwoot_reopen_conversation !== false) { - throw new BadRequestException('reopen_conversation is required'); - } - - if (chatwoot_conversation_pending !== true && chatwoot_conversation_pending !== false) { - throw new BadRequestException('conversation_pending is required'); - } - - const urlServer = this.configService.get('SERVER').URL; - - try { - this.chatwootService.create(instance, { - enabled: true, - account_id: chatwoot_account_id, - token: chatwoot_token, - url: chatwoot_url, - sign_msg: chatwoot_sign_msg || false, - name_inbox: instance.instanceName.split('-cwId-')[0], - number, - reopen_conversation: chatwoot_reopen_conversation || false, - conversation_pending: chatwoot_conversation_pending || false, - }); - - this.chatwootService.initInstanceChatwoot( - instance, - instance.instanceName.split('-cwId-')[0], - `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, - qrcode, - number, - ); - } catch (error) { - this.logger.log(error); - } - - return { - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook: { - webhook, - webhook_by_events, - webhook_base64, - events: webhookEvents, - }, - websocket: { - enabled: websocket_enabled, - events: websocketEvents, - }, - rabbitmq: { - enabled: rabbitmq_enabled, - events: rabbitmqEvents, - }, - sqs: { - enabled: sqs_enabled, - events: sqsEvents, - }, - typebot: { - enabled: typebot_url ? true : false, - url: typebot_url, - typebot, - expire: typebot_expire, - keyword_finish: typebot_keyword_finish, - delay_message: typebot_delay_message, - unknown_message: typebot_unknown_message, - listening_from_me: typebot_listening_from_me, - }, - settings, - chatwoot: { - enabled: true, - account_id: chatwoot_account_id, - token: chatwoot_token, - url: chatwoot_url, - sign_msg: chatwoot_sign_msg || false, - reopen_conversation: chatwoot_reopen_conversation || false, - conversation_pending: chatwoot_conversation_pending || false, - number, - name_inbox: instance.instanceName, - webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, - }, - proxy, - }; - } catch (error) { - this.logger.error(error.message[0]); - throw new BadRequestException(error.message[0]); - } - } - - public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { - try { - this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance'); - - const instance = this.waMonitor.waInstances[instanceName]; - const state = instance?.connectionStatus?.state; - - this.logger.verbose('state: ' + state); - - if (!state) { - throw new BadRequestException('The "' + instanceName + '" instance does not exist'); - } - - if (state == 'open') { - return await this.connectionState({ instanceName }); - } - - if (state == 'connecting') { - return instance.qrCode; - } - - if (state == 'close') { - this.logger.verbose('connecting'); - await instance.connectToWhatsapp(number); - - await delay(5000); - return instance.qrCode; - } - - return { - instance: { - instanceName: instanceName, - status: state, - }, - qrcode: instance?.qrCode, - }; - } catch (error) { - this.logger.error(error); - } - } - - public async restartInstance({ instanceName }: InstanceDto) { - try { - this.logger.verbose('requested restartInstance from ' + instanceName + ' instance'); - - const instance = this.waMonitor.waInstances[instanceName]; - const state = instance?.connectionStatus?.state; - - switch (state) { - case 'open': - this.logger.verbose('logging out instance: ' + instanceName); - await instance.reloadConnection(); - await delay(2000); - - return await this.connectionState({ instanceName }); - default: - return await this.connectionState({ instanceName }); - } - } catch (error) { - this.logger.error(error); - } - } - - public async connectionState({ instanceName }: InstanceDto) { - this.logger.verbose('requested connectionState from ' + instanceName + ' instance'); - return { - instance: { - instanceName: instanceName, - state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state, - }, - }; - } - - public async fetchInstances({ instanceName }: InstanceDto) { - 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(); - } - - public async logout({ instanceName }: InstanceDto) { - this.logger.verbose('requested logout from ' + instanceName + ' instance'); - const { instance } = await this.connectionState({ instanceName }); - - if (instance.state === 'close') { - throw new BadRequestException('The "' + instanceName + '" instance is not connected'); - } - - try { - this.logger.verbose('logging out instance: ' + instanceName); - await this.waMonitor.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName); - - this.logger.verbose('close connection instance: ' + instanceName); - this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); - - return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } }; - } catch (error) { - throw new InternalServerErrorException(error.toString()); - } - } - - public async deleteInstance({ instanceName }: InstanceDto) { - this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance'); - const { instance } = await this.connectionState({ instanceName }); - - if (instance.state === 'open') { - throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected'); - } - try { - this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues(); - - if (instance.state === 'connecting') { - this.logger.verbose('logging out instance: ' + instanceName); - - await this.logout({ instanceName }); - } - - this.logger.verbose('deleting instance: ' + instanceName); - - delete this.waMonitor.waInstances[instanceName]; - this.eventEmitter.emit('remove.instance', instanceName, 'inner'); - return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; - } catch (error) { - throw new BadRequestException(error.toString()); - } - } - - public async refreshToken(_: InstanceDto, oldToken: OldToken) { - this.logger.verbose('requested refreshToken'); - return await this.authService.refreshToken(oldToken); - } -} +import { delay } from '@whiskeysockets/baileys'; +import { isURL } from 'class-validator'; +import EventEmitter2 from 'eventemitter2'; + +import { ConfigService, HttpServer } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { BadRequestException, InternalServerErrorException } from '../../exceptions'; +import { RedisCache } from '../../libs/redis.client'; +import { InstanceDto } from '../dto/instance.dto'; +import { RepositoryBroker } from '../repository/repository.manager'; +import { AuthService, OldToken } from '../services/auth.service'; +import { ChatwootService } from '../services/chatwoot.service'; +import { WAMonitoringService } from '../services/monitor.service'; +import { ProxyService } from '../services/proxy.service'; +import { RabbitmqService } from '../services/rabbitmq.service'; +import { OpenaiService } from '../services/openai.service'; +import { SettingsService } from '../services/settings.service'; +import { SqsService } from '../services/sqs.service'; +import { TypebotService } from '../services/typebot.service'; +import { WebhookService } from '../services/webhook.service'; +import { WebsocketService } from '../services/websocket.service'; +import { WAStartupService } from '../services/whatsapp.service'; +import { wa } from '../types/wa.types'; + +export class InstanceController { + constructor( + private readonly waMonitor: WAMonitoringService, + private readonly configService: ConfigService, + private readonly repository: RepositoryBroker, + private readonly eventEmitter: EventEmitter2, + private readonly authService: AuthService, + private readonly webhookService: WebhookService, + private readonly chatwootService: ChatwootService, + private readonly settingsService: SettingsService, + private readonly websocketService: WebsocketService, + private readonly rabbitmqService: RabbitmqService, + private readonly openaiService: OpenaiService, + private readonly proxyService: ProxyService, + private readonly sqsService: SqsService, + private readonly typebotService: TypebotService, + private readonly cache: RedisCache, + ) {} + + private readonly logger = new Logger(InstanceController.name); + + public async createInstance({ + instanceName, + webhook, + webhook_by_events, + webhook_base64, + events, + qrcode, + number, + token, + chatwoot_account_id, + chatwoot_token, + chatwoot_url, + chatwoot_sign_msg, + chatwoot_reopen_conversation, + chatwoot_conversation_pending, + reject_call, + msg_call, + groups_ignore, + always_online, + read_messages, + read_status, + websocket_enabled, + websocket_events, + rabbitmq_enabled, + rabbitmq_events, + + openai_chave, + openai_enabled, + openai_events, + + sqs_enabled, + sqs_events, + + typebot_url, + typebot, + typebot_expire, + typebot_keyword_finish, + typebot_delay_message, + typebot_unknown_message, + typebot_listening_from_me, + proxy, + }: InstanceDto) { + try { + this.logger.verbose('requested createInstance from ' + instanceName + ' instance'); + + this.logger.verbose('checking duplicate token'); + + await this.authService.checkDuplicateToken(token); + + this.logger.verbose('creating instance'); + const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); + instance.instanceName = instanceName; + + this.logger.verbose('instance: ' + instance.instanceName + ' created'); + + this.waMonitor.waInstances[instance.instanceName] = instance; + this.waMonitor.delInstanceTime(instance.instanceName); + + this.logger.verbose('generating hash'); + const hash = await this.authService.generateHash( + { + instanceName: instance.instanceName, + }, + token, + ); + + this.logger.verbose('hash: ' + hash + ' generated'); + + let webhookEvents: string[]; + + if (webhook) { + if (!isURL(webhook, { require_tld: false })) { + throw new BadRequestException('Invalid "url" property in webhook'); + } + + this.logger.verbose('creating webhook'); + try { + let newEvents: string[] = []; + if (events.length === 0) { + newEvents = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } else { + newEvents = events; + } + this.webhookService.create(instance, { + enabled: true, + url: webhook, + events: newEvents, + webhook_by_events, + webhook_base64, + }); + + webhookEvents = (await this.webhookService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } + } + + let websocketEvents: string[]; + + if (websocket_enabled) { + this.logger.verbose('creating websocket'); + try { + let newEvents: string[] = []; + if (websocket_events.length === 0) { + newEvents = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } else { + newEvents = websocket_events; + } + this.websocketService.create(instance, { + enabled: true, + events: newEvents, + }); + + websocketEvents = (await this.websocketService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } + } + + let rabbitmqEvents: string[]; + + if (rabbitmq_enabled) { + this.logger.verbose('creating rabbitmq'); + try { + let newEvents: string[] = []; + if (rabbitmq_events.length === 0) { + newEvents = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } else { + newEvents = rabbitmq_events; + } + this.rabbitmqService.create(instance, { + enabled: true, + events: newEvents, + }); + + rabbitmqEvents = (await this.rabbitmqService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } + } + + let openaiEvents: string[]; + + if (openai_enabled) { + this.logger.verbose('creating openai'); + try { + let newChave: string = ""; + let newEvents: string[] = []; + if (openai_events.length === 0) { + newEvents = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } else { + newEvents = openai_events; + } + this.openaiService.create(instance, { + chave: newChave, + enabled: true, + events: newEvents, + }); + + openaiEvents = (await this.openaiService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } + } + + + if (proxy) { + this.logger.verbose('creating proxy'); + try { + this.proxyService.create( + instance, + { + enabled: true, + proxy, + }, + false, + ); + } catch (error) { + this.logger.log(error); + } + } + + + let sqsEvents: string[]; + + if (sqs_enabled) { + this.logger.verbose('creating sqs'); + try { + let newEvents: string[] = []; + if (sqs_events.length === 0) { + newEvents = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } else { + newEvents = sqs_events; + } + this.sqsService.create(instance, { + enabled: true, + events: newEvents, + }); + + sqsEvents = (await this.sqsService.find(instance)).events; + } catch (error) { + this.logger.log(error); + } + } + + if (typebot_url) { + try { + if (!isURL(typebot_url, { require_tld: false })) { + throw new BadRequestException('Invalid "url" property in typebot_url'); + } + + this.logger.verbose('creating typebot'); + + this.typebotService.create(instance, { + enabled: true, + url: typebot_url, + typebot: typebot, + expire: typebot_expire, + 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); + } + } + + this.logger.verbose('creating settings'); + const settings: wa.LocalSettings = { + reject_call: reject_call || false, + msg_call: msg_call || '', + groups_ignore: groups_ignore || false, + always_online: always_online || false, + read_messages: read_messages || false, + read_status: read_status || false, + }; + + this.logger.verbose('settings: ' + JSON.stringify(settings)); + + this.settingsService.create(instance, settings); + + if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { + let getQrcode: wa.QrCode; + + if (qrcode) { + this.logger.verbose('creating qrcode'); + await instance.connectToWhatsapp(number); + await delay(5000); + getQrcode = instance.qrCode; + } + + const result = { + instance: { + instanceName: instance.instanceName, + status: 'created', + }, + hash, + webhook: { + webhook, + webhook_by_events, + webhook_base64, + events: webhookEvents, + }, + websocket: { + enabled: websocket_enabled, + events: websocketEvents, + }, + rabbitmq: { + enabled: rabbitmq_enabled, + events: rabbitmqEvents, + }, + openai: { + chave: openai_chave, + enabled: openai_enabled, + events: openaiEvents, + }, + sqs: { + enabled: sqs_enabled, + events: sqsEvents, + }, + typebot: { + enabled: typebot_url ? true : false, + url: typebot_url, + typebot, + expire: typebot_expire, + 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, + }; + + this.logger.verbose('instance created'); + this.logger.verbose(result); + + return result; + } + + 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'); + } + + if (!isURL(chatwoot_url, { require_tld: false })) { + throw new BadRequestException('Invalid "url" property in chatwoot'); + } + + if (chatwoot_sign_msg !== true && chatwoot_sign_msg !== false) { + throw new BadRequestException('sign_msg is required'); + } + + if (chatwoot_reopen_conversation !== true && chatwoot_reopen_conversation !== false) { + throw new BadRequestException('reopen_conversation is required'); + } + + if (chatwoot_conversation_pending !== true && chatwoot_conversation_pending !== false) { + throw new BadRequestException('conversation_pending is required'); + } + + const urlServer = this.configService.get('SERVER').URL; + + try { + this.chatwootService.create(instance, { + enabled: true, + account_id: chatwoot_account_id, + token: chatwoot_token, + url: chatwoot_url, + sign_msg: chatwoot_sign_msg || false, + name_inbox: instance.instanceName.split('-cwId-')[0], + number, + reopen_conversation: chatwoot_reopen_conversation || false, + conversation_pending: chatwoot_conversation_pending || false, + }); + + this.chatwootService.initInstanceChatwoot( + instance, + instance.instanceName.split('-cwId-')[0], + `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, + qrcode, + number, + ); + } catch (error) { + this.logger.log(error); + } + + return { + instance: { + instanceName: instance.instanceName, + status: 'created', + }, + hash, + webhook: { + webhook, + webhook_by_events, + webhook_base64, + events: webhookEvents, + }, + websocket: { + enabled: websocket_enabled, + events: websocketEvents, + }, + rabbitmq: { + enabled: rabbitmq_enabled, + events: rabbitmqEvents, + }, + openai: { + chave: openai_chave, + enabled: openai_enabled, + events: openaiEvents, + }, + sqs: { + enabled: sqs_enabled, + events: sqsEvents, + }, + typebot: { + enabled: typebot_url ? true : false, + url: typebot_url, + typebot, + expire: typebot_expire, + keyword_finish: typebot_keyword_finish, + delay_message: typebot_delay_message, + unknown_message: typebot_unknown_message, + listening_from_me: typebot_listening_from_me, + }, + settings, + chatwoot: { + enabled: true, + account_id: chatwoot_account_id, + token: chatwoot_token, + url: chatwoot_url, + sign_msg: chatwoot_sign_msg || false, + reopen_conversation: chatwoot_reopen_conversation || false, + conversation_pending: chatwoot_conversation_pending || false, + number, + name_inbox: instance.instanceName, + webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`, + }, + }; + } catch (error) { + this.logger.error(error.message[0]); + throw new BadRequestException(error.message[0]); + } + } + + + public async qrInstance({ instanceName }: InstanceDto) { + try { + this.logger.verbose('requested qrInstance from ' + instanceName + ' instance'); + + this.logger.verbose('logging out instance: ' + instanceName); + ///this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); + + const qrcode = await this.waMonitor.waInstances[instanceName]?.instance.qrcode; + return qrcode.base64; + + } catch (error) { + this.logger.error(error); + return ''; + } + } + + public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { + try { + this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance'); + + const instance = this.waMonitor.waInstances[instanceName]; + const state = instance?.connectionStatus?.state; + + this.logger.verbose('state: ' + state); + + if (!state) { + throw new BadRequestException('The "' + instanceName + '" instance does not exist'); + } + + if (state == 'open') { + return await this.connectionState({ instanceName }); + } + + if (state == 'connecting') { + return instance.qrCode; + } + + if (state == 'close') { + this.logger.verbose('connecting'); + await instance.connectToWhatsapp(number); + + await delay(5000); + return instance.qrCode; + } + + return { + instance: { + instanceName: instanceName, + status: state, + }, + qrcode: instance?.qrCode, + }; + } catch (error) { + this.logger.error(error); + } + } + + public async restartInstance({ instanceName }: InstanceDto) { + try { + this.logger.verbose('requested restartInstance from ' + instanceName + ' instance'); + + const instance = this.waMonitor.waInstances[instanceName]; + const state = instance?.connectionStatus?.state; + + switch (state) { + case 'open': + this.logger.verbose('logging out instance: ' + instanceName); + await instance.reloadConnection(); + await delay(2000); + + return await this.connectionState({ instanceName }); + default: + return await this.connectionState({ instanceName }); + } + } catch (error) { + this.logger.error(error); + } + } + + public async connectionState({ instanceName }: InstanceDto) { + this.logger.verbose('requested connectionState from ' + instanceName + ' instance'); + return { + instance: { + instanceName: instanceName, + state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state, + }, + }; + } + + public async fetchInstances({ instanceName }: InstanceDto) { + 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(); + } + + public async logout({ instanceName }: InstanceDto) { + this.logger.verbose('requested logout from ' + instanceName + ' instance'); + const { instance } = await this.connectionState({ instanceName }); + + if (instance.state === 'close') { + throw new BadRequestException('The "' + instanceName + '" instance is not connected'); + } + + try { + this.logger.verbose('logging out instance: ' + instanceName); + await this.waMonitor.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName); + + this.logger.verbose('close connection instance: ' + instanceName); + this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); + + return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } }; + } catch (error) { + throw new InternalServerErrorException(error.toString()); + } + } + + public async deleteInstance({ instanceName }: InstanceDto) { + this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance'); + const { instance } = await this.connectionState({ instanceName }); + + if (instance.state === 'open') { + throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected'); + } + try { + this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues(); + this.waMonitor.waInstances[instanceName]?.removeOpenaiQueues(); + + if (instance.state === 'connecting') { + this.logger.verbose('logging out instance: ' + instanceName); + + await this.logout({ instanceName }); + delete this.waMonitor.waInstances[instanceName]; + return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; + } else { + this.logger.verbose('deleting instance: ' + instanceName); + + delete this.waMonitor.waInstances[instanceName]; + this.eventEmitter.emit('remove.instance', instanceName, 'inner'); + return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; + } + } catch (error) { + throw new BadRequestException(error.toString()); + } + } + + public async refreshToken(_: InstanceDto, oldToken: OldToken) { + this.logger.verbose('requested refreshToken'); + return await this.authService.refreshToken(oldToken); + } +} diff --git a/src/whatsapp/controllers/openai.controller.ts b/src/whatsapp/controllers/openai.controller.ts new file mode 100755 index 00000000..52cf3c0a --- /dev/null +++ b/src/whatsapp/controllers/openai.controller.ts @@ -0,0 +1,85 @@ +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { OpenaiDto } from '../dto/openai.dto'; +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; +import { OpenaiService } from '../services/openai.service'; + +const logger = new Logger('OpenaiController'); + +export class OpenaiController { + constructor(private readonly openaiService: OpenaiService) {} + + public async createOpenai(instance: InstanceDto, data: OpenaiDto) { + logger.verbose('requested createOpenai from ' + instance.instanceName + ' instance'); + + if (!data.chave) { + logger.verbose('openai sem chave'); + data.chave = ''; + } + + if (!data.enabled) { + logger.verbose('openai disabled'); + data.events = []; + } + + if (data.events?.length === 0) { + logger.verbose('openai events empty'); + data.events = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } + + return this.openaiService.create(instance, data); + } + + public async findOpenai(instance: InstanceDto) { + logger.verbose('requested findOpenai from ' + instance.instanceName + ' instance'); + return this.openaiService.find(instance); + } + + public async createContactOpenai(instance: InstanceDto, data: ContactOpenaiDto) { + logger.verbose('requested createOpenai from ' + instance.instanceName + ' instance'); + + if (!data.contact) { + logger.verbose('openai sem chave'); + data.contact = ''; + } + + if (!data.enabled) { + logger.verbose('openai disabled'); + data.enabled = false; + } + + data.owner = instance.instanceName; + + return this.openaiService.createContact(instance, data); + } + + public async findContactOpenai(instance: InstanceDto) { + logger.verbose('requested findOpenai from ' + instance.instanceName + ' instance'); + return this.openaiService.findContact(instance); + } +} diff --git a/src/whatsapp/controllers/proxy.controller.ts b/src/whatsapp/controllers/proxy.controller.ts old mode 100644 new mode 100755 index 1656d830..c3b3001d --- a/src/whatsapp/controllers/proxy.controller.ts +++ b/src/whatsapp/controllers/proxy.controller.ts @@ -1,26 +1,26 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { ProxyDto } from '../dto/proxy.dto'; -import { ProxyService } from '../services/proxy.service'; - -const logger = new Logger('ProxyController'); - -export class ProxyController { - constructor(private readonly proxyService: ProxyService) {} - - public async createProxy(instance: InstanceDto, data: ProxyDto) { - logger.verbose('requested createProxy from ' + instance.instanceName + ' instance'); - - if (!data.enabled) { - logger.verbose('proxy disabled'); - data.proxy = ''; - } - - return this.proxyService.create(instance, data); - } - - public async findProxy(instance: InstanceDto) { - logger.verbose('requested findProxy from ' + instance.instanceName + ' instance'); - return this.proxyService.find(instance); - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { ProxyDto } from '../dto/proxy.dto'; +import { ProxyService } from '../services/proxy.service'; + +const logger = new Logger('ProxyController'); + +export class ProxyController { + constructor(private readonly proxyService: ProxyService) {} + + public async createProxy(instance: InstanceDto, data: ProxyDto) { + logger.verbose('requested createProxy from ' + instance.instanceName + ' instance'); + + if (!data.enabled) { + logger.verbose('proxy disabled'); + data.proxy = ''; + } + + return this.proxyService.create(instance, data); + } + + public async findProxy(instance: InstanceDto) { + logger.verbose('requested findProxy from ' + instance.instanceName + ' instance'); + return this.proxyService.find(instance); + } +} diff --git a/src/whatsapp/controllers/rabbitmq.controller.ts b/src/whatsapp/controllers/rabbitmq.controller.ts old mode 100644 new mode 100755 index 8d33ce84..ed914ea7 --- a/src/whatsapp/controllers/rabbitmq.controller.ts +++ b/src/whatsapp/controllers/rabbitmq.controller.ts @@ -1,56 +1,56 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { RabbitmqDto } from '../dto/rabbitmq.dto'; -import { RabbitmqService } from '../services/rabbitmq.service'; - -const logger = new Logger('RabbitmqController'); - -export class RabbitmqController { - constructor(private readonly rabbitmqService: RabbitmqService) {} - - public async createRabbitmq(instance: InstanceDto, data: RabbitmqDto) { - logger.verbose('requested createRabbitmq from ' + instance.instanceName + ' instance'); - - if (!data.enabled) { - logger.verbose('rabbitmq disabled'); - data.events = []; - } - - if (data.events.length === 0) { - logger.verbose('rabbitmq events empty'); - data.events = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } - - return this.rabbitmqService.create(instance, data); - } - - public async findRabbitmq(instance: InstanceDto) { - logger.verbose('requested findRabbitmq from ' + instance.instanceName + ' instance'); - return this.rabbitmqService.find(instance); - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { RabbitmqDto } from '../dto/rabbitmq.dto'; +import { RabbitmqService } from '../services/rabbitmq.service'; + +const logger = new Logger('RabbitmqController'); + +export class RabbitmqController { + constructor(private readonly rabbitmqService: RabbitmqService) {} + + public async createRabbitmq(instance: InstanceDto, data: RabbitmqDto) { + logger.verbose('requested createRabbitmq from ' + instance.instanceName + ' instance'); + + if (!data.enabled) { + logger.verbose('rabbitmq disabled'); + data.events = []; + } + + if (data.events.length === 0) { + logger.verbose('rabbitmq events empty'); + data.events = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } + + return this.rabbitmqService.create(instance, data); + } + + public async findRabbitmq(instance: InstanceDto) { + logger.verbose('requested findRabbitmq from ' + instance.instanceName + ' instance'); + return this.rabbitmqService.find(instance); + } +} diff --git a/src/whatsapp/controllers/sendMessage.controller.ts b/src/whatsapp/controllers/sendMessage.controller.ts old mode 100644 new mode 100755 index 20e38ae5..46cbf2d1 --- a/src/whatsapp/controllers/sendMessage.controller.ts +++ b/src/whatsapp/controllers/sendMessage.controller.ts @@ -1,111 +1,111 @@ -import { isBase64, isURL } from 'class-validator'; - -import { Logger } from '../../config/logger.config'; -import { BadRequestException } from '../../exceptions'; -import { InstanceDto } from '../dto/instance.dto'; -import { - SendAudioDto, - SendButtonDto, - SendContactDto, - SendListDto, - SendLocationDto, - SendMediaDto, - SendPollDto, - SendReactionDto, - SendStatusDto, - SendStickerDto, - SendTextDto, -} from '../dto/sendMessage.dto'; -import { WAMonitoringService } from '../services/monitor.service'; - -const logger = new Logger('MessageRouter'); - -export class SendMessageController { - constructor(private readonly waMonitor: WAMonitoringService) {} - - public async sendText({ instanceName }: InstanceDto, data: SendTextDto) { - logger.verbose('requested sendText from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].textMessage(data); - } - - public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) { - logger.verbose('requested sendMedia from ' + instanceName + ' instance'); - - if ( - isBase64(data?.mediaMessage?.media) && - !data?.mediaMessage?.fileName && - data?.mediaMessage?.mediatype === 'document' - ) { - throw new BadRequestException('For base64 the file name must be informed.'); - } - - logger.verbose('isURL: ' + isURL(data?.mediaMessage?.media) + ', isBase64: ' + isBase64(data?.mediaMessage?.media)); - if (isURL(data?.mediaMessage?.media) || isBase64(data?.mediaMessage?.media)) { - return await this.waMonitor.waInstances[instanceName].mediaMessage(data); - } - throw new BadRequestException('Owned media must be a url or base64'); - } - - public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) { - logger.verbose('requested sendSticker from ' + instanceName + ' instance'); - - logger.verbose( - 'isURL: ' + isURL(data?.stickerMessage?.image) + ', isBase64: ' + isBase64(data?.stickerMessage?.image), - ); - if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) { - return await this.waMonitor.waInstances[instanceName].mediaSticker(data); - } - throw new BadRequestException('Owned media must be a url or base64'); - } - - public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) { - logger.verbose('requested sendWhatsAppAudio from ' + instanceName + ' instance'); - - logger.verbose('isURL: ' + isURL(data?.audioMessage?.audio) + ', isBase64: ' + isBase64(data?.audioMessage?.audio)); - if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) { - return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data); - } - throw new BadRequestException('Owned media must be a url or base64'); - } - - public async sendButtons({ instanceName }: InstanceDto, data: SendButtonDto) { - logger.verbose('requested sendButtons from ' + instanceName + ' instance'); - if (isBase64(data.buttonMessage.mediaMessage?.media) && !data.buttonMessage.mediaMessage?.fileName) { - throw new BadRequestException('For bse64 the file name must be informed.'); - } - return await this.waMonitor.waInstances[instanceName].buttonMessage(data); - } - - public async sendLocation({ instanceName }: InstanceDto, data: SendLocationDto) { - logger.verbose('requested sendLocation from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].locationMessage(data); - } - - public async sendList({ instanceName }: InstanceDto, data: SendListDto) { - logger.verbose('requested sendList from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].listMessage(data); - } - - public async sendContact({ instanceName }: InstanceDto, data: SendContactDto) { - logger.verbose('requested sendContact from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].contactMessage(data); - } - - public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) { - logger.verbose('requested sendReaction from ' + instanceName + ' instance'); - if (!data.reactionMessage.reaction.match(/[^()\w\sà-ú"-+]+/)) { - throw new BadRequestException('"reaction" must be an emoji'); - } - return await this.waMonitor.waInstances[instanceName].reactionMessage(data); - } - - public async sendPoll({ instanceName }: InstanceDto, data: SendPollDto) { - logger.verbose('requested sendPoll from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].pollMessage(data); - } - - public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto) { - logger.verbose('requested sendStatus from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].statusMessage(data); - } -} +import { isBase64, isURL } from 'class-validator'; + +import { Logger } from '../../config/logger.config'; +import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { + SendAudioDto, + SendButtonDto, + SendContactDto, + SendListDto, + SendLocationDto, + SendMediaDto, + SendPollDto, + SendReactionDto, + SendStatusDto, + SendStickerDto, + SendTextDto, +} from '../dto/sendMessage.dto'; +import { WAMonitoringService } from '../services/monitor.service'; + +const logger = new Logger('MessageRouter'); + +export class SendMessageController { + constructor(private readonly waMonitor: WAMonitoringService) {} + + public async sendText({ instanceName }: InstanceDto, data: SendTextDto) { + logger.verbose('requested sendText from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].textMessage(data); + } + + public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) { + logger.verbose('requested sendMedia from ' + instanceName + ' instance'); + + if ( + isBase64(data?.mediaMessage?.media) && + !data?.mediaMessage?.fileName && + data?.mediaMessage?.mediatype === 'document' + ) { + throw new BadRequestException('For base64 the file name must be informed.'); + } + + logger.verbose('isURL: ' + isURL(data?.mediaMessage?.media) + ', isBase64: ' + isBase64(data?.mediaMessage?.media)); + if (isURL(data?.mediaMessage?.media) || isBase64(data?.mediaMessage?.media)) { + return await this.waMonitor.waInstances[instanceName].mediaMessage(data); + } + throw new BadRequestException('Owned media must be a url or base64'); + } + + public async sendSticker({ instanceName }: InstanceDto, data: SendStickerDto) { + logger.verbose('requested sendSticker from ' + instanceName + ' instance'); + + logger.verbose( + 'isURL: ' + isURL(data?.stickerMessage?.image) + ', isBase64: ' + isBase64(data?.stickerMessage?.image), + ); + if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) { + return await this.waMonitor.waInstances[instanceName].mediaSticker(data); + } + throw new BadRequestException('Owned media must be a url or base64'); + } + + public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) { + logger.verbose('requested sendWhatsAppAudio from ' + instanceName + ' instance'); + + logger.verbose('isURL: ' + isURL(data?.audioMessage?.audio) + ', isBase64: ' + isBase64(data?.audioMessage?.audio)); + if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) { + return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data); + } + throw new BadRequestException('Owned media must be a url or base64'); + } + + public async sendButtons({ instanceName }: InstanceDto, data: SendButtonDto) { + logger.verbose('requested sendButtons from ' + instanceName + ' instance'); + if (isBase64(data.buttonMessage.mediaMessage?.media) && !data.buttonMessage.mediaMessage?.fileName) { + throw new BadRequestException('For bse64 the file name must be informed.'); + } + return await this.waMonitor.waInstances[instanceName].buttonMessage(data); + } + + public async sendLocation({ instanceName }: InstanceDto, data: SendLocationDto) { + logger.verbose('requested sendLocation from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].locationMessage(data); + } + + public async sendList({ instanceName }: InstanceDto, data: SendListDto) { + logger.verbose('requested sendList from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].listMessage(data); + } + + public async sendContact({ instanceName }: InstanceDto, data: SendContactDto) { + logger.verbose('requested sendContact from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].contactMessage(data); + } + + public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) { + logger.verbose('requested sendReaction from ' + instanceName + ' instance'); + if (!data.reactionMessage.reaction.match(/[^()\w\sà-ú"-+]+/)) { + throw new BadRequestException('"reaction" must be an emoji'); + } + return await this.waMonitor.waInstances[instanceName].reactionMessage(data); + } + + public async sendPoll({ instanceName }: InstanceDto, data: SendPollDto) { + logger.verbose('requested sendPoll from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].pollMessage(data); + } + + public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto) { + logger.verbose('requested sendStatus from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].statusMessage(data); + } +} diff --git a/src/whatsapp/controllers/settings.controller.ts b/src/whatsapp/controllers/settings.controller.ts old mode 100644 new mode 100755 index 1a8baafc..1d76cf99 --- a/src/whatsapp/controllers/settings.controller.ts +++ b/src/whatsapp/controllers/settings.controller.ts @@ -1,24 +1,24 @@ -// import { isURL } from 'class-validator'; - -import { Logger } from '../../config/logger.config'; -// import { BadRequestException } from '../../exceptions'; -import { InstanceDto } from '../dto/instance.dto'; -import { SettingsDto } from '../dto/settings.dto'; -import { SettingsService } from '../services/settings.service'; - -const logger = new Logger('SettingsController'); - -export class SettingsController { - constructor(private readonly settingsService: SettingsService) {} - - public async createSettings(instance: InstanceDto, data: SettingsDto) { - logger.verbose('requested createSettings from ' + instance.instanceName + ' instance'); - - return this.settingsService.create(instance, data); - } - - public async findSettings(instance: InstanceDto) { - logger.verbose('requested findSettings from ' + instance.instanceName + ' instance'); - return this.settingsService.find(instance); - } -} +// import { isURL } from 'class-validator'; + +import { Logger } from '../../config/logger.config'; +// import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { SettingsService } from '../services/settings.service'; + +const logger = new Logger('SettingsController'); + +export class SettingsController { + constructor(private readonly settingsService: SettingsService) {} + + public async createSettings(instance: InstanceDto, data: SettingsDto) { + logger.verbose('requested createSettings from ' + instance.instanceName + ' instance'); + + return this.settingsService.create(instance, data); + } + + public async findSettings(instance: InstanceDto) { + logger.verbose('requested findSettings from ' + instance.instanceName + ' instance'); + return this.settingsService.find(instance); + } +} diff --git a/src/whatsapp/controllers/sqs.controller.ts b/src/whatsapp/controllers/sqs.controller.ts old mode 100644 new mode 100755 index 063e29ed..c606054b --- a/src/whatsapp/controllers/sqs.controller.ts +++ b/src/whatsapp/controllers/sqs.controller.ts @@ -1,56 +1,56 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { SqsDto } from '../dto/sqs.dto'; -import { SqsService } from '../services/sqs.service'; - -const logger = new Logger('SqsController'); - -export class SqsController { - constructor(private readonly sqsService: SqsService) {} - - public async createSqs(instance: InstanceDto, data: SqsDto) { - logger.verbose('requested createSqs from ' + instance.instanceName + ' instance'); - - if (!data.enabled) { - logger.verbose('sqs disabled'); - data.events = []; - } - - if (data.events.length === 0) { - logger.verbose('sqs events empty'); - data.events = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } - - return this.sqsService.create(instance, data); - } - - public async findSqs(instance: InstanceDto) { - logger.verbose('requested findSqs from ' + instance.instanceName + ' instance'); - return this.sqsService.find(instance); - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { SqsDto } from '../dto/sqs.dto'; +import { SqsService } from '../services/sqs.service'; + +const logger = new Logger('SqsController'); + +export class SqsController { + constructor(private readonly sqsService: SqsService) { } + + public async createSqs(instance: InstanceDto, data: SqsDto) { + logger.verbose('requested createSqs from ' + instance.instanceName + ' instance'); + + if (!data.enabled) { + logger.verbose('sqs disabled'); + data.events = []; + } + + if (data.events.length === 0) { + logger.verbose('sqs events empty'); + data.events = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } + + return this.sqsService.create(instance, data); + } + + public async findSqs(instance: InstanceDto) { + logger.verbose('requested findSqs from ' + instance.instanceName + ' instance'); + return this.sqsService.find(instance); + } +} \ No newline at end of file diff --git a/src/whatsapp/controllers/typebot.controller.ts b/src/whatsapp/controllers/typebot.controller.ts old mode 100644 new mode 100755 index 53dc967f..573cec22 --- a/src/whatsapp/controllers/typebot.controller.ts +++ b/src/whatsapp/controllers/typebot.controller.ts @@ -1,46 +1,46 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { TypebotDto } from '../dto/typebot.dto'; -import { TypebotService } from '../services/typebot.service'; - -const logger = new Logger('TypebotController'); - -export class TypebotController { - constructor(private readonly typebotService: TypebotService) {} - - public async createTypebot(instance: InstanceDto, data: TypebotDto) { - logger.verbose('requested createTypebot from ' + instance.instanceName + ' instance'); - - if (!data.enabled) { - logger.verbose('typebot disabled'); - data.url = ''; - data.typebot = ''; - data.expire = 0; - data.sessions = []; - } else { - const saveData = await this.typebotService.find(instance); - - if (saveData.enabled) { - logger.verbose('typebot enabled'); - data.sessions = saveData.sessions; - } - } - - return this.typebotService.create(instance, data); - } - - public async findTypebot(instance: InstanceDto) { - logger.verbose('requested findTypebot from ' + instance.instanceName + ' instance'); - return this.typebotService.find(instance); - } - - public async changeStatus(instance: InstanceDto, data: any) { - logger.verbose('requested changeStatus from ' + instance.instanceName + ' instance'); - return this.typebotService.changeStatus(instance, data); - } - - public async startTypebot(instance: InstanceDto, data: any) { - logger.verbose('requested startTypebot from ' + instance.instanceName + ' instance'); - return this.typebotService.startTypebot(instance, data); - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { TypebotDto } from '../dto/typebot.dto'; +import { TypebotService } from '../services/typebot.service'; + +const logger = new Logger('TypebotController'); + +export class TypebotController { + constructor(private readonly typebotService: TypebotService) {} + + public async createTypebot(instance: InstanceDto, data: TypebotDto) { + logger.verbose('requested createTypebot from ' + instance.instanceName + ' instance'); + + if (!data.enabled) { + logger.verbose('typebot disabled'); + data.url = ''; + data.typebot = ''; + data.expire = 0; + data.sessions = []; + } else { + const saveData = await this.typebotService.find(instance); + + if (saveData.enabled) { + logger.verbose('typebot enabled'); + data.sessions = saveData.sessions; + } + } + + return this.typebotService.create(instance, data); + } + + public async findTypebot(instance: InstanceDto) { + logger.verbose('requested findTypebot from ' + instance.instanceName + ' instance'); + return this.typebotService.find(instance); + } + + public async changeStatus(instance: InstanceDto, data: any) { + logger.verbose('requested changeStatus from ' + instance.instanceName + ' instance'); + return this.typebotService.changeStatus(instance, data); + } + + public async startTypebot(instance: InstanceDto, data: any) { + logger.verbose('requested startTypebot from ' + instance.instanceName + ' instance'); + return this.typebotService.startTypebot(instance, data); + } +} diff --git a/src/whatsapp/controllers/views.controller.ts b/src/whatsapp/controllers/views.controller.ts old mode 100644 new mode 100755 index 7e15dfe7..bbe007a0 --- a/src/whatsapp/controllers/views.controller.ts +++ b/src/whatsapp/controllers/views.controller.ts @@ -1,23 +1,23 @@ -import { Request, Response } from 'express'; - -import { Auth, ConfigService, HttpServer } from '../../config/env.config'; -import { HttpStatus } from '../routers/index.router'; -import { WAMonitoringService } from '../services/monitor.service'; - -export class ViewsController { - constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {} - - public async manager(request: Request, response: Response) { - try { - const token = this.configService.get('AUTHENTICATION').API_KEY.KEY; - const port = this.configService.get('SERVER').PORT; - - const instances = await this.waMonitor.instanceInfo(); - - console.log('INSTANCES: ', instances); - return response.status(HttpStatus.OK).render('manager', { token, port, instances }); - } catch (error) { - console.log('ERROR: ', error); - } - } -} +import { Request, Response } from 'express'; + +import { Auth, ConfigService, HttpServer } from '../../config/env.config'; +import { HttpStatus } from '../routers/index.router'; +import { WAMonitoringService } from '../services/monitor.service'; + +export class ViewsController { + constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {} + + public async manager(request: Request, response: Response) { + try { + const token = this.configService.get('AUTHENTICATION').API_KEY.KEY; + const port = this.configService.get('SERVER').PORT; + + const instances = await this.waMonitor.instanceInfo(); + + console.log('INSTANCES: ', instances); + return response.status(HttpStatus.OK).render('manager', { token, port, instances }); + } catch (error) { + console.log('ERROR: ', error); + } + } +} diff --git a/src/whatsapp/controllers/webhook.controller.ts b/src/whatsapp/controllers/webhook.controller.ts old mode 100644 new mode 100755 index 8201f1b5..065e7dbf --- a/src/whatsapp/controllers/webhook.controller.ts +++ b/src/whatsapp/controllers/webhook.controller.ts @@ -1,64 +1,64 @@ -import { isURL } from 'class-validator'; - -import { Logger } from '../../config/logger.config'; -import { BadRequestException } from '../../exceptions'; -import { InstanceDto } from '../dto/instance.dto'; -import { WebhookDto } from '../dto/webhook.dto'; -import { WebhookService } from '../services/webhook.service'; - -const logger = new Logger('WebhookController'); - -export class WebhookController { - constructor(private readonly webhookService: WebhookService) {} - - public async createWebhook(instance: InstanceDto, data: WebhookDto) { - logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance'); - - if (!isURL(data.url, { require_tld: false })) { - throw new BadRequestException('Invalid "url" property'); - } - - data.enabled = data.enabled ?? true; - - if (!data.enabled) { - logger.verbose('webhook disabled'); - data.url = ''; - data.events = []; - } else if (data.events.length === 0) { - logger.verbose('webhook events empty'); - data.events = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } - - return this.webhookService.create(instance, data); - } - - public async findWebhook(instance: InstanceDto) { - logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance'); - return this.webhookService.find(instance); - } -} +import { isURL } from 'class-validator'; + +import { Logger } from '../../config/logger.config'; +import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { WebhookDto } from '../dto/webhook.dto'; +import { WebhookService } from '../services/webhook.service'; + +const logger = new Logger('WebhookController'); + +export class WebhookController { + constructor(private readonly webhookService: WebhookService) {} + + public async createWebhook(instance: InstanceDto, data: WebhookDto) { + logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance'); + + if (!isURL(data.url, { require_tld: false })) { + throw new BadRequestException('Invalid "url" property'); + } + + data.enabled = data.enabled ?? true; + + if (!data.enabled) { + logger.verbose('webhook disabled'); + data.url = ''; + data.events = []; + } else if (data.events.length === 0) { + logger.verbose('webhook events empty'); + data.events = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } + + return this.webhookService.create(instance, data); + } + + public async findWebhook(instance: InstanceDto) { + logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance'); + return this.webhookService.find(instance); + } +} diff --git a/src/whatsapp/controllers/websocket.controller.ts b/src/whatsapp/controllers/websocket.controller.ts old mode 100644 new mode 100755 index 5771027a..cc4e82e1 --- a/src/whatsapp/controllers/websocket.controller.ts +++ b/src/whatsapp/controllers/websocket.controller.ts @@ -1,56 +1,56 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { WebsocketDto } from '../dto/websocket.dto'; -import { WebsocketService } from '../services/websocket.service'; - -const logger = new Logger('WebsocketController'); - -export class WebsocketController { - constructor(private readonly websocketService: WebsocketService) {} - - public async createWebsocket(instance: InstanceDto, data: WebsocketDto) { - logger.verbose('requested createWebsocket from ' + instance.instanceName + ' instance'); - - if (!data.enabled) { - logger.verbose('websocket disabled'); - data.events = []; - } - - if (data.events.length === 0) { - logger.verbose('websocket events empty'); - data.events = [ - 'APPLICATION_STARTUP', - 'QRCODE_UPDATED', - 'MESSAGES_SET', - 'MESSAGES_UPSERT', - 'MESSAGES_UPDATE', - 'MESSAGES_DELETE', - 'SEND_MESSAGE', - 'CONTACTS_SET', - 'CONTACTS_UPSERT', - 'CONTACTS_UPDATE', - 'PRESENCE_UPDATE', - 'CHATS_SET', - 'CHATS_UPSERT', - 'CHATS_UPDATE', - 'CHATS_DELETE', - 'GROUPS_UPSERT', - 'GROUP_UPDATE', - 'GROUP_PARTICIPANTS_UPDATE', - 'CONNECTION_UPDATE', - 'CALL', - 'NEW_JWT_TOKEN', - 'TYPEBOT_START', - 'TYPEBOT_CHANGE_STATUS', - 'CHAMA_AI_ACTION', - ]; - } - - return this.websocketService.create(instance, data); - } - - public async findWebsocket(instance: InstanceDto) { - logger.verbose('requested findWebsocket from ' + instance.instanceName + ' instance'); - return this.websocketService.find(instance); - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { WebsocketDto } from '../dto/websocket.dto'; +import { WebsocketService } from '../services/websocket.service'; + +const logger = new Logger('WebsocketController'); + +export class WebsocketController { + constructor(private readonly websocketService: WebsocketService) {} + + public async createWebsocket(instance: InstanceDto, data: WebsocketDto) { + logger.verbose('requested createWebsocket from ' + instance.instanceName + ' instance'); + + if (!data.enabled) { + logger.verbose('websocket disabled'); + data.events = []; + } + + if (data.events.length === 0) { + logger.verbose('websocket events empty'); + data.events = [ + 'APPLICATION_STARTUP', + 'QRCODE_UPDATED', + 'MESSAGES_SET', + 'MESSAGES_UPSERT', + 'MESSAGES_UPDATE', + 'MESSAGES_DELETE', + 'SEND_MESSAGE', + 'CONTACTS_SET', + 'CONTACTS_UPSERT', + 'CONTACTS_UPDATE', + 'PRESENCE_UPDATE', + 'CHATS_SET', + 'CHATS_UPSERT', + 'CHATS_UPDATE', + 'CHATS_DELETE', + 'GROUPS_UPSERT', + 'GROUP_UPDATE', + 'GROUP_PARTICIPANTS_UPDATE', + 'CONNECTION_UPDATE', + 'CALL', + 'NEW_JWT_TOKEN', + 'TYPEBOT_START', + 'TYPEBOT_CHANGE_STATUS', + 'CHAMA_AI_ACTION', + ]; + } + + return this.websocketService.create(instance, data); + } + + public async findWebsocket(instance: InstanceDto) { + logger.verbose('requested findWebsocket from ' + instance.instanceName + ' instance'); + return this.websocketService.find(instance); + } +} diff --git a/src/whatsapp/dto/chamaai.dto.ts b/src/whatsapp/dto/chamaai.dto.ts old mode 100644 new mode 100755 index 2c71a07d..c3ee4308 --- a/src/whatsapp/dto/chamaai.dto.ts +++ b/src/whatsapp/dto/chamaai.dto.ts @@ -1,7 +1,7 @@ -export class ChamaaiDto { - enabled: boolean; - url: string; - token: string; - waNumber: string; - answerByAudio: boolean; -} +export class ChamaaiDto { + enabled: boolean; + url: string; + token: string; + waNumber: string; + answerByAudio: boolean; +} diff --git a/src/whatsapp/dto/chat.dto.ts b/src/whatsapp/dto/chat.dto.ts old mode 100644 new mode 100755 index f8a5da5f..129d07c2 --- a/src/whatsapp/dto/chat.dto.ts +++ b/src/whatsapp/dto/chat.dto.ts @@ -1,85 +1,85 @@ -import { proto, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; - -export class OnWhatsAppDto { - constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {} -} - -export class getBase64FromMediaMessageDto { - message: proto.WebMessageInfo; - convertToMp4?: boolean; -} - -export class WhatsAppNumberDto { - numbers: string[]; -} - -export class NumberDto { - number: string; -} - -export class NumberBusiness { - wid?: string; - jid?: string; - exists?: boolean; - isBusiness: boolean; - name?: string; - message?: string; - description?: string; - email?: string; - website?: string[]; - address?: string; -} - -export class ProfileNameDto { - name: string; -} - -export class ProfileStatusDto { - status: string; -} - -export class ProfilePictureDto { - number?: string; - // url or base64 - picture?: string; -} - -class Key { - id: string; - fromMe: boolean; - remoteJid: string; -} -export class ReadMessageDto { - read_messages: Key[]; -} - -export class LastMessage { - key: Key; - messageTimestamp?: number; -} - -export class ArchiveChatDto { - lastMessage?: LastMessage; - chat?: string; - archive: boolean; -} - -class PrivacySetting { - readreceipts: WAReadReceiptsValue; - profile: WAPrivacyValue; - status: WAPrivacyValue; - online: WAPrivacyOnlineValue; - last: WAPrivacyValue; - groupadd: WAPrivacyValue; -} - -export class PrivacySettingDto { - privacySettings: PrivacySetting; -} - -export class DeleteMessage { - id: string; - fromMe: boolean; - remoteJid: string; - participant?: string; -} +import { proto, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; + +export class OnWhatsAppDto { + constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {} +} + +export class getBase64FromMediaMessageDto { + message: proto.WebMessageInfo; + convertToMp4?: boolean; +} + +export class WhatsAppNumberDto { + numbers: string[]; +} + +export class NumberDto { + number: string; +} + +export class NumberBusiness { + wid?: string; + jid?: string; + exists?: boolean; + isBusiness: boolean; + name?: string; + message?: string; + description?: string; + email?: string; + website?: string[]; + address?: string; +} + +export class ProfileNameDto { + name: string; +} + +export class ProfileStatusDto { + status: string; +} + +export class ProfilePictureDto { + number?: string; + // url or base64 + picture?: string; +} + +class Key { + id: string; + fromMe: boolean; + remoteJid: string; +} +export class ReadMessageDto { + read_messages: Key[]; +} + +export class LastMessage { + key: Key; + messageTimestamp?: number; +} + +export class ArchiveChatDto { + lastMessage?: LastMessage; + chat?: string; + archive: boolean; +} + +class PrivacySetting { + readreceipts: WAReadReceiptsValue; + profile: WAPrivacyValue; + status: WAPrivacyValue; + online: WAPrivacyOnlineValue; + last: WAPrivacyValue; + groupadd: WAPrivacyValue; +} + +export class PrivacySettingDto { + privacySettings: PrivacySetting; +} + +export class DeleteMessage { + id: string; + fromMe: boolean; + remoteJid: string; + participant?: string; +} diff --git a/src/whatsapp/dto/chatwoot.dto.ts b/src/whatsapp/dto/chatwoot.dto.ts old mode 100644 new mode 100755 index b270c869..5617dadb --- a/src/whatsapp/dto/chatwoot.dto.ts +++ b/src/whatsapp/dto/chatwoot.dto.ts @@ -4,6 +4,7 @@ export class ChatwootDto { token?: string; url?: string; name_inbox?: string; + id_inbox?: string; sign_msg?: boolean; number?: string; reopen_conversation?: boolean; diff --git a/src/whatsapp/dto/contactopenai.dto.ts b/src/whatsapp/dto/contactopenai.dto.ts new file mode 100755 index 00000000..19c86aa8 --- /dev/null +++ b/src/whatsapp/dto/contactopenai.dto.ts @@ -0,0 +1,5 @@ +export class ContactOpenaiDto { + contact?: string; + enabled: boolean; + owner: string; +} diff --git a/src/whatsapp/dto/group.dto.ts b/src/whatsapp/dto/group.dto.ts old mode 100644 new mode 100755 index 6dfdc45c..9c0d84ca --- a/src/whatsapp/dto/group.dto.ts +++ b/src/whatsapp/dto/group.dto.ts @@ -1,52 +1,52 @@ -export class CreateGroupDto { - subject: string; - participants: string[]; - description?: string; - promoteParticipants?: boolean; -} - -export class GroupPictureDto { - groupJid: string; - image: string; -} - -export class GroupSubjectDto { - groupJid: string; - subject: string; -} - -export class GroupDescriptionDto { - groupJid: string; - description: string; -} - -export class GroupJid { - groupJid: string; -} - -export class GetParticipant { - getParticipants: string; -} - -export class GroupInvite { - inviteCode: string; -} - -export class GroupSendInvite { - groupJid: string; - description: string; - numbers: string[]; -} - -export class GroupUpdateParticipantDto extends GroupJid { - action: 'add' | 'remove' | 'promote' | 'demote'; - participants: string[]; -} - -export class GroupUpdateSettingDto extends GroupJid { - action: 'announcement' | 'not_announcement' | 'unlocked' | 'locked'; -} - -export class GroupToggleEphemeralDto extends GroupJid { - expiration: 0 | 86400 | 604800 | 7776000; -} +export class CreateGroupDto { + subject: string; + participants: string[]; + description?: string; + promoteParticipants?: boolean; +} + +export class GroupPictureDto { + groupJid: string; + image: string; +} + +export class GroupSubjectDto { + groupJid: string; + subject: string; +} + +export class GroupDescriptionDto { + groupJid: string; + description: string; +} + +export class GroupJid { + groupJid: string; +} + +export class GetParticipant { + getParticipants: string; +} + +export class GroupInvite { + inviteCode: string; +} + +export class GroupSendInvite { + groupJid: string; + description: string; + numbers: string[]; +} + +export class GroupUpdateParticipantDto extends GroupJid { + action: 'add' | 'remove' | 'promote' | 'demote'; + participants: string[]; +} + +export class GroupUpdateSettingDto extends GroupJid { + action: 'announcement' | 'not_announcement' | 'unlocked' | 'locked'; +} + +export class GroupToggleEphemeralDto extends GroupJid { + expiration: 0 | 86400 | 604800 | 7776000; +} diff --git a/src/whatsapp/dto/instance.dto.ts b/src/whatsapp/dto/instance.dto.ts old mode 100644 new mode 100755 index c63620c5..ffdedd4e --- a/src/whatsapp/dto/instance.dto.ts +++ b/src/whatsapp/dto/instance.dto.ts @@ -1,36 +1,44 @@ -export class InstanceDto { - instanceName: string; - qrcode?: boolean; - number?: string; - token?: string; - webhook?: string; - webhook_by_events?: boolean; - webhook_base64?: boolean; - events?: string[]; - reject_call?: boolean; - msg_call?: string; - groups_ignore?: boolean; - always_online?: boolean; - read_messages?: boolean; - read_status?: boolean; - chatwoot_account_id?: string; - chatwoot_token?: string; - chatwoot_url?: string; - chatwoot_sign_msg?: boolean; - chatwoot_reopen_conversation?: boolean; - chatwoot_conversation_pending?: boolean; - websocket_enabled?: boolean; - websocket_events?: string[]; - rabbitmq_enabled?: boolean; - rabbitmq_events?: string[]; - sqs_enabled?: boolean; - sqs_events?: string[]; - typebot_url?: string; - typebot?: string; - typebot_expire?: number; - typebot_keyword_finish?: string; - typebot_delay_message?: number; - typebot_unknown_message?: string; - typebot_listening_from_me?: boolean; - proxy?: string; -} +export class InstanceDto { + instanceName: string; + qrcode?: boolean; + number?: string; + token?: string; + webhook?: string; + webhook_by_events?: boolean; + webhook_base64?: boolean; + events?: string[]; + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + always_online?: boolean; + read_messages?: boolean; + read_status?: boolean; + chatwoot_account_id?: string; + chatwoot_token?: string; + chatwoot_url?: string; + chatwoot_sign_msg?: boolean; + chatwoot_reopen_conversation?: boolean; + chatwoot_conversation_pending?: boolean; + websocket_enabled?: boolean; + websocket_events?: string[]; + rabbitmq_enabled?: boolean; + rabbitmq_events?: string[]; + + openai_chave?: boolean; + openai_prompts?: string; + openai_enabled?: boolean; + openai_events?: string[]; + + sqs_enabled?: boolean; + sqs_events?: string[]; + + typebot_url?: string; + typebot?: string; + typebot_expire?: number; + typebot_keyword_finish?: string; + typebot_delay_message?: number; + typebot_unknown_message?: string; + typebot_listening_from_me?: boolean; + proxy_enabled?: boolean; + proxy?: string; +} diff --git a/src/whatsapp/dto/openai.dto.ts b/src/whatsapp/dto/openai.dto.ts new file mode 100755 index 00000000..33053a6d --- /dev/null +++ b/src/whatsapp/dto/openai.dto.ts @@ -0,0 +1,6 @@ +export class OpenaiDto { + chave?: string; + enabled: boolean; + prompts?: string; + events?: string[]; +} diff --git a/src/whatsapp/dto/proxy.dto.ts b/src/whatsapp/dto/proxy.dto.ts old mode 100644 new mode 100755 index 0b6b2e70..9c423acd --- a/src/whatsapp/dto/proxy.dto.ts +++ b/src/whatsapp/dto/proxy.dto.ts @@ -1,4 +1,4 @@ -export class ProxyDto { - enabled: boolean; - proxy: string; -} +export class ProxyDto { + enabled: boolean; + proxy: string; +} diff --git a/src/whatsapp/dto/rabbitmq.dto.ts b/src/whatsapp/dto/rabbitmq.dto.ts old mode 100644 new mode 100755 index 9bfd5b42..ee727109 --- a/src/whatsapp/dto/rabbitmq.dto.ts +++ b/src/whatsapp/dto/rabbitmq.dto.ts @@ -1,4 +1,4 @@ -export class RabbitmqDto { - enabled: boolean; - events?: string[]; -} +export class RabbitmqDto { + enabled: boolean; + events?: string[]; +} diff --git a/src/whatsapp/dto/sendMessage.dto.ts b/src/whatsapp/dto/sendMessage.dto.ts old mode 100644 new mode 100755 diff --git a/src/whatsapp/dto/settings.dto.ts b/src/whatsapp/dto/settings.dto.ts old mode 100644 new mode 100755 index 594ab3a4..7270d61a --- a/src/whatsapp/dto/settings.dto.ts +++ b/src/whatsapp/dto/settings.dto.ts @@ -1,8 +1,8 @@ -export class SettingsDto { - reject_call?: boolean; - msg_call?: string; - groups_ignore?: boolean; - always_online?: boolean; - read_messages?: boolean; - read_status?: boolean; -} +export class SettingsDto { + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + always_online?: boolean; + read_messages?: boolean; + read_status?: boolean; +} diff --git a/src/whatsapp/dto/sqs.dto.ts b/src/whatsapp/dto/sqs.dto.ts old mode 100644 new mode 100755 index 9b8aeedd..c36fd2cf --- a/src/whatsapp/dto/sqs.dto.ts +++ b/src/whatsapp/dto/sqs.dto.ts @@ -1,4 +1,4 @@ -export class SqsDto { - enabled: boolean; - events?: string[]; -} +export class SqsDto { + enabled: boolean; + events?: string[]; +} \ No newline at end of file diff --git a/src/whatsapp/dto/typebot.dto.ts b/src/whatsapp/dto/typebot.dto.ts old mode 100644 new mode 100755 index c6c1fbdd..56872570 --- a/src/whatsapp/dto/typebot.dto.ts +++ b/src/whatsapp/dto/typebot.dto.ts @@ -1,26 +1,26 @@ -export class Session { - remoteJid?: string; - sessionId?: string; - status?: string; - createdAt?: number; - updateAt?: number; - prefilledVariables?: PrefilledVariables; -} - -export class PrefilledVariables { - remoteJid?: string; - pushName?: string; - additionalData?: { [key: string]: any }; -} - -export class TypebotDto { - enabled?: boolean; - url: string; - typebot?: string; - expire?: number; - keyword_finish?: string; - delay_message?: number; - unknown_message?: string; - listening_from_me?: boolean; - sessions?: Session[]; -} +export class Session { + remoteJid?: string; + sessionId?: string; + status?: string; + createdAt?: number; + updateAt?: number; + prefilledVariables?: PrefilledVariables; +} + +export class PrefilledVariables { + remoteJid?: string; + pushName?: string; + additionalData?: { [key: string]: any }; +} + +export class TypebotDto { + enabled?: boolean; + url: string; + typebot?: string; + expire?: number; + keyword_finish?: string; + delay_message?: number; + unknown_message?: string; + listening_from_me?: boolean; + sessions?: Session[]; +} diff --git a/src/whatsapp/dto/webhook.dto.ts b/src/whatsapp/dto/webhook.dto.ts old mode 100644 new mode 100755 index 87a21883..c260ad1a --- a/src/whatsapp/dto/webhook.dto.ts +++ b/src/whatsapp/dto/webhook.dto.ts @@ -1,7 +1,7 @@ -export class WebhookDto { - enabled?: boolean; - url?: string; - events?: string[]; - webhook_by_events?: boolean; - webhook_base64?: boolean; -} +export class WebhookDto { + enabled?: boolean; + url?: string; + events?: string[]; + webhook_by_events?: boolean; + webhook_base64?: boolean; +} diff --git a/src/whatsapp/dto/websocket.dto.ts b/src/whatsapp/dto/websocket.dto.ts old mode 100644 new mode 100755 index 27f6d785..143e184c --- a/src/whatsapp/dto/websocket.dto.ts +++ b/src/whatsapp/dto/websocket.dto.ts @@ -1,4 +1,4 @@ -export class WebsocketDto { - enabled: boolean; - events?: string[]; -} +export class WebsocketDto { + enabled: boolean; + events?: string[]; +} diff --git a/src/whatsapp/guards/auth.guard.ts b/src/whatsapp/guards/auth.guard.ts old mode 100644 new mode 100755 index a72ebfff..b73eb414 --- a/src/whatsapp/guards/auth.guard.ts +++ b/src/whatsapp/guards/auth.guard.ts @@ -1,83 +1,83 @@ -import { isJWT } from 'class-validator'; -import { NextFunction, Request, Response } from 'express'; -import jwt from 'jsonwebtoken'; - -import { name } from '../../../package.json'; -import { Auth, configService } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { ForbiddenException, UnauthorizedException } from '../../exceptions'; -import { InstanceDto } from '../dto/instance.dto'; -import { JwtPayload } from '../services/auth.service'; -import { repository } from '../whatsapp.module'; - -const logger = new Logger('GUARD'); - -async function jwtGuard(req: Request, res: Response, next: NextFunction) { - const key = req.get('apikey'); - - if (key && configService.get('AUTHENTICATION').API_KEY.KEY !== key) { - throw new UnauthorizedException(); - } - - if (configService.get('AUTHENTICATION').API_KEY.KEY === key) { - return next(); - } - - if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { - throw new ForbiddenException('Missing global api key', 'The global api key must be set'); - } - - const jwtOpts = configService.get('AUTHENTICATION').JWT; - try { - const [bearer, token] = req.get('authorization').split(' '); - - if (bearer.toLowerCase() !== 'bearer') { - throw new UnauthorizedException(); - } - - if (!isJWT(token)) { - throw new UnauthorizedException(); - } - - const param = req.params as unknown as InstanceDto; - const decode = jwt.verify(token, jwtOpts.SECRET, { - ignoreExpiration: jwtOpts.EXPIRIN_IN === 0, - }) as JwtPayload; - - if (param.instanceName !== decode.instanceName || name !== decode.apiName) { - throw new UnauthorizedException(); - } - - return next(); - } catch (error) { - logger.error(error); - throw new UnauthorizedException(); - } -} - -async function apikey(req: Request, _: Response, next: NextFunction) { - const env = configService.get('AUTHENTICATION').API_KEY; - const key = req.get('apikey'); - - if (env.KEY === key) { - return next(); - } - - if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { - throw new ForbiddenException('Missing global api key', 'The global api key must be set'); - } - - try { - const param = req.params as unknown as InstanceDto; - const instanceKey = await repository.auth.find(param.instanceName); - if (instanceKey.apikey === key) { - return next(); - } - } catch (error) { - logger.error(error); - } - - throw new UnauthorizedException(); -} - -export const authGuard = { jwt: jwtGuard, apikey }; +import { isJWT } from 'class-validator'; +import { NextFunction, Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; + +import { name } from '../../../package.json'; +import { Auth, configService } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { ForbiddenException, UnauthorizedException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { JwtPayload } from '../services/auth.service'; +import { repository } from '../whatsapp.module'; + +const logger = new Logger('GUARD'); + +async function jwtGuard(req: Request, res: Response, next: NextFunction) { + const key = req.get('apikey'); + + if (key && configService.get('AUTHENTICATION').API_KEY.KEY !== key) { + throw new UnauthorizedException(); + } + + if (configService.get('AUTHENTICATION').API_KEY.KEY === key) { + return next(); + } + + if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { + throw new ForbiddenException('Missing global api key', 'The global api key must be set'); + } + + const jwtOpts = configService.get('AUTHENTICATION').JWT; + try { + const [bearer, token] = req.get('authorization').split(' '); + + if (bearer.toLowerCase() !== 'bearer') { + throw new UnauthorizedException(); + } + + if (!isJWT(token)) { + throw new UnauthorizedException(); + } + + const param = req.params as unknown as InstanceDto; + const decode = jwt.verify(token, jwtOpts.SECRET, { + ignoreExpiration: jwtOpts.EXPIRIN_IN === 0, + }) as JwtPayload; + + if (param.instanceName !== decode.instanceName || name !== decode.apiName) { + throw new UnauthorizedException(); + } + + return next(); + } catch (error) { + logger.error(error); + throw new UnauthorizedException(); + } +} + +async function apikey(req: Request, _: Response, next: NextFunction) { + const env = configService.get('AUTHENTICATION').API_KEY; + const key = req.get('apikey'); + + if (env.KEY === key) { + return next(); + } + + if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { + throw new ForbiddenException('Missing global api key', 'The global api key must be set'); + } + + try { + const param = req.params as unknown as InstanceDto; + const instanceKey = await repository.auth.find(param.instanceName); + if (instanceKey.apikey === key) { + return next(); + } + } catch (error) { + logger.error(error); + } + + throw new UnauthorizedException(); +} + +export const authGuard = { jwt: jwtGuard, apikey }; diff --git a/src/whatsapp/guards/instance.guard.ts b/src/whatsapp/guards/instance.guard.ts old mode 100644 new mode 100755 index 6b193411..bc0a8753 --- a/src/whatsapp/guards/instance.guard.ts +++ b/src/whatsapp/guards/instance.guard.ts @@ -1,74 +1,76 @@ -import { NextFunction, Request, Response } from 'express'; -import { existsSync } from 'fs'; -import { join } from 'path'; - -import { configService, Database, Redis } from '../../config/env.config'; -import { INSTANCE_DIR } from '../../config/path.config'; -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) { - try { - const db = configService.get('DATABASE'); - const redisConf = configService.get('REDIS'); - - const exists = !!waMonitor.waInstances[instanceName]; - - 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()); - } -} - -export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) { - if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) { - return next(); - } - - const param = req.params as unknown as InstanceDto; - if (!param?.instanceName) { - throw new BadRequestException('"instanceName" not provided.'); - } - - if (!(await getInstance(param.instanceName))) { - throw new NotFoundException(`The "${param.instanceName}" instance does not exist`); - } - - next(); -} - -export async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) { - if (req.originalUrl.includes('/instance/create')) { - const instance = req.body as InstanceDto; - if (await getInstance(instance.instanceName)) { - throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`); - } - - if (waMonitor.waInstances[instance.instanceName]) { - waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues(); - delete waMonitor.waInstances[instance.instanceName]; - } - } - - next(); -} +import { NextFunction, Request, Response } from 'express'; +import { existsSync } from 'fs'; +import { join } from 'path'; + +import { configService, Database, Redis } from '../../config/env.config'; +import { INSTANCE_DIR } from '../../config/path.config'; +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) { + try { + const db = configService.get('DATABASE'); + const redisConf = configService.get('REDIS'); + + const exists = !!waMonitor.waInstances[instanceName]; + + if (redisConf.ENABLED) { + const keyExists = await cache.keyExists(); + return exists || keyExists; + } + + if (db.ENABLED) { + const collection = dbserver + .getClient() + .db( + db.CONNECTION.DB_PREFIX_NAME + + db.CONNECTION.DB_PREFIX_FINAL_NAME) + .collection(instanceName); + return exists || (await collection.find({}).toArray()).length > 0; + } + + return exists || existsSync(join(INSTANCE_DIR, instanceName)); + } catch (error) { + throw new InternalServerErrorException(error?.toString()); + } +} + +export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) { + if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) { + return next(); + } + + const param = req.params as unknown as InstanceDto; + if (!param?.instanceName) { + throw new BadRequestException('"instanceName" not provided.'); + } + + if (!(await getInstance(param.instanceName))) { + throw new NotFoundException(`The "${param.instanceName}" instance does not exist`); + } + + next(); +} + +export async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) { + if (req.originalUrl.includes('/instance/create')) { + const instance = req.body as InstanceDto; + if (await getInstance(instance.instanceName)) { + throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`); + } + + if (waMonitor.waInstances[instance.instanceName]) { + waMonitor.waInstances[instance.instanceName]?.removeRabbitmqQueues(); + delete waMonitor.waInstances[instance.instanceName]; + } + } + + next(); +} diff --git a/src/whatsapp/models/auth.model.ts b/src/whatsapp/models/auth.model.ts old mode 100644 new mode 100755 index 0f7d5ec3..1852faa9 --- a/src/whatsapp/models/auth.model.ts +++ b/src/whatsapp/models/auth.model.ts @@ -1,18 +1,18 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class AuthRaw { - _id?: string; - jwt?: string; - apikey?: string; -} - -const authSchema = new Schema({ - _id: { type: String, _id: true }, - jwt: { type: String, minlength: 1 }, - apikey: { type: String, minlength: 1 }, -}); - -export const AuthModel = dbserver?.model(AuthRaw.name, authSchema, 'authentication'); -export type IAuthModel = typeof AuthModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class AuthRaw { + _id?: string; + jwt?: string; + apikey?: string; +} + +const authSchema = new Schema({ + _id: { type: String, _id: true }, + jwt: { type: String, minlength: 1 }, + apikey: { type: String, minlength: 1 }, +}); + +export const AuthModel = dbserver?.model(AuthRaw.name, authSchema, 'authentication'); +export type IAuthModel = typeof AuthModel; diff --git a/src/whatsapp/models/chamaai.model.ts b/src/whatsapp/models/chamaai.model.ts old mode 100644 new mode 100755 index d3d10aff..6afb8b28 --- a/src/whatsapp/models/chamaai.model.ts +++ b/src/whatsapp/models/chamaai.model.ts @@ -1,24 +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({ - _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; +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({ + _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; diff --git a/src/whatsapp/models/chat.model.ts b/src/whatsapp/models/chat.model.ts old mode 100644 new mode 100755 index 053d100e..1e26cc07 --- a/src/whatsapp/models/chat.model.ts +++ b/src/whatsapp/models/chat.model.ts @@ -1,19 +1,19 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class ChatRaw { - _id?: string; - id?: string; - owner: string; - lastMsgTimestamp?: number; -} - -const chatSchema = new Schema({ - _id: { type: String, _id: true }, - id: { type: String, required: true, minlength: 1 }, - owner: { type: String, required: true, minlength: 1 }, -}); - -export const ChatModel = dbserver?.model(ChatRaw.name, chatSchema, 'chats'); -export type IChatModel = typeof ChatModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class ChatRaw { + _id?: string; + id?: string; + owner: string; + lastMsgTimestamp?: number; +} + +const chatSchema = new Schema({ + _id: { type: String, _id: true }, + id: { type: String, required: true, minlength: 1 }, + owner: { type: String, required: true, minlength: 1 }, +}); + +export const ChatModel = dbserver?.model(ChatRaw.name, chatSchema, 'chats'); +export type IChatModel = typeof ChatModel; diff --git a/src/whatsapp/models/chatwoot.model.ts b/src/whatsapp/models/chatwoot.model.ts old mode 100644 new mode 100755 index ed7c2ef0..433c2ba5 --- a/src/whatsapp/models/chatwoot.model.ts +++ b/src/whatsapp/models/chatwoot.model.ts @@ -9,6 +9,7 @@ export class ChatwootRaw { token?: string; url?: string; name_inbox?: string; + id_inbox?: string; sign_msg?: boolean; number?: string; reopen_conversation?: boolean; @@ -22,6 +23,7 @@ const chatwootSchema = new Schema({ token: { type: String, required: true }, url: { type: String, required: true }, name_inbox: { type: String, required: true }, + id_inbox: { type: String, required: true }, sign_msg: { type: Boolean, required: true }, number: { type: String, required: true }, reopen_conversation: { type: Boolean, required: true }, diff --git a/src/whatsapp/models/contact.model.ts b/src/whatsapp/models/contact.model.ts old mode 100644 new mode 100755 index f5936b61..c9356b96 --- a/src/whatsapp/models/contact.model.ts +++ b/src/whatsapp/models/contact.model.ts @@ -1,22 +1,22 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class ContactRaw { - _id?: string; - pushName?: string; - id?: string; - profilePictureUrl?: string; - owner: string; -} - -const contactSchema = new Schema({ - _id: { type: String, _id: true }, - pushName: { type: String, minlength: 1 }, - id: { type: String, required: true, minlength: 1 }, - profilePictureUrl: { type: String, minlength: 1 }, - owner: { type: String, required: true, minlength: 1 }, -}); - -export const ContactModel = dbserver?.model(ContactRaw.name, contactSchema, 'contacts'); -export type IContactModel = typeof ContactModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class ContactRaw { + _id?: string; + pushName?: string; + id?: string; + profilePictureUrl?: string; + owner: string; +} + +const contactSchema = new Schema({ + _id: { type: String, _id: true }, + pushName: { type: String, minlength: 1 }, + id: { type: String, required: true, minlength: 1 }, + profilePictureUrl: { type: String, minlength: 1 }, + owner: { type: String, required: true, minlength: 1 }, +}); + +export const ContactModel = dbserver?.model(ContactRaw.name, contactSchema, 'contacts'); +export type IContactModel = typeof ContactModel; diff --git a/src/whatsapp/models/contactOpenai.model.ts b/src/whatsapp/models/contactOpenai.model.ts new file mode 100755 index 00000000..3a673d15 --- /dev/null +++ b/src/whatsapp/models/contactOpenai.model.ts @@ -0,0 +1,20 @@ +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class ContactOpenaiRaw { + _id?: string; + contact?: string; + enabled?: boolean; + owner: string; +} + +const contactOpenaiSchema = new Schema({ + _id: { type: String, _id: true }, + contact: { type: String, required: true, minlength: 1 }, + enabled: { type: Boolean, required: true }, + owner: { type: String, required: true, minlength: 1 }, +}); + +export const ContactOpenaiModel = dbserver?.model(ContactOpenaiRaw.name, contactOpenaiSchema, 'openai_contacts'); +export type IContactOpenaiModel = typeof ContactOpenaiModel; diff --git a/src/whatsapp/models/index.ts b/src/whatsapp/models/index.ts old mode 100644 new mode 100755 index 7903e5b5..76eaf240 --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -1,13 +1,15 @@ -export * from './auth.model'; -export * from './chamaai.model'; -export * from './chat.model'; -export * from './chatwoot.model'; -export * from './contact.model'; -export * from './message.model'; -export * from './proxy.model'; -export * from './rabbitmq.model'; -export * from './settings.model'; -export * from './sqs.model'; -export * from './typebot.model'; -export * from './webhook.model'; -export * from './websocket.model'; +export * from './auth.model'; +export * from './chamaai.model'; +export * from './chat.model'; +export * from './chatwoot.model'; +export * from './contact.model'; +export * from './message.model'; +export * from './proxy.model'; +export * from './rabbitmq.model'; +export * from './settings.model'; +export * from './sqs.model'; +export * from './typebot.model'; +export * from './webhook.model'; +export * from './websocket.model'; +export * from './openai.model'; +export * from './contactOpenai.model'; diff --git a/src/whatsapp/models/message.model.ts b/src/whatsapp/models/message.model.ts old mode 100644 new mode 100755 index 252cd6e4..e87d22a9 --- a/src/whatsapp/models/message.model.ts +++ b/src/whatsapp/models/message.model.ts @@ -1,71 +1,71 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; -import { wa } from '../types/wa.types'; - -class Key { - id?: string; - remoteJid?: string; - fromMe?: boolean; - participant?: string; -} - -export class MessageRaw { - _id?: string; - key?: Key; - pushName?: string; - participant?: string; - message?: object; - messageType?: string; - messageTimestamp?: number | Long.Long; - owner: string; - source?: 'android' | 'web' | 'ios'; - source_id?: string; - source_reply_id?: string; -} - -const messageSchema = new Schema({ - _id: { type: String, _id: true }, - key: { - id: { type: String, required: true, minlength: 1 }, - remoteJid: { type: String, required: true, minlength: 1 }, - fromMe: { type: Boolean, required: true }, - participant: { type: String, minlength: 1 }, - }, - pushName: { type: String }, - participant: { type: String }, - messageType: { type: String }, - message: { type: Object }, - source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] }, - messageTimestamp: { type: Number, required: true }, - owner: { type: String, required: true, minlength: 1 }, -}); - -export const MessageModel = dbserver?.model(MessageRaw.name, messageSchema, 'messages'); -export type IMessageModel = typeof MessageModel; - -export class MessageUpdateRaw { - _id?: string; - remoteJid?: string; - id?: string; - fromMe?: boolean; - participant?: string; - datetime?: number; - status?: wa.StatusMessage; - owner: string; - pollUpdates?: any; -} - -const messageUpdateSchema = new Schema({ - _id: { type: String, _id: true }, - remoteJid: { type: String, required: true, min: 1 }, - id: { type: String, required: true, min: 1 }, - fromMe: { type: Boolean, required: true }, - participant: { type: String, min: 1 }, - datetime: { type: Number, required: true, min: 1 }, - status: { type: String, required: true }, - owner: { type: String, required: true, min: 1 }, -}); - -export const MessageUpModel = dbserver?.model(MessageUpdateRaw.name, messageUpdateSchema, 'messageUpdate'); -export type IMessageUpModel = typeof MessageUpModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; +import { wa } from '../types/wa.types'; + +class Key { + id?: string; + remoteJid?: string; + fromMe?: boolean; + participant?: string; +} + +export class MessageRaw { + _id?: string; + key?: Key; + pushName?: string; + participant?: string; + message?: object; + messageType?: string; + messageTimestamp?: number | Long.Long; + owner: string; + source?: 'android' | 'web' | 'ios'; + source_id?: string; + source_reply_id?: string; +} + +const messageSchema = new Schema({ + _id: { type: String, _id: true }, + key: { + id: { type: String, required: true, minlength: 1 }, + remoteJid: { type: String, required: true, minlength: 1 }, + fromMe: { type: Boolean, required: true }, + participant: { type: String, minlength: 1 }, + }, + pushName: { type: String }, + participant: { type: String }, + messageType: { type: String }, + message: { type: Object }, + source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] }, + messageTimestamp: { type: Number, required: true }, + owner: { type: String, required: true, minlength: 1 }, +}); + +export const MessageModel = dbserver?.model(MessageRaw.name, messageSchema, 'messages'); +export type IMessageModel = typeof MessageModel; + +export class MessageUpdateRaw { + _id?: string; + remoteJid?: string; + id?: string; + fromMe?: boolean; + participant?: string; + datetime?: number; + status?: wa.StatusMessage; + owner: string; + pollUpdates?: any; +} + +const messageUpdateSchema = new Schema({ + _id: { type: String, _id: true }, + remoteJid: { type: String, required: true, min: 1 }, + id: { type: String, required: true, min: 1 }, + fromMe: { type: Boolean, required: true }, + participant: { type: String, min: 1 }, + datetime: { type: Number, required: true, min: 1 }, + status: { type: String, required: true }, + owner: { type: String, required: true, min: 1 }, +}); + +export const MessageUpModel = dbserver?.model(MessageUpdateRaw.name, messageUpdateSchema, 'messageUpdate'); +export type IMessageUpModel = typeof MessageUpModel; diff --git a/src/whatsapp/models/openai.model.ts b/src/whatsapp/models/openai.model.ts new file mode 100755 index 00000000..e7c3eded --- /dev/null +++ b/src/whatsapp/models/openai.model.ts @@ -0,0 +1,22 @@ +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class OpenaiRaw { + _id?: string; + chave?: string; + prompts?: string; + enabled?: boolean; + events?: string[]; +} + +const openaiSchema = new Schema({ + _id: { type: String, _id: true }, + chave: { type: String, required: true }, + prompts: { type: String, required: false }, + enabled: { type: Boolean, required: true }, + events: { type: [String], required: true }, +}); + +export const OpenaiModel = dbserver?.model(OpenaiRaw.name, openaiSchema, 'openai'); +export type IOpenaiModel = typeof OpenaiModel; diff --git a/src/whatsapp/models/proxy.model.ts b/src/whatsapp/models/proxy.model.ts old mode 100644 new mode 100755 index 3dea4f0c..84aaf6e0 --- a/src/whatsapp/models/proxy.model.ts +++ b/src/whatsapp/models/proxy.model.ts @@ -1,18 +1,18 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class ProxyRaw { - _id?: string; - enabled?: boolean; - proxy?: string; -} - -const proxySchema = new Schema({ - _id: { type: String, _id: true }, - enabled: { type: Boolean, required: true }, - proxy: { type: String, required: true }, -}); - -export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy'); -export type IProxyModel = typeof ProxyModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class ProxyRaw { + _id?: string; + enabled?: boolean; + proxy?: string; +} + +const proxySchema = new Schema({ + _id: { type: String, _id: true }, + enabled: { type: Boolean, required: true }, + proxy: { type: String, required: true }, +}); + +export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy'); +export type IProxyModel = typeof ProxyModel; diff --git a/src/whatsapp/models/rabbitmq.model.ts b/src/whatsapp/models/rabbitmq.model.ts old mode 100644 new mode 100755 index e89ee3b1..1a56b32a --- a/src/whatsapp/models/rabbitmq.model.ts +++ b/src/whatsapp/models/rabbitmq.model.ts @@ -1,18 +1,18 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class RabbitmqRaw { - _id?: string; - enabled?: boolean; - events?: string[]; -} - -const rabbitmqSchema = new Schema({ - _id: { type: String, _id: true }, - enabled: { type: Boolean, required: true }, - events: { type: [String], required: true }, -}); - -export const RabbitmqModel = dbserver?.model(RabbitmqRaw.name, rabbitmqSchema, 'rabbitmq'); -export type IRabbitmqModel = typeof RabbitmqModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class RabbitmqRaw { + _id?: string; + enabled?: boolean; + events?: string[]; +} + +const rabbitmqSchema = new Schema({ + _id: { type: String, _id: true }, + enabled: { type: Boolean, required: true }, + events: { type: [String], required: true }, +}); + +export const RabbitmqModel = dbserver?.model(RabbitmqRaw.name, rabbitmqSchema, 'rabbitmq'); +export type IRabbitmqModel = typeof RabbitmqModel; diff --git a/src/whatsapp/models/settings.model.ts b/src/whatsapp/models/settings.model.ts old mode 100644 new mode 100755 index 48593fbe..70588815 --- a/src/whatsapp/models/settings.model.ts +++ b/src/whatsapp/models/settings.model.ts @@ -1,26 +1,26 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class SettingsRaw { - _id?: string; - reject_call?: boolean; - msg_call?: string; - groups_ignore?: boolean; - always_online?: boolean; - read_messages?: boolean; - read_status?: boolean; -} - -const settingsSchema = new Schema({ - _id: { type: String, _id: true }, - reject_call: { type: Boolean, required: true }, - msg_call: { type: String, required: true }, - groups_ignore: { type: Boolean, required: true }, - always_online: { type: Boolean, required: true }, - read_messages: { type: Boolean, required: true }, - read_status: { type: Boolean, required: true }, -}); - -export const SettingsModel = dbserver?.model(SettingsRaw.name, settingsSchema, 'settings'); -export type ISettingsModel = typeof SettingsModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class SettingsRaw { + _id?: string; + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + always_online?: boolean; + read_messages?: boolean; + read_status?: boolean; +} + +const settingsSchema = new Schema({ + _id: { type: String, _id: true }, + reject_call: { type: Boolean, required: true }, + msg_call: { type: String, required: true }, + groups_ignore: { type: Boolean, required: true }, + always_online: { type: Boolean, required: true }, + read_messages: { type: Boolean, required: true }, + read_status: { type: Boolean, required: true }, +}); + +export const SettingsModel = dbserver?.model(SettingsRaw.name, settingsSchema, 'settings'); +export type ISettingsModel = typeof SettingsModel; diff --git a/src/whatsapp/models/sqs.model.ts b/src/whatsapp/models/sqs.model.ts old mode 100644 new mode 100755 index 2d5f432f..73d8c900 --- a/src/whatsapp/models/sqs.model.ts +++ b/src/whatsapp/models/sqs.model.ts @@ -1,18 +1,18 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class SqsRaw { - _id?: string; - enabled?: boolean; - events?: string[]; -} - -const sqsSchema = new Schema({ - _id: { type: String, _id: true }, - enabled: { type: Boolean, required: true }, - events: { type: [String], required: true }, -}); - -export const SqsModel = dbserver?.model(SqsRaw.name, sqsSchema, 'sqs'); -export type ISqsModel = typeof SqsModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class SqsRaw { + _id?: string; + enabled?: boolean; + events?: string[]; +} + +const sqsSchema = new Schema({ + _id: { type: String, _id: true }, + enabled: { type: Boolean, required: true }, + events: { type: [String], required: true }, +}); + +export const SqsModel = dbserver?.model(SqsRaw.name, sqsSchema, 'sqs'); +export type ISqsModel = typeof SqsModel; \ No newline at end of file diff --git a/src/whatsapp/models/typebot.model.ts b/src/whatsapp/models/typebot.model.ts old mode 100644 new mode 100755 index c8ae7105..010b8e29 --- a/src/whatsapp/models/typebot.model.ts +++ b/src/whatsapp/models/typebot.model.ts @@ -1,58 +1,58 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -class Session { - remoteJid?: string; - sessionId?: string; - status?: string; - createdAt?: number; - updateAt?: number; - prefilledVariables?: { - remoteJid?: string; - pushName?: string; - additionalData?: { [key: string]: any }; - }; -} - -export class TypebotRaw { - _id?: string; - enabled?: boolean; - url: string; - typebot?: string; - expire?: number; - keyword_finish?: string; - delay_message?: number; - unknown_message?: string; - listening_from_me?: boolean; - sessions?: Session[]; -} - -const typebotSchema = new Schema({ - _id: { type: String, _id: true }, - enabled: { type: Boolean, required: true }, - url: { type: String, required: true }, - typebot: { type: String, required: true }, - expire: { type: Number, required: true }, - 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 }, - sessionId: { type: String, required: true }, - status: { type: String, required: true }, - createdAt: { type: Number, required: true }, - updateAt: { type: Number, required: true }, - prefilledVariables: { - remoteJid: { type: String, required: false }, - pushName: { type: String, required: false }, - additionalData: { type: Schema.Types.Mixed, required: false }, - }, - }, - ], -}); - -export const TypebotModel = dbserver?.model(TypebotRaw.name, typebotSchema, 'typebot'); -export type ITypebotModel = typeof TypebotModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +class Session { + remoteJid?: string; + sessionId?: string; + status?: string; + createdAt?: number; + updateAt?: number; + prefilledVariables?: { + remoteJid?: string; + pushName?: string; + additionalData?: { [key: string]: any }; + }; +} + +export class TypebotRaw { + _id?: string; + enabled?: boolean; + url: string; + typebot?: string; + expire?: number; + keyword_finish?: string; + delay_message?: number; + unknown_message?: string; + listening_from_me?: boolean; + sessions?: Session[]; +} + +const typebotSchema = new Schema({ + _id: { type: String, _id: true }, + enabled: { type: Boolean, required: true }, + url: { type: String, required: true }, + typebot: { type: String, required: true }, + expire: { type: Number, required: true }, + 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 }, + sessionId: { type: String, required: true }, + status: { type: String, required: true }, + createdAt: { type: Number, required: true }, + updateAt: { type: Number, required: true }, + prefilledVariables: { + remoteJid: { type: String, required: false }, + pushName: { type: String, required: false }, + additionalData: { type: Schema.Types.Mixed, required: false }, + }, + }, + ], +}); + +export const TypebotModel = dbserver?.model(TypebotRaw.name, typebotSchema, 'typebot'); +export type ITypebotModel = typeof TypebotModel; diff --git a/src/whatsapp/models/webhook.model.ts b/src/whatsapp/models/webhook.model.ts old mode 100644 new mode 100755 index 9a1bb43d..31dd4c18 --- a/src/whatsapp/models/webhook.model.ts +++ b/src/whatsapp/models/webhook.model.ts @@ -1,24 +1,24 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class WebhookRaw { - _id?: string; - url?: string; - enabled?: boolean; - events?: string[]; - webhook_by_events?: boolean; - webhook_base64?: boolean; -} - -const webhookSchema = new Schema({ - _id: { type: String, _id: true }, - url: { type: String, required: true }, - enabled: { type: Boolean, required: true }, - events: { type: [String], required: true }, - webhook_by_events: { type: Boolean, required: true }, - webhook_base64: { type: Boolean, required: true }, -}); - -export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook'); -export type IWebhookModel = typeof WebhookModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class WebhookRaw { + _id?: string; + url?: string; + enabled?: boolean; + events?: string[]; + webhook_by_events?: boolean; + webhook_base64?: boolean; +} + +const webhookSchema = new Schema({ + _id: { type: String, _id: true }, + url: { type: String, required: true }, + enabled: { type: Boolean, required: true }, + events: { type: [String], required: true }, + webhook_by_events: { type: Boolean, required: true }, + webhook_base64: { type: Boolean, required: true }, +}); + +export const WebhookModel = dbserver?.model(WebhookRaw.name, webhookSchema, 'webhook'); +export type IWebhookModel = typeof WebhookModel; diff --git a/src/whatsapp/models/websocket.model.ts b/src/whatsapp/models/websocket.model.ts old mode 100644 new mode 100755 index e96332b1..36283283 --- a/src/whatsapp/models/websocket.model.ts +++ b/src/whatsapp/models/websocket.model.ts @@ -1,18 +1,18 @@ -import { Schema } from 'mongoose'; - -import { dbserver } from '../../libs/db.connect'; - -export class WebsocketRaw { - _id?: string; - enabled?: boolean; - events?: string[]; -} - -const websocketSchema = new Schema({ - _id: { type: String, _id: true }, - enabled: { type: Boolean, required: true }, - events: { type: [String], required: true }, -}); - -export const WebsocketModel = dbserver?.model(WebsocketRaw.name, websocketSchema, 'websocket'); -export type IWebsocketModel = typeof WebsocketModel; +import { Schema } from 'mongoose'; + +import { dbserver } from '../../libs/db.connect'; + +export class WebsocketRaw { + _id?: string; + enabled?: boolean; + events?: string[]; +} + +const websocketSchema = new Schema({ + _id: { type: String, _id: true }, + enabled: { type: Boolean, required: true }, + events: { type: [String], required: true }, +}); + +export const WebsocketModel = dbserver?.model(WebsocketRaw.name, websocketSchema, 'websocket'); +export type IWebsocketModel = typeof WebsocketModel; diff --git a/src/whatsapp/repository/auth.repository.ts b/src/whatsapp/repository/auth.repository.ts old mode 100644 new mode 100755 index 4da8980b..4c982327 --- a/src/whatsapp/repository/auth.repository.ts +++ b/src/whatsapp/repository/auth.repository.ts @@ -1,65 +1,65 @@ -import { readFileSync } from 'fs'; -import { join } from 'path'; - -import { Auth, ConfigService } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { AUTH_DIR } from '../../config/path.config'; -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { AuthRaw, IAuthModel } from '../models'; - -export class AuthRepository extends Repository { - constructor(private readonly authModel: IAuthModel, readonly configService: ConfigService) { - super(configService); - this.auth = configService.get('AUTHENTICATION'); - } - - private readonly auth: Auth; - private readonly logger = new Logger('AuthRepository'); - - public async create(data: AuthRaw, instance: string): Promise { - try { - this.logger.verbose('creating auth'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving auth to db'); - const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('auth saved to db: ' + insert.modifiedCount + ' auth'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving auth to store'); - - this.writeStore({ - path: join(AUTH_DIR, this.auth.TYPE), - fileName: instance, - data, - }); - this.logger.verbose('auth saved to store in path: ' + join(AUTH_DIR, this.auth.TYPE) + '/' + instance); - - this.logger.verbose('auth created'); - return { insertCount: 1 }; - } catch (error) { - return { error } as any; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding auth'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding auth in db'); - return await this.authModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding auth in store'); - - return JSON.parse( - readFileSync(join(AUTH_DIR, this.auth.TYPE, instance + '.json'), { - encoding: 'utf-8', - }), - ) as AuthRaw; - } catch (error) { - return {}; - } - } -} +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { Auth, ConfigService } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { AUTH_DIR } from '../../config/path.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { AuthRaw, IAuthModel } from '../models'; + +export class AuthRepository extends Repository { + constructor(private readonly authModel: IAuthModel, readonly configService: ConfigService) { + super(configService); + this.auth = configService.get('AUTHENTICATION'); + } + + private readonly auth: Auth; + private readonly logger = new Logger('AuthRepository'); + + public async create(data: AuthRaw, instance: string): Promise { + try { + this.logger.verbose('creating auth'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving auth to db'); + const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('auth saved to db: ' + insert.modifiedCount + ' auth'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving auth to store'); + + this.writeStore({ + path: join(AUTH_DIR, this.auth.TYPE), + fileName: instance, + data, + }); + this.logger.verbose('auth saved to store in path: ' + join(AUTH_DIR, this.auth.TYPE) + '/' + instance); + + this.logger.verbose('auth created'); + return { insertCount: 1 }; + } catch (error) { + return { error } as any; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding auth'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding auth in db'); + return await this.authModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding auth in store'); + + return JSON.parse( + readFileSync(join(AUTH_DIR, this.auth.TYPE, instance + '.json'), { + encoding: 'utf-8', + }), + ) as AuthRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/repository/chamaai.repository.ts b/src/whatsapp/repository/chamaai.repository.ts old mode 100644 new mode 100755 index a2009f41..9f64ec03 --- a/src/whatsapp/repository/chamaai.repository.ts +++ b/src/whatsapp/repository/chamaai.repository.ts @@ -1,62 +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 { - 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({ - 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 { - 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 {}; - } - } -} +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 { + 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({ + 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 { + 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 {}; + } + } +} diff --git a/src/whatsapp/repository/chat.repository.ts b/src/whatsapp/repository/chat.repository.ts old mode 100644 new mode 100755 index 68d653a4..96c3154f --- a/src/whatsapp/repository/chat.repository.ts +++ b/src/whatsapp/repository/chat.repository.ts @@ -1,117 +1,117 @@ -import { opendirSync, readFileSync, rmSync } from 'fs'; -import { join } from 'path'; - -import { ConfigService, StoreConf } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { ChatRaw, IChatModel } from '../models'; - -export class ChatQuery { - where: ChatRaw; -} - -export class ChatRepository extends Repository { - constructor(private readonly chatModel: IChatModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('ChatRepository'); - - public async insert(data: ChatRaw[], instanceName: string, saveDb = false): Promise { - this.logger.verbose('inserting chats'); - if (data.length === 0) { - this.logger.verbose('no chats to insert'); - return; - } - - try { - this.logger.verbose('saving chats to store'); - if (this.dbSettings.ENABLED && saveDb) { - this.logger.verbose('saving chats to db'); - const insert = await this.chatModel.insertMany([...data]); - - this.logger.verbose('chats saved to db: ' + insert.length + ' chats'); - return { insertCount: insert.length }; - } - - this.logger.verbose('saving chats to store'); - - const store = this.configService.get('STORE'); - - if (store.CHATS) { - this.logger.verbose('saving chats to store'); - data.forEach((chat) => { - this.writeStore({ - path: join(this.storePath, 'chats', instanceName), - fileName: chat.id, - data: chat, - }); - this.logger.verbose( - 'chats saved to store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id, - ); - }); - - this.logger.verbose('chats saved to store'); - return { insertCount: data.length }; - } - - this.logger.verbose('chats not saved to store'); - return { insertCount: 0 }; - } catch (error) { - return error; - } finally { - data = undefined; - } - } - - public async find(query: ChatQuery): Promise { - try { - this.logger.verbose('finding chats'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding chats in db'); - return await this.chatModel.find({ owner: query.where.owner }); - } - - this.logger.verbose('finding chats in store'); - - const chats: ChatRaw[] = []; - const openDir = opendirSync(join(this.storePath, 'chats', query.where.owner)); - for await (const dirent of openDir) { - if (dirent.isFile()) { - chats.push( - JSON.parse( - readFileSync(join(this.storePath, 'chats', query.where.owner, dirent.name), { - encoding: 'utf-8', - }), - ), - ); - } - } - - this.logger.verbose('chats found in store: ' + chats.length + ' chats'); - return chats; - } catch (error) { - return []; - } - } - - public async delete(query: ChatQuery) { - try { - this.logger.verbose('deleting chats'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('deleting chats in db'); - return await this.chatModel.deleteOne({ ...query.where }); - } - - this.logger.verbose('deleting chats in store'); - rmSync(join(this.storePath, 'chats', query.where.owner, query.where.id + '.josn'), { - force: true, - recursive: true, - }); - - return { deleted: { chatId: query.where.id } }; - } catch (error) { - return { error: error?.toString() }; - } - } -} +import { opendirSync, readFileSync, rmSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ChatRaw, IChatModel } from '../models'; + +export class ChatQuery { + where: ChatRaw; +} + +export class ChatRepository extends Repository { + constructor(private readonly chatModel: IChatModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('ChatRepository'); + + public async insert(data: ChatRaw[], instanceName: string, saveDb = false): Promise { + this.logger.verbose('inserting chats'); + if (data.length === 0) { + this.logger.verbose('no chats to insert'); + return; + } + + try { + this.logger.verbose('saving chats to store'); + if (this.dbSettings.ENABLED && saveDb) { + this.logger.verbose('saving chats to db'); + const insert = await this.chatModel.insertMany([...data]); + + this.logger.verbose('chats saved to db: ' + insert.length + ' chats'); + return { insertCount: insert.length }; + } + + this.logger.verbose('saving chats to store'); + + const store = this.configService.get('STORE'); + + if (store.CHATS) { + this.logger.verbose('saving chats to store'); + data.forEach((chat) => { + this.writeStore({ + path: join(this.storePath, 'chats', instanceName), + fileName: chat.id, + data: chat, + }); + this.logger.verbose( + 'chats saved to store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id, + ); + }); + + this.logger.verbose('chats saved to store'); + return { insertCount: data.length }; + } + + this.logger.verbose('chats not saved to store'); + return { insertCount: 0 }; + } catch (error) { + return error; + } finally { + data = undefined; + } + } + + public async find(query: ChatQuery): Promise { + try { + this.logger.verbose('finding chats'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding chats in db'); + return await this.chatModel.find({ owner: query.where.owner }); + } + + this.logger.verbose('finding chats in store'); + + const chats: ChatRaw[] = []; + const openDir = opendirSync(join(this.storePath, 'chats', query.where.owner)); + for await (const dirent of openDir) { + if (dirent.isFile()) { + chats.push( + JSON.parse( + readFileSync(join(this.storePath, 'chats', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), + ), + ); + } + } + + this.logger.verbose('chats found in store: ' + chats.length + ' chats'); + return chats; + } catch (error) { + return []; + } + } + + public async delete(query: ChatQuery) { + try { + this.logger.verbose('deleting chats'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('deleting chats in db'); + return await this.chatModel.deleteOne({ ...query.where }); + } + + this.logger.verbose('deleting chats in store'); + rmSync(join(this.storePath, 'chats', query.where.owner, query.where.id + '.josn'), { + force: true, + recursive: true, + }); + + return { deleted: { chatId: query.where.id } }; + } catch (error) { + return { error: error?.toString() }; + } + } +} diff --git a/src/whatsapp/repository/chatwoot.repository.ts b/src/whatsapp/repository/chatwoot.repository.ts old mode 100644 new mode 100755 index 47398d68..77241379 --- a/src/whatsapp/repository/chatwoot.repository.ts +++ b/src/whatsapp/repository/chatwoot.repository.ts @@ -1,62 +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 { ChatwootRaw, IChatwootModel } from '../models'; - -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 {}; - } - } -} +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 { ChatwootRaw, IChatwootModel } from '../models'; + +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/contact.repository.ts b/src/whatsapp/repository/contact.repository.ts old mode 100644 new mode 100755 index 03851607..58d6826a --- a/src/whatsapp/repository/contact.repository.ts +++ b/src/whatsapp/repository/contact.repository.ts @@ -1,171 +1,171 @@ -import { opendirSync, readFileSync } from 'fs'; -import { join } from 'path'; - -import { ConfigService, StoreConf } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { ContactRaw, IContactModel } from '../models'; - -export class ContactQuery { - where: ContactRaw; -} - -export class ContactRepository extends Repository { - constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('ContactRepository'); - - public async insert(data: ContactRaw[], instanceName: string, saveDb = false): Promise { - this.logger.verbose('inserting contacts'); - - if (data.length === 0) { - this.logger.verbose('no contacts to insert'); - return; - } - - try { - if (this.dbSettings.ENABLED && saveDb) { - this.logger.verbose('saving contacts to db'); - - const insert = await this.contactModel.insertMany([...data]); - - this.logger.verbose('contacts saved to db: ' + insert.length + ' contacts'); - return { insertCount: insert.length }; - } - - this.logger.verbose('saving contacts to store'); - - const store = this.configService.get('STORE'); - - if (store.CONTACTS) { - this.logger.verbose('saving contacts to store'); - data.forEach((contact) => { - this.writeStore({ - path: join(this.storePath, 'contacts', instanceName), - fileName: contact.id, - data: contact, - }); - this.logger.verbose( - 'contacts saved to store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id, - ); - }); - - this.logger.verbose('contacts saved to store: ' + data.length + ' contacts'); - return { insertCount: data.length }; - } - - this.logger.verbose('contacts not saved'); - return { insertCount: 0 }; - } catch (error) { - return error; - } finally { - data = undefined; - } - } - - public async update(data: ContactRaw[], instanceName: string, saveDb = false): Promise { - try { - this.logger.verbose('updating contacts'); - - if (data.length === 0) { - this.logger.verbose('no contacts to update'); - return; - } - - if (this.dbSettings.ENABLED && saveDb) { - this.logger.verbose('updating contacts in db'); - - const contacts = data.map((contact) => { - return { - updateOne: { - filter: { id: contact.id }, - update: { ...contact }, - upsert: true, - }, - }; - }); - - const { nModified } = await this.contactModel.bulkWrite(contacts); - - this.logger.verbose('contacts updated in db: ' + nModified + ' contacts'); - return { insertCount: nModified }; - } - - this.logger.verbose('updating contacts in store'); - - const store = this.configService.get('STORE'); - - if (store.CONTACTS) { - this.logger.verbose('updating contacts in store'); - data.forEach((contact) => { - this.writeStore({ - path: join(this.storePath, 'contacts', instanceName), - fileName: contact.id, - data: contact, - }); - this.logger.verbose( - 'contacts updated in store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id, - ); - }); - - this.logger.verbose('contacts updated in store: ' + data.length + ' contacts'); - - return { insertCount: data.length }; - } - - this.logger.verbose('contacts not updated'); - return { insertCount: 0 }; - } catch (error) { - return error; - } finally { - data = undefined; - } - } - - public async find(query: ContactQuery): Promise { - try { - this.logger.verbose('finding contacts'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding contacts in db'); - return await this.contactModel.find({ ...query.where }); - } - - this.logger.verbose('finding contacts in store'); - const contacts: ContactRaw[] = []; - if (query?.where?.id) { - this.logger.verbose('finding contacts in store by id'); - contacts.push( - JSON.parse( - readFileSync(join(this.storePath, 'contacts', query.where.owner, query.where.id + '.json'), { - encoding: 'utf-8', - }), - ), - ); - } else { - this.logger.verbose('finding contacts in store by owner'); - - const openDir = opendirSync(join(this.storePath, 'contacts', query.where.owner), { - encoding: 'utf-8', - }); - for await (const dirent of openDir) { - if (dirent.isFile()) { - contacts.push( - JSON.parse( - readFileSync(join(this.storePath, 'contacts', query.where.owner, dirent.name), { - encoding: 'utf-8', - }), - ), - ); - } - } - } - - this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts'); - return contacts; - } catch (error) { - return []; - } - } -} +import { opendirSync, readFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ContactRaw, IContactModel } from '../models'; + +export class ContactQuery { + where: ContactRaw; +} + +export class ContactRepository extends Repository { + constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('ContactRepository'); + + public async insert(data: ContactRaw[], instanceName: string, saveDb = false): Promise { + this.logger.verbose('inserting contacts'); + + if (data.length === 0) { + this.logger.verbose('no contacts to insert'); + return; + } + + try { + if (this.dbSettings.ENABLED && saveDb) { + this.logger.verbose('saving contacts to db'); + + const insert = await this.contactModel.insertMany([...data]); + + this.logger.verbose('contacts saved to db: ' + insert.length + ' contacts'); + return { insertCount: insert.length }; + } + + this.logger.verbose('saving contacts to store'); + + const store = this.configService.get('STORE'); + + if (store.CONTACTS) { + this.logger.verbose('saving contacts to store'); + data.forEach((contact) => { + this.writeStore({ + path: join(this.storePath, 'contacts', instanceName), + fileName: contact.id, + data: contact, + }); + this.logger.verbose( + 'contacts saved to store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id, + ); + }); + + this.logger.verbose('contacts saved to store: ' + data.length + ' contacts'); + return { insertCount: data.length }; + } + + this.logger.verbose('contacts not saved'); + return { insertCount: 0 }; + } catch (error) { + return error; + } finally { + data = undefined; + } + } + + public async update(data: ContactRaw[], instanceName: string, saveDb = false): Promise { + try { + this.logger.verbose('updating contacts'); + + if (data.length === 0) { + this.logger.verbose('no contacts to update'); + return; + } + + if (this.dbSettings.ENABLED && saveDb) { + this.logger.verbose('updating contacts in db'); + + const contacts = data.map((contact) => { + return { + updateOne: { + filter: { id: contact.id }, + update: { ...contact }, + upsert: true, + }, + }; + }); + + const { nModified } = await this.contactModel.bulkWrite(contacts); + + this.logger.verbose('contacts updated in db: ' + nModified + ' contacts'); + return { insertCount: nModified }; + } + + this.logger.verbose('updating contacts in store'); + + const store = this.configService.get('STORE'); + + if (store.CONTACTS) { + this.logger.verbose('updating contacts in store'); + data.forEach((contact) => { + this.writeStore({ + path: join(this.storePath, 'contacts', instanceName), + fileName: contact.id, + data: contact, + }); + this.logger.verbose( + 'contacts updated in store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id, + ); + }); + + this.logger.verbose('contacts updated in store: ' + data.length + ' contacts'); + + return { insertCount: data.length }; + } + + this.logger.verbose('contacts not updated'); + return { insertCount: 0 }; + } catch (error) { + return error; + } finally { + data = undefined; + } + } + + public async find(query: ContactQuery): Promise { + try { + this.logger.verbose('finding contacts'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding contacts in db'); + return await this.contactModel.find({ ...query.where }); + } + + this.logger.verbose('finding contacts in store'); + const contacts: ContactRaw[] = []; + if (query?.where?.id) { + this.logger.verbose('finding contacts in store by id'); + contacts.push( + JSON.parse( + readFileSync(join(this.storePath, 'contacts', query.where.owner, query.where.id + '.json'), { + encoding: 'utf-8', + }), + ), + ); + } else { + this.logger.verbose('finding contacts in store by owner'); + + const openDir = opendirSync(join(this.storePath, 'contacts', query.where.owner), { + encoding: 'utf-8', + }); + for await (const dirent of openDir) { + if (dirent.isFile()) { + contacts.push( + JSON.parse( + readFileSync(join(this.storePath, 'contacts', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), + ), + ); + } + } + } + + this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts'); + return contacts; + } catch (error) { + return []; + } + } +} diff --git a/src/whatsapp/repository/message.repository.ts b/src/whatsapp/repository/message.repository.ts old mode 100644 new mode 100755 index ed362815..5c39e3fd --- a/src/whatsapp/repository/message.repository.ts +++ b/src/whatsapp/repository/message.repository.ts @@ -1,147 +1,147 @@ -import { opendirSync, readFileSync } from 'fs'; -import { join } from 'path'; - -import { ConfigService, StoreConf } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { IMessageModel, MessageRaw } from '../models'; - -export class MessageQuery { - where: MessageRaw; - limit?: number; -} - -export class MessageRepository extends Repository { - constructor(private readonly messageModel: IMessageModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('MessageRepository'); - - public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise { - this.logger.verbose('inserting messages'); - - if (!Array.isArray(data) || data.length === 0) { - this.logger.verbose('no messages to insert'); - return; - } - - try { - if (this.dbSettings.ENABLED && saveDb) { - this.logger.verbose('saving messages to db'); - const cleanedData = data.map((obj) => { - const cleanedObj = { ...obj }; - if ('extendedTextMessage' in obj.message) { - const extendedTextMessage = obj.message.extendedTextMessage as { - contextInfo?: { - mentionedJid?: any; - }; - }; - - if (typeof extendedTextMessage === 'object' && extendedTextMessage !== null) { - if ('contextInfo' in extendedTextMessage) { - delete extendedTextMessage.contextInfo?.mentionedJid; - extendedTextMessage.contextInfo = {}; - } - } - } - return cleanedObj; - }); - - const insert = await this.messageModel.insertMany([...cleanedData]); - - this.logger.verbose('messages saved to db: ' + insert.length + ' messages'); - return { insertCount: insert.length }; - } - - this.logger.verbose('saving messages to store'); - - const store = this.configService.get('STORE'); - - if (store.MESSAGES) { - this.logger.verbose('saving messages to store'); - - data.forEach((message) => { - this.writeStore({ - path: join(this.storePath, 'messages', instanceName), - fileName: message.key.id, - data: message, - }); - this.logger.verbose( - 'messages saved to store in path: ' + join(this.storePath, 'messages', instanceName) + '/' + message.key.id, - ); - }); - - this.logger.verbose('messages saved to store: ' + data.length + ' messages'); - return { insertCount: data.length }; - } - - this.logger.verbose('messages not saved to store'); - return { insertCount: 0 }; - } catch (error) { - console.log('ERROR: ', error); - return error; - } finally { - data = undefined; - } - } - - public async find(query: MessageQuery) { - try { - this.logger.verbose('finding messages'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding messages in db'); - if (query?.where?.key) { - for (const [k, v] of Object.entries(query.where.key)) { - query.where['key.' + k] = v; - } - delete query?.where?.key; - } - - return await this.messageModel - .find({ ...query.where }) - .sort({ messageTimestamp: -1 }) - .limit(query?.limit ?? 0); - } - - this.logger.verbose('finding messages in store'); - const messages: MessageRaw[] = []; - if (query?.where?.key?.id) { - this.logger.verbose('finding messages in store by id'); - messages.push( - JSON.parse( - readFileSync(join(this.storePath, 'messages', query.where.owner, query.where.key.id + '.json'), { - encoding: 'utf-8', - }), - ), - ); - } else { - this.logger.verbose('finding messages in store by owner'); - const openDir = opendirSync(join(this.storePath, 'messages', query.where.owner), { - encoding: 'utf-8', - }); - - for await (const dirent of openDir) { - if (dirent.isFile()) { - messages.push( - JSON.parse( - readFileSync(join(this.storePath, 'messages', query.where.owner, dirent.name), { - encoding: 'utf-8', - }), - ), - ); - } - } - } - - this.logger.verbose('messages found in store: ' + messages.length + ' messages'); - return messages - .sort((x, y) => { - return (y.messageTimestamp as number) - (x.messageTimestamp as number); - }) - .splice(0, query?.limit ?? messages.length); - } catch (error) { - return []; - } - } -} +import { opendirSync, readFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { IMessageModel, MessageRaw } from '../models'; + +export class MessageQuery { + where: MessageRaw; + limit?: number; +} + +export class MessageRepository extends Repository { + constructor(private readonly messageModel: IMessageModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('MessageRepository'); + + public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise { + this.logger.verbose('inserting messages'); + + if (!Array.isArray(data) || data.length === 0) { + this.logger.verbose('no messages to insert'); + return; + } + + try { + if (this.dbSettings.ENABLED && saveDb) { + this.logger.verbose('saving messages to db'); + const cleanedData = data.map((obj) => { + const cleanedObj = { ...obj }; + if ('extendedTextMessage' in obj.message) { + const extendedTextMessage = obj.message.extendedTextMessage as { + contextInfo?: { + mentionedJid?: any; + }; + }; + + if (typeof extendedTextMessage === 'object' && extendedTextMessage !== null) { + if ('contextInfo' in extendedTextMessage) { + delete extendedTextMessage.contextInfo?.mentionedJid; + extendedTextMessage.contextInfo = {}; + } + } + } + return cleanedObj; + }); + + const insert = await this.messageModel.insertMany([...cleanedData]); + + this.logger.verbose('messages saved to db: ' + insert.length + ' messages'); + return { insertCount: insert.length }; + } + + this.logger.verbose('saving messages to store'); + + const store = this.configService.get('STORE'); + + if (store.MESSAGES) { + this.logger.verbose('saving messages to store'); + + data.forEach((message) => { + this.writeStore({ + path: join(this.storePath, 'messages', instanceName), + fileName: message.key.id, + data: message, + }); + this.logger.verbose( + 'messages saved to store in path: ' + join(this.storePath, 'messages', instanceName) + '/' + message.key.id, + ); + }); + + this.logger.verbose('messages saved to store: ' + data.length + ' messages'); + return { insertCount: data.length }; + } + + this.logger.verbose('messages not saved to store'); + return { insertCount: 0 }; + } catch (error) { + console.log('ERROR: ', error); + return error; + } finally { + data = undefined; + } + } + + public async find(query: MessageQuery) { + try { + this.logger.verbose('finding messages'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding messages in db'); + if (query?.where?.key) { + for (const [k, v] of Object.entries(query.where.key)) { + query.where['key.' + k] = v; + } + delete query?.where?.key; + } + + return await this.messageModel + .find({ ...query.where }) + .sort({ messageTimestamp: -1 }) + .limit(query?.limit ?? 0); + } + + this.logger.verbose('finding messages in store'); + const messages: MessageRaw[] = []; + if (query?.where?.key?.id) { + this.logger.verbose('finding messages in store by id'); + messages.push( + JSON.parse( + readFileSync(join(this.storePath, 'messages', query.where.owner, query.where.key.id + '.json'), { + encoding: 'utf-8', + }), + ), + ); + } else { + this.logger.verbose('finding messages in store by owner'); + const openDir = opendirSync(join(this.storePath, 'messages', query.where.owner), { + encoding: 'utf-8', + }); + + for await (const dirent of openDir) { + if (dirent.isFile()) { + messages.push( + JSON.parse( + readFileSync(join(this.storePath, 'messages', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), + ), + ); + } + } + } + + this.logger.verbose('messages found in store: ' + messages.length + ' messages'); + return messages + .sort((x, y) => { + return (y.messageTimestamp as number) - (x.messageTimestamp as number); + }) + .splice(0, query?.limit ?? messages.length); + } catch (error) { + return []; + } + } +} diff --git a/src/whatsapp/repository/messageUp.repository.ts b/src/whatsapp/repository/messageUp.repository.ts old mode 100644 new mode 100755 index b97bf59b..5f0eb149 --- a/src/whatsapp/repository/messageUp.repository.ts +++ b/src/whatsapp/repository/messageUp.repository.ts @@ -1,120 +1,120 @@ -import { opendirSync, readFileSync } from 'fs'; -import { join } from 'path'; - -import { ConfigService, StoreConf } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { IMessageUpModel, MessageUpdateRaw } from '../models'; - -export class MessageUpQuery { - where: MessageUpdateRaw; - limit?: number; -} - -export class MessageUpRepository extends Repository { - constructor(private readonly messageUpModel: IMessageUpModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('MessageUpRepository'); - - public async insert(data: MessageUpdateRaw[], instanceName: string, saveDb?: boolean): Promise { - this.logger.verbose('inserting message up'); - - if (data.length === 0) { - this.logger.verbose('no message up to insert'); - return; - } - - try { - if (this.dbSettings.ENABLED && saveDb) { - this.logger.verbose('saving message up to db'); - const insert = await this.messageUpModel.insertMany([...data]); - - this.logger.verbose('message up saved to db: ' + insert.length + ' message up'); - return { insertCount: insert.length }; - } - - this.logger.verbose('saving message up to store'); - - const store = this.configService.get('STORE'); - - if (store.MESSAGE_UP) { - this.logger.verbose('saving message up to store'); - data.forEach((update) => { - this.writeStore({ - path: join(this.storePath, 'message-up', instanceName), - fileName: update.id, - data: update, - }); - this.logger.verbose( - 'message up saved to store in path: ' + join(this.storePath, 'message-up', instanceName) + '/' + update.id, - ); - }); - - this.logger.verbose('message up saved to store: ' + data.length + ' message up'); - return { insertCount: data.length }; - } - - this.logger.verbose('message up not saved to store'); - return { insertCount: 0 }; - } catch (error) { - return error; - } - } - - public async find(query: MessageUpQuery) { - try { - this.logger.verbose('finding message up'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding message up in db'); - return await this.messageUpModel - .find({ ...query.where }) - .sort({ datetime: -1 }) - .limit(query?.limit ?? 0); - } - - this.logger.verbose('finding message up in store'); - - const messageUpdate: MessageUpdateRaw[] = []; - if (query?.where?.id) { - this.logger.verbose('finding message up in store by id'); - - messageUpdate.push( - JSON.parse( - readFileSync(join(this.storePath, 'message-up', query.where.owner, query.where.id + '.json'), { - encoding: 'utf-8', - }), - ), - ); - } else { - this.logger.verbose('finding message up in store by owner'); - - const openDir = opendirSync(join(this.storePath, 'message-up', query.where.owner), { - encoding: 'utf-8', - }); - - for await (const dirent of openDir) { - if (dirent.isFile()) { - messageUpdate.push( - JSON.parse( - readFileSync(join(this.storePath, 'message-up', query.where.owner, dirent.name), { - encoding: 'utf-8', - }), - ), - ); - } - } - } - - this.logger.verbose('message up found in store: ' + messageUpdate.length + ' message up'); - return messageUpdate - .sort((x, y) => { - return y.datetime - x.datetime; - }) - .splice(0, query?.limit ?? messageUpdate.length); - } catch (error) { - return []; - } - } -} +import { opendirSync, readFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { IMessageUpModel, MessageUpdateRaw } from '../models'; + +export class MessageUpQuery { + where: MessageUpdateRaw; + limit?: number; +} + +export class MessageUpRepository extends Repository { + constructor(private readonly messageUpModel: IMessageUpModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('MessageUpRepository'); + + public async insert(data: MessageUpdateRaw[], instanceName: string, saveDb?: boolean): Promise { + this.logger.verbose('inserting message up'); + + if (data.length === 0) { + this.logger.verbose('no message up to insert'); + return; + } + + try { + if (this.dbSettings.ENABLED && saveDb) { + this.logger.verbose('saving message up to db'); + const insert = await this.messageUpModel.insertMany([...data]); + + this.logger.verbose('message up saved to db: ' + insert.length + ' message up'); + return { insertCount: insert.length }; + } + + this.logger.verbose('saving message up to store'); + + const store = this.configService.get('STORE'); + + if (store.MESSAGE_UP) { + this.logger.verbose('saving message up to store'); + data.forEach((update) => { + this.writeStore({ + path: join(this.storePath, 'message-up', instanceName), + fileName: update.id, + data: update, + }); + this.logger.verbose( + 'message up saved to store in path: ' + join(this.storePath, 'message-up', instanceName) + '/' + update.id, + ); + }); + + this.logger.verbose('message up saved to store: ' + data.length + ' message up'); + return { insertCount: data.length }; + } + + this.logger.verbose('message up not saved to store'); + return { insertCount: 0 }; + } catch (error) { + return error; + } + } + + public async find(query: MessageUpQuery) { + try { + this.logger.verbose('finding message up'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding message up in db'); + return await this.messageUpModel + .find({ ...query.where }) + .sort({ datetime: -1 }) + .limit(query?.limit ?? 0); + } + + this.logger.verbose('finding message up in store'); + + const messageUpdate: MessageUpdateRaw[] = []; + if (query?.where?.id) { + this.logger.verbose('finding message up in store by id'); + + messageUpdate.push( + JSON.parse( + readFileSync(join(this.storePath, 'message-up', query.where.owner, query.where.id + '.json'), { + encoding: 'utf-8', + }), + ), + ); + } else { + this.logger.verbose('finding message up in store by owner'); + + const openDir = opendirSync(join(this.storePath, 'message-up', query.where.owner), { + encoding: 'utf-8', + }); + + for await (const dirent of openDir) { + if (dirent.isFile()) { + messageUpdate.push( + JSON.parse( + readFileSync(join(this.storePath, 'message-up', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), + ), + ); + } + } + } + + this.logger.verbose('message up found in store: ' + messageUpdate.length + ' message up'); + return messageUpdate + .sort((x, y) => { + return y.datetime - x.datetime; + }) + .splice(0, query?.limit ?? messageUpdate.length); + } catch (error) { + return []; + } + } +} diff --git a/src/whatsapp/repository/openai.repository.ts b/src/whatsapp/repository/openai.repository.ts new file mode 100755 index 00000000..8139fc39 --- /dev/null +++ b/src/whatsapp/repository/openai.repository.ts @@ -0,0 +1,153 @@ +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 { IContactOpenaiModel, ContactOpenaiRaw, IOpenaiModel, OpenaiRaw } from '../models'; + +export class OpenaiRepository extends Repository { + constructor( + private readonly openaiModel: IOpenaiModel, + private readonly contactopenaiModel: IContactOpenaiModel, + private readonly configService: ConfigService + ) { + super(configService); + } + + private readonly logger = new Logger('OpenaiRepository'); + + public async create(data: OpenaiRaw, instance: string): Promise { + try { + this.logger.verbose('creating openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving openai to db'); + const insert = await this.openaiModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('openai saved to db: ' + insert.modifiedCount + ' openai'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving openai to store'); + + this.writeStore({ + path: join(this.storePath, 'openai'), + fileName: instance, + data, + }); + + this.logger.verbose('openai saved to store in path: ' + join(this.storePath, 'openai') + '/' + instance); + + this.logger.verbose('openai created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + public async createContact(data: ContactOpenaiRaw, instance: string): Promise { + try { + this.logger.verbose('creating contact openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving openai to db'); + var resultado = await this.openaiModel.findOne({ owner: instance, contact: data.contact }); + if(!resultado){ + const insert = await this.contactopenaiModel.insertMany({ ...data }); + + this.logger.verbose('openai saved to db: ' + insert.length + ' openai_contacts'); + return { insertCount: insert.length }; + + }else{ + const contacts = [] + contacts[0] = { + updateOne: { + filter: { owner: data.owner, contact: data.contact }, + update: { ...data }, + upsert: true, + }, + }; + + const { nModified } = await this.contactopenaiModel.bulkWrite(contacts); + + this.logger.verbose('contacts updated in db: ' + nModified + ' contacts'); + return { insertCount: nModified }; + } + + } + + this.logger.verbose('saving openai to store'); + + this.writeStore({ + path: join(this.storePath, 'openai_contact'), + fileName: instance, + data, + }); + + this.logger.verbose('openai contact saved to store in path: ' + join(this.storePath, 'openai_contact') + '/' + instance); + + this.logger.verbose('openai contact created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding openai in db'); + return await this.openaiModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding openai in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'openai', instance + '.json'), { + encoding: 'utf-8', + }), + ) as OpenaiRaw; + } catch (error) { + return {}; + } + } + + public async findContact(instance: string, contact: string): Promise { + try { + this.logger.verbose('finding openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding openai in db'); + + return await this.contactopenaiModel.findOne({ owner: instance,contact: contact}); + } + + this.logger.verbose('finding openai in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'openai_contact', instance + '.json'), { + encoding: 'utf-8', + }), + ) as ContactOpenaiRaw; + } catch (error) { + + return ; + } + } + + public async findContactAll(instance: string): Promise { + try { + this.logger.verbose('finding openai'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding openai in db'); + return await this.contactopenaiModel.find({ owner: instance }); + } + + this.logger.verbose('finding openai in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'openai_contact', instance + '.json'), { + encoding: 'utf-8', + }), + ) as ContactOpenaiRaw; + } catch (error) { + + return; + } + } +} diff --git a/src/whatsapp/repository/proxy.repository.ts b/src/whatsapp/repository/proxy.repository.ts old mode 100644 new mode 100755 index 169e798f..323ed3cf --- a/src/whatsapp/repository/proxy.repository.ts +++ b/src/whatsapp/repository/proxy.repository.ts @@ -1,62 +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 { IProxyModel, ProxyRaw } from '../models'; - -export class ProxyRepository extends Repository { - constructor(private readonly proxyModel: IProxyModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('ProxyRepository'); - - public async create(data: ProxyRaw, instance: string): Promise { - try { - this.logger.verbose('creating proxy'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving proxy to db'); - const insert = await this.proxyModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('proxy saved to db: ' + insert.modifiedCount + ' proxy'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving proxy to store'); - - this.writeStore({ - path: join(this.storePath, 'proxy'), - fileName: instance, - data, - }); - - this.logger.verbose('proxy saved to store in path: ' + join(this.storePath, 'proxy') + '/' + instance); - - this.logger.verbose('proxy created'); - return { insertCount: 1 }; - } catch (error) { - return error; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding proxy'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding proxy in db'); - return await this.proxyModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding proxy in store'); - return JSON.parse( - readFileSync(join(this.storePath, 'proxy', instance + '.json'), { - encoding: 'utf-8', - }), - ) as ProxyRaw; - } catch (error) { - return {}; - } - } -} +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 { IProxyModel, ProxyRaw } from '../models'; + +export class ProxyRepository extends Repository { + constructor(private readonly proxyModel: IProxyModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('ProxyRepository'); + + public async create(data: ProxyRaw, instance: string): Promise { + try { + this.logger.verbose('creating proxy'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving proxy to db'); + const insert = await this.proxyModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('proxy saved to db: ' + insert.modifiedCount + ' proxy'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving proxy to store'); + + this.writeStore({ + path: join(this.storePath, 'proxy'), + fileName: instance, + data, + }); + + this.logger.verbose('proxy saved to store in path: ' + join(this.storePath, 'proxy') + '/' + instance); + + this.logger.verbose('proxy created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding proxy'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding proxy in db'); + return await this.proxyModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding proxy in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'proxy', instance + '.json'), { + encoding: 'utf-8', + }), + ) as ProxyRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/repository/rabbitmq.repository.ts b/src/whatsapp/repository/rabbitmq.repository.ts old mode 100644 new mode 100755 index 3dfb5ec2..7fccc6ce --- a/src/whatsapp/repository/rabbitmq.repository.ts +++ b/src/whatsapp/repository/rabbitmq.repository.ts @@ -1,62 +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 { IRabbitmqModel, RabbitmqRaw } from '../models'; - -export class RabbitmqRepository extends Repository { - constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('RabbitmqRepository'); - - public async create(data: RabbitmqRaw, instance: string): Promise { - try { - this.logger.verbose('creating rabbitmq'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving rabbitmq to db'); - const insert = await this.rabbitmqModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('rabbitmq saved to db: ' + insert.modifiedCount + ' rabbitmq'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving rabbitmq to store'); - - this.writeStore({ - path: join(this.storePath, 'rabbitmq'), - fileName: instance, - data, - }); - - this.logger.verbose('rabbitmq saved to store in path: ' + join(this.storePath, 'rabbitmq') + '/' + instance); - - this.logger.verbose('rabbitmq created'); - return { insertCount: 1 }; - } catch (error) { - return error; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding rabbitmq'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding rabbitmq in db'); - return await this.rabbitmqModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding rabbitmq in store'); - return JSON.parse( - readFileSync(join(this.storePath, 'rabbitmq', instance + '.json'), { - encoding: 'utf-8', - }), - ) as RabbitmqRaw; - } catch (error) { - return {}; - } - } -} +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 { IRabbitmqModel, RabbitmqRaw } from '../models'; + +export class RabbitmqRepository extends Repository { + constructor(private readonly rabbitmqModel: IRabbitmqModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('RabbitmqRepository'); + + public async create(data: RabbitmqRaw, instance: string): Promise { + try { + this.logger.verbose('creating rabbitmq'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving rabbitmq to db'); + const insert = await this.rabbitmqModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('rabbitmq saved to db: ' + insert.modifiedCount + ' rabbitmq'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving rabbitmq to store'); + + this.writeStore({ + path: join(this.storePath, 'rabbitmq'), + fileName: instance, + data, + }); + + this.logger.verbose('rabbitmq saved to store in path: ' + join(this.storePath, 'rabbitmq') + '/' + instance); + + this.logger.verbose('rabbitmq created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding rabbitmq'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding rabbitmq in db'); + return await this.rabbitmqModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding rabbitmq in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'rabbitmq', instance + '.json'), { + encoding: 'utf-8', + }), + ) as RabbitmqRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/repository/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts old mode 100644 new mode 100755 index ab4da1e3..2da43fc0 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -1,159 +1,169 @@ -import fs from 'fs'; -import { MongoClient } from 'mongodb'; -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'; -import { MessageRepository } from './message.repository'; -import { MessageUpRepository } from './messageUp.repository'; -import { ProxyRepository } from './proxy.repository'; -import { RabbitmqRepository } from './rabbitmq.repository'; -import { SettingsRepository } from './settings.repository'; -import { SqsRepository } from './sqs.repository'; -import { TypebotRepository } from './typebot.repository'; -import { WebhookRepository } from './webhook.repository'; -import { WebsocketRepository } from './websocket.repository'; -export class RepositoryBroker { - constructor( - public readonly message: MessageRepository, - public readonly chat: ChatRepository, - public readonly contact: ContactRepository, - public readonly messageUpdate: MessageUpRepository, - public readonly webhook: WebhookRepository, - public readonly chatwoot: ChatwootRepository, - public readonly settings: SettingsRepository, - public readonly websocket: WebsocketRepository, - public readonly rabbitmq: RabbitmqRepository, - public readonly sqs: SqsRepository, - public readonly typebot: TypebotRepository, - public readonly proxy: ProxyRepository, - public readonly chamaai: ChamaaiRepository, - public readonly auth: AuthRepository, - private configService: ConfigService, - dbServer?: MongoClient, - ) { - this.dbClient = dbServer; - this.__init_repo_without_db__(); - } - - private dbClient?: MongoClient; - private readonly logger = new Logger('RepositoryBroker'); - - public get dbServer() { - return this.dbClient; - } - - private __init_repo_without_db__() { - this.logger.verbose('initializing repository without db'); - if (!this.configService.get('DATABASE').ENABLED) { - const storePath = join(process.cwd(), 'store'); - - this.logger.verbose('creating store path: ' + storePath); - try { - const authDir = join(storePath, 'auth', this.configService.get('AUTHENTICATION').TYPE); - const chatsDir = join(storePath, 'chats'); - const contactsDir = join(storePath, 'contacts'); - const messagesDir = join(storePath, 'messages'); - const messageUpDir = join(storePath, 'message-up'); - const webhookDir = join(storePath, 'webhook'); - const chatwootDir = join(storePath, 'chatwoot'); - const settingsDir = join(storePath, 'settings'); - const websocketDir = join(storePath, 'websocket'); - const rabbitmqDir = join(storePath, 'rabbitmq'); - const sqsDir = join(storePath, 'sqs'); - const typebotDir = join(storePath, 'typebot'); - const proxyDir = join(storePath, 'proxy'); - const chamaaiDir = join(storePath, 'chamaai'); - const tempDir = join(storePath, 'temp'); - - if (!fs.existsSync(authDir)) { - this.logger.verbose('creating auth dir: ' + authDir); - fs.mkdirSync(authDir, { recursive: true }); - } - if (!fs.existsSync(chatsDir)) { - this.logger.verbose('creating chats dir: ' + chatsDir); - fs.mkdirSync(chatsDir, { recursive: true }); - } - if (!fs.existsSync(contactsDir)) { - this.logger.verbose('creating contacts dir: ' + contactsDir); - fs.mkdirSync(contactsDir, { recursive: true }); - } - if (!fs.existsSync(messagesDir)) { - this.logger.verbose('creating messages dir: ' + messagesDir); - fs.mkdirSync(messagesDir, { recursive: true }); - } - if (!fs.existsSync(messageUpDir)) { - this.logger.verbose('creating message-up dir: ' + messageUpDir); - fs.mkdirSync(messageUpDir, { recursive: true }); - } - if (!fs.existsSync(webhookDir)) { - this.logger.verbose('creating webhook dir: ' + webhookDir); - fs.mkdirSync(webhookDir, { recursive: true }); - } - if (!fs.existsSync(chatwootDir)) { - this.logger.verbose('creating chatwoot dir: ' + chatwootDir); - fs.mkdirSync(chatwootDir, { recursive: true }); - } - if (!fs.existsSync(settingsDir)) { - this.logger.verbose('creating settings dir: ' + settingsDir); - fs.mkdirSync(settingsDir, { recursive: true }); - } - if (!fs.existsSync(websocketDir)) { - this.logger.verbose('creating websocket dir: ' + websocketDir); - fs.mkdirSync(websocketDir, { recursive: true }); - } - if (!fs.existsSync(rabbitmqDir)) { - this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir); - fs.mkdirSync(rabbitmqDir, { recursive: true }); - } - if (!fs.existsSync(sqsDir)) { - this.logger.verbose('creating sqs dir: ' + sqsDir); - fs.mkdirSync(sqsDir, { recursive: true }); - } - if (!fs.existsSync(typebotDir)) { - this.logger.verbose('creating typebot dir: ' + typebotDir); - fs.mkdirSync(typebotDir, { recursive: true }); - } - if (!fs.existsSync(proxyDir)) { - 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 }); - } - } catch (error) { - this.logger.error(error); - } - } else { - try { - const storePath = join(process.cwd(), 'store'); - - this.logger.verbose('creating store path: ' + storePath); - - const tempDir = join(storePath, 'temp'); - const chatwootDir = join(storePath, 'chatwoot'); - - if (!fs.existsSync(chatwootDir)) { - this.logger.verbose('creating chatwoot dir: ' + chatwootDir); - fs.mkdirSync(chatwootDir, { recursive: true }); - } - if (!fs.existsSync(tempDir)) { - this.logger.verbose('creating temp dir: ' + tempDir); - fs.mkdirSync(tempDir, { recursive: true }); - } - } catch (error) { - this.logger.error(error); - } - } - } -} +import fs from 'fs'; +import { MongoClient } from 'mongodb'; +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'; +import { MessageRepository } from './message.repository'; +import { MessageUpRepository } from './messageUp.repository'; +import { ProxyRepository } from './proxy.repository'; +import { RabbitmqRepository } from './rabbitmq.repository'; +import { OpenaiRepository } from './openai.repository'; +import { SettingsRepository } from './settings.repository'; +import { SqsRepository } from './sqs.repository'; +import { TypebotRepository } from './typebot.repository'; +import { WebhookRepository } from './webhook.repository'; +import { WebsocketRepository } from './websocket.repository'; +export class RepositoryBroker { + constructor( + public readonly message: MessageRepository, + public readonly chat: ChatRepository, + public readonly contact: ContactRepository, + public readonly messageUpdate: MessageUpRepository, + public readonly webhook: WebhookRepository, + public readonly chatwoot: ChatwootRepository, + public readonly settings: SettingsRepository, + public readonly websocket: WebsocketRepository, + public readonly rabbitmq: RabbitmqRepository, + public readonly openai: OpenaiRepository, + public readonly openai_contact: OpenaiRepository, + public readonly sqs: SqsRepository, + public readonly typebot: TypebotRepository, + public readonly proxy: ProxyRepository, + public readonly chamaai: ChamaaiRepository, + public readonly auth: AuthRepository, + private configService: ConfigService, + dbServer?: MongoClient, + ) { + this.dbClient = dbServer; + this.__init_repo_without_db__(); + } + + private dbClient?: MongoClient; + private readonly logger = new Logger('RepositoryBroker'); + + public get dbServer() { + return this.dbClient; + } + + private __init_repo_without_db__() { + this.logger.verbose('initializing repository without db'); + if (!this.configService.get('DATABASE').ENABLED) { + const storePath = join(process.cwd(), 'store'); + + this.logger.verbose('creating store path: ' + storePath); + try { + const authDir = join(storePath, 'auth', this.configService.get('AUTHENTICATION').TYPE); + const chatsDir = join(storePath, 'chats'); + const contactsDir = join(storePath, 'contacts'); + const messagesDir = join(storePath, 'messages'); + const messageUpDir = join(storePath, 'message-up'); + const webhookDir = join(storePath, 'webhook'); + const chatwootDir = join(storePath, 'chatwoot'); + const settingsDir = join(storePath, 'settings'); + const sqsDir = join(storePath, 'sqs'); + const websocketDir = join(storePath, 'websocket'); + const rabbitmqDir = join(storePath, 'rabbitmq'); + const openaiDir = join(storePath, 'openai'); + const typebotDir = join(storePath, 'typebot'); + const proxyDir = join(storePath, 'proxy'); + const chamaaiDir = join(storePath, 'chamaai'); + const tempDir = join(storePath, 'temp'); + + if (!fs.existsSync(authDir)) { + this.logger.verbose('creating auth dir: ' + authDir); + fs.mkdirSync(authDir, { recursive: true }); + } + if (!fs.existsSync(chatsDir)) { + this.logger.verbose('creating chats dir: ' + chatsDir); + fs.mkdirSync(chatsDir, { recursive: true }); + } + if (!fs.existsSync(contactsDir)) { + this.logger.verbose('creating contacts dir: ' + contactsDir); + fs.mkdirSync(contactsDir, { recursive: true }); + } + if (!fs.existsSync(messagesDir)) { + this.logger.verbose('creating messages dir: ' + messagesDir); + fs.mkdirSync(messagesDir, { recursive: true }); + } + if (!fs.existsSync(messageUpDir)) { + this.logger.verbose('creating message-up dir: ' + messageUpDir); + fs.mkdirSync(messageUpDir, { recursive: true }); + } + if (!fs.existsSync(webhookDir)) { + this.logger.verbose('creating webhook dir: ' + webhookDir); + fs.mkdirSync(webhookDir, { recursive: true }); + } + if (!fs.existsSync(chatwootDir)) { + this.logger.verbose('creating chatwoot dir: ' + chatwootDir); + fs.mkdirSync(chatwootDir, { recursive: true }); + } + if (!fs.existsSync(settingsDir)) { + this.logger.verbose('creating settings dir: ' + settingsDir); + fs.mkdirSync(settingsDir, { recursive: true }); + } + + if (!fs.existsSync(sqsDir)) { + this.logger.verbose('creating sqs dir: ' + sqsDir); + fs.mkdirSync(sqsDir, { recursive: true }); + } + + if (!fs.existsSync(websocketDir)) { + this.logger.verbose('creating websocket dir: ' + websocketDir); + fs.mkdirSync(websocketDir, { recursive: true }); + } + if (!fs.existsSync(rabbitmqDir)) { + this.logger.verbose('creating rabbitmq dir: ' + rabbitmqDir); + fs.mkdirSync(rabbitmqDir, { recursive: true }); + } + if (!fs.existsSync(openaiDir)) { + this.logger.verbose('creating openai dir: ' + openaiDir); + fs.mkdirSync(openaiDir, { recursive: true }); + } + if (!fs.existsSync(typebotDir)) { + this.logger.verbose('creating typebot dir: ' + typebotDir); + fs.mkdirSync(typebotDir, { recursive: true }); + } + if (!fs.existsSync(proxyDir)) { + 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 }); + } + } catch (error) { + this.logger.error(error); + } + } else { + try { + const storePath = join(process.cwd(), 'store'); + + this.logger.verbose('creating store path: ' + storePath); + + const tempDir = join(storePath, 'temp'); + const chatwootDir = join(storePath, 'chatwoot'); + + if (!fs.existsSync(chatwootDir)) { + this.logger.verbose('creating chatwoot dir: ' + chatwootDir); + fs.mkdirSync(chatwootDir, { recursive: true }); + } + if (!fs.existsSync(tempDir)) { + this.logger.verbose('creating temp dir: ' + tempDir); + fs.mkdirSync(tempDir, { recursive: true }); + } + } catch (error) { + this.logger.error(error); + } + } + } +} diff --git a/src/whatsapp/repository/settings.repository.ts b/src/whatsapp/repository/settings.repository.ts old mode 100644 new mode 100755 index 4d09d79f..7042ebf1 --- a/src/whatsapp/repository/settings.repository.ts +++ b/src/whatsapp/repository/settings.repository.ts @@ -1,62 +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 { ISettingsModel, SettingsRaw } from '../models'; - -export class SettingsRepository extends Repository { - constructor(private readonly settingsModel: ISettingsModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('SettingsRepository'); - - public async create(data: SettingsRaw, instance: string): Promise { - try { - this.logger.verbose('creating settings'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving settings to db'); - const insert = await this.settingsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('settings saved to db: ' + insert.modifiedCount + ' settings'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving settings to store'); - - this.writeStore({ - path: join(this.storePath, 'settings'), - fileName: instance, - data, - }); - - this.logger.verbose('settings saved to store in path: ' + join(this.storePath, 'settings') + '/' + instance); - - this.logger.verbose('settings created'); - return { insertCount: 1 }; - } catch (error) { - return error; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding settings'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding settings in db'); - return await this.settingsModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding settings in store'); - return JSON.parse( - readFileSync(join(this.storePath, 'settings', instance + '.json'), { - encoding: 'utf-8', - }), - ) as SettingsRaw; - } catch (error) { - return {}; - } - } -} +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 { ISettingsModel, SettingsRaw } from '../models'; + +export class SettingsRepository extends Repository { + constructor(private readonly settingsModel: ISettingsModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('SettingsRepository'); + + public async create(data: SettingsRaw, instance: string): Promise { + try { + this.logger.verbose('creating settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving settings to db'); + const insert = await this.settingsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('settings saved to db: ' + insert.modifiedCount + ' settings'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving settings to store'); + + this.writeStore({ + path: join(this.storePath, 'settings'), + fileName: instance, + data, + }); + + this.logger.verbose('settings saved to store in path: ' + join(this.storePath, 'settings') + '/' + instance); + + this.logger.verbose('settings created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding settings in db'); + return await this.settingsModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding settings in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'settings', instance + '.json'), { + encoding: 'utf-8', + }), + ) as SettingsRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/repository/sqs.repository.ts b/src/whatsapp/repository/sqs.repository.ts old mode 100644 new mode 100755 index 50ea1cd3..f7961ea2 --- a/src/whatsapp/repository/sqs.repository.ts +++ b/src/whatsapp/repository/sqs.repository.ts @@ -1,62 +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 { ISqsModel, SqsRaw } from '../models'; - -export class SqsRepository extends Repository { - constructor(private readonly sqsModel: ISqsModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('SqsRepository'); - - public async create(data: SqsRaw, instance: string): Promise { - try { - this.logger.verbose('creating sqs'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving sqs to db'); - const insert = await this.sqsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('sqs saved to db: ' + insert.modifiedCount + ' sqs'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving sqs to store'); - - this.writeStore({ - path: join(this.storePath, 'sqs'), - fileName: instance, - data, - }); - - this.logger.verbose('sqs saved to store in path: ' + join(this.storePath, 'sqs') + '/' + instance); - - this.logger.verbose('sqs created'); - return { insertCount: 1 }; - } catch (error) { - return error; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding sqs'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding sqs in db'); - return await this.sqsModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding sqs in store'); - return JSON.parse( - readFileSync(join(this.storePath, 'sqs', instance + '.json'), { - encoding: 'utf-8', - }), - ) as SqsRaw; - } catch (error) { - return {}; - } - } -} +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 { ISqsModel, SqsRaw } from '../models'; + +export class SqsRepository extends Repository { + constructor(private readonly sqsModel: ISqsModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('SqsRepository'); + + public async create(data: SqsRaw, instance: string): Promise { + try { + this.logger.verbose('creating sqs'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving sqs to db'); + const insert = await this.sqsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('sqs saved to db: ' + insert.modifiedCount + ' sqs'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving sqs to store'); + + this.writeStore({ + path: join(this.storePath, 'sqs'), + fileName: instance, + data, + }); + + this.logger.verbose('sqs saved to store in path: ' + join(this.storePath, 'sqs') + '/' + instance); + + this.logger.verbose('sqs created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding sqs'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding sqs in db'); + return await this.sqsModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding sqs in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'sqs', instance + '.json'), { + encoding: 'utf-8', + }), + ) as SqsRaw; + } catch (error) { + return {}; + } + } +} \ No newline at end of file diff --git a/src/whatsapp/repository/typebot.repository.ts b/src/whatsapp/repository/typebot.repository.ts old mode 100644 new mode 100755 index 8653e9c9..5eff1f99 --- a/src/whatsapp/repository/typebot.repository.ts +++ b/src/whatsapp/repository/typebot.repository.ts @@ -1,68 +1,68 @@ -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 { ITypebotModel, TypebotRaw } from '../models'; - -export class TypebotRepository extends Repository { - constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('TypebotRepository'); - - public async create(data: TypebotRaw, instance: string): Promise { - try { - this.logger.verbose('creating typebot'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving typebot to db'); - const insert = await this.typebotModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('typebot saved to db: ' + insert.modifiedCount + ' typebot'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving typebot to store'); - - this.writeStore({ - path: join(this.storePath, 'typebot'), - fileName: instance, - data, - }); - - this.logger.verbose('typebot saved to store in path: ' + join(this.storePath, 'typebot') + '/' + instance); - - this.logger.verbose('typebot created'); - return { insertCount: 1 }; - } catch (error) { - return error; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding typebot'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding typebot in db'); - return await this.typebotModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding typebot in store'); - return JSON.parse( - readFileSync(join(this.storePath, 'typebot', instance + '.json'), { - encoding: 'utf-8', - }), - ) as TypebotRaw; - } catch (error) { - return { - enabled: false, - url: '', - typebot: '', - expire: 0, - sessions: [], - }; - } - } -} +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 { ITypebotModel, TypebotRaw } from '../models'; + +export class TypebotRepository extends Repository { + constructor(private readonly typebotModel: ITypebotModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('TypebotRepository'); + + public async create(data: TypebotRaw, instance: string): Promise { + try { + this.logger.verbose('creating typebot'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving typebot to db'); + const insert = await this.typebotModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('typebot saved to db: ' + insert.modifiedCount + ' typebot'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving typebot to store'); + + this.writeStore({ + path: join(this.storePath, 'typebot'), + fileName: instance, + data, + }); + + this.logger.verbose('typebot saved to store in path: ' + join(this.storePath, 'typebot') + '/' + instance); + + this.logger.verbose('typebot created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding typebot'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding typebot in db'); + return await this.typebotModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding typebot in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'typebot', instance + '.json'), { + encoding: 'utf-8', + }), + ) as TypebotRaw; + } catch (error) { + return { + enabled: false, + url: '', + typebot: '', + expire: 0, + sessions: [], + }; + } + } +} diff --git a/src/whatsapp/repository/webhook.repository.ts b/src/whatsapp/repository/webhook.repository.ts old mode 100644 new mode 100755 index 10074516..52514a42 --- a/src/whatsapp/repository/webhook.repository.ts +++ b/src/whatsapp/repository/webhook.repository.ts @@ -1,62 +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 { IWebhookModel, WebhookRaw } from '../models'; - -export class WebhookRepository extends Repository { - constructor(private readonly webhookModel: IWebhookModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('WebhookRepository'); - - public async create(data: WebhookRaw, instance: string): Promise { - try { - this.logger.verbose('creating webhook'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving webhook to db'); - const insert = await this.webhookModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('webhook saved to db: ' + insert.modifiedCount + ' webhook'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving webhook to store'); - - this.writeStore({ - path: join(this.storePath, 'webhook'), - fileName: instance, - data, - }); - - this.logger.verbose('webhook saved to store in path: ' + join(this.storePath, 'webhook') + '/' + instance); - - this.logger.verbose('webhook created'); - return { insertCount: 1 }; - } catch (error) { - return error; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding webhook'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding webhook in db'); - return await this.webhookModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding webhook in store'); - return JSON.parse( - readFileSync(join(this.storePath, 'webhook', instance + '.json'), { - encoding: 'utf-8', - }), - ) as WebhookRaw; - } catch (error) { - return {}; - } - } -} +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 { IWebhookModel, WebhookRaw } from '../models'; + +export class WebhookRepository extends Repository { + constructor(private readonly webhookModel: IWebhookModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('WebhookRepository'); + + public async create(data: WebhookRaw, instance: string): Promise { + try { + this.logger.verbose('creating webhook'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving webhook to db'); + const insert = await this.webhookModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('webhook saved to db: ' + insert.modifiedCount + ' webhook'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving webhook to store'); + + this.writeStore({ + path: join(this.storePath, 'webhook'), + fileName: instance, + data, + }); + + this.logger.verbose('webhook saved to store in path: ' + join(this.storePath, 'webhook') + '/' + instance); + + this.logger.verbose('webhook created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding webhook'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding webhook in db'); + return await this.webhookModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding webhook in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'webhook', instance + '.json'), { + encoding: 'utf-8', + }), + ) as WebhookRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/repository/websocket.repository.ts b/src/whatsapp/repository/websocket.repository.ts old mode 100644 new mode 100755 index 19823194..d00d1206 --- a/src/whatsapp/repository/websocket.repository.ts +++ b/src/whatsapp/repository/websocket.repository.ts @@ -1,62 +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 { IWebsocketModel, WebsocketRaw } from '../models'; - -export class WebsocketRepository extends Repository { - constructor(private readonly websocketModel: IWebsocketModel, private readonly configService: ConfigService) { - super(configService); - } - - private readonly logger = new Logger('WebsocketRepository'); - - public async create(data: WebsocketRaw, instance: string): Promise { - try { - this.logger.verbose('creating websocket'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('saving websocket to db'); - const insert = await this.websocketModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - - this.logger.verbose('websocket saved to db: ' + insert.modifiedCount + ' websocket'); - return { insertCount: insert.modifiedCount }; - } - - this.logger.verbose('saving websocket to store'); - - this.writeStore({ - path: join(this.storePath, 'websocket'), - fileName: instance, - data, - }); - - this.logger.verbose('websocket saved to store in path: ' + join(this.storePath, 'websocket') + '/' + instance); - - this.logger.verbose('websocket created'); - return { insertCount: 1 }; - } catch (error) { - return error; - } - } - - public async find(instance: string): Promise { - try { - this.logger.verbose('finding websocket'); - if (this.dbSettings.ENABLED) { - this.logger.verbose('finding websocket in db'); - return await this.websocketModel.findOne({ _id: instance }); - } - - this.logger.verbose('finding websocket in store'); - return JSON.parse( - readFileSync(join(this.storePath, 'websocket', instance + '.json'), { - encoding: 'utf-8', - }), - ) as WebsocketRaw; - } catch (error) { - return {}; - } - } -} +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 { IWebsocketModel, WebsocketRaw } from '../models'; + +export class WebsocketRepository extends Repository { + constructor(private readonly websocketModel: IWebsocketModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('WebsocketRepository'); + + public async create(data: WebsocketRaw, instance: string): Promise { + try { + this.logger.verbose('creating websocket'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving websocket to db'); + const insert = await this.websocketModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('websocket saved to db: ' + insert.modifiedCount + ' websocket'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving websocket to store'); + + this.writeStore({ + path: join(this.storePath, 'websocket'), + fileName: instance, + data, + }); + + this.logger.verbose('websocket saved to store in path: ' + join(this.storePath, 'websocket') + '/' + instance); + + this.logger.verbose('websocket created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding websocket'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding websocket in db'); + return await this.websocketModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding websocket in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'websocket', instance + '.json'), { + encoding: 'utf-8', + }), + ) as WebsocketRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/routers/chamaai.router.ts b/src/whatsapp/routers/chamaai.router.ts old mode 100644 new mode 100755 index e8021306..c3b3fbb3 --- a/src/whatsapp/routers/chamaai.router.ts +++ b/src/whatsapp/routers/chamaai.router.ts @@ -1,52 +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({ - 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({ - request: req, - schema: instanceNameSchema, - ClassRef: InstanceDto, - execute: (instance) => chamaaiController.findChamaai(instance), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +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({ + 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({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => chamaaiController.findChamaai(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/chat.router.ts b/src/whatsapp/routers/chat.router.ts old mode 100644 new mode 100755 index 285c29a0..0d4f01ed --- a/src/whatsapp/routers/chat.router.ts +++ b/src/whatsapp/routers/chat.router.ts @@ -1,354 +1,354 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { - archiveChatSchema, - contactValidateSchema, - deleteMessageSchema, - messageUpSchema, - messageValidateSchema, - privacySettingsSchema, - profileNameSchema, - profilePictureSchema, - profileSchema, - profileStatusSchema, - readMessageSchema, - whatsappNumberSchema, -} from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { - ArchiveChatDto, - DeleteMessage, - getBase64FromMediaMessageDto, - NumberDto, - PrivacySettingDto, - ProfileNameDto, - ProfilePictureDto, - ProfileStatusDto, - ReadMessageDto, - WhatsAppNumberDto, -} from '../dto/chat.dto'; -import { InstanceDto } from '../dto/instance.dto'; -import { ContactQuery } from '../repository/contact.repository'; -import { MessageQuery } from '../repository/message.repository'; -import { MessageUpQuery } from '../repository/messageUp.repository'; -import { chatController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('ChatRouter'); - -export class ChatRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('whatsappNumbers'), ...guards, async (req, res) => { - logger.verbose('request received in whatsappNumbers'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: whatsappNumberSchema, - ClassRef: WhatsAppNumberDto, - execute: (instance, data) => chatController.whatsappNumber(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => { - logger.verbose('request received in markMessageAsRead'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: readMessageSchema, - ClassRef: ReadMessageDto, - execute: (instance, data) => chatController.readMessage(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('archiveChat'), ...guards, async (req, res) => { - logger.verbose('request received in archiveChat'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: archiveChatSchema, - ClassRef: ArchiveChatDto, - execute: (instance, data) => chatController.archiveChat(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .delete(this.routerPath('deleteMessageForEveryone'), ...guards, async (req, res) => { - logger.verbose('request received in deleteMessageForEveryone'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: deleteMessageSchema, - ClassRef: DeleteMessage, - execute: (instance, data) => chatController.deleteMessage(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('fetchProfilePictureUrl'), ...guards, async (req, res) => { - logger.verbose('request received in fetchProfilePictureUrl'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: profilePictureSchema, - ClassRef: NumberDto, - execute: (instance, data) => chatController.fetchProfilePicture(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('fetchProfile'), ...guards, async (req, res) => { - logger.verbose('request received in fetchProfile'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: profileSchema, - ClassRef: NumberDto, - execute: (instance, data) => chatController.fetchProfile(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('findContacts'), ...guards, async (req, res) => { - logger.verbose('request received in findContacts'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: contactValidateSchema, - ClassRef: ContactQuery, - execute: (instance, data) => chatController.fetchContacts(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => { - logger.verbose('request received in getBase64FromMediaMessage'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: null, - ClassRef: getBase64FromMediaMessageDto, - execute: (instance, data) => chatController.getBase64FromMediaMessage(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('findMessages'), ...guards, async (req, res) => { - logger.verbose('request received in findMessages'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: messageValidateSchema, - ClassRef: MessageQuery, - execute: (instance, data) => chatController.fetchMessages(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('findStatusMessage'), ...guards, async (req, res) => { - logger.verbose('request received in findStatusMessage'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: messageUpSchema, - ClassRef: MessageUpQuery, - execute: (instance, data) => chatController.fetchStatusMessage(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('findChats'), ...guards, async (req, res) => { - logger.verbose('request received in findChats'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: null, - ClassRef: InstanceDto, - execute: (instance) => chatController.fetchChats(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }) - // Profile routes - .get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => { - logger.verbose('request received in fetchPrivacySettings'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: null, - ClassRef: InstanceDto, - execute: (instance) => chatController.fetchPrivacySettings(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .put(this.routerPath('updatePrivacySettings'), ...guards, async (req, res) => { - logger.verbose('request received in updatePrivacySettings'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: privacySettingsSchema, - ClassRef: PrivacySettingDto, - execute: (instance, data) => chatController.updatePrivacySettings(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('fetchBusinessProfile'), ...guards, async (req, res) => { - logger.verbose('request received in fetchBusinessProfile'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: profilePictureSchema, - ClassRef: ProfilePictureDto, - execute: (instance, data) => chatController.fetchBusinessProfile(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('updateProfileName'), ...guards, async (req, res) => { - logger.verbose('request received in updateProfileName'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: profileNameSchema, - ClassRef: ProfileNameDto, - execute: (instance, data) => chatController.updateProfileName(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('updateProfileStatus'), ...guards, async (req, res) => { - logger.verbose('request received in updateProfileStatus'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: profileStatusSchema, - ClassRef: ProfileStatusDto, - execute: (instance, data) => chatController.updateProfileStatus(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .put(this.routerPath('updateProfilePicture'), ...guards, async (req, res) => { - logger.verbose('request received in updateProfilePicture'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: profilePictureSchema, - ClassRef: ProfilePictureDto, - execute: (instance, data) => chatController.updateProfilePicture(instance, data), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .delete(this.routerPath('removeProfilePicture'), ...guards, async (req, res) => { - logger.verbose('request received in removeProfilePicture'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.dataValidate({ - request: req, - schema: profilePictureSchema, - ClassRef: ProfilePictureDto, - execute: (instance) => chatController.removeProfilePicture(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { + archiveChatSchema, + contactValidateSchema, + deleteMessageSchema, + messageUpSchema, + messageValidateSchema, + privacySettingsSchema, + profileNameSchema, + profilePictureSchema, + profileSchema, + profileStatusSchema, + readMessageSchema, + whatsappNumberSchema, +} from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { + ArchiveChatDto, + DeleteMessage, + getBase64FromMediaMessageDto, + NumberDto, + PrivacySettingDto, + ProfileNameDto, + ProfilePictureDto, + ProfileStatusDto, + ReadMessageDto, + WhatsAppNumberDto, +} from '../dto/chat.dto'; +import { InstanceDto } from '../dto/instance.dto'; +import { ContactQuery } from '../repository/contact.repository'; +import { MessageQuery } from '../repository/message.repository'; +import { MessageUpQuery } from '../repository/messageUp.repository'; +import { chatController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('ChatRouter'); + +export class ChatRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('whatsappNumbers'), ...guards, async (req, res) => { + logger.verbose('request received in whatsappNumbers'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: whatsappNumberSchema, + ClassRef: WhatsAppNumberDto, + execute: (instance, data) => chatController.whatsappNumber(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => { + logger.verbose('request received in markMessageAsRead'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: readMessageSchema, + ClassRef: ReadMessageDto, + execute: (instance, data) => chatController.readMessage(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('archiveChat'), ...guards, async (req, res) => { + logger.verbose('request received in archiveChat'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: archiveChatSchema, + ClassRef: ArchiveChatDto, + execute: (instance, data) => chatController.archiveChat(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .delete(this.routerPath('deleteMessageForEveryone'), ...guards, async (req, res) => { + logger.verbose('request received in deleteMessageForEveryone'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: deleteMessageSchema, + ClassRef: DeleteMessage, + execute: (instance, data) => chatController.deleteMessage(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('fetchProfilePictureUrl'), ...guards, async (req, res) => { + logger.verbose('request received in fetchProfilePictureUrl'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profilePictureSchema, + ClassRef: NumberDto, + execute: (instance, data) => chatController.fetchProfilePicture(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('fetchProfile'), ...guards, async (req, res) => { + logger.verbose('request received in fetchProfile'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profileSchema, + ClassRef: NumberDto, + execute: (instance, data) => chatController.fetchProfile(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('findContacts'), ...guards, async (req, res) => { + logger.verbose('request received in findContacts'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: contactValidateSchema, + ClassRef: ContactQuery, + execute: (instance, data) => chatController.fetchContacts(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('getBase64FromMediaMessage'), ...guards, async (req, res) => { + logger.verbose('request received in getBase64FromMediaMessage'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: getBase64FromMediaMessageDto, + execute: (instance, data) => chatController.getBase64FromMediaMessage(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('findMessages'), ...guards, async (req, res) => { + logger.verbose('request received in findMessages'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: messageValidateSchema, + ClassRef: MessageQuery, + execute: (instance, data) => chatController.fetchMessages(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('findStatusMessage'), ...guards, async (req, res) => { + logger.verbose('request received in findStatusMessage'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: messageUpSchema, + ClassRef: MessageUpQuery, + execute: (instance, data) => chatController.fetchStatusMessage(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('findChats'), ...guards, async (req, res) => { + logger.verbose('request received in findChats'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: InstanceDto, + execute: (instance) => chatController.fetchChats(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + // Profile routes + .get(this.routerPath('fetchPrivacySettings'), ...guards, async (req, res) => { + logger.verbose('request received in fetchPrivacySettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: InstanceDto, + execute: (instance) => chatController.fetchPrivacySettings(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .put(this.routerPath('updatePrivacySettings'), ...guards, async (req, res) => { + logger.verbose('request received in updatePrivacySettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: privacySettingsSchema, + ClassRef: PrivacySettingDto, + execute: (instance, data) => chatController.updatePrivacySettings(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('fetchBusinessProfile'), ...guards, async (req, res) => { + logger.verbose('request received in fetchBusinessProfile'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profilePictureSchema, + ClassRef: ProfilePictureDto, + execute: (instance, data) => chatController.fetchBusinessProfile(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('updateProfileName'), ...guards, async (req, res) => { + logger.verbose('request received in updateProfileName'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profileNameSchema, + ClassRef: ProfileNameDto, + execute: (instance, data) => chatController.updateProfileName(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('updateProfileStatus'), ...guards, async (req, res) => { + logger.verbose('request received in updateProfileStatus'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profileStatusSchema, + ClassRef: ProfileStatusDto, + execute: (instance, data) => chatController.updateProfileStatus(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .put(this.routerPath('updateProfilePicture'), ...guards, async (req, res) => { + logger.verbose('request received in updateProfilePicture'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profilePictureSchema, + ClassRef: ProfilePictureDto, + execute: (instance, data) => chatController.updateProfilePicture(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .delete(this.routerPath('removeProfilePicture'), ...guards, async (req, res) => { + logger.verbose('request received in removeProfilePicture'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profilePictureSchema, + ClassRef: ProfilePictureDto, + execute: (instance) => chatController.removeProfilePicture(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/chatwoot.router.ts b/src/whatsapp/routers/chatwoot.router.ts old mode 100644 new mode 100755 index eb779587..214cb2f4 --- a/src/whatsapp/routers/chatwoot.router.ts +++ b/src/whatsapp/routers/chatwoot.router.ts @@ -1,69 +1,69 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { chatwootSchema, instanceNameSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { ChatwootDto } from '../dto/chatwoot.dto'; -import { InstanceDto } from '../dto/instance.dto'; -// import { ChatwootService } from '../services/chatwoot.service'; -import { chatwootController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -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(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { chatwootSchema, instanceNameSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { ChatwootDto } from '../dto/chatwoot.dto'; +import { InstanceDto } from '../dto/instance.dto'; +// import { ChatwootService } from '../services/chatwoot.service'; +import { chatwootController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +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/group.router.ts b/src/whatsapp/routers/group.router.ts old mode 100644 new mode 100755 index f59e82a4..39b47e67 --- a/src/whatsapp/routers/group.router.ts +++ b/src/whatsapp/routers/group.router.ts @@ -1,284 +1,284 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { - createGroupSchema, - getParticipantsSchema, - groupInviteSchema, - groupJidSchema, - groupSendInviteSchema, - toggleEphemeralSchema, - updateGroupDescriptionSchema, - updateGroupPictureSchema, - updateGroupSubjectSchema, - updateParticipantsSchema, - updateSettingsSchema, -} from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { - CreateGroupDto, - GetParticipant, - GroupDescriptionDto, - GroupInvite, - GroupJid, - GroupPictureDto, - GroupSendInvite, - GroupSubjectDto, - GroupToggleEphemeralDto, - GroupUpdateParticipantDto, - GroupUpdateSettingDto, -} from '../dto/group.dto'; -import { groupController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('GroupRouter'); - -export class GroupRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('create'), ...guards, async (req, res) => { - logger.verbose('request received in createGroup'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: createGroupSchema, - ClassRef: CreateGroupDto, - execute: (instance, data) => groupController.createGroup(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('updateGroupSubject'), ...guards, async (req, res) => { - logger.verbose('request received in updateGroupSubject'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - - const response = await this.groupValidate({ - request: req, - schema: updateGroupSubjectSchema, - ClassRef: GroupSubjectDto, - execute: (instance, data) => groupController.updateGroupSubject(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('updateGroupPicture'), ...guards, async (req, res) => { - logger.verbose('request received in updateGroupPicture'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: updateGroupPictureSchema, - ClassRef: GroupPictureDto, - execute: (instance, data) => groupController.updateGroupPicture(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('updateGroupDescription'), ...guards, async (req, res) => { - logger.verbose('request received in updateGroupDescription'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: updateGroupDescriptionSchema, - ClassRef: GroupDescriptionDto, - execute: (instance, data) => groupController.updateGroupDescription(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('findGroupInfos'), ...guards, async (req, res) => { - logger.verbose('request received in findGroupInfos'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: groupJidSchema, - ClassRef: GroupJid, - execute: (instance, data) => groupController.findGroupInfo(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('fetchAllGroups'), ...guards, async (req, res) => { - logger.verbose('request received in fetchAllGroups'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.getParticipantsValidate({ - request: req, - schema: getParticipantsSchema, - ClassRef: GetParticipant, - execute: (instance, data) => groupController.fetchAllGroups(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('participants'), ...guards, async (req, res) => { - logger.verbose('request received in participants'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: groupJidSchema, - ClassRef: GroupJid, - execute: (instance, data) => groupController.findParticipants(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('inviteCode'), ...guards, async (req, res) => { - logger.verbose('request received in inviteCode'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: groupJidSchema, - ClassRef: GroupJid, - execute: (instance, data) => groupController.inviteCode(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('inviteInfo'), ...guards, async (req, res) => { - logger.verbose('request received in inviteInfo'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.inviteCodeValidate({ - request: req, - schema: groupInviteSchema, - ClassRef: GroupInvite, - execute: (instance, data) => groupController.inviteInfo(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('sendInvite'), ...guards, async (req, res) => { - logger.verbose('request received in sendInvite'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupNoValidate({ - request: req, - schema: groupSendInviteSchema, - ClassRef: GroupSendInvite, - execute: (instance, data) => groupController.sendInvite(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }) - .put(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => { - logger.verbose('request received in revokeInviteCode'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: groupJidSchema, - ClassRef: GroupJid, - execute: (instance, data) => groupController.revokeInviteCode(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('updateParticipant'), ...guards, async (req, res) => { - logger.verbose('request received in updateParticipant'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: updateParticipantsSchema, - ClassRef: GroupUpdateParticipantDto, - execute: (instance, data) => groupController.updateGParticipate(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('updateSetting'), ...guards, async (req, res) => { - logger.verbose('request received in updateSetting'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: updateSettingsSchema, - ClassRef: GroupUpdateSettingDto, - execute: (instance, data) => groupController.updateGSetting(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('toggleEphemeral'), ...guards, async (req, res) => { - logger.verbose('request received in toggleEphemeral'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: toggleEphemeralSchema, - ClassRef: GroupToggleEphemeralDto, - execute: (instance, data) => groupController.toggleEphemeral(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .delete(this.routerPath('leaveGroup'), ...guards, async (req, res) => { - logger.verbose('request received in leaveGroup'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.groupValidate({ - request: req, - schema: {}, - ClassRef: GroupJid, - execute: (instance, data) => groupController.leaveGroup(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { + createGroupSchema, + getParticipantsSchema, + groupInviteSchema, + groupJidSchema, + groupSendInviteSchema, + toggleEphemeralSchema, + updateGroupDescriptionSchema, + updateGroupPictureSchema, + updateGroupSubjectSchema, + updateParticipantsSchema, + updateSettingsSchema, +} from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { + CreateGroupDto, + GetParticipant, + GroupDescriptionDto, + GroupInvite, + GroupJid, + GroupPictureDto, + GroupSendInvite, + GroupSubjectDto, + GroupToggleEphemeralDto, + GroupUpdateParticipantDto, + GroupUpdateSettingDto, +} from '../dto/group.dto'; +import { groupController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('GroupRouter'); + +export class GroupRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('create'), ...guards, async (req, res) => { + logger.verbose('request received in createGroup'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: createGroupSchema, + ClassRef: CreateGroupDto, + execute: (instance, data) => groupController.createGroup(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('updateGroupSubject'), ...guards, async (req, res) => { + logger.verbose('request received in updateGroupSubject'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.groupValidate({ + request: req, + schema: updateGroupSubjectSchema, + ClassRef: GroupSubjectDto, + execute: (instance, data) => groupController.updateGroupSubject(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('updateGroupPicture'), ...guards, async (req, res) => { + logger.verbose('request received in updateGroupPicture'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: updateGroupPictureSchema, + ClassRef: GroupPictureDto, + execute: (instance, data) => groupController.updateGroupPicture(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('updateGroupDescription'), ...guards, async (req, res) => { + logger.verbose('request received in updateGroupDescription'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: updateGroupDescriptionSchema, + ClassRef: GroupDescriptionDto, + execute: (instance, data) => groupController.updateGroupDescription(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('findGroupInfos'), ...guards, async (req, res) => { + logger.verbose('request received in findGroupInfos'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: groupJidSchema, + ClassRef: GroupJid, + execute: (instance, data) => groupController.findGroupInfo(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('fetchAllGroups'), ...guards, async (req, res) => { + logger.verbose('request received in fetchAllGroups'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.getParticipantsValidate({ + request: req, + schema: getParticipantsSchema, + ClassRef: GetParticipant, + execute: (instance, data) => groupController.fetchAllGroups(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('participants'), ...guards, async (req, res) => { + logger.verbose('request received in participants'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: groupJidSchema, + ClassRef: GroupJid, + execute: (instance, data) => groupController.findParticipants(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('inviteCode'), ...guards, async (req, res) => { + logger.verbose('request received in inviteCode'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: groupJidSchema, + ClassRef: GroupJid, + execute: (instance, data) => groupController.inviteCode(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('inviteInfo'), ...guards, async (req, res) => { + logger.verbose('request received in inviteInfo'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.inviteCodeValidate({ + request: req, + schema: groupInviteSchema, + ClassRef: GroupInvite, + execute: (instance, data) => groupController.inviteInfo(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('sendInvite'), ...guards, async (req, res) => { + logger.verbose('request received in sendInvite'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupNoValidate({ + request: req, + schema: groupSendInviteSchema, + ClassRef: GroupSendInvite, + execute: (instance, data) => groupController.sendInvite(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .put(this.routerPath('revokeInviteCode'), ...guards, async (req, res) => { + logger.verbose('request received in revokeInviteCode'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: groupJidSchema, + ClassRef: GroupJid, + execute: (instance, data) => groupController.revokeInviteCode(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('updateParticipant'), ...guards, async (req, res) => { + logger.verbose('request received in updateParticipant'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: updateParticipantsSchema, + ClassRef: GroupUpdateParticipantDto, + execute: (instance, data) => groupController.updateGParticipate(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('updateSetting'), ...guards, async (req, res) => { + logger.verbose('request received in updateSetting'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: updateSettingsSchema, + ClassRef: GroupUpdateSettingDto, + execute: (instance, data) => groupController.updateGSetting(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('toggleEphemeral'), ...guards, async (req, res) => { + logger.verbose('request received in toggleEphemeral'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: toggleEphemeralSchema, + ClassRef: GroupToggleEphemeralDto, + execute: (instance, data) => groupController.toggleEphemeral(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .delete(this.routerPath('leaveGroup'), ...guards, async (req, res) => { + logger.verbose('request received in leaveGroup'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.groupValidate({ + request: req, + schema: {}, + ClassRef: GroupJid, + execute: (instance, data) => groupController.leaveGroup(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 old mode 100644 new mode 100755 index fbe28ddd..745fdb26 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -1,62 +1,63 @@ -import { Router } from 'express'; -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'; -import { InstanceRouter } from './instance.router'; -import { ProxyRouter } from './proxy.router'; -import { RabbitmqRouter } from './rabbitmq.router'; -import { MessageRouter } from './sendMessage.router'; -import { SettingsRouter } from './settings.router'; -import { SqsRouter } from './sqs.router'; -import { TypebotRouter } from './typebot.router'; -import { ViewsRouter } from './view.router'; -import { WebhookRouter } from './webhook.router'; -import { WebsocketRouter } from './websocket.router'; - -enum HttpStatus { - OK = 200, - CREATED = 201, - NOT_FOUND = 404, - FORBIDDEN = 403, - BAD_REQUEST = 400, - UNAUTHORIZED = 401, - INTERNAL_SERVER_ERROR = 500, -} - -const router = Router(); -const authType = configService.get('AUTHENTICATION').TYPE; -const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]]; - -const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); - -router - .get('/', (req, res) => { - res.status(HttpStatus.OK).json({ - status: HttpStatus.OK, - message: 'Welcome to the Evolution API, it is working!', - version: packageJson.version, - documentation: `${req.protocol}://${req.get('host')}/docs`, - }); - }) - .use('/instance', new InstanceRouter(configService, ...guards).router) - .use('/manager', new ViewsRouter().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('/chatwoot', new ChatwootRouter(...guards).router) - .use('/settings', new SettingsRouter(...guards).router) - .use('/websocket', new WebsocketRouter(...guards).router) - .use('/rabbitmq', new RabbitmqRouter(...guards).router) - .use('/sqs', new SqsRouter(...guards).router) - .use('/typebot', new TypebotRouter(...guards).router) - .use('/proxy', new ProxyRouter(...guards).router) - .use('/chamaai', new ChamaaiRouter(...guards).router); - -export { HttpStatus, router }; +import { Router } from 'express'; +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'; +import { InstanceRouter } from './instance.router'; +import { ProxyRouter } from './proxy.router'; +import { RabbitmqRouter } from './rabbitmq.router'; +import { OpenaiRouter } from './openai.router'; +import { MessageRouter } from './sendMessage.router'; +import { SettingsRouter } from './settings.router'; +import { SqsRouter } from './sqs.router'; +import { TypebotRouter } from './typebot.router'; +import { ViewsRouter } from './view.router'; +import { WebhookRouter } from './webhook.router'; +import { WebsocketRouter } from './websocket.router'; + +enum HttpStatus { + OK = 200, + CREATED = 201, + NOT_FOUND = 404, + FORBIDDEN = 403, + BAD_REQUEST = 400, + UNAUTHORIZED = 401, + INTERNAL_SERVER_ERROR = 500, +} + +const router = Router(); +const authType = configService.get('AUTHENTICATION').TYPE; +const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard[authType]]; + +const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + +router + .get('/', (req, res) => { + res.status(HttpStatus.OK).json({ + status: HttpStatus.OK, + message: 'Api', + // version: packageJson.version, + // documentation: `${req.protocol}://${req.get('host')}/docs`, + }); + }) + .use('/instance', new InstanceRouter(configService, ...guards).router) + .use('/manager', new ViewsRouter().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('/chatwoot', new ChatwootRouter(...guards).router) + .use('/settings', new SettingsRouter(...guards).router) + .use('/websocket', new WebsocketRouter(...guards).router) + .use('/rabbitmq', new RabbitmqRouter(...guards).router) + .use('/openai', new OpenaiRouter(...guards).router) + .use('/typebot', new TypebotRouter(...guards).router) + .use('/proxy', new ProxyRouter(...guards).router) + .use('/chamaai', new ChamaaiRouter(...guards).router); + +export { HttpStatus, router }; diff --git a/src/whatsapp/routers/instance.router.ts b/src/whatsapp/routers/instance.router.ts old mode 100644 new mode 100755 index 96a1a5da..8de9b769 --- a/src/whatsapp/routers/instance.router.ts +++ b/src/whatsapp/routers/instance.router.ts @@ -1,177 +1,201 @@ -import { RequestHandler, Router } from 'express'; - -import { Auth, ConfigService, Database } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { dbserver } from '../../libs/db.connect'; -import { instanceNameSchema, oldTokenSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { OldToken } from '../services/auth.service'; -import { instanceController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('InstanceRouter'); - -export class InstanceRouter extends RouterBroker { - constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) { - super(); - const auth = configService.get('AUTHENTICATION'); - this.router - .post('/create', ...guards, async (req, res) => { - logger.verbose('request received in createInstance'); - 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) => instanceController.createInstance(instance), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .put(this.routerPath('restart'), ...guards, async (req, res) => { - logger.verbose('request received in restartInstance'); - 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) => instanceController.restartInstance(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('connect'), ...guards, async (req, res) => { - logger.verbose('request received in connectInstance'); - 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) => instanceController.connectToWhatsapp(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('connectionState'), ...guards, async (req, res) => { - logger.verbose('request received in connectionState'); - 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) => instanceController.connectionState(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .get(this.routerPath('fetchInstances', false), ...guards, async (req, res) => { - logger.verbose('request received in fetchInstances'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: null, - ClassRef: InstanceDto, - execute: (instance) => instanceController.fetchInstances(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .delete(this.routerPath('logout'), ...guards, async (req, res) => { - logger.verbose('request received in logoutInstances'); - 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) => instanceController.logout(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }) - .delete(this.routerPath('delete'), ...guards, async (req, res) => { - logger.verbose('request received in deleteInstances'); - 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) => instanceController.deleteInstance(instance), - }); - - return res.status(HttpStatus.OK).json(response); - }); - - if (auth.TYPE === 'jwt') { - this.router.put('/refreshToken', async (req, res) => { - logger.verbose('request received in refreshToken'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: oldTokenSchema, - ClassRef: OldToken, - execute: (_, data) => instanceController.refreshToken(_, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }); - } - - this.router.delete('/deleteDatabase', async (req, res) => { - logger.verbose('request received in deleteDatabase'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const db = this.configService.get('DATABASE'); - if (db.ENABLED) { - try { - await dbserver.dropDatabase(); - return res - .status(HttpStatus.CREATED) - .json({ status: 'SUCCESS', error: false, response: { message: 'database deleted' } }); - } catch (error) { - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: error.message }); - } - } - - return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: 'Database is not enabled' }); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Auth, ConfigService, Database } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { dbserver } from '../../libs/db.connect'; +import { instanceNameSchema, oldTokenSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { OldToken } from '../services/auth.service'; +import { instanceController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('InstanceRouter'); + +export class InstanceRouter extends RouterBroker { + constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) { + super(); + const auth = configService.get('AUTHENTICATION'); + this.router + .post('/create', ...guards, async (req, res) => { + logger.verbose('request received in createInstance'); + 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) => instanceController.createInstance(instance), + }); + + + if (req.query['qrcode']) { + return res.status(HttpStatus.OK).render('qrcode', { + qrcode: response.qrcode.base64, + }); + } else { + return res.status(HttpStatus.CREATED).json(response); + } + //return res.status(HttpStatus.CREATED).json(response); + }) + .put(this.routerPath('restart'), ...guards, async (req, res) => { + logger.verbose('request received in restartInstance'); + 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) => instanceController.restartInstance(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('qr'), ...guards, async (req, res) => { + logger.verbose('request received in get qrCode'); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => instanceController.qrInstance(instance), + }); + return res.status(HttpStatus.OK).render('qrcode', { + qrcode: response, + }); + + }) + .get(this.routerPath('connect'), ...guards, async (req, res) => { + logger.verbose('request received in connectInstance'); + 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) => instanceController.connectToWhatsapp(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('connectionState'), ...guards, async (req, res) => { + logger.verbose('request received in connectionState'); + 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) => instanceController.connectionState(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .get(this.routerPath('fetchInstances', false), ...guards, async (req, res) => { + logger.verbose('request received in fetchInstances'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: null, + ClassRef: InstanceDto, + execute: (instance) => instanceController.fetchInstances(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .delete(this.routerPath('logout'), ...guards, async (req, res) => { + logger.verbose('request received in logoutInstances'); + 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) => instanceController.logout(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }) + .delete(this.routerPath('delete'), ...guards, async (req, res) => { + logger.verbose('request received in deleteInstances'); + 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) => instanceController.deleteInstance(instance), + }); + + return res.status(HttpStatus.OK).json(response); + }); + + if (auth.TYPE === 'jwt') { + this.router.put('/refreshToken', async (req, res) => { + logger.verbose('request received in refreshToken'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: oldTokenSchema, + ClassRef: OldToken, + execute: (_, data) => instanceController.refreshToken(_, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }); + } + + this.router.delete('/deleteDatabase', async (req, res) => { + logger.verbose('request received in deleteDatabase'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const db = this.configService.get('DATABASE'); + if (db.ENABLED) { + try { + await dbserver.dropDatabase(); + return res + .status(HttpStatus.CREATED) + .json({ status: 'SUCCESS', error: false, response: { message: 'database deleted' } }); + } catch (error) { + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: error.message }); + } + } + + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: 'Database is not enabled' }); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/openai.router.ts b/src/whatsapp/routers/openai.router.ts new file mode 100755 index 00000000..a6beb381 --- /dev/null +++ b/src/whatsapp/routers/openai.router.ts @@ -0,0 +1,86 @@ +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, openaiSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { OpenaiDto } from '../dto/openai.dto'; +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; +import { openaiController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('OpenaiRouter'); + +export class OpenaiRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setOpenai'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: openaiSchema, + ClassRef: OpenaiDto, + execute: (instance, data) => openaiController.createOpenai(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findOpenai'); + 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) => openaiController.findOpenai(instance), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('contact'), ...guards, async (req, res) => { + logger.verbose('request received in setOpenai'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: openaiSchema, + ClassRef: ContactOpenaiDto, + execute: (instance, data) => openaiController.createContactOpenai(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + + .get(this.routerPath('findcontact'), ...guards, async (req, res) => { + logger.verbose('request received in findOpenai'); + 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) => openaiController.findContactOpenai(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/proxy.router.ts b/src/whatsapp/routers/proxy.router.ts old mode 100644 new mode 100755 index 2ae0141b..624a2bbb --- a/src/whatsapp/routers/proxy.router.ts +++ b/src/whatsapp/routers/proxy.router.ts @@ -1,52 +1,52 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { instanceNameSchema, proxySchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { ProxyDto } from '../dto/proxy.dto'; -import { proxyController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('ProxyRouter'); - -export class ProxyRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { - logger.verbose('request received in setProxy'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: proxySchema, - ClassRef: ProxyDto, - execute: (instance, data) => proxyController.createProxy(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('find'), ...guards, async (req, res) => { - logger.verbose('request received in findProxy'); - 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) => proxyController.findProxy(instance), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, proxySchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { ProxyDto } from '../dto/proxy.dto'; +import { proxyController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('ProxyRouter'); + +export class ProxyRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setProxy'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: proxySchema, + ClassRef: ProxyDto, + execute: (instance, data) => proxyController.createProxy(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findProxy'); + 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) => proxyController.findProxy(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/rabbitmq.router.ts b/src/whatsapp/routers/rabbitmq.router.ts old mode 100644 new mode 100755 index e3f65283..a7b1b6fc --- a/src/whatsapp/routers/rabbitmq.router.ts +++ b/src/whatsapp/routers/rabbitmq.router.ts @@ -1,52 +1,52 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { instanceNameSchema, rabbitmqSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { RabbitmqDto } from '../dto/rabbitmq.dto'; -import { rabbitmqController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('RabbitmqRouter'); - -export class RabbitmqRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { - logger.verbose('request received in setRabbitmq'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: rabbitmqSchema, - ClassRef: RabbitmqDto, - execute: (instance, data) => rabbitmqController.createRabbitmq(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('find'), ...guards, async (req, res) => { - logger.verbose('request received in findRabbitmq'); - 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) => rabbitmqController.findRabbitmq(instance), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, rabbitmqSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { RabbitmqDto } from '../dto/rabbitmq.dto'; +import { rabbitmqController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('RabbitmqRouter'); + +export class RabbitmqRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setRabbitmq'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: rabbitmqSchema, + ClassRef: RabbitmqDto, + execute: (instance, data) => rabbitmqController.createRabbitmq(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findRabbitmq'); + 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) => rabbitmqController.findRabbitmq(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/sendMessage.router.ts b/src/whatsapp/routers/sendMessage.router.ts old mode 100644 new mode 100755 index d87db44d..32233589 --- a/src/whatsapp/routers/sendMessage.router.ts +++ b/src/whatsapp/routers/sendMessage.router.ts @@ -1,219 +1,219 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { - audioMessageSchema, - buttonMessageSchema, - contactMessageSchema, - listMessageSchema, - locationMessageSchema, - mediaMessageSchema, - pollMessageSchema, - reactionMessageSchema, - statusMessageSchema, - stickerMessageSchema, - textMessageSchema, -} from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { - SendAudioDto, - SendButtonDto, - SendContactDto, - SendListDto, - SendLocationDto, - SendMediaDto, - SendPollDto, - SendReactionDto, - SendStatusDto, - SendStickerDto, - SendTextDto, -} from '../dto/sendMessage.dto'; -import { sendMessageController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('MessageRouter'); - -export class MessageRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('sendText'), ...guards, async (req, res) => { - logger.verbose('request received in sendText'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: textMessageSchema, - ClassRef: SendTextDto, - execute: (instance, data) => sendMessageController.sendText(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendMedia'), ...guards, async (req, res) => { - logger.verbose('request received in sendMedia'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: mediaMessageSchema, - ClassRef: SendMediaDto, - execute: (instance, data) => sendMessageController.sendMedia(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendWhatsAppAudio'), ...guards, async (req, res) => { - logger.verbose('request received in sendWhatsAppAudio'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: audioMessageSchema, - ClassRef: SendMediaDto, - execute: (instance, data) => sendMessageController.sendWhatsAppAudio(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendButtons'), ...guards, async (req, res) => { - logger.verbose('request received in sendButtons'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: buttonMessageSchema, - ClassRef: SendButtonDto, - execute: (instance, data) => sendMessageController.sendButtons(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendLocation'), ...guards, async (req, res) => { - logger.verbose('request received in sendLocation'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: locationMessageSchema, - ClassRef: SendLocationDto, - execute: (instance, data) => sendMessageController.sendLocation(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendList'), ...guards, async (req, res) => { - logger.verbose('request received in sendList'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: listMessageSchema, - ClassRef: SendListDto, - execute: (instance, data) => sendMessageController.sendList(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendContact'), ...guards, async (req, res) => { - logger.verbose('request received in sendContact'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: contactMessageSchema, - ClassRef: SendContactDto, - execute: (instance, data) => sendMessageController.sendContact(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendReaction'), ...guards, async (req, res) => { - logger.verbose('request received in sendReaction'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: reactionMessageSchema, - ClassRef: SendReactionDto, - execute: (instance, data) => sendMessageController.sendReaction(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendPoll'), ...guards, async (req, res) => { - logger.verbose('request received in sendPoll'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: pollMessageSchema, - ClassRef: SendPollDto, - execute: (instance, data) => sendMessageController.sendPoll(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendStatus'), ...guards, async (req, res) => { - logger.verbose('request received in sendStatus'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: statusMessageSchema, - ClassRef: SendStatusDto, - execute: (instance, data) => sendMessageController.sendStatus(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }) - .post(this.routerPath('sendSticker'), ...guards, async (req, res) => { - logger.verbose('request received in sendSticker'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: stickerMessageSchema, - ClassRef: SendStickerDto, - execute: (instance, data) => sendMessageController.sendSticker(instance, data), - }); - - return res.status(HttpStatus.CREATED).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { + audioMessageSchema, + buttonMessageSchema, + contactMessageSchema, + listMessageSchema, + locationMessageSchema, + mediaMessageSchema, + pollMessageSchema, + reactionMessageSchema, + statusMessageSchema, + stickerMessageSchema, + textMessageSchema, +} from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { + SendAudioDto, + SendButtonDto, + SendContactDto, + SendListDto, + SendLocationDto, + SendMediaDto, + SendPollDto, + SendReactionDto, + SendStatusDto, + SendStickerDto, + SendTextDto, +} from '../dto/sendMessage.dto'; +import { sendMessageController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('MessageRouter'); + +export class MessageRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('sendText'), ...guards, async (req, res) => { + logger.verbose('request received in sendText'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: textMessageSchema, + ClassRef: SendTextDto, + execute: (instance, data) => sendMessageController.sendText(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendMedia'), ...guards, async (req, res) => { + logger.verbose('request received in sendMedia'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: mediaMessageSchema, + ClassRef: SendMediaDto, + execute: (instance, data) => sendMessageController.sendMedia(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendWhatsAppAudio'), ...guards, async (req, res) => { + logger.verbose('request received in sendWhatsAppAudio'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: audioMessageSchema, + ClassRef: SendMediaDto, + execute: (instance, data) => sendMessageController.sendWhatsAppAudio(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendButtons'), ...guards, async (req, res) => { + logger.verbose('request received in sendButtons'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: buttonMessageSchema, + ClassRef: SendButtonDto, + execute: (instance, data) => sendMessageController.sendButtons(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendLocation'), ...guards, async (req, res) => { + logger.verbose('request received in sendLocation'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: locationMessageSchema, + ClassRef: SendLocationDto, + execute: (instance, data) => sendMessageController.sendLocation(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendList'), ...guards, async (req, res) => { + logger.verbose('request received in sendList'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: listMessageSchema, + ClassRef: SendListDto, + execute: (instance, data) => sendMessageController.sendList(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendContact'), ...guards, async (req, res) => { + logger.verbose('request received in sendContact'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: contactMessageSchema, + ClassRef: SendContactDto, + execute: (instance, data) => sendMessageController.sendContact(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendReaction'), ...guards, async (req, res) => { + logger.verbose('request received in sendReaction'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: reactionMessageSchema, + ClassRef: SendReactionDto, + execute: (instance, data) => sendMessageController.sendReaction(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendPoll'), ...guards, async (req, res) => { + logger.verbose('request received in sendPoll'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: pollMessageSchema, + ClassRef: SendPollDto, + execute: (instance, data) => sendMessageController.sendPoll(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendStatus'), ...guards, async (req, res) => { + logger.verbose('request received in sendStatus'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: statusMessageSchema, + ClassRef: SendStatusDto, + execute: (instance, data) => sendMessageController.sendStatus(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) + .post(this.routerPath('sendSticker'), ...guards, async (req, res) => { + logger.verbose('request received in sendSticker'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: stickerMessageSchema, + ClassRef: SendStickerDto, + execute: (instance, data) => sendMessageController.sendSticker(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/settings.router.ts b/src/whatsapp/routers/settings.router.ts old mode 100644 new mode 100755 index 6bd4d549..1bbe3d0b --- a/src/whatsapp/routers/settings.router.ts +++ b/src/whatsapp/routers/settings.router.ts @@ -1,53 +1,53 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { instanceNameSchema, settingsSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { SettingsDto } from '../dto/settings.dto'; -// import { SettingsService } from '../services/settings.service'; -import { settingsController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('SettingsRouter'); - -export class SettingsRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { - logger.verbose('request received in setSettings'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: settingsSchema, - ClassRef: SettingsDto, - execute: (instance, data) => settingsController.createSettings(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('find'), ...guards, async (req, res) => { - logger.verbose('request received in findSettings'); - 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) => settingsController.findSettings(instance), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, settingsSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +// import { SettingsService } from '../services/settings.service'; +import { settingsController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('SettingsRouter'); + +export class SettingsRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setSettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: settingsSchema, + ClassRef: SettingsDto, + execute: (instance, data) => settingsController.createSettings(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findSettings'); + 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) => settingsController.findSettings(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/sqs.router.ts b/src/whatsapp/routers/sqs.router.ts old mode 100644 new mode 100755 index e1bf8e93..2275e916 --- a/src/whatsapp/routers/sqs.router.ts +++ b/src/whatsapp/routers/sqs.router.ts @@ -1,52 +1,52 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { instanceNameSchema, sqsSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { SqsDto } from '../dto/sqs.dto'; -import { sqsController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('SqsRouter'); - -export class SqsRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { - logger.verbose('request received in setSqs'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: sqsSchema, - ClassRef: SqsDto, - execute: (instance, data) => sqsController.createSqs(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('find'), ...guards, async (req, res) => { - logger.verbose('request received in findSqs'); - 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) => sqsController.findSqs(instance), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, sqsSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { SqsDto } from '../dto/sqs.dto'; +import { sqsController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('SqsRouter'); + +export class SqsRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setSqs'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: sqsSchema, + ClassRef: SqsDto, + execute: (instance, data) => sqsController.createSqs(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findSqs'); + 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) => sqsController.findSqs(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} \ No newline at end of file diff --git a/src/whatsapp/routers/typebot.router.ts b/src/whatsapp/routers/typebot.router.ts old mode 100644 new mode 100755 index 0f785de3..b9c6a1f8 --- a/src/whatsapp/routers/typebot.router.ts +++ b/src/whatsapp/routers/typebot.router.ts @@ -1,89 +1,89 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { - instanceNameSchema, - typebotSchema, - typebotStartSchema, - typebotStatusSchema, -} from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { TypebotDto } from '../dto/typebot.dto'; -import { typebotController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('TypebotRouter'); - -export class TypebotRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { - logger.verbose('request received in setTypebot'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: typebotSchema, - ClassRef: TypebotDto, - execute: (instance, data) => typebotController.createTypebot(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('find'), ...guards, async (req, res) => { - logger.verbose('request received in findTypebot'); - 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) => typebotController.findTypebot(instance), - }); - - res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('changeStatus'), ...guards, async (req, res) => { - logger.verbose('request received in changeStatusTypebot'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: typebotStatusSchema, - ClassRef: InstanceDto, - execute: (instance, data) => typebotController.changeStatus(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }) - .post(this.routerPath('start'), ...guards, async (req, res) => { - logger.verbose('request received in startTypebot'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: typebotStartSchema, - ClassRef: InstanceDto, - execute: (instance, data) => typebotController.startTypebot(instance, data), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { + instanceNameSchema, + typebotSchema, + typebotStartSchema, + typebotStatusSchema, +} from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { TypebotDto } from '../dto/typebot.dto'; +import { typebotController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('TypebotRouter'); + +export class TypebotRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setTypebot'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: typebotSchema, + ClassRef: TypebotDto, + execute: (instance, data) => typebotController.createTypebot(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findTypebot'); + 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) => typebotController.findTypebot(instance), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('changeStatus'), ...guards, async (req, res) => { + logger.verbose('request received in changeStatusTypebot'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: typebotStatusSchema, + ClassRef: InstanceDto, + execute: (instance, data) => typebotController.changeStatus(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }) + .post(this.routerPath('start'), ...guards, async (req, res) => { + logger.verbose('request received in startTypebot'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: typebotStartSchema, + ClassRef: InstanceDto, + execute: (instance, data) => typebotController.startTypebot(instance, data), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/view.router.ts b/src/whatsapp/routers/view.router.ts old mode 100644 new mode 100755 index 11002777..a89ebebd --- a/src/whatsapp/routers/view.router.ts +++ b/src/whatsapp/routers/view.router.ts @@ -1,16 +1,16 @@ -import { Router } from 'express'; - -import { RouterBroker } from '../abstract/abstract.router'; -import { viewsController } from '../whatsapp.module'; - -export class ViewsRouter extends RouterBroker { - constructor() { - super(); - - this.router.get('/', (req, res) => { - return viewsController.manager(req, res); - }); - } - - public readonly router = Router(); -} +import { Router } from 'express'; + +import { RouterBroker } from '../abstract/abstract.router'; +import { viewsController } from '../whatsapp.module'; + +export class ViewsRouter extends RouterBroker { + constructor() { + super(); + + this.router.get('/', (req, res) => { + return viewsController.manager(req, res); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/webhook.router.ts b/src/whatsapp/routers/webhook.router.ts old mode 100644 new mode 100755 index 835d6014..d0113724 --- a/src/whatsapp/routers/webhook.router.ts +++ b/src/whatsapp/routers/webhook.router.ts @@ -1,52 +1,52 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { instanceNameSchema, webhookSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { WebhookDto } from '../dto/webhook.dto'; -import { webhookController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('WebhookRouter'); - -export class WebhookRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { - logger.verbose('request received in setWebhook'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: webhookSchema, - ClassRef: WebhookDto, - execute: (instance, data) => webhookController.createWebhook(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('find'), ...guards, async (req, res) => { - logger.verbose('request received in findWebhook'); - 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) => webhookController.findWebhook(instance), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, webhookSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { WebhookDto } from '../dto/webhook.dto'; +import { webhookController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('WebhookRouter'); + +export class WebhookRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setWebhook'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: webhookSchema, + ClassRef: WebhookDto, + execute: (instance, data) => webhookController.createWebhook(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findWebhook'); + 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) => webhookController.findWebhook(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/websocket.router.ts b/src/whatsapp/routers/websocket.router.ts old mode 100644 new mode 100755 index f04cad0d..d0a59da0 --- a/src/whatsapp/routers/websocket.router.ts +++ b/src/whatsapp/routers/websocket.router.ts @@ -1,52 +1,52 @@ -import { RequestHandler, Router } from 'express'; - -import { Logger } from '../../config/logger.config'; -import { instanceNameSchema, websocketSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { WebsocketDto } from '../dto/websocket.dto'; -import { websocketController } from '../whatsapp.module'; -import { HttpStatus } from './index.router'; - -const logger = new Logger('WebsocketRouter'); - -export class WebsocketRouter extends RouterBroker { - constructor(...guards: RequestHandler[]) { - super(); - this.router - .post(this.routerPath('set'), ...guards, async (req, res) => { - logger.verbose('request received in setWebsocket'); - logger.verbose('request body: '); - logger.verbose(req.body); - - logger.verbose('request query: '); - logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: websocketSchema, - ClassRef: WebsocketDto, - execute: (instance, data) => websocketController.createWebsocket(instance, data), - }); - - res.status(HttpStatus.CREATED).json(response); - }) - .get(this.routerPath('find'), ...guards, async (req, res) => { - logger.verbose('request received in findWebsocket'); - 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) => websocketController.findWebsocket(instance), - }); - - res.status(HttpStatus.OK).json(response); - }); - } - - public readonly router = Router(); -} +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, websocketSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { WebsocketDto } from '../dto/websocket.dto'; +import { websocketController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('WebsocketRouter'); + +export class WebsocketRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setWebsocket'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: websocketSchema, + ClassRef: WebsocketDto, + execute: (instance, data) => websocketController.createWebsocket(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findWebsocket'); + 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) => websocketController.findWebsocket(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/services/auth.service.ts b/src/whatsapp/services/auth.service.ts old mode 100644 new mode 100755 index 915a07b7..420f9216 --- a/src/whatsapp/services/auth.service.ts +++ b/src/whatsapp/services/auth.service.ts @@ -1,177 +1,177 @@ -import axios from 'axios'; -import { isJWT } from 'class-validator'; -import { sign, verify } from 'jsonwebtoken'; -import { v4 } from 'uuid'; - -import { name as apiName } from '../../../package.json'; -import { Auth, ConfigService, Webhook } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { BadRequestException } from '../../exceptions'; -import { InstanceDto } from '../dto/instance.dto'; -import { RepositoryBroker } from '../repository/repository.manager'; -import { WAMonitoringService } from './monitor.service'; - -export type JwtPayload = { - instanceName: string; - apiName: string; - jwt?: string; - apikey?: string; - tokenId: string; -}; - -export class OldToken { - oldToken: string; -} - -export class AuthService { - constructor( - private readonly configService: ConfigService, - private readonly waMonitor: WAMonitoringService, - private readonly repository: RepositoryBroker, - ) {} - - private readonly logger = new Logger(AuthService.name); - - private async jwt(instance: InstanceDto) { - const jwtOpts = this.configService.get('AUTHENTICATION').JWT; - const token = sign( - { - instanceName: instance.instanceName, - apiName, - tokenId: v4(), - }, - jwtOpts.SECRET, - { expiresIn: jwtOpts.EXPIRIN_IN, encoding: 'utf8', subject: 'g-t' }, - ); - - this.logger.verbose('JWT token created: ' + token); - - const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName); - - this.logger.verbose('JWT token saved in database'); - - if (auth['error']) { - this.logger.error({ - localError: AuthService.name + '.jwt', - error: auth['error'], - }); - throw new BadRequestException('Authentication error', auth['error']?.toString()); - } - - return { jwt: token }; - } - - private async apikey(instance: InstanceDto, token?: string) { - const apikey = token ? token : v4().toUpperCase(); - - this.logger.verbose(token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey); - - const auth = await this.repository.auth.create({ apikey }, instance.instanceName); - - this.logger.verbose('APIKEY saved in database'); - - if (auth['error']) { - this.logger.error({ - localError: AuthService.name + '.apikey', - error: auth['error'], - }); - throw new BadRequestException('Authentication error', auth['error']?.toString()); - } - - return { apikey }; - } - - public async checkDuplicateToken(token: string) { - const instances = await this.waMonitor.instanceInfo(); - - this.logger.verbose('checking duplicate token'); - - const instance = instances.find((instance) => instance.instance.apikey === token); - - if (instance) { - throw new BadRequestException('Token already exists'); - } - - this.logger.verbose('available token'); - - return true; - } - - public async generateHash(instance: InstanceDto, token?: string) { - const options = this.configService.get('AUTHENTICATION'); - - this.logger.verbose('generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName); - - return (await this[options.TYPE](instance, token)) as { jwt: string } | { apikey: string }; - } - - public async refreshToken({ oldToken }: OldToken) { - this.logger.verbose('refreshing token'); - - if (!isJWT(oldToken)) { - throw new BadRequestException('Invalid "oldToken"'); - } - - try { - const jwtOpts = this.configService.get('AUTHENTICATION').JWT; - - this.logger.verbose('checking oldToken'); - - const decode = verify(oldToken, jwtOpts.SECRET, { - ignoreExpiration: true, - }) as Pick; - - this.logger.verbose('checking token in database'); - - const tokenStore = await this.repository.auth.find(decode.instanceName); - - const decodeTokenStore = verify(tokenStore.jwt, jwtOpts.SECRET, { - ignoreExpiration: true, - }) as Pick; - - this.logger.verbose('checking tokenId'); - - if (decode.tokenId !== decodeTokenStore.tokenId) { - throw new BadRequestException('Invalid "oldToken"'); - } - - this.logger.verbose('generating new token'); - - const token = { - jwt: (await this.jwt({ instanceName: decode.instanceName })).jwt, - instanceName: decode.instanceName, - }; - - try { - this.logger.verbose('checking webhook'); - const webhook = await this.repository.webhook.find(decode.instanceName); - if (webhook?.enabled && this.configService.get('WEBHOOK').EVENTS.NEW_JWT_TOKEN) { - this.logger.verbose('sending webhook'); - - const httpService = axios.create({ baseURL: webhook.url }); - await httpService.post( - '', - { - event: 'new.jwt', - instance: decode.instanceName, - data: token, - }, - { params: { owner: this.waMonitor.waInstances[decode.instanceName].wuid } }, - ); - } - } catch (error) { - this.logger.error(error); - } - - this.logger.verbose('token refreshed'); - - return token; - } catch (error) { - this.logger.error({ - localError: AuthService.name + '.refreshToken', - error, - }); - throw new BadRequestException('Invalid "oldToken"'); - } - } -} +import axios from 'axios'; +import { isJWT } from 'class-validator'; +import { sign, verify } from 'jsonwebtoken'; +import { v4 } from 'uuid'; + +import { name as apiName } from '../../../package.json'; +import { Auth, ConfigService, Webhook } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { RepositoryBroker } from '../repository/repository.manager'; +import { WAMonitoringService } from './monitor.service'; + +export type JwtPayload = { + instanceName: string; + apiName: string; + jwt?: string; + apikey?: string; + tokenId: string; +}; + +export class OldToken { + oldToken: string; +} + +export class AuthService { + constructor( + private readonly configService: ConfigService, + private readonly waMonitor: WAMonitoringService, + private readonly repository: RepositoryBroker, + ) {} + + private readonly logger = new Logger(AuthService.name); + + private async jwt(instance: InstanceDto) { + const jwtOpts = this.configService.get('AUTHENTICATION').JWT; + const token = sign( + { + instanceName: instance.instanceName, + apiName, + tokenId: v4(), + }, + jwtOpts.SECRET, + { expiresIn: jwtOpts.EXPIRIN_IN, encoding: 'utf8', subject: 'g-t' }, + ); + + this.logger.verbose('JWT token created: ' + token); + + const auth = await this.repository.auth.create({ jwt: token }, instance.instanceName); + + this.logger.verbose('JWT token saved in database'); + + if (auth['error']) { + this.logger.error({ + localError: AuthService.name + '.jwt', + error: auth['error'], + }); + throw new BadRequestException('Authentication error', auth['error']?.toString()); + } + + return { jwt: token }; + } + + private async apikey(instance: InstanceDto, token?: string) { + const apikey = token ? token : v4().toUpperCase(); + + this.logger.verbose(token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey); + + const auth = await this.repository.auth.create({ apikey }, instance.instanceName); + + this.logger.verbose('APIKEY saved in database'); + + if (auth['error']) { + this.logger.error({ + localError: AuthService.name + '.apikey', + error: auth['error'], + }); + throw new BadRequestException('Authentication error', auth['error']?.toString()); + } + + return { apikey }; + } + + public async checkDuplicateToken(token: string) { + const instances = await this.waMonitor.instanceInfo(); + + this.logger.verbose('checking duplicate token'); + + const instance = instances.find((instance) => instance.instance.apikey === token); + + if (instance) { + throw new BadRequestException('Token already exists'); + } + + this.logger.verbose('available token'); + + return true; + } + + public async generateHash(instance: InstanceDto, token?: string) { + const options = this.configService.get('AUTHENTICATION'); + + this.logger.verbose('generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName); + + return (await this[options.TYPE](instance, token)) as { jwt: string } | { apikey: string }; + } + + public async refreshToken({ oldToken }: OldToken) { + this.logger.verbose('refreshing token'); + + if (!isJWT(oldToken)) { + throw new BadRequestException('Invalid "oldToken"'); + } + + try { + const jwtOpts = this.configService.get('AUTHENTICATION').JWT; + + this.logger.verbose('checking oldToken'); + + const decode = verify(oldToken, jwtOpts.SECRET, { + ignoreExpiration: true, + }) as Pick; + + this.logger.verbose('checking token in database'); + + const tokenStore = await this.repository.auth.find(decode.instanceName); + + const decodeTokenStore = verify(tokenStore.jwt, jwtOpts.SECRET, { + ignoreExpiration: true, + }) as Pick; + + this.logger.verbose('checking tokenId'); + + if (decode.tokenId !== decodeTokenStore.tokenId) { + throw new BadRequestException('Invalid "oldToken"'); + } + + this.logger.verbose('generating new token'); + + const token = { + jwt: (await this.jwt({ instanceName: decode.instanceName })).jwt, + instanceName: decode.instanceName, + }; + + try { + this.logger.verbose('checking webhook'); + const webhook = await this.repository.webhook.find(decode.instanceName); + if (webhook?.enabled && this.configService.get('WEBHOOK').EVENTS.NEW_JWT_TOKEN) { + this.logger.verbose('sending webhook'); + + const httpService = axios.create({ baseURL: webhook.url }); + await httpService.post( + '', + { + event: 'new.jwt', + instance: decode.instanceName, + data: token, + }, + { params: { owner: this.waMonitor.waInstances[decode.instanceName].wuid } }, + ); + } + } catch (error) { + this.logger.error(error); + } + + this.logger.verbose('token refreshed'); + + return token; + } catch (error) { + this.logger.error({ + localError: AuthService.name + '.refreshToken', + error, + }); + throw new BadRequestException('Invalid "oldToken"'); + } + } +} diff --git a/src/whatsapp/services/chamaai.service.ts b/src/whatsapp/services/chamaai.service.ts old mode 100644 new mode 100755 index ad2a42ad..d4ebe1fd --- a/src/whatsapp/services/chamaai.service.ts +++ b/src/whatsapp/services/chamaai.service.ts @@ -1,230 +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 { - 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('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', - }); - } - } - } -} +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 { + 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('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', + }); + } + } + } +} diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts old mode 100644 new mode 100755 index 5523ccf2..c1586483 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1,1596 +1,1655 @@ -import ChatwootClient from '@figuro/chatwoot-sdk'; -import axios from 'axios'; -import FormData from 'form-data'; -import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs'; -import Jimp from 'jimp'; -import mimeTypes from 'mime-types'; -import path from 'path'; - -import { ConfigService } from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { ROOT_DIR } from '../../config/path.config'; -import { ChatwootDto } from '../dto/chatwoot.dto'; -import { InstanceDto } from '../dto/instance.dto'; -import { SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto'; -import { WAMonitoringService } from './monitor.service'; - -export class ChatwootService { - private messageCacheFile: string; - private messageCache: Set; - - private readonly logger = new Logger(ChatwootService.name); - - private provider: any; - - constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) { - this.messageCache = new Set(); - } - - private loadMessageCache(): Set { - this.logger.verbose('load message cache'); - try { - const cacheData = readFileSync(this.messageCacheFile, 'utf-8'); - const cacheArray = cacheData.split('\n'); - return new Set(cacheArray); - } catch (error) { - return new Set(); - } - } - - private saveMessageCache() { - this.logger.verbose('save message cache'); - const cacheData = Array.from(this.messageCache).join('\n'); - writeFileSync(this.messageCacheFile, cacheData, 'utf-8'); - this.logger.verbose('message cache saved'); - } - - private clearMessageCache() { - this.logger.verbose('clear message cache'); - this.messageCache.clear(); - this.saveMessageCache(); - } - - private async getProvider(instance: InstanceDto) { - this.logger.verbose('get provider to instance: ' + instance.instanceName); - try { - const provider = await this.waMonitor.waInstances[instance.instanceName].findChatwoot(); - - if (!provider) { - this.logger.warn('provider not found'); - return null; - } - - this.logger.verbose('provider found'); - - return provider; - } catch (error) { - this.logger.error('provider not found'); - return null; - } - } - - private async clientCw(instance: InstanceDto) { - this.logger.verbose('get client to instance: ' + instance.instanceName); - const provider = await this.getProvider(instance); - - if (!provider) { - this.logger.error('provider not found'); - return null; - } - - this.logger.verbose('provider found'); - - this.provider = provider; - - this.logger.verbose('create client to instance: ' + instance.instanceName); - const client = new ChatwootClient({ - config: { - basePath: provider.url, - with_credentials: true, - credentials: 'include', - token: provider.token, - }, - }); - - this.logger.verbose('client created'); - - return client; - } - - public create(instance: InstanceDto, data: ChatwootDto) { - this.logger.verbose('create chatwoot: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setChatwoot(data); - - this.logger.verbose('chatwoot created'); - return data; - } - - public async find(instance: InstanceDto): Promise { - this.logger.verbose('find chatwoot: ' + instance.instanceName); - try { - return await this.waMonitor.waInstances[instance.instanceName].findChatwoot(); - } catch (error) { - this.logger.error('chatwoot not found'); - return { enabled: null, url: '' }; - } - } - - public async getContact(instance: InstanceDto, id: number) { - this.logger.verbose('get contact to instance: ' + instance.instanceName); - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - if (!id) { - this.logger.warn('id is required'); - return null; - } - - this.logger.verbose('find contact in chatwoot'); - const contact = await client.contact.getContactable({ - accountId: this.provider.account_id, - id, - }); - - if (!contact) { - this.logger.warn('contact not found'); - return null; - } - - this.logger.verbose('contact found'); - return contact; - } - - public async initInstanceChatwoot( - instance: InstanceDto, - inboxName: string, - webhookUrl: string, - qrcode: boolean, - number: string, - ) { - this.logger.verbose('init instance chatwoot: ' + instance.instanceName); - - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - this.logger.verbose('find inbox in chatwoot'); - const findInbox: any = await client.inboxes.list({ - accountId: this.provider.account_id, - }); - - this.logger.verbose('check duplicate inbox'); - const checkDuplicate = findInbox.payload.map((inbox) => inbox.name).includes(inboxName); - - let inboxId: number; - - if (!checkDuplicate) { - this.logger.verbose('create inbox in chatwoot'); - const data = { - type: 'api', - webhook_url: webhookUrl, - }; - - const inbox = await client.inboxes.create({ - accountId: this.provider.account_id, - data: { - name: inboxName, - channel: data as any, - }, - }); - - if (!inbox) { - this.logger.warn('inbox not found'); - return null; - } - - inboxId = inbox.id; - } else { - this.logger.verbose('find inbox in chatwoot'); - const inbox = findInbox.payload.find((inbox) => inbox.name === inboxName); - - if (!inbox) { - this.logger.warn('inbox not found'); - return null; - } - - inboxId = inbox.id; - } - - this.logger.verbose('find contact in chatwoot and create if not exists'); - const contact = - (await this.findContact(instance, '123456')) || - ((await this.createContact( - instance, - '123456', - inboxId, - false, - 'EvolutionAPI', - 'https://evolution-api.com/files/evolution-api-favicon.png', - )) as any); - - if (!contact) { - this.logger.warn('contact not found'); - return null; - } - - const contactId = contact.id || contact.payload.contact.id; - - if (qrcode) { - this.logger.verbose('create conversation in chatwoot'); - const data = { - contact_id: contactId.toString(), - inbox_id: inboxId.toString(), - }; - - if (this.provider.conversation_pending) { - data['status'] = 'pending'; - } - - const conversation = await client.conversations.create({ - accountId: this.provider.account_id, - data, - }); - - if (!conversation) { - this.logger.warn('conversation not found'); - return null; - } - - this.logger.verbose('create message for init instance in chatwoot'); - - let contentMsg = 'init'; - - if (number) { - contentMsg = `init:${number}`; - } - - const message = await client.messages.create({ - accountId: this.provider.account_id, - conversationId: conversation.id, - data: { - content: contentMsg, - message_type: 'outgoing', - }, - }); - - if (!message) { - this.logger.warn('conversation not found'); - return null; - } - } - - this.logger.verbose('instance chatwoot initialized'); - return true; - } - - public async createContact( - instance: InstanceDto, - phoneNumber: string, - inboxId: number, - isGroup: boolean, - name?: string, - avatar_url?: string, - jid?: string, - ) { - this.logger.verbose('create contact to instance: ' + instance.instanceName); - - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - let data: any = {}; - if (!isGroup) { - this.logger.verbose('create contact in chatwoot'); - data = { - inbox_id: inboxId, - name: name || phoneNumber, - phone_number: `+${phoneNumber}`, - identifier: jid, - avatar_url: avatar_url, - }; - } else { - this.logger.verbose('create contact group in chatwoot'); - data = { - inbox_id: inboxId, - name: name || phoneNumber, - identifier: phoneNumber, - avatar_url: avatar_url, - }; - } - - this.logger.verbose('create contact in chatwoot'); - const contact = await client.contacts.create({ - accountId: this.provider.account_id, - data, - }); - - if (!contact) { - this.logger.warn('contact not found'); - return null; - } - - this.logger.verbose('contact created'); - return contact; - } - - public async updateContact(instance: InstanceDto, id: number, data: any) { - this.logger.verbose('update contact to instance: ' + instance.instanceName); - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - if (!id) { - this.logger.warn('id is required'); - return null; - } - - this.logger.verbose('update contact in chatwoot'); - try { - const contact = await client.contacts.update({ - accountId: this.provider.account_id, - id, - data, - }); - - this.logger.verbose('contact updated'); - return contact; - } catch (error) { - this.logger.error(error); - } - } - - public async findContact(instance: InstanceDto, phoneNumber: string) { - this.logger.verbose('find contact to instance: ' + instance.instanceName); - - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - let query: any; - - if (!phoneNumber.includes('@g.us')) { - this.logger.verbose('format phone number'); - query = `+${phoneNumber}`; - } else { - this.logger.verbose('format group id'); - query = phoneNumber; - } - - this.logger.verbose('find contact in chatwoot'); - const contact: any = await client.contacts.search({ - accountId: this.provider.account_id, - q: query, - }); - - if (!contact) { - this.logger.warn('contact not found'); - return null; - } - - if (!phoneNumber.includes('@g.us')) { - this.logger.verbose('return contact'); - return contact.payload.find((contact) => contact.phone_number === query); - } else { - this.logger.verbose('return group'); - return contact.payload.find((contact) => contact.identifier === query); - } - } - - public async createConversation(instance: InstanceDto, body: any) { - this.logger.verbose('create conversation to instance: ' + instance.instanceName); - try { - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - const isGroup = body.key.remoteJid.includes('@g.us'); - - this.logger.verbose('is group: ' + isGroup); - - const chatId = isGroup ? body.key.remoteJid : body.key.remoteJid.split('@')[0]; - - this.logger.verbose('chat id: ' + chatId); - - let nameContact: string; - - nameContact = !body.key.fromMe ? body.pushName : chatId; - - this.logger.verbose('get inbox to instance: ' + instance.instanceName); - const filterInbox = await this.getInbox(instance); - - if (!filterInbox) { - this.logger.warn('inbox not found'); - return null; - } - - if (isGroup) { - this.logger.verbose('get group name'); - const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId); - - nameContact = `${group.subject} (GROUP)`; - - this.logger.verbose('find or create participant in chatwoot'); - - const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture( - body.key.participant.split('@')[0], - ); - - const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]); - - if (findParticipant) { - if (!findParticipant.name || findParticipant.name === chatId) { - await this.updateContact(instance, findParticipant.id, { - name: body.pushName, - avatar_url: picture_url.profilePictureUrl || null, - }); - } - } else { - await this.createContact( - instance, - body.key.participant.split('@')[0], - filterInbox.id, - false, - body.pushName, - picture_url.profilePictureUrl || null, - body.key.participant, - ); - } - } - - this.logger.verbose('find or create contact in chatwoot'); - - const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId); - - const findContact = await this.findContact(instance, chatId); - - let contact: any; - if (body.key.fromMe) { - if (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( - instance, - chatId, - filterInbox.id, - isGroup, - nameContact, - picture_url.profilePictureUrl || null, - jid, - ); - } - } else { - if (findContact) { - if (!findContact.name || findContact.name === chatId) { - contact = await this.updateContact(instance, findContact.id, { - name: nameContact, - avatar_url: picture_url.profilePictureUrl || null, - }); - } else { - contact = await this.updateContact(instance, findContact.id, { - avatar_url: picture_url.profilePictureUrl || null, - }); - } - if (!contact) { - contact = await this.findContact(instance, chatId); - } - } else { - const jid = isGroup ? null : body.key.remoteJid; - contact = await this.createContact( - instance, - chatId, - filterInbox.id, - isGroup, - nameContact, - picture_url.profilePictureUrl || null, - jid, - ); - } - } - - if (!contact) { - this.logger.warn('contact not found'); - return null; - } - - const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id; - - if (!body.key.fromMe && contact.name === chatId && nameContact !== chatId) { - this.logger.verbose('update contact name in chatwoot'); - await this.updateContact(instance, contactId, { - name: nameContact, - }); - } - - this.logger.verbose('get contact conversations in chatwoot'); - const contactConversations = (await client.contacts.listConversations({ - accountId: this.provider.account_id, - id: contactId, - })) as any; - - if (contactConversations) { - let conversation: any; - if (this.provider.reopen_conversation) { - conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == filterInbox.id); - - if (this.provider.conversation_pending) { - await client.conversations.toggleStatus({ - accountId: this.provider.account_id, - conversationId: conversation.id, - data: { - status: 'pending', - }, - }); - } - } else { - conversation = contactConversations.payload.find( - (conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id, - ); - } - this.logger.verbose('return conversation if exists'); - - if (conversation) { - this.logger.verbose('conversation found'); - return conversation.id; - } - } - - this.logger.verbose('create conversation in chatwoot'); - const data = { - contact_id: contactId.toString(), - inbox_id: filterInbox.id.toString(), - }; - - if (this.provider.conversation_pending) { - data['status'] = 'pending'; - } - - const conversation = await client.conversations.create({ - accountId: this.provider.account_id, - data, - }); - - if (!conversation) { - this.logger.warn('conversation not found'); - return null; - } - - this.logger.verbose('conversation created'); - return conversation.id; - } catch (error) { - this.logger.error(error); - } - } - - public async getInbox(instance: InstanceDto) { - this.logger.verbose('get inbox to instance: ' + instance.instanceName); - - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - this.logger.verbose('find inboxes in chatwoot'); - const inbox = (await client.inboxes.list({ - accountId: this.provider.account_id, - })) as any; - - if (!inbox) { - this.logger.warn('inbox not found'); - return null; - } - - this.logger.verbose('find inbox by name'); - const findByName = inbox.payload.find((inbox) => inbox.name === instance.instanceName.split('-cwId-')[0]); - - if (!findByName) { - this.logger.warn('inbox not found'); - return null; - } - - this.logger.verbose('return inbox'); - return findByName; - } - - public async createMessage( - instance: InstanceDto, - conversationId: number, - content: string, - messageType: 'incoming' | 'outgoing' | undefined, - privateMessage?: boolean, - attachments?: { - content: unknown; - encoding: string; - filename: string; - }[], - ) { - this.logger.verbose('create message to instance: ' + instance.instanceName); - - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - this.logger.verbose('create message in chatwoot'); - const message = await client.messages.create({ - accountId: this.provider.account_id, - conversationId: conversationId, - data: { - content: content, - message_type: messageType, - attachments: attachments, - private: privateMessage || false, - }, - }); - - if (!message) { - this.logger.warn('message not found'); - return null; - } - - this.logger.verbose('message created'); - - return message; - } - - public async createBotMessage( - instance: InstanceDto, - content: string, - messageType: 'incoming' | 'outgoing' | undefined, - attachments?: { - content: unknown; - encoding: string; - filename: string; - }[], - ) { - this.logger.verbose('create bot message to instance: ' + instance.instanceName); - - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - this.logger.verbose('find contact in chatwoot'); - const contact = await this.findContact(instance, '123456'); - - if (!contact) { - this.logger.warn('contact not found'); - return null; - } - - this.logger.verbose('get inbox to instance: ' + instance.instanceName); - const filterInbox = await this.getInbox(instance); - - if (!filterInbox) { - this.logger.warn('inbox not found'); - return null; - } - - this.logger.verbose('find conversation in chatwoot'); - const findConversation = await client.conversations.list({ - accountId: this.provider.account_id, - inboxId: filterInbox.id, - }); - - if (!findConversation) { - this.logger.warn('conversation not found'); - return null; - } - - this.logger.verbose('find conversation by contact id'); - const conversation = findConversation.data.payload.find( - (conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', - ); - - if (!conversation) { - this.logger.warn('conversation not found'); - return; - } - - this.logger.verbose('create message in chatwoot'); - const message = await client.messages.create({ - accountId: this.provider.account_id, - conversationId: conversation.id, - data: { - content: content, - message_type: messageType, - attachments: attachments, - }, - }); - - if (!message) { - this.logger.warn('message not found'); - return null; - } - - this.logger.verbose('bot message created'); - - return message; - } - - private async sendData( - conversationId: number, - file: string, - messageType: 'incoming' | 'outgoing' | undefined, - content?: string, - ) { - this.logger.verbose('send data to chatwoot'); - - const data = new FormData(); - - if (content) { - this.logger.verbose('content found'); - data.append('content', content); - } - - this.logger.verbose('message type: ' + messageType); - data.append('message_type', messageType); - - this.logger.verbose('temp file found'); - data.append('attachments[]', createReadStream(file)); - - this.logger.verbose('get client to instance: ' + this.provider.instanceName); - 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, - }; - - this.logger.verbose('send data to chatwoot'); - try { - const { data } = await axios.request(config); - - this.logger.verbose('remove temp file'); - unlinkSync(file); - - this.logger.verbose('data sent'); - return data; - } catch (error) { - this.logger.error(error); - unlinkSync(file); - } - } - - public async createBotQr( - instance: InstanceDto, - content: string, - messageType: 'incoming' | 'outgoing' | undefined, - file?: string, - ) { - this.logger.verbose('create bot qr to instance: ' + instance.instanceName); - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - this.logger.verbose('find contact in chatwoot'); - const contact = await this.findContact(instance, '123456'); - - if (!contact) { - this.logger.warn('contact not found'); - return null; - } - - this.logger.verbose('get inbox to instance: ' + instance.instanceName); - const filterInbox = await this.getInbox(instance); - - if (!filterInbox) { - this.logger.warn('inbox not found'); - return null; - } - - this.logger.verbose('find conversation in chatwoot'); - const findConversation = await client.conversations.list({ - accountId: this.provider.account_id, - inboxId: filterInbox.id, - }); - - if (!findConversation) { - this.logger.warn('conversation not found'); - return null; - } - - this.logger.verbose('find conversation by contact id'); - const conversation = findConversation.data.payload.find( - (conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', - ); - - if (!conversation) { - this.logger.warn('conversation not found'); - return; - } - - this.logger.verbose('send data to chatwoot'); - const data = new FormData(); - - if (content) { - this.logger.verbose('content found'); - data.append('content', content); - } - - this.logger.verbose('message type: ' + messageType); - data.append('message_type', messageType); - - if (file) { - this.logger.verbose('temp file found'); - data.append('attachments[]', createReadStream(file)); - } - - this.logger.verbose('get client to instance: ' + this.provider.instanceName); - 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, - }; - - this.logger.verbose('send data to chatwoot'); - try { - const { data } = await axios.request(config); - - this.logger.verbose('remove temp file'); - unlinkSync(file); - - this.logger.verbose('data sent'); - return data; - } catch (error) { - this.logger.error(error); - } - } - - public async sendAttachment(waInstance: any, number: string, media: any, caption?: string) { - this.logger.verbose('send attachment to instance: ' + waInstance.instanceName); - - try { - this.logger.verbose('get media type'); - const parts = media.split('/'); - - const fileName = decodeURIComponent(parts[parts.length - 1]); - this.logger.verbose('file name: ' + fileName); - - const mimeType = mimeTypes.lookup(fileName).toString(); - this.logger.verbose('mime type: ' + mimeType); - - 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; - } - - this.logger.verbose('type: ' + type); - - if (type === 'audio') { - this.logger.verbose('send audio to instance: ' + waInstance.instanceName); - const data: SendAudioDto = { - number: number, - audioMessage: { - audio: media, - }, - options: { - delay: 1200, - presence: 'recording', - }, - }; - - await waInstance?.audioWhatsapp(data, true); - - this.logger.verbose('audio sent'); - return; - } - - this.logger.verbose('send media to instance: ' + waInstance.instanceName); - const data: SendMediaDto = { - number: number, - mediaMessage: { - mediatype: type as any, - fileName: fileName, - media: media, - }, - options: { - delay: 1200, - presence: 'composing', - }, - }; - - if (caption) { - this.logger.verbose('caption found'); - data.mediaMessage.caption = caption; - } - - await waInstance?.mediaMessage(data, true); - - this.logger.verbose('media sent'); - return; - } catch (error) { - this.logger.error(error); - } - } - - public async receiveWebhook(instance: InstanceDto, body: any) { - try { - await new Promise((resolve) => setTimeout(resolve, 500)); - - this.logger.verbose('receive webhook to chatwoot instance: ' + instance.instanceName); - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - this.logger.verbose('check if is bot'); - if (!body?.conversation || body.private || body.event === 'message_updated') return { message: 'bot' }; - - this.logger.verbose('check if is group'); - const chatId = - body.conversation.meta.sender?.phone_number?.replace('+', '') || body.conversation.meta.sender?.identifier; - const messageReceived = body.content; - const senderName = body?.sender?.name; - const waInstance = this.waMonitor.waInstances[instance.instanceName]; - - if (chatId === '123456' && body.message_type === 'outgoing') { - this.logger.verbose('check if is command'); - - const command = messageReceived.replace('/', ''); - - if (command.includes('init') || command.includes('iniciar')) { - this.logger.verbose('command init found'); - const state = waInstance?.connectionStatus?.state; - - if (state !== 'open') { - if (state === 'close') { - this.logger.verbose('request cleaning up instance: ' + instance.instanceName); - await this.waMonitor.cleaningUp(instance.instanceName); - } - this.logger.verbose('connect to whatsapp'); - const number = command.split(':')[1]; - await waInstance.connectToWhatsapp(number); - } else { - this.logger.verbose('whatsapp already connected'); - await this.createBotMessage(instance, `🚨 ${body.inbox.name} instance is connected.`, 'incoming'); - } - } - - if (command === 'status') { - this.logger.verbose('command status found'); - - const state = waInstance?.connectionStatus?.state; - - if (!state) { - this.logger.verbose('state not found'); - await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance not found.`, 'incoming'); - } - - if (state) { - this.logger.verbose('state: ' + state + ' found'); - await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance status: *${state}*`, 'incoming'); - } - } - - if (command === 'disconnect' || command === 'desconectar') { - this.logger.verbose('command disconnect found'); - - const msgLogout = `🚨 Disconnecting Whatsapp from inbox *${body.inbox.name}*: `; - - this.logger.verbose('send message to chatwoot'); - await this.createBotMessage(instance, msgLogout, 'incoming'); - - this.logger.verbose('disconnect to whatsapp'); - 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') { - this.logger.verbose('check if is group'); - - this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); - this.logger.verbose('cache file path: ' + this.messageCacheFile); - - this.messageCache = this.loadMessageCache(); - this.logger.verbose('cache file loaded'); - this.logger.verbose(this.messageCache); - - this.logger.verbose('check if message is cached'); - if (this.messageCache.has(body.id.toString())) { - this.logger.verbose('message is cached'); - return { message: 'bot' }; - } - - this.logger.verbose('clear cache'); - this.clearMessageCache(); - - this.logger.verbose('Format message to send'); - let formatText: string; - if (senderName === null || senderName === undefined) { - formatText = messageReceived; - } else { - formatText = this.provider.sign_msg ? `*${senderName}:*\n${messageReceived}` : messageReceived; - } - - for (const message of body.conversation.messages) { - this.logger.verbose('check if message is media'); - if (message.attachments && message.attachments.length > 0) { - this.logger.verbose('message is media'); - for (const attachment of message.attachments) { - this.logger.verbose('send media to whatsapp'); - if (!messageReceived) { - this.logger.verbose('message do not have text'); - formatText = null; - } - - await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText); - } - } else { - this.logger.verbose('message is text'); - - this.logger.verbose('send text to whatsapp'); - const data: SendTextDto = { - number: chatId, - textMessage: { - text: formatText, - }, - options: { - delay: 1200, - presence: 'composing', - }, - }; - - await waInstance?.textMessage(data, true); - } - } - } - - if (body.message_type === 'template' && body.event === 'message_created') { - this.logger.verbose('check if is template'); - - const data: SendTextDto = { - number: chatId, - textMessage: { - text: body.content.replace(/\\\r\n|\\\n|\n/g, '\n'), - }, - options: { - delay: 1200, - presence: 'composing', - }, - }; - - this.logger.verbose('send text to whatsapp'); - - await waInstance?.textMessage(data); - } - - return { message: 'bot' }; - } catch (error) { - this.logger.error(error); - - return { message: 'bot' }; - } - } - - private isMediaMessage(message: any) { - this.logger.verbose('check if is media message'); - const media = [ - 'imageMessage', - 'documentMessage', - 'documentWithCaptionMessage', - 'audioMessage', - 'videoMessage', - 'stickerMessage', - ]; - - const messageKeys = Object.keys(message); - - const result = messageKeys.some((key) => media.includes(key)); - - this.logger.verbose('is media message: ' + result); - return result; - } - - private getAdsMessage(msg: any) { - interface AdsMessage { - title: string; - body: string; - thumbnailUrl: string; - sourceUrl: string; - } - const adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo?.externalAdReply; - - this.logger.verbose('Get ads message if it exist'); - adsMessage && this.logger.verbose('Ads message: ' + adsMessage); - return adsMessage; - } - - private getTypeMessage(msg: any) { - this.logger.verbose('get type message'); - - const types = { - conversation: msg.conversation, - imageMessage: msg.imageMessage?.caption, - videoMessage: msg.videoMessage?.caption, - extendedTextMessage: msg.extendedTextMessage?.text, - messageContextInfo: msg.messageContextInfo?.stanzaId, - stickerMessage: undefined, - documentMessage: msg.documentMessage?.caption, - documentWithCaptionMessage: msg.documentWithCaptionMessage?.message?.documentMessage?.caption, - audioMessage: msg.audioMessage?.caption, - contactMessage: msg.contactMessage?.vcard, - contactsArrayMessage: msg.contactsArrayMessage, - locationMessage: msg.locationMessage, - liveLocationMessage: msg.liveLocationMessage, - }; - - 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; - - if (typeKey === 'locationMessage' || typeKey === 'liveLocationMessage') { - const latitude = result.degreesLatitude; - const longitude = result.degreesLongitude; - - const formattedLocation = `**Location:** - **latitude:** ${latitude} - **longitude:** ${longitude} - https://www.google.com/maps/search/?api=1&query=${latitude},${longitude} - `; - - this.logger.verbose('message content: ' + formattedLocation); - - return formattedLocation; - } - - if (typeKey === 'contactMessage') { - const vCardData = result.split('\n'); - const contactInfo = {}; - - vCardData.forEach((line) => { - const [key, value] = line.split(':'); - if (key && value) { - contactInfo[key] = value; - } - }); - - let formattedContact = `**Contact:** - **name:** ${contactInfo['FN']}`; - - let numberCount = 1; - Object.keys(contactInfo).forEach((key) => { - if (key.startsWith('item') && key.includes('TEL')) { - const phoneNumber = contactInfo[key]; - formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`; - numberCount++; - } - }); - - this.logger.verbose('message content: ' + formattedContact); - return formattedContact; - } - - if (typeKey === 'contactsArrayMessage') { - const formattedContacts = result.contacts.map((contact) => { - const vCardData = contact.vcard.split('\n'); - const contactInfo = {}; - - vCardData.forEach((line) => { - const [key, value] = line.split(':'); - if (key && value) { - contactInfo[key] = value; - } - }); - - let formattedContact = `**Contact:** - **name:** ${contact.displayName}`; - - let numberCount = 1; - Object.keys(contactInfo).forEach((key) => { - if (key.startsWith('item') && key.includes('TEL')) { - const phoneNumber = contactInfo[key]; - formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`; - numberCount++; - } - }); - - return formattedContact; - }); - - const formattedContactsArray = formattedContacts.join('\n\n'); - - this.logger.verbose('formatted contacts: ' + formattedContactsArray); - - return formattedContactsArray; - } - - 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; - } - - public async eventWhatsapp(event: string, instance: InstanceDto, body: any) { - this.logger.verbose('event whatsapp to instance: ' + instance.instanceName); - try { - const waInstance = this.waMonitor.waInstances[instance.instanceName]; - - if (!waInstance) { - this.logger.warn('wa instance not found'); - return null; - } - - const client = await this.clientCw(instance); - - if (!client) { - this.logger.warn('client not found'); - return null; - } - - if (event === 'messages.upsert' || event === 'send.message') { - this.logger.verbose('event messages.upsert'); - - if (body.key.remoteJid === 'status@broadcast') { - this.logger.verbose('status broadcast found'); - return; - } - - this.logger.verbose('get conversation message'); - const bodyMessage = await this.getConversationMessage(body.message); - - const isMedia = this.isMediaMessage(body.message); - - const adsMessage = this.getAdsMessage(body.message); - - if (!bodyMessage && !isMedia) { - this.logger.warn('no body message found'); - return; - } - - this.logger.verbose('get conversation in chatwoot'); - const getConversation = await this.createConversation(instance, body); - - if (!getConversation) { - this.logger.warn('conversation not found'); - return; - } - - const messageType = body.key.fromMe ? 'outgoing' : 'incoming'; - - this.logger.verbose('message type: ' + messageType); - - this.logger.verbose('is media: ' + isMedia); - - this.logger.verbose('check if is media'); - if (isMedia) { - this.logger.verbose('message is media'); - - this.logger.verbose('get base64 from media message'); - 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}`)}`; - - this.logger.verbose('temp file name: ' + nameFile); - - this.logger.verbose('create temp file'); - writeFileSync(fileName, fileData, 'utf8'); - - this.logger.verbose('check if is group'); - if (body.key.remoteJid.includes('@g.us')) { - this.logger.verbose('message is group'); - - const participantName = body.pushName; - - let content: string; - - if (!body.key.fromMe) { - this.logger.verbose('message is not from me'); - content = `**${participantName}:**\n\n${bodyMessage}`; - } else { - this.logger.verbose('message is from me'); - content = `${bodyMessage}`; - } - - this.logger.verbose('send data to chatwoot'); - const send = await this.sendData(getConversation, fileName, messageType, content); - - if (!send) { - this.logger.warn('message not sent'); - return; - } - - this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); - - this.messageCache = this.loadMessageCache(); - - this.messageCache.add(send.id.toString()); - - this.logger.verbose('save message cache'); - this.saveMessageCache(); - - return send; - } else { - this.logger.verbose('message is not group'); - - this.logger.verbose('send data to chatwoot'); - const send = await this.sendData(getConversation, fileName, messageType, bodyMessage); - - if (!send) { - this.logger.warn('message not sent'); - return; - } - - this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); - - this.messageCache = this.loadMessageCache(); - - this.messageCache.add(send.id.toString()); - - this.logger.verbose('save message cache'); - this.saveMessageCache(); - - return send; - } - } - - this.logger.verbose('check if has Ads Message'); - if (adsMessage) { - this.logger.verbose('message is from Ads'); - - this.logger.verbose('get base64 from media ads message'); - const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' }); - - const extension = mimeTypes.extension(imgBuffer.headers['content-type']); - const mimeType = extension && mimeTypes.lookup(extension); - - if (!mimeType) { - this.logger.warn('mimetype of Ads message not found'); - return; - } - - const random = Math.random().toString(36).substring(7); - const nameFile = `${random}.${mimeTypes.extension(mimeType)}`; - const fileData = Buffer.from(imgBuffer.data, 'binary'); - const fileName = `${path.join(waInstance?.storePath, 'temp', `${nameFile}`)}`; - - this.logger.verbose('temp file name: ' + nameFile); - this.logger.verbose('create temp file'); - await Jimp.read(fileData) - .then(async (img) => { - await img.cover(320, 180).writeAsync(fileName); - }) - .catch((err) => { - this.logger.error(`image is not write: ${err}`); - }); - const truncStr = (str: string, len: number) => { - return str.length > len ? str.substring(0, len) + '...' : str; - }; - - const title = truncStr(adsMessage.title, 40); - const description = truncStr(adsMessage.body, 75); - - this.logger.verbose('send data to chatwoot'); - const send = await this.sendData( - getConversation, - fileName, - messageType, - `${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`, - ); - - if (!send) { - this.logger.warn('message not sent'); - return; - } - - this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); - - this.messageCache = this.loadMessageCache(); - - this.messageCache.add(send.id.toString()); - - this.logger.verbose('save message cache'); - this.saveMessageCache(); - - return send; - } - - this.logger.verbose('check if is group'); - if (body.key.remoteJid.includes('@g.us')) { - this.logger.verbose('message is group'); - const participantName = body.pushName; - - let content: string; - - if (!body.key.fromMe) { - this.logger.verbose('message is not from me'); - content = `**${participantName}**\n\n${bodyMessage}`; - } else { - this.logger.verbose('message is from me'); - content = `${bodyMessage}`; - } - - this.logger.verbose('send data to chatwoot'); - const send = await this.createMessage(instance, getConversation, content, messageType); - - if (!send) { - this.logger.warn('message not sent'); - return; - } - - this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); - - this.messageCache = this.loadMessageCache(); - - this.messageCache.add(send.id.toString()); - - this.logger.verbose('save message cache'); - this.saveMessageCache(); - - return send; - } else { - this.logger.verbose('message is not group'); - - this.logger.verbose('send data to chatwoot'); - const send = await this.createMessage(instance, getConversation, bodyMessage, messageType); - - if (!send) { - this.logger.warn('message not sent'); - return; - } - - this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); - - this.messageCache = this.loadMessageCache(); - - this.messageCache.add(send.id.toString()); - - this.logger.verbose('save message cache'); - this.saveMessageCache(); - - return send; - } - } - - if (event === 'status.instance') { - this.logger.verbose('event status.instance'); - const data = body; - const inbox = await this.getInbox(instance); - - if (!inbox) { - this.logger.warn('inbox not found'); - return; - } - - const msgStatus = `⚡️ Instance status ${inbox.name}: ${data.status}`; - - this.logger.verbose('send message to chatwoot'); - await this.createBotMessage(instance, msgStatus, 'incoming'); - } - - if (event === 'connection.update') { - this.logger.verbose('event connection.update'); - - if (body.status === 'open') { - // if we have qrcode count then we understand that a new connection was established - if (this.waMonitor.waInstances[instance.instanceName].qrCode.count > 0) { - const msgConnection = `🚀 Connection successfully established!`; - this.logger.verbose('send message to chatwoot'); - await this.createBotMessage(instance, msgConnection, 'incoming'); - } - } - } - - if (event === 'qrcode.updated') { - this.logger.verbose('event qrcode.updated'); - if (body.statusCode === 500) { - this.logger.verbose('qrcode error'); - const erroQRcode = `🚨 QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.`; - - this.logger.verbose('send message to chatwoot'); - return await this.createBotMessage(instance, erroQRcode, 'incoming'); - } else { - this.logger.verbose('qrcode success'); - const fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64'); - - const fileName = `${path.join(waInstance?.storePath, 'temp', `${`${instance}.png`}`)}`; - - this.logger.verbose('temp file name: ' + fileName); - - this.logger.verbose('create temp file'); - writeFileSync(fileName, fileData, 'utf8'); - - this.logger.verbose('send qrcode to chatwoot'); - await this.createBotQr(instance, 'QRCode successfully generated!', 'incoming', fileName); - - let msgQrCode = `⚡️ QRCode successfully generated!\n\nScan this QR code within the next 40 seconds.`; - - if (body?.qrcode?.pairingCode) { - msgQrCode = - msgQrCode + - `\n\n*Pairing Code:* ${body.qrcode.pairingCode.substring(0, 4)}-${body.qrcode.pairingCode.substring( - 4, - 8, - )}`; - } - - this.logger.verbose('send message to chatwoot'); - await this.createBotMessage(instance, msgQrCode, 'incoming'); - } - } - } catch (error) { - this.logger.error(error); - } - } -} +import ChatwootClient from '@figuro/chatwoot-sdk'; +import axios from 'axios'; +import FormData from 'form-data'; +import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs'; +import Jimp from 'jimp'; +import mimeTypes from 'mime-types'; +import path from 'path'; + +import { ConfigService } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { ROOT_DIR } from '../../config/path.config'; +import { ChatwootDto } from '../dto/chatwoot.dto'; +import { InstanceDto } from '../dto/instance.dto'; +import { SendAudioDto, SendMediaDto, SendTextDto } from '../dto/sendMessage.dto'; +import { WAMonitoringService } from './monitor.service'; + +export class ChatwootService { + private messageCacheFile: string; + private messageCache: Set; + + private readonly logger = new Logger(ChatwootService.name); + + private provider: any; + + constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) { + this.messageCache = new Set(); + } + + private loadMessageCache(): Set { + this.logger.verbose('load message cache'); + try { + const cacheData = readFileSync(this.messageCacheFile, 'utf-8'); + const cacheArray = cacheData.split('\n'); + return new Set(cacheArray); + } catch (error) { + return new Set(); + } + } + + private saveMessageCache() { + this.logger.verbose('save message cache'); + const cacheData = Array.from(this.messageCache).join('\n'); + writeFileSync(this.messageCacheFile, cacheData, 'utf-8'); + this.logger.verbose('message cache saved'); + } + + private clearMessageCache() { + this.logger.verbose('clear message cache'); + this.messageCache.clear(); + this.saveMessageCache(); + } + + private async getProvider(instance: InstanceDto) { + this.logger.verbose('get provider to instance: ' + instance.instanceName); + try { + const provider = await this.waMonitor.waInstances[instance.instanceName].findChatwoot(); + + if (!provider) { + this.logger.warn('provider not found'); + return null; + } + + this.logger.verbose('provider found'); + + return provider; + } catch (error) { + this.logger.error('provider not found'); + return null; + } + } + + private async clientCw(instance: InstanceDto) { + this.logger.verbose('get client to instance: ' + instance.instanceName); + const provider = await this.getProvider(instance); + + if (!provider) { + this.logger.error('provider not found'); + return null; + } + + this.logger.verbose('provider found'); + + this.provider = provider; + + this.logger.verbose('create client to instance: ' + instance.instanceName); + const client = new ChatwootClient({ + config: { + basePath: provider.url, + with_credentials: true, + credentials: 'include', + token: provider.token, + }, + }); + + this.logger.verbose('client created'); + + return client; + } + + public create(instance: InstanceDto, data: ChatwootDto) { + this.logger.verbose('create chatwoot: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setChatwoot(data); + + this.logger.verbose('chatwoot created'); + return data; + } + + public async find(instance: InstanceDto): Promise { + this.logger.verbose('find chatwoot: ' + instance.instanceName); + try { + return await this.waMonitor.waInstances[instance.instanceName].findChatwoot(); + } catch (error) { + this.logger.error('chatwoot not found'); + return { enabled: null, url: '' }; + } + } + + public async getContact(instance: InstanceDto, id: number) { + this.logger.verbose('get contact to instance: ' + instance.instanceName); + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + if (!id) { + this.logger.warn('id is required'); + return null; + } + + this.logger.verbose('find contact in chatwoot'); + const contact = await client.contact.getContactable({ + accountId: this.provider.account_id, + id, + }); + + if (!contact) { + this.logger.warn('contact not found'); + return null; + } + + this.logger.verbose('contact found'); + return contact; + } + + public async initInstanceChatwoot( + instance: InstanceDto, + inboxName: string, + webhookUrl: string, + qrcode: boolean, + number: string, + ) { + this.logger.verbose('init instance chatwoot: ' + instance.instanceName); + + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + this.logger.verbose('find inbox in chatwoot'); + const findInbox: any = await client.inboxes.list({ + accountId: this.provider.account_id, + }); + + this.logger.verbose('check duplicate inbox'); + const checkDuplicate = findInbox.payload.map((inbox) => inbox.name).includes(inboxName); + + let inboxId: number; + + if (!checkDuplicate) { + this.logger.verbose('create inbox in chatwoot'); + const data = { + type: 'api', + webhook_url: webhookUrl, + }; + + const inbox = await client.inboxes.create({ + accountId: this.provider.account_id, + data: { + name: inboxName, + channel: data as any, + }, + }); + + if (!inbox) { + this.logger.warn('inbox not found'); + return null; + } + + inboxId = inbox.id; + } else { + this.logger.verbose('find inbox in chatwoot'); + const inbox = findInbox.payload.find((inbox) => inbox.name === inboxName); + + if (!inbox) { + this.logger.warn('inbox not found'); + return null; + } + + inboxId = inbox.id; + } + + this.logger.verbose('find contact in chatwoot and create if not exists'); + const contact = + (await this.findContact(instance, '123456')) || + ((await this.createContact( + instance, + '123456', + inboxId, + false, + 'Nex API', + 'https://nex-api.com.br/shared/themes/site/files/images/logo-purple.pnghttps://evolution-api.com/files/evolution-api-favicon.png', + )) as any); + + if (!contact) { + this.logger.warn('contact not found'); + return null; + } + + const contactId = contact.id || contact.payload.contact.id; + + if (qrcode) { + this.logger.verbose('create conversation in chatwoot'); + const data = { + contact_id: contactId.toString(), + inbox_id: inboxId.toString(), + }; + + if (this.provider.conversation_pending) { + data['status'] = 'pending'; + } + + const conversation = await client.conversations.create({ + accountId: this.provider.account_id, + data, + }); + + if (!conversation) { + this.logger.warn('conversation not found'); + return null; + } + + this.logger.verbose('create message for init instance in chatwoot'); + + let contentMsg = 'init'; + + if (number) { + contentMsg = `init:${number}`; + } + + const message = await client.messages.create({ + accountId: this.provider.account_id, + conversationId: conversation.id, + data: { + content: contentMsg, + message_type: 'outgoing', + }, + }); + + if (!message) { + this.logger.warn('conversation not found'); + return null; + } + } + + this.logger.verbose('instance chatwoot initialized'); + return true; + } + + public async createContact( + instance: InstanceDto, + phoneNumber: string, + inboxId: number, + isGroup: boolean, + name?: string, + avatar_url?: string, + jid?: string, + ) { + this.logger.verbose('create contact to instance: ' + instance.instanceName); + + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + let data: any = {}; + if (!isGroup) { + this.logger.verbose('create contact in chatwoot'); + data = { + inbox_id: inboxId, + name: name || phoneNumber, + phone_number: `+${phoneNumber}`, + identifier: jid, + avatar_url: avatar_url, + }; + } else { + this.logger.verbose('create contact group in chatwoot'); + data = { + inbox_id: inboxId, + name: name || phoneNumber, + identifier: phoneNumber, + avatar_url: avatar_url, + }; + } + + this.logger.verbose('create contact in chatwoot'); + const contact = await client.contacts.create({ + accountId: this.provider.account_id, + data, + }); + + if (!contact) { + this.logger.warn('contact not found'); + return null; + } + + this.logger.verbose('contact created'); + return contact; + } + + public async updateContact(instance: InstanceDto, id: number, data: any) { + this.logger.verbose('update contact to instance: ' + instance.instanceName); + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + if (!id) { + this.logger.warn('id is required'); + return null; + } + + this.logger.verbose('update contact in chatwoot'); + try { + const contact = await client.contacts.update({ + accountId: this.provider.account_id, + id, + data, + }); + + // const contact = await client.contacts.update({ + // accountId: this.provider.account_id, + // id, + // data, + // }); + this.logger.verbose('contact updated'); + return contact; + } catch (error) { + this.logger.error(error); + } + + } + + public async findContact(instance: InstanceDto, phoneNumber: string) { + this.logger.verbose('find contact to instance: ' + instance.instanceName); + + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + let query: any; + + if (!phoneNumber.includes('@g.us')) { + this.logger.verbose('format phone number'); + query = `+${phoneNumber}`; + } else { + this.logger.verbose('format group id'); + query = phoneNumber; + } + + this.logger.verbose('find contact in chatwoot'); + const contact: any = await client.contacts.search({ + accountId: this.provider.account_id, + q: query, + }); + + if (!contact) { + this.logger.warn('contact not found'); + return null; + } + + if (!phoneNumber.includes('@g.us')) { + this.logger.verbose('return contact'); + return contact.payload.find((contact) => contact.phone_number === query); + } else { + this.logger.verbose('return group'); + return contact.payload.find((contact) => contact.identifier === query); + } + } + + public async createConversation(instance: InstanceDto, body: any) { + this.logger.verbose('create conversation to instance: ' + instance.instanceName); + try { + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + const isGroup = body.key.remoteJid.includes('@g.us'); + + this.logger.verbose('is group: ' + isGroup); + + const chatId = isGroup ? body.key.remoteJid : body.key.remoteJid.split('@')[0]; + + this.logger.verbose('chat id: ' + chatId); + + let nameContact: string; + + nameContact = !body.key.fromMe ? body.pushName : chatId; + + this.logger.verbose('get inbox to instance: ' + instance.instanceName); + + var idInboxChat = 0; + + if(this.provider?.id_inbox){ + idInboxChat = this.provider?.id_inbox; + }else{ + const filterInbox = await this.getInbox(instance); + if (!filterInbox) { + this.logger.warn('inbox not found'); + return null; + } + idInboxChat = filterInbox.id; + } + + if (isGroup) { + this.logger.verbose('get group name'); + const group = await this.waMonitor.waInstances[instance.instanceName].client.groupMetadata(chatId); + + nameContact = `${group.subject} (GROUP)`; + + this.logger.verbose('find or create participant in chatwoot'); + + const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture( + body.key.participant.split('@')[0], + ); + + const findParticipant = await this.findContact(instance, body.key.participant.split('@')[0]); + + if (findParticipant) { + if (!findParticipant.name || findParticipant.name === chatId) { + await this.updateContact(instance, findParticipant.id, { + name: body.pushName, + avatar_url: picture_url.profilePictureUrl || null, + }); + } + // if (!contact) { + // contact = await this.findContact(instance, chatId); + // } + } else { + await this.createContact( + instance, + body.key.participant.split('@')[0], + idInboxChat, + false, + body.pushName, + picture_url.profilePictureUrl || null, + body.key.participant, + ); + } + } + + this.logger.verbose('find or create contact in chatwoot'); + + const picture_url = await this.waMonitor.waInstances[instance.instanceName].profilePicture(chatId); + + const findContact = await this.findContact(instance, chatId); + + let contact: any; + if (body.key.fromMe) { + if (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( + instance, + chatId, + idInboxChat, + isGroup, + nameContact, + picture_url.profilePictureUrl || null, + jid, + ); + } + } else { + if (findContact) { + if (!findContact.name || findContact.name === chatId) { + contact = await this.updateContact(instance, findContact.id, { + name: nameContact, + avatar_url: picture_url.profilePictureUrl || null, + }); + } else { + 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( + instance, + chatId, + idInboxChat, + isGroup, + nameContact, + picture_url.profilePictureUrl || null, + jid, + ); + } + } + + if (!contact) { + this.logger.warn('contact not found'); + return null; + } + + const contactId = contact?.payload?.id || contact?.payload?.contact?.id || contact?.id; + + if (!body.key.fromMe && contact.name === chatId && nameContact !== chatId) { + this.logger.verbose('update contact name in chatwoot'); + await this.updateContact(instance, contactId, { + name: nameContact, + }); + } + + this.logger.verbose('get contact conversations in chatwoot'); + const contactConversations = (await client.contacts.listConversations({ + accountId: this.provider.account_id, + id: contactId, + })) as any; + + if (contactConversations) { + let conversation: any; + if (this.provider.reopen_conversation) { + conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == idInboxChat); + + if (this.provider.conversation_pending) { + await client.conversations.toggleStatus({ + accountId: this.provider.account_id, + conversationId: conversation.id, + data: { + status: 'pending', + }, + }); + } + } else { + conversation = contactConversations.payload.find( + (conversation) => conversation.status !== 'resolved' && conversation.inbox_id == idInboxChat, + ); + } + this.logger.verbose('return conversation if exists'); + + if (conversation) { + this.logger.verbose('conversation found'); + return conversation.id; + } + } + + this.logger.verbose('create conversation in chatwoot'); + const data = { + contact_id: contactId.toString(), + inbox_id: idInboxChat.toString(), + }; + + if (this.provider.conversation_pending) { + data['status'] = 'pending'; + } + + const conversation = await client.conversations.create({ + accountId: this.provider.account_id, + data, + }); + + if (!conversation) { + this.logger.warn('conversation not found'); + return null; + } + + this.logger.verbose('conversation created'); + return conversation.id; + } catch (error) { + this.logger.error(error); + } + } + + public async getInbox(instance: InstanceDto) { + this.logger.verbose('get inbox to instance: ' + instance.instanceName); + + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + this.logger.verbose('find inboxes in chatwoot'); + const inbox = (await client.inboxes.list({ + accountId: this.provider.account_id, + })) as any; + + if (!inbox) { + this.logger.warn('inbox not found'); + return null; + } + + this.logger.verbose('find inbox by name'); + const findByName = inbox.payload.find((inbox) => inbox.name === instance.instanceName.split('-cwId-')[0]); + + if (!findByName) { + this.logger.warn('inbox not found'); + return null; + } + + this.logger.verbose('return inbox'); + return findByName; + } + + public async createMessage( + instance: InstanceDto, + conversationId: number, + content: string, + messageType: 'incoming' | 'outgoing' | undefined, + privateMessage?: boolean, + attachments?: { + content: unknown; + encoding: string; + filename: string; + }[], + ) { + this.logger.verbose('create message to instance: ' + instance.instanceName); + + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + this.logger.verbose('create message in chatwoot'); + const message = await client.messages.create({ + accountId: this.provider.account_id, + conversationId: conversationId, + data: { + content: content, + message_type: messageType, + attachments: attachments, + private: privateMessage || false, + }, + }); + + if (!message) { + this.logger.warn('message not found'); + return null; + } + + this.logger.verbose('message created'); + + return message; + } + + public async createBotMessage( + instance: InstanceDto, + content: string, + messageType: 'incoming' | 'outgoing' | undefined, + attachments?: { + content: unknown; + encoding: string; + filename: string; + }[], + ) { + this.logger.verbose('create bot message to instance: ' + instance.instanceName); + + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + this.logger.verbose('find contact in chatwoot'); + const contact = await this.findContact(instance, '123456'); + + if (!contact) { + this.logger.warn('contact not found'); + return null; + } + + if(this.provider?.id_inbox){ + + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: this.provider?.id_inbox, + }); + + }else{ + + this.logger.verbose('get inbox to instance: ' + instance.instanceName); + const filterInbox = await this.getInbox(instance); + + if (!filterInbox) { + this.logger.warn('inbox not found'); + return null; + } + + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: filterInbox.id, + }); + } + + if (!findConversation) { + this.logger.warn('conversation not found'); + return null; + } + + this.logger.verbose('find conversation by contact id'); + const conversation = findConversation.data.payload.find( + (conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', + ); + + if (!conversation) { + this.logger.warn('conversation not found'); + return; + } + + this.logger.verbose('create message in chatwoot'); + const message = await client.messages.create({ + accountId: this.provider.account_id, + conversationId: conversation.id, + data: { + content: content, + message_type: messageType, + attachments: attachments, + }, + }); + + if (!message) { + this.logger.warn('message not found'); + return null; + } + + this.logger.verbose('bot message created'); + + return message; + } + + private async sendData( + conversationId: number, + file: string, + messageType: 'incoming' | 'outgoing' | undefined, + content?: string, + ) { + this.logger.verbose('send data to chatwoot'); + + const data = new FormData(); + + if (content) { + this.logger.verbose('content found'); + data.append('content', content); + } + + this.logger.verbose('message type: ' + messageType); + data.append('message_type', messageType); + + this.logger.verbose('temp file found'); + data.append('attachments[]', createReadStream(file)); + + this.logger.verbose('get client to instance: ' + this.provider.instanceName); + 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, + }; + + this.logger.verbose('send data to chatwoot'); + try { + const { data } = await axios.request(config); + + this.logger.verbose('remove temp file'); + unlinkSync(file); + + this.logger.verbose('data sent'); + return data; + } catch (error) { + this.logger.error(error); + unlinkSync(file); + } + } + + public async createBotQr( + instance: InstanceDto, + content: string, + messageType: 'incoming' | 'outgoing' | undefined, + file?: string, + ) { + this.logger.verbose('create bot qr to instance: ' + instance.instanceName); + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + this.logger.verbose('find contact in chatwoot'); + const contact = await this.findContact(instance, '123456'); + + if (!contact) { + this.logger.warn('contact not found'); + return null; + } + + + if (this.provider?.id_inbox) { + + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: this.provider?.id_inbox, + }); + + } else { + + this.logger.verbose('get inbox to instance: ' + instance.instanceName); + const filterInbox = await this.getInbox(instance); + + if (!filterInbox) { + this.logger.warn('inbox not found'); + return null; + } + + this.logger.verbose('find conversation in chatwoot'); + var findConversation = await client.conversations.list({ + accountId: this.provider.account_id, + inboxId: filterInbox.id, + }); + } + + if (!findConversation) { + this.logger.warn('conversation not found'); + return null; + } + + this.logger.verbose('find conversation by contact id'); + const conversation = findConversation.data.payload.find( + (conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', + ); + + if (!conversation) { + this.logger.warn('conversation not found'); + return; + } + + this.logger.verbose('send data to chatwoot'); + const data = new FormData(); + + if (content) { + this.logger.verbose('content found'); + data.append('content', content); + } + + this.logger.verbose('message type: ' + messageType); + data.append('message_type', messageType); + + if (file) { + this.logger.verbose('temp file found'); + data.append('attachments[]', createReadStream(file)); + } + + this.logger.verbose('get client to instance: ' + this.provider.instanceName); + 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, + }; + + this.logger.verbose('send data to chatwoot'); + try { + const { data } = await axios.request(config); + + this.logger.verbose('remove temp file'); + unlinkSync(file); + + this.logger.verbose('data sent'); + return data; + } catch (error) { + this.logger.error(error); + } + } + + public async sendAttachment(waInstance: any, number: string, media: any, caption?: string) { + this.logger.verbose('send attachment to instance: ' + waInstance.instanceName); + + try { + this.logger.verbose('get media type'); + const parts = media.split('/'); + + const fileName = decodeURIComponent(parts[parts.length - 1]); + this.logger.verbose('file name: ' + fileName); + + const mimeType = mimeTypes.lookup(fileName).toString(); + this.logger.verbose('mime type: ' + mimeType); + + 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; + } + + this.logger.verbose('type: ' + type); + + if (type === 'audio') { + this.logger.verbose('send audio to instance: ' + waInstance.instanceName); + const data: SendAudioDto = { + number: number, + audioMessage: { + audio: media, + }, + options: { + delay: 1200, + presence: 'recording', + }, + }; + + await waInstance?.audioWhatsapp(data, true); + + this.logger.verbose('audio sent'); + return; + } + + this.logger.verbose('send media to instance: ' + waInstance.instanceName); + const data: SendMediaDto = { + number: number, + mediaMessage: { + mediatype: type as any, + fileName: fileName, + media: media, + }, + options: { + delay: 1200, + presence: 'composing', + }, + }; + + if (caption) { + this.logger.verbose('caption found'); + data.mediaMessage.caption = caption; + } + + await waInstance?.mediaMessage(data, true); + + this.logger.verbose('media sent'); + return; + } catch (error) { + this.logger.error(error); + } + } + + public async receiveWebhook(instance: InstanceDto, body: any) { + try { + await new Promise((resolve) => setTimeout(resolve, 500)); + + this.logger.verbose('receive webhook to chatwoot instance: ' + instance.instanceName); + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + this.logger.verbose('check if is bot'); + if (!body?.conversation || body.private || body.event === 'message_updated') return { message: 'bot' }; + + this.logger.verbose('check if is group'); + const chatId = + body.conversation.meta.sender?.phone_number?.replace('+', '') || body.conversation.meta.sender?.identifier; + const messageReceived = body.content; + const senderName = body?.sender?.name; + const waInstance = this.waMonitor.waInstances[instance.instanceName]; + + if (chatId === '123456' && body.message_type === 'outgoing') { + this.logger.verbose('check if is command'); + + const command = messageReceived.replace('/', ''); + + if (command.includes('init') || command.includes('iniciar')) { + this.logger.verbose('command init found'); + const state = waInstance?.connectionStatus?.state; + + if (state !== 'open') { + this.logger.verbose('connect to whatsapp'); + const number = command.split(':')[1]; + await waInstance.connectToWhatsapp(number); + } else { + this.logger.verbose('whatsapp already connected'); + await this.createBotMessage(instance, `🚨 ${body.inbox.name} instance is connected.`, 'incoming'); + } + } + + if (command === 'status') { + this.logger.verbose('command status found'); + + const state = waInstance?.connectionStatus?.state; + + if (!state) { + this.logger.verbose('state not found'); + await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance not found.`, 'incoming'); + } + + if (state) { + this.logger.verbose('state: ' + state + ' found'); + await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance status: *${state}*`, 'incoming'); + } + } + + if (command === 'disconnect' || command === 'desconectar') { + this.logger.verbose('command disconnect found'); + + const msgLogout = `🚨 Disconnecting Whatsapp from inbox *${body.inbox.name}*: `; + + this.logger.verbose('send message to chatwoot'); + await this.createBotMessage(instance, msgLogout, 'incoming'); + + this.logger.verbose('disconnect to whatsapp'); + 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') { + this.logger.verbose('check if is group'); + + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); + this.logger.verbose('cache file path: ' + this.messageCacheFile); + + this.messageCache = this.loadMessageCache(); + this.logger.verbose('cache file loaded'); + this.logger.verbose(this.messageCache); + + this.logger.verbose('check if message is cached'); + if (this.messageCache.has(body.id.toString())) { + this.logger.verbose('message is cached'); + return { message: 'bot' }; + } + + this.logger.verbose('clear cache'); + this.clearMessageCache(); + + this.logger.verbose('Format message to send'); + let formatText: string; + if (senderName === null || senderName === undefined) { + formatText = messageReceived; + } else { + formatText = this.provider.sign_msg ? `*${senderName}:*\n${messageReceived}` : messageReceived; + } + + if (body.content_attributes.in_reply_to){ + var idResposta = (body.content_attributes.in_reply_to-1); + var conversationId = body.conversation.contact_inbox.inbox_id; + let config = { + method: 'get', + maxBodyLength: Infinity, + url: `${this.provider.url}/api/v1/accounts/${this.provider.account_id}/conversations/${conversationId}/messages?after=` + idResposta, + headers: { + 'api_access_token': this.provider.token + } + }; + try { + const { data } = await axios.request(config); + if (data.payload[0] && data.payload[0].id == body.content_attributes.in_reply_to) { + formatText = formatText +`\n\nResposta à: \n` + data.payload[0].content + ``; + } + this.logger.verbose('data sent'); + } catch (error) { + this.logger.error(error); + } + } + + + for (const message of body.conversation.messages) { + this.logger.verbose('check if message is media'); + if (message.attachments && message.attachments.length > 0) { + this.logger.verbose('message is media'); + for (const attachment of message.attachments) { + this.logger.verbose('send media to whatsapp'); + if (!messageReceived) { + this.logger.verbose('message do not have text'); + formatText = null; + } + + await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText); + } + } else { + this.logger.verbose('message is text'); + + this.logger.verbose('send text to whatsapp'); + const data: SendTextDto = { + number: chatId, + textMessage: { + text: formatText, + }, + options: { + delay: 1200, + presence: 'composing', + }, + }; + + await waInstance?.textMessage(data, true); + } + } + } + + if (body.message_type === 'template' && body.event === 'message_created') { + this.logger.verbose('check if is template'); + + const data: SendTextDto = { + number: chatId, + textMessage: { + text: body.content.replace(/\\\r\n|\\\n|\n/g, '\n'), + }, + options: { + delay: 1200, + presence: 'composing', + }, + }; + + this.logger.verbose('send text to whatsapp'); + + await waInstance?.textMessage(data); + } + + return { message: 'bot' }; + } catch (error) { + this.logger.error(error); + + return { message: 'bot' }; + } + } + + private isMediaMessage(message: any) { + this.logger.verbose('check if is media message'); + const media = [ + 'imageMessage', + 'documentMessage', + 'documentWithCaptionMessage', + 'audioMessage', + 'videoMessage', + 'stickerMessage', + ]; + + const messageKeys = Object.keys(message); + + const result = messageKeys.some((key) => media.includes(key)); + + this.logger.verbose('is media message: ' + result); + return result; + } + + private getAdsMessage(msg: any) { + interface AdsMessage { + title: string; + body: string; + thumbnailUrl: string; + sourceUrl: string; + } + const adsMessage: AdsMessage | undefined = msg.extendedTextMessage?.contextInfo?.externalAdReply; + + this.logger.verbose('Get ads message if it exist'); + adsMessage && this.logger.verbose('Ads message: ' + adsMessage); + return adsMessage; + } + + private getTypeMessage(msg: any) { + this.logger.verbose('get type message'); + + const types = { + conversation: msg.conversation, + imageMessage: msg.imageMessage?.caption, + videoMessage: msg.videoMessage?.caption, + extendedTextMessage: msg.extendedTextMessage?.text, + messageContextInfo: msg.messageContextInfo?.stanzaId, + stickerMessage: undefined, + documentMessage: msg.documentMessage?.caption, + documentWithCaptionMessage: msg.documentWithCaptionMessage?.message?.documentMessage?.caption, + audioMessage: msg.audioMessage?.caption, + contactMessage: msg.contactMessage?.vcard, + contactsArrayMessage: msg.contactsArrayMessage, + locationMessage: msg.locationMessage, + liveLocationMessage: msg.liveLocationMessage, + }; + + 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; + + if (typeKey === 'locationMessage' || typeKey === 'liveLocationMessage') { + const latitude = result.degreesLatitude; + const longitude = result.degreesLongitude; + + const formattedLocation = `**Location:** + **latitude:** ${latitude} + **longitude:** ${longitude} + https://www.google.com/maps/search/?api=1&query=${latitude},${longitude} + `; + + this.logger.verbose('message content: ' + formattedLocation); + + return formattedLocation; + } + + if (typeKey === 'contactMessage') { + const vCardData = result.split('\n'); + const contactInfo = {}; + + vCardData.forEach((line) => { + const [key, value] = line.split(':'); + if (key && value) { + contactInfo[key] = value; + } + }); + + let formattedContact = `**Contact:** + **name:** ${contactInfo['FN']}`; + + let numberCount = 1; + Object.keys(contactInfo).forEach((key) => { + if (key.startsWith('item') && key.includes('TEL')) { + const phoneNumber = contactInfo[key]; + formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`; + numberCount++; + } + }); + + this.logger.verbose('message content: ' + formattedContact); + return formattedContact; + } + + if (typeKey === 'contactsArrayMessage') { + const formattedContacts = result.contacts.map((contact) => { + const vCardData = contact.vcard.split('\n'); + const contactInfo = {}; + + vCardData.forEach((line) => { + const [key, value] = line.split(':'); + if (key && value) { + contactInfo[key] = value; + } + }); + + let formattedContact = `**Contact:** + **name:** ${contact.displayName}`; + + let numberCount = 1; + Object.keys(contactInfo).forEach((key) => { + if (key.startsWith('item') && key.includes('TEL')) { + const phoneNumber = contactInfo[key]; + formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`; + numberCount++; + } + }); + + return formattedContact; + }); + + const formattedContactsArray = formattedContacts.join('\n\n'); + + this.logger.verbose('formatted contacts: ' + formattedContactsArray); + + return formattedContactsArray; + } + + 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; + } + + public async eventWhatsapp(event: string, instance: InstanceDto, body: any) { + this.logger.verbose('event whatsapp to instance: ' + instance.instanceName); + try { + const client = await this.clientCw(instance); + + if (!client) { + this.logger.warn('client not found'); + return null; + } + + const waInstance = this.waMonitor.waInstances[instance.instanceName]; + + if (!waInstance) { + this.logger.warn('wa instance not found'); + return null; + } + + if (event === 'messages.upsert' || event === 'send.message') { + this.logger.verbose('event messages.upsert'); + + if (body.key.remoteJid === 'status@broadcast') { + this.logger.verbose('status broadcast found'); + return; + } + + this.logger.verbose('get conversation message'); + const bodyMessage = await this.getConversationMessage(body.message); + + const isMedia = this.isMediaMessage(body.message); + + const adsMessage = this.getAdsMessage(body.message); + + if (!bodyMessage && !isMedia) { + this.logger.warn('no body message found'); + return; + } + + this.logger.verbose('get conversation in chatwoot'); + const getConversation = await this.createConversation(instance, body); + + if (!getConversation) { + this.logger.warn('conversation not found'); + return; + } + + const messageType = body.key.fromMe ? 'outgoing' : 'incoming'; + + this.logger.verbose('message type: ' + messageType); + + this.logger.verbose('is media: ' + isMedia); + + this.logger.verbose('check if is media'); + if (isMedia) { + this.logger.verbose('message is media'); + + this.logger.verbose('get base64 from media message'); + 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}`)}`; + + this.logger.verbose('temp file name: ' + nameFile); + + this.logger.verbose('create temp file'); + writeFileSync(fileName, fileData, 'utf8'); + + this.logger.verbose('check if is group'); + if (body.key.remoteJid.includes('@g.us')) { + this.logger.verbose('message is group'); + + const participantName = body.pushName; + + let content: string; + + if (!body.key.fromMe) { + this.logger.verbose('message is not from me'); + content = `**${participantName}:**\n\n${bodyMessage}`; + } else { + this.logger.verbose('message is from me'); + content = `${bodyMessage}`; + } + + this.logger.verbose('send data to chatwoot'); + const send = await this.sendData(getConversation, fileName, messageType, content); + + if (!send) { + this.logger.warn('message not sent'); + return; + } + + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); + + this.messageCache = this.loadMessageCache(); + + this.messageCache.add(send.id.toString()); + + this.logger.verbose('save message cache'); + this.saveMessageCache(); + + return send; + } else { + this.logger.verbose('message is not group'); + + this.logger.verbose('send data to chatwoot'); + const send = await this.sendData(getConversation, fileName, messageType, bodyMessage); + + if (!send) { + this.logger.warn('message not sent'); + return; + } + + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); + + this.messageCache = this.loadMessageCache(); + + this.messageCache.add(send.id.toString()); + + this.logger.verbose('save message cache'); + this.saveMessageCache(); + + return send; + } + } + + this.logger.verbose('check if has Ads Message'); + if (adsMessage) { + this.logger.verbose('message is from Ads'); + + this.logger.verbose('get base64 from media ads message'); + const imgBuffer = await axios.get(adsMessage.thumbnailUrl, { responseType: 'arraybuffer' }); + + const extension = mimeTypes.extension(imgBuffer.headers['content-type']); + const mimeType = extension && mimeTypes.lookup(extension); + + if (!mimeType) { + this.logger.warn('mimetype of Ads message not found'); + return; + } + + const random = Math.random().toString(36).substring(7); + const nameFile = `${random}.${mimeTypes.extension(mimeType)}`; + const fileData = Buffer.from(imgBuffer.data, 'binary'); + const fileName = `${path.join(waInstance?.storePath, 'temp', `${nameFile}`)}`; + + this.logger.verbose('temp file name: ' + nameFile); + this.logger.verbose('create temp file'); + await Jimp.read(fileData) + .then(async (img) => { + await img.cover(320, 180).writeAsync(fileName); + }) + .catch((err) => { + this.logger.error(`image is not write: ${err}`); + }); + const truncStr = (str: string, len: number) => { + return str.length > len ? str.substring(0, len) + '...' : str; + }; + + const title = truncStr(adsMessage.title, 40); + const description = truncStr(adsMessage.body, 75); + + this.logger.verbose('send data to chatwoot'); + const send = await this.sendData( + getConversation, + fileName, + messageType, + `${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`, + ); + + if (!send) { + this.logger.warn('message not sent'); + return; + } + + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); + + this.messageCache = this.loadMessageCache(); + + this.messageCache.add(send.id.toString()); + + this.logger.verbose('save message cache'); + this.saveMessageCache(); + + return send; + } + + this.logger.verbose('check if is group'); + if (body.key.remoteJid.includes('@g.us')) { + this.logger.verbose('message is group'); + const participantName = body.pushName; + + let content: string; + + if (!body.key.fromMe) { + this.logger.verbose('message is not from me'); + content = `**${participantName}**\n\n${bodyMessage}`; + } else { + this.logger.verbose('message is from me'); + content = `${bodyMessage}`; + } + + this.logger.verbose('send data to chatwoot'); + const send = await this.createMessage(instance, getConversation, content, messageType); + + if (!send) { + this.logger.warn('message not sent'); + return; + } + + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); + + this.messageCache = this.loadMessageCache(); + + this.messageCache.add(send.id.toString()); + + this.logger.verbose('save message cache'); + this.saveMessageCache(); + + return send; + } else { + this.logger.verbose('message is not group'); + + this.logger.verbose('send data to chatwoot'); + const send = await this.createMessage(instance, getConversation, bodyMessage, messageType); + + if (!send) { + this.logger.warn('message not sent'); + return; + } + + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); + + this.messageCache = this.loadMessageCache(); + + this.messageCache.add(send.id.toString()); + + this.logger.verbose('save message cache'); + this.saveMessageCache(); + + return send; + } + } + + if (event === 'status.instance') { + this.logger.verbose('event status.instance'); + const data = body; + + if (this.provider?.id_inbox) { + var msgStatus = `⚡️ Instance status ${this.provider?.name_inbox}: ${data.status}`; + } else { + const inbox = await this.getInbox(instance); + + if (!inbox) { + this.logger.warn('inbox not found'); + return; + } + + var msgStatus = `⚡️ Instance status ${inbox.name}: ${data.status}`; + } + + + this.logger.verbose('send message to chatwoot'); + await this.createBotMessage(instance, msgStatus, 'incoming'); + } + + // if (event === 'connection.update') { + // this.logger.verbose('event connection.update'); + + // if (body.status === 'open') { + // const msgConnection = `🚀 Connection successfully established!`; + + // this.logger.verbose('send message to chatwoot'); + // await this.createBotMessage(instance, msgConnection, 'incoming'); + // } + // } + + if (event === 'qrcode.updated') { + this.logger.verbose('event qrcode.updated'); + if (body.statusCode === 500) { + this.logger.verbose('qrcode error'); + const erroQRcode = `🚨 QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.`; + + this.logger.verbose('send message to chatwoot'); + return await this.createBotMessage(instance, erroQRcode, 'incoming'); + } else { + this.logger.verbose('qrcode success'); + const fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64'); + + const fileName = `${path.join(waInstance?.storePath, 'temp', `${`${instance}.png`}`)}`; + + this.logger.verbose('temp file name: ' + fileName); + + this.logger.verbose('create temp file'); + writeFileSync(fileName, fileData, 'utf8'); + + this.logger.verbose('send qrcode to chatwoot'); + await this.createBotQr(instance, 'QRCode successfully generated!', 'incoming', fileName); + + let msgQrCode = `⚡️ QRCode successfully generated!\n\nScan this QR code within the next 40 seconds.`; + + if (body?.qrcode?.pairingCode) { + msgQrCode = + msgQrCode + + `\n\n*Pairing Code:* ${body.qrcode.pairingCode.substring(0, 4)}-${body.qrcode.pairingCode.substring( + 4, + 8, + )}`; + } + + this.logger.verbose('send message to chatwoot'); + await this.createBotMessage(instance, msgQrCode, 'incoming'); + } + } + } catch (error) { + this.logger.error(error); + } + } +} diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts old mode 100644 new mode 100755 index 766569de..c0f8b0c1 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -1,391 +1,398 @@ -import { execSync } from 'child_process'; -import EventEmitter2 from 'eventemitter2'; -import { opendirSync, readdirSync, rmSync } from 'fs'; -import { Db } from 'mongodb'; -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 { - AuthModel, - ChamaaiModel, - // ChatModel, - ChatwootModel, - // ContactModel, - // MessageModel, - // MessageUpModel, - ProxyModel, - RabbitmqModel, - SettingsModel, - TypebotModel, - WebhookModel, - WebsocketModel, -} from '../models'; -import { RepositoryBroker } from '../repository/repository.manager'; -import { WAStartupService } from './whatsapp.service'; - -export class WAMonitoringService { - constructor( - private readonly eventEmitter: EventEmitter2, - private readonly configService: ConfigService, - private readonly repository: RepositoryBroker, - private readonly cache: RedisCache, - ) { - this.logger.verbose('instance created'); - - this.removeInstance(); - this.noConnection(); - // this.delInstanceFiles(); - - Object.assign(this.db, configService.get('DATABASE')); - Object.assign(this.redis, configService.get('REDIS')); - - this.dbInstance = this.db.ENABLED - ? this.repository.dbServer?.db(this.db.CONNECTION.DB_PREFIX_NAME + '-instances') - : undefined; - } - - private readonly db: Partial = {}; - private readonly redis: Partial = {}; - - private dbInstance: Db; - - private dbStore = dbserver; - - private readonly logger = new Logger(WAMonitoringService.name); - public readonly waInstances: Record = {}; - - public delInstanceTime(instance: string) { - const time = this.configService.get('DEL_INSTANCE'); - if (typeof time === 'number' && time > 0) { - this.logger.verbose(`Instance "${instance}" don't have connection, will be removed in ${time} minutes`); - - setTimeout(async () => { - if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { - if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') { - await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance); - this.waInstances[instance]?.client?.ws?.close(); - this.waInstances[instance]?.client?.end(undefined); - this.waInstances[instance]?.removeRabbitmqQueues(); - delete this.waInstances[instance]; - } else { - this.waInstances[instance]?.removeRabbitmqQueues(); - delete this.waInstances[instance]; - this.eventEmitter.emit('remove.instance', instance, 'inner'); - } - } - }, 1000 * 60 * time); - } - } - - 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[] = []; - - for await (const [key, value] of Object.entries(this.waInstances)) { - if (value) { - this.logger.verbose('get instance info: ' + key); - let chatwoot: any; - - const urlServer = this.configService.get('SERVER').URL; - - const findChatwoot = await this.waInstances[key].findChatwoot(); - - if (findChatwoot && findChatwoot.enabled) { - chatwoot = { - ...findChatwoot, - webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`, - }; - } - - if (value.connectionStatus.state === '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('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { - instanceData.instance['serverUrl'] = this.configService.get('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('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { - instanceData.instance['serverUrl'] = this.configService.get('SERVER').URL; - - instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey; - - instanceData.instance['chatwoot'] = chatwoot; - } - - instances.push(instanceData); - } - } - } - - this.logger.verbose('return instance info: ' + instances.length); - - return instances.find((i) => i.instance.instanceName === instanceName) ?? instances; - } - - private delInstanceFiles() { - this.logger.verbose('cron to delete instance files started'); - setInterval(async () => { - if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { - const collections = await this.dbInstance.collections(); - collections.forEach(async (collection) => { - const name = collection.namespace.replace(/^[\w-]+./, ''); - await this.dbInstance.collection(name).deleteMany({ - $or: [{ _id: { $regex: /^app.state.*/ } }, { _id: { $regex: /^session-.*/ } }], - }); - this.logger.verbose('instance files deleted: ' + name); - }); - } else if (!this.redis.ENABLED) { - const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); - for await (const dirent of dir) { - if (dirent.isDirectory()) { - const files = readdirSync(join(INSTANCE_DIR, dirent.name), { - encoding: 'utf-8', - }); - files.forEach(async (file) => { - if (file.match(/^app.state.*/) || file.match(/^session-.*/)) { - rmSync(join(INSTANCE_DIR, dirent.name, file), { - recursive: true, - force: true, - }); - } - }); - this.logger.verbose('instance files deleted: ' + dirent.name); - } - } - } - }, 3600 * 1000 * 2); - } - - public async cleaningUp(instanceName: string) { - this.logger.verbose('cleaning up instance: ' + instanceName); - if (this.redis.ENABLED) { - this.logger.verbose('cleaning up instance in redis: ' + instanceName); - this.cache.reference = instanceName; - await this.cache.delAll(); - return; - } - - if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { - this.logger.verbose('cleaning up instance in database: ' + instanceName); - await this.repository.dbServer.connect(); - const collections: any[] = await this.dbInstance.collections(); - if (collections.length > 0) { - await this.dbInstance.dropCollection(instanceName); - } - return; - } - - this.logger.verbose('cleaning up instance in files: ' + instanceName); - rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true }); - } - - public async cleaningStoreFiles(instanceName: string) { - if (!this.db.ENABLED) { - this.logger.verbose('cleaning store files instance: ' + instanceName); - rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true }); - - execSync(`rm -rf ${join(STORE_DIR, 'chats', instanceName)}`); - execSync(`rm -rf ${join(STORE_DIR, 'contacts', instanceName)}`); - execSync(`rm -rf ${join(STORE_DIR, 'message-up', instanceName)}`); - execSync(`rm -rf ${join(STORE_DIR, 'messages', instanceName)}`); - - 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; - } - - this.logger.verbose('cleaning store database instance: ' + instanceName); - - // await ChatModel.deleteMany({ owner: instanceName }); - // await ContactModel.deleteMany({ owner: instanceName }); - // await MessageUpModel.deleteMany({ owner: instanceName }); - // await MessageModel.deleteMany({ owner: instanceName }); - - await AuthModel.deleteMany({ _id: instanceName }); - await WebhookModel.deleteMany({ _id: instanceName }); - await ChatwootModel.deleteMany({ _id: instanceName }); - await ChamaaiModel.deleteMany({ _id: instanceName }); - await ProxyModel.deleteMany({ _id: instanceName }); - await RabbitmqModel.deleteMany({ _id: instanceName }); - await TypebotModel.deleteMany({ _id: instanceName }); - await WebsocketModel.deleteMany({ _id: instanceName }); - await SettingsModel.deleteMany({ _id: instanceName }); - - return; - } - - public async loadInstance() { - this.logger.verbose('Loading instances'); - - try { - if (this.redis.ENABLED) { - 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); - try { - this.logger.verbose('instance: ' + instanceName + ' - removing from memory'); - this.waInstances[instanceName] = undefined; - } catch (error) { - this.logger.error(error); - } - - try { - this.logger.verbose('request cleaning up instance: ' + instanceName); - this.cleaningUp(instanceName); - this.cleaningStoreFiles(instanceName); - } finally { - this.logger.warn(`Instance "${instanceName}" - REMOVED`); - } - }); - this.eventEmitter.on('logout.instance', async (instanceName: string) => { - this.logger.verbose('logout instance: ' + instanceName); - try { - // this.logger.verbose('request cleaning up instance: ' + instanceName); - // this.cleaningUp(instanceName); - } finally { - this.logger.warn(`Instance "${instanceName}" - LOGOUT`); - } - }); - } - - private noConnection() { - this.logger.verbose('checking instances without connection'); - this.eventEmitter.on('no.connection', async (instanceName) => { - try { - this.logger.verbose('logging out instance: ' + instanceName); - await this.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName); - - this.logger.verbose('close connection instance: ' + instanceName); - this.waInstances[instanceName]?.client?.ws?.close(); - - this.waInstances[instanceName].instance.qrcode = { count: 0 }; - this.waInstances[instanceName].stateConnection.state = 'close'; - } catch (error) { - this.logger.error({ - localError: 'noConnection', - warn: 'Error deleting instance from memory.', - error, - }); - } finally { - this.logger.warn(`Instance "${instanceName}" - NOT CONNECTION`); - } - }); - } -} +import { execSync } from 'child_process'; +import EventEmitter2 from 'eventemitter2'; +import { opendirSync, readdirSync, rmSync } from 'fs'; +import { Db } from 'mongodb'; +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 { + AuthModel, + ChamaaiModel, + ChatModel, + ChatwootModel, + ContactModel, + MessageModel, + MessageUpModel, + ProxyModel, + RabbitmqModel, + SettingsModel, + TypebotModel, + WebhookModel, + WebsocketModel, + +} from '../models'; +import { RepositoryBroker } from '../repository/repository.manager'; +import { WAStartupService } from './whatsapp.service'; + +export class WAMonitoringService { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly configService: ConfigService, + private readonly repository: RepositoryBroker, + private readonly cache: RedisCache, + ) { + this.logger.verbose('instance created'); + + this.removeInstance(); + this.noConnection(); + this.delInstanceFiles(); + + Object.assign(this.db, configService.get('DATABASE')); + Object.assign(this.redis, configService.get('REDIS')); + + this.dbInstance = this.db.ENABLED + ? this.repository.dbServer?.db( + this.db.CONNECTION.DB_PREFIX_NAME + + this.db.CONNECTION.DB_PREFIX_FINAL_NAME + ) + : undefined; + } + + private readonly db: Partial = {}; + private readonly redis: Partial = {}; + + private dbInstance: Db; + + private dbStore = dbserver; + + private readonly logger = new Logger(WAMonitoringService.name); + public readonly waInstances: Record = {}; + + public delInstanceTime(instance: string) { + const time = this.configService.get('DEL_INSTANCE'); + if (typeof time === 'number' && time > 0) { + this.logger.verbose(`Instance "${instance}" don't have connection, will be removed in ${time} minutes`); + + setTimeout(async () => { + if (this.waInstances[instance]?.connectionStatus?.state !== 'open') { + if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') { + await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance); + this.waInstances[instance]?.client?.ws?.close(); + this.waInstances[instance]?.client?.end(undefined); + this.waInstances[instance]?.removeRabbitmqQueues(); + delete this.waInstances[instance]; + } else { + this.waInstances[instance]?.removeRabbitmqQueues(); + delete this.waInstances[instance]; + this.eventEmitter.emit('remove.instance', instance, 'inner'); + } + } + }, 1000 * 60 * time); + } + } + + 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[] = []; + + for await (const [key, value] of Object.entries(this.waInstances)) { + if (value) { + this.logger.verbose('get instance info: ' + key); + let chatwoot: any; + + const urlServer = this.configService.get('SERVER').URL; + + const findChatwoot = await this.waInstances[key].findChatwoot(); + + if (findChatwoot && findChatwoot.enabled) { + chatwoot = { + ...findChatwoot, + webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`, + }; + } + + if (value.connectionStatus.state === '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('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { + instanceData.instance['serverUrl'] = this.configService.get('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('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { + instanceData.instance['serverUrl'] = this.configService.get('SERVER').URL; + + instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey; + + instanceData.instance['chatwoot'] = chatwoot; + } + + instances.push(instanceData); + } + } + } + + this.logger.verbose('return instance info: ' + instances.length); + + return instances.find((i) => i.instance.instanceName === instanceName) ?? instances; + } + + private delInstanceFiles() { + this.logger.verbose('cron to delete instance files started'); + setInterval(async () => { + if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { + const collections = await this.dbInstance.collections(); + collections.forEach(async (collection) => { + const name = collection.namespace.replace(/^[\w-]+./, ''); + await this.dbInstance.collection(name).deleteMany({ + $or: [{ _id: { $regex: /^app.state.*/ } }, { _id: { $regex: /^session-.*/ } }], + }); + this.logger.verbose('instance files deleted: ' + name); + }); + } else if (!this.redis.ENABLED) { + const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); + for await (const dirent of dir) { + if (dirent.isDirectory()) { + const files = readdirSync(join(INSTANCE_DIR, dirent.name), { + encoding: 'utf-8', + }); + files.forEach(async (file) => { + if (file.match(/^app.state.*/) || file.match(/^session-.*/)) { + rmSync(join(INSTANCE_DIR, dirent.name, file), { + recursive: true, + force: true, + }); + } + }); + this.logger.verbose('instance files deleted: ' + dirent.name); + } + } + } + }, 3600 * 1000 * 2); + } + + public async cleaningUp(instanceName: string) { + this.logger.verbose('cleaning up instance: ' + instanceName); + if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) { + this.logger.verbose('cleaning up instance in database: ' + instanceName); + await this.repository.dbServer.connect(); + const collections: any[] = await this.dbInstance.collections(); + if (collections.length > 0) { + await this.dbInstance.dropCollection(instanceName); + } + return; + } + + if (this.redis.ENABLED) { + this.logger.verbose('cleaning up instance in redis: ' + instanceName); + this.cache.reference = instanceName; + await this.cache.delAll(); + return; + } + + this.logger.verbose('cleaning up instance in files: ' + instanceName); + rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true }); + } + + public async cleaningStoreFiles(instanceName: string) { + if (!this.db.ENABLED) { + this.logger.verbose('cleaning store files instance: ' + instanceName); + rmSync(join(INSTANCE_DIR, instanceName), { recursive: true, force: true }); + + execSync(`rm -rf ${join(STORE_DIR, 'chats', instanceName)}`); + execSync(`rm -rf ${join(STORE_DIR, 'contacts', instanceName)}`); + execSync(`rm -rf ${join(STORE_DIR, 'message-up', instanceName)}`); + execSync(`rm -rf ${join(STORE_DIR, 'messages', instanceName)}`); + + 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; + } + + this.logger.verbose('cleaning store database instance: ' + instanceName); + + await AuthModel.deleteMany({ owner: instanceName }); + await ChatModel.deleteMany({ owner: instanceName }); + await ContactModel.deleteMany({ owner: instanceName }); + await MessageModel.deleteMany({ owner: instanceName }); + + await MessageUpModel.deleteMany({ owner: instanceName }); + await WebhookModel.deleteMany({ _id: instanceName }); + await ChatwootModel.deleteMany({ _id: instanceName }); + + await ChamaaiModel.deleteMany({ _id: instanceName }); + await ProxyModel.deleteMany({ _id: instanceName }); + await RabbitmqModel.deleteMany({ _id: instanceName }); + await TypebotModel.deleteMany({ _id: instanceName }); + await WebsocketModel.deleteMany({ _id: instanceName }); + + await SettingsModel.deleteMany({ _id: instanceName }); + + return; + } + + public async loadInstance() { + this.logger.verbose('Loading instances'); + + try { + if (this.redis.ENABLED) { + 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); + try { + this.logger.verbose('instance: ' + instanceName + ' - removing from memory'); + this.waInstances[instanceName] = undefined; + } catch (error) { + this.logger.error(error); + } + + try { + this.logger.verbose('request cleaning up instance: ' + instanceName); + this.cleaningUp(instanceName); + this.cleaningStoreFiles(instanceName); + } finally { + this.logger.warn(`Instance "${instanceName}" - REMOVED`); + } + }); + this.eventEmitter.on('logout.instance', async (instanceName: string) => { + this.logger.verbose('logout instance: ' + instanceName); + try { + this.logger.verbose('request cleaning up instance: ' + instanceName); + this.cleaningUp(instanceName); + } finally { + this.logger.warn(`Instance "${instanceName}" - LOGOUT`); + } + }); + } + + private noConnection() { + this.logger.verbose('checking instances without connection'); + this.eventEmitter.on('no.connection', async (instanceName) => { + try { + this.logger.verbose('logging out instance: ' + instanceName); + await this.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName); + + this.logger.verbose('close connection instance: ' + instanceName); + this.waInstances[instanceName]?.client?.ws?.close(); + + this.waInstances[instanceName].instance.qrcode = { count: 0 }; + this.waInstances[instanceName].stateConnection.state = 'close'; + } catch (error) { + this.logger.error({ + localError: 'noConnection', + warn: 'Error deleting instance from memory.', + error, + }); + } finally { + this.logger.warn(`Instance "${instanceName}" - NOT CONNECTION`); + } + }); + } +} diff --git a/src/whatsapp/services/openai.service.ts b/src/whatsapp/services/openai.service.ts new file mode 100755 index 00000000..5c10afa6 --- /dev/null +++ b/src/whatsapp/services/openai.service.ts @@ -0,0 +1,57 @@ +import { Logger } from '../../config/logger.config'; +import { initQueues } from '../../libs/amqp.server'; +import { InstanceDto } from '../dto/instance.dto'; +import { OpenaiDto } from '../dto/openai.dto'; +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; +import { OpenaiRaw } from '../models'; +import { WAMonitoringService } from './monitor.service'; + +export class OpenaiService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(OpenaiService.name); + + public create(instance: InstanceDto, data: OpenaiDto) { + this.logger.verbose('create openai: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setOpenai(data); + return { openai: { ...instance, openai: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find openai: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findOpenai(); + + if (Object.keys(result).length === 0) { + throw new Error('openai not found'); + } + + return result; + } catch (error) { + return { chave: '', enabled: false, events: [] }; + } + } + + public createContact(instance: InstanceDto, data: ContactOpenaiDto) { + this.logger.verbose('create openai: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setContactOpenai(data); + return { openai: { ...instance, openai: data } }; + } + + + public async findContact(instance: InstanceDto): Promise { + try { + this.logger.verbose('find openai: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findContactOpenai(); + + if (Object.keys(result).length === 0) { + throw new Error('openai not found'); + } + + return result; + } catch (error) { + return { chave: '', enabled: false, events: [] }; + } + } + +} diff --git a/src/whatsapp/services/proxy.service.ts b/src/whatsapp/services/proxy.service.ts old mode 100644 new mode 100755 index 1039fd5c..cb04c582 --- a/src/whatsapp/services/proxy.service.ts +++ b/src/whatsapp/services/proxy.service.ts @@ -1,33 +1,33 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { ProxyDto } from '../dto/proxy.dto'; -import { ProxyRaw } from '../models'; -import { WAMonitoringService } from './monitor.service'; - -export class ProxyService { - constructor(private readonly waMonitor: WAMonitoringService) {} - - private readonly logger = new Logger(ProxyService.name); - - public create(instance: InstanceDto, data: ProxyDto, reload = true) { - this.logger.verbose('create proxy: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setProxy(data, reload); - - return { proxy: { ...instance, proxy: data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - this.logger.verbose('find proxy: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[instance.instanceName].findProxy(); - - if (Object.keys(result).length === 0) { - throw new Error('Proxy not found'); - } - - return result; - } catch (error) { - return { enabled: false, proxy: '' }; - } - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { ProxyDto } from '../dto/proxy.dto'; +import { ProxyRaw } from '../models'; +import { WAMonitoringService } from './monitor.service'; + +export class ProxyService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(ProxyService.name); + + public create(instance: InstanceDto, data: ProxyDto, reload = true) { + this.logger.verbose('create proxy: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setProxy(data, reload); + + return { proxy: { ...instance, proxy: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find proxy: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findProxy(); + + if (Object.keys(result).length === 0) { + throw new Error('Proxy not found'); + } + + return result; + } catch (error) { + return { enabled: false, proxy: '' }; + } + } +} diff --git a/src/whatsapp/services/rabbitmq.service.ts b/src/whatsapp/services/rabbitmq.service.ts old mode 100644 new mode 100755 index a377595b..12e82587 --- a/src/whatsapp/services/rabbitmq.service.ts +++ b/src/whatsapp/services/rabbitmq.service.ts @@ -1,35 +1,35 @@ -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'; -import { WAMonitoringService } from './monitor.service'; - -export class RabbitmqService { - constructor(private readonly waMonitor: WAMonitoringService) {} - - private readonly logger = new Logger(RabbitmqService.name); - - public create(instance: InstanceDto, data: RabbitmqDto) { - this.logger.verbose('create rabbitmq: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data); - - initQueues(instance.instanceName, data.events); - return { rabbitmq: { ...instance, rabbitmq: data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - this.logger.verbose('find rabbitmq: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[instance.instanceName].findRabbitmq(); - - if (Object.keys(result).length === 0) { - throw new Error('Rabbitmq not found'); - } - - return result; - } catch (error) { - return { enabled: false, events: [] }; - } - } -} +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'; +import { WAMonitoringService } from './monitor.service'; + +export class RabbitmqService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(RabbitmqService.name); + + public create(instance: InstanceDto, data: RabbitmqDto) { + this.logger.verbose('create rabbitmq: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setRabbitmq(data); + + initQueues(instance.instanceName, data.events); + return { rabbitmq: { ...instance, rabbitmq: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find rabbitmq: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findRabbitmq(); + + if (Object.keys(result).length === 0) { + throw new Error('Rabbitmq not found'); + } + + return result; + } catch (error) { + return { enabled: false, events: [] }; + } + } +} diff --git a/src/whatsapp/services/settings.service.ts b/src/whatsapp/services/settings.service.ts old mode 100644 new mode 100755 index 6815ca40..2466fe6a --- a/src/whatsapp/services/settings.service.ts +++ b/src/whatsapp/services/settings.service.ts @@ -1,32 +1,32 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { SettingsDto } from '../dto/settings.dto'; -import { WAMonitoringService } from './monitor.service'; - -export class SettingsService { - constructor(private readonly waMonitor: WAMonitoringService) {} - - private readonly logger = new Logger(SettingsService.name); - - public create(instance: InstanceDto, data: SettingsDto) { - this.logger.verbose('create settings: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setSettings(data); - - return { settings: { ...instance, settings: data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - this.logger.verbose('find settings: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[instance.instanceName].findSettings(); - - if (Object.keys(result).length === 0) { - throw new Error('Settings not found'); - } - - return result; - } catch (error) { - return { reject_call: false, msg_call: '', groups_ignore: false }; - } - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { WAMonitoringService } from './monitor.service'; + +export class SettingsService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(SettingsService.name); + + public create(instance: InstanceDto, data: SettingsDto) { + this.logger.verbose('create settings: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setSettings(data); + + return { settings: { ...instance, settings: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find settings: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findSettings(); + + if (Object.keys(result).length === 0) { + throw new Error('Settings not found'); + } + + return result; + } catch (error) { + return { reject_call: false, msg_call: '', groups_ignore: false }; + } + } +} diff --git a/src/whatsapp/services/sqs.service.ts b/src/whatsapp/services/sqs.service.ts old mode 100644 new mode 100755 index 236d4ceb..aa902711 --- a/src/whatsapp/services/sqs.service.ts +++ b/src/whatsapp/services/sqs.service.ts @@ -1,35 +1,35 @@ -import { Logger } from '../../config/logger.config'; -import { initQueues } from '../../libs/sqs.server'; -import { InstanceDto } from '../dto/instance.dto'; -import { SqsDto } from '../dto/sqs.dto'; -import { SqsRaw } from '../models'; -import { WAMonitoringService } from './monitor.service'; - -export class SqsService { - constructor(private readonly waMonitor: WAMonitoringService) {} - - private readonly logger = new Logger(SqsService.name); - - public create(instance: InstanceDto, data: SqsDto) { - this.logger.verbose('create sqs: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setSqs(data); - - initQueues(instance.instanceName, data.events); - return { sqs: { ...instance, sqs: data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - this.logger.verbose('find sqs: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[instance.instanceName].findSqs(); - - if (Object.keys(result).length === 0) { - throw new Error('Sqs not found'); - } - - return result; - } catch (error) { - return { enabled: false, events: [] }; - } - } -} +import { Logger } from '../../config/logger.config'; +import { initQueues } from '../../libs/sqs.server'; +import { InstanceDto } from '../dto/instance.dto'; +import { SqsDto } from '../dto/sqs.dto'; +import { SqsRaw } from '../models'; +import { WAMonitoringService } from './monitor.service'; + +export class SqsService { + constructor(private readonly waMonitor: WAMonitoringService) { } + + private readonly logger = new Logger(SqsService.name); + + public create(instance: InstanceDto, data: SqsDto) { + this.logger.verbose('create sqs: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setSqs(data); + + initQueues(instance.instanceName, data.events); + return { sqs: { ...instance, sqs: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find sqs: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findSqs(); + + if (Object.keys(result).length === 0) { + throw new Error('Sqs not found'); + } + + return result; + } catch (error) { + return { enabled: false, events: [] }; + } + } +} \ No newline at end of file diff --git a/src/whatsapp/services/typebot.service.ts b/src/whatsapp/services/typebot.service.ts old mode 100644 new mode 100755 index f5962daf..e2e1e6b8 --- a/src/whatsapp/services/typebot.service.ts +++ b/src/whatsapp/services/typebot.service.ts @@ -1,759 +1,1010 @@ -import axios from 'axios'; - -import { ConfigService, Typebot } from '../../config/env.config'; -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 { - constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) {} - - private readonly logger = new Logger(TypebotService.name); - - public create(instance: InstanceDto, data: TypebotDto) { - this.logger.verbose('create typebot: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setTypebot(data); - - return { typebot: { ...instance, typebot: data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - this.logger.verbose('find typebot: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[instance.instanceName].findTypebot(); - - if (Object.keys(result).length === 0) { - throw new Error('Typebot not found'); - } - - return result; - } catch (error) { - return { enabled: false, url: '', typebot: '', expire: 0, sessions: [] }; - } - } - - public async changeStatus(instance: InstanceDto, data: any) { - const remoteJid = data.remoteJid; - const status = data.status; - - const findData = await this.find(instance); - - const session = findData.sessions.find((session) => session.remoteJid === remoteJid); - - if (session) { - if (status === 'closed') { - findData.sessions.splice(findData.sessions.indexOf(session), 1); - - const typebotData = { - enabled: findData.enabled, - url: findData.url, - typebot: findData.typebot, - expire: findData.expire, - 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); - - return { typebot: { ...instance, typebot: typebotData } }; - } - - findData.sessions.map((session) => { - if (session.remoteJid === remoteJid) { - session.status = status; - } - }); - } else { - const session: Session = { - remoteJid: remoteJid, - sessionId: Math.floor(Math.random() * 10000000000).toString(), - status: status, - createdAt: Date.now(), - updateAt: Date.now(), - prefilledVariables: { - remoteJid: remoteJid, - pushName: '', - additionalData: {}, - }, - }; - findData.sessions.push(session); - } - - const typebotData = { - enabled: findData.enabled, - url: findData.url, - typebot: findData.typebot, - expire: findData.expire, - 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 } }; - } - - public async startTypebot(instance: InstanceDto, data: any) { - if (data.remoteJid === 'status@broadcast') return; - - const remoteJid = data.remoteJid; - const url = data.url; - const typebot = data.typebot; - const startSession = data.startSession; - const variables = data.variables; - const findTypebot = await this.find(instance); - 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 prefilledVariables = { - remoteJid: remoteJid, - instanceName: instance.instanceName, - }; - - if (variables?.length) { - variables.forEach((variable: { name: string | number; value: string }) => { - prefilledVariables[variable.name] = variable.value; - }); - } - - if (startSession) { - const newSessions = await this.clearSessions(instance, remoteJid); - - const response = await this.createNewSession(instance, { - enabled: findTypebot.enabled, - url: url, - typebot: typebot, - remoteJid: remoteJid, - expire: expire, - keyword_finish: keyword_finish, - delay_message: delay_message, - unknown_message: unknown_message, - listening_from_me: listening_from_me, - sessions: newSessions, - prefilledVariables: prefilledVariables, - }); - - if (response.sessionId) { - await this.sendWAMessage(instance, remoteJid, response.messages, response.input, response.clientSideActions); - - this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { - remoteJid: remoteJid, - url: url, - typebot: typebot, - prefilledVariables: prefilledVariables, - sessionId: `${response.sessionId}`, - }); - } else { - throw new Error('Session ID not found in response'); - } - } else { - const id = Math.floor(Math.random() * 10000000000).toString(); - - const reqData = { - startParams: { - typebot: data.typebot, - prefilledVariables: prefilledVariables, - }, - }; - - try { - const version = this.configService.get('TYPEBOT').API_VERSION; - const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); - - await this.sendWAMessage( - instance, - remoteJid, - request.data.messages, - request.data.input, - request.data.clientSideActions, - ); - - this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { - remoteJid: remoteJid, - url: url, - typebot: typebot, - variables: variables, - sessionId: id, - }); - } catch (error) { - this.logger.error(error); - return; - } - } - - return { - typebot: { - ...instance, - typebot: { - url: url, - remoteJid: remoteJid, - typebot: typebot, - prefilledVariables: prefilledVariables, - }, - }, - }; - } - - 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; - } - - public async createNewSession(instance: InstanceDto, data: any) { - if (data.remoteJid === 'status@broadcast') return; - const id = Math.floor(Math.random() * 10000000000).toString(); - - const reqData = { - startParams: { - typebot: data.typebot, - prefilledVariables: { - ...data.prefilledVariables, - remoteJid: data.remoteJid, - pushName: data.pushName || data.prefilledVariables?.pushName || '', - instanceName: instance.instanceName, - }, - }, - }; - - try { - const version = this.configService.get('TYPEBOT').API_VERSION; - const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); - - if (request?.data?.sessionId) { - data.sessions.push({ - remoteJid: data.remoteJid, - sessionId: `${id}-${request.data.sessionId}`, - status: 'opened', - createdAt: Date.now(), - updateAt: Date.now(), - prefilledVariables: { - ...data.prefilledVariables, - remoteJid: data.remoteJid, - pushName: data.pushName || '', - instanceName: instance.instanceName, - }, - }); - - const typebotData = { - enabled: data.enabled, - url: data.url, - typebot: data.typebot, - expire: data.expire, - 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, - }; - - this.create(instance, typebotData); - } - return request.data; - } catch (error) { - this.logger.error(error); - return; - } - } - - public async clearSessions(instance: InstanceDto, remoteJid: string) { - const findTypebot = await this.find(instance); - const sessions = (findTypebot.sessions as Session[]) ?? []; - - const sessionWithRemoteJid = sessions.filter((session) => session.remoteJid === remoteJid); - - if (sessionWithRemoteJid.length > 0) { - sessionWithRemoteJid.forEach((session) => { - sessions.splice(sessions.indexOf(session), 1); - }); - - const typebotData = { - enabled: findTypebot.enabled, - url: findTypebot.url, - typebot: findTypebot.typebot, - expire: findTypebot.expire, - keyword_finish: findTypebot.keyword_finish, - delay_message: findTypebot.delay_message, - unknown_message: findTypebot.unknown_message, - listening_from_me: findTypebot.listening_from_me, - sessions, - }; - - this.create(instance, typebotData); - - return sessions; - } - - return sessions; - } - - public async sendWAMessage( - instance: InstanceDto, - remoteJid: string, - messages: any[], - input: any[], - clientSideActions: any[], - ) { - processMessages(this.waMonitor.waInstances[instance.instanceName], messages, input, clientSideActions).catch( - (err) => { - console.error('Erro ao processar mensagens:', err); - }, - ); - - function findItemAndGetSecondsToWait(array, targetId) { - if (!array) return null; - - for (const item of array) { - if (item.lastBubbleBlockId === targetId) { - return item.wait?.secondsToWaitFor; - } - } - return null; - } - - async function processMessages(instance, messages, input, clientSideActions) { - for (const message of messages) { - const wait = findItemAndGetSecondsToWait(clientSideActions, message.id); - - if (message.type === 'text') { - let formattedText = ''; - - let linkPreview = false; - - for (const richText of message.content.richText) { - for (const element of richText.children) { - let text = ''; - if (element.text) { - text = element.text; - } - - if (element.bold) { - text = `*${text}*`; - } - - if (element.italic) { - text = `_${text}_`; - } - - if (element.underline) { - text = `*${text}*`; - } - - if (element.url) { - const linkText = element.children[0].text; - text = `[${linkText}](${element.url})`; - linkPreview = true; - } - - formattedText += text; - } - formattedText += '\n'; - } - - formattedText = formattedText.replace(/\n$/, ''); - - await instance.textMessage({ - number: remoteJid.split('@')[0], - options: { - delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, - presence: 'composing', - linkPreview: linkPreview, - }, - textMessage: { - text: formattedText, - }, - }); - } - - if (message.type === 'image') { - await instance.mediaMessage({ - number: remoteJid.split('@')[0], - options: { - delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, - presence: 'composing', - }, - mediaMessage: { - mediatype: 'image', - media: message.content.url, - }, - }); - } - - if (message.type === 'video') { - await instance.mediaMessage({ - number: remoteJid.split('@')[0], - options: { - delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, - presence: 'composing', - }, - mediaMessage: { - mediatype: 'video', - media: message.content.url, - }, - }); - } - - if (message.type === 'audio') { - await instance.audioWhatsapp({ - number: remoteJid.split('@')[0], - options: { - delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, - presence: 'recording', - encoding: true, - }, - audioMessage: { - audio: message.content.url, - }, - }); - } - } - - if (input) { - if (input.type === 'choice input') { - let formattedText = ''; - - const items = input.items; - - for (const item of items) { - formattedText += `▶️ ${item.content}\n`; - } - - formattedText = formattedText.replace(/\n$/, ''); - - await instance.textMessage({ - number: remoteJid.split('@')[0], - options: { - delay: 1200, - presence: 'composing', - linkPreview: false, - }, - textMessage: { - text: formattedText, - }, - }); - } - } - } - } - - public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) { - 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); - - try { - if (session && expire && expire > 0) { - const now = Date.now(); - - const diff = now - session.updateAt; - - const diffInMinutes = Math.floor(diff / 1000 / 60); - - if (diffInMinutes > expire) { - const newSessions = await this.clearSessions(instance, remoteJid); - - const data = await this.createNewSession(instance, { - enabled: findTypebot.enabled, - url: url, - typebot: typebot, - expire: expire, - keyword_finish: keyword_finish, - delay_message: delay_message, - unknown_message: unknown_message, - listening_from_me: listening_from_me, - sessions: newSessions, - remoteJid: remoteJid, - pushName: msg.pushName, - }); - - await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); - - if (data.messages.length === 0) { - const content = this.getConversationMessage(msg.message); - - if (!content) { - if (unknown_message) { - this.waMonitor.waInstances[instance.instanceName].textMessage({ - number: remoteJid.split('@')[0], - options: { - delay: delay_message || 1000, - presence: 'composing', - }, - textMessage: { - text: unknown_message, - }, - }); - } - return; - } - - if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { - const newSessions = await this.clearSessions(instance, remoteJid); - - const typebotData = { - enabled: findTypebot.enabled, - url: url, - typebot: typebot, - expire: expire, - keyword_finish: keyword_finish, - delay_message: delay_message, - unknown_message: unknown_message, - listening_from_me: listening_from_me, - sessions: newSessions, - }; - - this.create(instance, typebotData); - - return; - } - - const reqData = { - message: content, - sessionId: data.sessionId, - }; - - try { - const version = this.configService.get('TYPEBOT').API_VERSION; - const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); - - await this.sendWAMessage( - instance, - remoteJid, - request.data.messages, - request.data.input, - request.data.clientSideActions, - ); - } catch (error) { - this.logger.error(error); - return; - } - } - - return; - } - } - - if (session && session.status !== 'opened') { - return; - } - - if (!session) { - const data = await this.createNewSession(instance, { - enabled: findTypebot.enabled, - url: url, - typebot: typebot, - expire: expire, - 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, - }); - - await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); - - if (data.messages.length === 0) { - const content = this.getConversationMessage(msg.message); - - if (!content) { - if (unknown_message) { - this.waMonitor.waInstances[instance.instanceName].textMessage({ - number: remoteJid.split('@')[0], - options: { - delay: delay_message || 1000, - presence: 'composing', - }, - textMessage: { - text: unknown_message, - }, - }); - } - return; - } - - if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { - sessions.splice(sessions.indexOf(session), 1); - - const typebotData = { - enabled: findTypebot.enabled, - url: url, - typebot: typebot, - expire: expire, - keyword_finish: keyword_finish, - delay_message: delay_message, - unknown_message: unknown_message, - listening_from_me: listening_from_me, - sessions, - }; - - this.create(instance, typebotData); - - return; - } - - const reqData = { - message: content, - sessionId: data.sessionId, - }; - - let request: any; - try { - const version = this.configService.get('TYPEBOT').API_VERSION; - request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); - - await this.sendWAMessage( - instance, - remoteJid, - request.data.messages, - request.data.input, - request.data.clientSideActions, - ); - } catch (error) { - this.logger.error(error); - return; - } - } - return; - } - - sessions.map((session) => { - if (session.remoteJid === remoteJid) { - session.updateAt = Date.now(); - } - }); - - const typebotData = { - enabled: findTypebot.enabled, - url: url, - typebot: typebot, - expire: expire, - keyword_finish: keyword_finish, - delay_message: delay_message, - unknown_message: unknown_message, - listening_from_me: listening_from_me, - sessions, - }; - - this.create(instance, typebotData); - - const content = this.getConversationMessage(msg.message); - - if (!content) { - if (unknown_message) { - this.waMonitor.waInstances[instance.instanceName].textMessage({ - number: remoteJid.split('@')[0], - options: { - delay: delay_message || 1000, - presence: 'composing', - }, - textMessage: { - text: unknown_message, - }, - }); - } - return; - } - - if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { - sessions.splice(sessions.indexOf(session), 1); - - const typebotData = { - enabled: findTypebot.enabled, - url: url, - typebot: typebot, - expire: expire, - keyword_finish: keyword_finish, - delay_message: delay_message, - unknown_message: unknown_message, - listening_from_me: listening_from_me, - sessions, - }; - - this.create(instance, typebotData); - - return; - } - - const reqData = { - message: content, - sessionId: session.sessionId.split('-')[1], - }; - - const version = this.configService.get('TYPEBOT').API_VERSION; - const request = await axios.post(`${url}/api/${version}/sendMessage`, reqData); - - await this.sendWAMessage( - instance, - remoteJid, - request.data.messages, - request.data.input, - request.data.clientSideActions, - ); - - return; - } catch (error) { - this.logger.error(error); - return; - } - } -} +import axios from 'axios'; + +import { ConfigService, Typebot } from '../../config/env.config'; +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 { + //constructor(private readonly waMonitor: WAMonitoringService) {} + constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) { } + + private readonly logger = new Logger(TypebotService.name); + + public create(instance: InstanceDto, data: TypebotDto) { + this.logger.verbose('create typebot: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setTypebot(data); + + return { typebot: { ...instance, typebot: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find typebot: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findTypebot(); + + if (Object.keys(result).length === 0) { + throw new Error('Typebot not found'); + } + + return result; + } catch (error) { + return { enabled: false, url: '', typebot: '', expire: 0, sessions: [] }; + } + } + + public async changeStatus(instance: InstanceDto, data: any) { + const remoteJid = data.remoteJid; + const status = data.status; + + const findData = await this.find(instance); + + const session = findData.sessions.find((session) => session.remoteJid === remoteJid); + + if (session) { + if (status === 'closed') { + findData.sessions.splice(findData.sessions.indexOf(session), 1); + + const typebotData = { + enabled: findData.enabled, + url: findData.url, + typebot: findData.typebot, + expire: findData.expire, + 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); + + return { typebot: { ...instance, typebot: typebotData } }; + } + + findData.sessions.map((session) => { + if (session.remoteJid === remoteJid) { + session.status = status; + } + }); + } else { + const session: Session = { + remoteJid: remoteJid, + sessionId: Math.floor(Math.random() * 10000000000).toString(), + status: status, + createdAt: Date.now(), + updateAt: Date.now(), + prefilledVariables: { + remoteJid: remoteJid, + pushName: '', + additionalData: {}, + }, + }; + findData.sessions.push(session); + } + + const typebotData = { + enabled: findData.enabled, + url: findData.url, + typebot: findData.typebot, + expire: findData.expire, + 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 } }; + } + + public async startTypebot(instance: InstanceDto, data: any) { + if (data.remoteJid === 'status@broadcast') return; + + const remoteJid = data.remoteJid; + const url = data.url; + const typebot = data.typebot; + const startSession = data.startSession; + const variables = data.variables; + const findTypebot = await this.find(instance); + 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 prefilledVariables = { + remoteJid: remoteJid, + instanceName: instance.instanceName, + }; + + if (variables?.length) { + variables.forEach((variable: { name: string | number; value: string }) => { + prefilledVariables[variable.name] = variable.value; + }); + } + + if (startSession) { + const newSessions = await this.clearSessions(instance, remoteJid); + + const response = await this.createNewSession(instance, { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + remoteJid: remoteJid, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions: newSessions, + prefilledVariables: prefilledVariables, + }); + + if (response.sessionId) { + await this.sendWAMessage(instance, remoteJid, response.messages, response.input, response.clientSideActions); + + this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { + remoteJid: remoteJid, + url: url, + typebot: typebot, + prefilledVariables: prefilledVariables, + sessionId: `${response.sessionId}`, + }); + } else { + throw new Error('Session ID not found in response'); + } + } else { + const id = Math.floor(Math.random() * 10000000000).toString(); + + const reqData = { + startParams: { + typebot: data.typebot, + prefilledVariables: prefilledVariables, + }, + }; + + try { + //const request = await axios.post(data.url + '/api/v1/sendMessage', reqData); + + const version = this.configService.get('TYPEBOT').API_VERSION; + const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + + this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_START, { + remoteJid: remoteJid, + url: url, + typebot: typebot, + variables: variables, + sessionId: id, + }); + } catch (error) { + this.logger.error(error); + return; + } + } + + return { + typebot: { + ...instance, + typebot: { + url: url, + remoteJid: remoteJid, + typebot: typebot, + prefilledVariables: prefilledVariables, + }, + }, + }; + } + + 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; + } + + public async createNewSession(instance: InstanceDto, data: any) { + if (data.remoteJid === 'status@broadcast') return; + const id = Math.floor(Math.random() * 10000000000).toString(); + + const reqData = { + startParams: { + typebot: data.typebot, + prefilledVariables: { + ...data.prefilledVariables, + remoteJid: data.remoteJid, + pushName: data.pushName || '', + instanceName: instance.instanceName, + }, + }, + }; + + try { + //const request = await axios.post(data.url + '/api/v1/sendMessage', reqData); + + const version = this.configService.get('TYPEBOT').API_VERSION; + const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); + + if (request?.data?.sessionId) { + data.sessions.push({ + remoteJid: data.remoteJid, + sessionId: `${id}-${request.data.sessionId}`, + status: 'opened', + createdAt: Date.now(), + updateAt: Date.now(), + prefilledVariables: { + ...data.prefilledVariables, + remoteJid: data.remoteJid, + pushName: data.pushName || '', + instanceName: instance.instanceName, + }, + }); + + const typebotData = { + enabled: data.enabled, + url: data.url, + typebot: data.typebot, + expire: data.expire, + 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, + }; + + this.create(instance, typebotData); + } + return request.data; + } catch (error) { + this.logger.error(error); + return; + } + } + + public async clearSessions(instance: InstanceDto, remoteJid: string) { + const findTypebot = await this.find(instance); + const sessions = (findTypebot.sessions as Session[]) ?? []; + + const sessionWithRemoteJid = sessions.filter((session) => session.remoteJid === remoteJid); + + if (sessionWithRemoteJid.length > 0) { + sessionWithRemoteJid.forEach((session) => { + sessions.splice(sessions.indexOf(session), 1); + }); + + const typebotData = { + enabled: findTypebot.enabled, + url: findTypebot.url, + typebot: findTypebot.typebot, + expire: findTypebot.expire, + keyword_finish: findTypebot.keyword_finish, + delay_message: findTypebot.delay_message, + unknown_message: findTypebot.unknown_message, + listening_from_me: findTypebot.listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return sessions; + } + + return sessions; + } + + public async sendWAMessage( + instance: InstanceDto, + remoteJid: string, + messages: any[], + input: any[], + clientSideActions: any[], + ) { + processMessages(this.waMonitor.waInstances[instance.instanceName], messages, input, clientSideActions).catch( + (err) => { + console.error('Erro ao processar mensagens:', err); + }, + ); + + function findItemAndGetSecondsToWait(array, targetId) { + if (!array) return null; + + for (const item of array) { + if (item.lastBubbleBlockId === targetId) { + return item.wait?.secondsToWaitFor; + } + } + return null; + } + + async function processMessages(instance, messages, input, clientSideActions) { + for (const message of messages) { + const wait = findItemAndGetSecondsToWait(clientSideActions, message.id); + + if (message.type === 'text') { + let formattedText = ''; + + let linkPreview = false; + + for (const richText of message.content.richText) { + for (const element of richText.children) { + let text = ''; + if (element.text) { + text = element.text; + } + + if (element.bold) { + text = `*${text}*`; + } + + if (element.italic) { + text = `_${text}_`; + } + + if (element.underline) { + text = `~${text}~`; + } + + if (element.url) { + const linkText = element.children[0].text; + text = `[${linkText}](${element.url})`; + linkPreview = true; + } + + formattedText += text; + } + formattedText += '\n'; + } + + formattedText = formattedText.replace(/\n$/, ''); + + await instance.textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, + presence: 'composing', + linkPreview: linkPreview, + }, + textMessage: { + text: formattedText, + }, + }); + } + + if (message.type === 'image') { + await instance.mediaMessage({ + number: remoteJid.split('@')[0], + options: { + delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, + presence: 'composing', + }, + mediaMessage: { + mediatype: 'image', + media: message.content.url, + }, + }); + } + + if (message.type === 'video') { + await instance.mediaMessage({ + number: remoteJid.split('@')[0], + options: { + delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, + presence: 'composing', + }, + mediaMessage: { + mediatype: 'video', + media: message.content.url, + }, + }); + } + + if (message.type === 'audio') { + await instance.audioWhatsapp({ + number: remoteJid.split('@')[0], + options: { + delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000, + presence: 'recording', + encoding: true, + }, + audioMessage: { + audio: message.content.url, + }, + }); + } + } + + if (input) { + if (input.type === 'choice input') { + let formattedText = ''; + + const items = input.items; + + for (const item of items) { + formattedText += `▶️ ${item.content}\n`; + } + + formattedText = formattedText.replace(/\n$/, ''); + + await instance.textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: 1200, + presence: 'composing', + linkPreview: false, + }, + textMessage: { + text: formattedText, + }, + }); + } + } + } + } + + public async sendTypebot(instance: InstanceDto, remoteJid: string, msg: MessageRaw) { + 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); + + try { + if (session && expire && expire > 0) { + const now = Date.now(); + + const diff = now - session.updateAt; + + const diffInMinutes = Math.floor(diff / 1000 / 60); + + if (diffInMinutes > expire) { + const newSessions = await this.clearSessions(instance, remoteJid); + + const data = await this.createNewSession(instance, { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions: newSessions, + remoteJid: remoteJid, + pushName: msg.pushName, + }); + + await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); + + if (data.messages.length === 0) { + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + const newSessions = await this.clearSessions(instance, remoteJid); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions: newSessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: data.sessionId, + }; + + try { + //const request = await axios.post(url + '/api/v1/sendMessage', reqData); + const version = this.configService.get('TYPEBOT').API_VERSION; + const request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + } catch (error) { + this.logger.error(error); + return; + } + } + + return; + } + } + + if (session && session.status !== 'opened') { + return; + } + + if (!session) { + const data = await this.createNewSession(instance, { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + 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, + }); + + await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); + + if (data.messages.length === 0) { + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: data.sessionId, + }; + + let request: any; + try { + //request = await axios.post(url + '/api/v1/sendMessage', reqData); + const version = this.configService.get('TYPEBOT').API_VERSION; + request = await axios.post(`${data.url}/api/${version}/sendMessage`, reqData); + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + } catch (error) { + this.logger.error(error); + return; + } + } + return; + } + + sessions.map((session) => { + if (session.remoteJid === remoteJid) { + session.updateAt = Date.now(); + } + }); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: session.sessionId.split('-')[1], + }; + + //const request = await axios.post(url + '/api/v1/sendMessage', reqData); + const version = this.configService.get('TYPEBOT').API_VERSION; + const request = await axios.post(`${url}/api/${version}/sendMessage`, reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + + return; + } catch (error) { + this.logger.error(error); + return; + } + + /* + if (session && expire && expire > 0) { + const now = Date.now(); + + const diff = now - session.updateAt; + + const diffInMinutes = Math.floor(diff / 1000 / 60); + + if (diffInMinutes > expire) { + //sessions.splice(sessions.indexOf(session), 1); + const newSessions = await this.clearSessions(instance, remoteJid); + + const data = await this.createNewSession(instance, { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + //sessions: sessions, + sessions: newSessions, + remoteJid: remoteJid, + pushName: msg.pushName, + }); + + await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); + + if (data.messages.length === 0) { + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: data.sessionId, + }; + + const request = await axios.post(url + '/api/v1/sendMessage', reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + } + + return; + } + } + + + if (session && session.status !== 'opened') { + return; + } + + if (!session) { + const data = await this.createNewSession(instance, { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + 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, + }); + + await this.sendWAMessage(instance, remoteJid, data.messages, data.input, data.clientSideActions); + + if (data.messages.length === 0) { + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: data.sessionId, + }; + + const request = await axios.post(url + '/api/v1/sendMessage', reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + } + return; + } + + sessions.map((session) => { + if (session.remoteJid === remoteJid) { + session.updateAt = Date.now(); + } + }); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + const content = this.getConversationMessage(msg.message); + + if (!content) { + if (unknown_message) { + this.waMonitor.waInstances[instance.instanceName].textMessage({ + number: remoteJid.split('@')[0], + options: { + delay: delay_message || 1000, + presence: 'composing', + }, + textMessage: { + text: unknown_message, + }, + }); + } + return; + } + + if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) { + sessions.splice(sessions.indexOf(session), 1); + + const typebotData = { + enabled: findTypebot.enabled, + url: url, + typebot: typebot, + expire: expire, + keyword_finish: keyword_finish, + delay_message: delay_message, + unknown_message: unknown_message, + listening_from_me: listening_from_me, + sessions, + }; + + this.create(instance, typebotData); + + return; + } + + const reqData = { + message: content, + sessionId: session.sessionId.split('-')[1], + }; + + const request = await axios.post(url + '/api/v1/sendMessage', reqData); + + await this.sendWAMessage( + instance, + remoteJid, + request.data.messages, + request.data.input, + request.data.clientSideActions, + ); + + return; + + */ + } +} diff --git a/src/whatsapp/services/webhook.service.ts b/src/whatsapp/services/webhook.service.ts old mode 100644 new mode 100755 index 810ce96d..3b66f33b --- a/src/whatsapp/services/webhook.service.ts +++ b/src/whatsapp/services/webhook.service.ts @@ -1,32 +1,32 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { WebhookDto } from '../dto/webhook.dto'; -import { WAMonitoringService } from './monitor.service'; - -export class WebhookService { - constructor(private readonly waMonitor: WAMonitoringService) {} - - private readonly logger = new Logger(WebhookService.name); - - public create(instance: InstanceDto, data: WebhookDto) { - this.logger.verbose('create webhook: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setWebhook(data); - - return { webhook: { ...instance, webhook: data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - this.logger.verbose('find webhook: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[instance.instanceName].findWebhook(); - - if (Object.keys(result).length === 0) { - throw new Error('Webhook not found'); - } - - return result; - } catch (error) { - return { enabled: false, url: '', events: [], webhook_by_events: false, webhook_base64: false }; - } - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { WebhookDto } from '../dto/webhook.dto'; +import { WAMonitoringService } from './monitor.service'; + +export class WebhookService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(WebhookService.name); + + public create(instance: InstanceDto, data: WebhookDto) { + this.logger.verbose('create webhook: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setWebhook(data); + + return { webhook: { ...instance, webhook: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find webhook: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findWebhook(); + + if (Object.keys(result).length === 0) { + throw new Error('Webhook not found'); + } + + return result; + } catch (error) { + return { enabled: false, url: '', events: [], webhook_by_events: false, webhook_base64: false }; + } + } +} diff --git a/src/whatsapp/services/websocket.service.ts b/src/whatsapp/services/websocket.service.ts old mode 100644 new mode 100755 index 20663bbf..a85d2eeb --- a/src/whatsapp/services/websocket.service.ts +++ b/src/whatsapp/services/websocket.service.ts @@ -1,33 +1,33 @@ -import { Logger } from '../../config/logger.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { WebsocketDto } from '../dto/websocket.dto'; -import { WebsocketRaw } from '../models'; -import { WAMonitoringService } from './monitor.service'; - -export class WebsocketService { - constructor(private readonly waMonitor: WAMonitoringService) {} - - private readonly logger = new Logger(WebsocketService.name); - - public create(instance: InstanceDto, data: WebsocketDto) { - this.logger.verbose('create websocket: ' + instance.instanceName); - this.waMonitor.waInstances[instance.instanceName].setWebsocket(data); - - return { websocket: { ...instance, websocket: data } }; - } - - public async find(instance: InstanceDto): Promise { - try { - this.logger.verbose('find websocket: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[instance.instanceName].findWebsocket(); - - if (Object.keys(result).length === 0) { - throw new Error('Websocket not found'); - } - - return result; - } catch (error) { - return { enabled: false, events: [] }; - } - } -} +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { WebsocketDto } from '../dto/websocket.dto'; +import { WebsocketRaw } from '../models'; +import { WAMonitoringService } from './monitor.service'; + +export class WebsocketService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(WebsocketService.name); + + public create(instance: InstanceDto, data: WebsocketDto) { + this.logger.verbose('create websocket: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setWebsocket(data); + + return { websocket: { ...instance, websocket: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find websocket: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findWebsocket(); + + if (Object.keys(result).length === 0) { + throw new Error('Websocket not found'); + } + + return result; + } catch (error) { + return { enabled: false, events: [] }; + } + } +} diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts old mode 100644 new mode 100755 index 630c6a27..6131b921 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1,3592 +1,3841 @@ -import ffmpegPath from '@ffmpeg-installer/ffmpeg'; -import { Boom } from '@hapi/boom'; -import makeWASocket, { - AnyMessageContent, - BufferedEventData, - BufferJSON, - CacheStore, - Chat, - ConnectionState, - Contact, - delay, - DisconnectReason, - downloadMediaMessage, - fetchLatestBaileysVersion, - generateWAMessageFromContent, - getAggregateVotesInPollMessage, - getContentType, - getDevice, - GroupMetadata, - isJidGroup, - isJidUser, - makeCacheableSignalKeyStore, - MessageUpsertType, - MiscMessageGenerationOptions, - ParticipantAction, - prepareWAMessageMedia, - proto, - useMultiFileAuthState, - UserFacingSocketConfig, - WABrowserDescription, - WAMediaUpload, - WAMessage, - WAMessageUpdate, - WASocket, -} from '@whiskeysockets/baileys'; -import axios from 'axios'; -import { exec, execSync } from 'child_process'; -import { arrayUnique, isBase64, isURL } from 'class-validator'; -import EventEmitter2 from 'eventemitter2'; -import fs, { existsSync, readFileSync } from 'fs'; -import Long from 'long'; -import NodeCache from 'node-cache'; -import { getMIMEType } from 'node-mime-types'; -import { release } from 'os'; -import { join } from 'path'; -import P from 'pino'; -import { ProxyAgent } from 'proxy-agent'; -import qrcode, { QRCodeToDataURLOptions } from 'qrcode'; -import qrcodeTerminal from 'qrcode-terminal'; -import sharp from 'sharp'; -import { v4 } from 'uuid'; - -import { - Auth, - CleanStoreConf, - ConfigService, - ConfigSessionPhone, - Database, - HttpServer, - Log, - QrCode, - Redis, - Sqs, - Webhook, - Websocket, -} from '../../config/env.config'; -import { Logger } from '../../config/logger.config'; -import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config'; -import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../exceptions'; -import { getAMQP, removeQueues } from '../../libs/amqp.server'; -import { dbserver } from '../../libs/db.connect'; -import { RedisCache } from '../../libs/redis.client'; -import { getIO } from '../../libs/socket.server'; -import { getSQS, removeQueues as removeQueuesSQS } from '../../libs/sqs.server'; -import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db'; -import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; -import { - ArchiveChatDto, - DeleteMessage, - getBase64FromMediaMessageDto, - LastMessage, - NumberBusiness, - OnWhatsAppDto, - PrivacySettingDto, - ReadMessageDto, - WhatsAppNumberDto, -} from '../dto/chat.dto'; -import { - CreateGroupDto, - GetParticipant, - GroupDescriptionDto, - GroupInvite, - GroupJid, - GroupPictureDto, - GroupSendInvite, - GroupSubjectDto, - GroupToggleEphemeralDto, - GroupUpdateParticipantDto, - GroupUpdateSettingDto, -} from '../dto/group.dto'; -import { - ContactMessage, - MediaMessage, - Options, - SendAudioDto, - SendButtonDto, - SendContactDto, - SendListDto, - SendLocationDto, - SendMediaDto, - SendPollDto, - SendReactionDto, - SendStatusDto, - SendStickerDto, - SendTextDto, - StatusMessage, -} from '../dto/sendMessage.dto'; -import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, SettingsRaw, SqsRaw, TypebotRaw } from '../models'; -import { ChatRaw } from '../models/chat.model'; -import { ChatwootRaw } from '../models/chatwoot.model'; -import { ContactRaw } from '../models/contact.model'; -import { MessageRaw, MessageUpdateRaw } from '../models/message.model'; -import { WebhookRaw } from '../models/webhook.model'; -import { WebsocketRaw } from '../models/websocket.model'; -import { ContactQuery } from '../repository/contact.repository'; -import { MessageQuery } from '../repository/message.repository'; -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 { TypebotService } from './typebot.service'; -export class WAStartupService { - constructor( - private readonly configService: ConfigService, - private readonly eventEmitter: EventEmitter2, - private readonly repository: RepositoryBroker, - private readonly cache: RedisCache, - ) { - this.logger.verbose('WAStartupService initialized'); - this.cleanStore(); - this.instance.qrcode = { count: 0 }; - } - - private readonly logger = new Logger(WAStartupService.name); - public readonly instance: wa.Instance = {}; - public client: WASocket; - private readonly localWebhook: wa.LocalWebHook = {}; - private readonly localChatwoot: wa.LocalChatwoot = {}; - private readonly localSettings: wa.LocalSettings = {}; - private readonly localWebsocket: wa.LocalWebsocket = {}; - private readonly localRabbitmq: wa.LocalRabbitmq = {}; - private readonly localSqs: wa.LocalSqs = {}; - 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(); - private readonly userDevicesCache: CacheStore = new NodeCache(); - private endSession = false; - private logBaileys = this.configService.get('LOG').BAILEYS; - - private phoneNumber: string; - - private chatwootService = new ChatwootService(waMonitor, this.configService); - - private typebotService = new TypebotService(waMonitor, this.configService); - - private chamaaiService = new ChamaaiService(waMonitor, this.configService); - - public set instanceName(name: string) { - this.logger.verbose(`Initializing instance '${name}'`); - if (!name) { - this.logger.verbose('Instance name not found, generating random name with uuid'); - this.instance.name = v4(); - return; - } - this.instance.name = name; - this.logger.verbose(`Instance '${this.instance.name}' initialized`); - this.logger.verbose('Sending instance status to webhook'); - this.sendDataWebhook(Events.STATUS_INSTANCE, { - 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() { - this.logger.verbose('Getting instance name'); - return this.instance.name; - } - - public get wuid() { - this.logger.verbose('Getting remoteJid of instance'); - return this.instance.wuid; - } - - public async getProfileName() { - this.logger.verbose('Getting profile name'); - let profileName = this.client.user?.name ?? this.client.user?.verifiedName; - if (!profileName) { - this.logger.verbose('Profile name not found, trying to get from database'); - if (this.configService.get('DATABASE').ENABLED) { - this.logger.verbose('Database enabled, trying to get from database'); - const collection = dbserver - .getClient() - .db(this.configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances') - .collection(this.instanceName); - const data = await collection.findOne({ _id: 'creds' }); - if (data) { - this.logger.verbose('Profile name found in database'); - const creds = JSON.parse(JSON.stringify(data), BufferJSON.reviver); - profileName = creds.me?.name || creds.me?.verifiedName; - } - } else if (existsSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'))) { - this.logger.verbose('Profile name found in file'); - const creds = JSON.parse( - readFileSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'), { - encoding: 'utf-8', - }), - ); - profileName = creds.me?.name || creds.me?.verifiedName; - } - } - - this.logger.verbose(`Profile name: ${profileName}`); - return profileName; - } - - public async getProfileStatus() { - this.logger.verbose('Getting profile status'); - const status = await this.client.fetchStatus(this.instance.wuid); - - this.logger.verbose(`Profile status: ${status.status}`); - return status.status; - } - - public get profilePictureUrl() { - this.logger.verbose('Getting profile picture url'); - return this.instance.profilePictureUrl; - } - - public get qrCode(): wa.QrCode { - this.logger.verbose('Getting qrcode'); - - return { - pairingCode: this.instance.qrcode?.pairingCode, - code: this.instance.qrcode?.code, - base64: this.instance.qrcode?.base64, - }; - } - - private async loadWebhook() { - this.logger.verbose('Loading webhook'); - const data = await this.repository.webhook.find(this.instanceName); - this.localWebhook.url = data?.url; - this.logger.verbose(`Webhook url: ${this.localWebhook.url}`); - - this.localWebhook.enabled = data?.enabled; - this.logger.verbose(`Webhook enabled: ${this.localWebhook.enabled}`); - - this.localWebhook.events = data?.events; - this.logger.verbose(`Webhook events: ${this.localWebhook.events}`); - - this.localWebhook.webhook_by_events = data?.webhook_by_events; - this.logger.verbose(`Webhook by events: ${this.localWebhook.webhook_by_events}`); - - this.localWebhook.webhook_base64 = data?.webhook_base64; - this.logger.verbose(`Webhook by webhook_base64: ${this.localWebhook.webhook_base64}`); - - this.logger.verbose('Webhook loaded'); - } - - public async setWebhook(data: WebhookRaw) { - this.logger.verbose('Setting webhook'); - await this.repository.webhook.create(data, this.instanceName); - this.logger.verbose(`Webhook url: ${data.url}`); - this.logger.verbose(`Webhook events: ${data.events}`); - Object.assign(this.localWebhook, data); - this.logger.verbose('Webhook set'); - } - - public async findWebhook() { - this.logger.verbose('Finding webhook'); - const data = await this.repository.webhook.find(this.instanceName); - - if (!data) { - this.logger.verbose('Webhook not found'); - throw new NotFoundException('Webhook not found'); - } - - this.logger.verbose(`Webhook url: ${data.url}`); - this.logger.verbose(`Webhook events: ${data.events}`); - 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.localChatwoot.name_inbox = data?.name_inbox; - this.logger.verbose(`Chatwoot inbox name: ${this.localChatwoot.name_inbox}`); - - this.localChatwoot.sign_msg = data?.sign_msg; - this.logger.verbose(`Chatwoot sign msg: ${this.localChatwoot.sign_msg}`); - - this.localChatwoot.number = data?.number; - this.logger.verbose(`Chatwoot number: ${this.localChatwoot.number}`); - - this.localChatwoot.reopen_conversation = data?.reopen_conversation; - this.logger.verbose(`Chatwoot reopen conversation: ${this.localChatwoot.reopen_conversation}`); - - this.localChatwoot.conversation_pending = data?.conversation_pending; - this.logger.verbose(`Chatwoot conversation pending: ${this.localChatwoot.conversation_pending}`); - - 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}`); - this.logger.verbose(`Chatwoot sign msg: ${data.sign_msg}`); - this.logger.verbose(`Chatwoot reopen conversation: ${data.reopen_conversation}`); - this.logger.verbose(`Chatwoot conversation pending: ${data.conversation_pending}`); - - 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'); - return null; - } - - 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}`); - this.logger.verbose(`Chatwoot sign msg: ${data.sign_msg}`); - this.logger.verbose(`Chatwoot reopen conversation: ${data.reopen_conversation}`); - this.logger.verbose(`Chatwoot conversation pending: ${data.conversation_pending}`); - - return data; - } - - private async loadSettings() { - this.logger.verbose('Loading settings'); - const data = await this.repository.settings.find(this.instanceName); - this.localSettings.reject_call = data?.reject_call; - this.logger.verbose(`Settings reject_call: ${this.localSettings.reject_call}`); - - this.localSettings.msg_call = data?.msg_call; - this.logger.verbose(`Settings msg_call: ${this.localSettings.msg_call}`); - - this.localSettings.groups_ignore = data?.groups_ignore; - this.logger.verbose(`Settings groups_ignore: ${this.localSettings.groups_ignore}`); - - this.localSettings.always_online = data?.always_online; - this.logger.verbose(`Settings always_online: ${this.localSettings.always_online}`); - - this.localSettings.read_messages = data?.read_messages; - this.logger.verbose(`Settings read_messages: ${this.localSettings.read_messages}`); - - this.localSettings.read_status = data?.read_status; - this.logger.verbose(`Settings read_status: ${this.localSettings.read_status}`); - - this.logger.verbose('Settings loaded'); - } - - public async setSettings(data: SettingsRaw) { - this.logger.verbose('Setting settings'); - await this.repository.settings.create(data, this.instanceName); - this.logger.verbose(`Settings reject_call: ${data.reject_call}`); - this.logger.verbose(`Settings msg_call: ${data.msg_call}`); - this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); - this.logger.verbose(`Settings always_online: ${data.always_online}`); - this.logger.verbose(`Settings read_messages: ${data.read_messages}`); - this.logger.verbose(`Settings read_status: ${data.read_status}`); - Object.assign(this.localSettings, data); - this.logger.verbose('Settings set'); - - this.client?.ws?.close(); - } - - public async findSettings() { - this.logger.verbose('Finding settings'); - const data = await this.repository.settings.find(this.instanceName); - - if (!data) { - this.logger.verbose('Settings not found'); - return null; - } - - this.logger.verbose(`Settings url: ${data.reject_call}`); - this.logger.verbose(`Settings msg_call: ${data.msg_call}`); - this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); - this.logger.verbose(`Settings always_online: ${data.always_online}`); - this.logger.verbose(`Settings read_messages: ${data.read_messages}`); - this.logger.verbose(`Settings read_status: ${data.read_status}`); - return data; - } - - private async loadWebsocket() { - this.logger.verbose('Loading websocket'); - const data = await this.repository.websocket.find(this.instanceName); - - this.localWebsocket.enabled = data?.enabled; - this.logger.verbose(`Websocket enabled: ${this.localWebsocket.enabled}`); - - this.localWebsocket.events = data?.events; - this.logger.verbose(`Websocket events: ${this.localWebsocket.events}`); - - this.logger.verbose('Websocket loaded'); - } - - public async setWebsocket(data: WebsocketRaw) { - this.logger.verbose('Setting websocket'); - await this.repository.websocket.create(data, this.instanceName); - this.logger.verbose(`Websocket events: ${data.events}`); - Object.assign(this.localWebsocket, data); - this.logger.verbose('Websocket set'); - } - - public async findWebsocket() { - this.logger.verbose('Finding websocket'); - const data = await this.repository.websocket.find(this.instanceName); - - if (!data) { - this.logger.verbose('Websocket not found'); - throw new NotFoundException('Websocket not found'); - } - - this.logger.verbose(`Websocket events: ${data.events}`); - return data; - } - - private async loadRabbitmq() { - this.logger.verbose('Loading rabbitmq'); - const data = await this.repository.rabbitmq.find(this.instanceName); - - this.localRabbitmq.enabled = data?.enabled; - this.logger.verbose(`Rabbitmq enabled: ${this.localRabbitmq.enabled}`); - - this.localRabbitmq.events = data?.events; - this.logger.verbose(`Rabbitmq events: ${this.localRabbitmq.events}`); - - this.logger.verbose('Rabbitmq loaded'); - } - - public async setRabbitmq(data: RabbitmqRaw) { - this.logger.verbose('Setting rabbitmq'); - await this.repository.rabbitmq.create(data, this.instanceName); - this.logger.verbose(`Rabbitmq events: ${data.events}`); - Object.assign(this.localRabbitmq, data); - this.logger.verbose('Rabbitmq set'); - } - - public async findRabbitmq() { - this.logger.verbose('Finding rabbitmq'); - const data = await this.repository.rabbitmq.find(this.instanceName); - - if (!data) { - this.logger.verbose('Rabbitmq not found'); - throw new NotFoundException('Rabbitmq not found'); - } - - this.logger.verbose(`Rabbitmq events: ${data.events}`); - return data; - } - - public async removeRabbitmqQueues() { - this.logger.verbose('Removing rabbitmq'); - - if (this.localRabbitmq.enabled) { - removeQueues(this.instanceName, this.localRabbitmq.events); - } - } - - private async loadSqs() { - this.logger.verbose('Loading sqs'); - const data = await this.repository.sqs.find(this.instanceName); - - this.localSqs.enabled = data?.enabled; - this.logger.verbose(`Sqs enabled: ${this.localSqs.enabled}`); - - this.localSqs.events = data?.events; - this.logger.verbose(`Sqs events: ${this.localSqs.events}`); - - this.logger.verbose('Sqs loaded'); - } - - public async setSqs(data: SqsRaw) { - this.logger.verbose('Setting sqs'); - await this.repository.sqs.create(data, this.instanceName); - this.logger.verbose(`Sqs events: ${data.events}`); - Object.assign(this.localSqs, data); - this.logger.verbose('Sqs set'); - } - - public async findSqs() { - this.logger.verbose('Finding sqs'); - const data = await this.repository.sqs.find(this.instanceName); - - if (!data) { - this.logger.verbose('Sqs not found'); - throw new NotFoundException('Sqs not found'); - } - - this.logger.verbose(`Sqs events: ${data.events}`); - return data; - } - - public async removeSqsQueues() { - this.logger.verbose('Removing sqs'); - - if (this.localSqs.enabled) { - removeQueuesSQS(this.instanceName, this.localSqs.events); - } - } - - private async loadTypebot() { - this.logger.verbose('Loading typebot'); - const data = await this.repository.typebot.find(this.instanceName); - - this.localTypebot.enabled = data?.enabled; - this.logger.verbose(`Typebot enabled: ${this.localTypebot.enabled}`); - - this.localTypebot.url = data?.url; - this.logger.verbose(`Typebot url: ${this.localTypebot.url}`); - - this.localTypebot.typebot = data?.typebot; - this.logger.verbose(`Typebot typebot: ${this.localTypebot.typebot}`); - - this.localTypebot.expire = data?.expire; - this.logger.verbose(`Typebot expire: ${this.localTypebot.expire}`); - - this.localTypebot.keyword_finish = data?.keyword_finish; - this.logger.verbose(`Typebot keyword_finish: ${this.localTypebot.keyword_finish}`); - - this.localTypebot.delay_message = data?.delay_message; - this.logger.verbose(`Typebot delay_message: ${this.localTypebot.delay_message}`); - - 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'); - } - - public async setTypebot(data: TypebotRaw) { - this.logger.verbose('Setting typebot'); - await this.repository.typebot.create(data, this.instanceName); - this.logger.verbose(`Typebot typebot: ${data.typebot}`); - this.logger.verbose(`Typebot expire: ${data.expire}`); - 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'); - } - - public async findTypebot() { - this.logger.verbose('Finding typebot'); - const data = await this.repository.typebot.find(this.instanceName); - - if (!data) { - this.logger.verbose('Typebot not found'); - throw new NotFoundException('Typebot not found'); - } - - return data; - } - - private async loadProxy() { - this.logger.verbose('Loading proxy'); - const data = await this.repository.proxy.find(this.instanceName); - - this.localProxy.enabled = data?.enabled; - this.logger.verbose(`Proxy enabled: ${this.localProxy.enabled}`); - - this.localProxy.proxy = data?.proxy; - this.logger.verbose(`Proxy proxy: ${this.localProxy.proxy}`); - - this.logger.verbose('Proxy loaded'); - } - - public async setProxy(data: ProxyRaw, reload = true) { - this.logger.verbose('Setting proxy'); - await this.repository.proxy.create(data, this.instanceName); - this.logger.verbose(`Proxy proxy: ${data.proxy}`); - Object.assign(this.localProxy, data); - this.logger.verbose('Proxy set'); - - if (reload) { - this.reloadConnection(); - } - } - - public async findProxy() { - this.logger.verbose('Finding proxy'); - const data = await this.repository.proxy.find(this.instanceName); - - if (!data) { - this.logger.verbose('Proxy not found'); - throw new NotFoundException('Proxy not found'); - } - - 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(event: Events, data: T, local = true) { - const webhookGlobal = this.configService.get('WEBHOOK'); - const webhookLocal = this.localWebhook.events; - const websocketLocal = this.localWebsocket.events; - const rabbitmqLocal = this.localRabbitmq.events; - const sqsLocal = this.localSqs.events; - const serverUrl = this.configService.get('SERVER').URL; - const we = event.replace(/[.-]/gm, '_').toUpperCase(); - const transformedWe = we.replace(/_/gm, '-').toLowerCase(); - const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds - const localISOTime = new Date(Date.now() - tzoffset).toISOString(); - const now = localISOTime; - - const expose = this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES; - const tokenStore = await this.repository.auth.find(this.instanceName); - const instanceApikey = tokenStore?.apikey || 'Apikey not found'; - - if (this.localRabbitmq.enabled) { - const amqp = getAMQP(); - - if (amqp) { - if (Array.isArray(rabbitmqLocal) && rabbitmqLocal.includes(we)) { - const exchangeName = this.instanceName ?? 'evolution_exchange'; - - amqp.assertExchange(exchangeName, 'topic', { - durable: true, - autoDelete: false, - }); - - const queueName = `${this.instanceName}.${event}`; - - amqp.assertQueue(queueName, { - durable: true, - autoDelete: false, - arguments: { - 'x-queue-type': 'quorum', - }, - }); - - amqp.bindQueue(queueName, exchangeName, event); - - const message = { - event, - instance: this.instance.name, - data, - server_url: serverUrl, - date_time: now, - sender: this.wuid, - }; - - if (expose && instanceApikey) { - message['apikey'] = instanceApikey; - } - - amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message))); - - if (this.configService.get('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); - } - } - } - } - - if (this.localSqs.enabled) { - const sqs = getSQS(); - - if (sqs) { - if (Array.isArray(sqsLocal) && sqsLocal.includes(we)) { - const eventFormatted = `${event.replace('.', '_').toLowerCase()}`; - - const queueName = `${this.instanceName}_${eventFormatted}.fifo`; - - const sqsConfig = this.configService.get('SQS'); - - const sqsUrl = `https://sqs.${sqsConfig.REGION}.amazonaws.com/${sqsConfig.ACCOUNT_ID}/${queueName}`; - - const message = { - event, - instance: this.instance.name, - data, - server_url: serverUrl, - date_time: now, - sender: this.wuid, - }; - - if (expose && instanceApikey) { - message['apikey'] = instanceApikey; - } - - const params = { - MessageBody: JSON.stringify(message), - MessageGroupId: 'evolution', - MessageDeduplicationId: `${this.instanceName}_${eventFormatted}_${Date.now()}`, - QueueUrl: sqsUrl, - }; - - sqs.sendMessage(params, (err, data) => { - if (err) { - this.logger.error({ - local: WAStartupService.name + '.sendData-SQS', - message: err?.message, - hostName: err?.hostname, - code: err?.code, - stack: err?.stack, - name: err?.name, - url: queueName, - server_url: serverUrl, - }); - } else { - if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { - const logData = { - local: WAStartupService.name + '.sendData-SQS', - 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); - } - } - }); - } - } - } - - if (this.configService.get('WEBSOCKET')?.ENABLED && this.localWebsocket.enabled) { - this.logger.verbose('Sending data to websocket on channel: ' + this.instance.name); - if (Array.isArray(websocketLocal) && websocketLocal.includes(we)) { - this.logger.verbose('Sending data to websocket on event: ' + event); - const io = getIO(); - - const message = { - event, - instance: this.instance.name, - data, - server_url: serverUrl, - date_time: now, - sender: this.wuid, - }; - - if (expose && instanceApikey) { - message['apikey'] = instanceApikey; - } - - 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').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); - } - } - } - - const globalApiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; - - if (local) { - if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) { - this.logger.verbose('Sending data to webhook local'); - let baseURL: string; - - if (this.localWebhook.webhook_by_events) { - baseURL = `${this.localWebhook.url}/${transformedWe}`; - } else { - baseURL = this.localWebhook.url; - } - - if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { - const logData = { - local: WAStartupService.name + '.sendDataWebhook-local', - url: baseURL, - event, - instance: this.instance.name, - data, - destination: this.localWebhook.url, - date_time: now, - sender: this.wuid, - server_url: serverUrl, - apikey: (expose && instanceApikey) || null, - }; - - if (expose && instanceApikey) { - logData['apikey'] = instanceApikey; - } - - this.logger.log(logData); - } - - try { - if (this.localWebhook.enabled && isURL(this.localWebhook.url, { require_tld: false })) { - const httpService = axios.create({ baseURL }); - const postData = { - event, - instance: this.instance.name, - data, - destination: this.localWebhook.url, - date_time: now, - sender: this.wuid, - server_url: serverUrl, - }; - - if (expose && instanceApikey) { - postData['apikey'] = instanceApikey; - } - - await httpService.post('', postData); - } - } catch (error) { - this.logger.error({ - local: WAStartupService.name + '.sendDataWebhook-local', - message: error?.message, - hostName: error?.hostname, - syscall: error?.syscall, - code: error?.code, - error: error?.errno, - stack: error?.stack, - name: error?.name, - url: baseURL, - server_url: serverUrl, - }); - } - } - } - - if (webhookGlobal.GLOBAL?.ENABLED) { - if (webhookGlobal.EVENTS[we]) { - this.logger.verbose('Sending data to webhook global'); - const globalWebhook = this.configService.get('WEBHOOK').GLOBAL; - - let globalURL; - - if (webhookGlobal.GLOBAL.WEBHOOK_BY_EVENTS) { - globalURL = `${globalWebhook.URL}/${transformedWe}`; - } else { - globalURL = globalWebhook.URL; - } - - const localUrl = this.localWebhook.url; - - if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { - const logData = { - local: WAStartupService.name + '.sendDataWebhook-global', - url: globalURL, - event, - instance: this.instance.name, - data, - destination: localUrl, - date_time: now, - sender: this.wuid, - server_url: serverUrl, - }; - - if (expose && globalApiKey) { - logData['apikey'] = globalApiKey; - } - - this.logger.log(logData); - } - - try { - if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) { - const httpService = axios.create({ baseURL: globalURL }); - const postData = { - event, - instance: this.instance.name, - data, - destination: localUrl, - date_time: now, - sender: this.wuid, - server_url: serverUrl, - }; - - if (expose && globalApiKey) { - postData['apikey'] = globalApiKey; - } - - await httpService.post('', postData); - } - } catch (error) { - this.logger.error({ - local: WAStartupService.name + '.sendDataWebhook-global', - message: error?.message, - hostName: error?.hostname, - syscall: error?.syscall, - code: error?.code, - error: error?.errno, - stack: error?.stack, - name: error?.name, - url: globalURL, - server_url: serverUrl, - }); - } - } - } - } - - private async connectionUpdate({ qr, connection, lastDisconnect }: Partial) { - this.logger.verbose('Connection update'); - if (qr) { - this.logger.verbose('QR code found'); - if (this.instance.qrcode.count === this.configService.get('QRCODE').LIMIT) { - this.logger.verbose('QR code limit reached'); - - this.logger.verbose('Sending data to webhook in event QRCODE_UPDATED'); - this.sendDataWebhook(Events.QRCODE_UPDATED, { - message: 'QR code limit reached, please login again', - 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, - state: 'refused', - statusReason: DisconnectReason.connectionClosed, - }); - - this.logger.verbose('endSession defined as true'); - this.endSession = true; - - this.logger.verbose('Emmiting event logout.instance'); - return this.eventEmitter.emit('no.connection', this.instance.name); - } - - this.logger.verbose('Incrementing QR code count'); - this.instance.qrcode.count++; - - const color = this.configService.get('QRCODE').COLOR; - - const optsQrcode: QRCodeToDataURLOptions = { - margin: 3, - scale: 4, - errorCorrectionLevel: 'H', - color: { light: '#ffffff', dark: color }, - }; - - if (this.phoneNumber) { - await delay(2000); - this.instance.qrcode.pairingCode = await this.client.requestPairingCode(this.phoneNumber); - } else { - this.instance.qrcode.pairingCode = null; - } - - this.logger.verbose('Generating QR code'); - qrcode.toDataURL(qr, optsQrcode, (error, base64) => { - if (error) { - this.logger.error('Qrcode generate failed:' + error.toString()); - return; - } - - this.instance.qrcode.base64 = base64; - this.instance.qrcode.code = qr; - - this.sendDataWebhook(Events.QRCODE_UPDATED, { - qrcode: { - instance: this.instance.name, - pairingCode: this.instance.qrcode.pairingCode, - code: qr, - base64, - }, - }); - - if (this.localChatwoot.enabled) { - this.chatwootService.eventWhatsapp( - Events.QRCODE_UPDATED, - { instanceName: this.instance.name }, - { - qrcode: { - instance: this.instance.name, - pairingCode: this.instance.qrcode.pairingCode, - code: qr, - base64, - }, - }, - ); - } - }); - - this.logger.verbose('Generating QR code in terminal'); - qrcodeTerminal.generate(qr, { small: true }, (qrcode) => - this.logger.log( - `\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` + - qrcode, - ), - ); - } - - if (connection) { - this.logger.verbose('Connection found'); - this.stateConnection = { - state: connection, - statusReason: (lastDisconnect?.error as Boom)?.output?.statusCode ?? 200, - }; - - this.logger.verbose('Sending data to webhook in event CONNECTION_UPDATE'); - this.sendDataWebhook(Events.CONNECTION_UPDATE, { - instance: this.instance.name, - ...this.stateConnection, - }); - } - - if (connection === 'close') { - this.logger.verbose('Connection closed'); - const shouldReconnect = (lastDisconnect.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut; - if (shouldReconnect) { - this.logger.verbose('Reconnecting to whatsapp'); - await this.connectToWhatsapp(); - } else { - this.logger.verbose('Do not reconnect to whatsapp'); - this.logger.verbose('Sending data to webhook in event STATUS_INSTANCE'); - this.sendDataWebhook(Events.STATUS_INSTANCE, { - instance: this.instance.name, - status: 'closed', - }); - - if (this.localChatwoot.enabled) { - this.chatwootService.eventWhatsapp( - Events.STATUS_INSTANCE, - { instanceName: this.instance.name }, - { - instance: this.instance.name, - status: 'closed', - }, - ); - } - - this.logger.verbose('Emittin event logout.instance'); - this.eventEmitter.emit('logout.instance', this.instance.name, 'inner'); - this.client?.ws?.close(); - this.client.end(new Error('Close connection')); - this.logger.verbose('Connection closed'); - } - } - - if (connection === 'open') { - this.logger.verbose('Connection opened'); - this.instance.wuid = this.client.user.id.replace(/:\d+/, ''); - this.instance.profilePictureUrl = (await this.profilePicture(this.instance.wuid)).profilePictureUrl; - this.logger.info( - ` - ┌──────────────────────────────┐ - │ CONNECTED TO WHATSAPP │ - └──────────────────────────────┘`.replace(/^ +/gm, ' '), - ); - - if (this.localChatwoot.enabled) { - this.chatwootService.eventWhatsapp( - Events.CONNECTION_UPDATE, - { instanceName: this.instance.name }, - { - instance: this.instance.name, - status: 'open', - }, - ); - } - } - } - - private async getMessage(key: proto.IMessageKey, full = false) { - this.logger.verbose('Getting message with key: ' + JSON.stringify(key)); - try { - const webMessageInfo = (await this.repository.message.find({ - where: { owner: this.instance.name, key: { id: key.id } }, - })) as unknown as proto.IWebMessageInfo[]; - if (full) { - this.logger.verbose('Returning full message'); - return webMessageInfo[0]; - } - if (webMessageInfo[0].message?.pollCreationMessage) { - this.logger.verbose('Returning poll message'); - const messageSecretBase64 = webMessageInfo[0].message?.messageContextInfo?.messageSecret; - - if (typeof messageSecretBase64 === 'string') { - const messageSecret = Buffer.from(messageSecretBase64, 'base64'); - - const msg = { - messageContextInfo: { - messageSecret, - }, - pollCreationMessage: webMessageInfo[0].message?.pollCreationMessage, - }; - - return msg; - } - } - - this.logger.verbose('Returning message'); - return webMessageInfo[0].message; - } catch (error) { - return { conversation: '' }; - } - } - - private cleanStore() { - this.logger.verbose('Cronjob to clean store initialized'); - const cleanStore = this.configService.get('CLEAN_STORE'); - const database = this.configService.get('DATABASE'); - if (cleanStore?.CLEANING_INTERVAL && !database.ENABLED) { - this.logger.verbose('Cronjob to clean store enabled'); - setInterval(() => { - try { - for (const [key, value] of Object.entries(cleanStore)) { - if (value === true) { - execSync( - `rm -rf ${join(this.storePath, key.toLowerCase().replace('_', '-'), this.instance.name)}/*.json`, - ); - this.logger.verbose( - `Cleaned ${join(this.storePath, key.toLowerCase().replace('_', '-'), this.instance.name)}/*.json`, - ); - } - } - } catch (error) { - this.logger.error(error); - } - }, (cleanStore?.CLEANING_INTERVAL ?? 3600) * 1000); - } - } - - private async defineAuthState() { - this.logger.verbose('Defining auth state'); - const db = this.configService.get('DATABASE'); - const redis = this.configService.get('REDIS'); - - if (redis?.ENABLED) { - this.logger.verbose('Redis enabled'); - this.cache.reference = this.instance.name; - return await useMultiFileAuthStateRedisDb(this.cache); - } - - if (db.SAVE_DATA.INSTANCE && db.ENABLED) { - this.logger.verbose('Database enabled'); - return await useMultiFileAuthStateDb(this.instance.name); - } - - this.logger.verbose('Store file enabled'); - return await useMultiFileAuthState(join(INSTANCE_DIR, this.instance.name)); - } - - public async connectToWhatsapp(number?: string): Promise { - this.logger.verbose('Connecting to whatsapp'); - try { - this.loadWebhook(); - this.loadChatwoot(); - this.loadSettings(); - this.loadWebsocket(); - this.loadRabbitmq(); - this.loadSqs(); - this.loadTypebot(); - this.loadProxy(); - this.loadChamaai(); - - this.instance.authState = await this.defineAuthState(); - - const { version } = await fetchLatestBaileysVersion(); - this.logger.verbose('Baileys version: ' + version); - const session = this.configService.get('CONFIG_SESSION_PHONE'); - const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; - this.logger.verbose('Browser: ' + JSON.stringify(browser)); - - let options; - - if (this.localProxy.enabled) { - this.logger.info('Proxy enabled: ' + this.localProxy.proxy); - - if (this.localProxy.proxy.includes('proxyscrape')) { - const response = await axios.get(this.localProxy.proxy); - const text = response.data; - const proxyUrls = text.split('\r\n'); - const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); - const proxyUrl = 'http://' + proxyUrls[rand]; - options = { - agent: new ProxyAgent(proxyUrl as any), - }; - } else { - options = { - agent: new ProxyAgent(this.localProxy.proxy as any), - }; - } - } - - const socketConfig: UserFacingSocketConfig = { - ...options, - auth: { - creds: this.instance.authState.state.creds, - keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), - }, - logger: P({ level: this.logBaileys }), - printQRInTerminal: false, - browser, - version, - markOnlineOnConnect: this.localSettings.always_online, - retryRequestDelayMs: 10, - connectTimeoutMs: 60_000, - qrTimeout: 40_000, - defaultQueryTimeoutMs: undefined, - emitOwnEvents: false, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, - syncFullHistory: false, - userDevicesCache: this.userDevicesCache, - transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 10 }, - patchMessageBeforeSending: (message) => { - const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage); - if (requiresPatch) { - message = { - viewOnceMessageV2: { - message: { - messageContextInfo: { - deviceListMetadataVersion: 2, - deviceListMetadata: {}, - }, - ...message, - }, - }, - }; - } - - return message; - }, - }; - - this.endSession = false; - - this.logger.verbose('Creating socket'); - - this.client = makeWASocket(socketConfig); - - this.logger.verbose('Socket created'); - - this.eventHandler(); - - this.logger.verbose('Socket event handler initialized'); - - this.phoneNumber = number; - - return this.client; - } catch (error) { - this.logger.error(error); - throw new InternalServerErrorException(error?.toString()); - } - } - - public async reloadConnection(): Promise { - try { - this.instance.authState = await this.defineAuthState(); - - const { version } = await fetchLatestBaileysVersion(); - const session = this.configService.get('CONFIG_SESSION_PHONE'); - const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; - - let options; - - if (this.localProxy.enabled) { - this.logger.verbose('Proxy enabled'); - options = { - agent: new ProxyAgent(this.localProxy.proxy as any), - fetchAgent: new ProxyAgent(this.localProxy.proxy as any), - }; - } - - const socketConfig: UserFacingSocketConfig = { - ...options, - auth: { - creds: this.instance.authState.state.creds, - keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), - }, - logger: P({ level: this.logBaileys }), - printQRInTerminal: false, - browser, - version, - markOnlineOnConnect: this.localSettings.always_online, - retryRequestDelayMs: 10, - connectTimeoutMs: 60_000, - qrTimeout: 40_000, - defaultQueryTimeoutMs: undefined, - emitOwnEvents: false, - msgRetryCounterCache: this.msgRetryCounterCache, - getMessage: async (key) => (await this.getMessage(key)) as Promise, - generateHighQualityLinkPreview: true, - syncFullHistory: false, - userDevicesCache: this.userDevicesCache, - transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 10 }, - patchMessageBeforeSending: (message) => { - const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage); - if (requiresPatch) { - message = { - viewOnceMessageV2: { - message: { - messageContextInfo: { - deviceListMetadataVersion: 2, - deviceListMetadata: {}, - }, - ...message, - }, - }, - }; - } - - return message; - }, - }; - - this.client = makeWASocket(socketConfig); - - return this.client; - } catch (error) { - this.logger.error(error); - throw new InternalServerErrorException(error?.toString()); - } - } - - private readonly chatHandle = { - 'chats.upsert': async (chats: Chat[], database: Database) => { - this.logger.verbose('Event received: chats.upsert'); - - this.logger.verbose('Finding chats in database'); - const chatsRepository = await this.repository.chat.find({ - where: { owner: this.instance.name }, - }); - - this.logger.verbose('Verifying if chats exists in database to insert'); - const chatsRaw: ChatRaw[] = []; - for await (const chat of chats) { - if (chatsRepository.find((cr) => cr.id === chat.id)) { - continue; - } - - chatsRaw.push({ id: chat.id, owner: this.instance.wuid }); - } - - this.logger.verbose('Sending data to webhook in event CHATS_UPSERT'); - this.sendDataWebhook(Events.CHATS_UPSERT, chatsRaw); - - this.logger.verbose('Inserting chats in database'); - this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS); - }, - - 'chats.update': async ( - chats: Partial< - proto.IConversation & { - lastMessageRecvTimestamp?: number; - } & { - conditional: (bufferedData: BufferedEventData) => boolean; - } - >[], - ) => { - this.logger.verbose('Event received: chats.update'); - const chatsRaw: ChatRaw[] = chats.map((chat) => { - return { id: chat.id, owner: this.instance.wuid }; - }); - - this.logger.verbose('Sending data to webhook in event CHATS_UPDATE'); - this.sendDataWebhook(Events.CHATS_UPDATE, chatsRaw); - }, - - 'chats.delete': async (chats: string[]) => { - this.logger.verbose('Event received: chats.delete'); - - this.logger.verbose('Deleting chats in database'); - chats.forEach( - async (chat) => - await this.repository.chat.delete({ - where: { owner: this.instance.name, id: chat }, - }), - ); - - this.logger.verbose('Sending data to webhook in event CHATS_DELETE'); - this.sendDataWebhook(Events.CHATS_DELETE, [...chats]); - }, - }; - - private readonly contactHandle = { - 'contacts.upsert': async (contacts: Contact[], database: Database) => { - this.logger.verbose('Event received: contacts.upsert'); - - this.logger.verbose('Finding contacts in database'); - const contactsRepository = await this.repository.contact.find({ - where: { owner: this.instance.name }, - }); - - this.logger.verbose('Verifying if contacts exists in database to insert'); - const contactsRaw: ContactRaw[] = []; - for await (const contact of contacts) { - if (contactsRepository.find((cr) => cr.id === contact.id)) { - continue; - } - - contactsRaw.push({ - id: contact.id, - pushName: contact?.name || contact?.verifiedName, - profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl, - owner: this.instance.name, - }); - } - - this.logger.verbose('Sending data to webhook in event CONTACTS_UPSERT'); - this.sendDataWebhook(Events.CONTACTS_UPSERT, contactsRaw); - - this.logger.verbose('Inserting contacts in database'); - this.repository.contact.insert(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS); - }, - - 'contacts.update': async (contacts: Partial[], database: Database) => { - this.logger.verbose('Event received: contacts.update'); - - this.logger.verbose('Verifying if contacts exists in database to update'); - const contactsRaw: ContactRaw[] = []; - for await (const contact of contacts) { - contactsRaw.push({ - id: contact.id, - pushName: contact?.name ?? contact?.verifiedName, - profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl, - owner: this.instance.name, - }); - } - - this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE'); - this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw); - - this.logger.verbose('Updating contacts in database'); - this.repository.contact.update(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS); - }, - }; - - private readonly messageHandle = { - 'messaging-history.set': async ( - { - messages, - chats, - isLatest, - }: { - chats: Chat[]; - contacts: Contact[]; - messages: proto.IWebMessageInfo[]; - isLatest: boolean; - }, - database: Database, - ) => { - this.logger.verbose('Event received: messaging-history.set'); - if (isLatest) { - this.logger.verbose('isLatest defined as true'); - const chatsRaw: ChatRaw[] = chats.map((chat) => { - return { - id: chat.id, - owner: this.instance.name, - lastMsgTimestamp: chat.lastMessageRecvTimestamp, - }; - }); - - this.logger.verbose('Sending data to webhook in event CHATS_SET'); - this.sendDataWebhook(Events.CHATS_SET, chatsRaw); - - this.logger.verbose('Inserting chats in database'); - this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS); - } - - const messagesRaw: MessageRaw[] = []; - const messagesRepository = await this.repository.message.find({ - where: { owner: this.instance.name }, - }); - for await (const [, m] of Object.entries(messages)) { - if (!m.message) { - continue; - } - if (messagesRepository.find((mr) => mr.owner === this.instance.name && mr.key.id === m.key.id)) { - continue; - } - - if (Long.isLong(m?.messageTimestamp)) { - m.messageTimestamp = m.messageTimestamp?.toNumber(); - } - - messagesRaw.push({ - key: m.key, - pushName: m.pushName, - participant: m.participant, - message: { ...m.message }, - messageType: getContentType(m.message), - messageTimestamp: m.messageTimestamp as number, - owner: this.instance.name, - }); - } - - this.logger.verbose('Sending data to webhook in event MESSAGES_SET'); - this.sendDataWebhook(Events.MESSAGES_SET, [...messagesRaw]); - - messages = undefined; - }, - - 'messages.upsert': async ( - { - messages, - type, - }: { - messages: proto.IWebMessageInfo[]; - type: MessageUpsertType; - }, - database: Database, - settings: SettingsRaw, - ) => { - try { - this.logger.verbose('Event received: messages.upsert'); - for (const received of messages) { - if ( - (type !== 'notify' && type !== 'append') || - received.message?.protocolMessage || - received.message?.pollUpdateMessage - ) { - this.logger.verbose('message rejected'); - return; - } - - if (Long.isLong(received.messageTimestamp)) { - received.messageTimestamp = received.messageTimestamp?.toNumber(); - } - - if (settings?.groups_ignore && received.key.remoteJid.includes('@g.us')) { - this.logger.verbose('group ignored'); - return; - } - - let messageRaw: MessageRaw; - - if ( - (this.localWebhook.webhook_base64 === true && received?.message.documentMessage) || - received?.message?.imageMessage - ) { - const buffer = await downloadMediaMessage( - { key: received.key, message: received?.message }, - 'buffer', - {}, - { - logger: P({ level: 'error' }) as any, - reuploadRequest: this.client.updateMediaMessage, - }, - ); - messageRaw = { - key: received.key, - pushName: received.pushName, - message: { - ...received.message, - base64: buffer ? buffer.toString('base64') : undefined, - }, - messageType: getContentType(received.message), - messageTimestamp: received.messageTimestamp as number, - owner: this.instance.name, - source: getDevice(received.key.id), - }; - } else { - messageRaw = { - key: received.key, - pushName: received.pushName, - message: { ...received.message }, - messageType: getContentType(received.message), - messageTimestamp: received.messageTimestamp as number, - owner: this.instance.name, - source: getDevice(received.key.id), - }; - } - - if (this.localSettings.read_messages && received.key.id !== 'status@broadcast') { - await this.client.readMessages([received.key]); - } - - if (this.localSettings.read_status && received.key.id === 'status@broadcast') { - await this.client.readMessages([received.key]); - } - - this.logger.log(messageRaw); - - this.logger.verbose('Sending data to webhook in event MESSAGES_UPSERT'); - this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); - - if (this.localChatwoot.enabled) { - await this.chatwootService.eventWhatsapp( - Events.MESSAGES_UPSERT, - { instanceName: this.instance.name }, - messageRaw, - ); - } - - const typebotSessionRemoteJid = this.localTypebot.sessions?.find( - (session) => session.remoteJid === received.key.remoteJid, - ); - - if ((this.localTypebot.enabled && type === 'notify') || typebotSessionRemoteJid) { - 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 && type === 'notify') { - await this.chamaaiService.sendChamaai( - { instanceName: this.instance.name }, - messageRaw.key.remoteJid, - messageRaw, - ); - } - - this.logger.verbose('Inserting message in database'); - await this.repository.message.insert([messageRaw], this.instance.name, database.SAVE_DATA.NEW_MESSAGE); - - this.logger.verbose('Verifying contact from message'); - const contact = await this.repository.contact.find({ - where: { owner: this.instance.name, id: received.key.remoteJid }, - }); - - const contactRaw: ContactRaw = { - id: received.key.remoteJid, - pushName: received.pushName, - profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl, - owner: this.instance.name, - }; - - if (contactRaw.id === 'status@broadcast') { - this.logger.verbose('Contact is status@broadcast'); - return; - } - - if (contact?.length) { - this.logger.verbose('Contact found in database'); - const contactRaw: ContactRaw = { - id: received.key.remoteJid, - pushName: contact[0].pushName, - profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl, - owner: this.instance.name, - }; - - this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE'); - this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw); - - if (this.localChatwoot.enabled) { - await this.chatwootService.eventWhatsapp( - Events.CONTACTS_UPDATE, - { instanceName: this.instance.name }, - contactRaw, - ); - } - - this.logger.verbose('Updating contact in database'); - await this.repository.contact.update([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS); - return; - } - - this.logger.verbose('Contact not found in database'); - - this.logger.verbose('Sending data to webhook in event CONTACTS_UPSERT'); - this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw); - - this.logger.verbose('Inserting contact in database'); - this.repository.contact.insert([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS); - } - } catch (error) { - this.logger.error(error); - } - }, - - 'messages.update': async (args: WAMessageUpdate[], database: Database, settings: SettingsRaw) => { - this.logger.verbose('Event received: messages.update'); - const status: Record = { - 0: 'ERROR', - 1: 'PENDING', - 2: 'SERVER_ACK', - 3: 'DELIVERY_ACK', - 4: 'READ', - 5: 'PLAYED', - }; - for await (const { key, update } of args) { - if (settings?.groups_ignore && key.remoteJid.includes('@g.us')) { - this.logger.verbose('group ignored'); - return; - } - if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) { - this.logger.verbose('Message update is valid'); - - let pollUpdates: any; - if (update.pollUpdates) { - this.logger.verbose('Poll update found'); - - this.logger.verbose('Getting poll message'); - const pollCreation = await this.getMessage(key); - this.logger.verbose(pollCreation); - - if (pollCreation) { - this.logger.verbose('Getting aggregate votes in poll message'); - pollUpdates = getAggregateVotesInPollMessage({ - message: pollCreation as proto.IMessage, - pollUpdates: update.pollUpdates, - }); - } - } - - if (status[update.status] === 'READ' && !key.fromMe) return; - - if (update.message === null && update.status === undefined) { - this.logger.verbose('Message deleted'); - - this.logger.verbose('Sending data to webhook in event MESSAGE_DELETE'); - this.sendDataWebhook(Events.MESSAGES_DELETE, key); - - const message: MessageUpdateRaw = { - ...key, - status: 'DELETED', - datetime: Date.now(), - owner: this.instance.name, - }; - - this.logger.verbose(message); - - this.logger.verbose('Inserting message in database'); - await this.repository.messageUpdate.insert( - [message], - this.instance.name, - database.SAVE_DATA.MESSAGE_UPDATE, - ); - return; - } - - const message: MessageUpdateRaw = { - ...key, - status: status[update.status], - datetime: Date.now(), - owner: this.instance.name, - pollUpdates, - }; - - this.logger.verbose(message); - - this.logger.verbose('Sending data to webhook in event MESSAGES_UPDATE'); - this.sendDataWebhook(Events.MESSAGES_UPDATE, message); - - this.logger.verbose('Inserting message in database'); - this.repository.messageUpdate.insert([message], this.instance.name, database.SAVE_DATA.MESSAGE_UPDATE); - } - } - }, - }; - - private readonly groupHandler = { - 'groups.upsert': (groupMetadata: GroupMetadata[]) => { - this.logger.verbose('Event received: groups.upsert'); - - this.logger.verbose('Sending data to webhook in event GROUPS_UPSERT'); - this.sendDataWebhook(Events.GROUPS_UPSERT, groupMetadata); - }, - - 'groups.update': (groupMetadataUpdate: Partial[]) => { - this.logger.verbose('Event received: groups.update'); - - this.logger.verbose('Sending data to webhook in event GROUPS_UPDATE'); - this.sendDataWebhook(Events.GROUPS_UPDATE, groupMetadataUpdate); - }, - - 'group-participants.update': (participantsUpdate: { - id: string; - participants: string[]; - action: ParticipantAction; - }) => { - this.logger.verbose('Event received: group-participants.update'); - - this.logger.verbose('Sending data to webhook in event GROUP_PARTICIPANTS_UPDATE'); - this.sendDataWebhook(Events.GROUP_PARTICIPANTS_UPDATE, participantsUpdate); - }, - }; - - private eventHandler() { - this.logger.verbose('Initializing event handler'); - this.client.ev.process(async (events) => { - if (!this.endSession) { - const database = this.configService.get('DATABASE'); - const settings = await this.findSettings(); - - if (events.call) { - this.logger.verbose('Listening event: call'); - const call = events.call[0]; - - if (settings?.reject_call && call.status == 'offer') { - this.logger.verbose('Rejecting call'); - this.client.rejectCall(call.id, call.from); - } - - if (settings?.msg_call?.trim().length > 0 && call.status == 'offer') { - this.logger.verbose('Sending message in call'); - const msg = await this.client.sendMessage(call.from, { - text: settings.msg_call, - }); - - this.logger.verbose('Sending data to event messages.upsert'); - this.client.ev.emit('messages.upsert', { - messages: [msg], - type: 'notify', - }); - } - - this.logger.verbose('Sending data to webhook in event CALL'); - this.sendDataWebhook(Events.CALL, call); - } - - if (events['connection.update']) { - this.logger.verbose('Listening event: connection.update'); - this.connectionUpdate(events['connection.update']); - } - - if (events['creds.update']) { - this.logger.verbose('Listening event: creds.update'); - this.instance.authState.saveCreds(); - } - - if (events['messaging-history.set']) { - this.logger.verbose('Listening event: messaging-history.set'); - const payload = events['messaging-history.set']; - this.messageHandle['messaging-history.set'](payload, database); - } - - if (events['messages.upsert']) { - this.logger.verbose('Listening event: messages.upsert'); - const payload = events['messages.upsert']; - this.messageHandle['messages.upsert'](payload, database, settings); - } - - if (events['messages.update']) { - this.logger.verbose('Listening event: messages.update'); - const payload = events['messages.update']; - this.messageHandle['messages.update'](payload, database, settings); - } - - if (events['presence.update']) { - this.logger.verbose('Listening event: presence.update'); - const payload = events['presence.update']; - - if (settings.groups_ignore && payload.id.includes('@g.us')) { - this.logger.verbose('group ignored'); - return; - } - this.sendDataWebhook(Events.PRESENCE_UPDATE, payload); - } - - if (!settings?.groups_ignore) { - if (events['groups.upsert']) { - this.logger.verbose('Listening event: groups.upsert'); - const payload = events['groups.upsert']; - this.groupHandler['groups.upsert'](payload); - } - - if (events['groups.update']) { - this.logger.verbose('Listening event: groups.update'); - const payload = events['groups.update']; - this.groupHandler['groups.update'](payload); - } - - if (events['group-participants.update']) { - this.logger.verbose('Listening event: group-participants.update'); - const payload = events['group-participants.update']; - this.groupHandler['group-participants.update'](payload); - } - } - - if (events['chats.upsert']) { - this.logger.verbose('Listening event: chats.upsert'); - const payload = events['chats.upsert']; - this.chatHandle['chats.upsert'](payload, database); - } - - if (events['chats.update']) { - this.logger.verbose('Listening event: chats.update'); - const payload = events['chats.update']; - this.chatHandle['chats.update'](payload); - } - - if (events['chats.delete']) { - this.logger.verbose('Listening event: chats.delete'); - const payload = events['chats.delete']; - this.chatHandle['chats.delete'](payload); - } - - if (events['contacts.upsert']) { - this.logger.verbose('Listening event: contacts.upsert'); - const payload = events['contacts.upsert']; - this.contactHandle['contacts.upsert'](payload, database); - } - - if (events['contacts.update']) { - this.logger.verbose('Listening event: contacts.update'); - const payload = events['contacts.update']; - this.contactHandle['contacts.update'](payload, database); - } - } - }); - } - - // Check if the number is MX or AR - private formatMXOrARNumber(jid: string): string { - const countryCode = jid.substring(0, 2); - - if (Number(countryCode) === 52 || Number(countryCode) === 54) { - if (jid.length === 13) { - const number = countryCode + jid.substring(3); - return number; - } - - return jid; - } - return jid; - } - - // Check if the number is br - private formatBRNumber(jid: string) { - const regexp = new RegExp(/^(\d{2})(\d{2})\d{1}(\d{8})$/); - if (regexp.test(jid)) { - const match = regexp.exec(jid); - if (match && match[1] === '55') { - const joker = Number.parseInt(match[3][0]); - const ddd = Number.parseInt(match[2]); - if (joker < 7 || ddd < 31) { - return match[0]; - } - return match[1] + match[2] + match[3]; - } - return jid; - } else { - return jid; - } - } - - private createJid(number: string): string { - this.logger.verbose('Creating jid with number: ' + number); - - if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) { - this.logger.verbose('Number already contains @g.us or @s.whatsapp.net or @lid'); - return number; - } - - if (number.includes('@broadcast')) { - this.logger.verbose('Number already contains @broadcast'); - return number; - } - - number = number - ?.replace(/\s/g, '') - .replace(/\+/g, '') - .replace(/\(/g, '') - .replace(/\)/g, '') - .split(':')[0] - .split('@')[0]; - - if (number.includes('-') && number.length >= 24) { - this.logger.verbose('Jid created is group: ' + `${number}@g.us`); - number = number.replace(/[^\d-]/g, ''); - return `${number}@g.us`; - } - - number = number.replace(/\D/g, ''); - - if (number.length >= 18) { - this.logger.verbose('Jid created is group: ' + `${number}@g.us`); - number = number.replace(/[^\d-]/g, ''); - return `${number}@g.us`; - } - - this.logger.verbose('Jid created is whatsapp: ' + `${number}@s.whatsapp.net`); - return `${number}@s.whatsapp.net`; - } - - public async profilePicture(number: string) { - const jid = this.createJid(number); - - this.logger.verbose('Getting profile picture with jid: ' + jid); - try { - this.logger.verbose('Getting profile picture url'); - return { - wuid: jid, - profilePictureUrl: await this.client.profilePictureUrl(jid, 'image'), - }; - } catch (error) { - this.logger.verbose('Profile picture not found'); - return { - wuid: jid, - profilePictureUrl: null, - }; - } - } - - public async getStatus(number: string) { - const jid = this.createJid(number); - - this.logger.verbose('Getting profile status with jid:' + jid); - try { - this.logger.verbose('Getting status'); - return { - wuid: jid, - status: (await this.client.fetchStatus(jid))?.status, - }; - } catch (error) { - this.logger.verbose('Status not found'); - return { - wuid: jid, - status: null, - }; - } - } - - public async fetchProfile(instanceName: string, number?: string) { - const jid = number ? this.createJid(number) : this.client?.user?.id; - - this.logger.verbose('Getting profile with jid: ' + jid); - try { - this.logger.verbose('Getting profile info'); - - if (number) { - const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); - const picture = await this.profilePicture(info?.jid); - const status = await this.getStatus(info?.jid); - const business = await this.fetchBusinessProfile(info?.jid); - - return { - wuid: info?.jid || jid, - name: info?.name, - numberExists: info?.exists, - picture: picture?.profilePictureUrl, - status: status?.status, - isBusiness: business.isBusiness, - email: business?.email, - description: business?.description, - website: business?.website?.shift(), - }; - } else { - const info = await waMonitor.instanceInfo(instanceName); - const business = await this.fetchBusinessProfile(jid); - - return { - wuid: jid, - name: info?.instance?.profileName, - numberExists: true, - picture: info?.instance?.profilePictureUrl, - status: info?.instance?.profileStatus, - isBusiness: business.isBusiness, - email: business?.email, - description: business?.description, - website: business?.website?.shift(), - }; - } - } catch (error) { - this.logger.verbose('Profile not found'); - return { - wuid: jid, - name: null, - picture: null, - status: null, - os: null, - isBusiness: false, - }; - } - } - - private async sendMessageWithTyping( - number: string, - message: T, - options?: Options, - isChatwoot = false, - ) { - this.logger.verbose('Sending message with typing'); - - this.logger.verbose(`Check if number "${number}" is WhatsApp`); - const isWA = (await this.whatsappNumber({ numbers: [number] }))?.shift(); - - this.logger.verbose(`Exists: "${isWA.exists}" | jid: ${isWA.jid}`); - if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) { - throw new BadRequestException(isWA); - } - - const sender = isWA.jid; - - try { - if (options?.delay) { - this.logger.verbose('Delaying message'); - - await this.client.presenceSubscribe(sender); - this.logger.verbose('Subscribing to presence'); - - await this.client.sendPresenceUpdate(options?.presence ?? 'composing', sender); - this.logger.verbose('Sending presence update: ' + options?.presence ?? 'composing'); - - await delay(options.delay); - this.logger.verbose('Set delay: ' + options.delay); - - await this.client.sendPresenceUpdate('paused', sender); - this.logger.verbose('Sending presence update: paused'); - } - - const linkPreview = options?.linkPreview != false ? undefined : false; - - let quoted: WAMessage; - - if (options?.quoted) { - const m = options?.quoted; - - const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); - - if (!msg) { - throw 'Message not found'; - } - - quoted = msg; - this.logger.verbose('Quoted message'); - } - - let mentions: string[]; - if (isJidGroup(sender)) { - try { - const group = await this.findGroup({ groupJid: sender }, 'inner'); - - if (!group) { - throw new NotFoundException('Group not found'); - } - - if (options?.mentions) { - this.logger.verbose('Mentions defined'); - - if (options.mentions?.everyOne) { - this.logger.verbose('Mentions everyone'); - - this.logger.verbose('Getting group metadata'); - mentions = group.participants.map((participant) => participant.id); - this.logger.verbose('Getting group metadata for mentions'); - } else if (options.mentions?.mentioned?.length) { - this.logger.verbose('Mentions manually defined'); - mentions = options.mentions.mentioned.map((mention) => { - const jid = this.createJid(mention); - if (isJidGroup(jid)) { - return null; - } - return jid; - }); - } - } - } catch (error) { - throw new NotFoundException('Group not found'); - } - } - - const messageSent = await (async () => { - const option = { - quoted, - }; - - if ( - !message['audio'] && - !message['poll'] && - !message['sticker'] && - !message['conversation'] && - sender !== 'status@broadcast' - ) { - if (message['reactionMessage']) { - this.logger.verbose('Sending reaction'); - return await this.client.sendMessage( - sender, - { - react: { - text: message['reactionMessage']['text'], - key: message['reactionMessage']['key'], - }, - } as unknown as AnyMessageContent, - option as unknown as MiscMessageGenerationOptions, - ); - } - - if (!message['audio']) { - this.logger.verbose('Sending message'); - return await this.client.sendMessage( - sender, - { - forward: { - key: { remoteJid: this.instance.wuid, fromMe: true }, - message, - }, - mentions, - }, - option as unknown as MiscMessageGenerationOptions, - ); - } - } - if (message['conversation']) { - this.logger.verbose('Sending message'); - return await this.client.sendMessage( - sender, - { - text: message['conversation'], - mentions, - linkPreview: linkPreview, - } as unknown as AnyMessageContent, - option as unknown as MiscMessageGenerationOptions, - ); - } - - if (sender === 'status@broadcast') { - this.logger.verbose('Sending message'); - return await this.client.sendMessage( - sender, - message['status'].content as unknown as AnyMessageContent, - { - backgroundColor: message['status'].option.backgroundColor, - font: message['status'].option.font, - statusJidList: message['status'].option.statusJidList, - } as unknown as MiscMessageGenerationOptions, - ); - } - - this.logger.verbose('Sending message'); - return await this.client.sendMessage( - sender, - message as unknown as AnyMessageContent, - option as unknown as MiscMessageGenerationOptions, - ); - })(); - - const messageRaw: MessageRaw = { - key: messageSent.key, - pushName: messageSent.pushName, - message: { ...messageSent.message }, - messageType: getContentType(messageSent.message), - messageTimestamp: messageSent.messageTimestamp as number, - owner: this.instance.name, - source: getDevice(messageSent.key.id), - }; - - this.logger.log(messageRaw); - - this.logger.verbose('Sending data to webhook in event SEND_MESSAGE'); - this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); - - if (this.localChatwoot.enabled && !isChatwoot) { - this.chatwootService.eventWhatsapp(Events.SEND_MESSAGE, { instanceName: this.instance.name }, messageRaw); - } - - this.logger.verbose('Inserting message in database'); - await this.repository.message.insert( - [messageRaw], - this.instance.name, - this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE, - ); - - return messageSent; - } catch (error) { - this.logger.error(error); - throw new BadRequestException(error.toString()); - } - } - - // Instance Controller - public get connectionStatus() { - this.logger.verbose('Getting connection status'); - return this.stateConnection; - } - - // Send Message Controller - public async textMessage(data: SendTextDto, isChatwoot = false) { - this.logger.verbose('Sending text message'); - return await this.sendMessageWithTyping( - data.number, - { - conversation: data.textMessage.text, - }, - data?.options, - isChatwoot, - ); - } - - public async pollMessage(data: SendPollDto) { - this.logger.verbose('Sending poll message'); - return await this.sendMessageWithTyping( - data.number, - { - poll: { - name: data.pollMessage.name, - selectableCount: data.pollMessage.selectableCount, - values: data.pollMessage.values, - }, - }, - data?.options, - ); - } - - private async formatStatusMessage(status: StatusMessage) { - this.logger.verbose('Formatting status message'); - - if (!status.type) { - throw new BadRequestException('Type is required'); - } - - if (!status.content) { - throw new BadRequestException('Content is required'); - } - - if (status.allContacts) { - this.logger.verbose('All contacts defined as true'); - - this.logger.verbose('Getting contacts from database'); - const contacts = await this.repository.contact.find({ - where: { owner: this.instance.name }, - }); - - if (!contacts.length) { - throw new BadRequestException('Contacts not found'); - } - - this.logger.verbose('Getting contacts with push name'); - status.statusJidList = contacts.filter((contact) => contact.pushName).map((contact) => contact.id); - - this.logger.verbose(status.statusJidList); - } - - if (!status.statusJidList?.length && !status.allContacts) { - throw new BadRequestException('StatusJidList is required'); - } - - if (status.type === 'text') { - this.logger.verbose('Type defined as text'); - - if (!status.backgroundColor) { - throw new BadRequestException('Background color is required'); - } - - if (!status.font) { - throw new BadRequestException('Font is required'); - } - - return { - content: { - text: status.content, - }, - option: { - backgroundColor: status.backgroundColor, - font: status.font, - statusJidList: status.statusJidList, - }, - }; - } - if (status.type === 'image') { - this.logger.verbose('Type defined as image'); - - return { - content: { - image: { - url: status.content, - }, - caption: status.caption, - }, - option: { - statusJidList: status.statusJidList, - }, - }; - } - if (status.type === 'video') { - this.logger.verbose('Type defined as video'); - - return { - content: { - video: { - url: status.content, - }, - caption: status.caption, - }, - option: { - statusJidList: status.statusJidList, - }, - }; - } - if (status.type === 'audio') { - this.logger.verbose('Type defined as audio'); - - this.logger.verbose('Processing audio'); - const convert = await this.processAudio(status.content, 'status@broadcast'); - if (typeof convert === 'string') { - this.logger.verbose('Audio processed'); - const audio = fs.readFileSync(convert).toString('base64'); - - const result = { - content: { - audio: Buffer.from(audio, 'base64'), - ptt: true, - mimetype: 'audio/mp4', - }, - option: { - statusJidList: status.statusJidList, - }, - }; - - fs.unlinkSync(convert); - - return result; - } else { - throw new InternalServerErrorException(convert); - } - } - - throw new BadRequestException('Type not found'); - } - - public async statusMessage(data: SendStatusDto) { - this.logger.verbose('Sending status message'); - const status = await this.formatStatusMessage(data.statusMessage); - - return await this.sendMessageWithTyping('status@broadcast', { - status, - }); - } - - private async prepareMediaMessage(mediaMessage: MediaMessage) { - try { - this.logger.verbose('Preparing media message'); - const prepareMedia = await prepareWAMessageMedia( - { - [mediaMessage.mediatype]: isURL(mediaMessage.media) - ? { url: mediaMessage.media } - : Buffer.from(mediaMessage.media, 'base64'), - } as any, - { upload: this.client.waUploadToServer }, - ); - - const mediaType = mediaMessage.mediatype + 'Message'; - this.logger.verbose('Media type: ' + mediaType); - - if (mediaMessage.mediatype === 'document' && !mediaMessage.fileName) { - this.logger.verbose('If media type is document and file name is not defined then'); - const regex = new RegExp(/.*\/(.+?)\./); - const arrayMatch = regex.exec(mediaMessage.media); - mediaMessage.fileName = arrayMatch[1]; - this.logger.verbose('File name: ' + mediaMessage.fileName); - } - - if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) { - mediaMessage.fileName = 'image.png'; - } - - if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) { - mediaMessage.fileName = 'video.mp4'; - } - - let mimetype: string; - - if (mediaMessage.mimetype) { - mimetype = mediaMessage.mimetype; - } else { - if (isURL(mediaMessage.media)) { - mimetype = getMIMEType(mediaMessage.media); - } else { - mimetype = getMIMEType(mediaMessage.fileName); - } - } - - this.logger.verbose('Mimetype: ' + mimetype); - - prepareMedia[mediaType].caption = mediaMessage?.caption; - prepareMedia[mediaType].mimetype = mimetype; - prepareMedia[mediaType].fileName = mediaMessage.fileName; - - if (mediaMessage.mediatype === 'video') { - this.logger.verbose('Is media type video then set gif playback as false'); - prepareMedia[mediaType].jpegThumbnail = Uint8Array.from( - readFileSync(join(process.cwd(), 'public', 'images', 'video-cover.png')), - ); - prepareMedia[mediaType].gifPlayback = false; - } - - this.logger.verbose('Generating wa message from content'); - return generateWAMessageFromContent( - '', - { [mediaType]: { ...prepareMedia[mediaType] } }, - { userJid: this.instance.wuid }, - ); - } catch (error) { - this.logger.error(error); - throw new InternalServerErrorException(error?.toString() || error); - } - } - - private async convertToWebP(image: string, number: string) { - try { - this.logger.verbose('Converting image to WebP to sticker'); - - let imagePath: string; - const hash = `${number}-${new Date().getTime()}`; - this.logger.verbose('Hash to image name: ' + hash); - - const outputPath = `${join(this.storePath, 'temp', `${hash}.webp`)}`; - this.logger.verbose('Output path: ' + outputPath); - - if (isBase64(image)) { - this.logger.verbose('Image is base64'); - - const base64Data = image.replace(/^data:image\/(jpeg|png|gif);base64,/, ''); - const imageBuffer = Buffer.from(base64Data, 'base64'); - imagePath = `${join(this.storePath, 'temp', `temp-${hash}.png`)}`; - this.logger.verbose('Image path: ' + imagePath); - - await sharp(imageBuffer).toFile(imagePath); - this.logger.verbose('Image created'); - } else { - this.logger.verbose('Image is url'); - - const timestamp = new Date().getTime(); - const url = `${image}?timestamp=${timestamp}`; - this.logger.verbose('including timestamp in url: ' + url); - - const response = await axios.get(url, { responseType: 'arraybuffer' }); - this.logger.verbose('Getting image from url'); - - const imageBuffer = Buffer.from(response.data, 'binary'); - imagePath = `${join(this.storePath, 'temp', `temp-${hash}.png`)}`; - this.logger.verbose('Image path: ' + imagePath); - - await sharp(imageBuffer).toFile(imagePath); - this.logger.verbose('Image created'); - } - - await sharp(imagePath).webp().toFile(outputPath); - this.logger.verbose('Image converted to WebP'); - - fs.unlinkSync(imagePath); - this.logger.verbose('Temp image deleted'); - - return outputPath; - } catch (error) { - console.error('Erro ao converter a imagem para WebP:', error); - } - } - - public async mediaSticker(data: SendStickerDto) { - this.logger.verbose('Sending media sticker'); - const convert = await this.convertToWebP(data.stickerMessage.image, data.number); - const result = await this.sendMessageWithTyping( - data.number, - { - sticker: { url: convert }, - }, - data?.options, - ); - - fs.unlinkSync(convert); - this.logger.verbose('Converted image deleted'); - - return result; - } - - public async mediaMessage(data: SendMediaDto, isChatwoot = false) { - this.logger.verbose('Sending media message'); - const generate = await this.prepareMediaMessage(data.mediaMessage); - - return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options, isChatwoot); - } - - public async processAudio(audio: string, number: string) { - this.logger.verbose('Processing audio'); - let tempAudioPath: string; - let outputAudio: string; - - const hash = `${number}-${new Date().getTime()}`; - this.logger.verbose('Hash to audio name: ' + hash); - - if (isURL(audio)) { - this.logger.verbose('Audio is url'); - - outputAudio = `${join(this.storePath, 'temp', `${hash}.mp4`)}`; - tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; - - this.logger.verbose('Output audio path: ' + outputAudio); - this.logger.verbose('Temp audio path: ' + tempAudioPath); - - const timestamp = new Date().getTime(); - const url = `${audio}?timestamp=${timestamp}`; - - this.logger.verbose('Including timestamp in url: ' + url); - - const response = await axios.get(url, { responseType: 'arraybuffer' }); - this.logger.verbose('Getting audio from url'); - - fs.writeFileSync(tempAudioPath, response.data); - } else { - this.logger.verbose('Audio is base64'); - - outputAudio = `${join(this.storePath, 'temp', `${hash}.mp4`)}`; - tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; - - this.logger.verbose('Output audio path: ' + outputAudio); - this.logger.verbose('Temp audio path: ' + tempAudioPath); - - const audioBuffer = Buffer.from(audio, 'base64'); - fs.writeFileSync(tempAudioPath, audioBuffer); - this.logger.verbose('Temp audio created'); - } - - this.logger.verbose('Converting audio to mp4'); - return new Promise((resolve, reject) => { - exec(`${ffmpegPath.path} -i ${tempAudioPath} -vn -ab 128k -ar 44100 -f ipod ${outputAudio} -y`, (error) => { - fs.unlinkSync(tempAudioPath); - this.logger.verbose('Temp audio deleted'); - - if (error) reject(error); - - this.logger.verbose('Audio converted to mp4'); - resolve(outputAudio); - }); - }); - } - - public async audioWhatsapp(data: SendAudioDto, isChatwoot = false) { - this.logger.verbose('Sending audio whatsapp'); - - if (!data.options?.encoding && data.options?.encoding !== false) { - data.options.encoding = true; - } - - if (data.options?.encoding) { - const convert = await this.processAudio(data.audioMessage.audio, data.number); - if (typeof convert === 'string') { - const audio = fs.readFileSync(convert).toString('base64'); - const result = this.sendMessageWithTyping( - data.number, - { - audio: Buffer.from(audio, 'base64'), - ptt: true, - mimetype: 'audio/mp4', - }, - { presence: 'recording', delay: data?.options?.delay }, - isChatwoot, - ); - - fs.unlinkSync(convert); - this.logger.verbose('Converted audio deleted'); - - return result; - } else { - throw new InternalServerErrorException(convert); - } - } - - return await this.sendMessageWithTyping( - data.number, - { - audio: isURL(data.audioMessage.audio) - ? { url: data.audioMessage.audio } - : Buffer.from(data.audioMessage.audio, 'base64'), - ptt: true, - mimetype: 'audio/ogg; codecs=opus', - }, - { presence: 'recording', delay: data?.options?.delay }, - isChatwoot, - ); - } - - public async buttonMessage(data: SendButtonDto) { - this.logger.verbose('Sending button message'); - const embeddedMedia: any = {}; - let mediatype = 'TEXT'; - - if (data.buttonMessage?.mediaMessage) { - mediatype = data.buttonMessage.mediaMessage?.mediatype.toUpperCase() ?? 'TEXT'; - embeddedMedia.mediaKey = mediatype.toLowerCase() + 'Message'; - const generate = await this.prepareMediaMessage(data.buttonMessage.mediaMessage); - embeddedMedia.message = generate.message[embeddedMedia.mediaKey]; - embeddedMedia.contentText = `*${data.buttonMessage.title}*\n\n${data.buttonMessage.description}`; - } - - const btnItems = { - text: data.buttonMessage.buttons.map((btn) => btn.buttonText), - ids: data.buttonMessage.buttons.map((btn) => btn.buttonId), - }; - - if (!arrayUnique(btnItems.text) || !arrayUnique(btnItems.ids)) { - throw new BadRequestException('Button texts cannot be repeated', 'Button IDs cannot be repeated.'); - } - - return await this.sendMessageWithTyping( - data.number, - { - buttonsMessage: { - text: !embeddedMedia?.mediaKey ? data.buttonMessage.title : undefined, - contentText: embeddedMedia?.contentText ?? data.buttonMessage.description, - footerText: data.buttonMessage?.footerText, - buttons: data.buttonMessage.buttons.map((button) => { - return { - buttonText: { - displayText: button.buttonText, - }, - buttonId: button.buttonId, - type: 1, - }; - }), - headerType: proto.Message.ButtonsMessage.HeaderType[mediatype], - [embeddedMedia?.mediaKey]: embeddedMedia?.message, - }, - }, - data?.options, - ); - } - - public async locationMessage(data: SendLocationDto) { - this.logger.verbose('Sending location message'); - return await this.sendMessageWithTyping( - data.number, - { - locationMessage: { - degreesLatitude: data.locationMessage.latitude, - degreesLongitude: data.locationMessage.longitude, - name: data.locationMessage?.name, - address: data.locationMessage?.address, - }, - }, - data?.options, - ); - } - - public async listMessage(data: SendListDto) { - this.logger.verbose('Sending list message'); - return await this.sendMessageWithTyping( - data.number, - { - listMessage: { - title: data.listMessage.title, - description: data.listMessage.description, - buttonText: data.listMessage?.buttonText, - footerText: data.listMessage?.footerText, - sections: data.listMessage.sections, - listType: 1, - }, - }, - data?.options, - ); - } - - public async contactMessage(data: SendContactDto) { - this.logger.verbose('Sending contact message'); - const message: proto.IMessage = {}; - - const vcard = (contact: ContactMessage) => { - this.logger.verbose('Creating vcard'); - let result = 'BEGIN:VCARD\n' + 'VERSION:3.0\n' + `N:${contact.fullName}\n` + `FN:${contact.fullName}\n`; - - if (contact.organization) { - this.logger.verbose('Organization defined'); - result += `ORG:${contact.organization};\n`; - } - - if (contact.email) { - this.logger.verbose('Email defined'); - result += `EMAIL:${contact.email}\n`; - } - - if (contact.url) { - this.logger.verbose('Url defined'); - result += `URL:${contact.url}\n`; - } - - if (!contact.wuid) { - this.logger.verbose('Wuid defined'); - contact.wuid = this.createJid(contact.phoneNumber); - } - - result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + 'END:VCARD'; - - this.logger.verbose('Vcard created'); - return result; - }; - - if (data.contactMessage.length === 1) { - message.contactMessage = { - displayName: data.contactMessage[0].fullName, - vcard: vcard(data.contactMessage[0]), - }; - } else { - message.contactsArrayMessage = { - displayName: `${data.contactMessage.length} contacts`, - contacts: data.contactMessage.map((contact) => { - return { - displayName: contact.fullName, - vcard: vcard(contact), - }; - }), - }; - } - - return await this.sendMessageWithTyping(data.number, { ...message }, data?.options); - } - - public async reactionMessage(data: SendReactionDto) { - this.logger.verbose('Sending reaction message'); - return await this.sendMessageWithTyping(data.reactionMessage.key.remoteJid, { - reactionMessage: { - key: data.reactionMessage.key, - text: data.reactionMessage.reaction, - }, - }); - } - - // Chat Controller - public async whatsappNumber(data: WhatsAppNumberDto) { - this.logger.verbose('Getting whatsapp number'); - - const onWhatsapp: OnWhatsAppDto[] = []; - for await (const number of data.numbers) { - let jid = this.createJid(number); - - if (isJidGroup(jid)) { - const group = await this.findGroup({ groupJid: jid }, 'inner'); - - if (!group) throw new BadRequestException('Group not found'); - - onWhatsapp.push(new OnWhatsAppDto(group.id, !!group?.id, group?.subject)); - } else { - jid = !jid.startsWith('+') ? `+${jid}` : jid; - const verify = await this.client.onWhatsApp(jid); - - const result = verify[0]; - - if (!result) { - onWhatsapp.push(new OnWhatsAppDto(jid, false)); - } else { - onWhatsapp.push(new OnWhatsAppDto(result.jid, result.exists)); - } - } - } - - return onWhatsapp; - } - - public async markMessageAsRead(data: ReadMessageDto) { - this.logger.verbose('Marking message as read'); - - try { - const keys: proto.IMessageKey[] = []; - data.read_messages.forEach((read) => { - if (isJidGroup(read.remoteJid) || isJidUser(read.remoteJid)) { - keys.push({ - remoteJid: read.remoteJid, - fromMe: read.fromMe, - id: read.id, - }); - } - }); - await this.client.readMessages(keys); - return { message: 'Read messages', read: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Read messages fail', error.toString()); - } - } - - public async getLastMessage(number: string) { - const messages = await this.fetchMessages({ - where: { - key: { - remoteJid: number, - }, - owner: this.instance.name, - }, - }); - - let lastMessage = messages.pop(); - - for (const message of messages) { - if (message.messageTimestamp >= lastMessage.messageTimestamp) { - lastMessage = message; - } - } - - return lastMessage as unknown as LastMessage; - } - - public async archiveChat(data: ArchiveChatDto) { - this.logger.verbose('Archiving chat'); - try { - let last_message = data.lastMessage; - let number = data.chat; - - if (!last_message && number) { - last_message = await this.getLastMessage(number); - } else { - last_message = data.lastMessage; - last_message.messageTimestamp = last_message?.messageTimestamp ?? Date.now(); - number = last_message?.key?.remoteJid; - } - - if (!last_message || Object.keys(last_message).length === 0) { - throw new NotFoundException('Last message not found'); - } - - await this.client.chatModify( - { - archive: data.archive, - lastMessages: [last_message], - }, - this.createJid(number), - ); - - return { - chatId: number, - archived: true, - }; - } catch (error) { - throw new InternalServerErrorException({ - archived: false, - message: ['An error occurred while archiving the chat. Open a calling.', error.toString()], - }); - } - } - - public async deleteMessage(del: DeleteMessage) { - this.logger.verbose('Deleting message'); - try { - return await this.client.sendMessage(del.remoteJid, { delete: del }); - } catch (error) { - throw new InternalServerErrorException('Error while deleting message for everyone', error?.toString()); - } - } - - public async getBase64FromMediaMessage(data: getBase64FromMediaMessageDto) { - this.logger.verbose('Getting base64 from media message'); - try { - const m = data?.message; - const convertToMp4 = data?.convertToMp4 ?? false; - - const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); - - if (!msg) { - throw 'Message not found'; - } - - for (const subtype of MessageSubtype) { - if (msg.message[subtype]) { - msg.message = msg.message[subtype].message; - } - } - - let mediaMessage: any; - let mediaType: string; - - for (const type of TypeMediaMessage) { - mediaMessage = msg.message[type]; - if (mediaMessage) { - mediaType = type; - break; - } - } - - if (!mediaMessage) { - throw 'The message is not of the media type'; - } - - if (typeof mediaMessage['mediaKey'] === 'object') { - msg.message = JSON.parse(JSON.stringify(msg.message)); - } - - this.logger.verbose('Downloading media message'); - const buffer = await downloadMediaMessage( - { key: msg?.key, message: msg?.message }, - 'buffer', - {}, - { - logger: P({ level: 'error' }) as any, - reuploadRequest: this.client.updateMediaMessage, - }, - ); - const typeMessage = getContentType(msg.message); - - if (convertToMp4 && typeMessage === 'audioMessage') { - this.logger.verbose('Converting audio to mp4'); - const number = msg.key.remoteJid.split('@')[0]; - const convert = await this.processAudio(buffer.toString('base64'), number); - - if (typeof convert === 'string') { - const audio = fs.readFileSync(convert).toString('base64'); - this.logger.verbose('Audio converted to mp4'); - - const result = { - mediaType, - fileName: mediaMessage['fileName'], - caption: mediaMessage['caption'], - size: { - fileLength: mediaMessage['fileLength'], - height: mediaMessage['height'], - width: mediaMessage['width'], - }, - mimetype: 'audio/mp4', - base64: Buffer.from(audio, 'base64').toString('base64'), - }; - - fs.unlinkSync(convert); - this.logger.verbose('Converted audio deleted'); - - this.logger.verbose('Media message downloaded'); - return result; - } - } - - this.logger.verbose('Media message downloaded'); - return { - mediaType, - fileName: mediaMessage['fileName'], - caption: mediaMessage['caption'], - size: { - fileLength: mediaMessage['fileLength'], - height: mediaMessage['height'], - width: mediaMessage['width'], - }, - mimetype: mediaMessage['mimetype'], - base64: buffer.toString('base64'), - }; - } catch (error) { - this.logger.error(error); - throw new BadRequestException(error.toString()); - } - } - - public async fetchContacts(query: ContactQuery) { - this.logger.verbose('Fetching contacts'); - if (query?.where) { - query.where.owner = this.instance.name; - if (query.where?.id) { - query.where.id = this.createJid(query.where.id); - } - } else { - query = { - where: { - owner: this.instance.name, - }, - }; - } - return await this.repository.contact.find(query); - } - - public async fetchMessages(query: MessageQuery) { - this.logger.verbose('Fetching messages'); - if (query?.where) { - if (query.where?.key?.remoteJid) { - query.where.key.remoteJid = this.createJid(query.where.key.remoteJid); - } - query.where.owner = this.instance.name; - } else { - query = { - where: { - owner: this.instance.name, - }, - limit: query?.limit, - }; - } - return await this.repository.message.find(query); - } - - public async fetchStatusMessage(query: MessageUpQuery) { - this.logger.verbose('Fetching status messages'); - if (query?.where) { - if (query.where?.remoteJid) { - query.where.remoteJid = this.createJid(query.where.remoteJid); - } - query.where.owner = this.instance.name; - } else { - query = { - where: { - owner: this.instance.name, - }, - limit: query?.limit, - }; - } - return await this.repository.messageUpdate.find(query); - } - - public async fetchChats() { - this.logger.verbose('Fetching chats'); - return await this.repository.chat.find({ where: { owner: this.instance.name } }); - } - - public async fetchPrivacySettings() { - this.logger.verbose('Fetching privacy settings'); - return await this.client.fetchPrivacySettings(); - } - - public async updatePrivacySettings(settings: PrivacySettingDto) { - this.logger.verbose('Updating privacy settings'); - try { - await this.client.updateReadReceiptsPrivacy(settings.privacySettings.readreceipts); - this.logger.verbose('Read receipts privacy updated'); - - await this.client.updateProfilePicturePrivacy(settings.privacySettings.profile); - this.logger.verbose('Profile picture privacy updated'); - - await this.client.updateStatusPrivacy(settings.privacySettings.status); - this.logger.verbose('Status privacy updated'); - - await this.client.updateOnlinePrivacy(settings.privacySettings.online); - this.logger.verbose('Online privacy updated'); - - await this.client.updateLastSeenPrivacy(settings.privacySettings.last); - this.logger.verbose('Last seen privacy updated'); - - await this.client.updateGroupsAddPrivacy(settings.privacySettings.groupadd); - this.logger.verbose('Groups add privacy updated'); - - this.reloadConnection(); - - return { - update: 'success', - data: { - readreceipts: settings.privacySettings.readreceipts, - profile: settings.privacySettings.profile, - status: settings.privacySettings.status, - online: settings.privacySettings.online, - last: settings.privacySettings.last, - groupadd: settings.privacySettings.groupadd, - }, - }; - } catch (error) { - throw new InternalServerErrorException('Error updating privacy settings', error.toString()); - } - } - - public async fetchBusinessProfile(number: string): Promise { - this.logger.verbose('Fetching business profile'); - try { - const jid = number ? this.createJid(number) : this.instance.wuid; - - const profile = await this.client.getBusinessProfile(jid); - this.logger.verbose('Trying to get business profile'); - - if (!profile) { - const info = await this.whatsappNumber({ numbers: [jid] }); - - return { - isBusiness: false, - message: 'Not is business profile', - ...info?.shift(), - }; - } - - this.logger.verbose('Business profile fetched'); - return { - isBusiness: true, - ...profile, - }; - } catch (error) { - throw new InternalServerErrorException('Error updating profile name', error.toString()); - } - } - - public async updateProfileName(name: string) { - this.logger.verbose('Updating profile name to ' + name); - try { - await this.client.updateProfileName(name); - - return { update: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Error updating profile name', error.toString()); - } - } - - public async updateProfileStatus(status: string) { - this.logger.verbose('Updating profile status to: ' + status); - try { - await this.client.updateProfileStatus(status); - - return { update: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Error updating profile status', error.toString()); - } - } - - public async updateProfilePicture(picture: string) { - this.logger.verbose('Updating profile picture'); - try { - let pic: WAMediaUpload; - if (isURL(picture)) { - this.logger.verbose('Picture is url'); - - const timestamp = new Date().getTime(); - const url = `${picture}?timestamp=${timestamp}`; - this.logger.verbose('Including timestamp in url: ' + url); - - pic = (await axios.get(url, { responseType: 'arraybuffer' })).data; - this.logger.verbose('Getting picture from url'); - } else if (isBase64(picture)) { - this.logger.verbose('Picture is base64'); - pic = Buffer.from(picture, 'base64'); - this.logger.verbose('Getting picture from base64'); - } else { - throw new BadRequestException('"profilePicture" must be a url or a base64'); - } - await this.client.updateProfilePicture(this.instance.wuid, pic); - this.logger.verbose('Profile picture updated'); - - return { update: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Error updating profile picture', error.toString()); - } - } - - public async removeProfilePicture() { - this.logger.verbose('Removing profile picture'); - try { - await this.client.removeProfilePicture(this.instance.wuid); - - return { update: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Error removing profile picture', error.toString()); - } - } - - // Group - public async createGroup(create: CreateGroupDto) { - this.logger.verbose('Creating group: ' + create.subject); - try { - 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); - - if (create?.description) { - this.logger.verbose('Updating group description: ' + create.description); - await this.client.groupUpdateDescription(id, create.description); - } - - if (create?.promoteParticipants) { - this.logger.verbose('Prometing group participants: ' + participants); - await this.updateGParticipant({ - groupJid: id, - action: 'promote', - participants: participants, - }); - } - - this.logger.verbose('Getting group metadata'); - const group = await this.client.groupMetadata(id); - - return group; - } catch (error) { - this.logger.error(error); - throw new InternalServerErrorException('Error creating group', error.toString()); - } - } - - public async updateGroupPicture(picture: GroupPictureDto) { - this.logger.verbose('Updating group picture'); - try { - let pic: WAMediaUpload; - if (isURL(picture.image)) { - this.logger.verbose('Picture is url'); - - const timestamp = new Date().getTime(); - const url = `${picture.image}?timestamp=${timestamp}`; - this.logger.verbose('Including timestamp in url: ' + url); - - pic = (await axios.get(url, { responseType: 'arraybuffer' })).data; - this.logger.verbose('Getting picture from url'); - } else if (isBase64(picture.image)) { - this.logger.verbose('Picture is base64'); - pic = Buffer.from(picture.image, 'base64'); - this.logger.verbose('Getting picture from base64'); - } else { - throw new BadRequestException('"profilePicture" must be a url or a base64'); - } - await this.client.updateProfilePicture(picture.groupJid, pic); - this.logger.verbose('Group picture updated'); - - return { update: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Error update group picture', error.toString()); - } - } - - public async updateGroupSubject(data: GroupSubjectDto) { - this.logger.verbose('Updating group subject to: ' + data.subject); - try { - await this.client.groupUpdateSubject(data.groupJid, data.subject); - - return { update: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Error updating group subject', error.toString()); - } - } - - public async updateGroupDescription(data: GroupDescriptionDto) { - this.logger.verbose('Updating group description to: ' + data.description); - try { - await this.client.groupUpdateDescription(data.groupJid, data.description); - - return { update: 'success' }; - } catch (error) { - throw new InternalServerErrorException('Error updating group description', error.toString()); - } - } - - public async findGroup(id: GroupJid, reply: 'inner' | 'out' = 'out') { - this.logger.verbose('Fetching group'); - try { - return await this.client.groupMetadata(id.groupJid); - } catch (error) { - if (reply === 'inner') { - return; - } - throw new NotFoundException('Error fetching group', error.toString()); - } - } - - public async fetchAllGroups(getParticipants: GetParticipant) { - this.logger.verbose('Fetching all groups'); - try { - const fetch = Object.values(await this.client.groupFetchAllParticipating()); - - const groups = fetch.map((group) => { - const result = { - id: group.id, - subject: group.subject, - subjectOwner: group.subjectOwner, - subjectTime: group.subjectTime, - size: group.participants.length, - creation: group.creation, - owner: group.owner, - desc: group.desc, - descId: group.descId, - restrict: group.restrict, - announce: group.announce, - }; - - if (getParticipants.getParticipants == 'true') { - result['participants'] = group.participants; - } - - return result; - }); - - return groups; - } catch (error) { - throw new NotFoundException('Error fetching group', error.toString()); - } - } - - public async inviteCode(id: GroupJid) { - this.logger.verbose('Fetching invite code for group: ' + id.groupJid); - try { - const code = await this.client.groupInviteCode(id.groupJid); - return { inviteUrl: `https://chat.whatsapp.com/${code}`, inviteCode: code }; - } catch (error) { - throw new NotFoundException('No invite code', error.toString()); - } - } - - public async inviteInfo(id: GroupInvite) { - this.logger.verbose('Fetching invite info for code: ' + id.inviteCode); - try { - return await this.client.groupGetInviteInfo(id.inviteCode); - } catch (error) { - throw new NotFoundException('No invite info', id.inviteCode); - } - } - - public async sendInvite(id: GroupSendInvite) { - this.logger.verbose('Sending invite for group: ' + id.groupJid); - try { - const inviteCode = await this.inviteCode({ groupJid: id.groupJid }); - this.logger.verbose('Getting invite code: ' + inviteCode.inviteCode); - - const inviteUrl = inviteCode.inviteUrl; - this.logger.verbose('Invite url: ' + inviteUrl); - - const numbers = id.numbers.map((number) => this.createJid(number)); - const description = id.description ?? ''; - - const msg = `${description}\n\n${inviteUrl}`; - - const message = { - conversation: msg, - }; - - for await (const number of numbers) { - await this.sendMessageWithTyping(number, message); - } - - this.logger.verbose('Invite sent for numbers: ' + numbers.join(', ')); - - return { send: true, inviteUrl }; - } catch (error) { - throw new NotFoundException('No send invite'); - } - } - - public async revokeInviteCode(id: GroupJid) { - this.logger.verbose('Revoking invite code for group: ' + id.groupJid); - try { - const inviteCode = await this.client.groupRevokeInvite(id.groupJid); - return { revoked: true, inviteCode }; - } catch (error) { - throw new NotFoundException('Revoke error', error.toString()); - } - } - - public async findParticipants(id: GroupJid) { - this.logger.verbose('Fetching participants for group: ' + id.groupJid); - try { - const participants = (await this.client.groupMetadata(id.groupJid)).participants; - return { participants }; - } catch (error) { - throw new NotFoundException('No participants', error.toString()); - } - } - - public async updateGParticipant(update: GroupUpdateParticipantDto) { - this.logger.verbose('Updating participants'); - try { - const participants = update.participants.map((p) => this.createJid(p)); - const updateParticipants = await this.client.groupParticipantsUpdate( - update.groupJid, - participants, - update.action, - ); - return { updateParticipants: updateParticipants }; - } catch (error) { - throw new BadRequestException('Error updating participants', error.toString()); - } - } - - public async updateGSetting(update: GroupUpdateSettingDto) { - this.logger.verbose('Updating setting for group: ' + update.groupJid); - try { - const updateSetting = await this.client.groupSettingUpdate(update.groupJid, update.action); - return { updateSetting: updateSetting }; - } catch (error) { - throw new BadRequestException('Error updating setting', error.toString()); - } - } - - public async toggleEphemeral(update: GroupToggleEphemeralDto) { - this.logger.verbose('Toggling ephemeral for group: ' + update.groupJid); - try { - await this.client.groupToggleEphemeral(update.groupJid, update.expiration); - return { success: true }; - } catch (error) { - throw new BadRequestException('Error updating setting', error.toString()); - } - } - - public async leaveGroup(id: GroupJid) { - this.logger.verbose('Leaving group: ' + id.groupJid); - try { - await this.client.groupLeave(id.groupJid); - return { groupJid: id.groupJid, leave: true }; - } catch (error) { - throw new BadRequestException('Unable to leave the group', error.toString()); - } - } -} +import ffmpegPath from '@ffmpeg-installer/ffmpeg'; +import { Boom } from '@hapi/boom'; +import makeWASocket, { + AnyMessageContent, + BufferedEventData, + BufferJSON, + CacheStore, + Chat, + ConnectionState, + Contact, + delay, + DisconnectReason, + downloadMediaMessage, + fetchLatestBaileysVersion, + generateWAMessageFromContent, + getAggregateVotesInPollMessage, + getContentType, + getDevice, + GroupMetadata, + isJidGroup, + isJidUser, + makeCacheableSignalKeyStore, + MessageUpsertType, + MiscMessageGenerationOptions, + ParticipantAction, + prepareWAMessageMedia, + proto, + useMultiFileAuthState, + UserFacingSocketConfig, + WABrowserDescription, + WAMediaUpload, + WAMessage, + WAMessageUpdate, + WASocket, +} from '@whiskeysockets/baileys'; +import axios from 'axios'; +import { exec, execSync } from 'child_process'; +import { arrayUnique, isBase64, isURL } from 'class-validator'; +import EventEmitter2 from 'eventemitter2'; +import fs, { existsSync, readFileSync } from 'fs'; +import Long from 'long'; +import NodeCache from 'node-cache'; +import { getMIMEType } from 'node-mime-types'; +import { release } from 'os'; +import { join } from 'path'; +import P from 'pino'; +import { ProxyAgent } from 'proxy-agent'; +import qrcode, { QRCodeToDataURLOptions } from 'qrcode'; +import qrcodeTerminal from 'qrcode-terminal'; +import sharp from 'sharp'; +import { v4 } from 'uuid'; + +import { + Auth, + CleanStoreConf, + ConfigService, + ConfigSessionPhone, + Database, + HttpServer, + Log, + QrCode, + Redis, + Sqs, + Webhook, + Websocket, +} from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config'; +import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../exceptions'; +import { getAMQP, removeQueues } from '../../libs/amqp.server'; +import { dbserver } from '../../libs/db.connect'; +import { RedisCache } from '../../libs/redis.client'; +import { getIO } from '../../libs/socket.server'; +import { getSQS, removeQueues as removeQueuesSQS } from '../../libs/sqs.server'; +import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db'; +import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; +import { + ArchiveChatDto, + DeleteMessage, + getBase64FromMediaMessageDto, + LastMessage, + NumberBusiness, + OnWhatsAppDto, + PrivacySettingDto, + ReadMessageDto, + WhatsAppNumberDto, +} from '../dto/chat.dto'; +import { + CreateGroupDto, + GetParticipant, + GroupDescriptionDto, + GroupInvite, + GroupJid, + GroupPictureDto, + GroupSendInvite, + GroupSubjectDto, + GroupToggleEphemeralDto, + GroupUpdateParticipantDto, + GroupUpdateSettingDto, +} from '../dto/group.dto'; +import { + ContactMessage, + MediaMessage, + Options, + SendAudioDto, + SendButtonDto, + SendContactDto, + SendListDto, + SendLocationDto, + SendMediaDto, + SendPollDto, + SendReactionDto, + SendStatusDto, + SendStickerDto, + SendTextDto, + StatusMessage, +} from '../dto/sendMessage.dto'; +import { ChamaaiRaw, ProxyRaw, RabbitmqRaw, OpenaiRaw, SettingsRaw, SqsRaw, TypebotRaw, ContactOpenaiRaw } from '../models'; + +import { ChatRaw } from '../models/chat.model'; +import { ChatwootRaw } from '../models/chatwoot.model'; +import { ContactRaw } from '../models/contact.model'; +import { MessageRaw, MessageUpdateRaw } from '../models/message.model'; +import { WebhookRaw } from '../models/webhook.model'; +import { WebsocketRaw } from '../models/websocket.model'; +import { ContactQuery } from '../repository/contact.repository'; +import { MessageQuery } from '../repository/message.repository'; +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'; + +import { Configuration, OpenAIApi, ChatCompletionRequestMessage } from "openai" + +import { openai, OpenAIService } from "../../libs/openai" +import { redis } from "../../libs/redis" + +import { initPrompt } from "../../utils/initPrompt" +import { ContactOpenaiDto } from '../dto/contactopenai.dto'; + +// https://wa.me/+5512982754592 +interface CustomerChat { + status?: "open" | "closed" + orderCode: string + chatAt: string + customer: { + name: string + phone: string + } + messages: ChatCompletionRequestMessage[] + orderSummary?: string +} + +async function completion( + apiKey: string, + messages: ChatCompletionRequestMessage[] +): Promise { + + const configuration = new Configuration({ + apiKey: apiKey, + }) + + const openai = new OpenAIApi(configuration) + + const completion = await openai.createChatCompletion({ + model: "gpt-3.5-turbo", + temperature: 0, + max_tokens: 256, + messages, + }) + + return completion.data.choices[0].message?.content +} + +export class WAStartupService { + constructor( + private readonly configService: ConfigService, + private readonly eventEmitter: EventEmitter2, + private readonly repository: RepositoryBroker, + private readonly cache: RedisCache, + ) { + this.logger.verbose('WAStartupService initialized'); + this.cleanStore(); + this.instance.qrcode = { count: 0 }; + } + + private readonly logger = new Logger(WAStartupService.name); + public readonly instance: wa.Instance = {}; + public client: WASocket; + private readonly localWebhook: wa.LocalWebHook = {}; + private readonly localChatwoot: wa.LocalChatwoot = {}; + private readonly localSettings: wa.LocalSettings = {}; + private readonly localWebsocket: wa.LocalWebsocket = {}; + private readonly localRabbitmq: wa.LocalRabbitmq = {}; + private readonly localOpenai: wa.LocalOpenai = {}; + private readonly localSqs: wa.LocalSqs = {}; + 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(); + private readonly userDevicesCache: CacheStore = new NodeCache(); + private endSession = false; + private logBaileys = this.configService.get('LOG').BAILEYS; + + private phoneNumber: string; + + private chatwootService = new ChatwootService(waMonitor, this.configService); + + //private typebotService = new TypebotService(waMonitor); + private typebotService = new TypebotService(waMonitor, this.configService); + + private chamaaiService = new ChamaaiService(waMonitor, this.configService); + + public set instanceName(name: string) { + this.logger.verbose(`Initializing instance '${name}'`); + if (!name) { + this.logger.verbose('Instance name not found, generating random name with uuid'); + this.instance.name = v4(); + return; + } + this.instance.name = name; + this.logger.verbose(`Instance '${this.instance.name}' initialized`); + this.logger.verbose('Sending instance status to webhook'); + this.sendDataWebhook(Events.STATUS_INSTANCE, { + 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() { + this.logger.verbose('Getting instance name'); + return this.instance.name; + } + + public get wuid() { + this.logger.verbose('Getting remoteJid of instance'); + return this.instance.wuid; + } + + public async getProfileName() { + this.logger.verbose('Getting profile name'); + let profileName = this.client.user?.name ?? this.client.user?.verifiedName; + if (!profileName) { + this.logger.verbose('Profile name not found, trying to get from database'); + if (this.configService.get('DATABASE').ENABLED) { + this.logger.verbose('Database enabled, trying to get from database'); + const collection = dbserver + .getClient() + .db( + this.configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + + this.configService.get('DATABASE').CONNECTION.DB_PREFIX_FINAL_NAME + ) + .collection(this.instanceName); + const data = await collection.findOne({ _id: 'creds' }); + if (data) { + this.logger.verbose('Profile name found in database'); + const creds = JSON.parse(JSON.stringify(data), BufferJSON.reviver); + profileName = creds.me?.name || creds.me?.verifiedName; + } + } else if (existsSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'))) { + this.logger.verbose('Profile name found in file'); + const creds = JSON.parse( + readFileSync(join(INSTANCE_DIR, this.instanceName, 'creds.json'), { + encoding: 'utf-8', + }), + ); + profileName = creds.me?.name || creds.me?.verifiedName; + } + } + + this.logger.verbose(`Profile name: ${profileName}`); + return profileName; + } + + public async getProfileStatus() { + this.logger.verbose('Getting profile status'); + const status = await this.client.fetchStatus(this.instance.wuid); + + this.logger.verbose(`Profile status: ${status.status}`); + return status.status; + } + + public get profilePictureUrl() { + this.logger.verbose('Getting profile picture url'); + return this.instance.profilePictureUrl; + } + + public get qrCode(): wa.QrCode { + this.logger.verbose('Getting qrcode'); + + return { + pairingCode: this.instance.qrcode?.pairingCode, + code: this.instance.qrcode?.code, + base64: this.instance.qrcode?.base64, + }; + } + + private async loadWebhook() { + this.logger.verbose('Loading webhook'); + const data = await this.repository.webhook.find(this.instanceName); + this.localWebhook.url = data?.url; + this.logger.verbose(`Webhook url: ${this.localWebhook.url}`); + + this.localWebhook.enabled = data?.enabled; + this.logger.verbose(`Webhook enabled: ${this.localWebhook.enabled}`); + + this.localWebhook.events = data?.events; + this.logger.verbose(`Webhook events: ${this.localWebhook.events}`); + + this.localWebhook.webhook_by_events = data?.webhook_by_events; + this.logger.verbose(`Webhook by events: ${this.localWebhook.webhook_by_events}`); + + this.localWebhook.webhook_base64 = data?.webhook_base64; + this.logger.verbose(`Webhook by webhook_base64: ${this.localWebhook.webhook_base64}`); + + this.logger.verbose('Webhook loaded'); + } + + public async setWebhook(data: WebhookRaw) { + this.logger.verbose('Setting webhook'); + await this.repository.webhook.create(data, this.instanceName); + this.logger.verbose(`Webhook url: ${data.url}`); + this.logger.verbose(`Webhook events: ${data.events}`); + Object.assign(this.localWebhook, data); + this.logger.verbose('Webhook set'); + } + + public async findWebhook() { + this.logger.verbose('Finding webhook'); + const data = await this.repository.webhook.find(this.instanceName); + + if (!data) { + this.logger.verbose('Webhook not found'); + throw new NotFoundException('Webhook not found'); + } + + this.logger.verbose(`Webhook url: ${data.url}`); + this.logger.verbose(`Webhook events: ${data.events}`); + 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.localChatwoot.name_inbox = data?.name_inbox; + this.logger.verbose(`Chatwoot inbox name: ${this.localChatwoot.name_inbox}`); + + this.localChatwoot.sign_msg = data?.sign_msg; + this.logger.verbose(`Chatwoot sign msg: ${this.localChatwoot.sign_msg}`); + + this.localChatwoot.number = data?.number; + this.logger.verbose(`Chatwoot number: ${this.localChatwoot.number}`); + + this.localChatwoot.reopen_conversation = data?.reopen_conversation; + this.logger.verbose(`Chatwoot reopen conversation: ${this.localChatwoot.reopen_conversation}`); + + this.localChatwoot.conversation_pending = data?.conversation_pending; + this.logger.verbose(`Chatwoot conversation pending: ${this.localChatwoot.conversation_pending}`); + + 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}`); + this.logger.verbose(`Chatwoot sign msg: ${data.sign_msg}`); + this.logger.verbose(`Chatwoot reopen conversation: ${data.reopen_conversation}`); + this.logger.verbose(`Chatwoot conversation pending: ${data.conversation_pending}`); + + 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'); + return null; + } + + 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}`); + this.logger.verbose(`Chatwoot sign msg: ${data.sign_msg}`); + this.logger.verbose(`Chatwoot reopen conversation: ${data.reopen_conversation}`); + this.logger.verbose(`Chatwoot conversation pending: ${data.conversation_pending}`); + + return data; + } + + private async loadSettings() { + this.logger.verbose('Loading settings'); + const data = await this.repository.settings.find(this.instanceName); + this.localSettings.reject_call = data?.reject_call; + this.logger.verbose(`Settings reject_call: ${this.localSettings.reject_call}`); + + this.localSettings.msg_call = data?.msg_call; + this.logger.verbose(`Settings msg_call: ${this.localSettings.msg_call}`); + + this.localSettings.groups_ignore = data?.groups_ignore; + this.logger.verbose(`Settings groups_ignore: ${this.localSettings.groups_ignore}`); + + this.localSettings.always_online = data?.always_online; + this.logger.verbose(`Settings always_online: ${this.localSettings.always_online}`); + + this.localSettings.read_messages = data?.read_messages; + this.logger.verbose(`Settings read_messages: ${this.localSettings.read_messages}`); + + this.localSettings.read_status = data?.read_status; + this.logger.verbose(`Settings read_status: ${this.localSettings.read_status}`); + + this.logger.verbose('Settings loaded'); + } + + public async setSettings(data: SettingsRaw) { + this.logger.verbose('Setting settings'); + await this.repository.settings.create(data, this.instanceName); + this.logger.verbose(`Settings reject_call: ${data.reject_call}`); + this.logger.verbose(`Settings msg_call: ${data.msg_call}`); + this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); + this.logger.verbose(`Settings always_online: ${data.always_online}`); + this.logger.verbose(`Settings read_messages: ${data.read_messages}`); + this.logger.verbose(`Settings read_status: ${data.read_status}`); + Object.assign(this.localSettings, data); + this.logger.verbose('Settings set'); + + this.client?.ws?.close(); + } + + public async findSettings() { + this.logger.verbose('Finding settings'); + const data = await this.repository.settings.find(this.instanceName); + + if (!data) { + this.logger.verbose('Settings not found'); + return null; + } + + this.logger.verbose(`Settings url: ${data.reject_call}`); + this.logger.verbose(`Settings msg_call: ${data.msg_call}`); + this.logger.verbose(`Settings groups_ignore: ${data.groups_ignore}`); + this.logger.verbose(`Settings always_online: ${data.always_online}`); + this.logger.verbose(`Settings read_messages: ${data.read_messages}`); + this.logger.verbose(`Settings read_status: ${data.read_status}`); + return data; + } + + private async loadWebsocket() { + this.logger.verbose('Loading websocket'); + const data = await this.repository.websocket.find(this.instanceName); + + this.localWebsocket.enabled = data?.enabled; + this.logger.verbose(`Websocket enabled: ${this.localWebsocket.enabled}`); + + this.localWebsocket.events = data?.events; + this.logger.verbose(`Websocket events: ${this.localWebsocket.events}`); + + this.logger.verbose('Websocket loaded'); + } + + public async setWebsocket(data: WebsocketRaw) { + this.logger.verbose('Setting websocket'); + await this.repository.websocket.create(data, this.instanceName); + this.logger.verbose(`Websocket events: ${data.events}`); + Object.assign(this.localWebsocket, data); + this.logger.verbose('Websocket set'); + } + + public async findWebsocket() { + this.logger.verbose('Finding websocket'); + const data = await this.repository.websocket.find(this.instanceName); + + if (!data) { + this.logger.verbose('Websocket not found'); + throw new NotFoundException('Websocket not found'); + } + + this.logger.verbose(`Websocket events: ${data.events}`); + return data; + } + + private async loadOpenai() { + this.logger.verbose('Loading openai'); + const data = await this.repository.openai.find(this.instanceName); + + this.localOpenai.chave = data?.chave; + this.logger.verbose(`Openai chave: ${this.localOpenai.chave}`); + + this.localOpenai.enabled = data?.enabled; + this.logger.verbose(`Openai enabled: ${this.localOpenai.enabled}`); + + this.localOpenai.events = data?.events; + this.logger.verbose(`Openai events: ${this.localOpenai.events}`); + + this.logger.verbose('Openai loaded'); + } + + public async setOpenai(data: OpenaiRaw) { + this.logger.verbose('Setting openai'); + await this.repository.openai.create(data, this.instanceName); + this.logger.verbose(`Openai events: ${data.events}`); + Object.assign(this.localOpenai, data); + this.logger.verbose('Openai set'); + } + + public async setContactOpenai(data: ContactOpenaiRaw) { + this.logger.verbose('Setting openai'); + await this.repository.openai.createContact(data, this.instanceName); + + this.logger.verbose(`Openai events: ${data.enabled}`); + Object.assign(this.localOpenai, data); + this.logger.verbose('Openai set'); + } + + public async findContactOpenai() { + this.logger.verbose('Finding openai'); + const data = await this.repository.openai.findContactAll(this.instanceName); + + if (!data) { + this.logger.verbose('Openai not found'); + throw new NotFoundException('Openai not found'); + } + + return data; + } + + public async findOpenai() { + this.logger.verbose('Finding openai'); + const data = await this.repository.openai.find(this.instanceName); + + if (!data) { + this.logger.verbose('Openai not found'); + throw new NotFoundException('Openai not found'); + } + + this.logger.verbose(`Openai events: ${data.events}`); + return data; + } + + public async removeOpenaiQueues() { + this.logger.verbose('Removing openai'); + + if (this.localOpenai.enabled) { + removeQueues(this.instanceName, this.localOpenai.events); + } + } + + private async loadRabbitmq() { + this.logger.verbose('Loading rabbitmq'); + const data = await this.repository.rabbitmq.find(this.instanceName); + + this.localRabbitmq.enabled = data?.enabled; + this.logger.verbose(`Rabbitmq enabled: ${this.localRabbitmq.enabled}`); + + this.localRabbitmq.events = data?.events; + this.logger.verbose(`Rabbitmq events: ${this.localRabbitmq.events}`); + + this.logger.verbose('Rabbitmq loaded'); + } + + public async setRabbitmq(data: RabbitmqRaw) { + this.logger.verbose('Setting rabbitmq'); + await this.repository.rabbitmq.create(data, this.instanceName); + this.logger.verbose(`Rabbitmq events: ${data.events}`); + Object.assign(this.localRabbitmq, data); + this.logger.verbose('Rabbitmq set'); + } + + public async findRabbitmq() { + this.logger.verbose('Finding rabbitmq'); + const data = await this.repository.rabbitmq.find(this.instanceName); + + if (!data) { + this.logger.verbose('Rabbitmq not found'); + throw new NotFoundException('Rabbitmq not found'); + } + + this.logger.verbose(`Rabbitmq events: ${data.events}`); + return data; + } + + public async removeRabbitmqQueues() { + this.logger.verbose('Removing rabbitmq'); + + if (this.localRabbitmq.enabled) { + removeQueues(this.instanceName, this.localRabbitmq.events); + } + } + + + private async loadSqs() { + this.logger.verbose('Loading sqs'); + const data = await this.repository.sqs.find(this.instanceName); + + this.localSqs.enabled = data?.enabled; + this.logger.verbose(`Sqs enabled: ${this.localSqs.enabled}`); + + this.localSqs.events = data?.events; + this.logger.verbose(`Sqs events: ${this.localSqs.events}`); + + this.logger.verbose('Sqs loaded'); + } + + public async setSqs(data: SqsRaw) { + this.logger.verbose('Setting sqs'); + await this.repository.sqs.create(data, this.instanceName); + this.logger.verbose(`Sqs events: ${data.events}`); + Object.assign(this.localSqs, data); + this.logger.verbose('Sqs set'); + } + + public async findSqs() { + this.logger.verbose('Finding sqs'); + const data = await this.repository.sqs.find(this.instanceName); + + if (!data) { + this.logger.verbose('Sqs not found'); + throw new NotFoundException('Sqs not found'); + } + + this.logger.verbose(`Sqs events: ${data.events}`); + return data; + } + + public async removeSqsQueues() { + this.logger.verbose('Removing sqs'); + + if (this.localSqs.enabled) { + removeQueuesSQS(this.instanceName, this.localSqs.events); + } + } + + private async loadTypebot() { + this.logger.verbose('Loading typebot'); + const data = await this.repository.typebot.find(this.instanceName); + + this.localTypebot.enabled = data?.enabled; + this.logger.verbose(`Typebot enabled: ${this.localTypebot.enabled}`); + + this.localTypebot.url = data?.url; + this.logger.verbose(`Typebot url: ${this.localTypebot.url}`); + + this.localTypebot.typebot = data?.typebot; + this.logger.verbose(`Typebot typebot: ${this.localTypebot.typebot}`); + + this.localTypebot.expire = data?.expire; + this.logger.verbose(`Typebot expire: ${this.localTypebot.expire}`); + + this.localTypebot.keyword_finish = data?.keyword_finish; + this.logger.verbose(`Typebot keyword_finish: ${this.localTypebot.keyword_finish}`); + + this.localTypebot.delay_message = data?.delay_message; + this.logger.verbose(`Typebot delay_message: ${this.localTypebot.delay_message}`); + + 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'); + } + + public async setTypebot(data: TypebotRaw) { + this.logger.verbose('Setting typebot'); + await this.repository.typebot.create(data, this.instanceName); + this.logger.verbose(`Typebot typebot: ${data.typebot}`); + this.logger.verbose(`Typebot expire: ${data.expire}`); + 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'); + } + + public async findTypebot() { + this.logger.verbose('Finding typebot'); + const data = await this.repository.typebot.find(this.instanceName); + + if (!data) { + this.logger.verbose('Typebot not found'); + throw new NotFoundException('Typebot not found'); + } + + return data; + } + + private async loadProxy() { + this.logger.verbose('Loading proxy'); + const data = await this.repository.proxy.find(this.instanceName); + + this.localProxy.enabled = data?.enabled; + this.logger.verbose(`Proxy enabled: ${this.localProxy.enabled}`); + + this.localProxy.proxy = data?.proxy; + this.logger.verbose(`Proxy proxy: ${this.localProxy.proxy}`); + + this.logger.verbose('Proxy loaded'); + } + + public async setProxy(data: ProxyRaw, reload = true) { + this.logger.verbose('Setting proxy'); + await this.repository.proxy.create(data, this.instanceName); + this.logger.verbose(`Proxy proxy: ${data.proxy}`); + Object.assign(this.localProxy, data); + this.logger.verbose('Proxy set'); + //this.reloadConnection(); + if (reload) { + this.reloadConnection(); + } + //this.client?.ws?.close(); + } + + public async findProxy() { + this.logger.verbose('Finding proxy'); + const data = await this.repository.proxy.find(this.instanceName); + + if (!data) { + this.logger.verbose('Proxy not found'); + throw new NotFoundException('Proxy not found'); + } + + 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(event: Events, data: T, local = true) { + const webhookGlobal = this.configService.get('WEBHOOK'); + const webhookLocal = this.localWebhook.events; + const websocketLocal = this.localWebsocket.events; + const rabbitmqLocal = this.localRabbitmq.events; + const sqsLocal = this.localSqs.events; + const serverUrl = this.configService.get('SERVER').URL; + const we = event.replace(/[.-]/gm, '_').toUpperCase(); + const transformedWe = we.replace(/_/gm, '-').toLowerCase(); + const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds + const localISOTime = new Date(Date.now() - tzoffset).toISOString(); + const now = localISOTime; + + const expose = this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES; + const tokenStore = await this.repository.auth.find(this.instanceName); + const instanceApikey = tokenStore?.apikey || 'Apikey not found'; + + if (this.localRabbitmq.enabled) { + const amqp = getAMQP(); + + if (amqp) { + if (Array.isArray(rabbitmqLocal) && rabbitmqLocal.includes(we)) { + const exchangeName = this.instanceName ?? 'evolution_exchange'; + + amqp.assertExchange(exchangeName, 'topic', { + durable: true, + autoDelete: false, + }); + + const queueName = `${this.instanceName}.${event}`; + + amqp.assertQueue(queueName, { + durable: true, + autoDelete: false, + arguments: { + 'x-queue-type': 'quorum', + }, + }); + + amqp.bindQueue(queueName, exchangeName, event); + + const message = { + event, + instance: this.instance.name, + data, + server_url: serverUrl, + date_time: now, + sender: this.wuid, + }; + + if (expose && instanceApikey) { + message['apikey'] = instanceApikey; + } + + amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message))); + + if (this.configService.get('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); + } + } + } + } + + + if (this.localSqs.enabled) { + const sqs = getSQS(); + + if (sqs) { + if (Array.isArray(sqsLocal) && sqsLocal.includes(we)) { + const eventFormatted = `${event.replace('.', '_').toLowerCase()}`; + + const queueName = `${this.instanceName}_${eventFormatted}.fifo`; + + const sqsConfig = this.configService.get('SQS'); + + const sqsUrl = `https://sqs.${sqsConfig.REGION}.amazonaws.com/${sqsConfig.ACCOUNT_ID}/${queueName}`; + + const message = { + event, + instance: this.instance.name, + data, + server_url: serverUrl, + date_time: now, + sender: this.wuid, + }; + + if (expose && instanceApikey) { + message['apikey'] = instanceApikey; + } + + const params = { + MessageBody: JSON.stringify(message), + MessageGroupId: 'evolution', + MessageDeduplicationId: `${this.instanceName}_${eventFormatted}_${Date.now()}`, + QueueUrl: sqsUrl, + }; + + sqs.sendMessage(params, (err, data) => { + if (err) { + this.logger.error({ + local: WAStartupService.name + '.sendData-SQS', + message: err?.message, + hostName: err?.hostname, + code: err?.code, + stack: err?.stack, + name: err?.name, + url: queueName, + server_url: serverUrl, + }); + } else { + if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { + const logData = { + local: WAStartupService.name + '.sendData-SQS', + 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); + } + } + }); + } + } + } + + if (this.configService.get('WEBSOCKET')?.ENABLED && this.localWebsocket.enabled) { + this.logger.verbose('Sending data to websocket on channel: ' + this.instance.name); + if (Array.isArray(websocketLocal) && websocketLocal.includes(we)) { + this.logger.verbose('Sending data to websocket on event: ' + event); + const io = getIO(); + + const message = { + event, + instance: this.instance.name, + data, + server_url: serverUrl, + date_time: now, + sender: this.wuid, + }; + + if (expose && instanceApikey) { + message['apikey'] = instanceApikey; + } + + 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').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); + } + } + } + + const globalApiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; + + if (local) { + if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) { + this.logger.verbose('Sending data to webhook local'); + let baseURL: string; + + if (this.localWebhook.webhook_by_events) { + baseURL = `${this.localWebhook.url}/${transformedWe}`; + } else { + baseURL = this.localWebhook.url; + } + + if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { + const logData = { + local: WAStartupService.name + '.sendDataWebhook-local', + url: baseURL, + event, + instance: this.instance.name, + data, + destination: this.localWebhook.url, + date_time: now, + sender: this.wuid, + server_url: serverUrl, + apikey: (expose && instanceApikey) || null, + }; + + if (expose && instanceApikey) { + logData['apikey'] = instanceApikey; + } + + this.logger.log(logData); + } + + try { + if (this.localWebhook.enabled && isURL(this.localWebhook.url, { require_tld: false })) { + const httpService = axios.create({ baseURL }); + const postData = { + event, + instance: this.instance.name, + data, + destination: this.localWebhook.url, + date_time: now, + sender: this.wuid, + server_url: serverUrl, + }; + + if (expose && instanceApikey) { + postData['apikey'] = instanceApikey; + } + + await httpService.post('', postData); + } + } catch (error) { + this.logger.error({ + local: WAStartupService.name + '.sendDataWebhook-local', + message: error?.message, + hostName: error?.hostname, + syscall: error?.syscall, + code: error?.code, + error: error?.errno, + stack: error?.stack, + name: error?.name, + url: baseURL, + server_url: serverUrl, + }); + } + } + } + + if (webhookGlobal.GLOBAL?.ENABLED) { + if (webhookGlobal.EVENTS[we]) { + this.logger.verbose('Sending data to webhook global'); + const globalWebhook = this.configService.get('WEBHOOK').GLOBAL; + + let globalURL; + + if (webhookGlobal.GLOBAL.WEBHOOK_BY_EVENTS) { + globalURL = `${globalWebhook.URL}/${transformedWe}`; + } else { + globalURL = globalWebhook.URL; + } + + const localUrl = this.localWebhook.url; + + if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { + const logData = { + local: WAStartupService.name + '.sendDataWebhook-global', + url: globalURL, + event, + instance: this.instance.name, + data, + destination: localUrl, + date_time: now, + sender: this.wuid, + server_url: serverUrl, + }; + + if (expose && globalApiKey) { + logData['apikey'] = globalApiKey; + } + + this.logger.log(logData); + } + + try { + if (globalWebhook && globalWebhook?.ENABLED && isURL(globalURL)) { + const httpService = axios.create({ baseURL: globalURL }); + const postData = { + event, + instance: this.instance.name, + data, + destination: localUrl, + date_time: now, + sender: this.wuid, + server_url: serverUrl, + }; + + if (expose && globalApiKey) { + postData['apikey'] = globalApiKey; + } + + await httpService.post('', postData); + } + } catch (error) { + this.logger.error({ + local: WAStartupService.name + '.sendDataWebhook-global', + message: error?.message, + hostName: error?.hostname, + syscall: error?.syscall, + code: error?.code, + error: error?.errno, + stack: error?.stack, + name: error?.name, + url: globalURL, + server_url: serverUrl, + }); + } + } + } + } + + private async connectionUpdate({ qr, connection, lastDisconnect }: Partial) { + this.logger.verbose('Connection update'); + if (qr) { + this.logger.verbose('QR code found'); + if (this.instance.qrcode.count === this.configService.get('QRCODE').LIMIT) { + this.logger.verbose('QR code limit reached'); + + this.logger.verbose('Sending data to webhook in event QRCODE_UPDATED'); + this.sendDataWebhook(Events.QRCODE_UPDATED, { + message: 'QR code limit reached, please login again', + 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, + state: 'refused', + statusReason: DisconnectReason.connectionClosed, + }); + + this.logger.verbose('endSession defined as true'); + this.endSession = true; + + this.logger.verbose('Emmiting event logout.instance'); + return this.eventEmitter.emit('no.connection', this.instance.name); + } + + this.logger.verbose('Incrementing QR code count'); + this.instance.qrcode.count++; + + const color = this.configService.get('QRCODE').COLOR; + + const optsQrcode: QRCodeToDataURLOptions = { + margin: 3, + scale: 4, + errorCorrectionLevel: 'H', + color: { light: '#ffffff', dark: color }, + }; + + if (this.phoneNumber) { + await delay(2000); + this.instance.qrcode.pairingCode = await this.client.requestPairingCode(this.phoneNumber); + } else { + this.instance.qrcode.pairingCode = null; + } + + this.logger.verbose('Generating QR code'); + qrcode.toDataURL(qr, optsQrcode, (error, base64) => { + if (error) { + this.logger.error('Qrcode generate failed:' + error.toString()); + return; + } + + this.instance.qrcode.base64 = base64; + this.instance.qrcode.code = qr; + + this.sendDataWebhook(Events.QRCODE_UPDATED, { + qrcode: { + instance: this.instance.name, + pairingCode: this.instance.qrcode.pairingCode, + code: qr, + base64, + }, + }); + + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.QRCODE_UPDATED, + { instanceName: this.instance.name }, + { + qrcode: { + instance: this.instance.name, + pairingCode: this.instance.qrcode.pairingCode, + code: qr, + base64, + }, + }, + ); + } + }); + + this.logger.verbose('Generating QR code in terminal'); + qrcodeTerminal.generate(qr, { small: true }, (qrcode) => + this.logger.log( + `\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` + + qrcode, + ), + ); + } + + if (connection) { + this.logger.verbose('Connection found'); + this.stateConnection = { + state: connection, + statusReason: (lastDisconnect?.error as Boom)?.output?.statusCode ?? 200, + }; + + this.logger.verbose('Sending data to webhook in event CONNECTION_UPDATE'); + this.sendDataWebhook(Events.CONNECTION_UPDATE, { + instance: this.instance.name, + ...this.stateConnection, + }); + } + + if (connection === 'close') { + this.logger.verbose('Connection closed'); + const shouldReconnect = (lastDisconnect.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut; + if (shouldReconnect) { + this.logger.verbose('Reconnecting to whatsapp'); + await this.connectToWhatsapp(); + } else { + this.logger.verbose('Do not reconnect to whatsapp'); + this.logger.verbose('Sending data to webhook in event STATUS_INSTANCE'); + this.sendDataWebhook(Events.STATUS_INSTANCE, { + instance: this.instance.name, + status: 'closed', + }); + + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.STATUS_INSTANCE, + { instanceName: this.instance.name }, + { + instance: this.instance.name, + status: 'closed', + }, + ); + } + + this.logger.verbose('Emittin event logout.instance'); + this.eventEmitter.emit('logout.instance', this.instance.name, 'inner'); + this.client?.ws?.close(); + this.client.end(new Error('Close connection')); + this.logger.verbose('Connection closed'); + } + } + + if (connection === 'open') { + this.logger.verbose('Connection opened'); + this.instance.wuid = this.client.user.id.replace(/:\d+/, ''); + this.instance.profilePictureUrl = (await this.profilePicture(this.instance.wuid)).profilePictureUrl; + this.logger.info( + ` + ┌──────────────────────────────┐ + │ CONNECTED TO WHATSAPP │ + └──────────────────────────────┘`.replace(/^ +/gm, ' '), + ); + + if (this.localChatwoot.enabled) { + this.chatwootService.eventWhatsapp( + Events.CONNECTION_UPDATE, + { instanceName: this.instance.name }, + { + instance: this.instance.name, + status: 'open', + }, + ); + } + } + } + + private async getMessage(key: proto.IMessageKey, full = false) { + this.logger.verbose('Getting message with key: ' + JSON.stringify(key)); + try { + const webMessageInfo = (await this.repository.message.find({ + where: { owner: this.instance.name, key: { id: key.id } }, + })) as unknown as proto.IWebMessageInfo[]; + if (full) { + this.logger.verbose('Returning full message'); + return webMessageInfo[0]; + } + if (webMessageInfo[0].message?.pollCreationMessage) { + this.logger.verbose('Returning poll message'); + const messageSecretBase64 = webMessageInfo[0].message?.messageContextInfo?.messageSecret; + + if (typeof messageSecretBase64 === 'string') { + const messageSecret = Buffer.from(messageSecretBase64, 'base64'); + + const msg = { + messageContextInfo: { + messageSecret, + }, + pollCreationMessage: webMessageInfo[0].message?.pollCreationMessage, + }; + + return msg; + } + } + + this.logger.verbose('Returning message'); + return webMessageInfo[0].message; + } catch (error) { + return { conversation: '' }; + } + } + + private cleanStore() { + this.logger.verbose('Cronjob to clean store initialized'); + const cleanStore = this.configService.get('CLEAN_STORE'); + const database = this.configService.get('DATABASE'); + if (cleanStore?.CLEANING_INTERVAL && !database.ENABLED) { + this.logger.verbose('Cronjob to clean store enabled'); + setInterval(() => { + try { + for (const [key, value] of Object.entries(cleanStore)) { + if (value === true) { + execSync( + `rm -rf ${join(this.storePath, key.toLowerCase().replace('_', '-'), this.instance.name)}/*.json`, + ); + this.logger.verbose( + `Cleaned ${join(this.storePath, key.toLowerCase().replace('_', '-'), this.instance.name)}/*.json`, + ); + } + } + } catch (error) { + this.logger.error(error); + } + }, (cleanStore?.CLEANING_INTERVAL ?? 3600) * 1000); + } + } + + private async defineAuthState() { + this.logger.verbose('Defining auth state'); + const db = this.configService.get('DATABASE'); + const redis = this.configService.get('REDIS'); + + if (redis?.ENABLED) { + this.logger.verbose('Redis enabled'); + this.cache.reference = this.instance.name; + return await useMultiFileAuthStateRedisDb(this.cache); + } + + if (db.SAVE_DATA.INSTANCE && db.ENABLED) { + this.logger.verbose('Database enabled'); + return await useMultiFileAuthStateDb(this.instance.name); + } + + this.logger.verbose('Store file enabled'); + return await useMultiFileAuthState(join(INSTANCE_DIR, this.instance.name)); + } + + public async connectToWhatsapp(number?: string): Promise { + this.logger.verbose('Connecting to whatsapp'); + try { + this.loadWebhook(); + this.loadChatwoot(); + this.loadSettings(); + this.loadWebsocket(); + this.loadRabbitmq(); + this.loadSqs(); + this.loadTypebot(); + this.loadProxy(); + this.loadChamaai(); + + this.instance.authState = await this.defineAuthState(); + + const { version } = await fetchLatestBaileysVersion(); + this.logger.verbose('Baileys version: ' + version); + const session = this.configService.get('CONFIG_SESSION_PHONE'); + const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; + this.logger.verbose('Browser: ' + JSON.stringify(browser)); + + let options; + + if (this.localProxy.enabled) { + this.logger.verbose('Proxy enabled'); + // options = { + // agent: new ProxyAgent(this.localProxy.proxy as any), + // fetchAgent: new ProxyAgent(this.localProxy.proxy as any), + // }; + if (this.localProxy.proxy.includes('proxyscrape')) { + const response = await axios.get(this.localProxy.proxy); + const text = response.data; + const proxyUrls = text.split('\r\n'); + const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length)); + const proxyUrl = 'http://' + proxyUrls[rand]; + console.log(proxyUrl); + options = { + agent: new ProxyAgent(proxyUrl as any), + }; + } else { + options = { + agent: new ProxyAgent(this.localProxy.proxy as any), + }; + } + } + + const socketConfig: UserFacingSocketConfig = { + ...options, + auth: { + creds: this.instance.authState.state.creds, + keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), + }, + logger: P({ level: this.logBaileys }), + printQRInTerminal: false, + browser, + version, + markOnlineOnConnect: this.localSettings.always_online, + retryRequestDelayMs: 10, + connectTimeoutMs: 60_000, + qrTimeout: 40_000, + defaultQueryTimeoutMs: undefined, + emitOwnEvents: false, + msgRetryCounterCache: this.msgRetryCounterCache, + getMessage: async (key) => (await this.getMessage(key)) as Promise, + generateHighQualityLinkPreview: true, + syncFullHistory: true, + userDevicesCache: this.userDevicesCache, + transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 10 }, + patchMessageBeforeSending: (message) => { + const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage); + if (requiresPatch) { + message = { + viewOnceMessageV2: { + message: { + messageContextInfo: { + deviceListMetadataVersion: 2, + deviceListMetadata: {}, + }, + ...message, + }, + }, + }; + } + + return message; + }, + }; + + this.endSession = false; + + this.logger.verbose('Creating socket'); + + this.client = makeWASocket(socketConfig); + + this.logger.verbose('Socket created'); + + this.eventHandler(); + + this.logger.verbose('Socket event handler initialized'); + + this.phoneNumber = number; + + return this.client; + } catch (error) { + this.logger.error(error); + throw new InternalServerErrorException(error?.toString()); + } + } + + public async reloadConnection(): Promise { + try { + this.instance.authState = await this.defineAuthState(); + + const { version } = await fetchLatestBaileysVersion(); + const session = this.configService.get('CONFIG_SESSION_PHONE'); + const browser: WABrowserDescription = [session.CLIENT, session.NAME, release()]; + + let options; + + if (this.localProxy.enabled) { + this.logger.verbose('Proxy enabled'); + options = { + agent: new ProxyAgent(this.localProxy.proxy as any), + fetchAgent: new ProxyAgent(this.localProxy.proxy as any), + }; + } + + const socketConfig: UserFacingSocketConfig = { + ...options, + auth: { + creds: this.instance.authState.state.creds, + keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' }) as any), + }, + logger: P({ level: this.logBaileys }), + printQRInTerminal: false, + browser, + version, + markOnlineOnConnect: this.localSettings.always_online, + connectTimeoutMs: 60_000, + qrTimeout: 40_000, + defaultQueryTimeoutMs: undefined, + emitOwnEvents: false, + msgRetryCounterCache: this.msgRetryCounterCache, + getMessage: async (key) => (await this.getMessage(key)) as Promise, + generateHighQualityLinkPreview: true, + syncFullHistory: true, + userDevicesCache: this.userDevicesCache, + transactionOpts: { maxCommitRetries: 1, delayBetweenTriesMs: 10 }, + patchMessageBeforeSending: (message) => { + const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage); + if (requiresPatch) { + message = { + viewOnceMessageV2: { + message: { + messageContextInfo: { + deviceListMetadataVersion: 2, + deviceListMetadata: {}, + }, + ...message, + }, + }, + }; + } + + return message; + }, + }; + + this.client = makeWASocket(socketConfig); + + return this.client; + } catch (error) { + this.logger.error(error); + throw new InternalServerErrorException(error?.toString()); + } + } + + private readonly chatHandle = { + 'chats.upsert': async (chats: Chat[], database: Database) => { + this.logger.verbose('Event received: chats.upsert'); + + this.logger.verbose('Finding chats in database'); + const chatsRepository = await this.repository.chat.find({ + where: { owner: this.instance.name }, + }); + + this.logger.verbose('Verifying if chats exists in database to insert'); + const chatsRaw: ChatRaw[] = []; + for await (const chat of chats) { + if (chatsRepository.find((cr) => cr.id === chat.id)) { + continue; + } + + chatsRaw.push({ id: chat.id, owner: this.instance.wuid }); + } + + this.logger.verbose('Sending data to webhook in event CHATS_UPSERT'); + this.sendDataWebhook(Events.CHATS_UPSERT, chatsRaw); + + this.logger.verbose('Inserting chats in database'); + this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS); + }, + + 'chats.update': async ( + chats: Partial< + proto.IConversation & { + lastMessageRecvTimestamp?: number; + } & { + conditional: (bufferedData: BufferedEventData) => boolean; + } + >[], + ) => { + this.logger.verbose('Event received: chats.update'); + const chatsRaw: ChatRaw[] = chats.map((chat) => { + return { id: chat.id, owner: this.instance.wuid }; + }); + + this.logger.verbose('Sending data to webhook in event CHATS_UPDATE'); + this.sendDataWebhook(Events.CHATS_UPDATE, chatsRaw); + }, + + 'chats.delete': async (chats: string[]) => { + this.logger.verbose('Event received: chats.delete'); + + this.logger.verbose('Deleting chats in database'); + chats.forEach( + async (chat) => + await this.repository.chat.delete({ + where: { owner: this.instance.name, id: chat }, + }), + ); + + this.logger.verbose('Sending data to webhook in event CHATS_DELETE'); + this.sendDataWebhook(Events.CHATS_DELETE, [...chats]); + }, + }; + + private readonly contactHandle = { + 'contacts.upsert': async (contacts: Contact[], database: Database) => { + this.logger.verbose('Event received: contacts.upsert'); + + this.logger.verbose('Finding contacts in database'); + const contactsRepository = await this.repository.contact.find({ + where: { owner: this.instance.name }, + }); + + this.logger.verbose('Verifying if contacts exists in database to insert'); + const contactsRaw: ContactRaw[] = []; + for await (const contact of contacts) { + if (contactsRepository.find((cr) => cr.id === contact.id)) { + continue; + } + + contactsRaw.push({ + id: contact.id, + pushName: contact?.name || contact?.verifiedName, + profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl, + owner: this.instance.name, + }); + } + + this.logger.verbose('Sending data to webhook in event CONTACTS_UPSERT'); + this.sendDataWebhook(Events.CONTACTS_UPSERT, contactsRaw); + + this.logger.verbose('Inserting contacts in database'); + this.repository.contact.insert(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS); + }, + + 'contacts.update': async (contacts: Partial[], database: Database) => { + this.logger.verbose('Event received: contacts.update'); + + this.logger.verbose('Verifying if contacts exists in database to update'); + const contactsRaw: ContactRaw[] = []; + for await (const contact of contacts) { + contactsRaw.push({ + id: contact.id, + pushName: contact?.name ?? contact?.verifiedName, + profilePictureUrl: (await this.profilePicture(contact.id)).profilePictureUrl, + owner: this.instance.name, + }); + } + + this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE'); + this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw); + + this.logger.verbose('Updating contacts in database'); + this.repository.contact.update(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS); + }, + }; + + private readonly messageHandle = { + 'messaging-history.set': async ( + { + messages, + chats, + isLatest, + }: { + chats: Chat[]; + contacts: Contact[]; + messages: proto.IWebMessageInfo[]; + isLatest: boolean; + }, + database: Database, + ) => { + this.logger.verbose('Event received: messaging-history.set'); + if (isLatest) { + this.logger.verbose('isLatest defined as true'); + const chatsRaw: ChatRaw[] = chats.map((chat) => { + return { + id: chat.id, + owner: this.instance.name, + lastMsgTimestamp: chat.lastMessageRecvTimestamp, + }; + }); + + this.logger.verbose('Sending data to webhook in event CHATS_SET'); + this.sendDataWebhook(Events.CHATS_SET, chatsRaw); + + this.logger.verbose('Inserting chats in database'); + this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS); + } + + const messagesRaw: MessageRaw[] = []; + const messagesRepository = await this.repository.message.find({ + where: { owner: this.instance.name }, + }); + for await (const [, m] of Object.entries(messages)) { + if (!m.message) { + continue; + } + if (messagesRepository.find((mr) => mr.owner === this.instance.name && mr.key.id === m.key.id)) { + continue; + } + + if (Long.isLong(m?.messageTimestamp)) { + m.messageTimestamp = m.messageTimestamp?.toNumber(); + } + + messagesRaw.push({ + key: m.key, + pushName: m.pushName, + participant: m.participant, + message: { ...m.message }, + messageType: getContentType(m.message), + messageTimestamp: m.messageTimestamp as number, + owner: this.instance.name, + }); + } + + this.logger.verbose('Sending data to webhook in event MESSAGES_SET'); + this.sendDataWebhook(Events.MESSAGES_SET, [...messagesRaw]); + + messages = undefined; + }, + + 'messages.upsert': async ( + { + messages, + type, + }: { + messages: proto.IWebMessageInfo[]; + type: MessageUpsertType; + }, + database: Database, + settings: SettingsRaw, + ) => { + this.logger.verbose('Event received: messages.upsert'); + const received = messages[0]; + + if (type !== 'notify' || received.message?.protocolMessage || received.message?.pollUpdateMessage) { + this.logger.verbose('message rejected'); + return; + } + + if (Long.isLong(received.messageTimestamp)) { + received.messageTimestamp = received.messageTimestamp?.toNumber(); + } + + if (settings?.groups_ignore && received.key.remoteJid.includes('@g.us')) { + this.logger.verbose('group ignored'); + return; + } + + let messageRaw: MessageRaw; + + if (received.key.remoteJid.includes('@g.us')) { + }else{ + + if (messages[0].message.conversation){ + + const openai = await this.repository.openai_contact.find(this.instance.name); + + if (openai && openai?.chave && openai?.enabled == true && openai?.prompts){ + + const customerPhone = `+${received.key.remoteJid.replace("@s.whatsapp.net", "")}` + const customerName = messages[0].pushName + + const contactOpenaiC = await this.repository.openai_contact.findContact(this.instance.name, customerPhone); + if (!contactOpenaiC){ + var data = new ContactOpenaiDto; + data.contact = customerPhone; + data.enabled = true; + data.owner = this.instance.name; + + await this.repository.openai_contact.createContact(data, this.instance.name) + } + + const contactOpenai = await this.repository.openai_contact.findContact(this.instance.name, customerPhone); + + if(contactOpenai?.enabled == true){ + + const customerKey = `instancia:${this.instance.name}customer:${customerPhone}:chat` + const orderCode = `#sk-${("00000" + Math.random()).slice(-5)}` + + const lastChat = JSON.parse((await redis.get(customerKey)) || "{}") + var storeName = 'Nome'; + const customerChat: CustomerChat = + lastChat?.status === "open" + ? (lastChat as CustomerChat) + : { + status: "open", + orderCode, + chatAt: new Date().toISOString(), + customer: { + name: customerName, + phone: customerPhone, + }, + messages: [ + { + role: "system", + content: initPrompt(storeName, orderCode, openai?.prompts), + }, + ], + orderSummary: "", + } + + this.logger.verbose(customerPhone+" 👤 "+ messages[0].message.conversation) + + customerChat.messages.push({ + role: "user", + content: messages[0].message.conversation, + }) + + const content = + (await completion(openai.chave, + customerChat.messages)) || "Não entendi..." + + customerChat.messages.push({ + role: "assistant", + content, + }) + + this.logger.verbose(customerPhone + " 🤖 "+ content) + + var dadosMessage = { + textMessage: received.message.conversation, + }; + + let mentions: string[]; + + const linkPreview = true; + + let quoted: WAMessage; + + const option = { + quoted, + }; + + this.client.sendMessage( + received.key.remoteJid, + { + text: content, + mentions, + linkPreview: linkPreview, + } as unknown as AnyMessageContent, + option as unknown as MiscMessageGenerationOptions, + ); + + if ( + customerChat.status === "open" && + content.match(customerChat.orderCode) + ) { + customerChat.status = "closed" + + customerChat.messages.push({ + role: "user", + content: + "Gere um resumo de pedido para registro no sistema da pizzaria, quem está solicitando é um robô.", + }) + + const content = + (await completion(openai.chave,customerChat.messages)) || "Não entendi..." + + this.logger.verbose(customerPhone + " 📦 "+ content) + + customerChat.orderSummary = content + } + redis.set(customerKey, JSON.stringify(customerChat)) + + this.logger.verbose(received); + + } + } + + } + + } + + if ( + (this.localWebhook.webhook_base64 === true && received?.message.documentMessage) || + received?.message.imageMessage + ) { + const buffer = await downloadMediaMessage( + { key: received.key, message: received?.message }, + 'buffer', + {}, + { + logger: P({ level: 'error' }) as any, + reuploadRequest: this.client.updateMediaMessage, + }, + ); + messageRaw = { + key: received.key, + pushName: received.pushName, + message: { + ...received.message, + base64: buffer ? buffer.toString('base64') : undefined, + }, + messageType: getContentType(received.message), + messageTimestamp: received.messageTimestamp as number, + owner: this.instance.name, + source: getDevice(received.key.id), + }; + } else { + messageRaw = { + key: received.key, + pushName: received.pushName, + message: { ...received.message }, + messageType: getContentType(received.message), + messageTimestamp: received.messageTimestamp as number, + owner: this.instance.name, + source: getDevice(received.key.id), + }; + } + + if (this.localSettings.read_messages && received.key.id !== 'status@broadcast') { + await this.client.readMessages([received.key]); + } + + if (this.localSettings.read_status && received.key.id === 'status@broadcast') { + await this.client.readMessages([received.key]); + } + + this.logger.log(messageRaw); + + this.logger.verbose('Sending data to webhook in event MESSAGES_UPSERT'); + this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); + + if (this.localChatwoot.enabled) { + await this.chatwootService.eventWhatsapp( + Events.MESSAGES_UPSERT, + { instanceName: this.instance.name }, + messageRaw, + ); + } + + //if (this.localTypebot.enabled) { + const typebotSessionRemoteJid = this.localTypebot.sessions?.find( + (session) => session.remoteJid === received.key.remoteJid, + ); + + if (this.localTypebot.enabled || typebotSessionRemoteJid) { + 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, + ); + } + + this.logger.verbose('Inserting message in database'); + await this.repository.message.insert([messageRaw], this.instance.name, database.SAVE_DATA.NEW_MESSAGE); + + this.logger.verbose('Verifying contact from message'); + const contact = await this.repository.contact.find({ + where: { owner: this.instance.name, id: received.key.remoteJid }, + }); + + const contactRaw: ContactRaw = { + id: received.key.remoteJid, + pushName: received.pushName, + profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl, + owner: this.instance.name, + }; + + if (contactRaw.id === 'status@broadcast') { + this.logger.verbose('Contact is status@broadcast'); + return; + } + + if (contact?.length) { + this.logger.verbose('Contact found in database'); + const contactRaw: ContactRaw = { + id: received.key.remoteJid, + pushName: contact[0].pushName, + profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl, + owner: this.instance.name, + }; + + this.logger.verbose('Sending data to webhook in event CONTACTS_UPDATE'); + this.sendDataWebhook(Events.CONTACTS_UPDATE, contactRaw); + + if (this.localChatwoot.enabled) { + await this.chatwootService.eventWhatsapp( + Events.CONTACTS_UPDATE, + { instanceName: this.instance.name }, + contactRaw, + ); + } + + this.logger.verbose('Updating contact in database'); + await this.repository.contact.update([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS); + return; + } + + this.logger.verbose('Contact not found in database'); + + this.logger.verbose('Sending data to webhook in event CONTACTS_UPSERT'); + this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw); + + this.logger.verbose('Inserting contact in database'); + this.repository.contact.insert([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS); + }, + + 'messages.update': async (args: WAMessageUpdate[], database: Database, settings: SettingsRaw) => { + this.logger.verbose('Event received: messages.update'); + const status: Record = { + 0: 'ERROR', + 1: 'PENDING', + 2: 'SERVER_ACK', + 3: 'DELIVERY_ACK', + 4: 'READ', + 5: 'PLAYED', + }; + for await (const { key, update } of args) { + if (settings?.groups_ignore && key.remoteJid.includes('@g.us')) { + this.logger.verbose('group ignored'); + return; + } + if (key.remoteJid !== 'status@broadcast' && !key?.remoteJid?.match(/(:\d+)/)) { + this.logger.verbose('Message update is valid'); + + let pollUpdates: any; + if (update.pollUpdates) { + this.logger.verbose('Poll update found'); + + this.logger.verbose('Getting poll message'); + const pollCreation = await this.getMessage(key); + this.logger.verbose(pollCreation); + + if (pollCreation) { + this.logger.verbose('Getting aggregate votes in poll message'); + pollUpdates = getAggregateVotesInPollMessage({ + message: pollCreation as proto.IMessage, + pollUpdates: update.pollUpdates, + }); + } + } + + if (status[update.status] === 'READ' && !key.fromMe) return; + + if (update.message === null && update.status === undefined) { + this.logger.verbose('Message deleted'); + + this.logger.verbose('Sending data to webhook in event MESSAGE_DELETE'); + this.sendDataWebhook(Events.MESSAGES_DELETE, key); + + const message: MessageUpdateRaw = { + ...key, + status: 'DELETED', + datetime: Date.now(), + owner: this.instance.name, + }; + + this.logger.verbose(message); + + this.logger.verbose('Inserting message in database'); + await this.repository.messageUpdate.insert( + [message], + this.instance.name, + database.SAVE_DATA.MESSAGE_UPDATE, + ); + return; + } + + const message: MessageUpdateRaw = { + ...key, + status: status[update.status], + datetime: Date.now(), + owner: this.instance.name, + pollUpdates, + }; + + this.logger.verbose(message); + + this.logger.verbose('Sending data to webhook in event MESSAGES_UPDATE'); + this.sendDataWebhook(Events.MESSAGES_UPDATE, message); + + this.logger.verbose('Inserting message in database'); + this.repository.messageUpdate.insert([message], this.instance.name, database.SAVE_DATA.MESSAGE_UPDATE); + } + } + }, + }; + + private readonly groupHandler = { + 'groups.upsert': (groupMetadata: GroupMetadata[]) => { + this.logger.verbose('Event received: groups.upsert'); + + this.logger.verbose('Sending data to webhook in event GROUPS_UPSERT'); + this.sendDataWebhook(Events.GROUPS_UPSERT, groupMetadata); + }, + + 'groups.update': (groupMetadataUpdate: Partial[]) => { + this.logger.verbose('Event received: groups.update'); + + this.logger.verbose('Sending data to webhook in event GROUPS_UPDATE'); + this.sendDataWebhook(Events.GROUPS_UPDATE, groupMetadataUpdate); + }, + + 'group-participants.update': (participantsUpdate: { + id: string; + participants: string[]; + action: ParticipantAction; + }) => { + this.logger.verbose('Event received: group-participants.update'); + + this.logger.verbose('Sending data to webhook in event GROUP_PARTICIPANTS_UPDATE'); + this.sendDataWebhook(Events.GROUP_PARTICIPANTS_UPDATE, participantsUpdate); + }, + }; + + private eventHandler() { + this.logger.verbose('Initializing event handler'); + this.client.ev.process(async (events) => { + if (!this.endSession) { + const database = this.configService.get('DATABASE'); + const settings = await this.findSettings(); + + if (events.call) { + this.logger.verbose('Listening event: call'); + const call = events.call[0]; + + if (settings?.reject_call && call.status == 'offer') { + this.logger.verbose('Rejecting call'); + this.client.rejectCall(call.id, call.from); + } + + if (settings?.msg_call?.trim().length > 0 && call.status == 'offer') { + this.logger.verbose('Sending message in call'); + const msg = await this.client.sendMessage(call.from, { + text: settings.msg_call, + }); + + this.logger.verbose('Sending data to event messages.upsert'); + this.client.ev.emit('messages.upsert', { + messages: [msg], + type: 'notify', + }); + } + + this.logger.verbose('Sending data to webhook in event CALL'); + this.sendDataWebhook(Events.CALL, call); + } + + if (events['connection.update']) { + this.logger.verbose('Listening event: connection.update'); + this.connectionUpdate(events['connection.update']); + } + + if (events['creds.update']) { + this.logger.verbose('Listening event: creds.update'); + this.instance.authState.saveCreds(); + } + + if (events['messaging-history.set']) { + this.logger.verbose('Listening event: messaging-history.set'); + const payload = events['messaging-history.set']; + this.messageHandle['messaging-history.set'](payload, database); + } + + if (events['messages.upsert']) { + this.logger.verbose('Listening event: messages.upsert'); + const payload = events['messages.upsert']; + this.messageHandle['messages.upsert'](payload, database, settings); + } + + if (events['messages.update']) { + this.logger.verbose('Listening event: messages.update'); + const payload = events['messages.update']; + this.messageHandle['messages.update'](payload, database, settings); + } + + if (events['presence.update']) { + this.logger.verbose('Listening event: presence.update'); + const payload = events['presence.update']; + + if (settings.groups_ignore && payload.id.includes('@g.us')) { + this.logger.verbose('group ignored'); + return; + } + this.sendDataWebhook(Events.PRESENCE_UPDATE, payload); + } + + if (!settings?.groups_ignore) { + if (events['groups.upsert']) { + this.logger.verbose('Listening event: groups.upsert'); + const payload = events['groups.upsert']; + this.groupHandler['groups.upsert'](payload); + } + + if (events['groups.update']) { + this.logger.verbose('Listening event: groups.update'); + const payload = events['groups.update']; + this.groupHandler['groups.update'](payload); + } + + if (events['group-participants.update']) { + this.logger.verbose('Listening event: group-participants.update'); + const payload = events['group-participants.update']; + this.groupHandler['group-participants.update'](payload); + } + } + + if (events['chats.upsert']) { + this.logger.verbose('Listening event: chats.upsert'); + const payload = events['chats.upsert']; + this.chatHandle['chats.upsert'](payload, database); + } + + if (events['chats.update']) { + this.logger.verbose('Listening event: chats.update'); + const payload = events['chats.update']; + this.chatHandle['chats.update'](payload); + } + + if (events['chats.delete']) { + this.logger.verbose('Listening event: chats.delete'); + const payload = events['chats.delete']; + this.chatHandle['chats.delete'](payload); + } + + if (events['contacts.upsert']) { + this.logger.verbose('Listening event: contacts.upsert'); + const payload = events['contacts.upsert']; + this.contactHandle['contacts.upsert'](payload, database); + } + + if (events['contacts.update']) { + this.logger.verbose('Listening event: contacts.update'); + const payload = events['contacts.update']; + this.contactHandle['contacts.update'](payload, database); + } + } + }); + } + + // Check if the number is MX or AR + private formatMXOrARNumber(jid: string): string { + const countryCode = jid.substring(0, 2); + + if (Number(countryCode) === 52 || Number(countryCode) === 54) { + if (jid.length === 13) { + const number = countryCode + jid.substring(3); + return number; + } + + return jid; + } + return jid; + } + + // Check if the number is br + private formatBRNumber(jid: string) { + const regexp = new RegExp(/^(\d{2})(\d{2})\d{1}(\d{8})$/); + if (regexp.test(jid)) { + const match = regexp.exec(jid); + if (match && match[1] === '55') { + const joker = Number.parseInt(match[3][0]); + const ddd = Number.parseInt(match[2]); + if (joker < 7 || ddd < 31) { + return match[0]; + } + return match[1] + match[2] + match[3]; + } + return jid; + } else { + return jid; + } + } + + private createJid(number: string): string { + this.logger.verbose('Creating jid with number: ' + number); + + //if (number.includes('@g.us') || number.includes('@s.whatsapp.net')) { + if (number.includes('@g.us') || number.includes('@s.whatsapp.net') || number.includes('@lid')) { + //this.logger.verbose('Number already contains @g.us or @s.whatsapp.net'); + this.logger.verbose('Number already contains @g.us or @s.whatsapp.net or @lid'); + return number; + } + + if (number.includes('@broadcast')) { + this.logger.verbose('Number already contains @broadcast'); + return number; + } + + number = number + ?.replace(/\s/g, '') + .replace(/\+/g, '') + .replace(/\(/g, '') + .replace(/\)/g, '') + .split(':')[0] + .split('@')[0]; + + if (number.includes('-') && number.length >= 24) { + this.logger.verbose('Jid created is group: ' + `${number}@g.us`); + number = number.replace(/[^\d-]/g, ''); + return `${number}@g.us`; + } + + number = number.replace(/\D/g, ''); + + if (number.length >= 18) { + this.logger.verbose('Jid created is group: ' + `${number}@g.us`); + number = number.replace(/[^\d-]/g, ''); + return `${number}@g.us`; + } + + this.logger.verbose('Jid created is whatsapp: ' + `${number}@s.whatsapp.net`); + return `${number}@s.whatsapp.net`; + } + + public async profilePicture(number: string) { + const jid = this.createJid(number); + + this.logger.verbose('Getting profile picture with jid: ' + jid); + try { + this.logger.verbose('Getting profile picture url'); + return { + wuid: jid, + profilePictureUrl: await this.client.profilePictureUrl(jid, 'image'), + }; + } catch (error) { + this.logger.verbose('Profile picture not found'); + return { + wuid: jid, + profilePictureUrl: null, + }; + } + } + + public async getStatus(number: string) { + const jid = this.createJid(number); + + this.logger.verbose('Getting profile status with jid:' + jid); + try { + this.logger.verbose('Getting status'); + return { + wuid: jid, + status: (await this.client.fetchStatus(jid))?.status, + }; + } catch (error) { + this.logger.verbose('Status not found'); + return { + wuid: jid, + status: null, + }; + } + } + + public async fetchProfile(instanceName: string, number?: string) { + const jid = number ? this.createJid(number) : this.client?.user?.id; + + this.logger.verbose('Getting profile with jid: ' + jid); + try { + this.logger.verbose('Getting profile info'); + + if (number) { + const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); + const picture = await this.profilePicture(info?.jid); + const status = await this.getStatus(info?.jid); + const business = await this.fetchBusinessProfile(info?.jid); + + return { + wuid: info?.jid || jid, + name: info?.name, + numberExists: info?.exists, + picture: picture?.profilePictureUrl, + status: status?.status, + isBusiness: business.isBusiness, + email: business?.email, + description: business?.description, + website: business?.website?.shift(), + }; + } else { + const info = await waMonitor.instanceInfo(instanceName); + const business = await this.fetchBusinessProfile(jid); + + return { + wuid: jid, + name: info?.instance?.profileName, + numberExists: true, + picture: info?.instance?.profilePictureUrl, + status: info?.instance?.profileStatus, + isBusiness: business.isBusiness, + email: business?.email, + description: business?.description, + website: business?.website?.shift(), + }; + } + } catch (error) { + this.logger.verbose('Profile not found'); + return { + wuid: jid, + name: null, + picture: null, + status: null, + os: null, + isBusiness: false, + }; + } + } + + private async sendMessageWithTyping( + number: string, + message: T, + options?: Options, + isChatwoot = false, + ) { + this.logger.verbose('Sending message with typing'); + + this.logger.verbose(`Check if number "${number}" is WhatsApp`); + const isWA = (await this.whatsappNumber({ numbers: [number] }))?.shift(); + + this.logger.verbose(`Exists: "${isWA.exists}" | jid: ${isWA.jid}`); + if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) { + throw new BadRequestException(isWA); + } + + const sender = isWA.jid; + + try { + if (options?.delay) { + this.logger.verbose('Delaying message'); + + await this.client.presenceSubscribe(sender); + this.logger.verbose('Subscribing to presence'); + + await this.client.sendPresenceUpdate(options?.presence ?? 'composing', sender); + this.logger.verbose('Sending presence update: ' + options?.presence ?? 'composing'); + + await delay(options.delay); + this.logger.verbose('Set delay: ' + options.delay); + + await this.client.sendPresenceUpdate('paused', sender); + this.logger.verbose('Sending presence update: paused'); + } + + const linkPreview = options?.linkPreview != false ? undefined : false; + + let quoted: WAMessage; + + if (options?.quoted) { + const m = options?.quoted; + + const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); + + if (!msg) { + throw 'Message not found'; + } + + quoted = msg; + this.logger.verbose('Quoted message'); + } + + let mentions: string[]; + if (isJidGroup(sender)) { + try { + const group = await this.findGroup({ groupJid: sender }, 'inner'); + + if (!group) { + throw new NotFoundException('Group not found'); + } + + if (options?.mentions) { + this.logger.verbose('Mentions defined'); + + if (options.mentions?.everyOne) { + this.logger.verbose('Mentions everyone'); + + this.logger.verbose('Getting group metadata'); + mentions = group.participants.map((participant) => participant.id); + this.logger.verbose('Getting group metadata for mentions'); + } else if (options.mentions?.mentioned?.length) { + this.logger.verbose('Mentions manually defined'); + mentions = options.mentions.mentioned.map((mention) => { + const jid = this.createJid(mention); + if (isJidGroup(jid)) { + return null; + } + return jid; + }); + } + } + } catch (error) { + throw new NotFoundException('Group not found'); + } + } + + const messageSent = await (async () => { + const option = { + quoted, + }; + + if ( + !message['audio'] && + !message['poll'] && + !message['sticker'] && + !message['conversation'] && + sender !== 'status@broadcast' + ) { + + if (message['reactionMessage']) { + this.logger.verbose('Sending reaction'); + return await this.client.sendMessage( + sender, + { + react: { + text: message['reactionMessage']['text'], + key: message['reactionMessage']['key'], + }, + } as unknown as AnyMessageContent, + option as unknown as MiscMessageGenerationOptions, + ); + } + + if (!message['audio']) { + this.logger.verbose('Sending message'); + return await this.client.sendMessage( + sender, + { + forward: { + key: { remoteJid: this.instance.wuid, fromMe: true }, + message, + }, + mentions, + }, + option as unknown as MiscMessageGenerationOptions, + ); + } + } + + if (message['conversation']) { + this.logger.verbose('Sending message'); + return await this.client.sendMessage( + sender, + { + text: message['conversation'], + mentions, + linkPreview: linkPreview, + } as unknown as AnyMessageContent, + option as unknown as MiscMessageGenerationOptions, + ); + } + + if (sender === 'status@broadcast') { + this.logger.verbose('Sending message'); + return await this.client.sendMessage( + sender, + message['status'].content as unknown as AnyMessageContent, + { + backgroundColor: message['status'].option.backgroundColor, + font: message['status'].option.font, + statusJidList: message['status'].option.statusJidList, + } as unknown as MiscMessageGenerationOptions, + ); + } + + this.logger.verbose('Sending message'); + return await this.client.sendMessage( + sender, + message as unknown as AnyMessageContent, + option as unknown as MiscMessageGenerationOptions, + ); + })(); + + const messageRaw: MessageRaw = { + key: messageSent.key, + pushName: messageSent.pushName, + message: { ...messageSent.message }, + messageType: getContentType(messageSent.message), + messageTimestamp: messageSent.messageTimestamp as number, + owner: this.instance.name, + source: getDevice(messageSent.key.id), + }; + + this.logger.log(messageRaw); + + this.logger.verbose('Sending data to webhook in event SEND_MESSAGE'); + this.sendDataWebhook(Events.SEND_MESSAGE, messageRaw); + + if (this.localChatwoot.enabled && !isChatwoot) { + this.chatwootService.eventWhatsapp(Events.SEND_MESSAGE, { instanceName: this.instance.name }, messageRaw); + } + + this.logger.verbose('Inserting message in database'); + await this.repository.message.insert( + [messageRaw], + this.instance.name, + this.configService.get('DATABASE').SAVE_DATA.NEW_MESSAGE, + ); + + return messageSent; + } catch (error) { + this.logger.error(error); + throw new BadRequestException(error.toString()); + } + } + + // Instance Controller + public get connectionStatus() { + this.logger.verbose('Getting connection status'); + return this.stateConnection; + } + + // Send Message Controller + public async textMessage(data: SendTextDto, isChatwoot = false) { + this.logger.verbose('Sending text message'); + return await this.sendMessageWithTyping( + data.number, + { + conversation: data.textMessage.text, + }, + data?.options, + isChatwoot, + ); + } + + public async pollMessage(data: SendPollDto) { + this.logger.verbose('Sending poll message'); + return await this.sendMessageWithTyping( + data.number, + { + poll: { + name: data.pollMessage.name, + selectableCount: data.pollMessage.selectableCount, + values: data.pollMessage.values, + }, + }, + data?.options, + ); + } + + private async formatStatusMessage(status: StatusMessage) { + this.logger.verbose('Formatting status message'); + + if (!status.type) { + throw new BadRequestException('Type is required'); + } + + if (!status.content) { + throw new BadRequestException('Content is required'); + } + + if (status.allContacts) { + this.logger.verbose('All contacts defined as true'); + + this.logger.verbose('Getting contacts from database'); + const contacts = await this.repository.contact.find({ + where: { owner: this.instance.name }, + }); + + if (!contacts.length) { + throw new BadRequestException('Contacts not found'); + } + + this.logger.verbose('Getting contacts with push name'); + status.statusJidList = contacts.filter((contact) => contact.pushName).map((contact) => contact.id); + + this.logger.verbose(status.statusJidList); + } + + if (!status.statusJidList?.length && !status.allContacts) { + throw new BadRequestException('StatusJidList is required'); + } + + if (status.type === 'text') { + this.logger.verbose('Type defined as text'); + + if (!status.backgroundColor) { + throw new BadRequestException('Background color is required'); + } + + if (!status.font) { + throw new BadRequestException('Font is required'); + } + + return { + content: { + text: status.content, + }, + option: { + backgroundColor: status.backgroundColor, + font: status.font, + statusJidList: status.statusJidList, + }, + }; + } + if (status.type === 'image') { + this.logger.verbose('Type defined as image'); + + return { + content: { + image: { + url: status.content, + }, + caption: status.caption, + }, + option: { + statusJidList: status.statusJidList, + }, + }; + } + if (status.type === 'video') { + this.logger.verbose('Type defined as video'); + + return { + content: { + video: { + url: status.content, + }, + caption: status.caption, + }, + option: { + statusJidList: status.statusJidList, + }, + }; + } + if (status.type === 'audio') { + this.logger.verbose('Type defined as audio'); + + this.logger.verbose('Processing audio'); + const convert = await this.processAudio(status.content, 'status@broadcast'); + if (typeof convert === 'string') { + this.logger.verbose('Audio processed'); + const audio = fs.readFileSync(convert).toString('base64'); + + const result = { + content: { + audio: Buffer.from(audio, 'base64'), + ptt: true, + mimetype: 'audio/mp4', + }, + option: { + statusJidList: status.statusJidList, + }, + }; + + fs.unlinkSync(convert); + + return result; + } else { + throw new InternalServerErrorException(convert); + } + } + + throw new BadRequestException('Type not found'); + } + + public async statusMessage(data: SendStatusDto) { + this.logger.verbose('Sending status message'); + const status = await this.formatStatusMessage(data.statusMessage); + + return await this.sendMessageWithTyping('status@broadcast', { + status, + }); + } + + private async prepareMediaMessage(mediaMessage: MediaMessage) { + try { + this.logger.verbose('Preparing media message'); + const prepareMedia = await prepareWAMessageMedia( + { + [mediaMessage.mediatype]: isURL(mediaMessage.media) + ? { url: mediaMessage.media } + : Buffer.from(mediaMessage.media, 'base64'), + } as any, + { upload: this.client.waUploadToServer }, + ); + + const mediaType = mediaMessage.mediatype + 'Message'; + this.logger.verbose('Media type: ' + mediaType); + + if (mediaMessage.mediatype === 'document' && !mediaMessage.fileName) { + this.logger.verbose('If media type is document and file name is not defined then'); + const regex = new RegExp(/.*\/(.+?)\./); + const arrayMatch = regex.exec(mediaMessage.media); + mediaMessage.fileName = arrayMatch[1]; + this.logger.verbose('File name: ' + mediaMessage.fileName); + } + + if (mediaMessage.mediatype === 'image' && !mediaMessage.fileName) { + mediaMessage.fileName = 'image.png'; + } + + if (mediaMessage.mediatype === 'video' && !mediaMessage.fileName) { + mediaMessage.fileName = 'video.mp4'; + } + + let mimetype: string; + + // if (isURL(mediaMessage.media)) { + // mimetype = getMIMEType(mediaMessage.media); + // } else { + // mimetype = getMIMEType(mediaMessage.fileName); + // } + + if (mediaMessage.mimetype) { + mimetype = mediaMessage.mimetype; + }else{ + if (isURL(mediaMessage.media)) { + mimetype = getMIMEType(mediaMessage.media); + } else { + mimetype = getMIMEType(mediaMessage.fileName); + } + } + + this.logger.verbose('Mimetype: ' + mimetype); + + prepareMedia[mediaType].caption = mediaMessage?.caption; + prepareMedia[mediaType].mimetype = mimetype; + prepareMedia[mediaType].fileName = mediaMessage.fileName; + + if (mediaMessage.mediatype === 'video') { + this.logger.verbose('Is media type video then set gif playback as false'); + prepareMedia[mediaType].jpegThumbnail = Uint8Array.from( + readFileSync(join(process.cwd(), 'public', 'images', 'video-cover.png')), + ); + prepareMedia[mediaType].gifPlayback = false; + } + + this.logger.verbose('Generating wa message from content'); + return generateWAMessageFromContent( + '', + { [mediaType]: { ...prepareMedia[mediaType] } }, + { userJid: this.instance.wuid }, + ); + } catch (error) { + this.logger.error(error); + throw new InternalServerErrorException(error?.toString() || error); + } + } + + private async convertToWebP(image: string, number: string) { + try { + this.logger.verbose('Converting image to WebP to sticker'); + + let imagePath: string; + const hash = `${number}-${new Date().getTime()}`; + this.logger.verbose('Hash to image name: ' + hash); + + const outputPath = `${join(this.storePath, 'temp', `${hash}.webp`)}`; + this.logger.verbose('Output path: ' + outputPath); + + if (isBase64(image)) { + this.logger.verbose('Image is base64'); + + const base64Data = image.replace(/^data:image\/(jpeg|png|gif);base64,/, ''); + const imageBuffer = Buffer.from(base64Data, 'base64'); + imagePath = `${join(this.storePath, 'temp', `temp-${hash}.png`)}`; + this.logger.verbose('Image path: ' + imagePath); + + await sharp(imageBuffer).toFile(imagePath); + this.logger.verbose('Image created'); + } else { + this.logger.verbose('Image is url'); + + const timestamp = new Date().getTime(); + const url = `${image}?timestamp=${timestamp}`; + this.logger.verbose('including timestamp in url: ' + url); + + const response = await axios.get(url, { responseType: 'arraybuffer' }); + this.logger.verbose('Getting image from url'); + + const imageBuffer = Buffer.from(response.data, 'binary'); + imagePath = `${join(this.storePath, 'temp', `temp-${hash}.png`)}`; + this.logger.verbose('Image path: ' + imagePath); + + await sharp(imageBuffer).toFile(imagePath); + this.logger.verbose('Image created'); + } + + await sharp(imagePath).webp().toFile(outputPath); + this.logger.verbose('Image converted to WebP'); + + fs.unlinkSync(imagePath); + this.logger.verbose('Temp image deleted'); + + return outputPath; + } catch (error) { + console.error('Erro ao converter a imagem para WebP:', error); + } + } + + public async mediaSticker(data: SendStickerDto) { + this.logger.verbose('Sending media sticker'); + const convert = await this.convertToWebP(data.stickerMessage.image, data.number); + const result = await this.sendMessageWithTyping( + data.number, + { + sticker: { url: convert }, + }, + data?.options, + ); + + fs.unlinkSync(convert); + this.logger.verbose('Converted image deleted'); + + return result; + } + + public async mediaMessage(data: SendMediaDto, isChatwoot = false) { + this.logger.verbose('Sending media message'); + const generate = await this.prepareMediaMessage(data.mediaMessage); + + return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options, isChatwoot); + } + + public async processAudio(audio: string, number: string) { + this.logger.verbose('Processing audio'); + let tempAudioPath: string; + let outputAudio: string; + + const hash = `${number}-${new Date().getTime()}`; + this.logger.verbose('Hash to audio name: ' + hash); + + if (isURL(audio)) { + this.logger.verbose('Audio is url'); + + outputAudio = `${join(this.storePath, 'temp', `${hash}.mp4`)}`; + tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; + + this.logger.verbose('Output audio path: ' + outputAudio); + this.logger.verbose('Temp audio path: ' + tempAudioPath); + + const timestamp = new Date().getTime(); + const url = `${audio}?timestamp=${timestamp}`; + + this.logger.verbose('Including timestamp in url: ' + url); + + const response = await axios.get(url, { responseType: 'arraybuffer' }); + this.logger.verbose('Getting audio from url'); + + fs.writeFileSync(tempAudioPath, response.data); + } else { + this.logger.verbose('Audio is base64'); + + outputAudio = `${join(this.storePath, 'temp', `${hash}.mp4`)}`; + tempAudioPath = `${join(this.storePath, 'temp', `temp-${hash}.mp3`)}`; + + this.logger.verbose('Output audio path: ' + outputAudio); + this.logger.verbose('Temp audio path: ' + tempAudioPath); + + const audioBuffer = Buffer.from(audio, 'base64'); + fs.writeFileSync(tempAudioPath, audioBuffer); + this.logger.verbose('Temp audio created'); + } + + this.logger.verbose('Converting audio to mp4'); + return new Promise((resolve, reject) => { + exec(`${ffmpegPath.path} -i ${tempAudioPath} -vn -ab 128k -ar 44100 -f ipod ${outputAudio} -y`, (error) => { + fs.unlinkSync(tempAudioPath); + this.logger.verbose('Temp audio deleted'); + + if (error) reject(error); + + this.logger.verbose('Audio converted to mp4'); + resolve(outputAudio); + }); + }); + } + + public async audioWhatsapp(data: SendAudioDto, isChatwoot = false) { + this.logger.verbose('Sending audio whatsapp'); + + if (!data.options?.encoding && data.options?.encoding !== false) { + data.options.encoding = true; + } + + if (data.options?.encoding) { + const convert = await this.processAudio(data.audioMessage.audio, data.number); + if (typeof convert === 'string') { + const audio = fs.readFileSync(convert).toString('base64'); + const result = this.sendMessageWithTyping( + data.number, + { + audio: Buffer.from(audio, 'base64'), + ptt: true, + mimetype: 'audio/mp4', + }, + { presence: 'recording', delay: data?.options?.delay }, + isChatwoot, + ); + + fs.unlinkSync(convert); + this.logger.verbose('Converted audio deleted'); + + return result; + } else { + throw new InternalServerErrorException(convert); + } + } + + return await this.sendMessageWithTyping( + data.number, + { + audio: isURL(data.audioMessage.audio) + ? { url: data.audioMessage.audio } + : Buffer.from(data.audioMessage.audio, 'base64'), + ptt: true, + mimetype: 'audio/ogg; codecs=opus', + }, + { presence: 'recording', delay: data?.options?.delay }, + isChatwoot, + ); + } + + public async buttonMessage(data: SendButtonDto) { + this.logger.verbose('Sending button message'); + const embeddedMedia: any = {}; + let mediatype = 'TEXT'; + + if (data.buttonMessage?.mediaMessage) { + mediatype = data.buttonMessage.mediaMessage?.mediatype.toUpperCase() ?? 'TEXT'; + embeddedMedia.mediaKey = mediatype.toLowerCase() + 'Message'; + const generate = await this.prepareMediaMessage(data.buttonMessage.mediaMessage); + embeddedMedia.message = generate.message[embeddedMedia.mediaKey]; + embeddedMedia.contentText = `*${data.buttonMessage.title}*\n\n${data.buttonMessage.description}`; + } + + const btnItems = { + text: data.buttonMessage.buttons.map((btn) => btn.buttonText), + ids: data.buttonMessage.buttons.map((btn) => btn.buttonId), + }; + + if (!arrayUnique(btnItems.text) || !arrayUnique(btnItems.ids)) { + throw new BadRequestException('Button texts cannot be repeated', 'Button IDs cannot be repeated.'); + } + + return await this.sendMessageWithTyping( + data.number, + { + buttonsMessage: { + text: !embeddedMedia?.mediaKey ? data.buttonMessage.title : undefined, + contentText: embeddedMedia?.contentText ?? data.buttonMessage.description, + footerText: data.buttonMessage?.footerText, + buttons: data.buttonMessage.buttons.map((button) => { + return { + buttonText: { + displayText: button.buttonText, + }, + buttonId: button.buttonId, + type: 1, + }; + }), + headerType: proto.Message.ButtonsMessage.HeaderType[mediatype], + [embeddedMedia?.mediaKey]: embeddedMedia?.message, + }, + }, + data?.options, + ); + } + + public async locationMessage(data: SendLocationDto) { + this.logger.verbose('Sending location message'); + return await this.sendMessageWithTyping( + data.number, + { + locationMessage: { + degreesLatitude: data.locationMessage.latitude, + degreesLongitude: data.locationMessage.longitude, + name: data.locationMessage?.name, + address: data.locationMessage?.address, + }, + }, + data?.options, + ); + } + + public async listMessage(data: SendListDto) { + this.logger.verbose('Sending list message'); + return await this.sendMessageWithTyping( + data.number, + { + listMessage: { + title: data.listMessage.title, + description: data.listMessage.description, + buttonText: data.listMessage?.buttonText, + footerText: data.listMessage?.footerText, + sections: data.listMessage.sections, + listType: 1, + }, + }, + data?.options, + ); + } + + public async contactMessage(data: SendContactDto) { + this.logger.verbose('Sending contact message'); + const message: proto.IMessage = {}; + + const vcard = (contact: ContactMessage) => { + this.logger.verbose('Creating vcard'); + let result = 'BEGIN:VCARD\n' + 'VERSION:3.0\n' + `N:${contact.fullName}\n` + `FN:${contact.fullName}\n`; + + if (contact.organization) { + this.logger.verbose('Organization defined'); + result += `ORG:${contact.organization};\n`; + } + + if (contact.email) { + this.logger.verbose('Email defined'); + result += `EMAIL:${contact.email}\n`; + } + + if (contact.url) { + this.logger.verbose('Url defined'); + result += `URL:${contact.url}\n`; + } + + if (!contact.wuid) { + this.logger.verbose('Wuid defined'); + contact.wuid = this.createJid(contact.phoneNumber); + } + + result += `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + 'item1.X-ABLabel:Celular\n' + 'END:VCARD'; + + this.logger.verbose('Vcard created'); + return result; + }; + + if (data.contactMessage.length === 1) { + message.contactMessage = { + displayName: data.contactMessage[0].fullName, + vcard: vcard(data.contactMessage[0]), + }; + } else { + message.contactsArrayMessage = { + displayName: `${data.contactMessage.length} contacts`, + contacts: data.contactMessage.map((contact) => { + return { + displayName: contact.fullName, + vcard: vcard(contact), + }; + }), + }; + } + + return await this.sendMessageWithTyping(data.number, { ...message }, data?.options); + } + + public async reactionMessage(data: SendReactionDto) { + this.logger.verbose('Sending reaction message'); + return await this.sendMessageWithTyping(data.reactionMessage.key.remoteJid, { + reactionMessage: { + key: data.reactionMessage.key, + text: data.reactionMessage.reaction, + }, + }); + } + + // Chat Controller + public async whatsappNumber(data: WhatsAppNumberDto) { + this.logger.verbose('Getting whatsapp number'); + + const onWhatsapp: OnWhatsAppDto[] = []; + for await (const number of data.numbers) { + let jid = this.createJid(number); + + if (isJidGroup(jid)) { + const group = await this.findGroup({ groupJid: jid }, 'inner'); + + if (!group) throw new BadRequestException('Group not found'); + + onWhatsapp.push(new OnWhatsAppDto(group.id, !!group?.id, group?.subject)); + } else { + jid = !jid.startsWith('+') ? `+${jid}` : jid; + const verify = await this.client.onWhatsApp(jid); + + const result = verify[0]; + + if (!result) { + onWhatsapp.push(new OnWhatsAppDto(jid, false)); + } else { + onWhatsapp.push(new OnWhatsAppDto(result.jid, result.exists)); + } + } + } + + return onWhatsapp; + } + + public async markMessageAsRead(data: ReadMessageDto) { + this.logger.verbose('Marking message as read'); + + try { + const keys: proto.IMessageKey[] = []; + data.read_messages.forEach((read) => { + if (isJidGroup(read.remoteJid) || isJidUser(read.remoteJid)) { + keys.push({ + remoteJid: read.remoteJid, + fromMe: read.fromMe, + id: read.id, + }); + } + }); + await this.client.readMessages(keys); + return { message: 'Read messages', read: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Read messages fail', error.toString()); + } + } + + public async getLastMessage(number: string) { + const messages = await this.fetchMessages({ + where: { + key: { + remoteJid: number, + }, + owner: this.instance.name, + }, + }); + + let lastMessage = messages.pop(); + + for (const message of messages) { + if (message.messageTimestamp >= lastMessage.messageTimestamp) { + lastMessage = message; + } + } + + return lastMessage as unknown as LastMessage; + } + + public async archiveChat(data: ArchiveChatDto) { + this.logger.verbose('Archiving chat'); + try { + let last_message = data.lastMessage; + let number = data.chat; + + if (!last_message && number) { + last_message = await this.getLastMessage(number); + } else { + last_message = data.lastMessage; + last_message.messageTimestamp = last_message?.messageTimestamp ?? Date.now(); + number = last_message?.key?.remoteJid; + } + + if (!last_message || Object.keys(last_message).length === 0) { + throw new NotFoundException('Last message not found'); + } + + await this.client.chatModify( + { + archive: data.archive, + lastMessages: [last_message], + }, + this.createJid(number), + ); + + return { + chatId: number, + archived: true, + }; + } catch (error) { + throw new InternalServerErrorException({ + archived: false, + message: ['An error occurred while archiving the chat. Open a calling.', error.toString()], + }); + } + } + + public async deleteMessage(del: DeleteMessage) { + this.logger.verbose('Deleting message'); + try { + return await this.client.sendMessage(del.remoteJid, { delete: del }); + } catch (error) { + throw new InternalServerErrorException('Error while deleting message for everyone', error?.toString()); + } + } + + public async getBase64FromMediaMessage(data: getBase64FromMediaMessageDto) { + this.logger.verbose('Getting base64 from media message'); + try { + const m = data?.message; + const convertToMp4 = data?.convertToMp4 ?? false; + + const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); + + if (!msg) { + throw 'Message not found'; + } + + for (const subtype of MessageSubtype) { + if (msg.message[subtype]) { + msg.message = msg.message[subtype].message; + } + } + + let mediaMessage: any; + let mediaType: string; + + for (const type of TypeMediaMessage) { + mediaMessage = msg.message[type]; + if (mediaMessage) { + mediaType = type; + break; + } + } + + if (!mediaMessage) { + throw 'The message is not of the media type'; + } + + if (typeof mediaMessage['mediaKey'] === 'object') { + msg.message = JSON.parse(JSON.stringify(msg.message)); + } + + this.logger.verbose('Downloading media message'); + const buffer = await downloadMediaMessage( + { key: msg?.key, message: msg?.message }, + 'buffer', + {}, + { + logger: P({ level: 'error' }) as any, + reuploadRequest: this.client.updateMediaMessage, + }, + ); + const typeMessage = getContentType(msg.message); + + if (convertToMp4 && typeMessage === 'audioMessage') { + this.logger.verbose('Converting audio to mp4'); + const number = msg.key.remoteJid.split('@')[0]; + const convert = await this.processAudio(buffer.toString('base64'), number); + + if (typeof convert === 'string') { + const audio = fs.readFileSync(convert).toString('base64'); + this.logger.verbose('Audio converted to mp4'); + + const result = { + mediaType, + fileName: mediaMessage['fileName'], + caption: mediaMessage['caption'], + size: { + fileLength: mediaMessage['fileLength'], + height: mediaMessage['height'], + width: mediaMessage['width'], + }, + mimetype: 'audio/mp4', + base64: Buffer.from(audio, 'base64').toString('base64'), + }; + + fs.unlinkSync(convert); + this.logger.verbose('Converted audio deleted'); + + this.logger.verbose('Media message downloaded'); + return result; + } + } + + this.logger.verbose('Media message downloaded'); + return { + mediaType, + fileName: mediaMessage['fileName'], + caption: mediaMessage['caption'], + size: { + fileLength: mediaMessage['fileLength'], + height: mediaMessage['height'], + width: mediaMessage['width'], + }, + mimetype: mediaMessage['mimetype'], + base64: buffer.toString('base64'), + }; + } catch (error) { + this.logger.error(error); + throw new BadRequestException(error.toString()); + } + } + + public async fetchContacts(query: ContactQuery) { + this.logger.verbose('Fetching contacts'); + if (query?.where) { + query.where.owner = this.instance.name; + if (query.where?.id) { + query.where.id = this.createJid(query.where.id); + } + } else { + query = { + where: { + owner: this.instance.name, + }, + }; + } + return await this.repository.contact.find(query); + } + + public async fetchMessages(query: MessageQuery) { + this.logger.verbose('Fetching messages'); + if (query?.where) { + if (query.where?.key?.remoteJid) { + query.where.key.remoteJid = this.createJid(query.where.key.remoteJid); + } + query.where.owner = this.instance.name; + } else { + query = { + where: { + owner: this.instance.name, + }, + limit: query?.limit, + }; + } + return await this.repository.message.find(query); + } + + public async fetchStatusMessage(query: MessageUpQuery) { + this.logger.verbose('Fetching status messages'); + if (query?.where) { + if (query.where?.remoteJid) { + query.where.remoteJid = this.createJid(query.where.remoteJid); + } + query.where.owner = this.instance.name; + } else { + query = { + where: { + owner: this.instance.name, + }, + limit: query?.limit, + }; + } + return await this.repository.messageUpdate.find(query); + } + + public async fetchChats() { + this.logger.verbose('Fetching chats'); + return await this.repository.chat.find({ where: { owner: this.instance.name } }); + } + + public async fetchPrivacySettings() { + this.logger.verbose('Fetching privacy settings'); + return await this.client.fetchPrivacySettings(); + } + + public async updatePrivacySettings(settings: PrivacySettingDto) { + this.logger.verbose('Updating privacy settings'); + try { + await this.client.updateReadReceiptsPrivacy(settings.privacySettings.readreceipts); + this.logger.verbose('Read receipts privacy updated'); + + await this.client.updateProfilePicturePrivacy(settings.privacySettings.profile); + this.logger.verbose('Profile picture privacy updated'); + + await this.client.updateStatusPrivacy(settings.privacySettings.status); + this.logger.verbose('Status privacy updated'); + + await this.client.updateOnlinePrivacy(settings.privacySettings.online); + this.logger.verbose('Online privacy updated'); + + await this.client.updateLastSeenPrivacy(settings.privacySettings.last); + this.logger.verbose('Last seen privacy updated'); + + await this.client.updateGroupsAddPrivacy(settings.privacySettings.groupadd); + this.logger.verbose('Groups add privacy updated'); + + //this.client?.ws?.close(); + this.reloadConnection(); + + return { + update: 'success', + data: { + readreceipts: settings.privacySettings.readreceipts, + profile: settings.privacySettings.profile, + status: settings.privacySettings.status, + online: settings.privacySettings.online, + last: settings.privacySettings.last, + groupadd: settings.privacySettings.groupadd, + }, + }; + } catch (error) { + throw new InternalServerErrorException('Error updating privacy settings', error.toString()); + } + } + + public async fetchBusinessProfile(number: string): Promise { + this.logger.verbose('Fetching business profile'); + try { + const jid = number ? this.createJid(number) : this.instance.wuid; + + const profile = await this.client.getBusinessProfile(jid); + this.logger.verbose('Trying to get business profile'); + + if (!profile) { + const info = await this.whatsappNumber({ numbers: [jid] }); + + return { + isBusiness: false, + message: 'Not is business profile', + ...info?.shift(), + }; + } + + this.logger.verbose('Business profile fetched'); + return { + isBusiness: true, + ...profile, + }; + } catch (error) { + throw new InternalServerErrorException('Error updating profile name', error.toString()); + } + } + + public async updateProfileName(name: string) { + this.logger.verbose('Updating profile name to ' + name); + try { + await this.client.updateProfileName(name); + + return { update: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error updating profile name', error.toString()); + } + } + + public async updateProfileStatus(status: string) { + this.logger.verbose('Updating profile status to: ' + status); + try { + await this.client.updateProfileStatus(status); + + return { update: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error updating profile status', error.toString()); + } + } + + public async updateProfilePicture(picture: string) { + this.logger.verbose('Updating profile picture'); + try { + let pic: WAMediaUpload; + if (isURL(picture)) { + this.logger.verbose('Picture is url'); + + const timestamp = new Date().getTime(); + const url = `${picture}?timestamp=${timestamp}`; + this.logger.verbose('Including timestamp in url: ' + url); + + pic = (await axios.get(url, { responseType: 'arraybuffer' })).data; + this.logger.verbose('Getting picture from url'); + } else if (isBase64(picture)) { + this.logger.verbose('Picture is base64'); + pic = Buffer.from(picture, 'base64'); + this.logger.verbose('Getting picture from base64'); + } else { + throw new BadRequestException('"profilePicture" must be a url or a base64'); + } + await this.client.updateProfilePicture(this.instance.wuid, pic); + this.logger.verbose('Profile picture updated'); + + return { update: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error updating profile picture', error.toString()); + } + } + + public async removeProfilePicture() { + this.logger.verbose('Removing profile picture'); + try { + await this.client.removeProfilePicture(this.instance.wuid); + + return { update: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error removing profile picture', error.toString()); + } + } + + // Group + public async createGroup(create: CreateGroupDto) { + this.logger.verbose('Creating group: ' + create.subject); + try { + 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); + + if (create?.description) { + this.logger.verbose('Updating group description: ' + create.description); + await this.client.groupUpdateDescription(id, create.description); + } + + if (create?.promoteParticipants) { + this.logger.verbose('Prometing group participants: ' + participants); + await this.updateGParticipant({ + groupJid: id, + action: 'promote', + participants: participants, + }); + } + + this.logger.verbose('Getting group metadata'); + const group = await this.client.groupMetadata(id); + + return group; + } catch (error) { + this.logger.error(error); + throw new InternalServerErrorException('Error creating group', error.toString()); + } + } + + public async updateGroupPicture(picture: GroupPictureDto) { + this.logger.verbose('Updating group picture'); + try { + let pic: WAMediaUpload; + if (isURL(picture.image)) { + this.logger.verbose('Picture is url'); + + const timestamp = new Date().getTime(); + const url = `${picture.image}?timestamp=${timestamp}`; + this.logger.verbose('Including timestamp in url: ' + url); + + pic = (await axios.get(url, { responseType: 'arraybuffer' })).data; + this.logger.verbose('Getting picture from url'); + } else if (isBase64(picture.image)) { + this.logger.verbose('Picture is base64'); + pic = Buffer.from(picture.image, 'base64'); + this.logger.verbose('Getting picture from base64'); + } else { + throw new BadRequestException('"profilePicture" must be a url or a base64'); + } + await this.client.updateProfilePicture(picture.groupJid, pic); + this.logger.verbose('Group picture updated'); + + return { update: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error update group picture', error.toString()); + } + } + + public async updateGroupSubject(data: GroupSubjectDto) { + this.logger.verbose('Updating group subject to: ' + data.subject); + try { + await this.client.groupUpdateSubject(data.groupJid, data.subject); + + return { update: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error updating group subject', error.toString()); + } + } + + public async updateGroupDescription(data: GroupDescriptionDto) { + this.logger.verbose('Updating group description to: ' + data.description); + try { + await this.client.groupUpdateDescription(data.groupJid, data.description); + + return { update: 'success' }; + } catch (error) { + throw new InternalServerErrorException('Error updating group description', error.toString()); + } + } + + public async findGroup(id: GroupJid, reply: 'inner' | 'out' = 'out') { + this.logger.verbose('Fetching group'); + try { + return await this.client.groupMetadata(id.groupJid); + } catch (error) { + if (reply === 'inner') { + return; + } + throw new NotFoundException('Error fetching group', error.toString()); + } + } + + public async fetchAllGroups(getParticipants: GetParticipant) { + this.logger.verbose('Fetching all groups'); + try { + const fetch = Object.values(await this.client.groupFetchAllParticipating()); + + const groups = fetch.map((group) => { + const result = { + id: group.id, + subject: group.subject, + subjectOwner: group.subjectOwner, + subjectTime: group.subjectTime, + size: group.participants.length, + creation: group.creation, + owner: group.owner, + desc: group.desc, + descId: group.descId, + restrict: group.restrict, + announce: group.announce, + }; + + if (getParticipants.getParticipants == 'true') { + result['participants'] = group.participants; + } + + return result; + }); + + return groups; + } catch (error) { + throw new NotFoundException('Error fetching group', error.toString()); + } + } + + public async inviteCode(id: GroupJid) { + this.logger.verbose('Fetching invite code for group: ' + id.groupJid); + try { + const code = await this.client.groupInviteCode(id.groupJid); + return { inviteUrl: `https://chat.whatsapp.com/${code}`, inviteCode: code }; + } catch (error) { + throw new NotFoundException('No invite code', error.toString()); + } + } + + public async inviteInfo(id: GroupInvite) { + this.logger.verbose('Fetching invite info for code: ' + id.inviteCode); + try { + return await this.client.groupGetInviteInfo(id.inviteCode); + } catch (error) { + throw new NotFoundException('No invite info', id.inviteCode); + } + } + + public async sendInvite(id: GroupSendInvite) { + this.logger.verbose('Sending invite for group: ' + id.groupJid); + try { + const inviteCode = await this.inviteCode({ groupJid: id.groupJid }); + this.logger.verbose('Getting invite code: ' + inviteCode.inviteCode); + + const inviteUrl = inviteCode.inviteUrl; + this.logger.verbose('Invite url: ' + inviteUrl); + + const numbers = id.numbers.map((number) => this.createJid(number)); + const description = id.description ?? ''; + + const msg = `${description}\n\n${inviteUrl}`; + + const message = { + conversation: msg, + }; + + for await (const number of numbers) { + await this.sendMessageWithTyping(number, message); + } + + this.logger.verbose('Invite sent for numbers: ' + numbers.join(', ')); + + return { send: true, inviteUrl }; + } catch (error) { + throw new NotFoundException('No send invite'); + } + } + + public async revokeInviteCode(id: GroupJid) { + this.logger.verbose('Revoking invite code for group: ' + id.groupJid); + try { + const inviteCode = await this.client.groupRevokeInvite(id.groupJid); + return { revoked: true, inviteCode }; + } catch (error) { + throw new NotFoundException('Revoke error', error.toString()); + } + } + + public async findParticipants(id: GroupJid) { + this.logger.verbose('Fetching participants for group: ' + id.groupJid); + try { + const participants = (await this.client.groupMetadata(id.groupJid)).participants; + return { participants }; + } catch (error) { + throw new NotFoundException('No participants', error.toString()); + } + } + + public async updateGParticipant(update: GroupUpdateParticipantDto) { + this.logger.verbose('Updating participants'); + try { + const participants = update.participants.map((p) => this.createJid(p)); + const updateParticipants = await this.client.groupParticipantsUpdate( + update.groupJid, + participants, + update.action, + ); + return { updateParticipants: updateParticipants }; + } catch (error) { + throw new BadRequestException('Error updating participants', error.toString()); + } + } + + public async updateGSetting(update: GroupUpdateSettingDto) { + this.logger.verbose('Updating setting for group: ' + update.groupJid); + try { + const updateSetting = await this.client.groupSettingUpdate(update.groupJid, update.action); + return { updateSetting: updateSetting }; + } catch (error) { + throw new BadRequestException('Error updating setting', error.toString()); + } + } + + public async toggleEphemeral(update: GroupToggleEphemeralDto) { + this.logger.verbose('Toggling ephemeral for group: ' + update.groupJid); + try { + await this.client.groupToggleEphemeral(update.groupJid, update.expiration); + return { success: true }; + } catch (error) { + throw new BadRequestException('Error updating setting', error.toString()); + } + } + + public async leaveGroup(id: GroupJid) { + this.logger.verbose('Leaving group: ' + id.groupJid); + try { + await this.client.groupLeave(id.groupJid); + return { groupJid: id.groupJid, leave: true }; + } catch (error) { + throw new BadRequestException('Unable to leave the group', error.toString()); + } + } +} diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts old mode 100644 new mode 100755 index 27582001..20cdd4be --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -1,139 +1,145 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'; - -export enum Events { - APPLICATION_STARTUP = 'application.startup', - QRCODE_UPDATED = 'qrcode.updated', - CONNECTION_UPDATE = 'connection.update', - STATUS_INSTANCE = 'status.instance', - MESSAGES_SET = 'messages.set', - MESSAGES_UPSERT = 'messages.upsert', - MESSAGES_UPDATE = 'messages.update', - MESSAGES_DELETE = 'messages.delete', - SEND_MESSAGE = 'send.message', - CONTACTS_SET = 'contacts.set', - CONTACTS_UPSERT = 'contacts.upsert', - CONTACTS_UPDATE = 'contacts.update', - PRESENCE_UPDATE = 'presence.update', - CHATS_SET = 'chats.set', - CHATS_UPDATE = 'chats.update', - CHATS_UPSERT = 'chats.upsert', - CHATS_DELETE = 'chats.delete', - GROUPS_UPSERT = 'groups.upsert', - 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 { - export type QrCode = { - count?: number; - pairingCode?: string; - base64?: string; - code?: string; - }; - export type Instance = { - qrcode?: QrCode; - pairingCode?: string; - authState?: { state: AuthenticationState; saveCreds: () => void }; - name?: string; - wuid?: string; - profileName?: string; - profilePictureUrl?: string; - }; - - export type LocalWebHook = { - enabled?: boolean; - url?: string; - events?: string[]; - webhook_by_events?: boolean; - webhook_base64?: boolean; - }; - - export type LocalChatwoot = { - enabled?: boolean; - account_id?: string; - token?: string; - url?: string; - name_inbox?: string; - sign_msg?: boolean; - number?: string; - reopen_conversation?: boolean; - conversation_pending?: boolean; - }; - - export type LocalSettings = { - reject_call?: boolean; - msg_call?: string; - groups_ignore?: boolean; - always_online?: boolean; - read_messages?: boolean; - read_status?: boolean; - }; - - export type LocalWebsocket = { - enabled?: boolean; - events?: string[]; - }; - - export type LocalRabbitmq = { - enabled?: boolean; - events?: string[]; - }; - - export type LocalSqs = { - enabled?: boolean; - events?: string[]; - }; - - type Session = { - remoteJid?: string; - sessionId?: string; - createdAt?: number; - }; - - export type LocalTypebot = { - enabled?: boolean; - url?: string; - typebot?: string; - expire?: number; - keyword_finish?: string; - delay_message?: number; - unknown_message?: string; - listening_from_me?: boolean; - sessions?: Session[]; - }; - - export type LocalProxy = { - enabled?: boolean; - proxy?: string; - }; - - export type LocalChamaai = { - enabled?: boolean; - url?: string; - token?: string; - waNumber?: string; - answerByAudio?: boolean; - }; - - export type StateConnection = { - instance?: string; - state?: WAConnectionState | 'refused'; - statusReason?: number; - }; - - export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED'; -} - -export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage']; - -export const MessageSubtype = [ - 'ephemeralMessage', - 'documentWithCaptionMessage', - 'viewOnceMessage', - 'viewOnceMessageV2', -]; +/* eslint-disable @typescript-eslint/no-namespace */ +import { AuthenticationState, WAConnectionState } from '@whiskeysockets/baileys'; + +export enum Events { + APPLICATION_STARTUP = 'application.startup', + QRCODE_UPDATED = 'qrcode.updated', + CONNECTION_UPDATE = 'connection.update', + STATUS_INSTANCE = 'status.instance', + MESSAGES_SET = 'messages.set', + MESSAGES_UPSERT = 'messages.upsert', + MESSAGES_UPDATE = 'messages.update', + MESSAGES_DELETE = 'messages.delete', + SEND_MESSAGE = 'send.message', + CONTACTS_SET = 'contacts.set', + CONTACTS_UPSERT = 'contacts.upsert', + CONTACTS_UPDATE = 'contacts.update', + PRESENCE_UPDATE = 'presence.update', + CHATS_SET = 'chats.set', + CHATS_UPDATE = 'chats.update', + CHATS_UPSERT = 'chats.upsert', + CHATS_DELETE = 'chats.delete', + GROUPS_UPSERT = 'groups.upsert', + 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 { + export type QrCode = { + count?: number; + pairingCode?: string; + base64?: string; + code?: string; + }; + export type Instance = { + qrcode?: QrCode; + pairingCode?: string; + authState?: { state: AuthenticationState; saveCreds: () => void }; + name?: string; + wuid?: string; + profileName?: string; + profilePictureUrl?: string; + }; + + export type LocalWebHook = { + enabled?: boolean; + url?: string; + events?: string[]; + webhook_by_events?: boolean; + webhook_base64?: boolean; + }; + + export type LocalChatwoot = { + enabled?: boolean; + account_id?: string; + token?: string; + url?: string; + name_inbox?: string; + sign_msg?: boolean; + number?: string; + reopen_conversation?: boolean; + conversation_pending?: boolean; + }; + + export type LocalSettings = { + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + always_online?: boolean; + read_messages?: boolean; + read_status?: boolean; + }; + + export type LocalWebsocket = { + enabled?: boolean; + events?: string[]; + }; + + export type LocalRabbitmq = { + enabled?: boolean; + events?: string[]; + }; + + export type LocalSqs = { + enabled?: boolean; + events?: string[]; + }; + + export type LocalOpenai = { + chave?: string; + enabled?: boolean; + events?: string[]; + }; + + type Session = { + remoteJid?: string; + sessionId?: string; + createdAt?: number; + }; + + export type LocalTypebot = { + enabled?: boolean; + url?: string; + typebot?: string; + expire?: number; + keyword_finish?: string; + delay_message?: number; + unknown_message?: string; + listening_from_me?: boolean; + sessions?: Session[]; + }; + + export type LocalProxy = { + enabled?: boolean; + proxy?: string; + }; + + export type LocalChamaai = { + enabled?: boolean; + url?: string; + token?: string; + waNumber?: string; + answerByAudio?: boolean; + }; + + export type StateConnection = { + instance?: string; + state?: WAConnectionState | 'refused'; + statusReason?: number; + }; + + export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED'; +} + +export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage']; + +export const MessageSubtype = [ + 'ephemeralMessage', + 'documentWithCaptionMessage', + 'viewOnceMessage', + 'viewOnceMessageV2', +]; diff --git a/src/whatsapp/whatsapp.module.ts b/src/whatsapp/whatsapp.module.ts old mode 100644 new mode 100755 index 0bb9409f..765f934c --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -1,162 +1,175 @@ -import { configService } from '../config/env.config'; -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'; -import { InstanceController } from './controllers/instance.controller'; -import { ProxyController } from './controllers/proxy.controller'; -import { RabbitmqController } from './controllers/rabbitmq.controller'; -import { SendMessageController } from './controllers/sendMessage.controller'; -import { SettingsController } from './controllers/settings.controller'; -import { SqsController } from './controllers/sqs.controller'; -import { TypebotController } from './controllers/typebot.controller'; -import { ViewsController } from './controllers/views.controller'; -import { WebhookController } from './controllers/webhook.controller'; -import { WebsocketController } from './controllers/websocket.controller'; -import { - AuthModel, - ChamaaiModel, - ChatModel, - ChatwootModel, - ContactModel, - MessageModel, - MessageUpModel, - ProxyModel, - RabbitmqModel, - SettingsModel, - SqsModel, - TypebotModel, - WebhookModel, - 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'; -import { MessageRepository } from './repository/message.repository'; -import { MessageUpRepository } from './repository/messageUp.repository'; -import { ProxyRepository } from './repository/proxy.repository'; -import { RabbitmqRepository } from './repository/rabbitmq.repository'; -import { RepositoryBroker } from './repository/repository.manager'; -import { SettingsRepository } from './repository/settings.repository'; -import { SqsRepository } from './repository/sqs.repository'; -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'; -import { RabbitmqService } from './services/rabbitmq.service'; -import { SettingsService } from './services/settings.service'; -import { SqsService } from './services/sqs.service'; -import { TypebotService } from './services/typebot.service'; -import { WebhookService } from './services/webhook.service'; -import { WebsocketService } from './services/websocket.service'; - -const logger = new Logger('WA MODULE'); - -const messageRepository = new MessageRepository(MessageModel, configService); -const chatRepository = new ChatRepository(ChatModel, configService); -const contactRepository = new ContactRepository(ContactModel, configService); -const messageUpdateRepository = new MessageUpRepository(MessageUpModel, configService); -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 sqsRepository = new SqsRepository(SqsModel, configService); -const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); -const settingsRepository = new SettingsRepository(SettingsModel, configService); -const authRepository = new AuthRepository(AuthModel, configService); - -export const repository = new RepositoryBroker( - messageRepository, - chatRepository, - contactRepository, - messageUpdateRepository, - webhookRepository, - chatwootRepository, - settingsRepository, - websocketRepository, - rabbitmqRepository, - sqsRepository, - typebotRepository, - proxyRepository, - chamaaiRepository, - authRepository, - configService, - dbserver?.getClient(), -); - -export const cache = new RedisCache(); - -export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache); - -const authService = new AuthService(configService, waMonitor, repository); - -const typebotService = new TypebotService(waMonitor, configService); - -export const typebotController = new TypebotController(typebotService); - -const webhookService = new WebhookService(waMonitor); - -export const webhookController = new WebhookController(webhookService); - -const websocketService = new WebsocketService(waMonitor); - -export const websocketController = new WebsocketController(websocketService); - -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); - -const sqsService = new SqsService(waMonitor); - -export const sqsController = new SqsController(sqsService); - -const chatwootService = new ChatwootService(waMonitor, configService); - -export const chatwootController = new ChatwootController(chatwootService, configService); - -const settingsService = new SettingsService(waMonitor); - -export const settingsController = new SettingsController(settingsService); - -export const instanceController = new InstanceController( - waMonitor, - configService, - repository, - eventEmitter, - authService, - webhookService, - chatwootService, - settingsService, - websocketService, - rabbitmqService, - proxyService, - sqsService, - typebotService, - cache, -); -export const viewsController = new ViewsController(waMonitor, configService); -export const sendMessageController = new SendMessageController(waMonitor); -export const chatController = new ChatController(waMonitor); -export const groupController = new GroupController(waMonitor); - -logger.info('Module - ON'); +import { configService } from '../config/env.config'; +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'; +import { InstanceController } from './controllers/instance.controller'; +import { ProxyController } from './controllers/proxy.controller'; +import { RabbitmqController } from './controllers/rabbitmq.controller'; +import { OpenaiController } from './controllers/openai.controller'; +import { SendMessageController } from './controllers/sendMessage.controller'; +import { SettingsController } from './controllers/settings.controller'; +import { SqsController } from './controllers/sqs.controller'; +import { TypebotController } from './controllers/typebot.controller'; +import { ViewsController } from './controllers/views.controller'; +import { WebhookController } from './controllers/webhook.controller'; +import { WebsocketController } from './controllers/websocket.controller'; +import { + AuthModel, + ChamaaiModel, + ChatModel, + ChatwootModel, + ContactModel, + MessageModel, + MessageUpModel, + ProxyModel, + RabbitmqModel, + OpenaiModel, + ContactOpenaiModel, + SettingsModel, + SqsModel, + TypebotModel, + WebhookModel, + 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'; +import { MessageRepository } from './repository/message.repository'; +import { MessageUpRepository } from './repository/messageUp.repository'; +import { ProxyRepository } from './repository/proxy.repository'; +import { RabbitmqRepository } from './repository/rabbitmq.repository'; +import { OpenaiRepository } from './repository/openai.repository'; +import { RepositoryBroker } from './repository/repository.manager'; +import { SettingsRepository } from './repository/settings.repository'; +import { SqsRepository } from './repository/sqs.repository'; +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'; +import { RabbitmqService } from './services/rabbitmq.service'; +import { OpenaiService } from './services/openai.service'; +import { SettingsService } from './services/settings.service'; +import { SqsService } from './services/sqs.service'; +import { TypebotService } from './services/typebot.service'; +import { WebhookService } from './services/webhook.service'; +import { WebsocketService } from './services/websocket.service'; + +const logger = new Logger('WA MODULE'); + +const messageRepository = new MessageRepository(MessageModel, configService); +const chatRepository = new ChatRepository(ChatModel, configService); +const contactRepository = new ContactRepository(ContactModel, configService); +const messageUpdateRepository = new MessageUpRepository(MessageUpModel, configService); +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 openaiRepository = new OpenaiRepository(OpenaiModel,ContactOpenaiModel, configService); +const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); +const settingsRepository = new SettingsRepository(SettingsModel, configService); +const sqsRepository = new SqsRepository(SqsModel, configService); +const authRepository = new AuthRepository(AuthModel, configService); + +export const repository = new RepositoryBroker( + messageRepository, + chatRepository, + contactRepository, + messageUpdateRepository, + webhookRepository, + chatwootRepository, + settingsRepository, + websocketRepository, + rabbitmqRepository, + openaiRepository, + openaiRepository, + sqsRepository, + typebotRepository, + proxyRepository, + chamaaiRepository, + authRepository, + configService, + dbserver?.getClient(), +); + +export const cache = new RedisCache(); + +export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache); + +const authService = new AuthService(configService, waMonitor, repository); + +const typebotService = new TypebotService(waMonitor, configService); + +export const typebotController = new TypebotController(typebotService); + +const webhookService = new WebhookService(waMonitor); + +export const webhookController = new WebhookController(webhookService); + +const websocketService = new WebsocketService(waMonitor); + +export const websocketController = new WebsocketController(websocketService); + +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); + +const sqsService = new SqsService(waMonitor); + +export const sqsController = new SqsController(sqsService); + +const openaiService = new OpenaiService(waMonitor); + +export const openaiController = new OpenaiController(openaiService); + +const chatwootService = new ChatwootService(waMonitor, configService); + +export const chatwootController = new ChatwootController(chatwootService, configService); + +const settingsService = new SettingsService(waMonitor); + +export const settingsController = new SettingsController(settingsService); + +export const instanceController = new InstanceController( + waMonitor, + configService, + repository, + eventEmitter, + authService, + webhookService, + chatwootService, + settingsService, + websocketService, + rabbitmqService, + openaiService, + proxyService, + sqsService, + typebotService, + cache, +); +export const viewsController = new ViewsController(waMonitor, configService); +export const sendMessageController = new SendMessageController(waMonitor); +export const chatController = new ChatController(waMonitor); +export const groupController = new GroupController(waMonitor); + +logger.info('Module - ON');