mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-07-27 02:48:39 -06:00
Merge branch 'develop' into main
This commit is contained in:
commit
2178897d28
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
|
19
CHANGELOG.md
19
CHANGELOG.md
@ -1,3 +1,22 @@
|
|||||||
|
# 1.6.2 (develop)
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Added update message endpoint
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
# 1.6.1 (2023-12-22 11:43)
|
# 1.6.1 (2023-12-22 11:43)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
FROM node:20.7.0-alpine AS builder
|
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.6.2" description="Api to control whatsapp features through http requests."
|
||||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||||
LABEL contact="contato@agenciadgcode.com"
|
LABEL contact="contato@agenciadgcode.com"
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "evolution-api",
|
"name": "evolution-api",
|
||||||
"version": "1.6.1",
|
"version": "1.6.2",
|
||||||
"description": "Rest api for communication with WhatsApp",
|
"description": "Rest api for communication with WhatsApp",
|
||||||
"main": "./dist/src/main.js",
|
"main": "./dist/src/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -46,7 +46,7 @@
|
|||||||
"@figuro/chatwoot-sdk": "^1.1.16",
|
"@figuro/chatwoot-sdk": "^1.1.16",
|
||||||
"@hapi/boom": "^10.0.1",
|
"@hapi/boom": "^10.0.1",
|
||||||
"@sentry/node": "^7.59.2",
|
"@sentry/node": "^7.59.2",
|
||||||
"@whiskeysockets/baileys": "github:PurpShell/Baileys#combined",
|
"@whiskeysockets/baileys": "^6.5.0",
|
||||||
"amqplib": "^0.10.3",
|
"amqplib": "^0.10.3",
|
||||||
"aws-sdk": "^2.1499.0",
|
"aws-sdk": "^2.1499.0",
|
||||||
"axios": "^1.3.5",
|
"axios": "^1.3.5",
|
||||||
|
@ -136,11 +136,22 @@ export type GlobalWebhook = {
|
|||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
WEBHOOK_BY_EVENTS: 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 SslConf = { PRIVKEY: string; FULLCHAIN: string };
|
||||||
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
|
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
|
||||||
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
||||||
export type QrCode = { LIMIT: number; COLOR: string };
|
export type QrCode = { LIMIT: number; COLOR: string };
|
||||||
export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean };
|
export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean };
|
||||||
|
export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal };
|
||||||
export type Production = boolean;
|
export type Production = boolean;
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
@ -160,6 +171,7 @@ export interface Env {
|
|||||||
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
||||||
QRCODE: QrCode;
|
QRCODE: QrCode;
|
||||||
TYPEBOT: Typebot;
|
TYPEBOT: Typebot;
|
||||||
|
CACHE: CacheConf;
|
||||||
AUTHENTICATION: Auth;
|
AUTHENTICATION: Auth;
|
||||||
PRODUCTION?: Production;
|
PRODUCTION?: Production;
|
||||||
WABUSSINESS: WABussiness;
|
WABUSSINESS: WABussiness;
|
||||||
@ -326,6 +338,18 @@ export class ConfigService {
|
|||||||
API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old',
|
API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old',
|
||||||
KEEP_OPEN: process.env.TYPEBOT_KEEP_OPEN === 'true',
|
KEEP_OPEN: process.env.TYPEBOT_KEEP_OPEN === '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: {
|
AUTHENTICATION: {
|
||||||
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
|
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
|
||||||
API_KEY: {
|
API_KEY: {
|
||||||
|
@ -162,6 +162,17 @@ TYPEBOT:
|
|||||||
API_VERSION: 'old' # old | latest
|
API_VERSION: 'old' # old | latest
|
||||||
KEEP_OPEN: false
|
KEEP_OPEN: false
|
||||||
|
|
||||||
|
# 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
|
# Defines an authentication type for the api
|
||||||
# We recommend using the apikey because it will allow you to use a custom token,
|
# 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
|
# if you use jwt, a random token will be generated and may be expired and you will have to generate a new token
|
||||||
|
@ -25,7 +25,7 @@ info:
|
|||||||
</font>
|
</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)
|
[](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.6.2
|
||||||
contact:
|
contact:
|
||||||
name: DavidsonGomes
|
name: DavidsonGomes
|
||||||
email: contato@agenciadgcode.com
|
email: contato@agenciadgcode.com
|
||||||
|
@ -27,6 +27,7 @@ export const initAMQP = () => {
|
|||||||
channel.assertExchange(exchangeName, 'topic', {
|
channel.assertExchange(exchangeName, 'topic', {
|
||||||
durable: true,
|
durable: true,
|
||||||
autoDelete: false,
|
autoDelete: false,
|
||||||
|
assert: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
amqpChannel = channel;
|
amqpChannel = channel;
|
||||||
@ -43,7 +44,7 @@ export const getAMQP = (): amqp.Channel | null => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const initQueues = (instanceName: string, events: string[]) => {
|
export const initQueues = (instanceName: string, events: string[]) => {
|
||||||
if (!events || !events.length) return;
|
if (!instanceName || !events || !events.length) return;
|
||||||
|
|
||||||
const queues = events.map((event) => {
|
const queues = events.map((event) => {
|
||||||
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||||
@ -56,6 +57,7 @@ export const initQueues = (instanceName: string, events: string[]) => {
|
|||||||
amqp.assertExchange(exchangeName, 'topic', {
|
amqp.assertExchange(exchangeName, 'topic', {
|
||||||
durable: true,
|
durable: true,
|
||||||
autoDelete: false,
|
autoDelete: false,
|
||||||
|
assert: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const queueName = `${instanceName}.${event}`;
|
const queueName = `${instanceName}.${event}`;
|
||||||
@ -89,6 +91,7 @@ export const removeQueues = (instanceName: string, events: string[]) => {
|
|||||||
amqp.assertExchange(exchangeName, 'topic', {
|
amqp.assertExchange(exchangeName, 'topic', {
|
||||||
durable: true,
|
durable: true,
|
||||||
autoDelete: false,
|
autoDelete: false,
|
||||||
|
assert: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const queueName = `${instanceName}.${event}`;
|
const queueName = `${instanceName}.${event}`;
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
@ -611,6 +611,26 @@ export const profileStatusSchema: JSONSchema7 = {
|
|||||||
...isNotEmpty('status'),
|
...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 = {
|
export const profilePictureSchema: JSONSchema7 = {
|
||||||
$id: v4(),
|
$id: v4(),
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -1127,7 +1147,18 @@ export const proxySchema: JSONSchema7 = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
enabled: { type: 'boolean', enum: [true, false] },
|
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'],
|
required: ['enabled', 'proxy'],
|
||||||
...isNotEmpty('enabled', 'proxy'),
|
...isNotEmpty('enabled', 'proxy'),
|
||||||
|
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>;
|
||||||
|
}
|
@ -10,6 +10,7 @@ import {
|
|||||||
ProfileStatusDto,
|
ProfileStatusDto,
|
||||||
ReadMessageDto,
|
ReadMessageDto,
|
||||||
SendPresenceDto,
|
SendPresenceDto,
|
||||||
|
UpdateMessageDto,
|
||||||
WhatsAppNumberDto,
|
WhatsAppNumberDto,
|
||||||
NumberBusiness,
|
NumberBusiness,
|
||||||
} from '../dto/chat.dto';
|
} from '../dto/chat.dto';
|
||||||
@ -123,4 +124,9 @@ export class ChatController {
|
|||||||
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||||
return await this.waMonitor.waInstances[instanceName].setWhatsappBusinessProfile(data);
|
return await this.waMonitor.waInstances[instanceName].setWhatsappBusinessProfile(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateMessage({ instanceName }: InstanceDto, data: UpdateMessageDto) {
|
||||||
|
logger.verbose('requested updateMessage from ' + instanceName + ' instance');
|
||||||
|
return await this.waMonitor.waInstances[instanceName].updateMessage(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ import { isURL } from 'class-validator';
|
|||||||
import { ConfigService, HttpServer } from '../../config/env.config';
|
import { ConfigService, HttpServer } from '../../config/env.config';
|
||||||
import { Logger } from '../../config/logger.config';
|
import { Logger } from '../../config/logger.config';
|
||||||
import { BadRequestException } from '../../exceptions';
|
import { BadRequestException } from '../../exceptions';
|
||||||
|
import { CacheEngine } from '../../libs/cacheengine';
|
||||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||||
import { InstanceDto } from '../dto/instance.dto';
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
import { RepositoryBroker } from '../repository/repository.manager';
|
import { RepositoryBroker } from '../repository/repository.manager';
|
||||||
|
import { CacheService } from '../services/cache.service';
|
||||||
import { ChatwootService } from '../services/chatwoot.service';
|
import { ChatwootService } from '../services/chatwoot.service';
|
||||||
import { waMonitor } from '../whatsapp.module';
|
import { waMonitor } from '../whatsapp.module';
|
||||||
|
|
||||||
@ -94,7 +96,9 @@ export class ChatwootController {
|
|||||||
|
|
||||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||||
const chatwootService = new ChatwootService(waMonitor, this.configService, 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);
|
return chatwootService.receiveWebhook(instance, data);
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ import { RedisCache } from '../../libs/redis.client';
|
|||||||
import { InstanceDto } from '../dto/instance.dto';
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
import { RepositoryBroker } from '../repository/repository.manager';
|
import { RepositoryBroker } from '../repository/repository.manager';
|
||||||
import { AuthService, OldToken } from '../services/auth.service';
|
import { AuthService, OldToken } from '../services/auth.service';
|
||||||
|
import { CacheService } from '../services/cache.service';
|
||||||
import { ChatwootService } from '../services/chatwoot.service';
|
import { ChatwootService } from '../services/chatwoot.service';
|
||||||
import { WAMonitoringService } from '../services/monitor.service';
|
import { WAMonitoringService } from '../services/monitor.service';
|
||||||
import { ProxyService } from '../services/proxy.service';
|
|
||||||
import { RabbitmqService } from '../services/rabbitmq.service';
|
import { RabbitmqService } from '../services/rabbitmq.service';
|
||||||
import { SettingsService } from '../services/settings.service';
|
import { SettingsService } from '../services/settings.service';
|
||||||
import { SqsService } from '../services/sqs.service';
|
import { SqsService } from '../services/sqs.service';
|
||||||
@ -34,10 +34,10 @@ export class InstanceController {
|
|||||||
private readonly settingsService: SettingsService,
|
private readonly settingsService: SettingsService,
|
||||||
private readonly websocketService: WebsocketService,
|
private readonly websocketService: WebsocketService,
|
||||||
private readonly rabbitmqService: RabbitmqService,
|
private readonly rabbitmqService: RabbitmqService,
|
||||||
private readonly proxyService: ProxyService,
|
|
||||||
private readonly sqsService: SqsService,
|
private readonly sqsService: SqsService,
|
||||||
private readonly typebotService: TypebotService,
|
private readonly typebotService: TypebotService,
|
||||||
private readonly cache: RedisCache,
|
private readonly cache: RedisCache,
|
||||||
|
private readonly chatwootCache: CacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private readonly logger = new Logger(InstanceController.name);
|
private readonly logger = new Logger(InstanceController.name);
|
||||||
@ -77,7 +77,6 @@ export class InstanceController {
|
|||||||
typebot_delay_message,
|
typebot_delay_message,
|
||||||
typebot_unknown_message,
|
typebot_unknown_message,
|
||||||
typebot_listening_from_me,
|
typebot_listening_from_me,
|
||||||
proxy,
|
|
||||||
}: InstanceDto) {
|
}: InstanceDto) {
|
||||||
try {
|
try {
|
||||||
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
||||||
@ -86,7 +85,15 @@ export class InstanceController {
|
|||||||
await this.authService.checkDuplicateToken(token);
|
await this.authService.checkDuplicateToken(token);
|
||||||
|
|
||||||
this.logger.verbose('creating instance');
|
this.logger.verbose('creating instance');
|
||||||
const instance = new WAStartupClass[integration](this.configService, this.eventEmitter, this.repository, this.cache);
|
|
||||||
|
const instance = new WAStartupService(
|
||||||
|
this.configService,
|
||||||
|
this.eventEmitter,
|
||||||
|
this.repository,
|
||||||
|
this.cache,
|
||||||
|
this.chatwootCache,
|
||||||
|
);
|
||||||
|
|
||||||
instance.instanceName = instanceName;
|
instance.instanceName = instanceName;
|
||||||
instance.instanceNumber = number;
|
instance.instanceNumber = number;
|
||||||
instance.instanceToken = token;
|
instance.instanceToken = token;
|
||||||
@ -261,22 +268,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[];
|
let sqsEvents: string[];
|
||||||
|
|
||||||
if (sqs_enabled) {
|
if (sqs_enabled) {
|
||||||
@ -419,7 +410,6 @@ export class InstanceController {
|
|||||||
settings,
|
settings,
|
||||||
webhook_url: webhook_url,
|
webhook_url: webhook_url,
|
||||||
qrcode: getQrcode,
|
qrcode: getQrcode,
|
||||||
proxy,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.verbose('instance created');
|
this.logger.verbose('instance created');
|
||||||
@ -525,7 +515,6 @@ export class InstanceController {
|
|||||||
name_inbox: instance.instanceName,
|
name_inbox: instance.instanceName,
|
||||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||||
},
|
},
|
||||||
proxy,
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error.message[0]);
|
this.logger.error(error.message[0]);
|
||||||
@ -584,6 +573,7 @@ export class InstanceController {
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case 'open':
|
case 'open':
|
||||||
this.logger.verbose('logging out instance: ' + instanceName);
|
this.logger.verbose('logging out instance: ' + instanceName);
|
||||||
|
instance.clearCacheChatwoot();
|
||||||
await instance.reloadConnection();
|
await instance.reloadConnection();
|
||||||
await delay(2000);
|
await delay(2000);
|
||||||
|
|
||||||
@ -649,6 +639,7 @@ export class InstanceController {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
||||||
|
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
|
||||||
|
|
||||||
if (instance.state === 'connecting') {
|
if (instance.state === 'connecting') {
|
||||||
this.logger.verbose('logging out instance: ' + instanceName);
|
this.logger.verbose('logging out instance: ' + instanceName);
|
||||||
@ -658,10 +649,15 @@ export class InstanceController {
|
|||||||
|
|
||||||
this.logger.verbose('deleting instance: ' + instanceName);
|
this.logger.verbose('deleting instance: ' + instanceName);
|
||||||
|
|
||||||
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
try {
|
||||||
instanceName,
|
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||||
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
instanceName,
|
||||||
});
|
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
delete this.waMonitor.waInstances[instanceName];
|
delete this.waMonitor.waInstances[instanceName];
|
||||||
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
|
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
|
||||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
|
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
import { Logger } from '../../config/logger.config';
|
import { Logger } from '../../config/logger.config';
|
||||||
|
import { BadRequestException } from '../../exceptions';
|
||||||
import { InstanceDto } from '../dto/instance.dto';
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
import { ProxyDto } from '../dto/proxy.dto';
|
import { ProxyDto } from '../dto/proxy.dto';
|
||||||
import { ProxyService } from '../services/proxy.service';
|
import { ProxyService } from '../services/proxy.service';
|
||||||
@ -13,7 +16,16 @@ export class ProxyController {
|
|||||||
|
|
||||||
if (!data.enabled) {
|
if (!data.enabled) {
|
||||||
logger.verbose('proxy disabled');
|
logger.verbose('proxy disabled');
|
||||||
data.proxy = '';
|
data.proxy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.proxy) {
|
||||||
|
logger.verbose('proxy enabled');
|
||||||
|
const { host, port, protocol, username, password } = data.proxy;
|
||||||
|
const testProxy = await this.testProxy(host, port, protocol, username, password);
|
||||||
|
if (!testProxy) {
|
||||||
|
throw new BadRequestException('Invalid proxy');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.proxyService.create(instance, data);
|
return this.proxyService.create(instance, data);
|
||||||
@ -23,4 +35,36 @@ export class ProxyController {
|
|||||||
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
|
logger.verbose('requested findProxy from ' + instance.instanceName + ' instance');
|
||||||
return this.proxyService.find(instance);
|
return this.proxyService.find(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async testProxy(host: string, port: string, protocol: string, username?: string, password?: string) {
|
||||||
|
logger.verbose('requested testProxy');
|
||||||
|
try {
|
||||||
|
let proxyConfig: any = {
|
||||||
|
host: host,
|
||||||
|
port: parseInt(port),
|
||||||
|
protocol: protocol,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (username && password) {
|
||||||
|
proxyConfig = {
|
||||||
|
...proxyConfig,
|
||||||
|
auth: {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const serverIp = await axios.get('http://meuip.com/api/meuip.php');
|
||||||
|
|
||||||
|
const response = await axios.get('http://meuip.com/api/meuip.php', {
|
||||||
|
proxy: proxyConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.verbose('testProxy response: ' + response.data);
|
||||||
|
return response.data !== serverIp.data;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('testProxy error: ' + error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,3 +103,9 @@ export class SendPresenceDto extends Metadata {
|
|||||||
delay: number;
|
delay: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateMessageDto extends Metadata {
|
||||||
|
number: string;
|
||||||
|
key: proto.IMessageKey;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
|
class Proxy {
|
||||||
|
host: string;
|
||||||
|
port: string;
|
||||||
|
protocol: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class ProxyDto {
|
export class ProxyDto {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
proxy: string;
|
proxy: Proxy;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ class ChatwootMessage {
|
|||||||
messageId?: number;
|
messageId?: number;
|
||||||
inboxId?: number;
|
inboxId?: number;
|
||||||
conversationId?: number;
|
conversationId?: number;
|
||||||
|
contactInbox?: { sourceId: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessageRaw {
|
export class MessageRaw {
|
||||||
@ -29,6 +30,7 @@ export class MessageRaw {
|
|||||||
source_id?: string;
|
source_id?: string;
|
||||||
source_reply_id?: string;
|
source_reply_id?: string;
|
||||||
chatwoot?: ChatwootMessage;
|
chatwoot?: ChatwootMessage;
|
||||||
|
contextInfo?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageSchema = new Schema<MessageRaw>({
|
const messageSchema = new Schema<MessageRaw>({
|
||||||
@ -50,6 +52,7 @@ const messageSchema = new Schema<MessageRaw>({
|
|||||||
messageId: { type: Number },
|
messageId: { type: Number },
|
||||||
inboxId: { type: Number },
|
inboxId: { type: Number },
|
||||||
conversationId: { type: Number },
|
conversationId: { type: Number },
|
||||||
|
contactInbox: { type: Object },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,16 +2,30 @@ import { Schema } from 'mongoose';
|
|||||||
|
|
||||||
import { dbserver } from '../../libs/db.connect';
|
import { dbserver } from '../../libs/db.connect';
|
||||||
|
|
||||||
|
class Proxy {
|
||||||
|
host?: string;
|
||||||
|
port?: string;
|
||||||
|
protocol?: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class ProxyRaw {
|
export class ProxyRaw {
|
||||||
_id?: string;
|
_id?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
proxy?: string;
|
proxy?: Proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxySchema = new Schema<ProxyRaw>({
|
const proxySchema = new Schema<ProxyRaw>({
|
||||||
_id: { type: String, _id: true },
|
_id: { type: String, _id: true },
|
||||||
enabled: { type: Boolean, required: 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');
|
export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy');
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { opendirSync, readFileSync } from 'fs';
|
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||||
@ -18,6 +18,19 @@ export class MessageRepository extends Repository {
|
|||||||
|
|
||||||
private readonly logger = new Logger('MessageRepository');
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||||
this.logger.verbose('inserting messages');
|
this.logger.verbose('inserting messages');
|
||||||
|
|
||||||
@ -91,14 +104,7 @@ export class MessageRepository extends Repository {
|
|||||||
this.logger.verbose('finding messages');
|
this.logger.verbose('finding messages');
|
||||||
if (this.dbSettings.ENABLED) {
|
if (this.dbSettings.ENABLED) {
|
||||||
this.logger.verbose('finding messages in db');
|
this.logger.verbose('finding messages in db');
|
||||||
for (const [o, p] of Object.entries(query?.where)) {
|
query = this.buildQuery(query);
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.messageModel
|
return await this.messageModel
|
||||||
.find({ ...query.where })
|
.find({ ...query.where })
|
||||||
@ -198,15 +204,23 @@ export class MessageRepository extends Repository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete(query: any) {
|
public async delete(query: MessageQuery) {
|
||||||
try {
|
try {
|
||||||
this.logger.verbose('deleting messages');
|
this.logger.verbose('deleting message');
|
||||||
if (this.dbSettings.ENABLED) {
|
if (this.dbSettings.ENABLED) {
|
||||||
this.logger.verbose('deleting messages in db');
|
this.logger.verbose('deleting message in db');
|
||||||
return await this.messageModel.deleteMany(query);
|
query = this.buildQuery(query);
|
||||||
|
|
||||||
|
return await this.messageModel.deleteOne({ ...query.where });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { deleted: { chatId: query.where.messageTimestamp } };
|
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) {
|
} catch (error) {
|
||||||
return { error: error?.toString() };
|
return { error: error?.toString() };
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
profileSchema,
|
profileSchema,
|
||||||
profileStatusSchema,
|
profileStatusSchema,
|
||||||
readMessageSchema,
|
readMessageSchema,
|
||||||
|
updateMessageSchema,
|
||||||
whatsappNumberSchema,
|
whatsappNumberSchema,
|
||||||
profileBusinessSchema,
|
profileBusinessSchema,
|
||||||
} from '../../validate/validate.schema';
|
} from '../../validate/validate.schema';
|
||||||
@ -29,6 +30,7 @@ import {
|
|||||||
ProfileStatusDto,
|
ProfileStatusDto,
|
||||||
ReadMessageDto,
|
ReadMessageDto,
|
||||||
SendPresenceDto,
|
SendPresenceDto,
|
||||||
|
UpdateMessageDto,
|
||||||
WhatsAppNumberDto,
|
WhatsAppNumberDto,
|
||||||
NumberBusiness,
|
NumberBusiness,
|
||||||
} from '../dto/chat.dto';
|
} from '../dto/chat.dto';
|
||||||
@ -383,6 +385,23 @@ export class ChatRouter extends RouterBroker {
|
|||||||
execute: (instance) => chatController.removeProfilePicture(instance),
|
execute: (instance) => chatController.removeProfilePicture(instance),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
return res.status(HttpStatus.OK).json(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,24 @@
|
|||||||
import ChatwootClient from '@figuro/chatwoot-sdk';
|
import ChatwootClient, { ChatwootAPIConfig, contact, conversation, inbox } from '@figuro/chatwoot-sdk';
|
||||||
|
import { request as chatwootRequest } from '@figuro/chatwoot-sdk/dist/core/request';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
import { createReadStream, unlinkSync, writeFileSync } from 'fs';
|
||||||
import Jimp from 'jimp';
|
import Jimp from 'jimp';
|
||||||
import mimeTypes from 'mime-types';
|
import mimeTypes from 'mime-types';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { ConfigService, HttpServer, WABussiness } from '../../config/env.config';
|
import { ConfigService, HttpServer, WABussiness } from '../../config/env.config';
|
||||||
import { Logger } from '../../config/logger.config';
|
import { Logger } from '../../config/logger.config';
|
||||||
import { ROOT_DIR } from '../../config/path.config';
|
import { ICache } from '../abstract/abstract.cache';
|
||||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||||
import { InstanceDto } from '../dto/instance.dto';
|
import { InstanceDto } from '../dto/instance.dto';
|
||||||
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto, SendTemplateDto } from '../dto/sendMessage.dto';
|
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto, SendTemplateDto } from '../dto/sendMessage.dto';
|
||||||
import { MessageRaw } from '../models';
|
import { ChatwootRaw, MessageRaw } from '../models';
|
||||||
import { RepositoryBroker } from '../repository/repository.manager';
|
import { RepositoryBroker } from '../repository/repository.manager';
|
||||||
import { Events } from '../types/wa.types';
|
import { Events } from '../types/wa.types';
|
||||||
import { WAMonitoringService } from './monitor.service';
|
import { WAMonitoringService } from './monitor.service';
|
||||||
|
|
||||||
export class ChatwootService {
|
export class ChatwootService {
|
||||||
private messageCacheFile: string;
|
|
||||||
private messageCache: Set<string>;
|
|
||||||
|
|
||||||
private readonly logger = new Logger(ChatwootService.name);
|
private readonly logger = new Logger(ChatwootService.name);
|
||||||
|
|
||||||
private provider: any;
|
private provider: any;
|
||||||
@ -29,35 +27,15 @@ export class ChatwootService {
|
|||||||
private readonly waMonitor: WAMonitoringService,
|
private readonly waMonitor: WAMonitoringService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly repository: RepositoryBroker,
|
private readonly repository: RepositoryBroker,
|
||||||
) {
|
private readonly cache: ICache,
|
||||||
this.messageCache = new Set();
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
private loadMessageCache(): Set<string> {
|
|
||||||
this.logger.verbose('load message cache');
|
|
||||||
try {
|
|
||||||
const cacheData = readFileSync(this.messageCacheFile, 'utf-8');
|
|
||||||
const cacheArray = cacheData.split('\n');
|
|
||||||
return new Set(cacheArray);
|
|
||||||
} catch (error) {
|
|
||||||
return new Set();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private saveMessageCache() {
|
|
||||||
this.logger.verbose('save message cache');
|
|
||||||
const cacheData = Array.from(this.messageCache).join('\n');
|
|
||||||
writeFileSync(this.messageCacheFile, cacheData, 'utf-8');
|
|
||||||
this.logger.verbose('message cache saved');
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearMessageCache() {
|
|
||||||
this.logger.verbose('clear message cache');
|
|
||||||
this.messageCache.clear();
|
|
||||||
this.saveMessageCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getProvider(instance: InstanceDto) {
|
private async getProvider(instance: InstanceDto) {
|
||||||
|
const cacheKey = `${instance.instanceName}:getProvider`;
|
||||||
|
if (await this.cache.has(cacheKey)) {
|
||||||
|
return (await this.cache.get(cacheKey)) as ChatwootRaw;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.verbose('get provider to instance: ' + instance.instanceName);
|
this.logger.verbose('get provider to instance: ' + instance.instanceName);
|
||||||
const provider = await this.waMonitor.waInstances[instance.instanceName]?.findChatwoot();
|
const provider = await this.waMonitor.waInstances[instance.instanceName]?.findChatwoot();
|
||||||
|
|
||||||
@ -68,6 +46,8 @@ export class ChatwootService {
|
|||||||
|
|
||||||
this.logger.verbose('provider found');
|
this.logger.verbose('provider found');
|
||||||
|
|
||||||
|
this.cache.set(cacheKey, provider);
|
||||||
|
|
||||||
return provider;
|
return provider;
|
||||||
// try {
|
// try {
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
@ -92,12 +72,7 @@ export class ChatwootService {
|
|||||||
|
|
||||||
this.logger.verbose('create client to instance: ' + instance.instanceName);
|
this.logger.verbose('create client to instance: ' + instance.instanceName);
|
||||||
const client = new ChatwootClient({
|
const client = new ChatwootClient({
|
||||||
config: {
|
config: this.getClientCwConfig(),
|
||||||
basePath: provider.url,
|
|
||||||
with_credentials: true,
|
|
||||||
credentials: 'include',
|
|
||||||
token: provider.token,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.verbose('client created');
|
this.logger.verbose('client created');
|
||||||
@ -105,6 +80,19 @@ export class ChatwootService {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getClientCwConfig(): ChatwootAPIConfig {
|
||||||
|
return {
|
||||||
|
basePath: this.provider.url,
|
||||||
|
with_credentials: true,
|
||||||
|
credentials: 'include',
|
||||||
|
token: this.provider.token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCache() {
|
||||||
|
return this.cache;
|
||||||
|
}
|
||||||
|
|
||||||
public async create(instance: InstanceDto, data: ChatwootDto) {
|
public async create(instance: InstanceDto, data: ChatwootDto) {
|
||||||
this.logger.verbose('create chatwoot: ' + instance.instanceName);
|
this.logger.verbose('create chatwoot: ' + instance.instanceName);
|
||||||
|
|
||||||
@ -419,6 +407,26 @@ export class ChatwootService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cacheKey = `${instance.instanceName}:createConversation-${body.key.remoteJid}`;
|
||||||
|
if (await this.cache.has(cacheKey)) {
|
||||||
|
const conversationId = (await this.cache.get(cacheKey)) as number;
|
||||||
|
let conversationExists: conversation | boolean;
|
||||||
|
try {
|
||||||
|
conversationExists = await client.conversations.get({
|
||||||
|
accountId: this.provider.account_id,
|
||||||
|
conversationId: conversationId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
conversationExists = false;
|
||||||
|
}
|
||||||
|
if (!conversationExists) {
|
||||||
|
this.cache.delete(cacheKey);
|
||||||
|
return await this.createConversation(instance, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
const isGroup = body.key.remoteJid.includes('@g.us');
|
const isGroup = body.key.remoteJid.includes('@g.us');
|
||||||
|
|
||||||
this.logger.verbose('is group: ' + isGroup);
|
this.logger.verbose('is group: ' + isGroup);
|
||||||
@ -569,6 +577,7 @@ export class ChatwootService {
|
|||||||
|
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
this.logger.verbose('conversation found');
|
this.logger.verbose('conversation found');
|
||||||
|
this.cache.set(cacheKey, conversation.id);
|
||||||
return conversation.id;
|
return conversation.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -594,6 +603,7 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.verbose('conversation created');
|
this.logger.verbose('conversation created');
|
||||||
|
this.cache.set(cacheKey, conversation.id);
|
||||||
return conversation.id;
|
return conversation.id;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
@ -603,6 +613,11 @@ export class ChatwootService {
|
|||||||
public async getInbox(instance: InstanceDto) {
|
public async getInbox(instance: InstanceDto) {
|
||||||
this.logger.verbose('get inbox to instance: ' + instance.instanceName);
|
this.logger.verbose('get inbox to instance: ' + instance.instanceName);
|
||||||
|
|
||||||
|
const cacheKey = `${instance.instanceName}:getInbox`;
|
||||||
|
if (await this.cache.has(cacheKey)) {
|
||||||
|
return (await this.cache.get(cacheKey)) as inbox;
|
||||||
|
}
|
||||||
|
|
||||||
const client = await this.clientCw(instance);
|
const client = await this.clientCw(instance);
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@ -629,6 +644,7 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.verbose('return inbox');
|
this.logger.verbose('return inbox');
|
||||||
|
this.cache.set(cacheKey, findByName);
|
||||||
return findByName;
|
return findByName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,6 +660,7 @@ export class ChatwootService {
|
|||||||
filename: string;
|
filename: string;
|
||||||
}[],
|
}[],
|
||||||
messageBody?: any,
|
messageBody?: any,
|
||||||
|
sourceId?: string,
|
||||||
) {
|
) {
|
||||||
this.logger.verbose('create message to instance: ' + instance.instanceName);
|
this.logger.verbose('create message to instance: ' + instance.instanceName);
|
||||||
|
|
||||||
@ -665,6 +682,7 @@ export class ChatwootService {
|
|||||||
message_type: messageType,
|
message_type: messageType,
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
private: privateMessage || false,
|
private: privateMessage || false,
|
||||||
|
source_id: sourceId,
|
||||||
content_attributes: {
|
content_attributes: {
|
||||||
...replyToIds,
|
...replyToIds,
|
||||||
},
|
},
|
||||||
@ -765,6 +783,7 @@ export class ChatwootService {
|
|||||||
content?: string,
|
content?: string,
|
||||||
instance?: InstanceDto,
|
instance?: InstanceDto,
|
||||||
messageBody?: any,
|
messageBody?: any,
|
||||||
|
sourceId?: string,
|
||||||
) {
|
) {
|
||||||
this.logger.verbose('send data to chatwoot');
|
this.logger.verbose('send data to chatwoot');
|
||||||
|
|
||||||
@ -791,6 +810,10 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sourceId) {
|
||||||
|
data.append('source_id', sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.verbose('get client to instance: ' + this.provider.instanceName);
|
this.logger.verbose('get client to instance: ' + this.provider.instanceName);
|
||||||
const config = {
|
const config = {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -916,17 +939,21 @@ export class ChatwootService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.logger.verbose('get media type');
|
this.logger.verbose('get media type');
|
||||||
const parts = media.split('/');
|
const parsedMedia = path.parse(decodeURIComponent(media));
|
||||||
|
let mimeType = mimeTypes.lookup(parsedMedia?.ext) || '';
|
||||||
|
let fileName = parsedMedia?.name + parsedMedia?.ext;
|
||||||
|
|
||||||
const fileName = decodeURIComponent(parts[parts.length - 1]);
|
if (!mimeType) {
|
||||||
this.logger.verbose('file name: ' + fileName);
|
const parts = media.split('/');
|
||||||
|
fileName = decodeURIComponent(parts[parts.length - 1]);
|
||||||
|
this.logger.verbose('file name: ' + fileName);
|
||||||
|
|
||||||
const response = await axios.get(media, {
|
const response = await axios.get(media, {
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
});
|
});
|
||||||
|
mimeType = response.headers['content-type'];
|
||||||
const mimeType = response.headers['content-type'];
|
this.logger.verbose('mime type: ' + mimeType);
|
||||||
this.logger.verbose('mime type: ' + mimeType);
|
}
|
||||||
|
|
||||||
let type = 'document';
|
let type = 'document';
|
||||||
|
|
||||||
@ -1008,6 +1035,17 @@ export class ChatwootService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invalidate the conversation cache if reopen_conversation is false and the conversation was resolved
|
||||||
|
if (
|
||||||
|
this.provider.reopen_conversation === false &&
|
||||||
|
body.event === 'conversation_status_changed' &&
|
||||||
|
body.status === 'resolved' &&
|
||||||
|
body.meta?.sender?.identifier
|
||||||
|
) {
|
||||||
|
const keyToDelete = `${instance.instanceName}:createConversation-${body.meta.sender.identifier}`;
|
||||||
|
this.cache.delete(keyToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.verbose('check if is bot');
|
this.logger.verbose('check if is bot');
|
||||||
if (
|
if (
|
||||||
!body?.conversation ||
|
!body?.conversation ||
|
||||||
@ -1029,7 +1067,7 @@ export class ChatwootService {
|
|||||||
.replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ```
|
.replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ```
|
||||||
: body.content;
|
: body.content;
|
||||||
|
|
||||||
const senderName = body?.sender?.available_name || body?.sender?.name;
|
const senderName = body?.conversation?.messages[0]?.sender?.available_name || body?.sender?.name;
|
||||||
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
||||||
|
|
||||||
this.logger.verbose('check if is a message deletion');
|
this.logger.verbose('check if is a message deletion');
|
||||||
@ -1044,7 +1082,18 @@ export class ChatwootService {
|
|||||||
limit: 1,
|
limit: 1,
|
||||||
});
|
});
|
||||||
if (message.length && message[0].key?.id) {
|
if (message.length && message[0].key?.id) {
|
||||||
|
this.logger.verbose('deleting message in whatsapp. Message id: ' + message[0].key.id);
|
||||||
await waInstance?.client?.sendMessage(message[0].key.remoteJid, { delete: message[0].key });
|
await waInstance?.client?.sendMessage(message[0].key.remoteJid, { delete: message[0].key });
|
||||||
|
|
||||||
|
this.logger.verbose('deleting message in repository. Message id: ' + message[0].key.id);
|
||||||
|
this.repository.message.delete({
|
||||||
|
where: {
|
||||||
|
owner: instance.instanceName,
|
||||||
|
chatwoot: {
|
||||||
|
messageId: body.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return { message: 'bot' };
|
return { message: 'bot' };
|
||||||
}
|
}
|
||||||
@ -1105,22 +1154,11 @@ export class ChatwootService {
|
|||||||
if (body.message_type === 'outgoing' && body?.conversation?.messages?.length && chatId !== '123456') {
|
if (body.message_type === 'outgoing' && body?.conversation?.messages?.length && chatId !== '123456') {
|
||||||
this.logger.verbose('check if is group');
|
this.logger.verbose('check if is group');
|
||||||
|
|
||||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
if (body?.conversation?.messages[0]?.source_id?.substring(0, 5) === 'WAID:') {
|
||||||
this.logger.verbose('cache file path: ' + this.messageCacheFile);
|
this.logger.verbose('message sent directly from whatsapp. Webhook ignored.');
|
||||||
|
|
||||||
this.messageCache = this.loadMessageCache();
|
|
||||||
this.logger.verbose('cache file loaded');
|
|
||||||
this.logger.verbose(this.messageCache);
|
|
||||||
|
|
||||||
this.logger.verbose('check if message is cached');
|
|
||||||
if (this.messageCache.has(body.id.toString())) {
|
|
||||||
this.logger.verbose('message is cached');
|
|
||||||
return { message: 'bot' };
|
return { message: 'bot' };
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.verbose('clear cache');
|
|
||||||
this.clearMessageCache();
|
|
||||||
|
|
||||||
this.logger.verbose('Format message to send');
|
this.logger.verbose('Format message to send');
|
||||||
let formatText: string;
|
let formatText: string;
|
||||||
const regex = /^▶️.*◀️$/;
|
const regex = /^▶️.*◀️$/;
|
||||||
@ -1197,6 +1235,9 @@ export class ChatwootService {
|
|||||||
messageId: body.id,
|
messageId: body.id,
|
||||||
inboxId: body.inbox?.id,
|
inboxId: body.inbox?.id,
|
||||||
conversationId: body.conversation?.id,
|
conversationId: body.conversation?.id,
|
||||||
|
contactInbox: {
|
||||||
|
sourceId: body.conversation?.contact_inbox?.source_id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
instance,
|
instance,
|
||||||
);
|
);
|
||||||
@ -1228,6 +1269,9 @@ export class ChatwootService {
|
|||||||
messageId: body.id,
|
messageId: body.id,
|
||||||
inboxId: body.inbox?.id,
|
inboxId: body.inbox?.id,
|
||||||
conversationId: body.conversation?.id,
|
conversationId: body.conversation?.id,
|
||||||
|
contactInbox: {
|
||||||
|
sourceId: body.conversation?.contact_inbox?.source_id,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
instance,
|
instance,
|
||||||
);
|
);
|
||||||
@ -1396,6 +1440,8 @@ export class ChatwootService {
|
|||||||
contactsArrayMessage: msg.contactsArrayMessage,
|
contactsArrayMessage: msg.contactsArrayMessage,
|
||||||
locationMessage: msg.locationMessage,
|
locationMessage: msg.locationMessage,
|
||||||
liveLocationMessage: msg.liveLocationMessage,
|
liveLocationMessage: msg.liveLocationMessage,
|
||||||
|
listMessage: msg.listMessage,
|
||||||
|
listResponseMessage: msg.listResponseMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.verbose('type message: ' + types);
|
this.logger.verbose('type message: ' + types);
|
||||||
@ -1413,11 +1459,27 @@ export class ChatwootService {
|
|||||||
const latitude = result.degreesLatitude;
|
const latitude = result.degreesLatitude;
|
||||||
const longitude = result.degreesLongitude;
|
const longitude = result.degreesLongitude;
|
||||||
|
|
||||||
const formattedLocation = `**Location:**
|
const locationName = result?.name || 'Unknown';
|
||||||
**latitude:** ${latitude}
|
const locationAddress = result?.address || 'Unknown';
|
||||||
**longitude:** ${longitude}
|
|
||||||
https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}
|
const formattedLocation =
|
||||||
`;
|
'*Localização:*\n\n' +
|
||||||
|
'_Latitude:_ ' +
|
||||||
|
latitude +
|
||||||
|
'\n' +
|
||||||
|
'_Longitude:_ ' +
|
||||||
|
longitude +
|
||||||
|
'\n' +
|
||||||
|
'_Nome:_ ' +
|
||||||
|
locationName +
|
||||||
|
'\n' +
|
||||||
|
'_Endereço:_ ' +
|
||||||
|
locationAddress +
|
||||||
|
'\n' +
|
||||||
|
'_Url:_ https://www.google.com/maps/search/?api=1&query=' +
|
||||||
|
latitude +
|
||||||
|
',' +
|
||||||
|
longitude;
|
||||||
|
|
||||||
this.logger.verbose('message content: ' + formattedLocation);
|
this.logger.verbose('message content: ' + formattedLocation);
|
||||||
|
|
||||||
@ -1435,19 +1497,17 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let formattedContact = `**Contact:**
|
let formattedContact = '*Contact:*\n\n' + '_Name:_ ' + contactInfo['FN'];
|
||||||
**name:** ${contactInfo['FN']}`;
|
|
||||||
|
|
||||||
let numberCount = 1;
|
let numberCount = 1;
|
||||||
Object.keys(contactInfo).forEach((key) => {
|
Object.keys(contactInfo).forEach((key) => {
|
||||||
if (key.startsWith('item') && key.includes('TEL')) {
|
if (key.startsWith('item') && key.includes('TEL')) {
|
||||||
const phoneNumber = contactInfo[key];
|
const phoneNumber = contactInfo[key];
|
||||||
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
|
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||||
numberCount++;
|
numberCount++;
|
||||||
}
|
} else if (key.includes('TEL')) {
|
||||||
if (key.includes('TEL')) {
|
|
||||||
const phoneNumber = contactInfo[key];
|
const phoneNumber = contactInfo[key];
|
||||||
formattedContact += `\n**number:** ${phoneNumber}`;
|
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||||
numberCount++;
|
numberCount++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1468,19 +1528,17 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let formattedContact = `**Contact:**
|
let formattedContact = '*Contact:*\n\n' + '_Name:_ ' + contact.displayName;
|
||||||
**name:** ${contact.displayName}`;
|
|
||||||
|
|
||||||
let numberCount = 1;
|
let numberCount = 1;
|
||||||
Object.keys(contactInfo).forEach((key) => {
|
Object.keys(contactInfo).forEach((key) => {
|
||||||
if (key.startsWith('item') && key.includes('TEL')) {
|
if (key.startsWith('item') && key.includes('TEL')) {
|
||||||
const phoneNumber = contactInfo[key];
|
const phoneNumber = contactInfo[key];
|
||||||
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
|
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||||
numberCount++;
|
numberCount++;
|
||||||
}
|
} else if (key.includes('TEL')) {
|
||||||
if (key.includes('TEL')) {
|
|
||||||
const phoneNumber = contactInfo[key];
|
const phoneNumber = contactInfo[key];
|
||||||
formattedContact += `\n**number:** ${phoneNumber}`;
|
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||||
numberCount++;
|
numberCount++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1495,6 +1553,62 @@ export class ChatwootService {
|
|||||||
return formattedContactsArray;
|
return formattedContactsArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeKey === 'listMessage') {
|
||||||
|
const listTitle = result?.title || 'Unknown';
|
||||||
|
const listDescription = result?.description || 'Unknown';
|
||||||
|
const listFooter = result?.footerText || 'Unknown';
|
||||||
|
|
||||||
|
let formattedList =
|
||||||
|
'*List Menu:*\n\n' +
|
||||||
|
'_Title_: ' +
|
||||||
|
listTitle +
|
||||||
|
'\n' +
|
||||||
|
'_Description_: ' +
|
||||||
|
listDescription +
|
||||||
|
'\n' +
|
||||||
|
'_Footer_: ' +
|
||||||
|
listFooter;
|
||||||
|
|
||||||
|
if (result.sections && result.sections.length > 0) {
|
||||||
|
result.sections.forEach((section, sectionIndex) => {
|
||||||
|
formattedList += '\n\n*Section ' + (sectionIndex + 1) + ':* ' + section.title || 'Unknown\n';
|
||||||
|
|
||||||
|
if (section.rows && section.rows.length > 0) {
|
||||||
|
section.rows.forEach((row, rowIndex) => {
|
||||||
|
formattedList += '\n*Line ' + (rowIndex + 1) + ':*\n';
|
||||||
|
formattedList += '_▪️ Title:_ ' + (row.title || 'Unknown') + '\n';
|
||||||
|
formattedList += '_▪️ Description:_ ' + (row.description || 'Unknown') + '\n';
|
||||||
|
formattedList += '_▪️ ID:_ ' + (row.rowId || 'Unknown') + '\n';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
formattedList += '\nNo lines found in this section.\n';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
formattedList += '\nNo sections found.\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeKey === 'listResponseMessage') {
|
||||||
|
const responseTitle = result?.title || 'Unknown';
|
||||||
|
const responseDescription = result?.description || 'Unknown';
|
||||||
|
const responseRowId = result?.singleSelectReply?.selectedRowId || 'Unknown';
|
||||||
|
|
||||||
|
const formattedResponseList =
|
||||||
|
'*List Response:*\n\n' +
|
||||||
|
'_Title_: ' +
|
||||||
|
responseTitle +
|
||||||
|
'\n' +
|
||||||
|
'_Description_: ' +
|
||||||
|
responseDescription +
|
||||||
|
'\n' +
|
||||||
|
'_ID_: ' +
|
||||||
|
responseRowId;
|
||||||
|
return formattedResponseList;
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.verbose('message content: ' + result);
|
this.logger.verbose('message content: ' + result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -1591,8 +1705,21 @@ export class ChatwootService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const random = Math.random().toString(36).substring(7);
|
let nameFile: string;
|
||||||
const nameFile = `${random}.${mimeTypes.extension(downloadBase64.mimetype)}`;
|
const messageBody = body?.message[body?.messageType];
|
||||||
|
const originalFilename = messageBody?.fileName || messageBody?.message?.documentMessage?.fileName;
|
||||||
|
if (originalFilename) {
|
||||||
|
const parsedFile = path.parse(originalFilename);
|
||||||
|
if (parsedFile.name && parsedFile.ext) {
|
||||||
|
nameFile = `${parsedFile.name}-${Math.floor(Math.random() * (99 - 10 + 1) + 10)}${parsedFile.ext}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nameFile) {
|
||||||
|
nameFile = `${Math.random().toString(36).substring(7)}.${
|
||||||
|
mimeTypes.extension(downloadBase64.mimetype) || ''
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
const fileData = Buffer.from(downloadBase64.base64, 'base64');
|
const fileData = Buffer.from(downloadBase64.base64, 'base64');
|
||||||
|
|
||||||
@ -1620,43 +1747,41 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.verbose('send data to chatwoot');
|
this.logger.verbose('send data to chatwoot');
|
||||||
const send = await this.sendData(getConversation, fileName, messageType, content, instance, body);
|
const send = await this.sendData(
|
||||||
|
getConversation,
|
||||||
|
fileName,
|
||||||
|
messageType,
|
||||||
|
content,
|
||||||
|
instance,
|
||||||
|
body,
|
||||||
|
'WAID:' + body.key.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (!send) {
|
if (!send) {
|
||||||
this.logger.warn('message not sent');
|
this.logger.warn('message not sent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
|
||||||
|
|
||||||
this.messageCache = this.loadMessageCache();
|
|
||||||
|
|
||||||
this.messageCache.add(send.id.toString());
|
|
||||||
|
|
||||||
this.logger.verbose('save message cache');
|
|
||||||
this.saveMessageCache();
|
|
||||||
|
|
||||||
return send;
|
return send;
|
||||||
} else {
|
} else {
|
||||||
this.logger.verbose('message is not group');
|
this.logger.verbose('message is not group');
|
||||||
|
|
||||||
this.logger.verbose('send data to chatwoot');
|
this.logger.verbose('send data to chatwoot');
|
||||||
const send = await this.sendData(getConversation, fileName, messageType, bodyMessage, instance, body);
|
const send = await this.sendData(
|
||||||
|
getConversation,
|
||||||
|
fileName,
|
||||||
|
messageType,
|
||||||
|
bodyMessage,
|
||||||
|
instance,
|
||||||
|
body,
|
||||||
|
'WAID:' + body.key.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (!send) {
|
if (!send) {
|
||||||
this.logger.warn('message not sent');
|
this.logger.warn('message not sent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
|
||||||
|
|
||||||
this.messageCache = this.loadMessageCache();
|
|
||||||
|
|
||||||
this.messageCache.add(send.id.toString());
|
|
||||||
|
|
||||||
this.logger.verbose('save message cache');
|
|
||||||
this.saveMessageCache();
|
|
||||||
|
|
||||||
return send;
|
return send;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1675,16 +1800,12 @@ export class ChatwootService {
|
|||||||
{
|
{
|
||||||
message: { extendedTextMessage: { contextInfo: { stanzaId: reactionMessage.key.id } } },
|
message: { extendedTextMessage: { contextInfo: { stanzaId: reactionMessage.key.id } } },
|
||||||
},
|
},
|
||||||
|
'WAID:' + body.key.id,
|
||||||
);
|
);
|
||||||
if (!send) {
|
if (!send) {
|
||||||
this.logger.warn('message not sent');
|
this.logger.warn('message not sent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
|
||||||
this.messageCache = this.loadMessageCache();
|
|
||||||
this.messageCache.add(send.id.toString());
|
|
||||||
this.logger.verbose('save message cache');
|
|
||||||
this.saveMessageCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -1734,6 +1855,7 @@ export class ChatwootService {
|
|||||||
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
|
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
|
||||||
instance,
|
instance,
|
||||||
body,
|
body,
|
||||||
|
'WAID:' + body.key.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!send) {
|
if (!send) {
|
||||||
@ -1741,15 +1863,6 @@ export class ChatwootService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
|
||||||
|
|
||||||
this.messageCache = this.loadMessageCache();
|
|
||||||
|
|
||||||
this.messageCache.add(send.id.toString());
|
|
||||||
|
|
||||||
this.logger.verbose('save message cache');
|
|
||||||
this.saveMessageCache();
|
|
||||||
|
|
||||||
return send;
|
return send;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1769,43 +1882,43 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.verbose('send data to chatwoot');
|
this.logger.verbose('send data to chatwoot');
|
||||||
const send = await this.createMessage(instance, getConversation, content, messageType, false, [], body);
|
const send = await this.createMessage(
|
||||||
|
instance,
|
||||||
|
getConversation,
|
||||||
|
content,
|
||||||
|
messageType,
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
body,
|
||||||
|
'WAID:' + body.key.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (!send) {
|
if (!send) {
|
||||||
this.logger.warn('message not sent');
|
this.logger.warn('message not sent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
|
||||||
|
|
||||||
this.messageCache = this.loadMessageCache();
|
|
||||||
|
|
||||||
this.messageCache.add(send.id.toString());
|
|
||||||
|
|
||||||
this.logger.verbose('save message cache');
|
|
||||||
this.saveMessageCache();
|
|
||||||
|
|
||||||
return send;
|
return send;
|
||||||
} else {
|
} else {
|
||||||
this.logger.verbose('message is not group');
|
this.logger.verbose('message is not group');
|
||||||
|
|
||||||
this.logger.verbose('send data to chatwoot');
|
this.logger.verbose('send data to chatwoot');
|
||||||
const send = await this.createMessage(instance, getConversation, bodyMessage, messageType, false, [], body);
|
const send = await this.createMessage(
|
||||||
|
instance,
|
||||||
|
getConversation,
|
||||||
|
bodyMessage,
|
||||||
|
messageType,
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
body,
|
||||||
|
'WAID:' + body.key.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (!send) {
|
if (!send) {
|
||||||
this.logger.warn('message not sent');
|
this.logger.warn('message not sent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
|
||||||
|
|
||||||
this.messageCache = this.loadMessageCache();
|
|
||||||
|
|
||||||
this.messageCache.add(send.id.toString());
|
|
||||||
|
|
||||||
this.logger.verbose('save message cache');
|
|
||||||
this.saveMessageCache();
|
|
||||||
|
|
||||||
return send;
|
return send;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1820,6 +1933,16 @@ export class ChatwootService {
|
|||||||
|
|
||||||
const message = await this.getMessageByKeyId(instance, body.key.id);
|
const message = await this.getMessageByKeyId(instance, body.key.id);
|
||||||
if (message?.chatwoot?.messageId && message?.chatwoot?.conversationId) {
|
if (message?.chatwoot?.messageId && message?.chatwoot?.conversationId) {
|
||||||
|
this.logger.verbose('deleting message in repository. Message id: ' + body.key.id);
|
||||||
|
this.repository.message.delete({
|
||||||
|
where: {
|
||||||
|
key: {
|
||||||
|
id: body.key.id,
|
||||||
|
},
|
||||||
|
owner: instance.instanceName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.logger.verbose('deleting message in chatwoot. Message id: ' + body.key.id);
|
this.logger.verbose('deleting message in chatwoot. Message id: ' + body.key.id);
|
||||||
return await client.messages.delete({
|
return await client.messages.delete({
|
||||||
accountId: this.provider.account_id,
|
accountId: this.provider.account_id,
|
||||||
@ -1829,6 +1952,44 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event === 'messages.read') {
|
||||||
|
this.logger.verbose('read message from instance: ' + instance.instanceName);
|
||||||
|
|
||||||
|
if (!body?.key?.id || !body?.key?.remoteJid) {
|
||||||
|
this.logger.warn('message id not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = await this.getMessageByKeyId(instance, body.key.id);
|
||||||
|
const { conversationId, contactInbox } = message?.chatwoot || {};
|
||||||
|
if (conversationId) {
|
||||||
|
let sourceId = contactInbox?.sourceId;
|
||||||
|
const inbox = (await this.getInbox(instance)) as inbox & {
|
||||||
|
inbox_identifier?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!sourceId && inbox) {
|
||||||
|
const contact = (await this.findContact(
|
||||||
|
instance,
|
||||||
|
this.getNumberFromRemoteJid(body.key.remoteJid),
|
||||||
|
)) as contact;
|
||||||
|
const contactInbox = contact?.contact_inboxes?.find((contactInbox) => contactInbox?.inbox?.id === inbox.id);
|
||||||
|
sourceId = contactInbox?.source_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceId && inbox?.inbox_identifier) {
|
||||||
|
const url =
|
||||||
|
`/public/api/v1/inboxes/${inbox.inbox_identifier}/contacts/${sourceId}` +
|
||||||
|
`/conversations/${conversationId}/update_last_seen`;
|
||||||
|
chatwootRequest(this.getClientCwConfig(), {
|
||||||
|
method: 'POST',
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event === 'status.instance') {
|
if (event === 'status.instance') {
|
||||||
this.logger.verbose('event status.instance');
|
this.logger.verbose('event status.instance');
|
||||||
const data = body;
|
const data = body;
|
||||||
@ -1854,6 +2015,7 @@ export class ChatwootService {
|
|||||||
const msgConnection = `🚀 Connection successfully established!`;
|
const msgConnection = `🚀 Connection successfully established!`;
|
||||||
this.logger.verbose('send message to chatwoot');
|
this.logger.verbose('send message to chatwoot');
|
||||||
await this.createBotMessage(instance, msgConnection, 'incoming');
|
await this.createBotMessage(instance, msgConnection, 'incoming');
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].qrCode.count = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1899,4 +2061,8 @@ export class ChatwootService {
|
|||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getNumberFromRemoteJid(remoteJid: string) {
|
||||||
|
return remoteJid.replace(/:\d+/, '').split('@')[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
WebsocketModel,
|
WebsocketModel,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { RepositoryBroker } from '../repository/repository.manager';
|
import { RepositoryBroker } from '../repository/repository.manager';
|
||||||
|
import { CacheService } from './cache.service';
|
||||||
import { WAStartupService } from './whatsapp.service';
|
import { WAStartupService } from './whatsapp.service';
|
||||||
import { WAStartupClass } from '../whatsapp.module';
|
import { WAStartupClass } from '../whatsapp.module';
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ export class WAMonitoringService {
|
|||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly repository: RepositoryBroker,
|
private readonly repository: RepositoryBroker,
|
||||||
private readonly cache: RedisCache,
|
private readonly cache: RedisCache,
|
||||||
|
private readonly chatwootCache: CacheService,
|
||||||
) {
|
) {
|
||||||
this.logger.verbose('instance created');
|
this.logger.verbose('instance created');
|
||||||
|
|
||||||
@ -360,14 +362,13 @@ export class WAMonitoringService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async setInstance(name: string) {
|
private async setInstance(name: string) {
|
||||||
const path = join(INSTANCE_DIR, name);
|
const instance = new WAStartupService(
|
||||||
let values: any;
|
this.configService,
|
||||||
if(this.db.ENABLED )
|
this.eventEmitter,
|
||||||
values = await this.dbInstance.collection(name).findOne({ _id: 'integration' })
|
this.repository,
|
||||||
else
|
this.cache,
|
||||||
values = JSON.parse(readFileSync(path + '/integration.json', 'utf8'));
|
this.chatwootCache,
|
||||||
const instance = new WAStartupClass[values.integration]
|
);
|
||||||
(this.configService, this.eventEmitter, this.repository, this.cache);
|
|
||||||
instance.instanceName = name;
|
instance.instanceName = name;
|
||||||
instance.instanceNumber = values.number;
|
instance.instanceNumber = values.number;
|
||||||
instance.instanceToken = values.token;
|
instance.instanceToken = values.token;
|
||||||
@ -451,6 +452,7 @@ export class WAMonitoringService {
|
|||||||
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
|
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
|
||||||
this.logger.verbose('logout instance: ' + instanceName);
|
this.logger.verbose('logout instance: ' + instanceName);
|
||||||
try {
|
try {
|
||||||
|
this.waInstances[instanceName]?.clearCacheChatwoot();
|
||||||
this.logger.verbose('request cleaning up instance: ' + instanceName);
|
this.logger.verbose('request cleaning up instance: ' + instanceName);
|
||||||
this.cleaningUp(instanceName);
|
this.cleaningUp(instanceName);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -27,7 +27,7 @@ export class ProxyService {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { enabled: false, proxy: '' };
|
return { enabled: false, proxy: null };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,6 +389,7 @@ export class TypebotService {
|
|||||||
input,
|
input,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
this.eventEmitter,
|
this.eventEmitter,
|
||||||
|
applyFormatting,
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
console.error('Erro ao processar mensagens:', err);
|
console.error('Erro ao processar mensagens:', err);
|
||||||
});
|
});
|
||||||
@ -403,85 +404,71 @@ export class TypebotService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyFormatting(element) {
|
||||||
|
let text = '';
|
||||||
|
|
||||||
async function processMessages(instance, messages, input, clientSideActions, eventEmitter) {
|
if (element.text) {
|
||||||
let qtdMessages = 0, buttonText = '';
|
text += element.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.type === 'p' || element.type === 'inline-variable' || element.type === 'a') {
|
||||||
|
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) {
|
for (const message of messages) {
|
||||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||||
|
|
||||||
if (message.type === 'text') {
|
if (message.type === 'text') {
|
||||||
let formattedText = '';
|
let formattedText = '';
|
||||||
|
|
||||||
let linkPreview = false;
|
|
||||||
|
|
||||||
for (const richText of message.content.richText) {
|
for (const richText of message.content.richText) {
|
||||||
if (richText.type === 'variable') {
|
for (const element of richText.children) {
|
||||||
for (const child of richText.children) {
|
formattedText += applyFormatting(element);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
formattedText += '\n';
|
formattedText += '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
formattedText = formattedText.replace(/\n$/, '');
|
formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
|
||||||
qtdMessages++;
|
|
||||||
if (instance?.constructor.name == Integration.WABussinessService &&
|
await instance.textMessage({
|
||||||
input?.type === 'choice input' && messages.length == qtdMessages) {
|
number: remoteJid.split('@')[0],
|
||||||
buttonText = formattedText;
|
options: {
|
||||||
} else {
|
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||||
await instance.textMessage({
|
presence: 'composing',
|
||||||
number: remoteJid.split('@')[0],
|
},
|
||||||
options: {
|
textMessage: {
|
||||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
text: formattedText,
|
||||||
presence: 'composing',
|
},
|
||||||
linkPreview: linkPreview,
|
});
|
||||||
},
|
|
||||||
textMessage: {
|
|
||||||
text: formattedText,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'image') {
|
if (message.type === 'image') {
|
||||||
await instance.mediaMessage({
|
await instance.mediaMessage({
|
||||||
number: remoteJid.split('@')[0],
|
number: remoteJid.split('@')[0],
|
||||||
@ -570,6 +557,19 @@ export class TypebotService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formattedText = formattedText.replace(/\n$/, '');
|
||||||
|
|
||||||
|
await instance.textMessage({
|
||||||
|
number: remoteJid.split('@')[0],
|
||||||
|
options: {
|
||||||
|
delay: 1200,
|
||||||
|
presence: 'composing',
|
||||||
|
},
|
||||||
|
textMessage: {
|
||||||
|
text: formattedText,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eventEmitter.emit('typebot:end', {
|
eventEmitter.emit('typebot:end', {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -112,9 +112,17 @@ export declare namespace wa {
|
|||||||
sessions?: Session[];
|
sessions?: Session[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Proxy = {
|
||||||
|
host?: string;
|
||||||
|
port?: string;
|
||||||
|
protocol?: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type LocalProxy = {
|
export type LocalProxy = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
proxy?: string;
|
proxy?: Proxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LocalChamaai = {
|
export type LocalChamaai = {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { configService } from '../config/env.config';
|
import { configService } from '../config/env.config';
|
||||||
import { eventEmitter } from '../config/event.config';
|
import { eventEmitter } from '../config/event.config';
|
||||||
import { Logger } from '../config/logger.config';
|
import { Logger } from '../config/logger.config';
|
||||||
|
import { CacheEngine } from '../libs/cacheengine';
|
||||||
import { dbserver } from '../libs/db.connect';
|
import { dbserver } from '../libs/db.connect';
|
||||||
import { RedisCache } from '../libs/redis.client';
|
import { RedisCache } from '../libs/redis.client';
|
||||||
import { ChamaaiController } from './controllers/chamaai.controller';
|
import { ChamaaiController } from './controllers/chamaai.controller';
|
||||||
@ -48,6 +49,7 @@ import { TypebotRepository } from './repository/typebot.repository';
|
|||||||
import { WebhookRepository } from './repository/webhook.repository';
|
import { WebhookRepository } from './repository/webhook.repository';
|
||||||
import { WebsocketRepository } from './repository/websocket.repository';
|
import { WebsocketRepository } from './repository/websocket.repository';
|
||||||
import { AuthService } from './services/auth.service';
|
import { AuthService } from './services/auth.service';
|
||||||
|
import { CacheService } from './services/cache.service';
|
||||||
import { ChamaaiService } from './services/chamaai.service';
|
import { ChamaaiService } from './services/chamaai.service';
|
||||||
import { ChatwootService } from './services/chatwoot.service';
|
import { ChatwootService } from './services/chatwoot.service';
|
||||||
import { WAMonitoringService } from './services/monitor.service';
|
import { WAMonitoringService } from './services/monitor.service';
|
||||||
@ -102,7 +104,9 @@ export const repository = new RepositoryBroker(
|
|||||||
|
|
||||||
export const cache = new RedisCache();
|
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);
|
const authService = new AuthService(configService, waMonitor, repository);
|
||||||
|
|
||||||
@ -134,7 +138,7 @@ const sqsService = new SqsService(waMonitor);
|
|||||||
|
|
||||||
export const sqsController = new SqsController(sqsService);
|
export const sqsController = new SqsController(sqsService);
|
||||||
|
|
||||||
const chatwootService = new ChatwootService(waMonitor, configService, repository);
|
const chatwootService = new ChatwootService(waMonitor, configService, repository, chatwootCache);
|
||||||
|
|
||||||
export const chatwootController = new ChatwootController(chatwootService, configService, repository);
|
export const chatwootController = new ChatwootController(chatwootService, configService, repository);
|
||||||
|
|
||||||
@ -153,10 +157,10 @@ export const instanceController = new InstanceController(
|
|||||||
settingsService,
|
settingsService,
|
||||||
websocketService,
|
websocketService,
|
||||||
rabbitmqService,
|
rabbitmqService,
|
||||||
proxyService,
|
|
||||||
sqsService,
|
sqsService,
|
||||||
typebotService,
|
typebotService,
|
||||||
cache,
|
cache,
|
||||||
|
chatwootCache,
|
||||||
);
|
);
|
||||||
export const sendMessageController = new SendMessageController(waMonitor);
|
export const sendMessageController = new SendMessageController(waMonitor);
|
||||||
export const chatController = new ChatController(waMonitor);
|
export const chatController = new ChatController(waMonitor);
|
||||||
|
Loading…
Reference in New Issue
Block a user