mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-23 04:22:02 -06:00
Merge branch 'release/1.7.0'
This commit is contained in:
commit
901954de33
64
.github/workflows/publish_docker_image.yml
vendored
Normal file
64
.github/workflows/publish_docker_image.yml
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
name: Build Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
build-amd:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Extract existing image metadata
|
||||
id: image-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: atendai/evolution-api
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push AMD image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
labels: ${{ steps.image-meta.outputs.labels }}
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
|
||||
build-arm:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204-arm
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Extract existing image metadata
|
||||
id: image-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: atendai/evolution-api
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push ARM image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
labels: ${{ steps.image-meta.outputs.labels }}
|
||||
platforms: linux/arm64
|
||||
push: true
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,6 +4,8 @@
|
||||
|
||||
/Docker/.env
|
||||
|
||||
.vscode
|
||||
|
||||
# Logs
|
||||
logs/**.json
|
||||
*.log
|
||||
@ -44,4 +46,5 @@ docker-compose.yaml
|
||||
/temp/*
|
||||
|
||||
.DS_Store
|
||||
*.DS_Store
|
||||
*.DS_Store
|
||||
.tool-versions
|
||||
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -9,5 +9,8 @@
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"prisma-smart-formatter.typescript.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma"
|
||||
"prisma-smart-formatter.prisma.defaultFormatter": "Prisma.prisma",
|
||||
"i18n-ally.localesPaths": [
|
||||
"store/messages"
|
||||
]
|
||||
}
|
43
CHANGELOG.md
43
CHANGELOG.md
@ -1,3 +1,46 @@
|
||||
# 1.7.0 (2024-03-11 18:23)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added update message endpoint
|
||||
* Add translate capabilities to QRMessages in CW
|
||||
* Join in Group by Invite Code
|
||||
* Read messages from whatsapp in chatwoot
|
||||
* Add support to use use redis in cacheservice
|
||||
* Add support for labels
|
||||
* Command to clearcache from chatwoot inbox
|
||||
* Whatsapp Cloud API Oficial
|
||||
|
||||
### Fixed
|
||||
|
||||
* Proxy configuration improvements
|
||||
* Correction in sending lists
|
||||
* Adjust in webhook_base64
|
||||
* Correction in typebot text formatting
|
||||
* Correction in chatwoot text formatting and render list message
|
||||
* Only use a axios request to get file mimetype if necessary
|
||||
* When possible use the original file extension
|
||||
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
|
||||
* Remove message ids cache in chatwoot to use chatwoot's api itself
|
||||
* Adjusts the quoted message, now has contextInfo in the message Raw
|
||||
* Collecting responses with text or numbers in Typebot
|
||||
* Added sendList endpoint to swagger documentation
|
||||
* Implemented a function to synchronize message deletions on WhatsApp, automatically reflecting in Chatwoot.
|
||||
* Improvement on numbers validation
|
||||
* Fix polls in message sending
|
||||
* Sending status message
|
||||
* Message 'connection successfully' spamming
|
||||
* Invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
|
||||
* Fix looping when deleting a message in chatwoot
|
||||
* When receiving a file from whatsapp, use the original filename in chatwoot if possible
|
||||
* Correction in the sendList Function
|
||||
* Implement contact upsert in messaging-history.set
|
||||
* Improve proxy error handling
|
||||
* Refactor fetching participants for group in WhatsApp service
|
||||
* Fixed problem where the typebot final keyword did not work
|
||||
* Typebot's wait now pauses the flow and composing is defined by the delay_message parameter in set typebot
|
||||
* Composing over 20s now loops until finished
|
||||
|
||||
# 1.6.1 (2023-12-22 11:43)
|
||||
|
||||
### Fixed
|
||||
|
@ -16,6 +16,7 @@ LOG_BAILEYS=error
|
||||
# Default time: 5 minutes
|
||||
# If you don't even want an expiration, enter the value false
|
||||
DEL_INSTANCE=false
|
||||
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
|
||||
|
||||
# Temporary data storage
|
||||
STORE_MESSAGES=true
|
||||
@ -47,10 +48,17 @@ REDIS_URI=redis://redis:6379
|
||||
REDIS_PREFIX_KEY=evdocker
|
||||
|
||||
RABBITMQ_ENABLED=false
|
||||
RABBITMQ_RABBITMQ_MODE=global
|
||||
RABBITMQ_EXCHANGE_NAME=evolution_exchange
|
||||
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
|
||||
|
||||
WEBSOCKET_ENABLED=false
|
||||
|
||||
WA_BUSINESS_TOKEN_WEBHOOK=evolution
|
||||
WA_BUSINESS_URL=https://graph.facebook.com
|
||||
WA_BUSINESS_VERSION=v18.0
|
||||
WA_BUSINESS_LANGUAGE=pt_BR
|
||||
|
||||
SQS_ENABLED=false
|
||||
SQS_ACCESS_KEY_ID=
|
||||
SQS_SECRET_ACCESS_KEY=
|
||||
@ -84,6 +92,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
WEBHOOK_EVENTS_LABELS_ASSOCIATION=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
|
||||
@ -109,6 +119,13 @@ QRCODE_COLOR=#198754
|
||||
TYPEBOT_API_VERSION=latest
|
||||
TYPEBOT_KEEP_OPEN=false
|
||||
|
||||
#Chatwoot
|
||||
# If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot.
|
||||
CHATWOOT_MESSAGE_DELETE=false # false | true
|
||||
# This db connection is used to import messages from whatsapp to chatwoot database
|
||||
CHATWOOT_IMPORT_DATABASE_CONNECTION_URI=postgres://user:password@hostname:port/dbname
|
||||
CHATWOOT_IMPORT_DATABASE_PLACEHOLDER_MEDIA_MESSAGE=true
|
||||
|
||||
# Defines an authentication type for the api
|
||||
# We recommend using the apikey because it will allow you to use a custom token,
|
||||
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
|
||||
|
@ -4,7 +4,7 @@ services:
|
||||
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: davidsongomes/evolution-api
|
||||
image: atendai/evolution-api
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
@ -19,4 +19,4 @@ services:
|
||||
|
||||
volumes:
|
||||
evolution_instances:
|
||||
evolution_store:
|
||||
evolution_store:
|
||||
|
@ -16,6 +16,7 @@ LOG_BAILEYS=error
|
||||
# Default time: 5 minutes
|
||||
# If you don't even want an expiration, enter the value false
|
||||
DEL_INSTANCE=false
|
||||
DEL_TEMP_INSTANCES=true # Delete instances with status closed on start
|
||||
|
||||
# Temporary data storage
|
||||
STORE_MESSAGES=true
|
||||
@ -73,6 +74,8 @@ WEBHOOK_EVENTS_GROUPS_UPSERT=true
|
||||
WEBHOOK_EVENTS_GROUPS_UPDATE=true
|
||||
WEBHOOK_EVENTS_GROUP_PARTICIPANTS_UPDATE=true
|
||||
WEBHOOK_EVENTS_CONNECTION_UPDATE=true
|
||||
WEBHOOK_EVENTS_LABELS_EDIT=true
|
||||
WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
||||
|
@ -62,7 +62,7 @@ services:
|
||||
|
||||
api:
|
||||
container_name: evolution_api
|
||||
image: davidsongomes/evolution-api
|
||||
image: atendai/evolution-api
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongodb
|
||||
@ -88,4 +88,4 @@ volumes:
|
||||
networks:
|
||||
evolution-net:
|
||||
external: true
|
||||
|
||||
|
||||
|
12
Dockerfile
12
Dockerfile
@ -1,6 +1,6 @@
|
||||
FROM node:20.7.0-alpine AS builder
|
||||
|
||||
LABEL version="1.6.1" description="Api to control whatsapp features through http requests."
|
||||
LABEL version="1.7.0" description="Api to control whatsapp features through http requests."
|
||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||
LABEL contact="contato@agenciadgcode.com"
|
||||
|
||||
@ -35,6 +35,7 @@ ENV LOG_COLOR=true
|
||||
ENV LOG_BAILEYS=error
|
||||
|
||||
ENV DEL_INSTANCE=false
|
||||
ENV DEL_TEMP_INSTANCES=true
|
||||
|
||||
ENV STORE_MESSAGES=true
|
||||
ENV STORE_MESSAGE_UP=true
|
||||
@ -62,10 +63,17 @@ ENV REDIS_URI=redis://redis:6379
|
||||
ENV REDIS_PREFIX_KEY=evolution
|
||||
|
||||
ENV RABBITMQ_ENABLED=false
|
||||
ENV RABBITMQ_MODE=global
|
||||
ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange
|
||||
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
|
||||
|
||||
ENV WEBSOCKET_ENABLED=false
|
||||
|
||||
ENV WA_BUSINESS_TOKEN_WEBHOOK=evolution
|
||||
ENV WA_BUSINESS_URL=https://graph.facebook.com
|
||||
ENV WA_BUSINESS_VERSION=v18.0
|
||||
ENV WA_BUSINESS_LANGUAGE=pt_BR
|
||||
|
||||
ENV SQS_ENABLED=false
|
||||
ENV SQS_ACCESS_KEY_ID=
|
||||
ENV SQS_SECRET_ACCESS_KEY=
|
||||
@ -98,6 +106,8 @@ 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_LABELS_EDIT=true
|
||||
ENV WEBHOOK_EVENTS_LABELS_ASSOCIATION=true
|
||||
ENV WEBHOOK_EVENTS_CALL=true
|
||||
|
||||
ENV WEBHOOK_EVENTS_NEW_JWT_TOKEN=false
|
||||
|
19
package.json
19
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "evolution-api",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0",
|
||||
"description": "Rest api for communication with WhatsApp",
|
||||
"main": "./dist/src/main.js",
|
||||
"scripts": {
|
||||
@ -46,11 +46,11 @@
|
||||
"@figuro/chatwoot-sdk": "^1.1.16",
|
||||
"@hapi/boom": "^10.0.1",
|
||||
"@sentry/node": "^7.59.2",
|
||||
"@whiskeysockets/baileys": "github:PurpShell/Baileys#combined",
|
||||
"@whiskeysockets/baileys": "6.6.0",
|
||||
"amqplib": "^0.10.3",
|
||||
"aws-sdk": "^2.1499.0",
|
||||
"axios": "^1.3.5",
|
||||
"class-validator": "^0.13.2",
|
||||
"axios": "^1.6.5",
|
||||
"class-validator": "^0.14.1",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^7.0.3",
|
||||
@ -60,28 +60,33 @@
|
||||
"exiftool-vendored": "^22.0.0",
|
||||
"express": "^4.18.2",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"hbs": "^4.2.0",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"i18next": "^23.7.19",
|
||||
"jimp": "^0.16.13",
|
||||
"join": "^3.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"libphonenumber-js": "^1.10.39",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"mongoose": "^6.10.5",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-mime-types": "^1.1.0",
|
||||
"node-windows": "^1.0.0-beta.8",
|
||||
"parse-bmfont-xml": "^1.1.4",
|
||||
"pg": "^8.11.3",
|
||||
"pino": "^8.11.0",
|
||||
"proxy-agent": "^6.3.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"redis": "^4.6.5",
|
||||
"sharp": "^0.30.7",
|
||||
"sharp": "^0.32.2",
|
||||
"socket.io": "^4.7.1",
|
||||
"socks-proxy-agent": "^8.0.1",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"uuid": "^9.0.0",
|
||||
"xml2js": "^0.6.2",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -34,6 +34,7 @@ export type SaveData = {
|
||||
MESSAGE_UPDATE: boolean;
|
||||
CONTACTS: boolean;
|
||||
CHATS: boolean;
|
||||
LABELS: boolean;
|
||||
};
|
||||
|
||||
export type StoreConf = {
|
||||
@ -41,6 +42,7 @@ export type StoreConf = {
|
||||
MESSAGE_UP: boolean;
|
||||
CONTACTS: boolean;
|
||||
CHATS: boolean;
|
||||
LABELS: boolean;
|
||||
};
|
||||
|
||||
export type CleanStoreConf = {
|
||||
@ -69,6 +71,8 @@ export type Redis = {
|
||||
|
||||
export type Rabbitmq = {
|
||||
ENABLED: boolean;
|
||||
MODE: string; // global, single, isolated
|
||||
EXCHANGE_NAME: string; // available for global and single, isolated mode will use instance name as exchange
|
||||
URI: string;
|
||||
};
|
||||
|
||||
@ -84,6 +88,13 @@ export type Websocket = {
|
||||
ENABLED: boolean;
|
||||
};
|
||||
|
||||
export type WaBusiness = {
|
||||
TOKEN_WEBHOOK: string;
|
||||
URL: string;
|
||||
VERSION: string;
|
||||
LANGUAGE: string;
|
||||
};
|
||||
|
||||
export type EventsWebhook = {
|
||||
APPLICATION_STARTUP: boolean;
|
||||
INSTANCE_CREATE: boolean;
|
||||
@ -103,6 +114,8 @@ export type EventsWebhook = {
|
||||
CHATS_DELETE: boolean;
|
||||
CHATS_UPSERT: boolean;
|
||||
CONNECTION_UPDATE: boolean;
|
||||
LABELS_EDIT: boolean;
|
||||
LABELS_ASSOCIATION: boolean;
|
||||
GROUPS_UPSERT: boolean;
|
||||
GROUP_UPDATE: boolean;
|
||||
GROUP_PARTICIPANTS_UPDATE: boolean;
|
||||
@ -127,16 +140,41 @@ export type Auth = {
|
||||
|
||||
export type DelInstance = number | boolean;
|
||||
|
||||
export type Language = string | 'en';
|
||||
|
||||
export type GlobalWebhook = {
|
||||
URL: string;
|
||||
ENABLED: boolean;
|
||||
WEBHOOK_BY_EVENTS: boolean;
|
||||
};
|
||||
export type CacheConfRedis = {
|
||||
ENABLED: boolean;
|
||||
URI: string;
|
||||
PREFIX_KEY: string;
|
||||
TTL: number;
|
||||
};
|
||||
export type CacheConfLocal = {
|
||||
ENABLED: boolean;
|
||||
TTL: number;
|
||||
};
|
||||
export type SslConf = { PRIVKEY: string; FULLCHAIN: string };
|
||||
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
|
||||
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
||||
export type QrCode = { LIMIT: number; COLOR: string };
|
||||
export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean };
|
||||
export type Chatwoot = {
|
||||
MESSAGE_DELETE: boolean;
|
||||
IMPORT: {
|
||||
DATABASE: {
|
||||
CONNECTION: {
|
||||
URI: string;
|
||||
};
|
||||
};
|
||||
PLACEHOLDER_MEDIA_MESSAGE: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal };
|
||||
export type Production = boolean;
|
||||
|
||||
export interface Env {
|
||||
@ -150,12 +188,17 @@ export interface Env {
|
||||
RABBITMQ: Rabbitmq;
|
||||
SQS: Sqs;
|
||||
WEBSOCKET: Websocket;
|
||||
WA_BUSINESS: WaBusiness;
|
||||
LOG: Log;
|
||||
DEL_INSTANCE: DelInstance;
|
||||
DEL_TEMP_INSTANCES: boolean;
|
||||
LANGUAGE: Language;
|
||||
WEBHOOK: Webhook;
|
||||
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
||||
QRCODE: QrCode;
|
||||
TYPEBOT: Typebot;
|
||||
CHATWOOT: Chatwoot;
|
||||
CACHE: CacheConf;
|
||||
AUTHENTICATION: Auth;
|
||||
PRODUCTION?: Production;
|
||||
}
|
||||
@ -209,6 +252,7 @@ export class ConfigService {
|
||||
MESSAGE_UP: process.env?.STORE_MESSAGE_UP === 'true',
|
||||
CONTACTS: process.env?.STORE_CONTACTS === 'true',
|
||||
CHATS: process.env?.STORE_CHATS === 'true',
|
||||
LABELS: process.env?.STORE_LABELS === 'true',
|
||||
},
|
||||
CLEAN_STORE: {
|
||||
CLEANING_INTERVAL: Number.isInteger(process.env?.CLEAN_STORE_CLEANING_TERMINAL)
|
||||
@ -231,6 +275,7 @@ export class ConfigService {
|
||||
MESSAGE_UPDATE: process.env?.DATABASE_SAVE_MESSAGE_UPDATE === 'true',
|
||||
CONTACTS: process.env?.DATABASE_SAVE_DATA_CONTACTS === 'true',
|
||||
CHATS: process.env?.DATABASE_SAVE_DATA_CHATS === 'true',
|
||||
LABELS: process.env?.DATABASE_SAVE_DATA_LABELS === 'true',
|
||||
},
|
||||
},
|
||||
REDIS: {
|
||||
@ -240,6 +285,8 @@ export class ConfigService {
|
||||
},
|
||||
RABBITMQ: {
|
||||
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
|
||||
MODE: process.env?.RABBITMQ_MODE || 'isolated',
|
||||
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
|
||||
URI: process.env.RABBITMQ_URI || '',
|
||||
},
|
||||
SQS: {
|
||||
@ -252,6 +299,12 @@ export class ConfigService {
|
||||
WEBSOCKET: {
|
||||
ENABLED: process.env?.WEBSOCKET_ENABLED === 'true',
|
||||
},
|
||||
WA_BUSINESS: {
|
||||
TOKEN_WEBHOOK: process.env.WA_BUSINESS_TOKEN_WEBHOOK || '',
|
||||
URL: process.env.WA_BUSINESS_URL || '',
|
||||
VERSION: process.env.WA_BUSINESS_VERSION || '',
|
||||
LANGUAGE: process.env.WA_BUSINESS_LANGUAGE || 'en',
|
||||
},
|
||||
LOG: {
|
||||
LEVEL: (process.env?.LOG_LEVEL.split(',') as LogLevel[]) || [
|
||||
'ERROR',
|
||||
@ -269,6 +322,10 @@ export class ConfigService {
|
||||
DEL_INSTANCE: isBooleanString(process.env?.DEL_INSTANCE)
|
||||
? process.env.DEL_INSTANCE === 'true'
|
||||
: Number.parseInt(process.env.DEL_INSTANCE) || false,
|
||||
DEL_TEMP_INSTANCES: isBooleanString(process.env?.DEL_TEMP_INSTANCES)
|
||||
? process.env.DEL_TEMP_INSTANCES === 'true'
|
||||
: true,
|
||||
LANGUAGE: process.env?.LANGUAGE || 'en',
|
||||
WEBHOOK: {
|
||||
GLOBAL: {
|
||||
URL: process.env?.WEBHOOK_GLOBAL_URL || '',
|
||||
@ -294,6 +351,8 @@ export class ConfigService {
|
||||
CHATS_UPSERT: process.env?.WEBHOOK_EVENTS_CHATS_UPSERT === 'true',
|
||||
CHATS_DELETE: process.env?.WEBHOOK_EVENTS_CHATS_DELETE === 'true',
|
||||
CONNECTION_UPDATE: process.env?.WEBHOOK_EVENTS_CONNECTION_UPDATE === 'true',
|
||||
LABELS_EDIT: process.env?.WEBHOOK_EVENTS_LABELS_EDIT === 'true',
|
||||
LABELS_ASSOCIATION: process.env?.WEBHOOK_EVENTS_LABELS_ASSOCIATION === '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',
|
||||
@ -318,6 +377,29 @@ export class ConfigService {
|
||||
API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old',
|
||||
KEEP_OPEN: process.env.TYPEBOT_KEEP_OPEN === 'true',
|
||||
},
|
||||
CHATWOOT: {
|
||||
MESSAGE_DELETE: process.env.CHATWOOT_MESSAGE_DELETE === 'false',
|
||||
IMPORT: {
|
||||
DATABASE: {
|
||||
CONNECTION: {
|
||||
URI: process.env.CHATWOOT_DATABASE_CONNECTION_URI || '',
|
||||
},
|
||||
},
|
||||
PLACEHOLDER_MEDIA_MESSAGE: process.env?.CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE === 'true',
|
||||
},
|
||||
},
|
||||
CACHE: {
|
||||
REDIS: {
|
||||
ENABLED: process.env?.CACHE_REDIS_ENABLED === 'true',
|
||||
URI: process.env?.CACHE_REDIS_URI || '',
|
||||
PREFIX_KEY: process.env?.CACHE_REDIS_PREFIX_KEY || 'evolution-cache',
|
||||
TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 604800,
|
||||
},
|
||||
LOCAL: {
|
||||
ENABLED: process.env?.CACHE_LOCAL_ENABLED === 'true',
|
||||
TTL: Number.parseInt(process.env?.CACHE_REDIS_TTL) || 86400,
|
||||
},
|
||||
},
|
||||
AUTHENTICATION: {
|
||||
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
|
||||
API_KEY: {
|
||||
|
@ -12,7 +12,6 @@ SERVER:
|
||||
DISABLE_MANAGER: false
|
||||
DISABLE_DOCS: false
|
||||
|
||||
|
||||
CORS:
|
||||
ORIGIN:
|
||||
- "*"
|
||||
@ -48,6 +47,7 @@ LOG:
|
||||
# Default time: 5 minutes
|
||||
# If you don't even want an expiration, enter the value false
|
||||
DEL_INSTANCE: false # or false
|
||||
DEL_TEMP_INSTANCES: true # Delete instances with status closed on start
|
||||
|
||||
# Temporary data storage
|
||||
STORE:
|
||||
@ -84,6 +84,8 @@ REDIS:
|
||||
|
||||
RABBITMQ:
|
||||
ENABLED: false
|
||||
MODE: "global"
|
||||
EXCHANGE_NAME: "evolution_exchange"
|
||||
URI: "amqp://guest:guest@localhost:5672"
|
||||
|
||||
SQS:
|
||||
@ -96,6 +98,12 @@ SQS:
|
||||
WEBSOCKET:
|
||||
ENABLED: false
|
||||
|
||||
WA_BUSINESS:
|
||||
TOKEN_WEBHOOK: evolution
|
||||
URL: https://graph.facebook.com
|
||||
VERSION: v18.0
|
||||
LANGUAGE: pt_BR
|
||||
|
||||
# Global Webhook Settings
|
||||
# Each instance's Webhook URL and events will be requested at the time it is created
|
||||
WEBHOOK:
|
||||
@ -127,6 +135,8 @@ WEBHOOK:
|
||||
GROUP_UPDATE: true
|
||||
GROUP_PARTICIPANTS_UPDATE: true
|
||||
CONNECTION_UPDATE: true
|
||||
LABELS_EDIT: true
|
||||
LABELS_ASSOCIATION: true
|
||||
CALL: true
|
||||
# This event fires every time a new token is requested via the refresh route
|
||||
NEW_JWT_TOKEN: false
|
||||
@ -150,9 +160,30 @@ QRCODE:
|
||||
COLOR: "#198754"
|
||||
|
||||
TYPEBOT:
|
||||
API_VERSION: 'old' # old | latest
|
||||
API_VERSION: "old" # old | latest
|
||||
KEEP_OPEN: false
|
||||
|
||||
CHATWOOT:
|
||||
# If you leave this option as false, when deleting the message for everyone on WhatsApp, it will not be deleted on Chatwoot.
|
||||
MESSAGE_DELETE: true # false | true
|
||||
IMPORT:
|
||||
# This db connection is used to import messages from whatsapp to chatwoot database
|
||||
DATABASE:
|
||||
CONNECTION:
|
||||
URI: "postgres://user:password@hostname:port/dbname"
|
||||
PLACEHOLDER_MEDIA_MESSAGE: true
|
||||
|
||||
# Cache to optimize application performance
|
||||
CACHE:
|
||||
REDIS:
|
||||
ENABLED: false
|
||||
URI: "redis://localhost:6379"
|
||||
PREFIX_KEY: "evolution-cache"
|
||||
TTL: 604800
|
||||
LOCAL:
|
||||
ENABLED: false
|
||||
TTL: 86400
|
||||
|
||||
# Defines an authentication type for the api
|
||||
# We recommend using the apikey because it will allow you to use a custom token,
|
||||
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
|
||||
@ -168,3 +199,5 @@ AUTHENTICATION:
|
||||
JWT:
|
||||
EXPIRIN_IN: 0 # seconds - 3600s === 1h | zero (0) - never expires
|
||||
SECRET: L=0YWt]b2w[WF>#>:&E`
|
||||
|
||||
LANGUAGE: "pt-BR" # pt-BR, en
|
||||
|
@ -25,7 +25,7 @@ info:
|
||||
</font>
|
||||
|
||||
[](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442)
|
||||
version: 1.6.1
|
||||
version: 1.7.0
|
||||
contact:
|
||||
name: DavidsonGomes
|
||||
email: contato@agenciadgcode.com
|
||||
@ -51,6 +51,7 @@ tags:
|
||||
- name: Send Message Controller
|
||||
- name: Chat Controller
|
||||
- name: Group Controller
|
||||
- name: Label Controller
|
||||
- name: Profile Settings
|
||||
- name: JWT
|
||||
- name: Settings
|
||||
@ -940,7 +941,72 @@ paths:
|
||||
description: Successful response
|
||||
content:
|
||||
application/json: {}
|
||||
|
||||
/message/sendList/{instanceName}:
|
||||
post:
|
||||
tags:
|
||||
- Send Message Controller
|
||||
summary: Send a list to a specified instance.
|
||||
description: This endpoint allows users to send a list to a chat.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
options:
|
||||
type: object
|
||||
properties:
|
||||
delay:
|
||||
type: integer
|
||||
presence:
|
||||
type: string
|
||||
listMessage:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
footerText:
|
||||
type: string
|
||||
nullable: true
|
||||
buttonText:
|
||||
type: string
|
||||
sections:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
rows:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
rowId:
|
||||
type: string
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: The name of the instance to which the poll should be sent.
|
||||
example: "evolution"
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json: {}
|
||||
|
||||
/chat/whatsappNumbers/{instanceName}:
|
||||
post:
|
||||
tags:
|
||||
@ -1791,6 +1857,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@ -1867,6 +1935,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@ -1943,6 +2013,8 @@ paths:
|
||||
"GROUP_UPDATE",
|
||||
"GROUP_PARTICIPANTS_UPDATE",
|
||||
"CONNECTION_UPDATE",
|
||||
"LABELS_EDIT",
|
||||
"LABELS_ASSOCIATION",
|
||||
"CALL",
|
||||
"NEW_JWT_TOKEN",
|
||||
]
|
||||
@ -1981,6 +2053,97 @@ paths:
|
||||
content:
|
||||
application/json: {}
|
||||
|
||||
/label/findLabels/{instanceName}:
|
||||
get:
|
||||
tags:
|
||||
- Label Controller
|
||||
summary: List all labels for an instance.
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: "- required"
|
||||
example: "evolution"
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
color:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
predefinedId:
|
||||
type: string
|
||||
required:
|
||||
- color
|
||||
- name
|
||||
- id
|
||||
/label/handleLabel/{instanceName}:
|
||||
put:
|
||||
tags:
|
||||
- Label Controller
|
||||
summary: Change the label (add or remove) for an specific chat.
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
description: "- required"
|
||||
example: "evolution"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
labelId:
|
||||
type: string
|
||||
action:
|
||||
type: string
|
||||
enum:
|
||||
- add
|
||||
- remove
|
||||
required:
|
||||
- number
|
||||
- labelId
|
||||
- action
|
||||
example:
|
||||
number: '553499999999'
|
||||
labelId: '1'
|
||||
action: add
|
||||
responses:
|
||||
"200":
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
numberJid:
|
||||
type: string
|
||||
labelId:
|
||||
type: string
|
||||
remove:
|
||||
type: boolean
|
||||
add:
|
||||
type: boolean
|
||||
required:
|
||||
- numberJid
|
||||
- labelId
|
||||
|
||||
/settings/set/{instanceName}:
|
||||
post:
|
||||
tags:
|
||||
@ -2011,6 +2174,9 @@ paths:
|
||||
read_status:
|
||||
type: boolean
|
||||
description: "Indicates whether to mark status messages as read."
|
||||
sync_full_history:
|
||||
type: boolean
|
||||
description: "Indicates whether to request a full history messages sync on connect."
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
@ -2076,6 +2242,15 @@ paths:
|
||||
conversation_pending:
|
||||
type: boolean
|
||||
description: "Indicates whether to mark conversations as pending."
|
||||
import_contacts:
|
||||
type: boolean
|
||||
description: "Indicates whether to import contacts from phone to Chatwoot when connecting."
|
||||
import_messages:
|
||||
type: boolean
|
||||
description: "Indicates whether to import messages from phone to Chatwoot when connecting."
|
||||
days_limit_import_messages:
|
||||
type: number
|
||||
description: "Indicates number of days to limit messages imported to Chatwoot."
|
||||
parameters:
|
||||
- name: instanceName
|
||||
in: path
|
||||
|
22
src/libs/cacheengine.ts
Normal file
22
src/libs/cacheengine.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { CacheConf, ConfigService } from '../config/env.config';
|
||||
import { ICache } from '../whatsapp/abstract/abstract.cache';
|
||||
import { LocalCache } from './localcache';
|
||||
import { RedisCache } from './rediscache';
|
||||
|
||||
export class CacheEngine {
|
||||
private engine: ICache;
|
||||
|
||||
constructor(private readonly configService: ConfigService, module: string) {
|
||||
const cacheConf = configService.get<CacheConf>('CACHE');
|
||||
|
||||
if (cacheConf?.REDIS?.ENABLED && cacheConf?.REDIS?.URI !== '') {
|
||||
this.engine = new RedisCache(configService, module);
|
||||
} else if (cacheConf?.LOCAL?.ENABLED) {
|
||||
this.engine = new LocalCache(configService, module);
|
||||
}
|
||||
}
|
||||
|
||||
public getEngine() {
|
||||
return this.engine;
|
||||
}
|
||||
}
|
48
src/libs/localcache.ts
Normal file
48
src/libs/localcache.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import NodeCache from 'node-cache';
|
||||
|
||||
import { CacheConf, CacheConfLocal, ConfigService } from '../config/env.config';
|
||||
import { ICache } from '../whatsapp/abstract/abstract.cache';
|
||||
|
||||
export class LocalCache implements ICache {
|
||||
private conf: CacheConfLocal;
|
||||
static localCache = new NodeCache();
|
||||
|
||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
||||
this.conf = this.configService.get<CacheConf>('CACHE')?.LOCAL;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
return LocalCache.localCache.get(this.buildKey(key));
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number) {
|
||||
return LocalCache.localCache.set(this.buildKey(key), value, ttl || this.conf.TTL);
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
return LocalCache.localCache.has(this.buildKey(key));
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
return LocalCache.localCache.del(this.buildKey(key));
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
const keys = await this.keys(appendCriteria);
|
||||
if (!keys?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return LocalCache.localCache.del(keys);
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
const filter = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}`;
|
||||
|
||||
return LocalCache.localCache.keys().filter((key) => key.substring(0, filter.length) === filter);
|
||||
}
|
||||
|
||||
buildKey(key: string) {
|
||||
return `${this.module}:${key}`;
|
||||
}
|
||||
}
|
49
src/libs/postgres.client.ts
Normal file
49
src/libs/postgres.client.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import postgresql from 'pg';
|
||||
|
||||
import { Chatwoot, configService } from '../config/env.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
|
||||
const { Pool } = postgresql;
|
||||
|
||||
class Postgres {
|
||||
private logger = new Logger(Postgres.name);
|
||||
private pool;
|
||||
private connected = false;
|
||||
|
||||
getConnection(connectionString: string) {
|
||||
if (this.connected) {
|
||||
return this.pool;
|
||||
} else {
|
||||
this.pool = new Pool({
|
||||
connectionString,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
this.pool.on('error', () => {
|
||||
this.logger.error('postgres disconnected');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.verbose('connecting new postgres');
|
||||
this.connected = true;
|
||||
} catch (e) {
|
||||
this.connected = false;
|
||||
this.logger.error('postgres connect exception caught: ' + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.pool;
|
||||
}
|
||||
}
|
||||
|
||||
getChatwootConnection() {
|
||||
const uri = configService.get<Chatwoot>('CHATWOOT').IMPORT.DATABASE.CONNECTION.URI;
|
||||
|
||||
return this.getConnection(uri);
|
||||
}
|
||||
}
|
||||
|
||||
export const postgresClient = new Postgres();
|
59
src/libs/rediscache.client.ts
Normal file
59
src/libs/rediscache.client.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { createClient, RedisClientType } from 'redis';
|
||||
|
||||
import { CacheConf, CacheConfRedis, configService } from '../config/env.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
|
||||
class Redis {
|
||||
private logger = new Logger(Redis.name);
|
||||
private client: RedisClientType = null;
|
||||
private conf: CacheConfRedis;
|
||||
private connected = false;
|
||||
|
||||
constructor() {
|
||||
this.conf = configService.get<CacheConf>('CACHE')?.REDIS;
|
||||
}
|
||||
|
||||
getConnection(): RedisClientType {
|
||||
if (this.connected) {
|
||||
return this.client;
|
||||
} else {
|
||||
this.client = createClient({
|
||||
url: this.conf.URI,
|
||||
});
|
||||
|
||||
this.client.on('connect', () => {
|
||||
this.logger.verbose('redis connecting');
|
||||
});
|
||||
|
||||
this.client.on('ready', () => {
|
||||
this.logger.verbose('redis ready');
|
||||
this.connected = true;
|
||||
});
|
||||
|
||||
this.client.on('error', () => {
|
||||
this.logger.error('redis disconnected');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
this.client.on('end', () => {
|
||||
this.logger.verbose('redis connection ended');
|
||||
this.connected = false;
|
||||
});
|
||||
|
||||
try {
|
||||
this.logger.verbose('connecting new redis client');
|
||||
this.client.connect();
|
||||
this.connected = true;
|
||||
this.logger.verbose('connected to new redis client');
|
||||
} catch (e) {
|
||||
this.connected = false;
|
||||
this.logger.error('redis connect exception caught: ' + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const redisClient = new Redis();
|
83
src/libs/rediscache.ts
Normal file
83
src/libs/rediscache.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { RedisClientType } from 'redis';
|
||||
|
||||
import { CacheConf, CacheConfRedis, ConfigService } from '../config/env.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
import { ICache } from '../whatsapp/abstract/abstract.cache';
|
||||
import { redisClient } from './rediscache.client';
|
||||
|
||||
export class RedisCache implements ICache {
|
||||
private readonly logger = new Logger(RedisCache.name);
|
||||
private client: RedisClientType;
|
||||
private conf: CacheConfRedis;
|
||||
|
||||
constructor(private readonly configService: ConfigService, private readonly module: string) {
|
||||
this.conf = this.configService.get<CacheConf>('CACHE')?.REDIS;
|
||||
this.client = redisClient.getConnection();
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
try {
|
||||
return JSON.parse(await this.client.get(this.buildKey(key)));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async set(key: string, value: any, ttl?: number) {
|
||||
try {
|
||||
await this.client.setEx(this.buildKey(key), ttl || this.conf?.TTL, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
try {
|
||||
return (await this.client.exists(this.buildKey(key))) > 0;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
try {
|
||||
return await this.client.del(this.buildKey(key));
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
try {
|
||||
const keys = await this.keys(appendCriteria);
|
||||
if (!keys?.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await this.client.del(keys);
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
try {
|
||||
const match = `${this.buildKey('')}${appendCriteria ? `${appendCriteria}:` : ''}*`;
|
||||
const keys = [];
|
||||
for await (const key of this.client.scanIterator({
|
||||
MATCH: match,
|
||||
COUNT: 100,
|
||||
})) {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
return [...new Set(keys)];
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
buildKey(key: string) {
|
||||
return `${this.conf?.PREFIX_KEY}:${this.module}:${key}`;
|
||||
}
|
||||
}
|
472
src/utils/chatwoot-import-helper.ts
Normal file
472
src/utils/chatwoot-import-helper.ts
Normal file
@ -0,0 +1,472 @@
|
||||
import { inbox } from '@figuro/chatwoot-sdk';
|
||||
import { proto } from '@whiskeysockets/baileys';
|
||||
|
||||
import { Chatwoot, configService } from '../config/env.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
import { postgresClient } from '../libs/postgres.client';
|
||||
import { InstanceDto } from '../whatsapp/dto/instance.dto';
|
||||
import { ChatwootRaw, ContactRaw, MessageRaw } from '../whatsapp/models';
|
||||
import { ChatwootService } from '../whatsapp/services/chatwoot.service';
|
||||
|
||||
type ChatwootUser = {
|
||||
user_type: string;
|
||||
user_id: number;
|
||||
};
|
||||
|
||||
type FksChatwoot = {
|
||||
phone_number: string;
|
||||
contact_id: string;
|
||||
conversation_id: string;
|
||||
};
|
||||
|
||||
type firstLastTimestamp = {
|
||||
first: number;
|
||||
last: number;
|
||||
};
|
||||
|
||||
type IWebMessageInfo = Omit<proto.IWebMessageInfo, 'key'> & Partial<Pick<proto.IWebMessageInfo, 'key'>>;
|
||||
|
||||
class ChatwootImport {
|
||||
private logger = new Logger(ChatwootImport.name);
|
||||
private repositoryMessagesCache = new Map<string, Set<string>>();
|
||||
private historyMessages = new Map<string, MessageRaw[]>();
|
||||
private historyContacts = new Map<string, ContactRaw[]>();
|
||||
|
||||
public getRepositoryMessagesCache(instance: InstanceDto) {
|
||||
return this.repositoryMessagesCache.has(instance.instanceName)
|
||||
? this.repositoryMessagesCache.get(instance.instanceName)
|
||||
: null;
|
||||
}
|
||||
|
||||
public setRepositoryMessagesCache(instance: InstanceDto, repositoryMessagesCache: Set<string>) {
|
||||
this.repositoryMessagesCache.set(instance.instanceName, repositoryMessagesCache);
|
||||
}
|
||||
|
||||
public deleteRepositoryMessagesCache(instance: InstanceDto) {
|
||||
this.repositoryMessagesCache.delete(instance.instanceName);
|
||||
}
|
||||
|
||||
public addHistoryMessages(instance: InstanceDto, messagesRaw: MessageRaw[]) {
|
||||
const actualValue = this.historyMessages.has(instance.instanceName)
|
||||
? this.historyMessages.get(instance.instanceName)
|
||||
: [];
|
||||
this.historyMessages.set(instance.instanceName, actualValue.concat(messagesRaw));
|
||||
}
|
||||
|
||||
public addHistoryContacts(instance: InstanceDto, contactsRaw: ContactRaw[]) {
|
||||
const actualValue = this.historyContacts.has(instance.instanceName)
|
||||
? this.historyContacts.get(instance.instanceName)
|
||||
: [];
|
||||
this.historyContacts.set(instance.instanceName, actualValue.concat(contactsRaw));
|
||||
}
|
||||
|
||||
public deleteHistoryMessages(instance: InstanceDto) {
|
||||
this.historyMessages.delete(instance.instanceName);
|
||||
}
|
||||
|
||||
public deleteHistoryContacts(instance: InstanceDto) {
|
||||
this.historyContacts.delete(instance.instanceName);
|
||||
}
|
||||
|
||||
public clearAll(instance: InstanceDto) {
|
||||
this.deleteRepositoryMessagesCache(instance);
|
||||
this.deleteHistoryMessages(instance);
|
||||
this.deleteHistoryContacts(instance);
|
||||
}
|
||||
|
||||
public getHistoryMessagesLenght(instance: InstanceDto) {
|
||||
return this.historyMessages.get(instance.instanceName)?.length ?? 0;
|
||||
}
|
||||
|
||||
public async importHistoryContacts(instance: InstanceDto, provider: ChatwootRaw) {
|
||||
try {
|
||||
if (this.getHistoryMessagesLenght(instance) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
let totalContactsImported = 0;
|
||||
|
||||
const contacts = this.historyContacts.get(instance.instanceName) || [];
|
||||
if (contacts.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let contactsChunk: ContactRaw[] = this.sliceIntoChunks(contacts, 3000);
|
||||
while (contactsChunk.length > 0) {
|
||||
// inserting contacts in chatwoot db
|
||||
let sqlInsert = `INSERT INTO contacts
|
||||
(name, phone_number, account_id, identifier, created_at, updated_at) VALUES `;
|
||||
const bindInsert = [provider.account_id];
|
||||
|
||||
for (const contact of contactsChunk) {
|
||||
bindInsert.push(contact.pushName);
|
||||
const bindName = `$${bindInsert.length}`;
|
||||
|
||||
bindInsert.push(`+${contact.id.split('@')[0]}`);
|
||||
const bindPhoneNumber = `$${bindInsert.length}`;
|
||||
|
||||
bindInsert.push(contact.id);
|
||||
const bindIdentifier = `$${bindInsert.length}`;
|
||||
|
||||
sqlInsert += `(${bindName}, ${bindPhoneNumber}, $1, ${bindIdentifier}, NOW(), NOW()),`;
|
||||
}
|
||||
if (sqlInsert.slice(-1) === ',') {
|
||||
sqlInsert = sqlInsert.slice(0, -1);
|
||||
}
|
||||
sqlInsert += ` ON CONFLICT (identifier, account_id)
|
||||
DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
phone_number = EXCLUDED.phone_number,
|
||||
identifier = EXCLUDED.identifier`;
|
||||
|
||||
totalContactsImported += (await pgClient.query(sqlInsert, bindInsert))?.rowCount ?? 0;
|
||||
contactsChunk = this.sliceIntoChunks(contacts, 3000);
|
||||
}
|
||||
|
||||
this.deleteHistoryContacts(instance);
|
||||
|
||||
return totalContactsImported;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on import history contacts: ${error.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async importHistoryMessages(
|
||||
instance: InstanceDto,
|
||||
chatwootService: ChatwootService,
|
||||
inbox: inbox,
|
||||
provider: ChatwootRaw,
|
||||
) {
|
||||
try {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const chatwootUser = await this.getChatwootUser(provider);
|
||||
if (!chatwootUser) {
|
||||
throw new Error('User not found to import messages.');
|
||||
}
|
||||
|
||||
let totalMessagesImported = 0;
|
||||
|
||||
const messagesOrdered = this.historyMessages.get(instance.instanceName) || [];
|
||||
if (messagesOrdered.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ordering messages by number and timestamp asc
|
||||
messagesOrdered.sort((a, b) => {
|
||||
return (
|
||||
parseInt(a.key.remoteJid) - parseInt(b.key.remoteJid) ||
|
||||
(a.messageTimestamp as number) - (b.messageTimestamp as number)
|
||||
);
|
||||
});
|
||||
|
||||
const allMessagesMappedByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesOrdered);
|
||||
// Map structure: +552199999999 => { first message timestamp from number, last message timestamp from number}
|
||||
const phoneNumbersWithTimestamp = new Map<string, firstLastTimestamp>();
|
||||
allMessagesMappedByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
|
||||
phoneNumbersWithTimestamp.set(phoneNumber, {
|
||||
first: messages[0]?.messageTimestamp as number,
|
||||
last: messages[messages.length - 1]?.messageTimestamp as number,
|
||||
});
|
||||
});
|
||||
|
||||
// processing messages in batch
|
||||
const batchSize = 4000;
|
||||
let messagesChunk: MessageRaw[] = this.sliceIntoChunks(messagesOrdered, batchSize);
|
||||
while (messagesChunk.length > 0) {
|
||||
// Map structure: +552199999999 => MessageRaw[]
|
||||
const messagesByPhoneNumber = this.createMessagesMapByPhoneNumber(messagesChunk);
|
||||
|
||||
if (messagesByPhoneNumber.size > 0) {
|
||||
const fksByNumber = await this.selectOrCreateFksFromChatwoot(
|
||||
provider,
|
||||
inbox,
|
||||
phoneNumbersWithTimestamp,
|
||||
messagesByPhoneNumber,
|
||||
);
|
||||
|
||||
// inserting messages in chatwoot db
|
||||
let sqlInsertMsg = `INSERT INTO messages
|
||||
(content, account_id, inbox_id, conversation_id, message_type, private, content_type,
|
||||
sender_type, sender_id, created_at, updated_at) VALUES `;
|
||||
const bindInsertMsg = [provider.account_id, inbox.id];
|
||||
|
||||
messagesByPhoneNumber.forEach((messages: MessageRaw[], phoneNumber: string) => {
|
||||
const fksChatwoot = fksByNumber.get(phoneNumber);
|
||||
|
||||
messages.forEach((message) => {
|
||||
if (!message.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fksChatwoot?.conversation_id || !fksChatwoot?.contact_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentMessage = this.getContentMessage(chatwootService, message);
|
||||
if (!contentMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
bindInsertMsg.push(contentMessage);
|
||||
const bindContent = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(fksChatwoot.conversation_id);
|
||||
const bindConversationId = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.key.fromMe ? '1' : '0');
|
||||
const bindMessageType = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_type : 'Contact');
|
||||
const bindSenderType = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.key.fromMe ? chatwootUser.user_id : fksChatwoot.contact_id);
|
||||
const bindSenderId = `$${bindInsertMsg.length}`;
|
||||
|
||||
bindInsertMsg.push(message.messageTimestamp as number);
|
||||
const bindmessageTimestamp = `$${bindInsertMsg.length}`;
|
||||
|
||||
sqlInsertMsg += `(${bindContent}, $1, $2, ${bindConversationId}, ${bindMessageType}, FALSE, 0,
|
||||
${bindSenderType},${bindSenderId}, to_timestamp(${bindmessageTimestamp}), to_timestamp(${bindmessageTimestamp})),`;
|
||||
});
|
||||
});
|
||||
if (bindInsertMsg.length > 2) {
|
||||
if (sqlInsertMsg.slice(-1) === ',') {
|
||||
sqlInsertMsg = sqlInsertMsg.slice(0, -1);
|
||||
}
|
||||
totalMessagesImported += (await pgClient.query(sqlInsertMsg, bindInsertMsg))?.rowCount ?? 0;
|
||||
}
|
||||
}
|
||||
messagesChunk = this.sliceIntoChunks(messagesOrdered, batchSize);
|
||||
}
|
||||
|
||||
this.deleteHistoryMessages(instance);
|
||||
this.deleteRepositoryMessagesCache(instance);
|
||||
|
||||
this.importHistoryContacts(instance, provider);
|
||||
|
||||
return totalMessagesImported;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on import history messages: ${error.toString()}`);
|
||||
|
||||
this.deleteHistoryMessages(instance);
|
||||
this.deleteRepositoryMessagesCache(instance);
|
||||
}
|
||||
}
|
||||
|
||||
public async selectOrCreateFksFromChatwoot(
|
||||
provider: ChatwootRaw,
|
||||
inbox: inbox,
|
||||
phoneNumbersWithTimestamp: Map<string, firstLastTimestamp>,
|
||||
messagesByPhoneNumber: Map<string, MessageRaw[]>,
|
||||
): Promise<Map<string, FksChatwoot>> {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const bindValues = [provider.account_id, inbox.id];
|
||||
const phoneNumberBind = Array.from(messagesByPhoneNumber.keys())
|
||||
.map((phoneNumber) => {
|
||||
const phoneNumberTimestamp = phoneNumbersWithTimestamp.get(phoneNumber);
|
||||
|
||||
if (phoneNumberTimestamp) {
|
||||
bindValues.push(phoneNumber);
|
||||
let bindStr = `($${bindValues.length},`;
|
||||
|
||||
bindValues.push(phoneNumberTimestamp.first);
|
||||
bindStr += `$${bindValues.length},`;
|
||||
|
||||
bindValues.push(phoneNumberTimestamp.last);
|
||||
return `${bindStr}$${bindValues.length})`;
|
||||
}
|
||||
})
|
||||
.join(',');
|
||||
|
||||
// select (or insert when necessary) data from tables contacts, contact_inboxes, conversations from chatwoot db
|
||||
const sqlFromChatwoot = `WITH
|
||||
phone_number AS (
|
||||
SELECT phone_number, created_at::INTEGER, last_activity_at::INTEGER FROM (
|
||||
VALUES
|
||||
${phoneNumberBind}
|
||||
) as t (phone_number, created_at, last_activity_at)
|
||||
),
|
||||
|
||||
only_new_phone_number AS (
|
||||
SELECT * FROM phone_number
|
||||
WHERE phone_number NOT IN (
|
||||
SELECT phone_number
|
||||
FROM contacts
|
||||
JOIN contact_inboxes ci ON ci.contact_id = contacts.id AND ci.inbox_id = $2
|
||||
JOIN conversations con ON con.contact_inbox_id = ci.id
|
||||
AND con.account_id = $1
|
||||
AND con.inbox_id = $2
|
||||
AND con.contact_id = contacts.id
|
||||
WHERE contacts.account_id = $1
|
||||
)
|
||||
),
|
||||
|
||||
new_contact AS (
|
||||
INSERT INTO contacts (name, phone_number, account_id, identifier, created_at, updated_at)
|
||||
SELECT REPLACE(p.phone_number, '+', ''), p.phone_number, $1, CONCAT(REPLACE(p.phone_number, '+', ''),
|
||||
'@s.whatsapp.net'), to_timestamp(p.created_at), to_timestamp(p.last_activity_at)
|
||||
FROM only_new_phone_number AS p
|
||||
ON CONFLICT(identifier, account_id) DO UPDATE SET updated_at = EXCLUDED.updated_at
|
||||
RETURNING id, phone_number, created_at, updated_at
|
||||
),
|
||||
|
||||
new_contact_inbox AS (
|
||||
INSERT INTO contact_inboxes (contact_id, inbox_id, source_id, created_at, updated_at)
|
||||
SELECT new_contact.id, $2, gen_random_uuid(), new_contact.created_at, new_contact.updated_at
|
||||
FROM new_contact
|
||||
RETURNING id, contact_id, created_at, updated_at
|
||||
),
|
||||
|
||||
new_conversation AS (
|
||||
INSERT INTO conversations (account_id, inbox_id, status, contact_id,
|
||||
contact_inbox_id, uuid, last_activity_at, created_at, updated_at)
|
||||
SELECT $1, $2, 0, new_contact_inbox.contact_id, new_contact_inbox.id, gen_random_uuid(),
|
||||
new_contact_inbox.updated_at, new_contact_inbox.created_at, new_contact_inbox.updated_at
|
||||
FROM new_contact_inbox
|
||||
RETURNING id, contact_id
|
||||
)
|
||||
|
||||
SELECT new_contact.phone_number, new_conversation.contact_id, new_conversation.id AS conversation_id
|
||||
FROM new_conversation
|
||||
JOIN new_contact ON new_conversation.contact_id = new_contact.id
|
||||
|
||||
UNION
|
||||
|
||||
SELECT p.phone_number, c.id contact_id, con.id conversation_id
|
||||
FROM phone_number p
|
||||
JOIN contacts c ON c.phone_number = p.phone_number
|
||||
JOIN contact_inboxes ci ON ci.contact_id = c.id AND ci.inbox_id = $2
|
||||
JOIN conversations con ON con.contact_inbox_id = ci.id AND con.account_id = $1
|
||||
AND con.inbox_id = $2 AND con.contact_id = c.id`;
|
||||
|
||||
const fksFromChatwoot = await pgClient.query(sqlFromChatwoot, bindValues);
|
||||
|
||||
return new Map(fksFromChatwoot.rows.map((item: FksChatwoot) => [item.phone_number, item]));
|
||||
}
|
||||
|
||||
public async getChatwootUser(provider: ChatwootRaw): Promise<ChatwootUser> {
|
||||
try {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const sqlUser = `SELECT owner_type AS user_type, owner_id AS user_id
|
||||
FROM access_tokens
|
||||
WHERE token = $1`;
|
||||
|
||||
return (await pgClient.query(sqlUser, [provider.token]))?.rows[0] || false;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on getChatwootUser: ${error.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
public createMessagesMapByPhoneNumber(messages: MessageRaw[]): Map<string, MessageRaw[]> {
|
||||
return messages.reduce((acc: Map<string, MessageRaw[]>, message: MessageRaw) => {
|
||||
if (!this.isIgnorePhoneNumber(message?.key?.remoteJid)) {
|
||||
const phoneNumber = message?.key?.remoteJid?.split('@')[0];
|
||||
if (phoneNumber) {
|
||||
const phoneNumberPlus = `+${phoneNumber}`;
|
||||
const messages = acc.has(phoneNumberPlus) ? acc.get(phoneNumberPlus) : [];
|
||||
messages.push(message);
|
||||
acc.set(phoneNumberPlus, messages);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
public async getContactsOrderByRecentConversations(
|
||||
inbox: inbox,
|
||||
provider: ChatwootRaw,
|
||||
limit = 50,
|
||||
): Promise<{ id: number; phone_number: string; identifier: string }[]> {
|
||||
try {
|
||||
const pgClient = postgresClient.getChatwootConnection();
|
||||
|
||||
const sql = `SELECT contacts.id, contacts.identifier, contacts.phone_number
|
||||
FROM conversations
|
||||
JOIN contacts ON contacts.id = conversations.contact_id
|
||||
WHERE conversations.account_id = $1
|
||||
AND inbox_id = $2
|
||||
ORDER BY conversations.last_activity_at DESC
|
||||
LIMIT $3`;
|
||||
|
||||
return (await pgClient.query(sql, [provider.account_id, inbox.id, limit]))?.rows;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error on get recent conversations: ${error.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getContentMessage(chatwootService: ChatwootService, msg: IWebMessageInfo) {
|
||||
const contentMessage = chatwootService.getConversationMessage(msg.message);
|
||||
if (contentMessage) {
|
||||
return contentMessage;
|
||||
}
|
||||
|
||||
if (!configService.get<Chatwoot>('CHATWOOT').IMPORT.PLACEHOLDER_MEDIA_MESSAGE) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const types = {
|
||||
documentMessage: msg.message.documentMessage,
|
||||
documentWithCaptionMessage: msg.message.documentWithCaptionMessage?.message?.documentMessage,
|
||||
imageMessage: msg.message.imageMessage,
|
||||
videoMessage: msg.message.videoMessage,
|
||||
audioMessage: msg.message.audioMessage,
|
||||
stickerMessage: msg.message.stickerMessage,
|
||||
templateMessage: msg.message.templateMessage?.hydratedTemplate?.hydratedContentText,
|
||||
};
|
||||
const typeKey = Object.keys(types).find((key) => types[key] !== undefined);
|
||||
|
||||
switch (typeKey) {
|
||||
case 'documentMessage':
|
||||
return `_<File: ${msg.message.documentMessage.fileName}${
|
||||
msg.message.documentMessage.caption ? ` ${msg.message.documentMessage.caption}` : ''
|
||||
}>_`;
|
||||
|
||||
case 'documentWithCaptionMessage':
|
||||
return `_<File: ${msg.message.documentWithCaptionMessage.message.documentMessage.fileName}${
|
||||
msg.message.documentWithCaptionMessage.message.documentMessage.caption
|
||||
? ` ${msg.message.documentWithCaptionMessage.message.documentMessage.caption}`
|
||||
: ''
|
||||
}>_`;
|
||||
|
||||
case 'templateMessage':
|
||||
return msg.message.templateMessage.hydratedTemplate.hydratedTitleText
|
||||
? `*${msg.message.templateMessage.hydratedTemplate.hydratedTitleText}*\\n`
|
||||
: '' + msg.message.templateMessage.hydratedTemplate.hydratedContentText;
|
||||
|
||||
case 'imageMessage':
|
||||
return '_<Image Message>_';
|
||||
|
||||
case 'videoMessage':
|
||||
return '_<Video Message>_';
|
||||
|
||||
case 'audioMessage':
|
||||
return '_<Audio Message>_';
|
||||
|
||||
case 'stickerMessage':
|
||||
return '_<Sticker Message>_';
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public sliceIntoChunks(arr: any[], chunkSize: number) {
|
||||
return arr.splice(0, chunkSize);
|
||||
}
|
||||
|
||||
public isGroup(remoteJid: string) {
|
||||
return remoteJid.includes('@g.us');
|
||||
}
|
||||
|
||||
public isIgnorePhoneNumber(remoteJid: string) {
|
||||
return this.isGroup(remoteJid) || remoteJid === 'status@broadcast' || remoteJid === '0@s.whatsapp.net';
|
||||
}
|
||||
}
|
||||
|
||||
export const chatwootImport = new ChatwootImport();
|
32
src/utils/i18n.ts
Normal file
32
src/utils/i18n.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import fs from 'fs';
|
||||
import i18next from 'i18next';
|
||||
import path from 'path';
|
||||
|
||||
import { ConfigService, Language } from '../config/env.config';
|
||||
|
||||
const languages = ['en', 'pt-BR'];
|
||||
const translationsPath = path.join(__dirname, 'translations');
|
||||
const configService: ConfigService = new ConfigService();
|
||||
|
||||
const resources: any = {};
|
||||
|
||||
languages.forEach((language) => {
|
||||
const languagePath = path.join(translationsPath, `${language}.json`);
|
||||
if (fs.existsSync(languagePath)) {
|
||||
resources[language] = {
|
||||
translation: require(languagePath),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
i18next.init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
lng: configService.get<Language>('LANGUAGE'),
|
||||
debug: false,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
export default i18next;
|
17
src/utils/makeProxyAgent.ts
Normal file
17
src/utils/makeProxyAgent.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
|
||||
import { wa } from '../whatsapp/types/wa.types';
|
||||
|
||||
export function makeProxyAgent(proxy: wa.Proxy | string) {
|
||||
if (typeof proxy === 'string') {
|
||||
return new HttpsProxyAgent(proxy);
|
||||
}
|
||||
|
||||
const { host, password, port, protocol, username } = proxy;
|
||||
let proxyUrl = `${protocol}://${host}:${port}`;
|
||||
|
||||
if (username && password) {
|
||||
proxyUrl = `${protocol}://${username}:${password}@${host}:${port}`;
|
||||
}
|
||||
return new HttpsProxyAgent(proxyUrl);
|
||||
}
|
25
src/utils/translations/en.json
Normal file
25
src/utils/translations/en.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"qrgeneratedsuccesfully": "QRCode successfully generated!",
|
||||
"scanqr": "Scan this QR code within the next 40 seconds.",
|
||||
"qrlimitreached": "QRCode generation limit reached, to generate a new QRCode, send the 'init' message again.",
|
||||
"numbernotinwhatsapp": "The message was not sent as the contact is not a valid Whatsapp number.",
|
||||
"cw.inbox.connected": "🚀 Connection successfully established!",
|
||||
"cw.inbox.disconnect": "🚨 Disconnecting WhatsApp from inbox *{{inboxName}}*.",
|
||||
"cw.inbox.alreadyConnected": "🚨 {{inboxName}} instance is connected.",
|
||||
"cw.inbox.clearCache": "✅ {{inboxName}} instance cache cleared.",
|
||||
"cw.inbox.notFound": "⚠️ {{inboxName}} instance not found.",
|
||||
"cw.inbox.status": "⚠️ {{inboxName}} instance status: *{{state}}*.",
|
||||
"cw.import.startImport": "💬 Starting to import messages. Please wait...",
|
||||
"cw.import.importingMessages": "💬 Importing messages. More one moment...",
|
||||
"cw.import.messagesImported": "💬 {{totalMessagesImported}} messages imported. Refresh page to see the new messages.",
|
||||
"cw.import.messagesException": "💬 Something went wrong in importing messages.",
|
||||
"cw.locationMessage.location": "Location",
|
||||
"cw.locationMessage.latitude": "Latitude",
|
||||
"cw.locationMessage.longitude": "Longitude",
|
||||
"cw.locationMessage.locationName": "Name",
|
||||
"cw.locationMessage.locationAddress": "Address",
|
||||
"cw.locationMessage.locationUrl": "URL",
|
||||
"cw.contactMessage.contact": "Contact",
|
||||
"cw.contactMessage.name": "Name",
|
||||
"cw.contactMessage.number": "Number"
|
||||
}
|
25
src/utils/translations/pt-BR.json
Normal file
25
src/utils/translations/pt-BR.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"qrgeneratedsuccesfully": "QRCode gerado com sucesso!",
|
||||
"scanqr": "Escaneie o QRCode com o WhatsApp nos próximos 40 segundos.",
|
||||
"qrlimitreached": "Limite de geração de QRCode atingido! Para gerar um novo QRCode, envie o texto 'init' nesta conversa.",
|
||||
"numbernotinwhatsapp": "A mensagem não foi enviada, pois o contato não é um número válido do WhatsApp.",
|
||||
"cw.inbox.connected": "🚀 Conectado com sucesso!",
|
||||
"cw.inbox.disconnect": "🚨 Instância *{{inboxName}}* desconectada do WhatsApp.",
|
||||
"cw.inbox.alreadyConnected": "🚨 Instância *{{inboxName}}* já está conectada.",
|
||||
"cw.inbox.clearCache": "✅ Instância *{{inboxName}}* cache removido.",
|
||||
"cw.inbox.notFound": "⚠️ Instância *{{inboxName}}* não encontrada.",
|
||||
"cw.inbox.status": "⚠️ Status da instância {{inboxName}}: *{{state}}*.",
|
||||
"cw.import.startImport": "💬 Iniciando importação de mensagens. Por favor, aguarde...",
|
||||
"cw.import.importingMessages": "💬 Importando mensagens. Mais um momento...",
|
||||
"cw.import.messagesImported": "💬 {{totalMessagesImported}} mensagens importadas. Atualize a página para ver as novas mensagens.",
|
||||
"cw.import.messagesException": "💬 Não foi possível importar as mensagens.",
|
||||
"cw.locationMessage.location": "Localização",
|
||||
"cw.locationMessage.latitude": "Latitude",
|
||||
"cw.locationMessage.longitude": "Longitude",
|
||||
"cw.locationMessage.locationName": "Nome",
|
||||
"cw.locationMessage.locationAddress": "Endereço",
|
||||
"cw.locationMessage.locationUrl": "URL",
|
||||
"cw.contactMessage.contact": "Contato",
|
||||
"cw.contactMessage.name": "Nome",
|
||||
"cw.contactMessage.number": "Número"
|
||||
}
|
@ -53,6 +53,8 @@ export const instanceNameSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -275,6 +277,26 @@ export const audioMessageSchema: JSONSchema7 = {
|
||||
required: ['audioMessage', 'number'],
|
||||
};
|
||||
|
||||
export const templateMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { ...numberDefinition },
|
||||
options: { ...optionsSchema },
|
||||
templateMessage: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
language: { type: 'string' },
|
||||
components: { type: 'array' },
|
||||
},
|
||||
required: ['name', 'language'],
|
||||
...isNotEmpty('name', 'language'),
|
||||
},
|
||||
},
|
||||
required: ['templateMessage', 'number'],
|
||||
};
|
||||
|
||||
export const buttonMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@ -517,6 +539,17 @@ export const privacySettingsSchema: JSONSchema7 = {
|
||||
required: ['privacySettings'],
|
||||
};
|
||||
|
||||
export const blockUserSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
status: { type: 'string', enum: ['block', 'unblock'] },
|
||||
},
|
||||
required: ['number', 'status'],
|
||||
...isNotEmpty('number', 'status'),
|
||||
};
|
||||
|
||||
export const archiveChatSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@ -592,6 +625,26 @@ export const profileStatusSchema: JSONSchema7 = {
|
||||
...isNotEmpty('status'),
|
||||
};
|
||||
|
||||
export const updateMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
text: { type: 'string' },
|
||||
key: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
remoteJid: { type: 'string' },
|
||||
fromMe: { type: 'boolean', enum: [true, false] },
|
||||
},
|
||||
required: ['id', 'fromMe', 'remoteJid'],
|
||||
...isNotEmpty('id', 'remoteJid'),
|
||||
},
|
||||
},
|
||||
...isNotEmpty('number', 'text', 'key'),
|
||||
};
|
||||
|
||||
export const profilePictureSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@ -751,6 +804,16 @@ export const groupInviteSchema: JSONSchema7 = {
|
||||
...isNotEmpty('inviteCode'),
|
||||
};
|
||||
|
||||
export const AcceptGroupInviteSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
inviteCode: { type: 'string', pattern: '^[a-zA-Z0-9]{22}$' },
|
||||
},
|
||||
required: ['inviteCode'],
|
||||
...isNotEmpty('inviteCode'),
|
||||
};
|
||||
|
||||
export const updateParticipantsSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@ -867,6 +930,8 @@ export const webhookSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -893,6 +958,9 @@ export const chatwootSchema: JSONSchema7 = {
|
||||
reopen_conversation: { type: 'boolean', enum: [true, false] },
|
||||
conversation_pending: { type: 'boolean', enum: [true, false] },
|
||||
auto_create: { type: 'boolean', enum: [true, false] },
|
||||
import_contacts: { type: 'boolean', enum: [true, false] },
|
||||
import_messages: { type: 'boolean', enum: [true, false] },
|
||||
days_limit_import_messages: { type: 'number' },
|
||||
},
|
||||
required: ['enabled', 'account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'],
|
||||
...isNotEmpty('account_id', 'token', 'url', 'sign_msg', 'reopen_conversation', 'conversation_pending'),
|
||||
@ -908,9 +976,10 @@ export const settingsSchema: JSONSchema7 = {
|
||||
always_online: { type: 'boolean', enum: [true, false] },
|
||||
read_messages: { type: 'boolean', enum: [true, false] },
|
||||
read_status: { type: 'boolean', enum: [true, false] },
|
||||
sync_full_history: { 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'),
|
||||
required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'],
|
||||
...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'),
|
||||
};
|
||||
|
||||
export const websocketSchema: JSONSchema7 = {
|
||||
@ -943,6 +1012,8 @@ export const websocketSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -986,6 +1057,8 @@ export const rabbitmqSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -1029,6 +1102,8 @@ export const sqsSchema: JSONSchema7 = {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -1086,7 +1161,18 @@ export const proxySchema: JSONSchema7 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
proxy: { type: 'string' },
|
||||
proxy: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
protocol: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string' },
|
||||
},
|
||||
required: ['host', 'port', 'protocol'],
|
||||
...isNotEmpty('host', 'port', 'protocol'),
|
||||
},
|
||||
},
|
||||
required: ['enabled', 'proxy'],
|
||||
...isNotEmpty('enabled', 'proxy'),
|
||||
@ -1105,3 +1191,14 @@ export const chamaaiSchema: JSONSchema7 = {
|
||||
required: ['enabled', 'url', 'token', 'waNumber', 'answerByAudio'],
|
||||
...isNotEmpty('enabled', 'url', 'token', 'waNumber', 'answerByAudio'),
|
||||
};
|
||||
|
||||
export const handleLabelSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { ...numberDefinition },
|
||||
labelId: { type: 'string' },
|
||||
action: { type: 'string', enum: ['add', 'remove'] },
|
||||
},
|
||||
required: ['number', 'labelId', 'action'],
|
||||
};
|
||||
|
13
src/whatsapp/abstract/abstract.cache.ts
Normal file
13
src/whatsapp/abstract/abstract.cache.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export interface ICache {
|
||||
get(key: string): Promise<any>;
|
||||
|
||||
set(key: string, value: any, ttl?: number): void;
|
||||
|
||||
has(key: string): Promise<boolean>;
|
||||
|
||||
keys(appendCriteria?: string): Promise<string[]>;
|
||||
|
||||
delete(key: string | string[]): Promise<number>;
|
||||
|
||||
deleteAll(appendCriteria?: string): Promise<number>;
|
||||
}
|
@ -21,7 +21,6 @@ const logger = new Logger('Validate');
|
||||
export abstract class RouterBroker {
|
||||
constructor() {}
|
||||
public routerPath(path: string, param = true) {
|
||||
// const route = param ? '/:instanceName/' + path : '/' + path;
|
||||
let route = '/' + path;
|
||||
param ? (route += '/:instanceName') : null;
|
||||
|
||||
@ -56,10 +55,6 @@ export abstract class RouterBroker {
|
||||
message = stack.replace('instance.', '');
|
||||
}
|
||||
return message;
|
||||
// return {
|
||||
// property: property.replace('instance.', ''),
|
||||
// message,
|
||||
// };
|
||||
});
|
||||
logger.error(message);
|
||||
throw new BadRequestException(message);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
NumberDto,
|
||||
@ -10,6 +11,7 @@ import {
|
||||
ProfileStatusDto,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
@ -117,4 +119,14 @@ export class ChatController {
|
||||
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].removeProfilePicture();
|
||||
}
|
||||
|
||||
public async updateMessage({ instanceName }: InstanceDto, data: UpdateMessageDto) {
|
||||
logger.verbose('requested updateMessage from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].updateMessage(data);
|
||||
}
|
||||
|
||||
public async blockUser({ instanceName }: InstanceDto, data: BlockUserDto) {
|
||||
logger.verbose('requested blockUser from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].blockUser(data);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,11 @@ import { isURL } from 'class-validator';
|
||||
import { ConfigService, HttpServer } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { CacheEngine } from '../../libs/cacheengine';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { CacheService } from '../services/cache.service';
|
||||
import { ChatwootService } from '../services/chatwoot.service';
|
||||
import { waMonitor } from '../whatsapp.module';
|
||||
|
||||
@ -49,6 +51,9 @@ export class ChatwootController {
|
||||
data.sign_delimiter = null;
|
||||
data.reopen_conversation = false;
|
||||
data.conversation_pending = false;
|
||||
data.import_contacts = false;
|
||||
data.import_messages = false;
|
||||
data.days_limit_import_messages = 0;
|
||||
data.auto_create = false;
|
||||
}
|
||||
|
||||
@ -94,7 +99,9 @@ export class ChatwootController {
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository);
|
||||
|
||||
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository, chatwootCache);
|
||||
|
||||
return chatwootService.receiveWebhook(instance, data);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
AcceptGroupInvite,
|
||||
CreateGroupDto,
|
||||
GetParticipant,
|
||||
GroupDescriptionDto,
|
||||
@ -65,6 +66,11 @@ export class GroupController {
|
||||
return await this.waMonitor.waInstances[instance.instanceName].sendInvite(data);
|
||||
}
|
||||
|
||||
public async acceptInviteCode(instance: InstanceDto, inviteCode: AcceptGroupInvite) {
|
||||
logger.verbose('requested acceptInviteCode from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].acceptInviteCode(inviteCode);
|
||||
}
|
||||
|
||||
public async revokeInviteCode(instance: InstanceDto, groupJid: GroupJid) {
|
||||
logger.verbose('requested revokeInviteCode from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].revokeInviteCode(groupJid);
|
||||
|
@ -3,24 +3,26 @@ import { isURL } from 'class-validator';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ConfigService, HttpServer } from '../../config/env.config';
|
||||
import { ConfigService, HttpServer, WaBusiness } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException, InternalServerErrorException } from '../../exceptions';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { AuthService, OldToken } from '../services/auth.service';
|
||||
import { CacheService } from '../services/cache.service';
|
||||
import { ChatwootService } from '../services/chatwoot.service';
|
||||
import { IntegrationService } from '../services/integration.service';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { ProxyService } from '../services/proxy.service';
|
||||
import { RabbitmqService } from '../services/rabbitmq.service';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
import { SqsService } from '../services/sqs.service';
|
||||
import { TypebotService } from '../services/typebot.service';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
import { WebsocketService } from '../services/websocket.service';
|
||||
import { WAStartupService } from '../services/whatsapp.service';
|
||||
import { Events, wa } from '../types/wa.types';
|
||||
import { BaileysStartupService } from '../services/whatsapp.baileys.service';
|
||||
import { BusinessStartupService } from '../services/whatsapp.business.service';
|
||||
import { Events, Integration, wa } from '../types/wa.types';
|
||||
|
||||
export class InstanceController {
|
||||
constructor(
|
||||
@ -34,10 +36,11 @@ export class InstanceController {
|
||||
private readonly settingsService: SettingsService,
|
||||
private readonly websocketService: WebsocketService,
|
||||
private readonly rabbitmqService: RabbitmqService,
|
||||
private readonly proxyService: ProxyService,
|
||||
private readonly sqsService: SqsService,
|
||||
private readonly typebotService: TypebotService,
|
||||
private readonly integrationService: IntegrationService,
|
||||
private readonly cache: RedisCache,
|
||||
private readonly chatwootCache: CacheService,
|
||||
) {}
|
||||
|
||||
private readonly logger = new Logger(InstanceController.name);
|
||||
@ -50,6 +53,7 @@ export class InstanceController {
|
||||
events,
|
||||
qrcode,
|
||||
number,
|
||||
integration,
|
||||
token,
|
||||
chatwoot_account_id,
|
||||
chatwoot_token,
|
||||
@ -57,12 +61,16 @@ export class InstanceController {
|
||||
chatwoot_sign_msg,
|
||||
chatwoot_reopen_conversation,
|
||||
chatwoot_conversation_pending,
|
||||
chatwoot_import_contacts,
|
||||
chatwoot_import_messages,
|
||||
chatwoot_days_limit_import_messages,
|
||||
reject_call,
|
||||
msg_call,
|
||||
groups_ignore,
|
||||
always_online,
|
||||
read_messages,
|
||||
read_status,
|
||||
sync_full_history,
|
||||
websocket_enabled,
|
||||
websocket_events,
|
||||
rabbitmq_enabled,
|
||||
@ -76,7 +84,6 @@ export class InstanceController {
|
||||
typebot_delay_message,
|
||||
typebot_unknown_message,
|
||||
typebot_listening_from_me,
|
||||
proxy,
|
||||
}: InstanceDto) {
|
||||
try {
|
||||
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
||||
@ -84,8 +91,32 @@ export class InstanceController {
|
||||
this.logger.verbose('checking duplicate token');
|
||||
await this.authService.checkDuplicateToken(token);
|
||||
|
||||
if (!token && integration === Integration.WHATSAPP_BUSINESS) {
|
||||
throw new BadRequestException('token is required');
|
||||
}
|
||||
|
||||
this.logger.verbose('creating instance');
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
let instance: BaileysStartupService | BusinessStartupService;
|
||||
if (integration === Integration.WHATSAPP_BUSINESS) {
|
||||
instance = new BusinessStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
} else {
|
||||
instance = new BaileysStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
}
|
||||
|
||||
await this.waMonitor.saveInstance({ integration, instanceName, token, number });
|
||||
|
||||
instance.instanceName = instanceName;
|
||||
|
||||
const instanceId = v4();
|
||||
@ -142,6 +173,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -192,6 +225,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -239,6 +274,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -259,22 +296,6 @@ export class InstanceController {
|
||||
}
|
||||
}
|
||||
|
||||
if (proxy) {
|
||||
this.logger.verbose('creating proxy');
|
||||
try {
|
||||
this.proxyService.create(
|
||||
instance,
|
||||
{
|
||||
enabled: true,
|
||||
proxy,
|
||||
},
|
||||
false,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
let sqsEvents: string[];
|
||||
|
||||
if (sqs_enabled) {
|
||||
@ -302,6 +323,8 @@ export class InstanceController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -353,12 +376,30 @@ export class InstanceController {
|
||||
always_online: always_online || false,
|
||||
read_messages: read_messages || false,
|
||||
read_status: read_status || false,
|
||||
sync_full_history: sync_full_history ?? false,
|
||||
};
|
||||
|
||||
this.logger.verbose('settings: ' + JSON.stringify(settings));
|
||||
|
||||
this.settingsService.create(instance, settings);
|
||||
|
||||
let webhook_wa_business = null,
|
||||
access_token_wa_business = '';
|
||||
|
||||
if (integration === Integration.WHATSAPP_BUSINESS) {
|
||||
if (!number) {
|
||||
throw new BadRequestException('number is required');
|
||||
}
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
webhook_wa_business = `${urlServer}/webhook/whatsapp/${encodeURIComponent(instance.instanceName)}`;
|
||||
access_token_wa_business = this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK;
|
||||
}
|
||||
|
||||
this.integrationService.create(instance, {
|
||||
integration,
|
||||
number,
|
||||
token,
|
||||
});
|
||||
if (!chatwoot_account_id || !chatwoot_token || !chatwoot_url) {
|
||||
let getQrcode: wa.QrCode;
|
||||
|
||||
@ -373,6 +414,9 @@ export class InstanceController {
|
||||
instance: {
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
integration: integration,
|
||||
webhook_wa_business,
|
||||
access_token_wa_business,
|
||||
status: 'created',
|
||||
},
|
||||
hash,
|
||||
@ -406,7 +450,6 @@ export class InstanceController {
|
||||
},
|
||||
settings,
|
||||
qrcode: getQrcode,
|
||||
proxy,
|
||||
};
|
||||
|
||||
this.logger.verbose('instance created');
|
||||
@ -456,6 +499,9 @@ export class InstanceController {
|
||||
number,
|
||||
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||
conversation_pending: chatwoot_conversation_pending || false,
|
||||
import_contacts: chatwoot_import_contacts ?? true,
|
||||
import_messages: chatwoot_import_messages ?? true,
|
||||
days_limit_import_messages: chatwoot_days_limit_import_messages ?? 60,
|
||||
auto_create: true,
|
||||
});
|
||||
} catch (error) {
|
||||
@ -466,6 +512,9 @@ export class InstanceController {
|
||||
instance: {
|
||||
instanceName: instance.instanceName,
|
||||
instanceId: instanceId,
|
||||
integration: integration,
|
||||
webhook_wa_business,
|
||||
access_token_wa_business,
|
||||
status: 'created',
|
||||
},
|
||||
hash,
|
||||
@ -506,11 +555,13 @@ export class InstanceController {
|
||||
sign_msg: chatwoot_sign_msg || false,
|
||||
reopen_conversation: chatwoot_reopen_conversation || false,
|
||||
conversation_pending: chatwoot_conversation_pending || false,
|
||||
import_contacts: chatwoot_import_contacts ?? true,
|
||||
import_messages: chatwoot_import_messages ?? true,
|
||||
days_limit_import_messages: chatwoot_days_limit_import_messages || 60,
|
||||
number,
|
||||
name_inbox: instance.instanceName,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||
},
|
||||
proxy,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error.message[0]);
|
||||
@ -569,6 +620,7 @@ export class InstanceController {
|
||||
switch (state) {
|
||||
case 'open':
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
instance.clearCacheChatwoot();
|
||||
await instance.reloadConnection();
|
||||
await delay(2000);
|
||||
|
||||
@ -591,13 +643,13 @@ export class InstanceController {
|
||||
};
|
||||
}
|
||||
|
||||
public async fetchInstances({ instanceName, instanceId }: InstanceDto) {
|
||||
public async fetchInstances({ instanceName, instanceId, number }: InstanceDto) {
|
||||
if (instanceName) {
|
||||
this.logger.verbose('requested fetchInstances from ' + instanceName + ' instance');
|
||||
this.logger.verbose('instanceName: ' + instanceName);
|
||||
return this.waMonitor.instanceInfo(instanceName);
|
||||
} else if (instanceId) {
|
||||
return this.waMonitor.instanceInfoById(instanceId);
|
||||
} else if (instanceId || number) {
|
||||
return this.waMonitor.instanceInfoById(instanceId, number);
|
||||
}
|
||||
|
||||
this.logger.verbose('requested fetchInstances (all instances)');
|
||||
@ -613,11 +665,7 @@ export class InstanceController {
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
await this.waMonitor.waInstances[instanceName]?.client?.logout('Log out instance: ' + instanceName);
|
||||
|
||||
this.logger.verbose('close connection instance: ' + instanceName);
|
||||
this.waMonitor.waInstances[instanceName]?.client?.ws?.close();
|
||||
this.waMonitor.waInstances[instanceName]?.logoutInstance();
|
||||
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance logged out' } };
|
||||
} catch (error) {
|
||||
@ -634,6 +682,7 @@ export class InstanceController {
|
||||
}
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
||||
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
|
||||
|
||||
if (instance.state === 'connecting') {
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
@ -643,10 +692,15 @@ export class InstanceController {
|
||||
|
||||
this.logger.verbose('deleting instance: ' + instanceName);
|
||||
|
||||
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||
instanceName,
|
||||
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||
});
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||
instanceName,
|
||||
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
|
||||
delete this.waMonitor.waInstances[instanceName];
|
||||
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
|
||||
|
20
src/whatsapp/controllers/label.controller.ts
Normal file
20
src/whatsapp/controllers/label.controller.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { HandleLabelDto } from '../dto/label.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
|
||||
const logger = new Logger('LabelController');
|
||||
|
||||
export class LabelController {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async fetchLabels({ instanceName }: InstanceDto) {
|
||||
logger.verbose('requested fetchLabels from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].fetchLabels();
|
||||
}
|
||||
|
||||
public async handleLabel({ instanceName }: InstanceDto, data: HandleLabelDto) {
|
||||
logger.verbose('requested chat label change from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].handleLabel(data);
|
||||
}
|
||||
}
|
@ -1,19 +1,36 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException, NotFoundException } from '../../exceptions';
|
||||
import { makeProxyAgent } from '../../utils/makeProxyAgent';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ProxyDto } from '../dto/proxy.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { ProxyService } from '../services/proxy.service';
|
||||
|
||||
const logger = new Logger('ProxyController');
|
||||
|
||||
export class ProxyController {
|
||||
constructor(private readonly proxyService: ProxyService) {}
|
||||
constructor(private readonly proxyService: ProxyService, private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async createProxy(instance: InstanceDto, data: ProxyDto) {
|
||||
logger.verbose('requested createProxy from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
||||
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
|
||||
}
|
||||
|
||||
if (!data.enabled) {
|
||||
logger.verbose('proxy disabled');
|
||||
data.proxy = '';
|
||||
data.proxy = null;
|
||||
}
|
||||
|
||||
if (data.proxy) {
|
||||
const testProxy = await this.testProxy(data.proxy);
|
||||
if (!testProxy) {
|
||||
throw new BadRequestException('Invalid proxy');
|
||||
}
|
||||
logger.verbose('proxy enabled');
|
||||
}
|
||||
|
||||
return this.proxyService.create(instance, data);
|
||||
@ -21,6 +38,35 @@ export class ProxyController {
|
||||
|
||||
public async findProxy(instance: InstanceDto) {
|
||||
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
|
||||
|
||||
if (!this.waMonitor.waInstances[instance.instanceName]) {
|
||||
throw new NotFoundException(`The "${instance.instanceName}" instance does not exist`);
|
||||
}
|
||||
|
||||
return this.proxyService.find(instance);
|
||||
}
|
||||
|
||||
private async testProxy(proxy: ProxyDto['proxy']) {
|
||||
logger.verbose('requested testProxy');
|
||||
try {
|
||||
const serverIp = await axios.get('https://icanhazip.com/');
|
||||
const response = await axios.get('https://icanhazip.com/', {
|
||||
httpsAgent: makeProxyAgent(proxy),
|
||||
});
|
||||
|
||||
logger.verbose('[testProxy] from IP: ' + response?.data + ' To IP: ' + serverIp?.data);
|
||||
return response?.data !== serverIp?.data;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.data) {
|
||||
logger.error('testProxy error: ' + error.response.data);
|
||||
} else if (axios.isAxiosError(error)) {
|
||||
logger.error('testProxy error: ');
|
||||
logger.verbose(error.cause ?? error.message);
|
||||
} else {
|
||||
logger.error('testProxy error: ');
|
||||
logger.verbose(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ export class RabbitmqController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTemplateDto,
|
||||
SendTextDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
@ -28,6 +29,11 @@ export class SendMessageController {
|
||||
return await this.waMonitor.waInstances[instanceName].textMessage(data);
|
||||
}
|
||||
|
||||
public async sendTemplate({ instanceName }: InstanceDto, data: SendTemplateDto) {
|
||||
logger.verbose('requested sendList from ' + instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instanceName].templateMessage(data);
|
||||
}
|
||||
|
||||
public async sendMedia({ instanceName }: InstanceDto, data: SendMediaDto) {
|
||||
logger.verbose('requested sendMedia from ' + instanceName + ' instance');
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
// 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';
|
||||
|
@ -38,6 +38,8 @@ export class SqsController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
@ -4,12 +4,13 @@ import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { WebhookDto } from '../dto/webhook.dto';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { WebhookService } from '../services/webhook.service';
|
||||
|
||||
const logger = new Logger('WebhookController');
|
||||
|
||||
export class WebhookController {
|
||||
constructor(private readonly webhookService: WebhookService) {}
|
||||
constructor(private readonly webhookService: WebhookService, private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
public async createWebhook(instance: InstanceDto, data: WebhookDto) {
|
||||
logger.verbose('requested createWebhook from ' + instance.instanceName + ' instance');
|
||||
@ -46,6 +47,8 @@ export class WebhookController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
@ -61,4 +64,9 @@ export class WebhookController {
|
||||
logger.verbose('requested findWebhook from ' + instance.instanceName + ' instance');
|
||||
return this.webhookService.find(instance);
|
||||
}
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
return await this.waMonitor.waInstances[instance.instanceName].connectToWhatsapp(data);
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ export class WebsocketController {
|
||||
'GROUP_UPDATE',
|
||||
'GROUP_PARTICIPANTS_UPDATE',
|
||||
'CONNECTION_UPDATE',
|
||||
'LABELS_EDIT',
|
||||
'LABELS_ASSOCIATION',
|
||||
'CALL',
|
||||
'NEW_JWT_TOKEN',
|
||||
'TYPEBOT_START',
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { proto, WAPresence, 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 number: string,
|
||||
public readonly name?: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class getBase64FromMediaMessageDto {
|
||||
@ -26,8 +31,12 @@ export class NumberBusiness {
|
||||
message?: string;
|
||||
description?: string;
|
||||
email?: string;
|
||||
websites?: string[];
|
||||
website?: string[];
|
||||
address?: string;
|
||||
about?: string;
|
||||
vertical?: string;
|
||||
profilehandle?: string;
|
||||
}
|
||||
|
||||
export class ProfileNameDto {
|
||||
@ -100,3 +109,14 @@ export class SendPresenceDto extends Metadata {
|
||||
delay: number;
|
||||
};
|
||||
}
|
||||
|
||||
export class UpdateMessageDto extends Metadata {
|
||||
number: string;
|
||||
key: proto.IMessageKey;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class BlockUserDto {
|
||||
number: string;
|
||||
status: 'block' | 'unblock';
|
||||
}
|
||||
|
@ -9,5 +9,8 @@ export class ChatwootDto {
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
auto_create?: boolean;
|
||||
}
|
||||
|
@ -32,6 +32,10 @@ export class GroupInvite {
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export class AcceptGroupInvite {
|
||||
inviteCode: string;
|
||||
}
|
||||
|
||||
export class GroupSendInvite {
|
||||
groupJid: string;
|
||||
description: string;
|
||||
|
@ -3,6 +3,7 @@ export class InstanceDto {
|
||||
instanceId?: string;
|
||||
qrcode?: boolean;
|
||||
number?: string;
|
||||
integration?: string;
|
||||
token?: string;
|
||||
webhook?: string;
|
||||
webhook_by_events?: boolean;
|
||||
@ -14,12 +15,16 @@ export class InstanceDto {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
chatwoot_account_id?: string;
|
||||
chatwoot_token?: string;
|
||||
chatwoot_url?: string;
|
||||
chatwoot_sign_msg?: boolean;
|
||||
chatwoot_reopen_conversation?: boolean;
|
||||
chatwoot_conversation_pending?: boolean;
|
||||
chatwoot_import_contacts?: boolean;
|
||||
chatwoot_import_messages?: boolean;
|
||||
chatwoot_days_limit_import_messages?: number;
|
||||
websocket_enabled?: boolean;
|
||||
websocket_events?: string[];
|
||||
rabbitmq_enabled?: boolean;
|
||||
|
5
src/whatsapp/dto/integration.dto.ts
Normal file
5
src/whatsapp/dto/integration.dto.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class IntegrationDto {
|
||||
integration: string;
|
||||
number: string;
|
||||
token: string;
|
||||
}
|
12
src/whatsapp/dto/label.dto.ts
Normal file
12
src/whatsapp/dto/label.dto.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export class LabelDto {
|
||||
id?: string;
|
||||
name: string;
|
||||
color: number;
|
||||
predefinedId?: string;
|
||||
}
|
||||
|
||||
export class HandleLabelDto {
|
||||
number: string;
|
||||
labelId: string;
|
||||
action: 'add' | 'remove';
|
||||
}
|
@ -1,4 +1,12 @@
|
||||
class Proxy {
|
||||
host: string;
|
||||
port: string;
|
||||
protocol: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class ProxyDto {
|
||||
enabled: boolean;
|
||||
proxy: string;
|
||||
proxy: Proxy;
|
||||
}
|
||||
|
@ -142,6 +142,16 @@ export class ContactMessage {
|
||||
email?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export class TemplateMessage {
|
||||
name: string;
|
||||
language: string;
|
||||
components: any;
|
||||
}
|
||||
|
||||
export class SendTemplateDto extends Metadata {
|
||||
templateMessage: TemplateMessage;
|
||||
}
|
||||
export class SendContactDto extends Metadata {
|
||||
contactMessage: ContactMessage[];
|
||||
}
|
||||
|
@ -5,4 +5,5 @@ export class SettingsDto {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
}
|
||||
|
@ -7,12 +7,19 @@ export class ChatRaw {
|
||||
id?: string;
|
||||
owner: string;
|
||||
lastMsgTimestamp?: number;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
type ChatRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type ChatRawSelect = ChatRawBoolean<ChatRaw>;
|
||||
|
||||
const chatSchema = new Schema<ChatRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
id: { type: String, required: true, minlength: 1 },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
labels: { type: [String], default: [] },
|
||||
});
|
||||
|
||||
export const ChatModel = dbserver?.model(ChatRaw.name, chatSchema, 'chats');
|
||||
|
@ -14,6 +14,9 @@ export class ChatwootRaw {
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
}
|
||||
|
||||
const chatwootSchema = new Schema<ChatwootRaw>({
|
||||
@ -28,6 +31,9 @@ const chatwootSchema = new Schema<ChatwootRaw>({
|
||||
number: { type: String, required: true },
|
||||
reopen_conversation: { type: Boolean, required: true },
|
||||
conversation_pending: { type: Boolean, required: true },
|
||||
import_contacts: { type: Boolean, required: true },
|
||||
import_messages: { type: Boolean, required: true },
|
||||
days_limit_import_messages: { type: Number, required: true },
|
||||
});
|
||||
|
||||
export const ChatwootModel = dbserver?.model(ChatwootRaw.name, chatwootSchema, 'chatwoot');
|
||||
|
@ -10,6 +10,11 @@ export class ContactRaw {
|
||||
owner: string;
|
||||
}
|
||||
|
||||
type ContactRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type ContactRawSelect = ContactRawBoolean<ContactRaw>;
|
||||
|
||||
const contactSchema = new Schema<ContactRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
pushName: { type: String, minlength: 1 },
|
||||
|
@ -3,6 +3,8 @@ export * from './chamaai.model';
|
||||
export * from './chat.model';
|
||||
export * from './chatwoot.model';
|
||||
export * from './contact.model';
|
||||
export * from './integration.model';
|
||||
export * from './label.model';
|
||||
export * from './message.model';
|
||||
export * from './proxy.model';
|
||||
export * from './rabbitmq.model';
|
||||
|
20
src/whatsapp/models/integration.model.ts
Normal file
20
src/whatsapp/models/integration.model.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class IntegrationRaw {
|
||||
_id?: string;
|
||||
integration?: string;
|
||||
number?: string;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
const sqsSchema = new Schema<IntegrationRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
integration: { type: String, required: true },
|
||||
number: { type: String, required: true },
|
||||
token: { type: String, required: true },
|
||||
});
|
||||
|
||||
export const IntegrationModel = dbserver?.model(IntegrationRaw.name, sqsSchema, 'integration');
|
||||
export type IntegrationModel = typeof IntegrationModel;
|
29
src/whatsapp/models/label.model.ts
Normal file
29
src/whatsapp/models/label.model.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
export class LabelRaw {
|
||||
_id?: string;
|
||||
id?: string;
|
||||
owner: string;
|
||||
name: string;
|
||||
color: number;
|
||||
predefinedId?: string;
|
||||
}
|
||||
|
||||
type LabelRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type LabelRawSelect = LabelRawBoolean<LabelRaw>;
|
||||
|
||||
const labelSchema = new Schema<LabelRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
id: { type: String, required: true, minlength: 1 },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
name: { type: String, required: true, minlength: 1 },
|
||||
color: { type: Number, required: true, min: 0, max: 19 },
|
||||
predefinedId: { type: String },
|
||||
});
|
||||
|
||||
export const LabelModel = dbserver?.model(LabelRaw.name, labelSchema, 'labels');
|
||||
export type ILabelModel = typeof LabelModel;
|
@ -14,6 +14,7 @@ class ChatwootMessage {
|
||||
messageId?: number;
|
||||
inboxId?: number;
|
||||
conversationId?: number;
|
||||
contactInbox?: { sourceId: string };
|
||||
}
|
||||
|
||||
export class MessageRaw {
|
||||
@ -25,12 +26,20 @@ export class MessageRaw {
|
||||
messageType?: string;
|
||||
messageTimestamp?: number | Long.Long;
|
||||
owner: string;
|
||||
source?: 'android' | 'web' | 'ios';
|
||||
source?: 'android' | 'web' | 'ios' | 'unknown' | 'desktop';
|
||||
source_id?: string;
|
||||
source_reply_id?: string;
|
||||
chatwoot?: ChatwootMessage;
|
||||
contextInfo?: any;
|
||||
}
|
||||
|
||||
type MessageRawBoolean<T> = {
|
||||
[P in keyof T]?: 0 | 1;
|
||||
};
|
||||
export type MessageRawSelect = Omit<MessageRawBoolean<MessageRaw>, 'key'> & {
|
||||
key?: MessageRawBoolean<Key>;
|
||||
};
|
||||
|
||||
const messageSchema = new Schema<MessageRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
key: {
|
||||
@ -43,13 +52,14 @@ const messageSchema = new Schema<MessageRaw>({
|
||||
participant: { type: String },
|
||||
messageType: { type: String },
|
||||
message: { type: Object },
|
||||
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios'] },
|
||||
source: { type: String, minlength: 3, enum: ['android', 'web', 'ios', 'unknown', 'desktop'] },
|
||||
messageTimestamp: { type: Number, required: true },
|
||||
owner: { type: String, required: true, minlength: 1 },
|
||||
chatwoot: {
|
||||
messageId: { type: Number },
|
||||
inboxId: { type: Number },
|
||||
conversationId: { type: Number },
|
||||
contactInbox: { type: Object },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -2,16 +2,30 @@ import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
class Proxy {
|
||||
host?: string;
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class ProxyRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
proxy?: string;
|
||||
proxy?: Proxy;
|
||||
}
|
||||
|
||||
const proxySchema = new Schema<ProxyRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
proxy: { type: String, required: true },
|
||||
proxy: {
|
||||
host: { type: String, required: true },
|
||||
port: { type: String, required: true },
|
||||
protocol: { type: String, required: true },
|
||||
username: { type: String, required: false },
|
||||
password: { type: String, required: false },
|
||||
},
|
||||
});
|
||||
|
||||
export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy');
|
||||
|
@ -10,6 +10,7 @@ export class SettingsRaw {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
}
|
||||
|
||||
const settingsSchema = new Schema<SettingsRaw>({
|
||||
@ -20,6 +21,7 @@ const settingsSchema = new Schema<SettingsRaw>({
|
||||
always_online: { type: Boolean, required: true },
|
||||
read_messages: { type: Boolean, required: true },
|
||||
read_status: { type: Boolean, required: true },
|
||||
sync_full_history: { type: Boolean, required: true },
|
||||
});
|
||||
|
||||
export const SettingsModel = dbserver?.model(SettingsRaw.name, settingsSchema, 'settings');
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { AUTH_DIR } from '../../config/path.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { AuthRaw, IAuthModel } from '../models';
|
||||
import { AuthRaw, IAuthModel, IntegrationModel } from '../models';
|
||||
|
||||
export class AuthRepository extends Repository {
|
||||
constructor(private readonly authModel: IAuthModel, readonly configService: ConfigService) {
|
||||
constructor(
|
||||
private readonly authModel: IAuthModel,
|
||||
private readonly integrationModel: IntegrationModel,
|
||||
readonly configService: ConfigService,
|
||||
) {
|
||||
super(configService);
|
||||
this.auth = configService.get<Auth>('AUTHENTICATION');
|
||||
}
|
||||
@ -64,6 +68,37 @@ export class AuthRepository extends Repository {
|
||||
}
|
||||
}
|
||||
|
||||
public async list(): Promise<AuthRaw[]> {
|
||||
try {
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('listing auth in db');
|
||||
return await this.authModel.find();
|
||||
}
|
||||
|
||||
this.logger.verbose('listing auth in store');
|
||||
|
||||
const auths: AuthRaw[] = [];
|
||||
const openDir = opendirSync(join(AUTH_DIR, this.auth.TYPE), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
auths.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(AUTH_DIR, this.auth.TYPE, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return auths;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async findInstanceNameById(instanceId: string): Promise<string | null> {
|
||||
try {
|
||||
this.logger.verbose('finding auth by instanceId');
|
||||
@ -79,4 +114,22 @@ export class AuthRepository extends Repository {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async findInstanceNameByNumber(number: string): Promise<string | null> {
|
||||
try {
|
||||
this.logger.verbose('finding auth by number');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding auth in db');
|
||||
const instance = await this.integrationModel.findOne({ number });
|
||||
|
||||
const response = await this.authModel.findOne({ _id: instance._id });
|
||||
|
||||
return response._id;
|
||||
}
|
||||
|
||||
this.logger.verbose('finding auth in store is not supported');
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,10 @@ 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';
|
||||
import { ChatRaw, ChatRawSelect, IChatModel } from '../models';
|
||||
|
||||
export class ChatQuery {
|
||||
select?: ChatRawSelect;
|
||||
where: ChatRaw;
|
||||
}
|
||||
|
||||
@ -69,7 +70,7 @@ export class ChatRepository extends Repository {
|
||||
this.logger.verbose('finding chats');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding chats in db');
|
||||
return await this.chatModel.find({ owner: query.where.owner });
|
||||
return await this.chatModel.find({ owner: query.where.owner }).select(query.select ?? {});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding chats in store');
|
||||
@ -114,4 +115,63 @@ export class ChatRepository extends Repository {
|
||||
return { error: error?.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
public async update(data: ChatRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('updating chats');
|
||||
|
||||
if (data.length === 0) {
|
||||
this.logger.verbose('no chats to update');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('updating chats in db');
|
||||
|
||||
const chats = data.map((chat) => {
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { id: chat.id },
|
||||
update: { ...chat },
|
||||
upsert: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { nModified } = await this.chatModel.bulkWrite(chats);
|
||||
|
||||
this.logger.verbose('chats updated in db: ' + nModified + ' chats');
|
||||
return { insertCount: nModified };
|
||||
}
|
||||
|
||||
this.logger.verbose('updating chats in store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.CONTACTS) {
|
||||
this.logger.verbose('updating chats in store');
|
||||
data.forEach((chat) => {
|
||||
this.writeStore({
|
||||
path: join(this.storePath, 'chats', instanceName),
|
||||
fileName: chat.id,
|
||||
data: chat,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'chats updated in store in path: ' + join(this.storePath, 'chats', instanceName) + '/' + chat.id,
|
||||
);
|
||||
});
|
||||
|
||||
this.logger.verbose('chats updated in store: ' + data.length + ' chats');
|
||||
|
||||
return { insertCount: data.length };
|
||||
}
|
||||
|
||||
this.logger.verbose('chats not updated');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,18 @@ import { join } from 'path';
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ContactRaw, IContactModel } from '../models';
|
||||
import { ContactRaw, ContactRawSelect, IContactModel } from '../models';
|
||||
|
||||
export class ContactQuery {
|
||||
select?: ContactRawSelect;
|
||||
where: ContactRaw;
|
||||
}
|
||||
|
||||
export class ContactQueryMany {
|
||||
owner: ContactRaw['owner'];
|
||||
ids: ContactRaw['id'][];
|
||||
}
|
||||
|
||||
export class ContactRepository extends Repository {
|
||||
constructor(private readonly contactModel: IContactModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
@ -129,7 +135,7 @@ export class ContactRepository extends Repository {
|
||||
this.logger.verbose('finding contacts');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding contacts in db');
|
||||
return await this.contactModel.find({ ...query.where });
|
||||
return await this.contactModel.find({ ...query.where }).select(query.select ?? {});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding contacts in store');
|
||||
@ -168,4 +174,54 @@ export class ContactRepository extends Repository {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async findManyById(query: ContactQueryMany): Promise<ContactRaw[]> {
|
||||
try {
|
||||
this.logger.verbose('finding contacts');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding contacts in db');
|
||||
return await this.contactModel.find({
|
||||
owner: query.owner,
|
||||
id: { $in: query.ids },
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding contacts in store');
|
||||
const contacts: ContactRaw[] = [];
|
||||
if (query.ids.length > 0) {
|
||||
this.logger.verbose('finding contacts in store by id');
|
||||
query.ids.forEach((id) => {
|
||||
contacts.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'contacts', query.owner, id + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.logger.verbose('finding contacts in store by owner');
|
||||
|
||||
const openDir = opendirSync(join(this.storePath, 'contacts', query.owner), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
contacts.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'contacts', query.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('contacts found in store: ' + contacts.length + ' contacts');
|
||||
return contacts;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
64
src/whatsapp/repository/integration.repository.ts
Normal file
64
src/whatsapp/repository/integration.repository.ts
Normal file
@ -0,0 +1,64 @@
|
||||
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 { IntegrationModel, IntegrationRaw } from '../models';
|
||||
|
||||
export class IntegrationRepository extends Repository {
|
||||
constructor(private readonly integrationModel: IntegrationModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('IntegrationRepository');
|
||||
|
||||
public async create(data: IntegrationRaw, instance: string): Promise<IInsert> {
|
||||
try {
|
||||
this.logger.verbose('creating integration');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('saving integration to db');
|
||||
const insert = await this.integrationModel.replaceOne({ _id: instance }, { ...data }, { upsert: true });
|
||||
|
||||
this.logger.verbose('integration saved to db: ' + insert.modifiedCount + ' integration');
|
||||
return { insertCount: insert.modifiedCount };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving integration to store');
|
||||
|
||||
this.writeStore<IntegrationRaw>({
|
||||
path: join(this.storePath, 'integration'),
|
||||
fileName: instance,
|
||||
data,
|
||||
});
|
||||
|
||||
this.logger.verbose(
|
||||
'integration saved to store in path: ' + join(this.storePath, 'integration') + '/' + instance,
|
||||
);
|
||||
|
||||
this.logger.verbose('integration created');
|
||||
return { insertCount: 1 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(instance: string): Promise<IntegrationRaw> {
|
||||
try {
|
||||
this.logger.verbose('finding integration');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding integration in db');
|
||||
return await this.integrationModel.findOne({ _id: instance });
|
||||
}
|
||||
|
||||
this.logger.verbose('finding integration in store');
|
||||
return JSON.parse(
|
||||
readFileSync(join(this.storePath, 'integration', instance + '.json'), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
) as IntegrationRaw;
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
111
src/whatsapp/repository/label.repository.ts
Normal file
111
src/whatsapp/repository/label.repository.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { ILabelModel, LabelRaw, LabelRawSelect } from '../models';
|
||||
|
||||
export class LabelQuery {
|
||||
select?: LabelRawSelect;
|
||||
where: Partial<LabelRaw>;
|
||||
}
|
||||
|
||||
export class LabelRepository extends Repository {
|
||||
constructor(private readonly labelModel: ILabelModel, private readonly configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
private readonly logger = new Logger('LabelRepository');
|
||||
|
||||
public async insert(data: LabelRaw, instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
this.logger.verbose('inserting labels');
|
||||
|
||||
try {
|
||||
if (this.dbSettings.ENABLED && saveDb) {
|
||||
this.logger.verbose('saving labels to db');
|
||||
const insert = await this.labelModel.findOneAndUpdate({ id: data.id }, data, { upsert: true });
|
||||
|
||||
this.logger.verbose(`label ${data.name} saved to db`);
|
||||
return { insertCount: Number(!!insert._id) };
|
||||
}
|
||||
|
||||
this.logger.verbose('saving label to store');
|
||||
|
||||
const store = this.configService.get<StoreConf>('STORE');
|
||||
|
||||
if (store.LABELS) {
|
||||
this.logger.verbose('saving label to store');
|
||||
this.writeStore<LabelRaw>({
|
||||
path: join(this.storePath, 'labels', instanceName),
|
||||
fileName: data.id,
|
||||
data,
|
||||
});
|
||||
this.logger.verbose(
|
||||
'labels saved to store in path: ' + join(this.storePath, 'labels', instanceName) + '/' + data.id,
|
||||
);
|
||||
|
||||
this.logger.verbose(`label ${data.name} saved to store`);
|
||||
return { insertCount: 1 };
|
||||
}
|
||||
|
||||
this.logger.verbose('labels not saved to store');
|
||||
return { insertCount: 0 };
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
data = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(query: LabelQuery): Promise<LabelRaw[]> {
|
||||
try {
|
||||
this.logger.verbose('finding labels');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding labels in db');
|
||||
return await this.labelModel.find({ owner: query.where.owner }).select(query.select ?? {});
|
||||
}
|
||||
|
||||
this.logger.verbose('finding labels in store');
|
||||
|
||||
const labels: LabelRaw[] = [];
|
||||
const openDir = opendirSync(join(this.storePath, 'labels', query.where.owner));
|
||||
for await (const dirent of openDir) {
|
||||
if (dirent.isFile()) {
|
||||
labels.push(
|
||||
JSON.parse(
|
||||
readFileSync(join(this.storePath, 'labels', query.where.owner, dirent.name), {
|
||||
encoding: 'utf-8',
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('labels found in store: ' + labels.length + ' labels');
|
||||
return labels;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(query: LabelQuery) {
|
||||
try {
|
||||
this.logger.verbose('deleting labels');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('deleting labels in db');
|
||||
return await this.labelModel.deleteOne({ ...query.where });
|
||||
}
|
||||
|
||||
this.logger.verbose('deleting labels in store');
|
||||
rmSync(join(this.storePath, 'labels', query.where.owner, query.where.id + '.josn'), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
return { deleted: { labelId: query.where.id } };
|
||||
} catch (error) {
|
||||
return { error: error?.toString() };
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { IInsert, Repository } from '../abstract/abstract.repository';
|
||||
import { IMessageModel, MessageRaw } from '../models';
|
||||
import { IMessageModel, MessageRaw, MessageRawSelect } from '../models';
|
||||
|
||||
export class MessageQuery {
|
||||
select?: MessageRawSelect;
|
||||
where: MessageRaw;
|
||||
limit?: number;
|
||||
}
|
||||
@ -18,6 +19,28 @@ export class MessageRepository extends Repository {
|
||||
|
||||
private readonly logger = new Logger('MessageRepository');
|
||||
|
||||
public buildQuery(query: MessageQuery): MessageQuery {
|
||||
for (const [o, p] of Object.entries(query?.where || {})) {
|
||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
query.where[`${o}.${k}`] = v;
|
||||
}
|
||||
delete query.where[o];
|
||||
}
|
||||
}
|
||||
|
||||
for (const [o, p] of Object.entries(query?.select || {})) {
|
||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
query.select[`${o}.${k}`] = v;
|
||||
}
|
||||
delete query.select[o];
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
this.logger.verbose('inserting messages');
|
||||
|
||||
@ -91,14 +114,7 @@ export class MessageRepository extends Repository {
|
||||
this.logger.verbose('finding messages');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding messages in db');
|
||||
for (const [o, p] of Object.entries(query?.where)) {
|
||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
query.where[`${o}.${k}`] = v;
|
||||
}
|
||||
delete query.where[o];
|
||||
}
|
||||
}
|
||||
query = this.buildQuery(query);
|
||||
|
||||
return await this.messageModel
|
||||
.find({ ...query.where })
|
||||
@ -143,6 +159,7 @@ export class MessageRepository extends Repository {
|
||||
})
|
||||
.splice(0, query?.limit ?? messages.length);
|
||||
} catch (error) {
|
||||
this.logger.error(`error on message find: ${error.toString()}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@ -197,4 +214,26 @@ export class MessageRepository extends Repository {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(query: MessageQuery) {
|
||||
try {
|
||||
this.logger.verbose('deleting message');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('deleting message in db');
|
||||
query = this.buildQuery(query);
|
||||
|
||||
return await this.messageModel.deleteOne({ ...query.where });
|
||||
}
|
||||
|
||||
this.logger.verbose('deleting message in store');
|
||||
rmSync(join(this.storePath, 'messages', query.where.owner, query.where.key.id + '.json'), {
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
return { deleted: { messageId: query.where.key.id } };
|
||||
} catch (error) {
|
||||
return { error: error?.toString() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import { ChamaaiRepository } from './chamaai.repository';
|
||||
import { ChatRepository } from './chat.repository';
|
||||
import { ChatwootRepository } from './chatwoot.repository';
|
||||
import { ContactRepository } from './contact.repository';
|
||||
import { IntegrationRepository } from './integration.repository';
|
||||
import { LabelRepository } from './label.repository';
|
||||
import { MessageRepository } from './message.repository';
|
||||
import { MessageUpRepository } from './messageUp.repository';
|
||||
import { ProxyRepository } from './proxy.repository';
|
||||
@ -33,7 +35,9 @@ export class RepositoryBroker {
|
||||
public readonly typebot: TypebotRepository,
|
||||
public readonly proxy: ProxyRepository,
|
||||
public readonly chamaai: ChamaaiRepository,
|
||||
public readonly integration: IntegrationRepository,
|
||||
public readonly auth: AuthRepository,
|
||||
public readonly labels: LabelRepository,
|
||||
private configService: ConfigService,
|
||||
dbServer?: MongoClient,
|
||||
) {
|
||||
@ -69,6 +73,7 @@ export class RepositoryBroker {
|
||||
const typebotDir = join(storePath, 'typebot');
|
||||
const proxyDir = join(storePath, 'proxy');
|
||||
const chamaaiDir = join(storePath, 'chamaai');
|
||||
const integrationDir = join(storePath, 'integration');
|
||||
const tempDir = join(storePath, 'temp');
|
||||
|
||||
if (!fs.existsSync(authDir)) {
|
||||
@ -127,6 +132,10 @@ export class RepositoryBroker {
|
||||
this.logger.verbose('creating chamaai dir: ' + chamaaiDir);
|
||||
fs.mkdirSync(chamaaiDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(integrationDir)) {
|
||||
this.logger.verbose('creating integration dir: ' + integrationDir);
|
||||
fs.mkdirSync(integrationDir, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
this.logger.verbose('creating temp dir: ' + tempDir);
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
@ -3,6 +3,7 @@ import { RequestHandler, Router } from 'express';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
archiveChatSchema,
|
||||
blockUserSchema,
|
||||
contactValidateSchema,
|
||||
deleteMessageSchema,
|
||||
messageUpSchema,
|
||||
@ -14,11 +15,13 @@ import {
|
||||
profileSchema,
|
||||
profileStatusSchema,
|
||||
readMessageSchema,
|
||||
updateMessageSchema,
|
||||
whatsappNumberSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
ArchiveChatDto,
|
||||
BlockUserDto,
|
||||
DeleteMessage,
|
||||
getBase64FromMediaMessageDto,
|
||||
NumberDto,
|
||||
@ -28,6 +31,7 @@ import {
|
||||
ProfileStatusDto,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
} from '../dto/chat.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
@ -58,7 +62,7 @@ export class ChatRouter extends RouterBroker {
|
||||
execute: (instance, data) => chatController.whatsappNumber(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('markMessageAsRead'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in markMessageAsRead');
|
||||
@ -365,6 +369,40 @@ export class ChatRouter extends RouterBroker {
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateMessage'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateMessage');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<UpdateMessageDto>({
|
||||
request: req,
|
||||
schema: updateMessageSchema,
|
||||
ClassRef: UpdateMessageDto,
|
||||
execute: (instance, data) => chatController.updateMessage(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('updateBlockStatus'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in updateBlockStatus');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<BlockUserDto>({
|
||||
request: req,
|
||||
schema: blockUserSchema,
|
||||
ClassRef: BlockUserDto,
|
||||
execute: (instance, data) => chatController.blockUser(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import { chatwootSchema, instanceNameSchema } from '../../validate/validate.sche
|
||||
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';
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import {
|
||||
AcceptGroupInviteSchema,
|
||||
createGroupSchema,
|
||||
getParticipantsSchema,
|
||||
groupInviteSchema,
|
||||
@ -16,6 +17,7 @@ import {
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import {
|
||||
AcceptGroupInvite,
|
||||
CreateGroupDto,
|
||||
GetParticipant,
|
||||
GroupDescriptionDto,
|
||||
@ -182,6 +184,22 @@ export class GroupRouter extends RouterBroker {
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('acceptInviteCode'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in acceptInviteCode');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.inviteCodeValidate<AcceptGroupInvite>({
|
||||
request: req,
|
||||
schema: AcceptGroupInviteSchema,
|
||||
ClassRef: AcceptGroupInvite,
|
||||
execute: (instance, data) => groupController.acceptInviteCode(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendInvite'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendInvite');
|
||||
logger.verbose('request body: ');
|
||||
|
@ -9,6 +9,7 @@ import { ChatRouter } from './chat.router';
|
||||
import { ChatwootRouter } from './chatwoot.router';
|
||||
import { GroupRouter } from './group.router';
|
||||
import { InstanceRouter } from './instance.router';
|
||||
import { LabelRouter } from './label.router';
|
||||
import { ProxyRouter } from './proxy.router';
|
||||
import { RabbitmqRouter } from './rabbitmq.router';
|
||||
import { MessageRouter } from './sendMessage.router';
|
||||
@ -53,7 +54,7 @@ router
|
||||
.use('/message', new MessageRouter(...guards).router)
|
||||
.use('/chat', new ChatRouter(...guards).router)
|
||||
.use('/group', new GroupRouter(...guards).router)
|
||||
.use('/webhook', new WebhookRouter(...guards).router)
|
||||
.use('/webhook', new WebhookRouter(configService, ...guards).router)
|
||||
.use('/chatwoot', new ChatwootRouter(...guards).router)
|
||||
.use('/settings', new SettingsRouter(...guards).router)
|
||||
.use('/websocket', new WebsocketRouter(...guards).router)
|
||||
@ -61,6 +62,7 @@ router
|
||||
.use('/sqs', new SqsRouter(...guards).router)
|
||||
.use('/typebot', new TypebotRouter(...guards).router)
|
||||
.use('/proxy', new ProxyRouter(...guards).router)
|
||||
.use('/chamaai', new ChamaaiRouter(...guards).router);
|
||||
.use('/chamaai', new ChamaaiRouter(...guards).router)
|
||||
.use('/label', new LabelRouter(...guards).router);
|
||||
|
||||
export { HttpStatus, router };
|
||||
|
53
src/whatsapp/routers/label.router.ts
Normal file
53
src/whatsapp/routers/label.router.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { handleLabelSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
import { HandleLabelDto, LabelDto } from '../dto/label.dto';
|
||||
import { labelController } from '../whatsapp.module';
|
||||
import { HttpStatus } from './index.router';
|
||||
|
||||
const logger = new Logger('LabelRouter');
|
||||
|
||||
export class LabelRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.get(this.routerPath('findLabels'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in findLabels');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<LabelDto>({
|
||||
request: req,
|
||||
schema: null,
|
||||
ClassRef: LabelDto,
|
||||
execute: (instance) => labelController.fetchLabels(instance),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.put(this.routerPath('handleLabel'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in handleLabel');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
|
||||
const response = await this.dataValidate<HandleLabelDto>({
|
||||
request: req,
|
||||
schema: handleLabelSchema,
|
||||
ClassRef: HandleLabelDto,
|
||||
execute: (instance, data) => labelController.handleLabel(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router = Router();
|
||||
}
|
@ -12,6 +12,7 @@ import {
|
||||
reactionMessageSchema,
|
||||
statusMessageSchema,
|
||||
stickerMessageSchema,
|
||||
templateMessageSchema,
|
||||
textMessageSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
@ -26,6 +27,7 @@ import {
|
||||
SendReactionDto,
|
||||
SendStatusDto,
|
||||
SendStickerDto,
|
||||
SendTemplateDto,
|
||||
SendTextDto,
|
||||
} from '../dto/sendMessage.dto';
|
||||
import { sendMessageController } from '../whatsapp.module';
|
||||
@ -85,6 +87,22 @@ export class MessageRouter extends RouterBroker {
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendTemplate'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendTemplate');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<SendTemplateDto>({
|
||||
request: req,
|
||||
schema: templateMessageSchema,
|
||||
ClassRef: SendTemplateDto,
|
||||
execute: (instance, data) => sendMessageController.sendTemplate(instance, data),
|
||||
});
|
||||
|
||||
return res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('sendButtons'), ...guards, async (req, res) => {
|
||||
logger.verbose('request received in sendButtons');
|
||||
logger.verbose('request body: ');
|
||||
|
@ -5,7 +5,6 @@ import { instanceNameSchema, settingsSchema } from '../../validate/validate.sche
|
||||
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';
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
import { ConfigService, WaBusiness } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { instanceNameSchema, webhookSchema } from '../../validate/validate.schema';
|
||||
import { RouterBroker } from '../abstract/abstract.router';
|
||||
@ -11,7 +12,7 @@ import { HttpStatus } from './index.router';
|
||||
const logger = new Logger('WebhookRouter');
|
||||
|
||||
export class WebhookRouter extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
constructor(readonly configService: ConfigService, ...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('set'), ...guards, async (req, res) => {
|
||||
@ -45,6 +46,31 @@ export class WebhookRouter extends RouterBroker {
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.post(this.routerPath('whatsapp'), async (req, res) => {
|
||||
logger.verbose('request received in webhook');
|
||||
logger.verbose('request body: ');
|
||||
logger.verbose(req.body);
|
||||
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
const response = await this.dataValidate<InstanceDto>({
|
||||
request: req,
|
||||
schema: instanceNameSchema,
|
||||
ClassRef: InstanceDto,
|
||||
execute: (instance, data) => webhookController.receiveWebhook(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
})
|
||||
.get(this.routerPath('whatsapp'), async (req, res) => {
|
||||
logger.verbose('request received in webhook');
|
||||
logger.verbose('request query: ');
|
||||
logger.verbose(req.query);
|
||||
if (req.query['hub.verify_token'] === this.configService.get<WaBusiness>('WA_BUSINESS').TOKEN_WEBHOOK)
|
||||
res.send(req.query['hub.challenge']);
|
||||
else res.send('Error, wrong validation token');
|
||||
logger.verbose('Error, wrong validation token');
|
||||
});
|
||||
}
|
||||
|
||||
|
62
src/whatsapp/services/cache.service.ts
Normal file
62
src/whatsapp/services/cache.service.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { ICache } from '../abstract/abstract.cache';
|
||||
|
||||
export class CacheService {
|
||||
private readonly logger = new Logger(CacheService.name);
|
||||
|
||||
constructor(private readonly cache: ICache) {
|
||||
if (cache) {
|
||||
this.logger.verbose(`cacheservice created using cache engine: ${cache.constructor?.name}`);
|
||||
} else {
|
||||
this.logger.verbose(`cacheservice disabled`);
|
||||
}
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice getting key: ${key}`);
|
||||
return this.cache.get(key);
|
||||
}
|
||||
|
||||
async set(key: string, value: any) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice setting key: ${key}`);
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
||||
async has(key: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice has key: ${key}`);
|
||||
return this.cache.has(key);
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice deleting key: ${key}`);
|
||||
return this.cache.delete(key);
|
||||
}
|
||||
|
||||
async deleteAll(appendCriteria?: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice deleting all keys`);
|
||||
return this.cache.deleteAll(appendCriteria);
|
||||
}
|
||||
|
||||
async keys(appendCriteria?: string) {
|
||||
if (!this.cache) {
|
||||
return;
|
||||
}
|
||||
this.logger.verbose(`cacheservice getting all keys`);
|
||||
return this.cache.keys(appendCriteria);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
33
src/whatsapp/services/integration.service.ts
Normal file
33
src/whatsapp/services/integration.service.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { IntegrationDto } from '../dto/integration.dto';
|
||||
import { IntegrationRaw } from '../models';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class IntegrationService {
|
||||
constructor(private readonly waMonitor: WAMonitoringService) {}
|
||||
|
||||
private readonly logger = new Logger(IntegrationService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: IntegrationDto) {
|
||||
this.logger.verbose('create integration: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setIntegration(data);
|
||||
|
||||
return { integration: { ...instance, integration: data } };
|
||||
}
|
||||
|
||||
public async find(instance: InstanceDto): Promise<IntegrationRaw> {
|
||||
try {
|
||||
this.logger.verbose('find integration: ' + instance.instanceName);
|
||||
const result = await this.waMonitor.waInstances[instance.instanceName].findIntegration();
|
||||
|
||||
if (Object.keys(result).length === 0) {
|
||||
throw new Error('Integration not found');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { integration: '', number: '', token: '' };
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,21 @@
|
||||
import { execSync } from 'child_process';
|
||||
import EventEmitter2 from 'eventemitter2';
|
||||
import { opendirSync, readdirSync, rmSync } from 'fs';
|
||||
import { existsSync, mkdirSync, opendirSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
||||
import { Db } from 'mongodb';
|
||||
import { Collection } from 'mongoose';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Auth, ConfigService, Database, DelInstance, HttpServer, Redis } from '../../config/env.config';
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { INSTANCE_DIR, STORE_DIR } from '../../config/path.config';
|
||||
import { NotFoundException } from '../../exceptions';
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
import { RedisCache } from '../../libs/redis.client';
|
||||
import {
|
||||
AuthModel,
|
||||
ChamaaiModel,
|
||||
// ChatModel,
|
||||
ChatwootModel,
|
||||
// ContactModel,
|
||||
// MessageModel,
|
||||
// MessageUpModel,
|
||||
ContactModel,
|
||||
LabelModel,
|
||||
ProxyModel,
|
||||
RabbitmqModel,
|
||||
SettingsModel,
|
||||
@ -26,7 +24,10 @@ import {
|
||||
WebsocketModel,
|
||||
} from '../models';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { WAStartupService } from './whatsapp.service';
|
||||
import { Integration } from '../types/wa.types';
|
||||
import { CacheService } from './cache.service';
|
||||
import { BaileysStartupService } from './whatsapp.baileys.service';
|
||||
import { BusinessStartupService } from './whatsapp.business.service';
|
||||
|
||||
export class WAMonitoringService {
|
||||
constructor(
|
||||
@ -34,12 +35,12 @@ export class WAMonitoringService {
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
private readonly cache: RedisCache,
|
||||
private readonly chatwootCache: CacheService,
|
||||
) {
|
||||
this.logger.verbose('instance created');
|
||||
|
||||
this.removeInstance();
|
||||
this.noConnection();
|
||||
// this.delInstanceFiles();
|
||||
|
||||
Object.assign(this.db, configService.get<Database>('DATABASE'));
|
||||
Object.assign(this.redis, configService.get<Redis>('REDIS'));
|
||||
@ -54,10 +55,8 @@ export class WAMonitoringService {
|
||||
|
||||
private dbInstance: Db;
|
||||
|
||||
private dbStore = dbserver;
|
||||
|
||||
private readonly logger = new Logger(WAMonitoringService.name);
|
||||
public readonly waInstances: Record<string, WAStartupService> = {};
|
||||
public readonly waInstances: Record<string, BaileysStartupService | BusinessStartupService> = {};
|
||||
|
||||
public delInstanceTime(instance: string) {
|
||||
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
||||
@ -67,9 +66,11 @@ export class WAMonitoringService {
|
||||
setTimeout(async () => {
|
||||
if (this.waInstances[instance]?.connectionStatus?.state !== 'open') {
|
||||
if (this.waInstances[instance]?.connectionStatus?.state === 'connecting') {
|
||||
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
||||
this.waInstances[instance]?.client?.ws?.close();
|
||||
this.waInstances[instance]?.client?.end(undefined);
|
||||
if ((await this.waInstances[instance].findIntegration()).integration === Integration.WHATSAPP_BAILEYS) {
|
||||
await this.waInstances[instance]?.client?.logout('Log out instance: ' + instance);
|
||||
this.waInstances[instance]?.client?.ws?.close();
|
||||
this.waInstances[instance]?.client?.end(undefined);
|
||||
}
|
||||
this.waInstances[instance]?.removeRabbitmqQueues();
|
||||
delete this.waInstances[instance];
|
||||
} else {
|
||||
@ -106,6 +107,16 @@ export class WAMonitoringService {
|
||||
};
|
||||
}
|
||||
|
||||
const findIntegration = await this.waInstances[key].findIntegration();
|
||||
|
||||
let integration: any;
|
||||
if (findIntegration) {
|
||||
integration = {
|
||||
...findIntegration,
|
||||
webhook_wa_business: `${urlServer}/webhook/whatsapp/${encodeURIComponent(key)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (value.connectionStatus.state === 'open') {
|
||||
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
|
||||
|
||||
@ -127,6 +138,8 @@ export class WAMonitoringService {
|
||||
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
|
||||
|
||||
instanceData.instance['chatwoot'] = chatwoot;
|
||||
|
||||
instanceData.instance['integration'] = integration;
|
||||
}
|
||||
|
||||
instances.push(instanceData);
|
||||
@ -147,6 +160,8 @@ export class WAMonitoringService {
|
||||
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
|
||||
|
||||
instanceData.instance['chatwoot'] = chatwoot;
|
||||
|
||||
instanceData.instance['integration'] = integration;
|
||||
}
|
||||
|
||||
instances.push(instanceData);
|
||||
@ -159,9 +174,21 @@ export class WAMonitoringService {
|
||||
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
|
||||
}
|
||||
|
||||
public async instanceInfoById(instanceId?: string) {
|
||||
public async instanceInfoById(instanceId?: string, number?: string) {
|
||||
this.logger.verbose('get instance info');
|
||||
const instanceName = await this.repository.auth.findInstanceNameById(instanceId);
|
||||
let instanceName: string;
|
||||
if (instanceId) {
|
||||
instanceName = await this.repository.auth.findInstanceNameById(instanceId);
|
||||
if (!instanceName) {
|
||||
throw new NotFoundException(`Instance "${instanceId}" not found`);
|
||||
}
|
||||
} else if (number) {
|
||||
instanceName = await this.repository.auth.findInstanceNameByNumber(number);
|
||||
if (!instanceName) {
|
||||
throw new NotFoundException(`Instance "${number}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!instanceName) {
|
||||
throw new NotFoundException(`Instance "${instanceId}" not found`);
|
||||
}
|
||||
@ -170,75 +197,7 @@ export class WAMonitoringService {
|
||||
throw new NotFoundException(`Instance "${instanceName}" not found`);
|
||||
}
|
||||
|
||||
const instances: any[] = [];
|
||||
|
||||
for await (const [key, value] of Object.entries(this.waInstances)) {
|
||||
if (value) {
|
||||
this.logger.verbose('get instance info: ' + key);
|
||||
let chatwoot: any;
|
||||
|
||||
const urlServer = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
const findChatwoot = await this.waInstances[key].findChatwoot();
|
||||
|
||||
if (findChatwoot && findChatwoot.enabled) {
|
||||
chatwoot = {
|
||||
...findChatwoot,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(key)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (value.connectionStatus.state === 'open') {
|
||||
this.logger.verbose('instance: ' + key + ' - connectionStatus: open');
|
||||
|
||||
const instanceData = {
|
||||
instance: {
|
||||
instanceName: key,
|
||||
instanceId: (await this.repository.auth.find(key))?.instanceId,
|
||||
owner: value.wuid,
|
||||
profileName: (await value.getProfileName()) || 'not loaded',
|
||||
profilePictureUrl: value.profilePictureUrl,
|
||||
profileStatus: (await value.getProfileStatus()) || '',
|
||||
status: value.connectionStatus.state,
|
||||
},
|
||||
};
|
||||
|
||||
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
|
||||
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
|
||||
|
||||
instanceData.instance['chatwoot'] = chatwoot;
|
||||
}
|
||||
|
||||
instances.push(instanceData);
|
||||
} else {
|
||||
this.logger.verbose('instance: ' + key + ' - connectionStatus: ' + value.connectionStatus.state);
|
||||
|
||||
const instanceData = {
|
||||
instance: {
|
||||
instanceName: key,
|
||||
instanceId: (await this.repository.auth.find(key))?.instanceId,
|
||||
status: value.connectionStatus.state,
|
||||
},
|
||||
};
|
||||
|
||||
if (this.configService.get<Auth>('AUTHENTICATION').EXPOSE_IN_FETCH_INSTANCES) {
|
||||
instanceData.instance['serverUrl'] = this.configService.get<HttpServer>('SERVER').URL;
|
||||
|
||||
instanceData.instance['apikey'] = (await this.repository.auth.find(key))?.apikey;
|
||||
|
||||
instanceData.instance['chatwoot'] = chatwoot;
|
||||
}
|
||||
|
||||
instances.push(instanceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('return instance info: ' + instances.length);
|
||||
|
||||
return instances.find((i) => i.instance.instanceName === instanceName) ?? instances;
|
||||
return this.instanceInfo(instanceName);
|
||||
}
|
||||
|
||||
private delInstanceFiles() {
|
||||
@ -318,17 +277,13 @@ export class WAMonitoringService {
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'typebot', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'websocket', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'settings', instanceName + '*')}`);
|
||||
execSync(`rm -rf ${join(STORE_DIR, 'labels', instanceName + '*')}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.verbose('cleaning store database instance: ' + instanceName);
|
||||
|
||||
// await ChatModel.deleteMany({ owner: instanceName });
|
||||
// await ContactModel.deleteMany({ owner: instanceName });
|
||||
// await MessageUpModel.deleteMany({ owner: instanceName });
|
||||
// await MessageModel.deleteMany({ owner: instanceName });
|
||||
|
||||
await AuthModel.deleteMany({ _id: instanceName });
|
||||
await WebhookModel.deleteMany({ _id: instanceName });
|
||||
await ChatwootModel.deleteMany({ _id: instanceName });
|
||||
@ -338,6 +293,8 @@ export class WAMonitoringService {
|
||||
await TypebotModel.deleteMany({ _id: instanceName });
|
||||
await WebsocketModel.deleteMany({ _id: instanceName });
|
||||
await SettingsModel.deleteMany({ _id: instanceName });
|
||||
await LabelModel.deleteMany({ owner: instanceName });
|
||||
await ContactModel.deleteMany({ owner: instanceName });
|
||||
|
||||
return;
|
||||
}
|
||||
@ -358,9 +315,56 @@ export class WAMonitoringService {
|
||||
}
|
||||
}
|
||||
|
||||
public async saveInstance(data: any) {
|
||||
this.logger.verbose('Save instance');
|
||||
|
||||
try {
|
||||
const msgParsed = JSON.parse(JSON.stringify(data));
|
||||
if (this.db.ENABLED && this.db.SAVE_DATA.INSTANCE) {
|
||||
await this.repository.dbServer.connect();
|
||||
await this.dbInstance.collection(data.instanceName).replaceOne({ _id: 'integration' }, msgParsed, {
|
||||
upsert: true,
|
||||
});
|
||||
} else {
|
||||
const path = join(INSTANCE_DIR, data.instanceName);
|
||||
if (!existsSync(path)) mkdirSync(path, { recursive: true });
|
||||
writeFileSync(path + '/integration.json', JSON.stringify(msgParsed));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async setInstance(name: string) {
|
||||
const instance = new WAStartupService(this.configService, this.eventEmitter, this.repository, this.cache);
|
||||
instance.instanceName = name;
|
||||
const integration = await this.repository.integration.find(name);
|
||||
|
||||
let instance: BaileysStartupService | BusinessStartupService;
|
||||
if (integration && integration.integration === Integration.WHATSAPP_BUSINESS) {
|
||||
instance = new BusinessStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
|
||||
instance.instanceName = name;
|
||||
} else {
|
||||
instance = new BaileysStartupService(
|
||||
this.configService,
|
||||
this.eventEmitter,
|
||||
this.repository,
|
||||
this.cache,
|
||||
this.chatwootCache,
|
||||
);
|
||||
|
||||
instance.instanceName = name;
|
||||
|
||||
if (!integration) {
|
||||
await instance.setIntegration({ integration: Integration.WHATSAPP_BAILEYS });
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.verbose('Instance loaded: ' + name);
|
||||
await instance.connectToWhatsapp();
|
||||
this.logger.verbose('connectToWhatsapp: ' + name);
|
||||
@ -385,7 +389,7 @@ export class WAMonitoringService {
|
||||
this.logger.verbose('Database enabled');
|
||||
await this.repository.dbServer.connect();
|
||||
const collections: any[] = await this.dbInstance.collections();
|
||||
|
||||
await this.deleteTempInstances(collections);
|
||||
if (collections.length > 0) {
|
||||
this.logger.verbose('Reading collections and setting instances');
|
||||
await Promise.all(collections.map((coll) => this.setInstance(coll.namespace.replace(/^[\w-]+\./, ''))));
|
||||
@ -442,6 +446,7 @@ export class WAMonitoringService {
|
||||
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
|
||||
this.logger.verbose('logout instance: ' + instanceName);
|
||||
try {
|
||||
this.waInstances[instanceName]?.clearCacheChatwoot();
|
||||
this.logger.verbose('request cleaning up instance: ' + instanceName);
|
||||
this.cleaningUp(instanceName);
|
||||
} finally {
|
||||
@ -473,4 +478,27 @@ export class WAMonitoringService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteTempInstances(collections: Collection<Document>[]) {
|
||||
const shouldDelete = this.configService.get<boolean>('DEL_TEMP_INSTANCES');
|
||||
if (!shouldDelete) {
|
||||
this.logger.verbose('Temp instances deletion is disabled');
|
||||
return;
|
||||
}
|
||||
this.logger.verbose('Cleaning up temp instances');
|
||||
const auths = await this.repository.auth.list();
|
||||
if (auths.length === 0) {
|
||||
this.logger.verbose('No temp instances found');
|
||||
return;
|
||||
}
|
||||
let tempInstances = 0;
|
||||
auths.forEach((auth) => {
|
||||
if (collections.find((coll) => coll.namespace.replace(/^[\w-]+\./, '') === auth._id)) {
|
||||
return;
|
||||
}
|
||||
tempInstances++;
|
||||
this.eventEmitter.emit('remove.instance', auth._id, 'inner');
|
||||
});
|
||||
this.logger.verbose('Temp instances removed: ' + tempInstances);
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ export class ProxyService {
|
||||
|
||||
private readonly logger = new Logger(ProxyService.name);
|
||||
|
||||
public create(instance: InstanceDto, data: ProxyDto, reload = true) {
|
||||
public create(instance: InstanceDto, data: ProxyDto) {
|
||||
this.logger.verbose('create proxy: ' + instance.instanceName);
|
||||
this.waMonitor.waInstances[instance.instanceName].setProxy(data, reload);
|
||||
this.waMonitor.waInstances[instance.instanceName].setProxy(data);
|
||||
|
||||
return { proxy: { ...instance, proxy: data } };
|
||||
}
|
||||
@ -27,7 +27,7 @@ export class ProxyService {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, proxy: '' };
|
||||
return { enabled: false, proxy: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -274,6 +274,7 @@ export class TypebotService {
|
||||
const types = {
|
||||
conversation: msg.conversation,
|
||||
extendedTextMessage: msg.extendedTextMessage?.text,
|
||||
responseRowId: msg.listResponseMessage?.singleSelectReply?.selectedRowId,
|
||||
};
|
||||
|
||||
this.logger.verbose('type message: ' + types);
|
||||
@ -389,6 +390,7 @@ export class TypebotService {
|
||||
input,
|
||||
clientSideActions,
|
||||
this.eventEmitter,
|
||||
applyFormatting,
|
||||
).catch((err) => {
|
||||
console.error('Erro ao processar mensagens:', err);
|
||||
});
|
||||
@ -404,72 +406,67 @@ export class TypebotService {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function processMessages(instance, messages, input, clientSideActions, eventEmitter) {
|
||||
for (const message of messages) {
|
||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||
function applyFormatting(element) {
|
||||
let text = '';
|
||||
|
||||
if (element.text) {
|
||||
text += element.text;
|
||||
}
|
||||
|
||||
if (
|
||||
element.children &&
|
||||
(element.type === 'p' ||
|
||||
element.type === 'a' ||
|
||||
element.type === 'inline-variable' ||
|
||||
element.type === 'variable')
|
||||
) {
|
||||
for (const child of element.children) {
|
||||
text += applyFormatting(child);
|
||||
}
|
||||
}
|
||||
|
||||
let formats = '';
|
||||
|
||||
if (element.bold) {
|
||||
formats += '*';
|
||||
}
|
||||
|
||||
if (element.italic) {
|
||||
formats += '_';
|
||||
}
|
||||
|
||||
if (element.underline) {
|
||||
formats += '~';
|
||||
}
|
||||
|
||||
let formattedText = `${formats}${text}${formats.split('').reverse().join('')}`;
|
||||
|
||||
if (element.url) {
|
||||
formattedText = element.children[0]?.text ? `[${formattedText}]\n(${element.url})` : `${element.url}`;
|
||||
}
|
||||
|
||||
return formattedText;
|
||||
}
|
||||
|
||||
async function processMessages(instance, messages, input, clientSideActions, eventEmitter, applyFormatting) {
|
||||
for (const message of messages) {
|
||||
if (message.type === 'text') {
|
||||
let formattedText = '';
|
||||
|
||||
let linkPreview = false;
|
||||
|
||||
for (const richText of message.content.richText) {
|
||||
if (richText.type === 'variable') {
|
||||
for (const child of richText.children) {
|
||||
for (const grandChild of child.children) {
|
||||
formattedText += grandChild.text;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const element of richText.children) {
|
||||
let text = '';
|
||||
|
||||
if (element.type === 'inline-variable') {
|
||||
for (const child of element.children) {
|
||||
for (const grandChild of child.children) {
|
||||
text += grandChild.text;
|
||||
}
|
||||
}
|
||||
} else if (element.text) {
|
||||
text = element.text;
|
||||
}
|
||||
|
||||
// if (element.text) {
|
||||
// text = element.text;
|
||||
// }
|
||||
|
||||
if (element.bold) {
|
||||
text = `*${text}*`;
|
||||
}
|
||||
|
||||
if (element.italic) {
|
||||
text = `_${text}_`;
|
||||
}
|
||||
|
||||
if (element.underline) {
|
||||
text = `*${text}*`;
|
||||
}
|
||||
|
||||
if (element.url) {
|
||||
const linkText = element.children[0].text;
|
||||
text = `[${linkText}](${element.url})`;
|
||||
linkPreview = true;
|
||||
}
|
||||
|
||||
formattedText += text;
|
||||
}
|
||||
for (const element of richText.children) {
|
||||
formattedText += applyFormatting(element);
|
||||
}
|
||||
formattedText += '\n';
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: linkPreview,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
@ -481,7 +478,7 @@ export class TypebotService {
|
||||
await instance.mediaMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
mediaMessage: {
|
||||
@ -495,7 +492,7 @@ export class TypebotService {
|
||||
await instance.mediaMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
mediaMessage: {
|
||||
@ -509,7 +506,7 @@ export class TypebotService {
|
||||
await instance.audioWhatsapp({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'recording',
|
||||
encoding: true,
|
||||
},
|
||||
@ -518,6 +515,12 @@ export class TypebotService {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||
|
||||
if (wait) {
|
||||
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (input) {
|
||||
@ -535,9 +538,8 @@ export class TypebotService {
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: 1200,
|
||||
delay: instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: false,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
@ -709,7 +711,7 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
@ -720,7 +722,7 @@ export class TypebotService {
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
sessions: newSessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
@ -801,7 +803,7 @@ export class TypebotService {
|
||||
}
|
||||
|
||||
if (keyword_finish && content.toLowerCase() === keyword_finish.toLowerCase()) {
|
||||
sessions.splice(sessions.indexOf(session), 1);
|
||||
const newSessions = await this.clearSessions(instance, remoteJid);
|
||||
|
||||
const typebotData = {
|
||||
enabled: findTypebot.enabled,
|
||||
@ -812,7 +814,7 @@ export class TypebotService {
|
||||
delay_message: delay_message,
|
||||
unknown_message: unknown_message,
|
||||
listening_from_me: listening_from_me,
|
||||
sessions,
|
||||
sessions: newSessions,
|
||||
};
|
||||
|
||||
this.create(instance, typebotData);
|
||||
|
3224
src/whatsapp/services/whatsapp.baileys.service.ts
Normal file
3224
src/whatsapp/services/whatsapp.baileys.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
1269
src/whatsapp/services/whatsapp.business.service.ts
Normal file
1269
src/whatsapp/services/whatsapp.business.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,10 @@ export enum Events {
|
||||
TYPEBOT_START = 'typebot.start',
|
||||
TYPEBOT_CHANGE_STATUS = 'typebot.change-status',
|
||||
CHAMA_AI_ACTION = 'chama-ai.action',
|
||||
LABELS_EDIT = 'labels.edit',
|
||||
LABELS_ASSOCIATION = 'labels.association',
|
||||
CREDS_UPDATE = 'creds.update',
|
||||
MESSAGING_HISTORY_SET = 'messaging-history.set',
|
||||
}
|
||||
|
||||
export declare namespace wa {
|
||||
@ -65,6 +69,9 @@ export declare namespace wa {
|
||||
number?: string;
|
||||
reopen_conversation?: boolean;
|
||||
conversation_pending?: boolean;
|
||||
import_contacts?: boolean;
|
||||
import_messages?: boolean;
|
||||
days_limit_import_messages?: number;
|
||||
};
|
||||
|
||||
export type LocalSettings = {
|
||||
@ -74,6 +81,7 @@ export declare namespace wa {
|
||||
always_online?: boolean;
|
||||
read_messages?: boolean;
|
||||
read_status?: boolean;
|
||||
sync_full_history?: boolean;
|
||||
};
|
||||
|
||||
export type LocalWebsocket = {
|
||||
@ -109,9 +117,17 @@ export declare namespace wa {
|
||||
sessions?: Session[];
|
||||
};
|
||||
|
||||
type Proxy = {
|
||||
host?: string;
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export type LocalProxy = {
|
||||
enabled?: boolean;
|
||||
proxy?: string;
|
||||
proxy?: Proxy;
|
||||
};
|
||||
|
||||
export type LocalChamaai = {
|
||||
@ -122,6 +138,12 @@ export declare namespace wa {
|
||||
answerByAudio?: boolean;
|
||||
};
|
||||
|
||||
export type LocalIntegration = {
|
||||
integration?: string;
|
||||
number?: string;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
export type StateConnection = {
|
||||
instance?: string;
|
||||
state?: WAConnectionState | 'refused';
|
||||
@ -139,3 +161,8 @@ export const MessageSubtype = [
|
||||
'viewOnceMessage',
|
||||
'viewOnceMessageV2',
|
||||
];
|
||||
|
||||
export const Integration = {
|
||||
WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',
|
||||
WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { configService } from '../config/env.config';
|
||||
import { eventEmitter } from '../config/event.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
import { CacheEngine } from '../libs/cacheengine';
|
||||
import { dbserver } from '../libs/db.connect';
|
||||
import { RedisCache } from '../libs/redis.client';
|
||||
import { ChamaaiController } from './controllers/chamaai.controller';
|
||||
@ -8,6 +9,7 @@ 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 { LabelController } from './controllers/label.controller';
|
||||
import { ProxyController } from './controllers/proxy.controller';
|
||||
import { RabbitmqController } from './controllers/rabbitmq.controller';
|
||||
import { SendMessageController } from './controllers/sendMessage.controller';
|
||||
@ -22,6 +24,7 @@ import {
|
||||
ChatModel,
|
||||
ChatwootModel,
|
||||
ContactModel,
|
||||
IntegrationModel,
|
||||
MessageModel,
|
||||
MessageUpModel,
|
||||
ProxyModel,
|
||||
@ -32,11 +35,14 @@ import {
|
||||
WebhookModel,
|
||||
WebsocketModel,
|
||||
} from './models';
|
||||
import { LabelModel } from './models/label.model';
|
||||
import { AuthRepository } from './repository/auth.repository';
|
||||
import { ChamaaiRepository } from './repository/chamaai.repository';
|
||||
import { ChatRepository } from './repository/chat.repository';
|
||||
import { ChatwootRepository } from './repository/chatwoot.repository';
|
||||
import { ContactRepository } from './repository/contact.repository';
|
||||
import { IntegrationRepository } from './repository/integration.repository';
|
||||
import { LabelRepository } from './repository/label.repository';
|
||||
import { MessageRepository } from './repository/message.repository';
|
||||
import { MessageUpRepository } from './repository/messageUp.repository';
|
||||
import { ProxyRepository } from './repository/proxy.repository';
|
||||
@ -48,8 +54,10 @@ import { TypebotRepository } from './repository/typebot.repository';
|
||||
import { WebhookRepository } from './repository/webhook.repository';
|
||||
import { WebsocketRepository } from './repository/websocket.repository';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { CacheService } from './services/cache.service';
|
||||
import { ChamaaiService } from './services/chamaai.service';
|
||||
import { ChatwootService } from './services/chatwoot.service';
|
||||
import { IntegrationService } from './services/integration.service';
|
||||
import { WAMonitoringService } from './services/monitor.service';
|
||||
import { ProxyService } from './services/proxy.service';
|
||||
import { RabbitmqService } from './services/rabbitmq.service';
|
||||
@ -72,9 +80,11 @@ const proxyRepository = new ProxyRepository(ProxyModel, configService);
|
||||
const chamaaiRepository = new ChamaaiRepository(ChamaaiModel, configService);
|
||||
const rabbitmqRepository = new RabbitmqRepository(RabbitmqModel, configService);
|
||||
const sqsRepository = new SqsRepository(SqsModel, configService);
|
||||
const integrationRepository = new IntegrationRepository(IntegrationModel, configService);
|
||||
const chatwootRepository = new ChatwootRepository(ChatwootModel, configService);
|
||||
const settingsRepository = new SettingsRepository(SettingsModel, configService);
|
||||
const authRepository = new AuthRepository(AuthModel, configService);
|
||||
const authRepository = new AuthRepository(AuthModel, IntegrationModel, configService);
|
||||
const labelRepository = new LabelRepository(LabelModel, configService);
|
||||
|
||||
export const repository = new RepositoryBroker(
|
||||
messageRepository,
|
||||
@ -90,14 +100,18 @@ export const repository = new RepositoryBroker(
|
||||
typebotRepository,
|
||||
proxyRepository,
|
||||
chamaaiRepository,
|
||||
integrationRepository,
|
||||
authRepository,
|
||||
labelRepository,
|
||||
configService,
|
||||
dbserver?.getClient(),
|
||||
);
|
||||
|
||||
export const cache = new RedisCache();
|
||||
|
||||
export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache);
|
||||
const chatwootCache = new CacheService(new CacheEngine(configService, ChatwootService.name).getEngine());
|
||||
|
||||
export const waMonitor = new WAMonitoringService(eventEmitter, configService, repository, cache, chatwootCache);
|
||||
|
||||
const authService = new AuthService(configService, waMonitor, repository);
|
||||
|
||||
@ -107,7 +121,7 @@ export const typebotController = new TypebotController(typebotService);
|
||||
|
||||
const webhookService = new WebhookService(waMonitor);
|
||||
|
||||
export const webhookController = new WebhookController(webhookService);
|
||||
export const webhookController = new WebhookController(webhookService, waMonitor);
|
||||
|
||||
const websocketService = new WebsocketService(waMonitor);
|
||||
|
||||
@ -115,7 +129,7 @@ export const websocketController = new WebsocketController(websocketService);
|
||||
|
||||
const proxyService = new ProxyService(waMonitor);
|
||||
|
||||
export const proxyController = new ProxyController(proxyService);
|
||||
export const proxyController = new ProxyController(proxyService, waMonitor);
|
||||
|
||||
const chamaaiService = new ChamaaiService(waMonitor, configService);
|
||||
|
||||
@ -129,7 +143,9 @@ const sqsService = new SqsService(waMonitor);
|
||||
|
||||
export const sqsController = new SqsController(sqsService);
|
||||
|
||||
const chatwootService = new ChatwootService(waMonitor, configService, repository);
|
||||
const integrationService = new IntegrationService(waMonitor);
|
||||
|
||||
const chatwootService = new ChatwootService(waMonitor, configService, repository, chatwootCache);
|
||||
|
||||
export const chatwootController = new ChatwootController(chatwootService, configService, repository);
|
||||
|
||||
@ -148,13 +164,15 @@ export const instanceController = new InstanceController(
|
||||
settingsService,
|
||||
websocketService,
|
||||
rabbitmqService,
|
||||
proxyService,
|
||||
sqsService,
|
||||
typebotService,
|
||||
integrationService,
|
||||
cache,
|
||||
chatwootCache,
|
||||
);
|
||||
export const sendMessageController = new SendMessageController(waMonitor);
|
||||
export const chatController = new ChatController(waMonitor);
|
||||
export const groupController = new GroupController(waMonitor);
|
||||
export const labelController = new LabelController(waMonitor);
|
||||
|
||||
logger.info('Module - ON');
|
||||
|
@ -18,5 +18,9 @@
|
||||
"incremental": true,
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"exclude": ["node_modules", "./test", "./dist", "./prisma"]
|
||||
"exclude": ["node_modules", "./test", "./dist", "./prisma"],
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/**/*.json"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user