diff --git a/dev-env b/dev-env new file mode 100644 index 00000000..9a93b693 --- /dev/null +++ b/dev-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 old mode 100755 new mode 100644 index a51b1ff1..86a45b42 --- a/src/config.ts +++ b/src/config.ts @@ -1,14 +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, - }, -} +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 100755 new mode 100644 index 599a1cd3..306a4032 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -1,348 +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; - 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(); +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/dev-env.yml b/src/dev-env.yml old mode 100755 new mode 100644 index c56757c4..bb57f26c --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -1,179 +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: 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 +# ⚠️ +# ⚠️ 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: "Chat 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.yaml b/src/docs/swagger.yaml old mode 100755 new mode 100644 index 52f0c72e..5f9235ac --- 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.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: {} +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/libs/amqp.server.ts b/src/libs/amqp.server.ts old mode 100755 new mode 100644 index d53b44ad..758c49ed --- 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, 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); -}; +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 100755 new mode 100644 index ecb1e08a..815d04ce --- a/src/libs/db.connect.ts +++ b/src/libs/db.connect.ts @@ -1,26 +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 + 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; - } -})(); +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 old mode 100755 new mode 100644 index 5eb7c9b7..7f3dba41 --- a/src/libs/openai.ts +++ b/src/libs/openai.ts @@ -1,34 +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; - } - -} +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 100755 new mode 100644 index 4164c129..a1ed4017 --- a/src/libs/redis.client.ts +++ b/src/libs/redis.client.ts @@ -1,132 +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 { - - 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); - } - } -} +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 old mode 100755 new mode 100644 index 8414321c..154d1dee --- a/src/libs/redis.ts +++ b/src/libs/redis.ts @@ -1,9 +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, -}) +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/sqs.server.ts b/src/libs/sqs.server.ts old mode 100755 new mode 100644 index 35a69afd..014c6bc2 --- 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 100755 new mode 100644 index c7ee54b5..46615c61 --- 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 old mode 100755 new mode 100644 index ce0a71ae..4603a55c --- a/src/prompts/contabilAgent.ts +++ b/src/prompts/contabilAgent.ts @@ -1,49 +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? - -` +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 old mode 100755 new mode 100644 index 53c0c91b..eeea4ab0 --- a/src/prompts/pizzaAgent.ts +++ b/src/prompts/pizzaAgent.ts @@ -1,94 +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 -` +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 old mode 100755 new mode 100644 index 79f2a3b5..6512a050 --- a/src/utils/initPrompt.ts +++ b/src/utils/initPrompt.ts @@ -1,13 +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) - } -} +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/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts old mode 100755 new mode 100644 index 4c290550..ac25f21e --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -1,112 +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 + - 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'); - }, - }; -} +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 await writeData(creds, 'creds'); + }, + }; +} diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts old mode 100755 new mode 100644 index ee19c1fd..e8c9d3fd --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -1,1140 +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 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'), -}; +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/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts old mode 100755 new mode 100644 index f5896dc1..f1660e33 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -1,740 +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 { 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); - } -} +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 old mode 100755 new mode 100644 index 52cf3c0a..45eaea90 --- a/src/whatsapp/controllers/openai.controller.ts +++ b/src/whatsapp/controllers/openai.controller.ts @@ -1,85 +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); - } -} +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/sqs.controller.ts b/src/whatsapp/controllers/sqs.controller.ts old mode 100755 new mode 100644 index c606054b..ea5c84ba --- 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/dto/contactopenai.dto.ts b/src/whatsapp/dto/contactopenai.dto.ts old mode 100755 new mode 100644 index 19c86aa8..af1eee64 --- a/src/whatsapp/dto/contactopenai.dto.ts +++ b/src/whatsapp/dto/contactopenai.dto.ts @@ -1,5 +1,5 @@ -export class ContactOpenaiDto { - contact?: string; - enabled: boolean; - owner: string; -} +export class ContactOpenaiDto { + contact?: string; + enabled: boolean; + owner: string; +} diff --git a/src/whatsapp/dto/instance.dto.ts b/src/whatsapp/dto/instance.dto.ts old mode 100755 new mode 100644 index ffdedd4e..0ccd9e9f --- a/src/whatsapp/dto/instance.dto.ts +++ b/src/whatsapp/dto/instance.dto.ts @@ -1,44 +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[]; - - 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; -} +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 old mode 100755 new mode 100644 index 33053a6d..fba34fdf --- a/src/whatsapp/dto/openai.dto.ts +++ b/src/whatsapp/dto/openai.dto.ts @@ -1,6 +1,6 @@ -export class OpenaiDto { - chave?: string; - enabled: boolean; - prompts?: string; - events?: string[]; -} +export class OpenaiDto { + chave?: string; + enabled: boolean; + prompts?: string; + events?: string[]; +} diff --git a/src/whatsapp/dto/sqs.dto.ts b/src/whatsapp/dto/sqs.dto.ts old mode 100755 new mode 100644 index c36fd2cf..23cbbd22 --- 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/guards/instance.guard.ts b/src/whatsapp/guards/instance.guard.ts old mode 100755 new mode 100644 index bc0a8753..65fdafb7 --- a/src/whatsapp/guards/instance.guard.ts +++ b/src/whatsapp/guards/instance.guard.ts @@ -1,76 +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 + - 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(); -} +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/contactOpenai.model.ts b/src/whatsapp/models/contactOpenai.model.ts old mode 100755 new mode 100644 index 3a673d15..c6015764 --- a/src/whatsapp/models/contactOpenai.model.ts +++ b/src/whatsapp/models/contactOpenai.model.ts @@ -1,20 +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; +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 100755 new mode 100644 index 76eaf240..98cb200f --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -1,15 +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 './openai.model'; -export * from './contactOpenai.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/openai.model.ts b/src/whatsapp/models/openai.model.ts old mode 100755 new mode 100644 index e7c3eded..4e863686 --- a/src/whatsapp/models/openai.model.ts +++ b/src/whatsapp/models/openai.model.ts @@ -1,22 +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; +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/sqs.model.ts b/src/whatsapp/models/sqs.model.ts old mode 100755 new mode 100644 index 73d8c900..3972f59e --- 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'); +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/repository/openai.repository.ts b/src/whatsapp/repository/openai.repository.ts old mode 100755 new mode 100644 index 8139fc39..23711e01 --- a/src/whatsapp/repository/openai.repository.ts +++ b/src/whatsapp/repository/openai.repository.ts @@ -1,153 +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; - } - } -} +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/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts old mode 100755 new mode 100644 index 2da43fc0..1fd1bed9 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -1,169 +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 { 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); - } - } - } -} +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/sqs.repository.ts b/src/whatsapp/repository/sqs.repository.ts old mode 100755 new mode 100644 index f7961ea2..9e398e1b --- 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/routers/index.router.ts b/src/whatsapp/routers/index.router.ts old mode 100755 new mode 100644 index 745fdb26..4cb5bf82 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -1,63 +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 { 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 }; +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 100755 new mode 100644 index 8de9b769..b73222ac --- a/src/whatsapp/routers/instance.router.ts +++ b/src/whatsapp/routers/instance.router.ts @@ -1,201 +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), - }); - - - 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(); -} +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 old mode 100755 new mode 100644 index a6beb381..b3a4c09a --- a/src/whatsapp/routers/openai.router.ts +++ b/src/whatsapp/routers/openai.router.ts @@ -1,86 +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(); -} +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/sqs.router.ts b/src/whatsapp/routers/sqs.router.ts old mode 100755 new mode 100644 index 2275e916..9ae2a2ab --- 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/services/._whatsapp.service.ts b/src/whatsapp/services/._whatsapp.service.ts new file mode 100644 index 00000000..57e85320 Binary files /dev/null and b/src/whatsapp/services/._whatsapp.service.ts differ diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts old mode 100755 new mode 100644 index c1586483..5026669a --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1,1655 +1,1661 @@ -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); - } - } -} +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') { + 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; + } + + 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 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; + + 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') { + // 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); + } + } +} diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts old mode 100755 new mode 100644 index c0f8b0c1..b696d165 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -1,398 +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 + - 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`); - } - }); - } -} +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 old mode 100755 new mode 100644 index 5c10afa6..f80008ed --- a/src/whatsapp/services/openai.service.ts +++ b/src/whatsapp/services/openai.service.ts @@ -1,57 +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: [] }; - } - } - -} +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/sqs.service.ts b/src/whatsapp/services/sqs.service.ts old mode 100755 new mode 100644 index aa902711..06b1ae59 --- 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 100755 new mode 100644 index e2e1e6b8..00eb9639 --- a/src/whatsapp/services/typebot.service.ts +++ b/src/whatsapp/services/typebot.service.ts @@ -1,1010 +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) {} - 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; - - */ - } -} +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 || data.prefilledVariables?.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/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts old mode 100755 new mode 100644 index 6131b921..5a15c56c --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1,3841 +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, 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()); - } - } -} +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 100755 new mode 100644 index 20cdd4be..66549f5a --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -1,145 +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[]; - }; - - 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', -]; +/* 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 100755 new mode 100644 index 765f934c..e78b5f6c --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -1,175 +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 { 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'); +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');