diff --git a/.DS_Store b/.DS_Store index 11ffa2bd..f07b5fd4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.eslintrc.js b/.eslintrc.js index d3545e60..f805da92 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,10 +3,14 @@ module.exports = { parserOptions: { sourceType: 'CommonJS', }, - plugins: ['@typescript-eslint/eslint-plugin'], + plugins: [ + '@typescript-eslint', + 'simple-import-sort', + 'import' + ], extends: [ + 'eslint:recommended', 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', 'plugin:prettier/recommended' ], globals: { @@ -26,7 +30,11 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'error', + 'import/first': 'error', + 'import/no-duplicates': 'error', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', '@typescript-eslint/ban-types': [ 'error', { diff --git a/.gitignore b/.gitignore index e707b33e..2364b077 100644 --- a/.gitignore +++ b/.gitignore @@ -16,16 +16,19 @@ lerna-debug.log* /docker-compose-data /docker-data +docker-compose.yaml + # Package /yarn.lock /package-lock.json -# IDE - VSCode +# IDEs .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +.nova/* # Prisma /prisma/migrations @@ -38,3 +41,6 @@ lerna-debug.log* /store /temp/* + +.DS_Store +*.DS_Store \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js index 067abea5..f55f3f06 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -2,8 +2,11 @@ module.exports = { semi: true, trailingComma: 'all', singleQuote: true, - printWidth: 90, + printWidth: 120, + arrowParens: 'always', tabWidth: 2, - bracketSameLine: true, - bracketSpacing: true + useTabs: false, + bracketSameLine: false, + bracketSpacing: true, + parser: 'typescript' } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d70a8685..552a7d0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,124 @@ +# 1.4.8 (2023-07-27 10:27) + +### Fixed + +* Fixed error return bug + +# 1.4.7 (2023-07-27 08:47) + +### Fixed + +* Fixed error return bug +* Fixed problem of getting message when deleting message in chatwoot +* Change in error return pattern + +# 1.4.6 (2023-07-26 17:54) + +### Fixed + +* Fixed bug of creating new inbox by chatwoot +* When conversation reopens is pending when conversation pending is true +* Added docker-compose file with dockerhub image + +# 1.4.5 (2023-07-26 09:32) + +### Fixed + +* Fixed problems in localization template in chatwoot +* Fix mids going duplicated in chatwoot + +# 1.4.4 (2023-07-25 15:24) + +### Fixed + +* Fixed chatwoot line wrap issue +* Solved receive location in chatwoot +* When requesting the pairing code, it also brings the qr code +* Option reopen_conversation in chatwoot endpoint +* Option conversation_pending in chatwoot endpoint + +# 1.4.3 (2023-07-25 10:51) + +### Fixed + +* Adjusts in settings with options always_online, read_messages and read_status +* Fixed send webhook for event CALL +* Create instance with settings + +# 1.4.2 (2023-07-24 20:52) + +### Fixed + +* Fixed validation is set settings +* Adjusts in group validations +* Ajusts in sticker message to chatwoot + +# 1.4.1 (2023-07-24 18:28) + +### Fixed + +* Fixed reconnect with pairing code or qrcode +* Fixed problem in createJid + +# 1.4.0 (2023-07-24 17:03) + +### Features + +* Added connection functionality via pairing code +* Added fetch profile endpoint in chat controller +* Created settings controller +* Added reject call and send text message when receiving a call +* Added setting to ignore group messages +* Added connection with pairing code in chatwoot with command /init:{NUMBER} +* Added encoding option in endpoint sendWhatsAppAudio + +### Fixed + +* Added link preview option in send text message +* Fixed problem with fileSha256 appearing when sending a sticker in chatwoot +* Fixed issue where it was not possible to open a conversation when sent at first by me on my cell phone in chatwoot +* Now it only updates the contact name if it is the same as the phone number in chatwoot +* Now accepts all chatwoot inbox templates +* Command to create new instances set to /new_instance:{NAME}:{NUMBER} +* Fix in chatwoot set, sign msg can now be disabled + +### Integrations + +- Chatwoot: v2.18.0 - v3.0.0 (Beta) + +# 1.3.2 (2023-07-21 17:19) + +### Fixed + +* Fix in update settings that needed to restart after updated +* Correction in the use of the api with mongodb +* Adjustments to search endpoint for contacts, chats, messages and Status messages +* Now when deleting the instance, the data referring to it in mongodb is also deleted +* It is now validated if the instance name contains uppercase and special characters +* For compatibility reasons, container mode has been removed +* Added docker-compose files example + +### Integrations + +- Chatwoot: v2.18.0 + # 1.3.1 (2023-07-20 07:48) ### Fixed * Adjust in create store files +### Integrations + +- Chatwoot: v2.18.0 + # 1.3.0 (2023-07-19 11:33) ### Features * Added messages.delete event * Added restart instance endpoint -* Created automation for creating instances in the chatwoot bot with the command '#inbox_whatsapp:' +* Created automation for creating instances in the chatwoot bot with the command '#inbox_whatsapp:{INSTANCE_NAME} * Change Baileys version to: 6.4.0 * Send contact in chatwoot * Send contact array in chatwoot diff --git a/Docker/.env.example b/Docker/.env.example index 81eee8ca..f4e8291d 100644 --- a/Docker/.env.example +++ b/Docker/.env.example @@ -1,13 +1,13 @@ # Server URL - Set your application url -SERVER_URL='http://localhost:8080' +SERVER_URL=http://localhost:8080 # Cors - * for all or set separate by commas - ex.: 'yourdomain1.com, yourdomain2.com' -CORS_ORIGIN='*' -CORS_METHODS='POST,GET,PUT,DELETE' +CORS_ORIGIN=* +CORS_METHODS=POST,GET,PUT,DELETE CORS_CREDENTIALS=true # Determine the logs to be displayed -LOG_LEVEL='ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS' +LOG_LEVEL=ERROR,WARN,DEBUG,INFO,LOG,VERBOSE,DARK,WEBHOOKS LOG_COLOR=true # Log Baileys - "fatal" | "error" | "warn" | "info" | "debug" | "trace" LOG_BAILEYS=error @@ -31,9 +31,9 @@ CLEAN_STORE_CONTACTS=true CLEAN_STORE_CHATS=true # Permanent data storage -DATABASE_ENABLED=false +DATABASE_ENABLED=true DATABASE_CONNECTION_URI=mongodb://root:root@mongodb:27017/?authSource=admin&readPreference=primary&ssl=false&directConnection=true -DATABASE_CONNECTION_DB_PREFIX_NAME=evolution +DATABASE_CONNECTION_DB_PREFIX_NAME=evdocker # Choose the data you want to save in the application's database or store DATABASE_SAVE_DATA_INSTANCE=false @@ -42,9 +42,9 @@ DATABASE_SAVE_MESSAGE_UPDATE=false DATABASE_SAVE_DATA_CONTACTS=false DATABASE_SAVE_DATA_CHATS=false -REDIS_ENABLED=false +REDIS_ENABLED=true REDIS_URI=redis://redis:6379 -REDIS_PREFIX_KEY=evolution +REDIS_PREFIX_KEY=evdocker # Global Webhook Settings # Each instance's Webhook URL and events will be requested at the time it is created @@ -73,11 +73,12 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true WEBHOOK_EVENTS_GROUPS_UPDATE=true WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true WEBHOOK_EVENTS_CONNECTION_UPDATE=true +WEBHOOK_EVENTS_CALL=true # This event fires every time a new token is requested via the refresh route WEBHOOK_EVENTS_NEW_JWT_TOKEN=false # Name that will be displayed on smartphone connection -CONFIG_SESSION_PHONE_CLIENT='Evolution API' +CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI # Browser Name = chrome | firefox | edge | opera | safari CONFIG_SESSION_PHONE_NAME=chrome @@ -88,22 +89,12 @@ QRCODE_LIMIT=30 # We recommend using the apikey because it will allow you to use a custom token, # if you use jwt, a random token will be generated and may be expired and you will have to generate a new token # jwt or 'apikey' -AUTHENTICATION_TYPE='apikey' +AUTHENTICATION_TYPE=apikey ## Define a global apikey to access all instances. ### OBS: This key must be inserted in the request header to create an instance. -AUTHENTICATION_API_KEY='B6D711FCDE4D4FD5936544120E713976' +AUTHENTICATION_API_KEY=B6D711FCDE4D4FD5936544120E713976 AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true ## Set the secret key to encrypt and decrypt your token and its expiration time # seconds - 3600s ===1h | zero (0) - never expires AUTHENTICATION_JWT_EXPIRIN_IN=0 -AUTHENTICATION_JWT_SECRET='L0YWtjb2w554WFqPG' -# Set the instance name and webhook url to create an instance in init the application -# With this option activated, you work with a url per webhook event, respecting the local url and the name of each event -# container or server -AUTHENTICATION_INSTANCE_MODE=server -# if you are using container mode, set the container name and the webhook url to default instance -AUTHENTICATION_INSTANCE_NAME=evolution -AUTHENTICATION_INSTANCE_WEBHOOK_URL='' -AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID=1 -AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN=123456 -AUTHENTICATION_INSTANCE_CHATWOOT_URL='' +AUTHENTICATION_JWT_SECRET='L=0YWt]b2w[WF>#>:&E`' diff --git a/Docker/redis/docker-compose.yaml b/Docker/redis/docker-compose.yaml index 6409b851..b0bb5985 100644 --- a/Docker/redis/docker-compose.yaml +++ b/Docker/redis/docker-compose.yaml @@ -12,13 +12,6 @@ services: - evolution_redis:/data ports: - 6379:6379 - - rebrow: - image: marian/rebrow - ports: - - 5001:5001 - links: - - redis volumes: evolution_redis: diff --git a/Dockerfile b/Dockerfile index 088f6fa1..0b3ac950 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ COPY ./package.json . ENV DOCKER_ENV=true -ENV SERVER_URL='http://localhost:8080' +ENV SERVER_URL=http://localhost:8080 ENV CORS_ORIGIN=* ENV CORS_METHODS=POST,GET,PUT,DELETE @@ -74,10 +74,11 @@ ENV WEBHOOK_EVENTS_GROUPS_UPSERT=true ENV WEBHOOK_EVENTS_GROUPS_UPDATE=true ENV WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true ENV WEBHOOK_EVENTS_CONNECTION_UPDATE=true +ENV WEBHOOK_EVENTS_CALL=true ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false -ENV CONFIG_SESSION_PHONE_CLIENT='Evolution API' +ENV CONFIG_SESSION_PHONE_CLIENT=EvolutionAPI ENV CONFIG_SESSION_PHONE_NAME=chrome ENV QRCODE_LIMIT=30 diff --git a/docker-compose.yaml.example b/docker-compose.yaml.example new file mode 100644 index 00000000..93e04cc0 --- /dev/null +++ b/docker-compose.yaml.example @@ -0,0 +1,28 @@ +version: '3.3' + +services: + api: + container_name: evolution_api + image: evolution/api:local + restart: always + ports: + - 8080:8080 + volumes: + - evolution_instances:/evolution/instances + - evolution_store:/evolution/store + networks: + - evolution-net + env_file: + - ./Docker/.env + command: ['node', './dist/src/main.js'] + expose: + - 8080 + +volumes: + evolution_instances: + evolution_store: + +networks: + evolution-net: + external: true + \ No newline at end of file diff --git a/docker-compose.yaml.example.complete b/docker-compose.yaml.example.complete new file mode 100644 index 00000000..4e453b51 --- /dev/null +++ b/docker-compose.yaml.example.complete @@ -0,0 +1,79 @@ +version: '3.3' + +services: + api: + container_name: evolution_api + image: evolution/api:local + restart: always + ports: + - 8080:8080 + volumes: + - evolution_instances:/evolution/instances + - evolution_store:/evolution/store + networks: + - evolution-net + env_file: + - ./Docker/.env + command: ['node', './dist/src/main.js'] + expose: + - 8080 + + mongodb: + container_name: mongodb + image: mongo + restart: always + ports: + - 27017:27017 + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=root + - PUID=1000 + - PGID=1000 + volumes: + - evolution_mongodb_data:/data/db + - evolution_mongodb_configdb:/data/configdb + networks: + - evolution-net + expose: + - 27017 + + mongo-express: + image: mongo-express + networks: + - evolution-net + environment: + ME_CONFIG_BASICAUTH_USERNAME: root + ME_CONFIG_BASICAUTH_PASSWORD: root + ME_CONFIG_MONGODB_SERVER: mongodb + ME_CONFIG_MONGODB_ADMINUSERNAME: root + ME_CONFIG_MONGODB_ADMINPASSWORD: root + ports: + - 8081:8081 + links: + - mongodb + + redis: + image: redis:latest + container_name: redis + command: > + redis-server + --port 6379 + --appendonly yes + volumes: + - evolution_redis:/data + networks: + - evolution-net + ports: + - 6379:6379 + +volumes: + evolution_instances: + evolution_store: + evolution_mongodb_data: + evolution_mongodb_configdb: + evolution_redis: + +networks: + evolution-net: + external: true + \ No newline at end of file diff --git a/docker-compose.yaml.example.dockerhub b/docker-compose.yaml.example.dockerhub new file mode 100644 index 00000000..6136997d --- /dev/null +++ b/docker-compose.yaml.example.dockerhub @@ -0,0 +1,28 @@ +version: '3.3' + +services: + api: + container_name: evolution_api + image: davidsongomes/evolution-api:latest + restart: always + ports: + - 8080:8080 + volumes: + - evolution_instances:/evolution/instances + - evolution_store:/evolution/store + networks: + - evolution-net + env_file: + - ./Docker/.env + command: ['node', './dist/src/main.js'] + expose: + - 8080 + +volumes: + evolution_instances: + evolution_store: + +networks: + evolution-net: + external: true + \ No newline at end of file diff --git a/package.json b/package.json index 09df55a4..0d5169f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evolution-api", - "version": "1.3.0", + "version": "1.4.8", "description": "Rest api for communication with WhatsApp", "main": "./dist/src/main.js", "scripts": { @@ -8,7 +8,8 @@ "start": "ts-node --files --transpile-only ./src/main.ts", "start:prod": "bash start.sh", "dev:server": "clear && tsnd --files --transpile-only --respawn --ignore-watch node_modules ./src/main.ts", - "test": "clear && tsnd --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts" + "test": "clear && tsnd --files --transpile-only --respawn --ignore-watch node_modules ./test/all.test.ts", + "lint": "eslint --fix --ext .ts src" }, "repository": { "type": "git", @@ -45,7 +46,7 @@ "@figuro/chatwoot-sdk": "^1.1.14", "@hapi/boom": "^10.0.1", "@sentry/node": "^7.59.2", - "@whiskeysockets/baileys": "^6.4.0", + "@whiskeysockets/baileys": "github:EvolutionAPI/Baileys", "axios": "^1.3.5", "class-validator": "^0.13.2", "compression": "^1.7.4", @@ -81,17 +82,20 @@ "@types/express": "^4.17.17", "@types/js-yaml": "^4.0.5", "@types/jsonwebtoken": "^8.5.9", + "@types/mime-types": "^2.1.1", "@types/node": "^18.15.11", "@types/node-windows": "^0.1.2", "@types/qrcode": "^1.5.0", "@types/qrcode-terminal": "^0.12.0", "@types/uuid": "^8.3.4", - "@typescript-eslint/eslint-plugin": "^5.57.1", - "@typescript-eslint/parser": "^5.57.1", - "eslint": "^8.38.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^8.45.0", "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.7", + "eslint-plugin-simple-import-sort": "^10.0.0", + "prettier": "^2.8.8", "ts-node-dev": "^2.0.0", "typescript": "^4.9.5" } diff --git a/src/config/env.config.ts b/src/config/env.config.ts index 7221f474..b90141ff 100644 --- a/src/config/env.config.ts +++ b/src/config/env.config.ts @@ -1,7 +1,7 @@ +import { isBooleanString } from 'class-validator'; import { readFileSync } from 'fs'; import { load } from 'js-yaml'; import { join } from 'path'; -import { isBooleanString } from 'class-validator'; export type HttpServer = { TYPE: 'http' | 'https'; PORT: number; URL: string }; @@ -14,15 +14,7 @@ export type Cors = { export type LogBaileys = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'; -export type LogLevel = - | 'ERROR' - | 'WARN' - | 'DEBUG' - | 'INFO' - | 'LOG' - | 'VERBOSE' - | 'DARK' - | 'WEBHOOKS'; +export type LogLevel = 'ERROR' | 'WARN' | 'DEBUG' | 'INFO' | 'LOG' | 'VERBOSE' | 'DARK' | 'WEBHOOKS'; export type Log = { LEVEL: LogLevel[]; @@ -89,25 +81,18 @@ export type EventsWebhook = { GROUPS_UPSERT: boolean; GROUP_UPDATE: boolean; GROUP_PARTICIPANTS_UPDATE: boolean; + CALL: boolean; NEW_JWT_TOKEN: boolean; }; export type ApiKey = { KEY: string }; export type Jwt = { EXPIRIN_IN: number; SECRET: string }; -export type Instance = { - NAME: string; - WEBHOOK_URL: string; - MODE: string; - CHATWOOT_ACCOUNT_ID: string; - CHATWOOT_TOKEN: string; - CHATWOOT_URL: string; -}; + export type Auth = { API_KEY: ApiKey; EXPOSE_IN_FETCH_INSTANCES: boolean; JWT: Jwt; TYPE: 'jwt' | 'apikey'; - INSTANCE: Instance; }; export type DelInstance = number | boolean; @@ -163,9 +148,7 @@ export class ConfigService { } private envYaml(): Env { - return load( - readFileSync(join(process.cwd(), 'src', 'env.yml'), { encoding: 'utf-8' }), - ) as Env; + return load(readFileSync(join(process.cwd(), 'src', 'env.yml'), { encoding: 'utf-8' })) as Env; } private envProcess(): Env { @@ -251,8 +234,8 @@ export class ConfigService { 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', + 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', }, }, @@ -268,23 +251,13 @@ export class ConfigService { API_KEY: { KEY: process.env.AUTHENTICATION_API_KEY, }, - EXPOSE_IN_FETCH_INSTANCES: - process.env?.AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES === 'true', + 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, }, - INSTANCE: { - NAME: process.env.AUTHENTICATION_INSTANCE_NAME, - WEBHOOK_URL: process.env.AUTHENTICATION_INSTANCE_WEBHOOK_URL, - MODE: process.env.AUTHENTICATION_INSTANCE_MODE, - CHATWOOT_ACCOUNT_ID: - process.env.AUTHENTICATION_INSTANCE_CHATWOOT_ACCOUNT_ID || '', - CHATWOOT_TOKEN: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_TOKEN || '', - CHATWOOT_URL: process.env.AUTHENTICATION_INSTANCE_CHATWOOT_URL || '', - }, }, }; } diff --git a/src/config/logger.config.ts b/src/config/logger.config.ts index 26e4c38f..a5ca6a23 100644 --- a/src/config/logger.config.ts +++ b/src/config/logger.config.ts @@ -1,6 +1,7 @@ -import { configService, Log } from './env.config'; import dayjs from 'dayjs'; +import { configService, Log } from './env.config'; + const formatDateLog = (timestamp: number) => dayjs(timestamp) .toDate() diff --git a/src/db/db.connect.ts b/src/db/db.connect.ts index 59530b48..b11610c7 100644 --- a/src/db/db.connect.ts +++ b/src/db/db.connect.ts @@ -1,4 +1,5 @@ import mongoose from 'mongoose'; + import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; diff --git a/src/db/redis.client.ts b/src/db/redis.client.ts index 50e7efcb..dffeb949 100644 --- a/src/db/redis.client.ts +++ b/src/db/redis.client.ts @@ -1,7 +1,8 @@ import { createClient, RedisClientType } from '@redis/client'; -import { Logger } from '../config/logger.config'; import { BufferJSON } from '@whiskeysockets/baileys'; + import { Redis } from '../config/env.config'; +import { Logger } from '../config/logger.config'; export class RedisCache { constructor() { @@ -59,11 +60,7 @@ export class RedisCache { 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, - ); + return await this.client.hSet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field, json); } catch (error) { this.logger.error(error); } @@ -72,10 +69,7 @@ export class RedisCache { public async readData(field: string) { try { this.logger.verbose('readData: ' + field); - const data = await this.client.hGet( - this.redisEnv.PREFIX_KEY + ':' + this.instanceName, - field, - ); + const data = await this.client.hGet(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); if (data) { this.logger.verbose('readData: ' + field + ' success'); @@ -92,10 +86,7 @@ export class RedisCache { public async removeData(field: string) { try { this.logger.verbose('removeData: ' + field); - return await this.client.hDel( - this.redisEnv.PREFIX_KEY + ':' + this.instanceName, - field, - ); + return await this.client.hDel(this.redisEnv.PREFIX_KEY + ':' + this.instanceName, field); } catch (error) { this.logger.error(error); } @@ -104,9 +95,7 @@ export class RedisCache { 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, - ); + const result = await this.client.del(hash || this.redisEnv.PREFIX_KEY + ':' + this.instanceName); return result; } catch (error) { diff --git a/src/dev-env.yml b/src/dev-env.yml index c0f907fc..b45d3201 100644 --- a/src/dev-env.yml +++ b/src/dev-env.yml @@ -110,6 +110,7 @@ WEBHOOK: 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 @@ -136,14 +137,4 @@ AUTHENTICATION: # 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` - # Set the instance name and webhook url to create an instance in init the application - INSTANCE: - # With this option activated, you work with a url per webhook event, respecting the local url and the name of each event - MODE: server # container or server - # if you are using container mode, set the container name and the webhook url to default instance - NAME: evolution - WEBHOOK_URL: - CHATWOOT_ACCOUNT_ID: 1 - CHATWOOT_TOKEN: 123456 - CHATWOOT_URL: + SECRET: L=0YWt]b2w[WF>#>:&E` \ No newline at end of file diff --git a/src/exceptions/401.exception.ts b/src/exceptions/401.exception.ts index 72734d4e..d5424c71 100644 --- a/src/exceptions/401.exception.ts +++ b/src/exceptions/401.exception.ts @@ -5,7 +5,7 @@ export class UnauthorizedException { throw { status: HttpStatus.UNAUTHORIZED, error: 'Unauthorized', - message: objectError.length > 0 ? objectError : undefined, + message: objectError.length > 0 ? objectError : 'Unauthorized', }; } } diff --git a/src/main.ts b/src/main.ts index ac66e7b5..1077d64d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,17 @@ +import 'express-async-errors'; + import compression from 'compression'; -import { configService, Cors, HttpServer } from './config/env.config'; import cors from 'cors'; import express, { json, NextFunction, Request, Response, urlencoded } from 'express'; import { join } from 'path'; + +import { configService, Cors, HttpServer } from './config/env.config'; import { onUnexpectedError } from './config/error.config'; import { Logger } from './config/logger.config'; import { ROOT_DIR } from './config/path.config'; -import { waMonitor } from './whatsapp/whatsapp.module'; -import { HttpStatus, router } from './whatsapp/routers/index.router'; -import 'express-async-errors'; import { ServerUP } from './utils/server-up'; -import * as Sentry from '@sentry/node'; +import { HttpStatus, router } from './whatsapp/routers/index.router'; +import { waMonitor } from './whatsapp/whatsapp.module'; function initWA() { waMonitor.loadInstance(); @@ -20,27 +21,6 @@ function bootstrap() { const logger = new Logger('SERVER'); const app = express(); - // Sentry.init({ - // dsn: '', - // integrations: [ - // // enable HTTP calls tracing - // new Sentry.Integrations.Http({ tracing: true }), - // // enable Express.js middleware tracing - // new Sentry.Integrations.Express({ app }), - // // Automatically instrument Node.js libraries and frameworks - // ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(), - // ], - - // // Set tracesSampleRate to 1.0 to capture 100% - // // of transactions for performance monitoring. - // // We recommend adjusting this value in production - // tracesSampleRate: 1.0, - // }); - - // app.use(Sentry.Handlers.requestHandler()); - - // app.use(Sentry.Handlers.tracingHandler()); - app.use( cors({ origin(requestOrigin, callback) { @@ -65,26 +45,29 @@ function bootstrap() { app.use('/', router); - // app.use(Sentry.Handlers.errorHandler()); - - // app.use(function onError(err, req, res, next) { - // res.statusCode = 500; - // res.end(res.sentry + '\n'); - // }); - app.use( (err: Error, req: Request, res: Response, next: NextFunction) => { if (err) { - return res.status(err['status'] || 500).json(err); + 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, - message: `Cannot ${method.toUpperCase()} ${url}`, error: 'Not Found', + response: { + message: [`Cannot ${method.toUpperCase()} ${url}`], + }, }); next(); @@ -96,9 +79,7 @@ function bootstrap() { ServerUP.app = app; const server = ServerUP[httpServer.TYPE]; - server.listen(httpServer.PORT, () => - logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT), - ); + server.listen(httpServer.PORT, () => logger.log(httpServer.TYPE.toUpperCase() + ' - ON: ' + httpServer.PORT)); initWA(); diff --git a/src/utils/server-up.ts b/src/utils/server-up.ts index 9868efc0..e06caea7 100644 --- a/src/utils/server-up.ts +++ b/src/utils/server-up.ts @@ -1,8 +1,9 @@ import { Express } from 'express'; import { readFileSync } from 'fs'; -import { configService, SslConf } from '../config/env.config'; -import * as https from 'https'; import * as http from 'http'; +import * as https from 'https'; + +import { configService, SslConf } from '../config/env.config'; export class ServerUP { static #app: Express; diff --git a/src/utils/use-multi-file-auth-state-db.ts b/src/utils/use-multi-file-auth-state-db.ts index d0c518da..8b0d76e4 100644 --- a/src/utils/use-multi-file-auth-state-db.ts +++ b/src/utils/use-multi-file-auth-state-db.ts @@ -6,6 +6,7 @@ import { proto, SignalDataTypeMap, } from '@whiskeysockets/baileys'; + import { configService, Database } from '../config/env.config'; import { Logger } from '../config/logger.config'; import { dbserver } from '../db/db.connect'; @@ -24,12 +25,12 @@ export async function useMultiFileAuthStateDb( const writeData = async (data: any, key: string): Promise => { try { await client.connect(); - return await collection.replaceOne( - { _id: key }, - JSON.parse(JSON.stringify(data, BufferJSON.replacer)), - { upsert: true }, - ); - } catch {} + 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 => { @@ -38,14 +39,18 @@ export async function useMultiFileAuthStateDb( const data = await collection.findOne({ _id: key }); const creds = JSON.stringify(data); return JSON.parse(creds, BufferJSON.reviver); - } catch {} + } catch (error) { + logger.error(error); + } }; const removeData = async (key: string) => { try { await client.connect(); return await collection.deleteOne({ _id: key }); - } catch {} + } catch (error) { + logger.error(error); + } }; const creds: AuthenticationCreds = (await readData('creds')) || initAuthCreds(); diff --git a/src/utils/use-multi-file-auth-state-redis-db.ts b/src/utils/use-multi-file-auth-state-redis-db.ts index c5450ddc..57439ced 100644 --- a/src/utils/use-multi-file-auth-state-redis-db.ts +++ b/src/utils/use-multi-file-auth-state-redis-db.ts @@ -5,9 +5,9 @@ import { proto, SignalDataTypeMap, } from '@whiskeysockets/baileys'; -import { RedisCache } from '../db/redis.client'; + import { Logger } from '../config/logger.config'; -import { Redis } from '../config/env.config'; +import { RedisCache } from '../db/redis.client'; export async function useMultiFileAuthStateRedisDb(cache: RedisCache): Promise<{ state: AuthenticationState; diff --git a/src/validate/validate.schema.ts b/src/validate/validate.schema.ts index 2b9ce19a..8c1a4667 100644 --- a/src/validate/validate.schema.ts +++ b/src/validate/validate.schema.ts @@ -53,11 +53,13 @@ export const instanceNameSchema: JSONSchema7 = { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'CALL', 'NEW_JWT_TOKEN', ], }, }, qrcode: { type: 'boolean', enum: [true, false] }, + number: { type: 'string', pattern: '^\\d+[\\.@\\w-]+' }, token: { type: 'string' }, }, ...isNotEmpty('instanceName'), @@ -123,7 +125,6 @@ const optionsSchema: JSONSchema7 = { const numberDefinition: JSONSchema7Definition = { type: 'string', - pattern: '^\\d+[\\.@\\w-]+', description: 'Invalid format', }; @@ -398,7 +399,7 @@ export const contactMessageSchema: JSONSchema7 = { email: { type: 'string' }, url: { type: 'string' }, }, - required: ['fullName', 'wuid', 'phoneNumber'], + required: ['fullName', 'phoneNumber'], ...isNotEmpty('fullName'), }, minItems: 1, @@ -445,7 +446,6 @@ export const whatsappNumberSchema: JSONSchema7 = { uniqueItems: true, items: { type: 'string', - pattern: '^\\d+', description: '"numbers" must be an array of numeric strings', }, }, @@ -456,7 +456,7 @@ export const readMessageSchema: JSONSchema7 = { $id: v4(), type: 'object', properties: { - readMessages: { + read_messages: { type: 'array', minItems: 1, uniqueItems: true, @@ -471,7 +471,7 @@ export const readMessageSchema: JSONSchema7 = { }, }, }, - required: ['readMessages'], + required: ['read_messages'], }; export const privacySettingsSchema: JSONSchema7 = { @@ -587,6 +587,17 @@ export const profilePictureSchema: JSONSchema7 = { }, }; +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', @@ -657,6 +668,7 @@ export const createGroupSchema: JSONSchema7 = { subject: { type: 'string' }, description: { type: 'string' }, profilePicture: { type: 'string' }, + promoteParticipants: { type: 'boolean', enum: [true, false] }, participants: { type: 'array', minItems: 1, @@ -843,6 +855,7 @@ export const webhookSchema: JSONSchema7 = { 'GROUP_UPDATE', 'GROUP_PARTICIPANTS_UPDATE', 'CONNECTION_UPDATE', + 'CALL', 'NEW_JWT_TOKEN', ], }, @@ -861,7 +874,24 @@ export const chatwootSchema: JSONSchema7 = { 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'], - ...isNotEmpty('account_id', 'token', 'url', 'sign_msg'), + 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'), }; diff --git a/src/whatsapp/abstract/abstract.repository.ts b/src/whatsapp/abstract/abstract.repository.ts index a7215383..a5b7a841 100644 --- a/src/whatsapp/abstract/abstract.repository.ts +++ b/src/whatsapp/abstract/abstract.repository.ts @@ -1,5 +1,6 @@ import { existsSync, mkdirSync, writeFileSync } from 'fs'; import { join } from 'path'; + import { ConfigService, Database } from '../../config/env.config'; import { ROOT_DIR } from '../../config/path.config'; @@ -34,11 +35,9 @@ export abstract class Repository implements IRepository { mkdirSync(create.path, { recursive: true }); } try { - writeFileSync( - join(create.path, create.fileName + '.json'), - JSON.stringify({ ...create.data }), - { encoding: 'utf-8' }, - ); + writeFileSync(join(create.path, create.fileName + '.json'), JSON.stringify({ ...create.data }), { + encoding: 'utf-8', + }); return { message: 'create - success' }; } finally { @@ -46,19 +45,23 @@ export abstract class Repository implements IRepository { } }; - public insert(data: any, instanceName: string, saveDb = false): Promise { + // eslint-disable-next-line + public insert(data: any, instanceName: string, saveDb = false): Promise { throw new Error('Method not implemented.'); } - public update(data: any, instanceName: string, saveDb = false): Promise { + // eslint-disable-next-line + public update(data: any, instanceName: string, saveDb = false): Promise { throw new Error('Method not implemented.'); } - public find(query: any): Promise { + // eslint-disable-next-line + public find(query: any): Promise { throw new Error('Method not implemented.'); } - delete(query: any, force?: boolean): Promise { + // eslint-disable-next-line + delete(query: any, force?: boolean): Promise { throw new Error('Method not implemented.'); } } diff --git a/src/whatsapp/abstract/abstract.router.ts b/src/whatsapp/abstract/abstract.router.ts index cb224cd6..7c603880 100644 --- a/src/whatsapp/abstract/abstract.router.ts +++ b/src/whatsapp/abstract/abstract.router.ts @@ -1,11 +1,13 @@ -import { InstanceDto } from '../dto/instance.dto'; -import { JSONSchema7 } from 'json-schema'; -import { Request } from 'express'; -import { validate } from 'jsonschema'; -import { BadRequestException } from '../../exceptions'; import 'express-async-errors'; + +import { Request } from 'express'; +import { JSONSchema7 } from 'json-schema'; +import { validate } from 'jsonschema'; + import { Logger } from '../../config/logger.config'; -import { GetParticipant, GroupInvite, GroupJid } from '../dto/group.dto'; +import { BadRequestException } from '../../exceptions'; +import { GetParticipant, GroupInvite } from '../dto/group.dto'; +import { InstanceDto } from '../dto/instance.dto'; type DataValidate = { request: Request; @@ -46,20 +48,21 @@ export abstract class RouterBroker { const v = schema ? validate(ref, schema) : { valid: true, errors: [] }; if (!v.valid) { - const message: any[] = v.errors.map(({ property, stack, schema }) => { + const message: any[] = v.errors.map(({ stack, schema }) => { let message: string; if (schema['description']) { message = schema['description']; } else { message = stack.replace('instance.', ''); } - return { - property: property.replace('instance.', ''), - message, - }; + return message; + // return { + // property: property.replace('instance.', ''), + // message, + // }; }); - logger.error([...message]); - throw new BadRequestException(...message); + logger.error(message); + throw new BadRequestException(message); } return await execute(instance, ref); @@ -99,21 +102,29 @@ export abstract class RouterBroker { public async groupValidate(args: DataValidate) { const { request, ClassRef, schema, execute } = args; - const groupJid = request.query as unknown as GroupJid; - - if (!groupJid?.groupJid) { - throw new BadRequestException( - 'The group id needs to be informed in the query', - 'ex: "groupJid=120362@g.us"', - ); - } - const instance = request.params as unknown as InstanceDto; const body = request.body; + let groupJid = body?.groupJid; + + if (!groupJid) { + if (request.query?.groupJid) { + groupJid = request.query.groupJid; + } else { + throw new BadRequestException('The group id needs to be informed in the query', 'ex: "groupJid=120362@g.us"'); + } + } + + if (!groupJid.endsWith('@g.us')) { + groupJid = groupJid + '@g.us'; + } + + Object.assign(body, { + groupJid: groupJid, + }); + const ref = new ClassRef(); - Object.assign(body, groupJid); Object.assign(ref, body); const v = validate(ref, schema); @@ -186,9 +197,7 @@ export abstract class RouterBroker { const getParticipants = request.query as unknown as GetParticipant; if (!getParticipants?.getParticipants) { - throw new BadRequestException( - 'The getParticipants needs to be informed in the query', - ); + throw new BadRequestException('The getParticipants needs to be informed in the query'); } const instance = request.params as unknown as InstanceDto; diff --git a/src/whatsapp/controllers/chat.controller.ts b/src/whatsapp/controllers/chat.controller.ts index 0a176059..0299841c 100644 --- a/src/whatsapp/controllers/chat.controller.ts +++ b/src/whatsapp/controllers/chat.controller.ts @@ -1,7 +1,8 @@ -import { proto } from '@whiskeysockets/baileys'; +import { Logger } from '../../config/logger.config'; import { ArchiveChatDto, DeleteMessage, + getBase64FromMediaMessageDto, NumberDto, PrivacySettingDto, ProfileNameDto, @@ -9,14 +10,12 @@ import { ProfileStatusDto, ReadMessageDto, WhatsAppNumberDto, - getBase64FromMediaMessageDto, } from '../dto/chat.dto'; import { InstanceDto } from '../dto/instance.dto'; import { ContactQuery } from '../repository/contact.repository'; import { MessageQuery } from '../repository/message.repository'; import { MessageUpQuery } from '../repository/messageUp.repository'; import { WAMonitoringService } from '../services/monitor.service'; -import { Logger } from '../../config/logger.config'; const logger = new Logger('ChatController'); @@ -48,18 +47,18 @@ export class ChatController { return await this.waMonitor.waInstances[instanceName].profilePicture(data.number); } + public async fetchProfile({ instanceName }: InstanceDto, data: NumberDto) { + logger.verbose('requested fetchProfile from ' + instanceName + ' instance'); + return await this.waMonitor.waInstances[instanceName].fetchProfile(instanceName, data.number); + } + public async fetchContacts({ instanceName }: InstanceDto, query: ContactQuery) { logger.verbose('requested fetchContacts from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].fetchContacts(query); } - public async getBase64FromMediaMessage( - { instanceName }: InstanceDto, - data: getBase64FromMediaMessageDto, - ) { - logger.verbose( - 'requested getBase64FromMediaMessage from ' + instanceName + ' instance', - ); + public async getBase64FromMediaMessage({ instanceName }: InstanceDto, data: getBase64FromMediaMessageDto) { + logger.verbose('requested getBase64FromMediaMessage from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].getBase64FromMediaMessage(data); } @@ -83,22 +82,14 @@ export class ChatController { return await this.waMonitor.waInstances[instanceName].fetchPrivacySettings(); } - public async updatePrivacySettings( - { instanceName }: InstanceDto, - data: PrivacySettingDto, - ) { + public async updatePrivacySettings({ instanceName }: InstanceDto, data: PrivacySettingDto) { logger.verbose('requested updatePrivacySettings from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].updatePrivacySettings(data); } - public async fetchBusinessProfile( - { instanceName }: InstanceDto, - data: ProfilePictureDto, - ) { + public async fetchBusinessProfile({ instanceName }: InstanceDto, data: ProfilePictureDto) { logger.verbose('requested fetchBusinessProfile from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].fetchBusinessProfile( - data.number, - ); + return await this.waMonitor.waInstances[instanceName].fetchBusinessProfile(data.number); } public async updateProfileName({ instanceName }: InstanceDto, data: ProfileNameDto) { @@ -106,30 +97,17 @@ export class ChatController { return await this.waMonitor.waInstances[instanceName].updateProfileName(data.name); } - public async updateProfileStatus( - { instanceName }: InstanceDto, - data: ProfileStatusDto, - ) { + public async updateProfileStatus({ instanceName }: InstanceDto, data: ProfileStatusDto) { logger.verbose('requested updateProfileStatus from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].updateProfileStatus( - data.status, - ); + return await this.waMonitor.waInstances[instanceName].updateProfileStatus(data.status); } - public async updateProfilePicture( - { instanceName }: InstanceDto, - data: ProfilePictureDto, - ) { + public async updateProfilePicture({ instanceName }: InstanceDto, data: ProfilePictureDto) { logger.verbose('requested updateProfilePicture from ' + instanceName + ' instance'); - return await this.waMonitor.waInstances[instanceName].updateProfilePicture( - data.picture, - ); + return await this.waMonitor.waInstances[instanceName].updateProfilePicture(data.picture); } - public async removeProfilePicture( - { instanceName }: InstanceDto, - data: ProfilePictureDto, - ) { + public async removeProfilePicture({ instanceName }: InstanceDto) { logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance'); return await this.waMonitor.waInstances[instanceName].removeProfilePicture(); } diff --git a/src/whatsapp/controllers/chatwoot.controller.ts b/src/whatsapp/controllers/chatwoot.controller.ts index de0aef7a..ab291c43 100644 --- a/src/whatsapp/controllers/chatwoot.controller.ts +++ b/src/whatsapp/controllers/chatwoot.controller.ts @@ -1,24 +1,20 @@ import { isURL } from 'class-validator'; -import { BadRequestException } from '../../exceptions'; -import { InstanceDto } from '../dto/instance.dto'; -import { ChatwootDto } from '../dto/chatwoot.dto'; -import { ChatwootService } from '../services/chatwoot.service'; -import { Logger } from '../../config/logger.config'; -import { waMonitor } from '../whatsapp.module'; + import { ConfigService, HttpServer } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { BadRequestException } from '../../exceptions'; +import { ChatwootDto } from '../dto/chatwoot.dto'; +import { InstanceDto } from '../dto/instance.dto'; +import { ChatwootService } from '../services/chatwoot.service'; +import { waMonitor } from '../whatsapp.module'; const logger = new Logger('ChatwootController'); export class ChatwootController { - constructor( - private readonly chatwootService: ChatwootService, - private readonly configService: ConfigService, - ) {} + constructor(private readonly chatwootService: ChatwootService, private readonly configService: ConfigService) {} public async createChatwoot(instance: InstanceDto, data: ChatwootDto) { - logger.verbose( - 'requested createChatwoot from ' + instance.instanceName + ' instance', - ); + logger.verbose('requested createChatwoot from ' + instance.instanceName + ' instance'); if (data.enabled) { if (!isURL(data.url, { require_tld: false })) { @@ -33,7 +29,7 @@ export class ChatwootController { throw new BadRequestException('token is required'); } - if (!data.sign_msg) { + if (data.sign_msg !== true && data.sign_msg !== false) { throw new BadRequestException('sign_msg is required'); } } @@ -44,6 +40,8 @@ export class ChatwootController { data.token = ''; data.url = ''; data.sign_msg = false; + data.reopen_conversation = false; + data.conversation_pending = false; } data.name_inbox = instance.instanceName; @@ -87,11 +85,15 @@ export class ChatwootController { } public async receiveWebhook(instance: InstanceDto, data: any) { - logger.verbose( - 'requested receiveWebhook from ' + instance.instanceName + ' instance', - ); + logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance'); const chatwootService = new ChatwootService(waMonitor, this.configService); return chatwootService.receiveWebhook(instance, data); } + + public async newInstance(data: any) { + const chatwootService = new ChatwootService(waMonitor, this.configService); + + return chatwootService.newInstance(data); + } } diff --git a/src/whatsapp/controllers/group.controller.ts b/src/whatsapp/controllers/group.controller.ts index f4d381ce..0cf093ca 100644 --- a/src/whatsapp/controllers/group.controller.ts +++ b/src/whatsapp/controllers/group.controller.ts @@ -1,3 +1,4 @@ +import { Logger } from '../../config/logger.config'; import { CreateGroupDto, GetParticipant, @@ -13,7 +14,6 @@ import { } from '../dto/group.dto'; import { InstanceDto } from '../dto/instance.dto'; import { WAMonitoringService } from '../services/monitor.service'; -import { Logger } from '../../config/logger.config'; const logger = new Logger('ChatController'); @@ -26,33 +26,18 @@ export class GroupController { } public async updateGroupPicture(instance: InstanceDto, update: GroupPictureDto) { - logger.verbose( - 'requested updateGroupPicture from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].updateGroupPicture( - update, - ); + logger.verbose('requested updateGroupPicture from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGroupPicture(update); } public async updateGroupSubject(instance: InstanceDto, update: GroupSubjectDto) { - logger.verbose( - 'requested updateGroupSubject from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject( - update, - ); + logger.verbose('requested updateGroupSubject from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGroupSubject(update); } - public async updateGroupDescription( - instance: InstanceDto, - update: GroupDescriptionDto, - ) { - logger.verbose( - 'requested updateGroupDescription from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription( - update, - ); + public async updateGroupDescription(instance: InstanceDto, update: GroupDescriptionDto) { + logger.verbose('requested updateGroupDescription from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGroupDescription(update); } public async findGroupInfo(instance: InstanceDto, groupJid: GroupJid) { @@ -61,12 +46,8 @@ export class GroupController { } public async fetchAllGroups(instance: InstanceDto, getPaticipants: GetParticipant) { - logger.verbose( - 'requested fetchAllGroups from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups( - getPaticipants, - ); + logger.verbose('requested fetchAllGroups from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].fetchAllGroups(getPaticipants); } public async inviteCode(instance: InstanceDto, groupJid: GroupJid) { @@ -85,49 +66,28 @@ export class GroupController { } public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) { - logger.verbose( - 'requested revokeInviteCode from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode( - groupJid, - ); + logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid); } public async findParticipants(instance: InstanceDto, groupJid: GroupJid) { - logger.verbose( - 'requested findParticipants from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].findParticipants( - groupJid, - ); + logger.verbose('requested findParticipants from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].findParticipants(groupJid); } - public async updateGParticipate( - instance: InstanceDto, - update: GroupUpdateParticipantDto, - ) { - logger.verbose( - 'requested updateGParticipate from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].updateGParticipant( - update, - ); + public async updateGParticipate(instance: InstanceDto, update: GroupUpdateParticipantDto) { + logger.verbose('requested updateGParticipate from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].updateGParticipant(update); } public async updateGSetting(instance: InstanceDto, update: GroupUpdateSettingDto) { - logger.verbose( - 'requested updateGSetting from ' + instance.instanceName + ' instance', - ); + logger.verbose('requested updateGSetting from ' + instance.instanceName + ' instance'); return await this.waMonitor.waInstances[instance.instanceName].updateGSetting(update); } public async toggleEphemeral(instance: InstanceDto, update: GroupToggleEphemeralDto) { - logger.verbose( - 'requested toggleEphemeral from ' + instance.instanceName + ' instance', - ); - return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral( - update, - ); + logger.verbose('requested toggleEphemeral from ' + instance.instanceName + ' instance'); + return await this.waMonitor.waInstances[instance.instanceName].toggleEphemeral(update); } public async leaveGroup(instance: InstanceDto, groupJid: GroupJid) { diff --git a/src/whatsapp/controllers/instance.controller.ts b/src/whatsapp/controllers/instance.controller.ts index f0adb3a3..2b62af23 100644 --- a/src/whatsapp/controllers/instance.controller.ts +++ b/src/whatsapp/controllers/instance.controller.ts @@ -1,18 +1,20 @@ import { delay } from '@whiskeysockets/baileys'; +import { isURL } from 'class-validator'; import EventEmitter2 from 'eventemitter2'; -import { Auth, ConfigService, HttpServer } from '../../config/env.config'; + +import { ConfigService, HttpServer } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { RedisCache } from '../../db/redis.client'; import { BadRequestException, InternalServerErrorException } from '../../exceptions'; import { InstanceDto } from '../dto/instance.dto'; import { RepositoryBroker } from '../repository/repository.manager'; import { AuthService, OldToken } from '../services/auth.service'; -import { WAMonitoringService } from '../services/monitor.service'; -import { WAStartupService } from '../services/whatsapp.service'; -import { WebhookService } from '../services/webhook.service'; import { ChatwootService } from '../services/chatwoot.service'; -import { Logger } from '../../config/logger.config'; +import { WAMonitoringService } from '../services/monitor.service'; +import { SettingsService } from '../services/settings.service'; +import { WebhookService } from '../services/webhook.service'; +import { WAStartupService } from '../services/whatsapp.service'; import { wa } from '../types/wa.types'; -import { RedisCache } from '../../db/redis.client'; -import { isURL } from 'class-validator'; export class InstanceController { constructor( @@ -23,6 +25,7 @@ export class InstanceController { private readonly authService: AuthService, private readonly webhookService: WebhookService, private readonly chatwootService: ChatwootService, + private readonly settingsService: SettingsService, private readonly cache: RedisCache, ) {} @@ -34,40 +37,31 @@ export class InstanceController { webhook_by_events, 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, }: InstanceDto) { - this.logger.verbose('requested createInstance from ' + instanceName + ' instance'); - - const mode = this.configService.get('AUTHENTICATION').INSTANCE.MODE; - - if (mode === 'container') { - this.logger.verbose('container mode'); - - if (Object.keys(this.waMonitor.waInstances).length > 0) { - throw new BadRequestException([ - 'Instance already created', - 'Only one instance can be created', - ]); - } + 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 - .toLowerCase() - .replace(/[^a-z0-9]/g, '') - .replace(' ', ''); + 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; @@ -89,6 +83,7 @@ export class InstanceController { if (!isURL(webhook, { require_tld: false })) { throw new BadRequestException('Invalid "url" property in webhook'); } + this.logger.verbose('creating webhook'); try { this.webhookService.create(instance, { @@ -104,162 +99,31 @@ export class InstanceController { } } - if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) { - this.logger.verbose('instance created'); - this.logger.verbose({ - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook, - events: getEvents, - }); - - return { - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook, - events: getEvents, - }; - } - - 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'); - } - - 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, - }); - - this.chatwootService.initInstanceChatwoot( - instance, - instance.instanceName, - `${urlServer}/chatwoot/webhook/${instance.instanceName}`, - qrcode, - ); - } catch (error) { - this.logger.log(error); - } - - return { - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - chatwoot: { - enabled: true, - account_id: chatwoot_account_id, - token: chatwoot_token, - url: chatwoot_url, - sign_msg: chatwoot_sign_msg || false, - name_inbox: instance.instanceName, - webhook_url: `${urlServer}/chatwoot/webhook/${instance.instanceName}`, - }, + 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, }; - } else { - this.logger.verbose('server mode'); - this.logger.verbose('checking duplicate token'); - await this.authService.checkDuplicateToken(token); + this.logger.verbose('settings: ' + JSON.stringify(settings)); - this.logger.verbose('creating instance'); - const instance = new WAStartupService( - this.configService, - this.eventEmitter, - this.repository, - this.cache, - ); - instance.instanceName = instanceName - .toLowerCase() - .replace(/[^a-z0-9]/g, '') - .replace(' ', ''); - - 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 getEvents: string[]; - - if (webhook) { - if (!isURL(webhook, { require_tld: false })) { - throw new BadRequestException('Invalid "url" property in webhook'); - } - - this.logger.verbose('creating webhook'); - try { - this.webhookService.create(instance, { - enabled: true, - url: webhook, - events, - webhook_by_events, - }); - - getEvents = (await this.webhookService.find(instance)).events; - } catch (error) { - this.logger.log(error); - } - } + 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(); - await delay(2000); + await instance.connectToWhatsapp(number); + await delay(5000); getQrcode = instance.qrCode; } - this.logger.verbose('instance created'); - this.logger.verbose({ - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook, - webhook_by_events, - events: getEvents, - qrcode: getQrcode, - }); - - return { + const result = { instance: { instanceName: instance.instanceName, status: 'created', @@ -268,8 +132,14 @@ export class InstanceController { webhook, webhook_by_events, events: getEvents, + settings, qrcode: getQrcode, }; + + this.logger.verbose('instance created'); + this.logger.verbose(result); + + return result; } if (!chatwoot_account_id) { @@ -288,6 +158,18 @@ export class InstanceController { 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 { @@ -298,6 +180,9 @@ export class InstanceController { url: chatwoot_url, sign_msg: chatwoot_sign_msg || false, name_inbox: instance.instanceName, + number, + reopen_conversation: chatwoot_reopen_conversation || false, + conversation_pending: chatwoot_conversation_pending || false, }); this.chatwootService.initInstanceChatwoot( @@ -305,6 +190,7 @@ export class InstanceController { instance.instanceName, `${urlServer}/chatwoot/webhook/${instance.instanceName}`, qrcode, + number, ); } catch (error) { this.logger.log(error); @@ -319,41 +205,58 @@ export class InstanceController { webhook, webhook_by_events, events: getEvents, + 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/${instance.instanceName}`, }, }; + } catch (error) { + this.logger.error(error.message[0]); + throw new BadRequestException(error.message[0]); } } - public async connectToWhatsapp({ instanceName }: InstanceDto) { + public async connectToWhatsapp({ instanceName, number = null }: InstanceDto) { try { - this.logger.verbose( - 'requested connectToWhatsapp from ' + instanceName + ' instance', - ); + this.logger.verbose('requested connectToWhatsapp from ' + instanceName + ' instance'); const instance = this.waMonitor.waInstances[instanceName]; const state = instance?.connectionStatus?.state; this.logger.verbose('state: ' + state); - switch (state) { - case 'close': - this.logger.verbose('connecting'); - await instance.connectToWhatsapp(); - await delay(2000); - return instance.qrCode; - case 'connecting': - return instance.qrCode; - default: - return await this.connectionState({ instanceName }); + 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); } @@ -366,7 +269,7 @@ export class InstanceController { this.logger.verbose('logging out instance: ' + instanceName); this.waMonitor.waInstances[instanceName]?.client?.ws?.close(); - return { error: false, message: 'Instance restarted' }; + return { status: 'SUCCESS', error: false, response: { message: 'Instance restarted' } }; } catch (error) { this.logger.error(error); } @@ -374,7 +277,12 @@ export class InstanceController { public async connectionState({ instanceName }: InstanceDto) { this.logger.verbose('requested connectionState from ' + instanceName + ' instance'); - return this.waMonitor.waInstances[instanceName]?.connectionStatus; + return { + instance: { + instanceName: instanceName, + state: this.waMonitor.waInstances[instanceName]?.connectionStatus?.state, + }, + }; } public async fetchInstances({ instanceName }: InstanceDto) { @@ -389,24 +297,20 @@ export class InstanceController { public async logout({ instanceName }: InstanceDto) { this.logger.verbose('requested logout from ' + instanceName + ' instance'); - const stateConn = await this.connectionState({ instanceName }); + const { instance } = await this.connectionState({ instanceName }); - if (stateConn.state === 'close') { - throw new BadRequestException( - 'The "' + instanceName + '" instance is not connected', - ); + 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, - ); + 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 { error: false, message: 'Instance logged out' }; + return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } }; } catch (error) { throw new InternalServerErrorException(error.toString()); } @@ -414,26 +318,24 @@ export class InstanceController { public async deleteInstance({ instanceName }: InstanceDto) { this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance'); - const stateConn = await this.connectionState({ instanceName }); + const { instance } = await this.connectionState({ instanceName }); - if (stateConn.state === 'open') { - throw new BadRequestException( - 'The "' + instanceName + '" instance needs to be disconnected', - ); + if (instance.state === 'open') { + throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected'); } try { - if (stateConn.state === 'connecting') { + if (instance.state === 'connecting') { this.logger.verbose('logging out instance: ' + instanceName); await this.logout({ instanceName }); delete this.waMonitor.waInstances[instanceName]; - return { error: false, message: 'Instance deleted' }; + 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 { error: false, message: 'Instance deleted' }; + return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } }; } } catch (error) { throw new BadRequestException(error.toString()); diff --git a/src/whatsapp/controllers/sendMessage.controller.ts b/src/whatsapp/controllers/sendMessage.controller.ts index fb942a9c..20e38ae5 100644 --- a/src/whatsapp/controllers/sendMessage.controller.ts +++ b/src/whatsapp/controllers/sendMessage.controller.ts @@ -1,4 +1,6 @@ import { isBase64, isURL } from 'class-validator'; + +import { Logger } from '../../config/logger.config'; import { BadRequestException } from '../../exceptions'; import { InstanceDto } from '../dto/instance.dto'; import { @@ -16,8 +18,6 @@ import { } from '../dto/sendMessage.dto'; import { WAMonitoringService } from '../services/monitor.service'; -import { Logger } from '../../config/logger.config'; - const logger = new Logger('MessageRouter'); export class SendMessageController { @@ -39,12 +39,7 @@ export class SendMessageController { throw new BadRequestException('For base64 the file name must be informed.'); } - logger.verbose( - 'isURL: ' + - isURL(data?.mediaMessage?.media) + - ', isBase64: ' + - isBase64(data?.mediaMessage?.media), - ); + logger.verbose('isURL: ' + isURL(data?.mediaMessage?.media) + ', isBase64: ' + isBase64(data?.mediaMessage?.media)); if (isURL(data?.mediaMessage?.media) || isBase64(data?.mediaMessage?.media)) { return await this.waMonitor.waInstances[instanceName].mediaMessage(data); } @@ -55,10 +50,7 @@ export class SendMessageController { logger.verbose('requested sendSticker from ' + instanceName + ' instance'); logger.verbose( - 'isURL: ' + - isURL(data?.stickerMessage?.image) + - ', isBase64: ' + - isBase64(data?.stickerMessage?.image), + 'isURL: ' + isURL(data?.stickerMessage?.image) + ', isBase64: ' + isBase64(data?.stickerMessage?.image), ); if (isURL(data.stickerMessage.image) || isBase64(data.stickerMessage.image)) { return await this.waMonitor.waInstances[instanceName].mediaSticker(data); @@ -69,12 +61,7 @@ export class SendMessageController { public async sendWhatsAppAudio({ instanceName }: InstanceDto, data: SendAudioDto) { logger.verbose('requested sendWhatsAppAudio from ' + instanceName + ' instance'); - logger.verbose( - 'isURL: ' + - isURL(data?.audioMessage?.audio) + - ', isBase64: ' + - isBase64(data?.audioMessage?.audio), - ); + logger.verbose('isURL: ' + isURL(data?.audioMessage?.audio) + ', isBase64: ' + isBase64(data?.audioMessage?.audio)); if (isURL(data.audioMessage.audio) || isBase64(data.audioMessage.audio)) { return await this.waMonitor.waInstances[instanceName].audioWhatsapp(data); } @@ -83,10 +70,7 @@ export class SendMessageController { public async sendButtons({ instanceName }: InstanceDto, data: SendButtonDto) { logger.verbose('requested sendButtons from ' + instanceName + ' instance'); - if ( - isBase64(data.buttonMessage.mediaMessage?.media) && - !data.buttonMessage.mediaMessage?.fileName - ) { + if (isBase64(data.buttonMessage.mediaMessage?.media) && !data.buttonMessage.mediaMessage?.fileName) { throw new BadRequestException('For bse64 the file name must be informed.'); } return await this.waMonitor.waInstances[instanceName].buttonMessage(data); @@ -109,7 +93,7 @@ export class SendMessageController { public async sendReaction({ instanceName }: InstanceDto, data: SendReactionDto) { logger.verbose('requested sendReaction from ' + instanceName + ' instance'); - if (!data.reactionMessage.reaction.match(/[^\(\)\w\sà-ú"-\+]+/)) { + if (!data.reactionMessage.reaction.match(/[^()\w\sà-ú"-+]+/)) { throw new BadRequestException('"reaction" must be an emoji'); } return await this.waMonitor.waInstances[instanceName].reactionMessage(data); diff --git a/src/whatsapp/controllers/settings.controller.ts b/src/whatsapp/controllers/settings.controller.ts new file mode 100644 index 00000000..1a8baafc --- /dev/null +++ b/src/whatsapp/controllers/settings.controller.ts @@ -0,0 +1,24 @@ +// import { isURL } from 'class-validator'; + +import { Logger } from '../../config/logger.config'; +// import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { SettingsService } from '../services/settings.service'; + +const logger = new Logger('SettingsController'); + +export class SettingsController { + constructor(private readonly settingsService: SettingsService) {} + + public async createSettings(instance: InstanceDto, data: SettingsDto) { + logger.verbose('requested createSettings from ' + instance.instanceName + ' instance'); + + return this.settingsService.create(instance, data); + } + + public async findSettings(instance: InstanceDto) { + logger.verbose('requested findSettings from ' + instance.instanceName + ' instance'); + return this.settingsService.find(instance); + } +} diff --git a/src/whatsapp/controllers/views.controller.ts b/src/whatsapp/controllers/views.controller.ts index 3f54ef39..5f4060ac 100644 --- a/src/whatsapp/controllers/views.controller.ts +++ b/src/whatsapp/controllers/views.controller.ts @@ -1,4 +1,5 @@ import { Request, Response } from 'express'; + import { Auth, ConfigService } from '../../config/env.config'; import { BadRequestException } from '../../exceptions'; import { InstanceDto } from '../dto/instance.dto'; @@ -6,10 +7,7 @@ import { HttpStatus } from '../routers/index.router'; import { WAMonitoringService } from '../services/monitor.service'; export class ViewsController { - constructor( - private readonly waMonit: WAMonitoringService, - private readonly configService: ConfigService, - ) {} + constructor(private readonly waMonit: WAMonitoringService, private readonly configService: ConfigService) {} public async qrcode(request: Request, response: Response) { try { diff --git a/src/whatsapp/controllers/webhook.controller.ts b/src/whatsapp/controllers/webhook.controller.ts index b5747b2e..281147db 100644 --- a/src/whatsapp/controllers/webhook.controller.ts +++ b/src/whatsapp/controllers/webhook.controller.ts @@ -1,9 +1,10 @@ import { isURL } from 'class-validator'; + +import { Logger } from '../../config/logger.config'; import { BadRequestException } from '../../exceptions'; import { InstanceDto } from '../dto/instance.dto'; import { WebhookDto } from '../dto/webhook.dto'; import { WebhookService } from '../services/webhook.service'; -import { Logger } from '../../config/logger.config'; const logger = new Logger('WebhookController'); diff --git a/src/whatsapp/dto/chat.dto.ts b/src/whatsapp/dto/chat.dto.ts index 07757194..f2f9b1cc 100644 --- a/src/whatsapp/dto/chat.dto.ts +++ b/src/whatsapp/dto/chat.dto.ts @@ -1,16 +1,7 @@ -import { - WAPrivacyOnlineValue, - WAPrivacyValue, - WAReadReceiptsValue, - proto, -} from '@whiskeysockets/baileys'; +import { proto, WAPrivacyOnlineValue, WAPrivacyValue, WAReadReceiptsValue } from '@whiskeysockets/baileys'; export class OnWhatsAppDto { - constructor( - public readonly jid: string, - public readonly exists: boolean, - public readonly name?: string, - ) {} + constructor(public readonly jid: string, public readonly exists: boolean, public readonly name?: string) {} } export class getBase64FromMediaMessageDto { @@ -26,6 +17,19 @@ export class NumberDto { number: string; } +export class NumberBusiness { + wid?: string; + jid?: string; + exists?: boolean; + isBusiness: boolean; + name?: string; + message?: string; + description?: string; + email?: string; + website?: string[]; + address?: string; +} + export class ProfileNameDto { name: string; } @@ -46,7 +50,7 @@ class Key { remoteJid: string; } export class ReadMessageDto { - readMessages: Key[]; + read_messages: Key[]; } class LastMessage { diff --git a/src/whatsapp/dto/chatwoot.dto.ts b/src/whatsapp/dto/chatwoot.dto.ts index e78b0676..b270c869 100644 --- a/src/whatsapp/dto/chatwoot.dto.ts +++ b/src/whatsapp/dto/chatwoot.dto.ts @@ -5,4 +5,7 @@ export class ChatwootDto { url?: string; name_inbox?: string; sign_msg?: boolean; + number?: string; + reopen_conversation?: boolean; + conversation_pending?: boolean; } diff --git a/src/whatsapp/dto/group.dto.ts b/src/whatsapp/dto/group.dto.ts index bc36e27f..6dfdc45c 100644 --- a/src/whatsapp/dto/group.dto.ts +++ b/src/whatsapp/dto/group.dto.ts @@ -1,7 +1,8 @@ export class CreateGroupDto { subject: string; - description?: string; participants: string[]; + description?: string; + promoteParticipants?: boolean; } export class GroupPictureDto { diff --git a/src/whatsapp/dto/instance.dto.ts b/src/whatsapp/dto/instance.dto.ts index ce282e03..c317060f 100644 --- a/src/whatsapp/dto/instance.dto.ts +++ b/src/whatsapp/dto/instance.dto.ts @@ -1,12 +1,21 @@ export class InstanceDto { instanceName: string; + qrcode?: boolean; + number?: string; + token?: string; webhook?: string; webhook_by_events?: boolean; events?: string[]; - qrcode?: boolean; - token?: 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; } diff --git a/src/whatsapp/dto/sendMessage.dto.ts b/src/whatsapp/dto/sendMessage.dto.ts index 51a55cec..c2ddb3a2 100644 --- a/src/whatsapp/dto/sendMessage.dto.ts +++ b/src/whatsapp/dto/sendMessage.dto.ts @@ -15,6 +15,8 @@ export class Options { presence?: WAPresence; quoted?: Quoted; mentions?: Mentions; + linkPreview?: boolean; + encoding?: boolean; } class OptionsMessage { options: Options; diff --git a/src/whatsapp/dto/settings.dto.ts b/src/whatsapp/dto/settings.dto.ts new file mode 100644 index 00000000..594ab3a4 --- /dev/null +++ b/src/whatsapp/dto/settings.dto.ts @@ -0,0 +1,8 @@ +export class SettingsDto { + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + always_online?: boolean; + read_messages?: boolean; + read_status?: boolean; +} diff --git a/src/whatsapp/guards/auth.guard.ts b/src/whatsapp/guards/auth.guard.ts index 8607cab4..72148885 100644 --- a/src/whatsapp/guards/auth.guard.ts +++ b/src/whatsapp/guards/auth.guard.ts @@ -1,12 +1,13 @@ import { isJWT } from 'class-validator'; import { NextFunction, Request, Response } from 'express'; import jwt from 'jsonwebtoken'; + +import { name } from '../../../package.json'; import { Auth, configService } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; -import { name } from '../../../package.json'; +import { ForbiddenException, UnauthorizedException } from '../../exceptions'; import { InstanceDto } from '../dto/instance.dto'; import { JwtPayload } from '../services/auth.service'; -import { ForbiddenException, UnauthorizedException } from '../../exceptions'; import { repository } from '../whatsapp.module'; const logger = new Logger('GUARD'); @@ -22,15 +23,8 @@ async function jwtGuard(req: Request, res: Response, next: NextFunction) { return next(); } - if ( - (req.originalUrl.includes('/instance/create') || - req.originalUrl.includes('/instance/fetchInstances')) && - !key - ) { - throw new ForbiddenException( - 'Missing global api key', - 'The global api key must be set', - ); + if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { + throw new ForbiddenException('Missing global api key', 'The global api key must be set'); } const jwtOpts = configService.get('AUTHENTICATION').JWT; @@ -69,15 +63,8 @@ async function apikey(req: Request, res: Response, next: NextFunction) { return next(); } - if ( - (req.originalUrl.includes('/instance/create') || - req.originalUrl.includes('/instance/fetchInstances')) && - !key - ) { - throw new ForbiddenException( - 'Missing global api key', - 'The global api key must be set', - ); + if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) { + throw new ForbiddenException('Missing global api key', 'The global api key must be set'); } try { @@ -86,7 +73,9 @@ async function apikey(req: Request, res: Response, next: NextFunction) { if (instanceKey.apikey === key) { return next(); } - } catch (error) {} + } catch (error) { + logger.error(error); + } throw new UnauthorizedException(); } diff --git a/src/whatsapp/guards/instance.guard.ts b/src/whatsapp/guards/instance.guard.ts index 1e79ff1d..69d20414 100644 --- a/src/whatsapp/guards/instance.guard.ts +++ b/src/whatsapp/guards/instance.guard.ts @@ -1,16 +1,13 @@ 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 { dbserver } from '../../db/db.connect'; -import { - BadRequestException, - ForbiddenException, - NotFoundException, -} from '../../exceptions'; +import { BadRequestException, ForbiddenException, NotFoundException } from '../../exceptions'; import { InstanceDto } from '../dto/instance.dto'; import { cache, waMonitor } from '../whatsapp.module'; -import { Database, Redis, configService } from '../../config/env.config'; async function getInstance(instanceName: string) { const db = configService.get('DATABASE'); @@ -35,10 +32,7 @@ async function getInstance(instanceName: string) { } export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) { - if ( - req.originalUrl.includes('/instance/create') || - req.originalUrl.includes('/instance/fetchInstances') - ) { + if (req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) { return next(); } @@ -58,9 +52,7 @@ export async function instanceLoggedGuard(req: Request, _: Response, next: NextF 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.`, - ); + throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`); } if (waMonitor.waInstances[instance.instanceName]) { diff --git a/src/whatsapp/models/auth.model.ts b/src/whatsapp/models/auth.model.ts index b5da800d..5c5b6a41 100644 --- a/src/whatsapp/models/auth.model.ts +++ b/src/whatsapp/models/auth.model.ts @@ -1,4 +1,5 @@ import { Schema } from 'mongoose'; + import { dbserver } from '../../db/db.connect'; export class AuthRaw { diff --git a/src/whatsapp/models/chat.model.ts b/src/whatsapp/models/chat.model.ts index ebf7f217..20153603 100644 --- a/src/whatsapp/models/chat.model.ts +++ b/src/whatsapp/models/chat.model.ts @@ -1,4 +1,5 @@ import { Schema } from 'mongoose'; + import { dbserver } from '../../db/db.connect'; export class ChatRaw { diff --git a/src/whatsapp/models/chatwoot.model.ts b/src/whatsapp/models/chatwoot.model.ts index ca082309..d72f6e74 100644 --- a/src/whatsapp/models/chatwoot.model.ts +++ b/src/whatsapp/models/chatwoot.model.ts @@ -1,4 +1,5 @@ import { Schema } from 'mongoose'; + import { dbserver } from '../../db/db.connect'; export class ChatwootRaw { @@ -9,6 +10,9 @@ export class ChatwootRaw { url?: string; name_inbox?: string; sign_msg?: boolean; + number?: string; + reopen_conversation?: boolean; + conversation_pending?: boolean; } const chatwootSchema = new Schema({ @@ -19,11 +23,8 @@ const chatwootSchema = new Schema({ url: { type: String, required: true }, name_inbox: { type: String, required: true }, sign_msg: { type: Boolean, required: true }, + number: { type: String, required: true }, }); -export const ChatwootModel = dbserver?.model( - ChatwootRaw.name, - chatwootSchema, - 'chatwoot', -); +export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot'); export type IChatwootModel = typeof ChatwootModel; diff --git a/src/whatsapp/models/contact.model.ts b/src/whatsapp/models/contact.model.ts index c15411fa..d9b51e1e 100644 --- a/src/whatsapp/models/contact.model.ts +++ b/src/whatsapp/models/contact.model.ts @@ -1,4 +1,5 @@ import { Schema } from 'mongoose'; + import { dbserver } from '../../db/db.connect'; export class ContactRaw { diff --git a/src/whatsapp/models/index.ts b/src/whatsapp/models/index.ts index e0b773f0..ee472de6 100644 --- a/src/whatsapp/models/index.ts +++ b/src/whatsapp/models/index.ts @@ -1,6 +1,7 @@ +export * from './auth.model'; export * from './chat.model'; +export * from './chatwoot.model'; export * from './contact.model'; export * from './message.model'; -export * from './auth.model'; +export * from './settings.model'; export * from './webhook.model'; -export * from './chatwoot.model'; diff --git a/src/whatsapp/models/message.model.ts b/src/whatsapp/models/message.model.ts index 4a684e53..764b04d9 100644 --- a/src/whatsapp/models/message.model.ts +++ b/src/whatsapp/models/message.model.ts @@ -1,4 +1,5 @@ import { Schema } from 'mongoose'; + import { dbserver } from '../../db/db.connect'; import { wa } from '../types/wa.types'; @@ -64,9 +65,5 @@ const messageUpdateSchema = new Schema({ owner: { type: String, required: true, min: 1 }, }); -export const MessageUpModel = dbserver?.model( - MessageUpdateRaw.name, - messageUpdateSchema, - 'messageUpdate', -); +export const MessageUpModel = dbserver?.model(MessageUpdateRaw.name, messageUpdateSchema, 'messageUpdate'); export type IMessageUpModel = typeof MessageUpModel; diff --git a/src/whatsapp/models/settings.model.ts b/src/whatsapp/models/settings.model.ts new file mode 100644 index 00000000..c928be42 --- /dev/null +++ b/src/whatsapp/models/settings.model.ts @@ -0,0 +1,26 @@ +import { Schema } from 'mongoose'; + +import { dbserver } from '../../db/db.connect'; + +export class SettingsRaw { + _id?: string; + reject_call?: boolean; + msg_call?: string; + groups_ignore?: boolean; + always_online?: boolean; + read_messages?: boolean; + read_status?: boolean; +} + +const settingsSchema = new Schema({ + _id: { type: String, _id: true }, + reject_call: { type: Boolean, required: true }, + msg_call: { type: String, required: true }, + groups_ignore: { type: Boolean, required: true }, + always_online: { type: Boolean, required: true }, + read_messages: { type: Boolean, required: true }, + read_status: { type: Boolean, required: true }, +}); + +export const SettingsModel = dbserver?.model(SettingsRaw.name, settingsSchema, 'settings'); +export type ISettingsModel = typeof SettingsModel; diff --git a/src/whatsapp/models/webhook.model.ts b/src/whatsapp/models/webhook.model.ts index 62ee38f4..fa91326c 100644 --- a/src/whatsapp/models/webhook.model.ts +++ b/src/whatsapp/models/webhook.model.ts @@ -1,4 +1,5 @@ import { Schema } from 'mongoose'; + import { dbserver } from '../../db/db.connect'; export class WebhookRaw { diff --git a/src/whatsapp/repository/auth.repository.ts b/src/whatsapp/repository/auth.repository.ts index c795737c..4da8980b 100644 --- a/src/whatsapp/repository/auth.repository.ts +++ b/src/whatsapp/repository/auth.repository.ts @@ -1,16 +1,14 @@ -import { join } from 'path'; -import { Auth, ConfigService } from '../../config/env.config'; -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { IAuthModel, AuthRaw } from '../models'; import { readFileSync } from 'fs'; -import { AUTH_DIR } from '../../config/path.config'; +import { join } from 'path'; + +import { Auth, ConfigService } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; +import { AUTH_DIR } from '../../config/path.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { AuthRaw, IAuthModel } from '../models'; export class AuthRepository extends Repository { - constructor( - private readonly authModel: IAuthModel, - readonly configService: ConfigService, - ) { + constructor(private readonly authModel: IAuthModel, readonly configService: ConfigService) { super(configService); this.auth = configService.get('AUTHENTICATION'); } @@ -23,11 +21,7 @@ export class AuthRepository extends Repository { this.logger.verbose('creating auth'); if (this.dbSettings.ENABLED) { this.logger.verbose('saving auth to db'); - const insert = await this.authModel.replaceOne( - { _id: instance }, - { ...data }, - { upsert: true }, - ); + const insert = await this.authModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); this.logger.verbose('auth saved to db: ' + insert.modifiedCount + ' auth'); return { insertCount: insert.modifiedCount }; @@ -40,9 +34,7 @@ export class AuthRepository extends Repository { fileName: instance, data, }); - this.logger.verbose( - 'auth saved to store in path: ' + join(AUTH_DIR, this.auth.TYPE) + '/' + instance, - ); + this.logger.verbose('auth saved to store in path: ' + join(AUTH_DIR, this.auth.TYPE) + '/' + instance); this.logger.verbose('auth created'); return { insertCount: 1 }; diff --git a/src/whatsapp/repository/chat.repository.ts b/src/whatsapp/repository/chat.repository.ts index 0f05760c..68d653a4 100644 --- a/src/whatsapp/repository/chat.repository.ts +++ b/src/whatsapp/repository/chat.repository.ts @@ -1,29 +1,23 @@ -import { join } from 'path'; -import { ConfigService, StoreConf } from '../../config/env.config'; -import { IInsert, Repository } from '../abstract/abstract.repository'; import { opendirSync, readFileSync, rmSync } from 'fs'; -import { ChatRaw, IChatModel } from '../models'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ChatRaw, IChatModel } from '../models'; export class ChatQuery { where: ChatRaw; } export class ChatRepository extends Repository { - constructor( - private readonly chatModel: IChatModel, - private readonly configService: ConfigService, - ) { + constructor(private readonly chatModel: IChatModel, private readonly configService: ConfigService) { super(configService); } private readonly logger = new Logger('ChatRepository'); - public async insert( - data: ChatRaw[], - instanceName: string, - saveDb = false, - ): Promise { + public async insert(data: ChatRaw[], instanceName: string, saveDb = false): Promise { this.logger.verbose('inserting chats'); if (data.length === 0) { this.logger.verbose('no chats to insert'); @@ -53,10 +47,7 @@ export class ChatRepository extends Repository { data: chat, }); this.logger.verbose( - 'chats saved to store in path: ' + - join(this.storePath, 'chats', instanceName) + - '/' + - chat.id, + 'chats saved to store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id, ); }); @@ -89,10 +80,9 @@ export class ChatRepository extends Repository { if (dirent.isFile()) { chats.push( JSON.parse( - readFileSync( - join(this.storePath, 'chats', query.where.owner, dirent.name), - { encoding: 'utf-8' }, - ), + readFileSync(join(this.storePath, 'chats', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), ), ); } diff --git a/src/whatsapp/repository/chatwoot.repository.ts b/src/whatsapp/repository/chatwoot.repository.ts index 3d24022a..47398d68 100644 --- a/src/whatsapp/repository/chatwoot.repository.ts +++ b/src/whatsapp/repository/chatwoot.repository.ts @@ -1,15 +1,13 @@ -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { ConfigService } from '../../config/env.config'; -import { join } from 'path'; import { readFileSync } from 'fs'; -import { IChatwootModel, ChatwootRaw } from '../models'; +import { join } from 'path'; + +import { ConfigService } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ChatwootRaw, IChatwootModel } from '../models'; export class ChatwootRepository extends Repository { - constructor( - private readonly chatwootModel: IChatwootModel, - private readonly configService: ConfigService, - ) { + constructor(private readonly chatwootModel: IChatwootModel, private readonly configService: ConfigService) { super(configService); } @@ -20,15 +18,9 @@ export class ChatwootRepository extends Repository { this.logger.verbose('creating chatwoot'); if (this.dbSettings.ENABLED) { this.logger.verbose('saving chatwoot to db'); - const insert = await this.chatwootModel.replaceOne( - { _id: instance }, - { ...data }, - { upsert: true }, - ); + const insert = await this.chatwootModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); - this.logger.verbose( - 'chatwoot saved to db: ' + insert.modifiedCount + ' chatwoot', - ); + this.logger.verbose('chatwoot saved to db: ' + insert.modifiedCount + ' chatwoot'); return { insertCount: insert.modifiedCount }; } @@ -40,12 +32,7 @@ export class ChatwootRepository extends Repository { data, }); - this.logger.verbose( - 'chatwoot saved to store in path: ' + - join(this.storePath, 'chatwoot') + - '/' + - instance, - ); + this.logger.verbose('chatwoot saved to store in path: ' + join(this.storePath, 'chatwoot') + '/' + instance); this.logger.verbose('chatwoot created'); return { insertCount: 1 }; diff --git a/src/whatsapp/repository/contact.repository.ts b/src/whatsapp/repository/contact.repository.ts index 648b5bf4..03851607 100644 --- a/src/whatsapp/repository/contact.repository.ts +++ b/src/whatsapp/repository/contact.repository.ts @@ -1,29 +1,23 @@ import { opendirSync, readFileSync } from 'fs'; import { join } from 'path'; + import { ConfigService, StoreConf } from '../../config/env.config'; -import { ContactRaw, IContactModel } from '../models'; -import { IInsert, Repository } from '../abstract/abstract.repository'; import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ContactRaw, IContactModel } from '../models'; export class ContactQuery { where: ContactRaw; } export class ContactRepository extends Repository { - constructor( - private readonly contactModel: IContactModel, - private readonly configService: ConfigService, - ) { + constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) { super(configService); } private readonly logger = new Logger('ContactRepository'); - public async insert( - data: ContactRaw[], - instanceName: string, - saveDb = false, - ): Promise { + public async insert(data: ContactRaw[], instanceName: string, saveDb = false): Promise { this.logger.verbose('inserting contacts'); if (data.length === 0) { @@ -54,10 +48,7 @@ export class ContactRepository extends Repository { data: contact, }); this.logger.verbose( - 'contacts saved to store in path: ' + - join(this.storePath, 'contacts', instanceName) + - '/' + - contact.id, + 'contacts saved to store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id, ); }); @@ -74,11 +65,7 @@ export class ContactRepository extends Repository { } } - public async update( - data: ContactRaw[], - instanceName: string, - saveDb = false, - ): Promise { + public async update(data: ContactRaw[], instanceName: string, saveDb = false): Promise { try { this.logger.verbose('updating contacts'); @@ -119,10 +106,7 @@ export class ContactRepository extends Repository { data: contact, }); this.logger.verbose( - 'contacts updated in store in path: ' + - join(this.storePath, 'contacts', instanceName) + - '/' + - contact.id, + 'contacts updated in store in path: ' + join(this.storePath, 'contacts', instanceName) + '/' + contact.id, ); }); @@ -154,15 +138,9 @@ export class ContactRepository extends Repository { this.logger.verbose('finding contacts in store by id'); contacts.push( JSON.parse( - readFileSync( - join( - this.storePath, - 'contacts', - query.where.owner, - query.where.id + '.json', - ), - { encoding: 'utf-8' }, - ), + readFileSync(join(this.storePath, 'contacts', query.where.owner, query.where.id + '.json'), { + encoding: 'utf-8', + }), ), ); } else { @@ -175,10 +153,9 @@ export class ContactRepository extends Repository { if (dirent.isFile()) { contacts.push( JSON.parse( - readFileSync( - join(this.storePath, 'contacts', query.where.owner, dirent.name), - { encoding: 'utf-8' }, - ), + readFileSync(join(this.storePath, 'contacts', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), ), ); } diff --git a/src/whatsapp/repository/message.repository.ts b/src/whatsapp/repository/message.repository.ts index dbfe01fc..ed362815 100644 --- a/src/whatsapp/repository/message.repository.ts +++ b/src/whatsapp/repository/message.repository.ts @@ -1,9 +1,10 @@ -import { ConfigService, StoreConf } from '../../config/env.config'; -import { join } from 'path'; -import { IMessageModel, MessageRaw } from '../models'; -import { IInsert, Repository } from '../abstract/abstract.repository'; import { opendirSync, readFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { IMessageModel, MessageRaw } from '../models'; export class MessageQuery { where: MessageRaw; @@ -11,20 +12,13 @@ export class MessageQuery { } export class MessageRepository extends Repository { - constructor( - private readonly messageModel: IMessageModel, - private readonly configService: ConfigService, - ) { + constructor(private readonly messageModel: IMessageModel, private readonly configService: ConfigService) { super(configService); } private readonly logger = new Logger('MessageRepository'); - public async insert( - data: MessageRaw[], - instanceName: string, - saveDb = false, - ): Promise { + public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise { this.logger.verbose('inserting messages'); if (!Array.isArray(data) || data.length === 0) { @@ -74,10 +68,7 @@ export class MessageRepository extends Repository { data: message, }); this.logger.verbose( - 'messages saved to store in path: ' + - join(this.storePath, 'messages', instanceName) + - '/' + - message.key.id, + 'messages saved to store in path: ' + join(this.storePath, 'messages', instanceName) + '/' + message.key.id, ); }); @@ -119,15 +110,9 @@ export class MessageRepository extends Repository { this.logger.verbose('finding messages in store by id'); messages.push( JSON.parse( - readFileSync( - join( - this.storePath, - 'messages', - query.where.owner, - query.where.key.id + '.json', - ), - { encoding: 'utf-8' }, - ), + readFileSync(join(this.storePath, 'messages', query.where.owner, query.where.key.id + '.json'), { + encoding: 'utf-8', + }), ), ); } else { @@ -140,10 +125,9 @@ export class MessageRepository extends Repository { if (dirent.isFile()) { messages.push( JSON.parse( - readFileSync( - join(this.storePath, 'messages', query.where.owner, dirent.name), - { encoding: 'utf-8' }, - ), + readFileSync(join(this.storePath, 'messages', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), ), ); } diff --git a/src/whatsapp/repository/messageUp.repository.ts b/src/whatsapp/repository/messageUp.repository.ts index 6a9f9cc4..b97bf59b 100644 --- a/src/whatsapp/repository/messageUp.repository.ts +++ b/src/whatsapp/repository/messageUp.repository.ts @@ -1,9 +1,10 @@ -import { ConfigService, StoreConf } from '../../config/env.config'; -import { IMessageUpModel, MessageUpdateRaw } from '../models'; -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { join } from 'path'; import { opendirSync, readFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService, StoreConf } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { IMessageUpModel, MessageUpdateRaw } from '../models'; export class MessageUpQuery { where: MessageUpdateRaw; @@ -11,20 +12,13 @@ export class MessageUpQuery { } export class MessageUpRepository extends Repository { - constructor( - private readonly messageUpModel: IMessageUpModel, - private readonly configService: ConfigService, - ) { + constructor(private readonly messageUpModel: IMessageUpModel, private readonly configService: ConfigService) { super(configService); } private readonly logger = new Logger('MessageUpRepository'); - public async insert( - data: MessageUpdateRaw[], - instanceName: string, - saveDb?: boolean, - ): Promise { + public async insert(data: MessageUpdateRaw[], instanceName: string, saveDb?: boolean): Promise { this.logger.verbose('inserting message up'); if (data.length === 0) { @@ -54,10 +48,7 @@ export class MessageUpRepository extends Repository { data: update, }); this.logger.verbose( - 'message up saved to store in path: ' + - join(this.storePath, 'message-up', instanceName) + - '/' + - update.id, + 'message up saved to store in path: ' + join(this.storePath, 'message-up', instanceName) + '/' + update.id, ); }); @@ -91,42 +82,32 @@ export class MessageUpRepository extends Repository { messageUpdate.push( JSON.parse( - readFileSync( - join( - this.storePath, - 'message-up', - query.where.owner, - query.where.id + '.json', - ), - { encoding: 'utf-8' }, - ), + readFileSync(join(this.storePath, 'message-up', query.where.owner, query.where.id + '.json'), { + encoding: 'utf-8', + }), ), ); } else { this.logger.verbose('finding message up in store by owner'); - const openDir = opendirSync( - join(this.storePath, 'message-up', query.where.owner), - { encoding: 'utf-8' }, - ); + const openDir = opendirSync(join(this.storePath, 'message-up', query.where.owner), { + encoding: 'utf-8', + }); for await (const dirent of openDir) { if (dirent.isFile()) { messageUpdate.push( JSON.parse( - readFileSync( - join(this.storePath, 'message-up', query.where.owner, dirent.name), - { encoding: 'utf-8' }, - ), + readFileSync(join(this.storePath, 'message-up', query.where.owner, dirent.name), { + encoding: 'utf-8', + }), ), ); } } } - this.logger.verbose( - 'message up found in store: ' + messageUpdate.length + ' message up', - ); + this.logger.verbose('message up found in store: ' + messageUpdate.length + ' message up'); return messageUpdate .sort((x, y) => { return y.datetime - x.datetime; diff --git a/src/whatsapp/repository/repository.manager.ts b/src/whatsapp/repository/repository.manager.ts index aa0fbd65..e1292329 100644 --- a/src/whatsapp/repository/repository.manager.ts +++ b/src/whatsapp/repository/repository.manager.ts @@ -1,17 +1,17 @@ -import { MessageRepository } from './message.repository'; -import { ChatRepository } from './chat.repository'; -import { ContactRepository } from './contact.repository'; -import { MessageUpRepository } from './messageUp.repository'; -import { MongoClient } from 'mongodb'; -import { WebhookRepository } from './webhook.repository'; -import { ChatwootRepository } from './chatwoot.repository'; - -import { AuthRepository } from './auth.repository'; -import { Auth, ConfigService, Database } from '../../config/env.config'; -import { execSync } from 'child_process'; -import { join } from 'path'; 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 { 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 { SettingsRepository } from './settings.repository'; +import { WebhookRepository } from './webhook.repository'; export class RepositoryBroker { constructor( public readonly message: MessageRepository, @@ -20,6 +20,7 @@ export class RepositoryBroker { public readonly messageUpdate: MessageUpRepository, public readonly webhook: WebhookRepository, public readonly chatwoot: ChatwootRepository, + public readonly settings: SettingsRepository, public readonly auth: AuthRepository, private configService: ConfigService, dbServer?: MongoClient, @@ -42,20 +43,16 @@ export class RepositoryBroker { this.logger.verbose('creating store path: ' + storePath); try { - const authDir = join( - storePath, - 'auth', - this.configService.get('AUTHENTICATION').TYPE, - ); + 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 tempDir = join(storePath, 'temp'); - // Check if directories exist, create them if not if (!fs.existsSync(authDir)) { this.logger.verbose('creating auth dir: ' + authDir); fs.mkdirSync(authDir, { recursive: true }); @@ -80,6 +77,30 @@ export class RepositoryBroker { 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(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 }); diff --git a/src/whatsapp/repository/settings.repository.ts b/src/whatsapp/repository/settings.repository.ts new file mode 100644 index 00000000..4d09d79f --- /dev/null +++ b/src/whatsapp/repository/settings.repository.ts @@ -0,0 +1,62 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { ConfigService } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { ISettingsModel, SettingsRaw } from '../models'; + +export class SettingsRepository extends Repository { + constructor(private readonly settingsModel: ISettingsModel, private readonly configService: ConfigService) { + super(configService); + } + + private readonly logger = new Logger('SettingsRepository'); + + public async create(data: SettingsRaw, instance: string): Promise { + try { + this.logger.verbose('creating settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('saving settings to db'); + const insert = await this.settingsModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); + + this.logger.verbose('settings saved to db: ' + insert.modifiedCount + ' settings'); + return { insertCount: insert.modifiedCount }; + } + + this.logger.verbose('saving settings to store'); + + this.writeStore({ + path: join(this.storePath, 'settings'), + fileName: instance, + data, + }); + + this.logger.verbose('settings saved to store in path: ' + join(this.storePath, 'settings') + '/' + instance); + + this.logger.verbose('settings created'); + return { insertCount: 1 }; + } catch (error) { + return error; + } + } + + public async find(instance: string): Promise { + try { + this.logger.verbose('finding settings'); + if (this.dbSettings.ENABLED) { + this.logger.verbose('finding settings in db'); + return await this.settingsModel.findOne({ _id: instance }); + } + + this.logger.verbose('finding settings in store'); + return JSON.parse( + readFileSync(join(this.storePath, 'settings', instance + '.json'), { + encoding: 'utf-8', + }), + ) as SettingsRaw; + } catch (error) { + return {}; + } + } +} diff --git a/src/whatsapp/repository/webhook.repository.ts b/src/whatsapp/repository/webhook.repository.ts index d9b34af1..10074516 100644 --- a/src/whatsapp/repository/webhook.repository.ts +++ b/src/whatsapp/repository/webhook.repository.ts @@ -1,15 +1,13 @@ -import { IInsert, Repository } from '../abstract/abstract.repository'; -import { ConfigService } from '../../config/env.config'; -import { join } from 'path'; import { readFileSync } from 'fs'; -import { IWebhookModel, WebhookRaw } from '../models'; +import { join } from 'path'; + +import { ConfigService } from '../../config/env.config'; import { Logger } from '../../config/logger.config'; +import { IInsert, Repository } from '../abstract/abstract.repository'; +import { IWebhookModel, WebhookRaw } from '../models'; export class WebhookRepository extends Repository { - constructor( - private readonly webhookModel: IWebhookModel, - private readonly configService: ConfigService, - ) { + constructor(private readonly webhookModel: IWebhookModel, private readonly configService: ConfigService) { super(configService); } @@ -20,11 +18,7 @@ export class WebhookRepository extends Repository { this.logger.verbose('creating webhook'); if (this.dbSettings.ENABLED) { this.logger.verbose('saving webhook to db'); - const insert = await this.webhookModel.replaceOne( - { _id: instance }, - { ...data }, - { upsert: true }, - ); + const insert = await this.webhookModel.replaceOne({ _id: instance }, { ...data }, { upsert: true }); this.logger.verbose('webhook saved to db: ' + insert.modifiedCount + ' webhook'); return { insertCount: insert.modifiedCount }; @@ -38,12 +32,7 @@ export class WebhookRepository extends Repository { data, }); - this.logger.verbose( - 'webhook saved to store in path: ' + - join(this.storePath, 'webhook') + - '/' + - instance, - ); + this.logger.verbose('webhook saved to store in path: ' + join(this.storePath, 'webhook') + '/' + instance); this.logger.verbose('webhook created'); return { insertCount: 1 }; diff --git a/src/whatsapp/routers/chat.router.ts b/src/whatsapp/routers/chat.router.ts index 50ead521..285c29a0 100644 --- a/src/whatsapp/routers/chat.router.ts +++ b/src/whatsapp/routers/chat.router.ts @@ -1,4 +1,6 @@ import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; import { archiveChatSchema, contactValidateSchema, @@ -8,13 +10,16 @@ import { privacySettingsSchema, profileNameSchema, profilePictureSchema, + profileSchema, profileStatusSchema, readMessageSchema, whatsappNumberSchema, } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; import { ArchiveChatDto, DeleteMessage, + getBase64FromMediaMessageDto, NumberDto, PrivacySettingDto, ProfileNameDto, @@ -22,17 +27,13 @@ import { ProfileStatusDto, ReadMessageDto, WhatsAppNumberDto, - getBase64FromMediaMessageDto, } from '../dto/chat.dto'; +import { InstanceDto } from '../dto/instance.dto'; import { ContactQuery } from '../repository/contact.repository'; import { MessageQuery } from '../repository/message.repository'; -import { chatController } from '../whatsapp.module'; -import { RouterBroker } from '../abstract/abstract.router'; -import { HttpStatus } from './index.router'; import { MessageUpQuery } from '../repository/messageUp.repository'; -import { proto } from '@whiskeysockets/baileys'; -import { InstanceDto } from '../dto/instance.dto'; -import { Logger } from '../../config/logger.config'; +import { chatController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; const logger = new Logger('ChatRouter'); @@ -91,27 +92,23 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) - .delete( - this.routerPath('deleteMessageForEveryone'), - ...guards, - async (req, res) => { - logger.verbose('request received in deleteMessageForEveryone'); - logger.verbose('request body: '); - logger.verbose(req.body); + .delete(this.routerPath('deleteMessageForEveryone'), ...guards, async (req, res) => { + logger.verbose('request received in deleteMessageForEveryone'); + logger.verbose('request body: '); + logger.verbose(req.body); - logger.verbose('request query: '); - logger.verbose(req.query); + logger.verbose('request query: '); + logger.verbose(req.query); - const response = await this.dataValidate({ - request: req, - schema: deleteMessageSchema, - ClassRef: DeleteMessage, - execute: (instance, data) => chatController.deleteMessage(instance, data), - }); + const response = await this.dataValidate({ + request: req, + schema: deleteMessageSchema, + ClassRef: DeleteMessage, + execute: (instance, data) => chatController.deleteMessage(instance, data), + }); - return res.status(HttpStatus.CREATED).json(response); - }, - ) + return res.status(HttpStatus.CREATED).json(response); + }) .post(this.routerPath('fetchProfilePictureUrl'), ...guards, async (req, res) => { logger.verbose('request received in fetchProfilePictureUrl'); logger.verbose('request body: '); @@ -129,6 +126,23 @@ export class ChatRouter extends RouterBroker { return res.status(HttpStatus.OK).json(response); }) + .post(this.routerPath('fetchProfile'), ...guards, async (req, res) => { + logger.verbose('request received in fetchProfile'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + + const response = await this.dataValidate({ + request: req, + schema: profileSchema, + ClassRef: NumberDto, + execute: (instance, data) => chatController.fetchProfile(instance, data), + }); + + return res.status(HttpStatus.OK).json(response); + }) .post(this.routerPath('findContacts'), ...guards, async (req, res) => { logger.verbose('request received in findContacts'); logger.verbose('request body: '); @@ -158,8 +172,7 @@ export class ChatRouter extends RouterBroker { request: req, schema: null, ClassRef: getBase64FromMediaMessageDto, - execute: (instance, data) => - chatController.getBase64FromMediaMessage(instance, data), + execute: (instance, data) => chatController.getBase64FromMediaMessage(instance, data), }); return res.status(HttpStatus.CREATED).json(response); @@ -245,8 +258,7 @@ export class ChatRouter extends RouterBroker { request: req, schema: privacySettingsSchema, ClassRef: PrivacySettingDto, - execute: (instance, data) => - chatController.updatePrivacySettings(instance, data), + execute: (instance, data) => chatController.updatePrivacySettings(instance, data), }); return res.status(HttpStatus.CREATED).json(response); @@ -263,8 +275,7 @@ export class ChatRouter extends RouterBroker { request: req, schema: profilePictureSchema, ClassRef: ProfilePictureDto, - execute: (instance, data) => - chatController.fetchBusinessProfile(instance, data), + execute: (instance, data) => chatController.fetchBusinessProfile(instance, data), }); return res.status(HttpStatus.OK).json(response); @@ -315,8 +326,7 @@ export class ChatRouter extends RouterBroker { request: req, schema: profilePictureSchema, ClassRef: ProfilePictureDto, - execute: (instance, data) => - chatController.updateProfilePicture(instance, data), + execute: (instance, data) => chatController.updateProfilePicture(instance, data), }); return res.status(HttpStatus.OK).json(response); @@ -333,8 +343,7 @@ export class ChatRouter extends RouterBroker { request: req, schema: profilePictureSchema, ClassRef: ProfilePictureDto, - execute: (instance, data) => - chatController.removeProfilePicture(instance, data), + execute: (instance) => chatController.removeProfilePicture(instance), }); return res.status(HttpStatus.OK).json(response); diff --git a/src/whatsapp/routers/chatwoot.router.ts b/src/whatsapp/routers/chatwoot.router.ts index 3d87f137..eb779587 100644 --- a/src/whatsapp/routers/chatwoot.router.ts +++ b/src/whatsapp/routers/chatwoot.router.ts @@ -1,12 +1,13 @@ import { RequestHandler, Router } from 'express'; -import { instanceNameSchema, chatwootSchema } from '../../validate/validate.schema'; -import { RouterBroker } from '../abstract/abstract.router'; -import { InstanceDto } from '../dto/instance.dto'; -import { ChatwootDto } from '../dto/chatwoot.dto'; -import { chatwootController } from '../whatsapp.module'; -import { ChatwootService } from '../services/chatwoot.service'; -import { HttpStatus } from './index.router'; + import { Logger } from '../../config/logger.config'; +import { chatwootSchema, instanceNameSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { ChatwootDto } from '../dto/chatwoot.dto'; +import { InstanceDto } from '../dto/instance.dto'; +// import { ChatwootService } from '../services/chatwoot.service'; +import { chatwootController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; const logger = new Logger('ChatwootRouter'); diff --git a/src/whatsapp/routers/group.router.ts b/src/whatsapp/routers/group.router.ts index 4c1b3023..f59e82a4 100644 --- a/src/whatsapp/routers/group.router.ts +++ b/src/whatsapp/routers/group.router.ts @@ -1,34 +1,35 @@ import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; import { createGroupSchema, + getParticipantsSchema, + groupInviteSchema, groupJidSchema, - updateParticipantsSchema, - updateSettingsSchema, + groupSendInviteSchema, toggleEphemeralSchema, + updateGroupDescriptionSchema, updateGroupPictureSchema, updateGroupSubjectSchema, - updateGroupDescriptionSchema, - groupInviteSchema, - groupSendInviteSchema, - getParticipantsSchema, + updateParticipantsSchema, + updateSettingsSchema, } from '../../validate/validate.schema'; import { RouterBroker } from '../abstract/abstract.router'; import { CreateGroupDto, + GetParticipant, + GroupDescriptionDto, GroupInvite, GroupJid, GroupPictureDto, + GroupSendInvite, GroupSubjectDto, - GroupDescriptionDto, + GroupToggleEphemeralDto, GroupUpdateParticipantDto, GroupUpdateSettingDto, - GroupToggleEphemeralDto, - GroupSendInvite, - GetParticipant, } from '../dto/group.dto'; import { groupController } from '../whatsapp.module'; import { HttpStatus } from './index.router'; -import { Logger } from '../../config/logger.config'; const logger = new Logger('GroupRouter'); @@ -96,8 +97,7 @@ export class GroupRouter extends RouterBroker { request: req, schema: updateGroupDescriptionSchema, ClassRef: GroupDescriptionDto, - execute: (instance, data) => - groupController.updateGroupDescription(instance, data), + execute: (instance, data) => groupController.updateGroupDescription(instance, data), }); res.status(HttpStatus.CREATED).json(response); diff --git a/src/whatsapp/routers/index.router.ts b/src/whatsapp/routers/index.router.ts index 5d8a2c05..db082799 100644 --- a/src/whatsapp/routers/index.router.ts +++ b/src/whatsapp/routers/index.router.ts @@ -1,15 +1,17 @@ import { Router } from 'express'; +import fs from 'fs'; + import { Auth, configService } from '../../config/env.config'; -import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard'; import { authGuard } from '../guards/auth.guard'; +import { instanceExistsGuard, instanceLoggedGuard } from '../guards/instance.guard'; import { ChatRouter } from './chat.router'; +import { ChatwootRouter } from './chatwoot.router'; import { GroupRouter } from './group.router'; import { InstanceRouter } from './instance.router'; import { MessageRouter } from './sendMessage.router'; +import { SettingsRouter } from './settings.router'; import { ViewsRouter } from './view.router'; import { WebhookRouter } from './webhook.router'; -import { ChatwootRouter } from './chatwoot.router'; -import fs from 'fs'; enum HttpStatus { OK = 200, @@ -35,15 +37,12 @@ router version: packageJson.version, }); }) - .use( - '/instance', - new InstanceRouter(configService, ...guards).router, - new ViewsRouter(instanceExistsGuard).router, - ) + .use('/instance', new InstanceRouter(configService, ...guards).router, new ViewsRouter(instanceExistsGuard).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('/chatwoot', new ChatwootRouter(...guards).router) + .use('/settings', new SettingsRouter(...guards).router); -export { router, HttpStatus }; +export { HttpStatus, router }; diff --git a/src/whatsapp/routers/instance.router.ts b/src/whatsapp/routers/instance.router.ts index 850ffebd..20aa434c 100644 --- a/src/whatsapp/routers/instance.router.ts +++ b/src/whatsapp/routers/instance.router.ts @@ -1,13 +1,14 @@ import { RequestHandler, Router } from 'express'; -import { instanceNameSchema, oldTokenSchema } from '../../validate/validate.schema'; -import { InstanceDto } from '../dto/instance.dto'; -import { instanceController } from '../whatsapp.module'; -import { RouterBroker } from '../abstract/abstract.router'; -import { HttpStatus } from './index.router'; -import { OldToken } from '../services/auth.service'; + import { Auth, ConfigService, Database } from '../../config/env.config'; -import { dbserver } from '../../db/db.connect'; import { Logger } from '../../config/logger.config'; +import { dbserver } from '../../db/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'); @@ -162,17 +163,13 @@ export class InstanceRouter extends RouterBroker { await dbserver.dropDatabase(); return res .status(HttpStatus.CREATED) - .json({ error: false, message: 'Database deleted' }); + .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: error.message }); } } - return res - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .json({ error: true, message: 'Database is not enabled' }); + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: 'Database is not enabled' }); }); } diff --git a/src/whatsapp/routers/sendMessage.router.ts b/src/whatsapp/routers/sendMessage.router.ts index f6f9c3eb..d87db44d 100644 --- a/src/whatsapp/routers/sendMessage.router.ts +++ b/src/whatsapp/routers/sendMessage.router.ts @@ -1,4 +1,6 @@ import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; import { audioMessageSchema, buttonMessageSchema, @@ -12,6 +14,7 @@ import { stickerMessageSchema, textMessageSchema, } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; import { SendAudioDto, SendButtonDto, @@ -26,9 +29,7 @@ import { SendTextDto, } from '../dto/sendMessage.dto'; import { sendMessageController } from '../whatsapp.module'; -import { RouterBroker } from '../abstract/abstract.router'; import { HttpStatus } from './index.router'; -import { Logger } from '../../config/logger.config'; const logger = new Logger('MessageRouter'); @@ -79,8 +80,7 @@ export class MessageRouter extends RouterBroker { request: req, schema: audioMessageSchema, ClassRef: SendMediaDto, - execute: (instance, data) => - sendMessageController.sendWhatsAppAudio(instance, data), + execute: (instance, data) => sendMessageController.sendWhatsAppAudio(instance, data), }); return res.status(HttpStatus.CREATED).json(response); diff --git a/src/whatsapp/routers/settings.router.ts b/src/whatsapp/routers/settings.router.ts new file mode 100644 index 00000000..6bd4d549 --- /dev/null +++ b/src/whatsapp/routers/settings.router.ts @@ -0,0 +1,53 @@ +import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; +import { instanceNameSchema, settingsSchema } from '../../validate/validate.schema'; +import { RouterBroker } from '../abstract/abstract.router'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +// import { SettingsService } from '../services/settings.service'; +import { settingsController } from '../whatsapp.module'; +import { HttpStatus } from './index.router'; + +const logger = new Logger('SettingsRouter'); + +export class SettingsRouter extends RouterBroker { + constructor(...guards: RequestHandler[]) { + super(); + this.router + .post(this.routerPath('set'), ...guards, async (req, res) => { + logger.verbose('request received in setSettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: settingsSchema, + ClassRef: SettingsDto, + execute: (instance, data) => settingsController.createSettings(instance, data), + }); + + res.status(HttpStatus.CREATED).json(response); + }) + .get(this.routerPath('find'), ...guards, async (req, res) => { + logger.verbose('request received in findSettings'); + logger.verbose('request body: '); + logger.verbose(req.body); + + logger.verbose('request query: '); + logger.verbose(req.query); + const response = await this.dataValidate({ + request: req, + schema: instanceNameSchema, + ClassRef: InstanceDto, + execute: (instance) => settingsController.findSettings(instance), + }); + + res.status(HttpStatus.OK).json(response); + }); + } + + public readonly router = Router(); +} diff --git a/src/whatsapp/routers/view.router.ts b/src/whatsapp/routers/view.router.ts index b5ddc008..c5e18129 100644 --- a/src/whatsapp/routers/view.router.ts +++ b/src/whatsapp/routers/view.router.ts @@ -1,4 +1,5 @@ import { RequestHandler, Router } from 'express'; + import { RouterBroker } from '../abstract/abstract.router'; import { viewsController } from '../whatsapp.module'; diff --git a/src/whatsapp/routers/webhook.router.ts b/src/whatsapp/routers/webhook.router.ts index c520d9d5..835d6014 100644 --- a/src/whatsapp/routers/webhook.router.ts +++ b/src/whatsapp/routers/webhook.router.ts @@ -1,11 +1,12 @@ import { RequestHandler, Router } from 'express'; + +import { Logger } from '../../config/logger.config'; import { instanceNameSchema, webhookSchema } from '../../validate/validate.schema'; import { RouterBroker } from '../abstract/abstract.router'; import { InstanceDto } from '../dto/instance.dto'; import { WebhookDto } from '../dto/webhook.dto'; import { webhookController } from '../whatsapp.module'; import { HttpStatus } from './index.router'; -import { Logger } from '../../config/logger.config'; const logger = new Logger('WebhookRouter'); diff --git a/src/whatsapp/services/auth.service.ts b/src/whatsapp/services/auth.service.ts index 88a84905..915a07b7 100644 --- a/src/whatsapp/services/auth.service.ts +++ b/src/whatsapp/services/auth.service.ts @@ -1,14 +1,15 @@ -import { Auth, ConfigService, Webhook } from '../../config/env.config'; -import { InstanceDto } from '../dto/instance.dto'; -import { name as apiName } from '../../../package.json'; -import { verify, sign } from 'jsonwebtoken'; -import { Logger } from '../../config/logger.config'; -import { v4 } from 'uuid'; -import { isJWT } from 'class-validator'; -import { BadRequestException } from '../../exceptions'; import axios from 'axios'; -import { WAMonitoringService } from './monitor.service'; +import { isJWT } from 'class-validator'; +import { sign, verify } from 'jsonwebtoken'; +import { v4 } from 'uuid'; + +import { name as apiName } from '../../../package.json'; +import { Auth, ConfigService, Webhook } from '../../config/env.config'; +import { Logger } from '../../config/logger.config'; +import { BadRequestException } from '../../exceptions'; +import { InstanceDto } from '../dto/instance.dto'; import { RepositoryBroker } from '../repository/repository.manager'; +import { WAMonitoringService } from './monitor.service'; export type JwtPayload = { instanceName: string; @@ -63,9 +64,7 @@ export class AuthService { private async apikey(instance: InstanceDto, token?: string) { const apikey = token ? token : v4().toUpperCase(); - this.logger.verbose( - token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey, - ); + this.logger.verbose(token ? 'APIKEY defined: ' + apikey : 'APIKEY created: ' + apikey); const auth = await this.repository.auth.create({ apikey }, instance.instanceName); @@ -101,13 +100,9 @@ export class AuthService { public async generateHash(instance: InstanceDto, token?: string) { const options = this.configService.get('AUTHENTICATION'); - this.logger.verbose( - 'generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName, - ); + this.logger.verbose('generating hash ' + options.TYPE + ' to instance: ' + instance.instanceName); - return (await this[options.TYPE](instance, token)) as - | { jwt: string } - | { apikey: string }; + return (await this[options.TYPE](instance, token)) as { jwt: string } | { apikey: string }; } public async refreshToken({ oldToken }: OldToken) { @@ -150,10 +145,7 @@ export class AuthService { try { this.logger.verbose('checking webhook'); const webhook = await this.repository.webhook.find(decode.instanceName); - if ( - webhook?.enabled && - this.configService.get('WEBHOOK').EVENTS.NEW_JWT_TOKEN - ) { + if (webhook?.enabled && this.configService.get('WEBHOOK').EVENTS.NEW_JWT_TOKEN) { this.logger.verbose('sending webhook'); const httpService = axios.create({ baseURL: webhook.url }); diff --git a/src/whatsapp/services/chatwoot.service.ts b/src/whatsapp/services/chatwoot.service.ts index decb5822..23efc66f 100644 --- a/src/whatsapp/services/chatwoot.service.ts +++ b/src/whatsapp/services/chatwoot.service.ts @@ -1,18 +1,17 @@ -import { InstanceDto } from '../dto/instance.dto'; -import path from 'path'; -import { ChatwootDto } from '../dto/chatwoot.dto'; -import { WAMonitoringService } from './monitor.service'; -import { Logger } from '../../config/logger.config'; import ChatwootClient from '@figuro/chatwoot-sdk'; -import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs'; import axios from 'axios'; import FormData from 'form-data'; -import { SendTextDto } from '../dto/sendMessage.dto'; +import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs'; import mimeTypes from 'mime-types'; -import { SendAudioDto } from '../dto/sendMessage.dto'; -import { SendMediaDto } from '../dto/sendMessage.dto'; -import { ROOT_DIR } from '../../config/path.config'; +import path from 'path'; + import { ConfigService, HttpServer } 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; @@ -22,10 +21,7 @@ export class ChatwootService { private provider: any; - constructor( - private readonly waMonitor: WAMonitoringService, - private readonly configService: ConfigService, - ) { + constructor(private readonly waMonitor: WAMonitoringService, private readonly configService: ConfigService) { this.messageCache = new Set(); } @@ -56,9 +52,7 @@ export class ChatwootService { private async getProvider(instance: InstanceDto) { this.logger.verbose('get provider to instance: ' + instance.instanceName); try { - const provider = await this.waMonitor.waInstances[ - instance.instanceName - ].findChatwoot(); + const provider = await this.waMonitor.waInstances[instance.instanceName].findChatwoot(); if (!provider) { this.logger.warn('provider not found'); @@ -154,6 +148,7 @@ export class ChatwootService { inboxName: string, webhookUrl: string, qrcode: boolean, + number: string, ) { this.logger.verbose('init instance chatwoot: ' + instance.instanceName); @@ -170,9 +165,7 @@ export class ChatwootService { }); this.logger.verbose('check duplicate inbox'); - const checkDuplicate = findInbox.payload - .map((inbox) => inbox.name) - .includes(inboxName); + const checkDuplicate = findInbox.payload.map((inbox) => inbox.name).includes(inboxName); let inboxId: number; @@ -212,13 +205,7 @@ export class ChatwootService { this.logger.verbose('find contact in chatwoot and create if not exists'); const contact = (await this.findContact(instance, '123456')) || - ((await this.createContact( - instance, - '123456', - inboxId, - false, - 'EvolutionAPI', - )) as any); + ((await this.createContact(instance, '123456', inboxId, false, 'EvolutionAPI')) as any); if (!contact) { this.logger.warn('contact not found'); @@ -229,12 +216,18 @@ export class ChatwootService { 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: { - contact_id: contactId.toString(), - inbox_id: inboxId.toString(), - }, + data, }); if (!conversation) { @@ -243,11 +236,18 @@ export class ChatwootService { } 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: '/init', + content: contentMsg, message_type: 'outgoing', }, }); @@ -410,28 +410,25 @@ export class ChatwootService { if (isGroup) { this.logger.verbose('get group name'); - const group = await this.waMonitor.waInstances[ - instance.instanceName - ].client.groupMetadata(chatId); + 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, + 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) { - await this.updateContact(instance, findParticipant.id, { - name: body.pushName, - avatar_url: picture_url.profilePictureUrl || null, - }); + if (!findParticipant.name || findParticipant.name === chatId) { + await this.updateContact(instance, findParticipant.id, { + name: body.pushName, + avatar_url: picture_url.profilePictureUrl || null, + }); + } } else { await this.createContact( instance, @@ -446,21 +443,34 @@ export class ChatwootService { this.logger.verbose('find or create contact in chatwoot'); - const picture_url = await this.waMonitor.waInstances[ - instance.instanceName - ].profilePicture(chatId); + 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) { - contact = findContact; + if (findContact) { + contact = findContact; + } else { + contact = await this.createContact( + instance, + chatId, + filterInbox.id, + isGroup, + nameContact, + picture_url.profilePictureUrl || null, + ); + } } else { if (findContact) { - contact = await this.updateContact(instance, findContact.id, { - name: nameContact, - avatar_url: picture_url.profilePictureUrl || null, - }); + if (!findContact.name || findContact.name === chatId) { + contact = await this.updateContact(instance, findContact.id, { + name: nameContact, + avatar_url: picture_url.profilePictureUrl || null, + }); + } else { + contact = findContact; + } } else { contact = await this.createContact( instance, @@ -478,8 +488,7 @@ export class ChatwootService { return null; } - const contactId = - contact?.payload?.id || contact?.payload?.contact?.id || contact?.id; + 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'); @@ -495,11 +504,26 @@ export class ChatwootService { })) as any; if (contactConversations) { + let conversation: any; + if (this.provider.reopen_conversation) { + conversation = contactConversations.payload.find((conversation) => conversation.inbox_id == filterInbox.id); + + if (this.provider.conversation_pending) { + await client.conversations.toggleStatus({ + accountId: this.provider.account_id, + conversationId: conversation.id, + data: { + status: 'pending', + }, + }); + } + } else { + conversation = contactConversations.payload.find( + (conversation) => conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id, + ); + } this.logger.verbose('return conversation if exists'); - const conversation = contactConversations.payload.find( - (conversation) => - conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id, - ); + if (conversation) { this.logger.verbose('conversation found'); return conversation.id; @@ -507,12 +531,18 @@ export class ChatwootService { } this.logger.verbose('create conversation in chatwoot'); + const data = { + contact_id: contactId.toString(), + inbox_id: filterInbox.id.toString(), + }; + + if (this.provider.conversation_pending) { + data['status'] = 'pending'; + } + const conversation = await client.conversations.create({ accountId: this.provider.account_id, - data: { - contact_id: `${contactId}`, - inbox_id: `${filterInbox.id}`, - }, + data, }); if (!conversation) { @@ -548,9 +578,7 @@ export class ChatwootService { } this.logger.verbose('find inbox by name'); - const findByName = inbox.payload.find( - (inbox) => inbox.name === instance.instanceName, - ); + const findByName = inbox.payload.find((inbox) => inbox.name === instance.instanceName); if (!findByName) { this.logger.warn('inbox not found'); @@ -652,8 +680,7 @@ export class ChatwootService { this.logger.verbose('find conversation by contact id'); const conversation = findConversation.data.payload.find( - (conversation) => - conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', + (conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', ); if (!conversation) { @@ -773,8 +800,7 @@ export class ChatwootService { this.logger.verbose('find conversation by contact id'); const conversation = findConversation.data.payload.find( - (conversation) => - conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', + (conversation) => conversation?.meta?.sender?.id === contact.id && conversation.status === 'open', ); if (!conversation) { @@ -824,12 +850,7 @@ export class ChatwootService { } } - public async sendAttachment( - waInstance: any, - number: string, - media: any, - caption?: string, - ) { + public async sendAttachment(waInstance: any, number: string, media: any, caption?: string) { this.logger.verbose('send attachment to instance: ' + waInstance.instanceName); try { @@ -910,9 +931,7 @@ export class ChatwootService { public async receiveWebhook(instance: InstanceDto, body: any) { try { - this.logger.verbose( - 'receive webhook to chatwoot instance: ' + instance.instanceName, - ); + this.logger.verbose('receive webhook to chatwoot instance: ' + instance.instanceName); const client = await this.clientCw(instance); if (!client) { @@ -921,12 +940,11 @@ export class ChatwootService { } this.logger.verbose('check if is bot'); - if (!body?.conversation || body.private) return { message: '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; + 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]; @@ -936,20 +954,17 @@ export class ChatwootService { const command = messageReceived.replace('/', ''); - if (command === 'init' || command === 'iniciar') { + 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'); - await waInstance.connectToWhatsapp(); + 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', - ); + await this.createBotMessage(instance, `🚨 ${body.inbox.name} instance is connected.`, 'incoming'); } } @@ -960,20 +975,12 @@ export class ChatwootService { if (!state) { this.logger.verbose('state not found'); - await this.createBotMessage( - instance, - `⚠️ ${body.inbox.name} instance not found.`, - 'incoming', - ); + 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', - ); + await this.createBotMessage(instance, `⚠️ ${body.inbox.name} instance status: *${state}*`, 'incoming'); } } @@ -990,7 +997,7 @@ export class ChatwootService { await waInstance?.client?.ws?.close(); } - if (command.includes('#inbox_whatsapp')) { + if (command.includes('new_instance')) { const urlServer = this.configService.get('SERVER').URL; const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; @@ -1001,8 +1008,14 @@ export class ChatwootService { chatwoot_token: this.provider.token, chatwoot_url: this.provider.url, chatwoot_sign_msg: this.provider.sign_msg, + chatwoot_reopen_conversation: this.provider.reopen_conversation, + chatwoot_conversation_pending: this.provider.conversation_pending, }; + if (command.split(':')[2]) { + data['number'] = command.split(':')[2]; + } + const config = { method: 'post', maxBodyLength: Infinity, @@ -1018,19 +1031,10 @@ export class ChatwootService { } } - if ( - body.message_type === 'outgoing' && - body?.conversation?.messages?.length && - chatId !== '123456' - ) { + 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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); this.logger.verbose('cache file path: ' + this.messageCacheFile); this.messageCache = this.loadMessageCache(); @@ -1051,9 +1055,7 @@ export class ChatwootService { if (senderName === null || senderName === undefined) { formatText = messageReceived; } else { - formatText = this.provider.sign_msg - ? `*${senderName}:*\n\n${messageReceived}` - : messageReceived; + formatText = this.provider.sign_msg ? `*${senderName}:*\n\n${messageReceived}` : messageReceived; } for (const message of body.conversation.messages) { @@ -1067,12 +1069,7 @@ export class ChatwootService { formatText = null; } - await this.sendAttachment( - waInstance, - chatId, - attachment.data_url, - formatText, - ); + await this.sendAttachment(waInstance, chatId, attachment.data_url, formatText); } } else { this.logger.verbose('message is text'); @@ -1094,17 +1091,13 @@ export class ChatwootService { } } - if ( - body.message_type === 'template' && - body.content_type === 'input_csat' && - body.event === 'message_created' - ) { - this.logger.verbose('check if is csat'); + 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, + text: body.content.replace(/\\\r\n|\\\n|\n/g, '\n'), }, options: { delay: 1200, @@ -1153,13 +1146,14 @@ export class ChatwootService { videoMessage: msg.videoMessage?.caption, extendedTextMessage: msg.extendedTextMessage?.text, messageContextInfo: msg.messageContextInfo?.stanzaId, - stickerMessage: msg.stickerMessage?.fileSha256.toString('base64'), + stickerMessage: undefined, documentMessage: msg.documentMessage?.caption, - documentWithCaptionMessage: - msg.documentWithCaptionMessage?.message?.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); @@ -1173,6 +1167,21 @@ export class ChatwootService { 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 = {}; @@ -1276,6 +1285,16 @@ export class ChatwootService { return; } + this.logger.verbose('get conversation message'); + const bodyMessage = await this.getConversationMessage(body.message); + + const isMedia = this.isMediaMessage(body.message); + + if (!bodyMessage && !isMedia) { + this.logger.warn('no body message found'); + return; + } + this.logger.verbose('get conversation in chatwoot'); const getConversion = await this.createConversation(instance, body); @@ -1288,18 +1307,8 @@ export class ChatwootService { this.logger.verbose('message type: ' + messageType); - const isMedia = this.isMediaMessage(body.message); - this.logger.verbose('is media: ' + isMedia); - this.logger.verbose('get conversation message'); - const bodyMessage = await this.getConversationMessage(body.message); - - if (!bodyMessage && !isMedia) { - this.logger.warn('no body message found'); - return; - } - this.logger.verbose('check if is media'); if (isMedia) { this.logger.verbose('message is media'); @@ -1340,24 +1349,14 @@ export class ChatwootService { } this.logger.verbose('send data to chatwoot'); - const send = await this.sendData( - getConversion, - fileName, - messageType, - content, - ); + const send = await this.sendData(getConversion, 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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); this.messageCache = this.loadMessageCache(); @@ -1371,24 +1370,14 @@ export class ChatwootService { this.logger.verbose('message is not group'); this.logger.verbose('send data to chatwoot'); - const send = await this.sendData( - getConversion, - fileName, - messageType, - bodyMessage, - ); + const send = await this.sendData(getConversion, 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.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); this.messageCache = this.loadMessageCache(); @@ -1417,24 +1406,14 @@ export class ChatwootService { } this.logger.verbose('send data to chatwoot'); - const send = await this.createMessage( - instance, - getConversion, - content, - messageType, - ); + const send = await this.createMessage(instance, getConversion, content, messageType); if (!send) { this.logger.warn('message not sent'); return; } - this.messageCacheFile = path.join( - ROOT_DIR, - 'store', - 'chatwoot', - `${instance.instanceName}_cache.txt`, - ); + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); this.messageCache = this.loadMessageCache(); @@ -1448,24 +1427,14 @@ export class ChatwootService { this.logger.verbose('message is not group'); this.logger.verbose('send data to chatwoot'); - const send = await this.createMessage( - instance, - getConversion, - bodyMessage, - messageType, - ); + const send = await this.createMessage(instance, getConversion, bodyMessage, messageType); if (!send) { this.logger.warn('message not sent'); return; } - this.messageCacheFile = path.join( - ROOT_DIR, - 'store', - 'chatwoot', - `${instance.instanceName}_cache.txt`, - ); + this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`); this.messageCache = this.loadMessageCache(); @@ -1515,16 +1484,9 @@ export class ChatwootService { 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 fileData = Buffer.from(body?.qrcode.base64.replace('data:image/png;base64,', ''), 'base64'); - const fileName = `${path.join( - waInstance?.storePath, - 'temp', - `${`${instance}.png`}`, - )}`; + const fileName = `${path.join(waInstance?.storePath, 'temp', `${`${instance}.png`}`)}`; this.logger.verbose('temp file name: ' + fileName); @@ -1532,14 +1494,18 @@ export class ChatwootService { writeFileSync(fileName, fileData, 'utf8'); this.logger.verbose('send qrcode to chatwoot'); - await this.createBotQr( - instance, - 'QRCode successfully generated!', - 'incoming', - fileName, - ); + await this.createBotQr(instance, 'QRCode successfully generated!', 'incoming', fileName); - const msgQrCode = `⚡️ QRCode successfully generated!\n\nScan this QR code within the next 40 seconds:`; + 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'); @@ -1549,4 +1515,50 @@ export class ChatwootService { this.logger.error(error); } } + + public async newInstance(data: any) { + try { + const instanceName = data.instanceName; + const qrcode = true; + const number = data.number; + const accountId = data.accountId; + const chatwootToken = data.token; + const chatwootUrl = data.url; + const signMsg = true; + const urlServer = this.configService.get('SERVER').URL; + const apiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; + + const requestData = { + instanceName, + qrcode, + chatwoot_account_id: accountId, + chatwoot_token: chatwootToken, + chatwoot_url: chatwootUrl, + chatwoot_sign_msg: signMsg, + }; + + if (number) { + requestData['number'] = number; + } + + // eslint-disable-next-line + const config = { + method: 'post', + maxBodyLength: Infinity, + url: `${urlServer}/instance/create`, + headers: { + 'Content-Type': 'application/json', + apikey: apiKey, + }, + data: requestData, + }; + + // await axios.request(config); + + return true; + } catch (error) { + this.logger.error(error); + return null; + } + } } diff --git a/src/whatsapp/services/monitor.service.ts b/src/whatsapp/services/monitor.service.ts index 8b347c21..50f671d9 100644 --- a/src/whatsapp/services/monitor.service.ts +++ b/src/whatsapp/services/monitor.service.ts @@ -1,23 +1,26 @@ -import { opendirSync, readdirSync, rmSync } from 'fs'; -import { WAStartupService } from './whatsapp.service'; -import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config'; -import EventEmitter2 from 'eventemitter2'; -import { join } from 'path'; -import { Logger } from '../../config/logger.config'; -import { - Auth, - ConfigService, - Database, - DelInstance, - HttpServer, - Redis, -} from '../../config/env.config'; -import { RepositoryBroker } from '../repository/repository.manager'; -import { NotFoundException } from '../../exceptions'; -import { Db } from 'mongodb'; -import { initInstance } from '../whatsapp.module'; -import { RedisCache } from '../../db/redis.client'; 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 { dbserver } from '../../db/db.connect'; +import { RedisCache } from '../../db/redis.client'; +import { NotFoundException } from '../../exceptions'; +import { + AuthModel, + ChatwootModel, + ContactModel, + MessageModel, + MessageUpModel, + SettingsModel, + WebhookModel, +} from '../models'; +import { RepositoryBroker } from '../repository/repository.manager'; +import { WAStartupService } from './whatsapp.service'; export class WAMonitoringService { constructor( @@ -45,22 +48,20 @@ export class WAMonitoringService { 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`, - ); + 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, - ); + await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance); this.waInstances[instance]?.client?.ws?.close(); this.waInstances[instance]?.client?.end(undefined); delete this.waInstances[instance]; @@ -90,7 +91,7 @@ export class WAMonitoringService { const findChatwoot = await this.waInstances[key].findChatwoot(); - if (findChatwoot.enabled) { + if (findChatwoot && findChatwoot.enabled) { chatwoot = { ...findChatwoot, webhook_url: `${urlServer}/chatwoot/webhook/${key}`, @@ -112,21 +113,16 @@ export class WAMonitoringService { }; if (this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { - instanceData.instance['serverUrl'] = - this.configService.get('SERVER').URL; + instanceData.instance['serverUrl'] = this.configService.get('SERVER').URL; - instanceData.instance['apikey'] = ( - await this.repository.auth.find(key) - ).apikey; + 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, - ); + this.logger.verbose('instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state); const instanceData = { instance: { @@ -136,12 +132,9 @@ export class WAMonitoringService { }; if (this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) { - instanceData.instance['serverUrl'] = - this.configService.get('SERVER').URL; + instanceData.instance['serverUrl'] = this.configService.get('SERVER').URL; - instanceData.instance['apikey'] = ( - await this.repository.auth.find(key) - ).apikey; + instanceData.instance['apikey'] = (await this.repository.auth.find(key)).apikey; instanceData.instance['chatwoot'] = chatwoot; } @@ -164,14 +157,11 @@ export class WAMonitoringService { collections.forEach(async (collection) => { const name = collection.namespace.replace(/^[\w-]+./, ''); await this.dbInstance.collection(name).deleteMany({ - $or: [ - { _id: { $regex: /^app.state.*/ } }, - { _id: { $regex: /^session-.*/ } }, - ], + $or: [{ _id: { $regex: /^app.state.*/ } }, { _id: { $regex: /^session-.*/ } }], }); this.logger.verbose('instance files deleted: ' + name); }); - } else if (this.redis.ENABLED) { + // } else if (this.redis.ENABLED) { } else { const dir = opendirSync(INSTANCE_DIR, { encoding: 'utf-8' }); for await (const dirent of dir) { @@ -218,11 +208,8 @@ export class WAMonitoringService { } public async cleaningStoreFiles(instanceName: string) { - this.logger.verbose('cleaning store files instance: ' + instanceName); - if (!this.db.ENABLED) { - const instance = this.waInstances[instanceName]; - + 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)}`); @@ -233,18 +220,29 @@ export class WAMonitoringService { execSync(`rm -rf ${join(STORE_DIR, 'auth', 'apikey', instanceName + '.json')}`); execSync(`rm -rf ${join(STORE_DIR, 'webhook', instanceName + '.json')}`); execSync(`rm -rf ${join(STORE_DIR, 'chatwoot', instanceName + '*')}`); + execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`); + + return; } + + this.logger.verbose('cleaning store database instance: ' + instanceName); + + await AuthModel.deleteMany({ owner: instanceName }); + await ContactModel.deleteMany({ owner: instanceName }); + await MessageModel.deleteMany({ owner: instanceName }); + await MessageUpModel.deleteMany({ owner: instanceName }); + await AuthModel.deleteMany({ _id: instanceName }); + await WebhookModel.deleteMany({ _id: instanceName }); + await ChatwootModel.deleteMany({ _id: instanceName }); + await SettingsModel.deleteMany({ _id: instanceName }); + + return; } public async loadInstance() { this.logger.verbose('load instances'); const set = async (name: string) => { - const instance = new WAStartupService( - this.configService, - this.eventEmitter, - this.repository, - this.cache, - ); + const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache); instance.instanceName = name; this.logger.verbose('instance loaded: ' + name); @@ -264,7 +262,6 @@ export class WAMonitoringService { keys.forEach(async (k) => await set(k.split(':')[1])); } else { this.logger.verbose('no instance keys found'); - initInstance(); } return; } @@ -275,12 +272,9 @@ export class WAMonitoringService { const collections: any[] = await this.dbInstance.collections(); if (collections.length > 0) { this.logger.verbose('reading collections and setting instances'); - collections.forEach( - async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, '')), - ); + collections.forEach(async (coll) => await set(coll.namespace.replace(/^[\w-]+\./, ''))); } else { this.logger.verbose('no collections found'); - initInstance(); } return; } @@ -301,7 +295,6 @@ export class WAMonitoringService { await set(dirent.name); } else { this.logger.verbose('no instance files found'); - initInstance(); } } } catch (error) { @@ -315,7 +308,9 @@ export class WAMonitoringService { try { this.logger.verbose('instance: ' + instanceName + ' - removing from memory'); this.waInstances[instanceName] = undefined; - } catch {} + } catch (error) { + this.logger.error(error); + } try { this.logger.verbose('request cleaning up instance: ' + instanceName); diff --git a/src/whatsapp/services/settings.service.ts b/src/whatsapp/services/settings.service.ts new file mode 100644 index 00000000..6815ca40 --- /dev/null +++ b/src/whatsapp/services/settings.service.ts @@ -0,0 +1,32 @@ +import { Logger } from '../../config/logger.config'; +import { InstanceDto } from '../dto/instance.dto'; +import { SettingsDto } from '../dto/settings.dto'; +import { WAMonitoringService } from './monitor.service'; + +export class SettingsService { + constructor(private readonly waMonitor: WAMonitoringService) {} + + private readonly logger = new Logger(SettingsService.name); + + public create(instance: InstanceDto, data: SettingsDto) { + this.logger.verbose('create settings: ' + instance.instanceName); + this.waMonitor.waInstances[instance.instanceName].setSettings(data); + + return { settings: { ...instance, settings: data } }; + } + + public async find(instance: InstanceDto): Promise { + try { + this.logger.verbose('find settings: ' + instance.instanceName); + const result = await this.waMonitor.waInstances[instance.instanceName].findSettings(); + + if (Object.keys(result).length === 0) { + throw new Error('Settings not found'); + } + + return result; + } catch (error) { + return { reject_call: false, msg_call: '', groups_ignore: false }; + } + } +} diff --git a/src/whatsapp/services/webhook.service.ts b/src/whatsapp/services/webhook.service.ts index 2370e05b..dd0a88cd 100644 --- a/src/whatsapp/services/webhook.service.ts +++ b/src/whatsapp/services/webhook.service.ts @@ -1,7 +1,7 @@ +import { Logger } from '../../config/logger.config'; import { InstanceDto } from '../dto/instance.dto'; import { WebhookDto } from '../dto/webhook.dto'; import { WAMonitoringService } from './monitor.service'; -import { Logger } from '../../config/logger.config'; export class WebhookService { constructor(private readonly waMonitor: WAMonitoringService) {} @@ -18,9 +18,7 @@ export class WebhookService { public async find(instance: InstanceDto): Promise { try { this.logger.verbose('find webhook: ' + instance.instanceName); - const result = await this.waMonitor.waInstances[ - instance.instanceName - ].findWebhook(); + const result = await this.waMonitor.waInstances[instance.instanceName].findWebhook(); if (Object.keys(result).length === 0) { throw new Error('Webhook not found'); diff --git a/src/whatsapp/services/whatsapp.service.ts b/src/whatsapp/services/whatsapp.service.ts index 65cb40bb..6de7d847 100644 --- a/src/whatsapp/services/whatsapp.service.ts +++ b/src/whatsapp/services/whatsapp.service.ts @@ -1,9 +1,10 @@ +import ffmpegPath from '@ffmpeg-installer/ffmpeg'; +import { Boom } from '@hapi/boom'; import makeWASocket, { AnyMessageContent, BufferedEventData, BufferJSON, CacheStore, - makeCacheableSignalKeyStore, Chat, ConnectionState, Contact, @@ -12,11 +13,13 @@ import makeWASocket, { downloadMediaMessage, fetchLatestBaileysVersion, generateWAMessageFromContent, + getAggregateVotesInPollMessage, getContentType, getDevice, GroupMetadata, isJidGroup, isJidUser, + makeCacheableSignalKeyStore, MessageUpsertType, MiscMessageGenerationOptions, ParticipantAction, @@ -29,8 +32,23 @@ import makeWASocket, { WAMessage, WAMessageUpdate, WASocket, - getAggregateVotesInPollMessage, } 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 qrcode, { QRCodeToDataURLOptions } from 'qrcode'; +import qrcodeTerminal from 'qrcode-terminal'; +import sharp from 'sharp'; +import { v4 } from 'uuid'; + import { Auth, CleanStoreConf, @@ -38,31 +56,41 @@ import { ConfigSessionPhone, Database, HttpServer, + Log, QrCode, Redis, Webhook, } from '../../config/env.config'; -import fs from 'fs'; import { Logger } from '../../config/logger.config'; import { INSTANCE_DIR, ROOT_DIR } from '../../config/path.config'; -import { existsSync, readFileSync } from 'fs'; -import { join } from 'path'; -import axios from 'axios'; -import { v4 } from 'uuid'; -import qrcode, { QRCodeToDataURLOptions } from 'qrcode'; -import qrcodeTerminal from 'qrcode-terminal'; -import { Events, TypeMediaMessage, wa, MessageSubtype } from '../types/wa.types'; -import { Boom } from '@hapi/boom'; -import EventEmitter2 from 'eventemitter2'; -import { release } from 'os'; -import P from 'pino'; -import { execSync, exec } from 'child_process'; -import ffmpegPath from '@ffmpeg-installer/ffmpeg'; -import { RepositoryBroker } from '../repository/repository.manager'; -import { MessageRaw, MessageUpdateRaw } from '../models/message.model'; -import { ContactRaw } from '../models/contact.model'; -import { ChatRaw } from '../models/chat.model'; -import { getMIMEType } from 'node-mime-types'; +import { dbserver } from '../../db/db.connect'; +import { RedisCache } from '../../db/redis.client'; +import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../exceptions'; +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, + 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, @@ -73,57 +101,26 @@ import { SendListDto, SendLocationDto, SendMediaDto, - SendReactionDto, - SendTextDto, SendPollDto, - SendStickerDto, + SendReactionDto, SendStatusDto, + SendStickerDto, + SendTextDto, StatusMessage, } from '../dto/sendMessage.dto'; -import { arrayUnique, isBase64, isURL } from 'class-validator'; -import { - ArchiveChatDto, - DeleteMessage, - OnWhatsAppDto, - PrivacySettingDto, - ReadMessageDto, - WhatsAppNumberDto, - getBase64FromMediaMessageDto, -} from '../dto/chat.dto'; -import { MessageQuery } from '../repository/message.repository'; -import { ContactQuery } from '../repository/contact.repository'; -import { - BadRequestException, - InternalServerErrorException, - NotFoundException, -} from '../../exceptions'; -import { - CreateGroupDto, - GroupInvite, - GroupJid, - GroupPictureDto, - GroupUpdateParticipantDto, - GroupUpdateSettingDto, - GroupToggleEphemeralDto, - GroupSubjectDto, - GroupDescriptionDto, - GroupSendInvite, - GetParticipant, -} from '../dto/group.dto'; -import { MessageUpQuery } from '../repository/messageUp.repository'; -import { useMultiFileAuthStateDb } from '../../utils/use-multi-file-auth-state-db'; -import Long from 'long'; -import { WebhookRaw } from '../models/webhook.model'; +import { SettingsRaw } from '../models'; +import { ChatRaw } from '../models/chat.model'; import { ChatwootRaw } from '../models/chatwoot.model'; -import { dbserver } from '../../db/db.connect'; -import NodeCache from 'node-cache'; -import { useMultiFileAuthStateRedisDb } from '../../utils/use-multi-file-auth-state-redis-db'; -import sharp from 'sharp'; -import { RedisCache } from '../../db/redis.client'; -import { Log } from '../../config/env.config'; -import ProxyAgent from 'proxy-agent'; -import { ChatwootService } from './chatwoot.service'; +import { ContactRaw } from '../models/contact.model'; +import { MessageRaw, MessageUpdateRaw } from '../models/message.model'; +import { WebhookRaw } from '../models/webhook.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 { ChatwootService } from './chatwoot.service'; export class WAStartupService { constructor( @@ -142,6 +139,7 @@ export class WAStartupService { public client: WASocket; private readonly localWebhook: wa.LocalWebHook = {}; private readonly localChatwoot: wa.LocalChatwoot = {}; + private readonly localSettings: wa.LocalSettings = {}; private stateConnection: wa.StateConnection = { state: 'close' }; public readonly storePath = join(ROOT_DIR, 'store'); private readonly msgRetryCounterCache: CacheStore = new NodeCache(); @@ -149,6 +147,8 @@ export class WAStartupService { private endSession = false; private logBaileys = this.configService.get('LOG').BAILEYS; + private phoneNumber: string; + private chatwootService = new ChatwootService(waMonitor, this.configService); public set instanceName(name: string) { @@ -197,10 +197,7 @@ export class WAStartupService { this.logger.verbose('Database enabled, trying to get from database'); const collection = dbserver .getClient() - .db( - this.configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + - '-instances', - ) + .db(this.configService.get('DATABASE').CONNECTION.DB_PREFIX_NAME + '-instances') .collection(this.instanceName); const data = await collection.findOne({ _id: 'creds' }); if (data) { @@ -238,7 +235,9 @@ export class WAStartupService { 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, }; @@ -306,6 +305,15 @@ export class WAStartupService { 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'); } @@ -317,6 +325,8 @@ export class WAStartupService { 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'); @@ -328,7 +338,7 @@ export class WAStartupService { if (!data) { this.logger.verbose('Chatwoot not found'); - throw new NotFoundException('Chatwoot not found'); + return null; } this.logger.verbose(`Chatwoot account id: ${data.account_id}`); @@ -336,29 +346,86 @@ export class WAStartupService { 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; + } + public async sendDataWebhook(event: Events, data: T, local = true) { const webhookGlobal = this.configService.get('WEBHOOK'); const webhookLocal = this.localWebhook.events; const serverUrl = this.configService.get('SERVER').URL; - const we = event.replace(/[\.-]/gm, '_').toUpperCase(); + const we = event.replace(/[.-]/gm, '_').toUpperCase(); const transformedWe = we.replace(/_/gm, '-').toLowerCase(); - const instance = this.configService.get('AUTHENTICATION').INSTANCE; - const expose = - this.configService.get('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES; + 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'; + const instanceApikey = tokenStore?.apikey || 'Apikey not found'; const globalApiKey = this.configService.get('AUTHENTICATION').API_KEY.KEY; - if (local && instance.MODE !== 'container') { + if (local) { if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) { this.logger.verbose('Sending data to webhook local'); - let baseURL; + let baseURL: string; if (this.localWebhook.webhook_by_events) { baseURL = `${this.localWebhook.url}/${transformedWe}`; @@ -432,13 +499,7 @@ export class WAStartupService { globalURL = globalWebhook.URL; } - let localUrl; - - if (instance.MODE === 'container') { - localUrl = instance.WEBHOOK_URL; - } else { - localUrl = this.localWebhook.url; - } + const localUrl = this.localWebhook.url; if (this.configService.get('LOG').LEVEL.includes('WEBHOOKS')) { const logData = { @@ -493,11 +554,7 @@ export class WAStartupService { } } - private async connectionUpdate({ - qr, - connection, - lastDisconnect, - }: Partial) { + private async connectionUpdate({ qr, connection, lastDisconnect }: Partial) { this.logger.verbose('Connection update'); if (qr) { this.logger.verbose('QR code found'); @@ -562,6 +619,13 @@ export class WAStartupService { color: { light: '#ffffff', dark: '#198754' }, }; + 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) { @@ -573,7 +637,12 @@ export class WAStartupService { this.instance.qrcode.code = qr; this.sendDataWebhook(Events.QRCODE_UPDATED, { - qrcode: { instance: this.instance.name, code: qr, base64 }, + qrcode: { + instance: this.instance.name, + pairingCode: this.instance.qrcode.pairingCode, + code: qr, + base64, + }, }); if (this.localChatwoot.enabled) { @@ -581,7 +650,12 @@ export class WAStartupService { Events.QRCODE_UPDATED, { instanceName: this.instance.name }, { - qrcode: { instance: this.instance.name, code: qr, base64 }, + qrcode: { + instance: this.instance.name, + pairingCode: this.instance.qrcode.pairingCode, + code: qr, + base64, + }, }, ); } @@ -590,7 +664,7 @@ export class WAStartupService { this.logger.verbose('Generating QR code in terminal'); qrcodeTerminal.generate(qr, { small: true }, (qrcode) => this.logger.log( - `\n{ instance: ${this.instance.name}, qrcodeCount: ${this.instance.qrcode.count} }\n` + + `\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` + qrcode, ), ); @@ -612,8 +686,7 @@ export class WAStartupService { if (connection === 'close') { this.logger.verbose('Connection closed'); - const shouldReconnect = - (lastDisconnect.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut; + const shouldReconnect = (lastDisconnect.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut; if (shouldReconnect) { this.logger.verbose('Reconnecting to whatsapp'); await this.connectToWhatsapp(); @@ -622,7 +695,7 @@ export class WAStartupService { this.logger.verbose('Sending data to webhook in event STATUS_INSTANCE'); this.sendDataWebhook(Events.STATUS_INSTANCE, { instance: this.instance.name, - status: 'removed', + status: 'closed', }); if (this.localChatwoot.enabled) { @@ -631,7 +704,7 @@ export class WAStartupService { { instanceName: this.instance.name }, { instance: this.instance.name, - status: 'removed', + status: 'closed', }, ); } @@ -647,9 +720,7 @@ export class WAStartupService { 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.instance.profilePictureUrl = (await this.profilePicture(this.instance.wuid)).profilePictureUrl; this.logger.info( ` ┌──────────────────────────────┐ @@ -682,8 +753,7 @@ export class WAStartupService { } if (webMessageInfo[0].message?.pollCreationMessage) { this.logger.verbose('Returning poll message'); - const messageSecretBase64 = - webMessageInfo[0].message?.messageContextInfo?.messageSecret; + const messageSecretBase64 = webMessageInfo[0].message?.messageContextInfo?.messageSecret; if (typeof messageSecretBase64 === 'string') { const messageSecret = Buffer.from(messageSecretBase64, 'base64'); @@ -717,22 +787,16 @@ export class WAStartupService { for (const [key, value] of Object.entries(cleanStore)) { if (value === true) { execSync( - `rm -rf ${join( - this.storePath, - key.toLowerCase().replace('_', '-'), - this.instance.name, - )}/*.json`, + `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`, + `Cleaned ${join(this.storePath, key.toLowerCase().replace('_', '-'), this.instance.name)}/*.json`, ); } } - } catch (error) {} + } catch (error) { + this.logger.error(error); + } }, (cleanStore?.CLEANING_INTERVAL ?? 3600) * 1000); } } @@ -757,11 +821,12 @@ export class WAStartupService { return await useMultiFileAuthState(join(INSTANCE_DIR, this.instance.name)); } - public async connectToWhatsapp(): Promise { + public async connectToWhatsapp(number?: string): Promise { this.logger.verbose('Connecting to whatsapp'); try { this.loadWebhook(); this.loadChatwoot(); + this.loadSettings(); this.instance.authState = await this.defineAuthState(); @@ -774,32 +839,25 @@ export class WAStartupService { const socketConfig: UserFacingSocketConfig = { auth: { creds: this.instance.authState.state.creds, - keys: makeCacheableSignalKeyStore( - this.instance.authState.state.keys, - P({ level: 'error' }), - ), + keys: makeCacheableSignalKeyStore(this.instance.authState.state.keys, P({ level: 'error' })), }, 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, + 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 - ); + const requiresPatch = !!(message.buttonsMessage || message.listMessage || message.templateMessage); if (requiresPatch) { message = { viewOnceMessageV2: { @@ -830,6 +888,8 @@ export class WAStartupService { this.logger.verbose('Socket event handler initialized'); + this.phoneNumber = number; + return this.client; } catch (error) { this.logger.error(error); @@ -860,11 +920,7 @@ export class WAStartupService { await this.sendDataWebhook(Events.CHATS_UPSERT, chatsRaw); this.logger.verbose('Inserting chats in database'); - await this.repository.chat.insert( - chatsRaw, - this.instance.name, - database.SAVE_DATA.CHATS, - ); + await this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS); }, 'chats.update': async ( @@ -929,11 +985,7 @@ export class WAStartupService { await this.sendDataWebhook(Events.CONTACTS_UPSERT, contactsRaw); this.logger.verbose('Inserting contacts in database'); - await this.repository.contact.insert( - contactsRaw, - this.instance.name, - database.SAVE_DATA.CONTACTS, - ); + await this.repository.contact.insert(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS); }, 'contacts.update': async (contacts: Partial[], database: Database) => { @@ -954,11 +1006,7 @@ export class WAStartupService { await this.sendDataWebhook(Events.CONTACTS_UPDATE, contactsRaw); this.logger.verbose('Updating contacts in database'); - await this.repository.contact.update( - contactsRaw, - this.instance.name, - database.SAVE_DATA.CONTACTS, - ); + await this.repository.contact.update(contactsRaw, this.instance.name, database.SAVE_DATA.CONTACTS); }, }; @@ -991,11 +1039,7 @@ export class WAStartupService { await this.sendDataWebhook(Events.CHATS_SET, chatsRaw); this.logger.verbose('Inserting chats in database'); - await this.repository.chat.insert( - chatsRaw, - this.instance.name, - database.SAVE_DATA.CHATS, - ); + await this.repository.chat.insert(chatsRaw, this.instance.name, database.SAVE_DATA.CHATS); } const messagesRaw: MessageRaw[] = []; @@ -1006,11 +1050,7 @@ export class WAStartupService { if (!m.message) { continue; } - if ( - messagesRepository.find( - (mr) => mr.owner === this.instance.name && mr.key.id === m.key.id, - ) - ) { + if (messagesRepository.find((mr) => mr.owner === this.instance.name && mr.key.id === m.key.id)) { continue; } @@ -1044,15 +1084,12 @@ export class WAStartupService { 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 - ) { + if (type !== 'notify' || received.message?.protocolMessage || received.message?.pollUpdateMessage) { this.logger.verbose('message rejected'); return; } @@ -1061,6 +1098,11 @@ export class WAStartupService { received.messageTimestamp = received.messageTimestamp?.toNumber(); } + if (settings?.groups_ignore && received.key.remoteJid.includes('@g.us')) { + this.logger.verbose('group ignored'); + return; + } + const messageRaw: MessageRaw = { key: received.key, pushName: received.pushName, @@ -1071,6 +1113,14 @@ export class WAStartupService { 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'); @@ -1085,11 +1135,7 @@ export class WAStartupService { } this.logger.verbose('Inserting message in database'); - await this.repository.message.insert( - [messageRaw], - this.instance.name, - database.SAVE_DATA.NEW_MESSAGE, - ); + 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({ @@ -1099,8 +1145,7 @@ export class WAStartupService { const contactRaw: ContactRaw = { id: received.key.remoteJid, pushName: received.pushName, - profilePictureUrl: (await this.profilePicture(received.key.remoteJid)) - .profilePictureUrl, + profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl, owner: this.instance.name, }; @@ -1114,8 +1159,7 @@ export class WAStartupService { const contactRaw: ContactRaw = { id: received.key.remoteJid, pushName: contact[0].pushName, - profilePictureUrl: (await this.profilePicture(received.key.remoteJid)) - .profilePictureUrl, + profilePictureUrl: (await this.profilePicture(received.key.remoteJid)).profilePictureUrl, owner: this.instance.name, }; @@ -1131,11 +1175,7 @@ export class WAStartupService { } this.logger.verbose('Updating contact in database'); - await this.repository.contact.update( - [contactRaw], - this.instance.name, - database.SAVE_DATA.CONTACTS, - ); + await this.repository.contact.update([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS); return; } @@ -1145,14 +1185,10 @@ export class WAStartupService { await this.sendDataWebhook(Events.CONTACTS_UPSERT, contactRaw); this.logger.verbose('Inserting contact in database'); - await this.repository.contact.insert( - [contactRaw], - this.instance.name, - database.SAVE_DATA.CONTACTS, - ); + await this.repository.contact.insert([contactRaw], this.instance.name, database.SAVE_DATA.CONTACTS); }, - 'messages.update': async (args: WAMessageUpdate[], database: Database) => { + 'messages.update': async (args: WAMessageUpdate[], database: Database, settings: SettingsRaw) => { this.logger.verbose('Event received: messages.update'); const status: Record = { 0: 'ERROR', @@ -1163,6 +1199,10 @@ export class WAStartupService { 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'); @@ -1190,6 +1230,22 @@ export class WAStartupService { this.logger.verbose('Sending data to webhook in event MESSAGE_DELETE'); await 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; } @@ -1207,11 +1263,7 @@ export class WAStartupService { await this.sendDataWebhook(Events.MESSAGES_UPDATE, message); this.logger.verbose('Inserting message in database'); - await this.repository.messageUpdate.insert( - [message], - this.instance.name, - database.SAVE_DATA.MESSAGE_UPDATE, - ); + await this.repository.messageUpdate.insert([message], this.instance.name, database.SAVE_DATA.MESSAGE_UPDATE); } } }, @@ -1246,9 +1298,36 @@ export class WAStartupService { private eventHandler() { this.logger.verbose('Initializing event handler'); - this.client.ev.process((events) => { + 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'); @@ -1269,37 +1348,44 @@ export class WAStartupService { if (events['messages.upsert']) { this.logger.verbose('Listening event: messages.upsert'); const payload = events['messages.upsert']; - this.messageHandle['messages.upsert'](payload, database); + 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); + 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 (events['groups.upsert']) { - this.logger.verbose('Listening event: groups.upsert'); - const payload = events['groups.upsert']; - this.groupHandler['groups.upsert'](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['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['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']) { @@ -1382,35 +1468,25 @@ export class WAStartupService { return number; } - const countryCode = number.substring(0, 2); + number = number + ?.replace(/\s/g, '') + .replace(/\+/g, '') + .replace(/\(/g, '') + .replace(/\)/g, '') + .split(':')[0] + .split('@')[0]; - if (Number(countryCode) === 55) { - const formattedBRNumber = this.formatBRNumber(number); - if (formattedBRNumber !== number) { - this.logger.verbose( - 'Jid created is whatsapp in format BR: ' + - `${formattedBRNumber}@s.whatsapp.net`, - ); - return `${formattedBRNumber}@s.whatsapp.net`; - } - } - - if (Number(countryCode) === 52 || Number(countryCode) === 54) { - console.log('numero mexicano'); - - const formattedMXARNumber = this.formatMXOrARNumber(number); - - if (formattedMXARNumber !== number) { - this.logger.verbose( - 'Jid created is whatsapp in format MXAR: ' + - `${formattedMXARNumber}@s.whatsapp.net`, - ); - return `${formattedMXARNumber}@s.whatsapp.net`; - } - } - - if (number.includes('-')) { + 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`; } @@ -1437,22 +1513,88 @@ export class WAStartupService { } } - private async sendMessageWithTyping( - number: string, - message: T, - options?: Options, - ) { + 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'); + const business = await this.fetchBusinessProfile(jid); + + if (number) { + const info = (await this.whatsappNumber({ numbers: [jid] }))?.shift(); + const picture = await this.profilePicture(jid); + const status = await this.getStatus(jid); + + return { + wuid: 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); + + 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) { this.logger.verbose('Sending message with typing'); - const jid = this.createJid(number); - const numberWA = await this.whatsappNumber({ numbers: [jid] }); + const numberWA = await this.whatsappNumber({ numbers: [number] }); const isWA = numberWA[0]; if (!isWA.exists && !isJidGroup(isWA.jid) && !isWA.jid.includes('@broadcast')) { throw new BadRequestException(isWA); } - const sender = isJidGroup(jid) ? jid : isWA.jid; + const sender = isWA.jid; try { if (options?.delay) { @@ -1461,10 +1603,8 @@ export class WAStartupService { await this.client.presenceSubscribe(sender); this.logger.verbose('Subscribing to presence'); - await this.client.sendPresenceUpdate(options?.presence ?? 'composing', jid); - this.logger.verbose( - 'Sending presence update: ' + options?.presence ?? 'composing', - ); + 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); @@ -1473,14 +1613,14 @@ export class WAStartupService { 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); + const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); if (!msg) { throw 'Message not found'; @@ -1502,25 +1642,19 @@ export class WAStartupService { if (options?.mentions) { this.logger.verbose('Mentions defined'); - if ( - !Array.isArray(options.mentions.mentioned) && - !options.mentions.everyOne - ) { - throw new BadRequestException('Mentions must be an array'); - } - - if (options.mentions.everyOne) { + if (options.mentions?.everyOne) { this.logger.verbose('Mentions everyone'); this.logger.verbose('Getting group metadata'); mentions = groupMetadata.participants.map((participant) => participant.id); this.logger.verbose('Getting group metadata for mentions'); - } else { + } 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)) { - throw new BadRequestException('Mentions must be a number'); + return null; + // throw new BadRequestException('Mentions must be a number'); } return jid; }); @@ -1566,6 +1700,7 @@ export class WAStartupService { { text: message['conversation'], mentions, + linkPreview: linkPreview, } as unknown as AnyMessageContent, option as unknown as MiscMessageGenerationOptions, ); @@ -1686,9 +1821,7 @@ export class WAStartupService { } this.logger.verbose('Getting contacts with push name'); - status.statusJidList = contacts - .filter((contact) => contact.pushName) - .map((contact) => contact.id); + status.statusJidList = contacts.filter((contact) => contact.pushName).map((contact) => contact.id); this.logger.verbose(status.statusJidList); } @@ -1805,9 +1938,7 @@ export class WAStartupService { 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', - ); + 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]; @@ -1920,11 +2051,7 @@ export class WAStartupService { this.logger.verbose('Sending media message'); const generate = await this.prepareMediaMessage(data.mediaMessage); - return await this.sendMessageWithTyping( - data.number, - { ...generate.message }, - data?.options, - ); + return await this.sendMessageWithTyping(data.number, { ...generate.message }, data?.options); } private async processAudio(audio: string, number: string) { @@ -1969,43 +2096,59 @@ export class WAStartupService { 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, _stdout, _stderr) => { - fs.unlinkSync(tempAudioPath); - this.logger.verbose('Temp audio deleted'); + 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); + if (error) reject(error); - this.logger.verbose('Audio converted to mp4'); - resolve(outputAudio); - }, - ); + this.logger.verbose('Audio converted to mp4'); + resolve(outputAudio); + }); }); } public async audioWhatsapp(data: SendAudioDto) { this.logger.verbose('Sending audio whatsapp'); - 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 }, - ); - fs.unlinkSync(convert); - this.logger.verbose('Converted audio deleted'); - - return result; - } else { - throw new InternalServerErrorException(convert); + 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 }, + ); + + 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 }, + ); } public async buttonMessage(data: SendButtonDto) { @@ -2027,10 +2170,7 @@ export class WAStartupService { }; if (!arrayUnique(btnItems.text) || !arrayUnique(btnItems.ids)) { - throw new BadRequestException( - 'Button texts cannot be repeated', - 'Button IDs cannot be repeated.', - ); + throw new BadRequestException('Button texts cannot be repeated', 'Button IDs cannot be repeated.'); } return await this.sendMessageWithTyping( @@ -2097,11 +2237,7 @@ export class WAStartupService { 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`; + 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'); @@ -2118,10 +2254,12 @@ export class WAStartupService { result += `URL:${contact.url}\n`; } - result += - `item1.TEL;waid=${contact.wuid}:${contact.phoneNumber}\n` + - 'item1.X-ABLabel:Celular\n' + - 'END:VCARD'; + 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; @@ -2163,8 +2301,8 @@ export class WAStartupService { const onWhatsapp: OnWhatsAppDto[] = []; for await (const number of data.numbers) { - const jid = this.createJid(number); - // const jid = `${number}@s.whatsapp.net`; + let jid = this.createJid(number); + if (isJidGroup(jid)) { const group = await this.findGroup({ groupJid: jid }, 'inner'); @@ -2172,6 +2310,7 @@ export class WAStartupService { 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]; @@ -2191,7 +2330,7 @@ export class WAStartupService { this.logger.verbose('Marking message as read'); try { const keys: proto.IMessageKey[] = []; - data.readMessages.forEach((read) => { + data.read_messages.forEach((read) => { if (isJidGroup(read.remoteJid) || isJidUser(read.remoteJid)) { keys.push({ remoteJid: read.remoteJid, @@ -2210,8 +2349,7 @@ export class WAStartupService { public async archiveChat(data: ArchiveChatDto) { this.logger.verbose('Archiving chat'); try { - data.lastMessage.messageTimestamp = - data.lastMessage?.messageTimestamp ?? Date.now(); + data.lastMessage.messageTimestamp = data.lastMessage?.messageTimestamp ?? Date.now(); await this.client.chatModify( { archive: data.archive, @@ -2227,10 +2365,7 @@ export class WAStartupService { } catch (error) { throw new InternalServerErrorException({ archived: false, - message: [ - 'An error occurred while archiving the chat. Open a calling.', - error.toString(), - ], + message: ['An error occurred while archiving the chat. Open a calling.', error.toString()], }); } } @@ -2240,10 +2375,7 @@ export class WAStartupService { try { return await this.client.sendMessage(del.remoteJid, { delete: del }); } catch (error) { - throw new InternalServerErrorException( - 'Error while deleting message for everyone', - error?.toString(), - ); + throw new InternalServerErrorException('Error while deleting message for everyone', error?.toString()); } } @@ -2253,9 +2385,7 @@ export class WAStartupService { const m = data?.message; const convertToMp4 = data?.convertToMp4 ?? false; - const msg = m?.message - ? m - : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); + const msg = m?.message ? m : ((await this.getMessage(m.key, true)) as proto.IWebMessageInfo); if (!msg) { throw 'Message not found'; @@ -2351,6 +2481,9 @@ export class WAStartupService { 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: { @@ -2364,6 +2497,9 @@ export class WAStartupService { 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 = { @@ -2379,6 +2515,9 @@ export class WAStartupService { 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 = { @@ -2422,45 +2561,49 @@ export class WAStartupService { await this.client.updateGroupsAddPrivacy(settings.privacySettings.groupadd); this.logger.verbose('Groups add privacy updated'); - // reinicia a instancia + this.client?.ws?.close(); - return { update: 'success', data: await this.client.fetchPrivacySettings() }; + 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(), - ); + throw new InternalServerErrorException('Error updating privacy settings', error.toString()); } } - public async fetchBusinessProfile(number: string) { + public async fetchBusinessProfile(number: string): Promise { this.logger.verbose('Fetching business profile'); try { - let jid; - - if (!number) { - jid = this.instance.wuid; - } else { - jid = this.createJid(number); - } + 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 { - exists: false, - message: 'Business profile not found', + isBusiness: false, + message: 'Not is business profile', + ...info?.shift(), }; } this.logger.verbose('Business profile fetched'); - return profile; + return { + isBusiness: true, + ...profile, + }; } catch (error) { - throw new InternalServerErrorException( - 'Error updating profile name', - error.toString(), - ); + throw new InternalServerErrorException('Error updating profile name', error.toString()); } } @@ -2471,10 +2614,7 @@ export class WAStartupService { return { update: 'success' }; } catch (error) { - throw new InternalServerErrorException( - 'Error updating profile name', - error.toString(), - ); + throw new InternalServerErrorException('Error updating profile name', error.toString()); } } @@ -2485,10 +2625,7 @@ export class WAStartupService { return { update: 'success' }; } catch (error) { - throw new InternalServerErrorException( - 'Error updating profile status', - error.toString(), - ); + throw new InternalServerErrorException('Error updating profile status', error.toString()); } } @@ -2517,10 +2654,7 @@ export class WAStartupService { return { update: 'success' }; } catch (error) { - throw new InternalServerErrorException( - 'Error updating profile picture', - error.toString(), - ); + throw new InternalServerErrorException('Error updating profile picture', error.toString()); } } @@ -2531,10 +2665,7 @@ export class WAStartupService { return { update: 'success' }; } catch (error) { - throw new InternalServerErrorException( - 'Error removing profile picture', - error.toString(), - ); + throw new InternalServerErrorException('Error removing profile picture', error.toString()); } } @@ -2551,10 +2682,19 @@ export class WAStartupService { await this.client.groupUpdateDescription(id, create.description); } + if (create?.promoteParticipants) { + this.logger.verbose('Prometing group participants: ' + create.description); + await this.updateGParticipant({ + groupJid: id, + action: 'promote', + participants: participants, + }); + } + const group = await this.client.groupMetadata(id); this.logger.verbose('Getting group metadata'); - return { groupMetadata: group }; + return group; } catch (error) { this.logger.error(error); throw new InternalServerErrorException('Error creating group', error.toString()); @@ -2586,10 +2726,7 @@ export class WAStartupService { return { update: 'success' }; } catch (error) { - throw new InternalServerErrorException( - 'Error update group picture', - error.toString(), - ); + throw new InternalServerErrorException('Error update group picture', error.toString()); } } @@ -2600,10 +2737,7 @@ export class WAStartupService { return { update: 'success' }; } catch (error) { - throw new InternalServerErrorException( - 'Error updating group subject', - error.toString(), - ); + throw new InternalServerErrorException('Error updating group subject', error.toString()); } } @@ -2614,10 +2748,7 @@ export class WAStartupService { return { update: 'success' }; } catch (error) { - throw new InternalServerErrorException( - 'Error updating group description', - error.toString(), - ); + throw new InternalServerErrorException('Error updating group description', error.toString()); } } @@ -2753,10 +2884,7 @@ export class WAStartupService { 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, - ); + const updateSetting = await this.client.groupSettingUpdate(update.groupJid, update.action); return { updateSetting: updateSetting }; } catch (error) { throw new BadRequestException('Error updating setting', error.toString()); @@ -2766,10 +2894,7 @@ export class WAStartupService { public async toggleEphemeral(update: GroupToggleEphemeralDto) { this.logger.verbose('Toggling ephemeral for group: ' + update.groupJid); try { - const toggleEphemeral = await this.client.groupToggleEphemeral( - update.groupJid, - update.expiration, - ); + await this.client.groupToggleEphemeral(update.groupJid, update.expiration); return { success: true }; } catch (error) { throw new BadRequestException('Error updating setting', error.toString()); diff --git a/src/whatsapp/types/wa.types.ts b/src/whatsapp/types/wa.types.ts index 169df515..d4769a49 100644 --- a/src/whatsapp/types/wa.types.ts +++ b/src/whatsapp/types/wa.types.ts @@ -22,12 +22,19 @@ export enum Events { GROUPS_UPSERT = 'groups.upsert', GROUPS_UPDATE = 'groups.update', GROUP_PARTICIPANTS_UPDATE = 'group-participants.update', + CALL = 'call', } export declare namespace wa { - export type QrCode = { count?: number; base64?: string; code?: string }; + 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; @@ -49,6 +56,18 @@ export declare namespace wa { 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 StateConnection = { @@ -57,22 +76,10 @@ export declare namespace wa { statusReason?: number; }; - export type StatusMessage = - | 'ERROR' - | 'PENDING' - | 'SERVER_ACK' - | 'DELIVERY_ACK' - | 'READ' - | 'PLAYED'; + export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED'; } -export const TypeMediaMessage = [ - 'imageMessage', - 'documentMessage', - 'audioMessage', - 'videoMessage', - 'stickerMessage', -]; +export const TypeMediaMessage = ['imageMessage', 'documentMessage', 'audioMessage', 'videoMessage', 'stickerMessage']; export const MessageSubtype = [ 'ephemeralMessage', diff --git a/src/whatsapp/whatsapp.module.ts b/src/whatsapp/whatsapp.module.ts index 46f8ecd1..b742afa9 100644 --- a/src/whatsapp/whatsapp.module.ts +++ b/src/whatsapp/whatsapp.module.ts @@ -1,39 +1,40 @@ -import { Auth, configService } from '../config/env.config'; -import { Logger } from '../config/logger.config'; +import { configService } from '../config/env.config'; import { eventEmitter } from '../config/event.config'; -import { MessageRepository } from './repository/message.repository'; -import { WAMonitoringService } from './services/monitor.service'; -import { ChatRepository } from './repository/chat.repository'; -import { ContactRepository } from './repository/contact.repository'; -import { MessageUpRepository } from './repository/messageUp.repository'; +import { Logger } from '../config/logger.config'; +import { dbserver } from '../db/db.connect'; +import { RedisCache } from '../db/redis.client'; 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 { SendMessageController } from './controllers/sendMessage.controller'; -import { AuthService } from './services/auth.service'; -import { GroupController } from './controllers/group.controller'; +import { SettingsController } from './controllers/settings.controller'; import { ViewsController } from './controllers/views.controller'; -import { WebhookService } from './services/webhook.service'; import { WebhookController } from './controllers/webhook.controller'; -import { ChatwootService } from './services/chatwoot.service'; -import { ChatwootController } from './controllers/chatwoot.controller'; -import { RepositoryBroker } from './repository/repository.manager'; import { AuthModel, ChatModel, + ChatwootModel, ContactModel, MessageModel, MessageUpModel, - ChatwootModel, + SettingsModel, WebhookModel, } from './models'; -import { dbserver } from '../db/db.connect'; -import { WebhookRepository } from './repository/webhook.repository'; -import { ChatwootRepository } from './repository/chatwoot.repository'; import { AuthRepository } from './repository/auth.repository'; -import { WAStartupService } from './services/whatsapp.service'; -import { delay } from '@whiskeysockets/baileys'; -import { Events } from './types/wa.types'; -import { RedisCache } from '../db/redis.client'; +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 { RepositoryBroker } from './repository/repository.manager'; +import { SettingsRepository } from './repository/settings.repository'; +import { WebhookRepository } from './repository/webhook.repository'; +import { AuthService } from './services/auth.service'; +import { ChatwootService } from './services/chatwoot.service'; +import { WAMonitoringService } from './services/monitor.service'; +import { SettingsService } from './services/settings.service'; +import { WebhookService } from './services/webhook.service'; const logger = new Logger('WA MODULE'); @@ -43,6 +44,7 @@ const contactRepository = new ContactRepository(ContactModel, configService); const messageUpdateRepository = new MessageUpRepository(MessageUpModel, configService); const webhookRepository = new WebhookRepository(WebhookModel, configService); const chatwootRepository = new ChatwootRepository(ChatwootModel, configService); +const settingsRepository = new SettingsRepository(SettingsModel, configService); const authRepository = new AuthRepository(AuthModel, configService); export const repository = new RepositoryBroker( @@ -52,6 +54,7 @@ export const repository = new RepositoryBroker( messageUpdateRepository, webhookRepository, chatwootRepository, + settingsRepository, authRepository, configService, dbserver?.getClient(), @@ -59,12 +62,7 @@ export const repository = new RepositoryBroker( export const cache = new RedisCache(); -export const waMonitor = new WAMonitoringService( - eventEmitter, - configService, - repository, - cache, -); +export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache); const authService = new AuthService(configService, waMonitor, repository); @@ -76,6 +74,10 @@ 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, @@ -84,6 +86,7 @@ export const instanceController = new InstanceController( authService, webhookService, chatwootService, + settingsService, cache, ); export const viewsController = new ViewsController(waMonitor, configService); @@ -91,111 +94,4 @@ export const sendMessageController = new SendMessageController(waMonitor); export const chatController = new ChatController(waMonitor); export const groupController = new GroupController(waMonitor); -export async function initInstance() { - const instance = new WAStartupService(configService, eventEmitter, repository, cache); - - const mode = configService.get('AUTHENTICATION').INSTANCE.MODE; - - logger.verbose('Sending data webhook for event: ' + Events.APPLICATION_STARTUP); - instance.sendDataWebhook( - Events.APPLICATION_STARTUP, - { - message: 'Application startup', - mode, - }, - false, - ); - - if (mode === 'container') { - logger.verbose('Application startup in container mode'); - - const instanceName = configService.get('AUTHENTICATION').INSTANCE.NAME; - logger.verbose('Instance name: ' + instanceName); - - const instanceWebhook = - configService.get('AUTHENTICATION').INSTANCE.WEBHOOK_URL; - logger.verbose('Instance webhook: ' + instanceWebhook); - - const chatwootAccountId = - configService.get('AUTHENTICATION').INSTANCE.CHATWOOT_ACCOUNT_ID; - logger.verbose('Chatwoot account id: ' + chatwootAccountId); - - const chatwootToken = - configService.get('AUTHENTICATION').INSTANCE.CHATWOOT_TOKEN; - logger.verbose('Chatwoot token: ' + chatwootToken); - - const chatwootUrl = configService.get('AUTHENTICATION').INSTANCE.CHATWOOT_URL; - logger.verbose('Chatwoot url: ' + chatwootUrl); - - instance.instanceName = instanceName; - - waMonitor.waInstances[instance.instanceName] = instance; - waMonitor.delInstanceTime(instance.instanceName); - - const hash = await authService.generateHash({ - instanceName: instance.instanceName, - token: configService.get('AUTHENTICATION').API_KEY.KEY, - }); - logger.verbose('Hash generated: ' + hash); - - if (instanceWebhook) { - logger.verbose('Creating webhook for instance: ' + instanceName); - try { - webhookService.create(instance, { enabled: true, url: instanceWebhook }); - logger.verbose('Webhook created'); - } catch (error) { - logger.log(error); - } - } - - if (chatwootUrl && chatwootToken && chatwootAccountId) { - logger.verbose('Creating chatwoot for instance: ' + instanceName); - try { - chatwootService.create(instance, { - enabled: true, - url: chatwootUrl, - token: chatwootToken, - account_id: chatwootAccountId, - sign_msg: false, - }); - logger.verbose('Chatwoot created'); - } catch (error) { - logger.log(error); - } - } - - try { - const state = instance.connectionStatus?.state; - - switch (state) { - case 'close': - await instance.connectToWhatsapp(); - await delay(2000); - return instance.qrCode; - case 'connecting': - return instance.qrCode; - default: - return await this.connectionState({ instanceName }); - } - } catch (error) { - logger.log(error); - } - - const result = { - instance: { - instanceName: instance.instanceName, - status: 'created', - }, - hash, - webhook: instanceWebhook, - }; - - logger.info(result); - - return result; - } - - return null; -} - logger.info('Module - ON');