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)
|
||||
|
||||
### Fixed
|
||||
|
@ -1,6 +1,6 @@
|
||||
FROM node:20.7.0-alpine AS builder
|
||||
|
||||
LABEL version="1.6.1" description="Api to control whatsapp features through http requests."
|
||||
LABEL version="1.6.2" description="Api to control whatsapp features through http requests."
|
||||
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
|
||||
LABEL contact="contato@agenciadgcode.com"
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "evolution-api",
|
||||
"version": "1.6.1",
|
||||
"version": "1.6.2",
|
||||
"description": "Rest api for communication with WhatsApp",
|
||||
"main": "./dist/src/main.js",
|
||||
"scripts": {
|
||||
@ -46,7 +46,7 @@
|
||||
"@figuro/chatwoot-sdk": "^1.1.16",
|
||||
"@hapi/boom": "^10.0.1",
|
||||
"@sentry/node": "^7.59.2",
|
||||
"@whiskeysockets/baileys": "github:PurpShell/Baileys#combined",
|
||||
"@whiskeysockets/baileys": "^6.5.0",
|
||||
"amqplib": "^0.10.3",
|
||||
"aws-sdk": "^2.1499.0",
|
||||
"axios": "^1.3.5",
|
||||
|
@ -136,11 +136,22 @@ export type GlobalWebhook = {
|
||||
ENABLED: boolean;
|
||||
WEBHOOK_BY_EVENTS: boolean;
|
||||
};
|
||||
export type CacheConfRedis = {
|
||||
ENABLED: boolean;
|
||||
URI: string;
|
||||
PREFIX_KEY: string;
|
||||
TTL: number;
|
||||
};
|
||||
export type CacheConfLocal = {
|
||||
ENABLED: boolean;
|
||||
TTL: number;
|
||||
};
|
||||
export type SslConf = { PRIVKEY: string; FULLCHAIN: string };
|
||||
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
|
||||
export type ConfigSessionPhone = { CLIENT: string; NAME: string };
|
||||
export type QrCode = { LIMIT: number; COLOR: string };
|
||||
export type Typebot = { API_VERSION: string; KEEP_OPEN: boolean };
|
||||
export type CacheConf = { REDIS: CacheConfRedis; LOCAL: CacheConfLocal };
|
||||
export type Production = boolean;
|
||||
|
||||
export interface Env {
|
||||
@ -160,6 +171,7 @@ export interface Env {
|
||||
CONFIG_SESSION_PHONE: ConfigSessionPhone;
|
||||
QRCODE: QrCode;
|
||||
TYPEBOT: Typebot;
|
||||
CACHE: CacheConf;
|
||||
AUTHENTICATION: Auth;
|
||||
PRODUCTION?: Production;
|
||||
WABUSSINESS: WABussiness;
|
||||
@ -326,6 +338,18 @@ export class ConfigService {
|
||||
API_VERSION: process.env?.TYPEBOT_API_VERSION || 'old',
|
||||
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: {
|
||||
TYPE: process.env.AUTHENTICATION_TYPE as 'apikey',
|
||||
API_KEY: {
|
||||
|
@ -162,6 +162,17 @@ TYPEBOT:
|
||||
API_VERSION: 'old' # old | latest
|
||||
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
|
||||
# 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
|
||||
|
@ -25,7 +25,7 @@ info:
|
||||
</font>
|
||||
|
||||
[](https://god.gw.postman.com/run-collection/26869335-5546d063-156b-4529-915f-909dd628c090?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D26869335-5546d063-156b-4529-915f-909dd628c090%26entityType%3Dcollection%26workspaceId%3D339a4ee7-378b-45c9-b5b8-fd2c0a9c2442)
|
||||
version: 1.6.1
|
||||
version: 1.6.2
|
||||
contact:
|
||||
name: DavidsonGomes
|
||||
email: contato@agenciadgcode.com
|
||||
|
@ -27,6 +27,7 @@ export const initAMQP = () => {
|
||||
channel.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
assert: true,
|
||||
});
|
||||
|
||||
amqpChannel = channel;
|
||||
@ -43,7 +44,7 @@ export const getAMQP = (): amqp.Channel | null => {
|
||||
};
|
||||
|
||||
export const initQueues = (instanceName: string, events: string[]) => {
|
||||
if (!events || !events.length) return;
|
||||
if (!instanceName || !events || !events.length) return;
|
||||
|
||||
const queues = events.map((event) => {
|
||||
return `${event.replace(/_/g, '.').toLowerCase()}`;
|
||||
@ -56,6 +57,7 @@ export const initQueues = (instanceName: string, events: string[]) => {
|
||||
amqp.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
assert: true,
|
||||
});
|
||||
|
||||
const queueName = `${instanceName}.${event}`;
|
||||
@ -89,6 +91,7 @@ export const removeQueues = (instanceName: string, events: string[]) => {
|
||||
amqp.assertExchange(exchangeName, 'topic', {
|
||||
durable: true,
|
||||
autoDelete: false,
|
||||
assert: true,
|
||||
});
|
||||
|
||||
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'),
|
||||
};
|
||||
|
||||
export const updateMessageSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'string' },
|
||||
text: { type: 'string' },
|
||||
key: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
remoteJid: { type: 'string' },
|
||||
fromMe: { type: 'boolean', enum: [true, false] },
|
||||
},
|
||||
required: ['id', 'fromMe', 'remoteJid'],
|
||||
...isNotEmpty('id', 'remoteJid'),
|
||||
},
|
||||
},
|
||||
...isNotEmpty('number', 'text', 'key'),
|
||||
};
|
||||
|
||||
export const profilePictureSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
@ -1127,7 +1147,18 @@ export const proxySchema: JSONSchema7 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', enum: [true, false] },
|
||||
proxy: { type: 'string' },
|
||||
proxy: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
host: { type: 'string' },
|
||||
port: { type: 'string' },
|
||||
protocol: { type: 'string' },
|
||||
username: { type: 'string' },
|
||||
password: { type: 'string' },
|
||||
},
|
||||
required: ['host', 'port', 'protocol'],
|
||||
...isNotEmpty('host', 'port', 'protocol'),
|
||||
},
|
||||
},
|
||||
required: ['enabled', 'proxy'],
|
||||
...isNotEmpty('enabled', 'proxy'),
|
||||
|
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,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
NumberBusiness,
|
||||
} from '../dto/chat.dto';
|
||||
@ -123,4 +124,9 @@ export class ChatController {
|
||||
logger.verbose('requested removeProfilePicture from ' + instanceName + ' instance');
|
||||
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 { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { CacheEngine } from '../../libs/cacheengine';
|
||||
import { ChatwootDto } from '../dto/chatwoot.dto';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { CacheService } from '../services/cache.service';
|
||||
import { ChatwootService } from '../services/chatwoot.service';
|
||||
import { waMonitor } from '../whatsapp.module';
|
||||
|
||||
@ -94,7 +96,9 @@ export class ChatwootController {
|
||||
|
||||
public async receiveWebhook(instance: InstanceDto, data: any) {
|
||||
logger.verbose('requested receiveWebhook from ' + instance.instanceName + ' instance');
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository);
|
||||
|
||||
const chatwootCache = new CacheService(new CacheEngine(this.configService, ChatwootService.name).getEngine());
|
||||
const chatwootService = new ChatwootService(waMonitor, this.configService, this.repository, chatwootCache);
|
||||
|
||||
return chatwootService.receiveWebhook(instance, data);
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ import { RedisCache } from '../../libs/redis.client';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { AuthService, OldToken } from '../services/auth.service';
|
||||
import { CacheService } from '../services/cache.service';
|
||||
import { ChatwootService } from '../services/chatwoot.service';
|
||||
import { WAMonitoringService } from '../services/monitor.service';
|
||||
import { ProxyService } from '../services/proxy.service';
|
||||
import { RabbitmqService } from '../services/rabbitmq.service';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
import { SqsService } from '../services/sqs.service';
|
||||
@ -34,10 +34,10 @@ export class InstanceController {
|
||||
private readonly settingsService: SettingsService,
|
||||
private readonly websocketService: WebsocketService,
|
||||
private readonly rabbitmqService: RabbitmqService,
|
||||
private readonly proxyService: ProxyService,
|
||||
private readonly sqsService: SqsService,
|
||||
private readonly typebotService: TypebotService,
|
||||
private readonly cache: RedisCache,
|
||||
private readonly chatwootCache: CacheService,
|
||||
) {}
|
||||
|
||||
private readonly logger = new Logger(InstanceController.name);
|
||||
@ -77,7 +77,6 @@ export class InstanceController {
|
||||
typebot_delay_message,
|
||||
typebot_unknown_message,
|
||||
typebot_listening_from_me,
|
||||
proxy,
|
||||
}: InstanceDto) {
|
||||
try {
|
||||
this.logger.verbose('requested createInstance from ' + instanceName + ' instance');
|
||||
@ -86,7 +85,15 @@ export class InstanceController {
|
||||
await this.authService.checkDuplicateToken(token);
|
||||
|
||||
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.instanceNumber = number;
|
||||
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[];
|
||||
|
||||
if (sqs_enabled) {
|
||||
@ -419,7 +410,6 @@ export class InstanceController {
|
||||
settings,
|
||||
webhook_url: webhook_url,
|
||||
qrcode: getQrcode,
|
||||
proxy,
|
||||
};
|
||||
|
||||
this.logger.verbose('instance created');
|
||||
@ -525,7 +515,6 @@ export class InstanceController {
|
||||
name_inbox: instance.instanceName,
|
||||
webhook_url: `${urlServer}/chatwoot/webhook/${encodeURIComponent(instance.instanceName)}`,
|
||||
},
|
||||
proxy,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(error.message[0]);
|
||||
@ -584,6 +573,7 @@ export class InstanceController {
|
||||
switch (state) {
|
||||
case 'open':
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
instance.clearCacheChatwoot();
|
||||
await instance.reloadConnection();
|
||||
await delay(2000);
|
||||
|
||||
@ -649,6 +639,7 @@ export class InstanceController {
|
||||
}
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
|
||||
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
|
||||
|
||||
if (instance.state === 'connecting') {
|
||||
this.logger.verbose('logging out instance: ' + instanceName);
|
||||
@ -658,10 +649,15 @@ export class InstanceController {
|
||||
|
||||
this.logger.verbose('deleting instance: ' + instanceName);
|
||||
|
||||
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||
instanceName,
|
||||
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||
});
|
||||
try {
|
||||
this.waMonitor.waInstances[instanceName].sendDataWebhook(Events.INSTANCE_DELETE, {
|
||||
instanceName,
|
||||
instanceId: (await this.repository.auth.find(instanceName))?.instanceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
|
||||
delete this.waMonitor.waInstances[instanceName];
|
||||
this.eventEmitter.emit('remove.instance', instanceName, 'inner');
|
||||
return { status: 'SUCCESS', error: false, response: { message: 'Instance deleted' } };
|
||||
|
@ -1,4 +1,7 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { Logger } from '../../config/logger.config';
|
||||
import { BadRequestException } from '../../exceptions';
|
||||
import { InstanceDto } from '../dto/instance.dto';
|
||||
import { ProxyDto } from '../dto/proxy.dto';
|
||||
import { ProxyService } from '../services/proxy.service';
|
||||
@ -13,7 +16,16 @@ export class ProxyController {
|
||||
|
||||
if (!data.enabled) {
|
||||
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);
|
||||
@ -23,4 +35,36 @@ export class ProxyController {
|
||||
logger.verbose('requested findProxy from ' + instance.instanceName + ' 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;
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
enabled: boolean;
|
||||
proxy: string;
|
||||
proxy: Proxy;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ class ChatwootMessage {
|
||||
messageId?: number;
|
||||
inboxId?: number;
|
||||
conversationId?: number;
|
||||
contactInbox?: { sourceId: string };
|
||||
}
|
||||
|
||||
export class MessageRaw {
|
||||
@ -29,6 +30,7 @@ export class MessageRaw {
|
||||
source_id?: string;
|
||||
source_reply_id?: string;
|
||||
chatwoot?: ChatwootMessage;
|
||||
contextInfo?: any;
|
||||
}
|
||||
|
||||
const messageSchema = new Schema<MessageRaw>({
|
||||
@ -50,6 +52,7 @@ const messageSchema = new Schema<MessageRaw>({
|
||||
messageId: { type: Number },
|
||||
inboxId: { type: Number },
|
||||
conversationId: { type: Number },
|
||||
contactInbox: { type: Object },
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -2,16 +2,30 @@ import { Schema } from 'mongoose';
|
||||
|
||||
import { dbserver } from '../../libs/db.connect';
|
||||
|
||||
class Proxy {
|
||||
host?: string;
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export class ProxyRaw {
|
||||
_id?: string;
|
||||
enabled?: boolean;
|
||||
proxy?: string;
|
||||
proxy?: Proxy;
|
||||
}
|
||||
|
||||
const proxySchema = new Schema<ProxyRaw>({
|
||||
_id: { type: String, _id: true },
|
||||
enabled: { type: Boolean, required: true },
|
||||
proxy: { type: String, required: true },
|
||||
proxy: {
|
||||
host: { type: String, required: true },
|
||||
port: { type: String, required: true },
|
||||
protocol: { type: String, required: true },
|
||||
username: { type: String, required: false },
|
||||
password: { type: String, required: false },
|
||||
},
|
||||
});
|
||||
|
||||
export const ProxyModel = dbserver?.model(ProxyRaw.name, proxySchema, 'proxy');
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { opendirSync, readFileSync } from 'fs';
|
||||
import { opendirSync, readFileSync, rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ConfigService, StoreConf } from '../../config/env.config';
|
||||
@ -18,6 +18,19 @@ export class MessageRepository extends Repository {
|
||||
|
||||
private readonly logger = new Logger('MessageRepository');
|
||||
|
||||
public buildQuery(query: MessageQuery): MessageQuery {
|
||||
for (const [o, p] of Object.entries(query?.where)) {
|
||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
query.where[`${o}.${k}`] = v;
|
||||
}
|
||||
delete query.where[o];
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public async insert(data: MessageRaw[], instanceName: string, saveDb = false): Promise<IInsert> {
|
||||
this.logger.verbose('inserting messages');
|
||||
|
||||
@ -91,14 +104,7 @@ export class MessageRepository extends Repository {
|
||||
this.logger.verbose('finding messages');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('finding messages in db');
|
||||
for (const [o, p] of Object.entries(query?.where)) {
|
||||
if (typeof p === 'object' && p !== null && !Array.isArray(p)) {
|
||||
for (const [k, v] of Object.entries(p)) {
|
||||
query.where[`${o}.${k}`] = v;
|
||||
}
|
||||
delete query.where[o];
|
||||
}
|
||||
}
|
||||
query = this.buildQuery(query);
|
||||
|
||||
return await this.messageModel
|
||||
.find({ ...query.where })
|
||||
@ -198,15 +204,23 @@ export class MessageRepository extends Repository {
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(query: any) {
|
||||
public async delete(query: MessageQuery) {
|
||||
try {
|
||||
this.logger.verbose('deleting messages');
|
||||
this.logger.verbose('deleting message');
|
||||
if (this.dbSettings.ENABLED) {
|
||||
this.logger.verbose('deleting messages in db');
|
||||
return await this.messageModel.deleteMany(query);
|
||||
this.logger.verbose('deleting message in db');
|
||||
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) {
|
||||
return { error: error?.toString() };
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
profileSchema,
|
||||
profileStatusSchema,
|
||||
readMessageSchema,
|
||||
updateMessageSchema,
|
||||
whatsappNumberSchema,
|
||||
profileBusinessSchema,
|
||||
} from '../../validate/validate.schema';
|
||||
@ -29,6 +30,7 @@ import {
|
||||
ProfileStatusDto,
|
||||
ReadMessageDto,
|
||||
SendPresenceDto,
|
||||
UpdateMessageDto,
|
||||
WhatsAppNumberDto,
|
||||
NumberBusiness,
|
||||
} from '../dto/chat.dto';
|
||||
@ -383,6 +385,23 @@ export class ChatRouter extends RouterBroker {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
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 FormData from 'form-data';
|
||||
import { createReadStream, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
||||
import { createReadStream, unlinkSync, writeFileSync } from 'fs';
|
||||
import Jimp from 'jimp';
|
||||
import mimeTypes from 'mime-types';
|
||||
import path from 'path';
|
||||
|
||||
import { ConfigService, HttpServer, WABussiness } from '../../config/env.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 { InstanceDto } from '../dto/instance.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 { Events } from '../types/wa.types';
|
||||
import { WAMonitoringService } from './monitor.service';
|
||||
|
||||
export class ChatwootService {
|
||||
private messageCacheFile: string;
|
||||
private messageCache: Set<string>;
|
||||
|
||||
private readonly logger = new Logger(ChatwootService.name);
|
||||
|
||||
private provider: any;
|
||||
@ -29,35 +27,15 @@ export class ChatwootService {
|
||||
private readonly waMonitor: WAMonitoringService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
) {
|
||||
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 readonly cache: ICache,
|
||||
) {}
|
||||
|
||||
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);
|
||||
const provider = await this.waMonitor.waInstances[instance.instanceName]?.findChatwoot();
|
||||
|
||||
@ -68,6 +46,8 @@ export class ChatwootService {
|
||||
|
||||
this.logger.verbose('provider found');
|
||||
|
||||
this.cache.set(cacheKey, provider);
|
||||
|
||||
return provider;
|
||||
// try {
|
||||
// } catch (error) {
|
||||
@ -92,12 +72,7 @@ export class ChatwootService {
|
||||
|
||||
this.logger.verbose('create client to instance: ' + instance.instanceName);
|
||||
const client = new ChatwootClient({
|
||||
config: {
|
||||
basePath: provider.url,
|
||||
with_credentials: true,
|
||||
credentials: 'include',
|
||||
token: provider.token,
|
||||
},
|
||||
config: this.getClientCwConfig(),
|
||||
});
|
||||
|
||||
this.logger.verbose('client created');
|
||||
@ -105,6 +80,19 @@ export class ChatwootService {
|
||||
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) {
|
||||
this.logger.verbose('create chatwoot: ' + instance.instanceName);
|
||||
|
||||
@ -419,6 +407,26 @@ export class ChatwootService {
|
||||
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');
|
||||
|
||||
this.logger.verbose('is group: ' + isGroup);
|
||||
@ -569,6 +577,7 @@ export class ChatwootService {
|
||||
|
||||
if (conversation) {
|
||||
this.logger.verbose('conversation found');
|
||||
this.cache.set(cacheKey, conversation.id);
|
||||
return conversation.id;
|
||||
}
|
||||
}
|
||||
@ -594,6 +603,7 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
this.logger.verbose('conversation created');
|
||||
this.cache.set(cacheKey, conversation.id);
|
||||
return conversation.id;
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
@ -603,6 +613,11 @@ export class ChatwootService {
|
||||
public async getInbox(instance: InstanceDto) {
|
||||
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);
|
||||
|
||||
if (!client) {
|
||||
@ -629,6 +644,7 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
this.logger.verbose('return inbox');
|
||||
this.cache.set(cacheKey, findByName);
|
||||
return findByName;
|
||||
}
|
||||
|
||||
@ -644,6 +660,7 @@ export class ChatwootService {
|
||||
filename: string;
|
||||
}[],
|
||||
messageBody?: any,
|
||||
sourceId?: string,
|
||||
) {
|
||||
this.logger.verbose('create message to instance: ' + instance.instanceName);
|
||||
|
||||
@ -665,6 +682,7 @@ export class ChatwootService {
|
||||
message_type: messageType,
|
||||
attachments: attachments,
|
||||
private: privateMessage || false,
|
||||
source_id: sourceId,
|
||||
content_attributes: {
|
||||
...replyToIds,
|
||||
},
|
||||
@ -765,6 +783,7 @@ export class ChatwootService {
|
||||
content?: string,
|
||||
instance?: InstanceDto,
|
||||
messageBody?: any,
|
||||
sourceId?: string,
|
||||
) {
|
||||
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);
|
||||
const config = {
|
||||
method: 'post',
|
||||
@ -916,17 +939,21 @@ export class ChatwootService {
|
||||
|
||||
try {
|
||||
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]);
|
||||
this.logger.verbose('file name: ' + fileName);
|
||||
if (!mimeType) {
|
||||
const parts = media.split('/');
|
||||
fileName = decodeURIComponent(parts[parts.length - 1]);
|
||||
this.logger.verbose('file name: ' + fileName);
|
||||
|
||||
const response = await axios.get(media, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
const mimeType = response.headers['content-type'];
|
||||
this.logger.verbose('mime type: ' + mimeType);
|
||||
const response = await axios.get(media, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
mimeType = response.headers['content-type'];
|
||||
this.logger.verbose('mime type: ' + mimeType);
|
||||
}
|
||||
|
||||
let type = 'document';
|
||||
|
||||
@ -1008,6 +1035,17 @@ export class ChatwootService {
|
||||
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');
|
||||
if (
|
||||
!body?.conversation ||
|
||||
@ -1029,7 +1067,7 @@ export class ChatwootService {
|
||||
.replaceAll(/(?<!`)`((?!\s)([^`*]+?)(?<!\s))`(?!`)/g, '```$1```') // Substitui ` por ```
|
||||
: 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];
|
||||
|
||||
this.logger.verbose('check if is a message deletion');
|
||||
@ -1044,7 +1082,18 @@ export class ChatwootService {
|
||||
limit: 1,
|
||||
});
|
||||
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 });
|
||||
|
||||
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' };
|
||||
}
|
||||
@ -1105,22 +1154,11 @@ export class ChatwootService {
|
||||
if (body.message_type === 'outgoing' && body?.conversation?.messages?.length && chatId !== '123456') {
|
||||
this.logger.verbose('check if is group');
|
||||
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
this.logger.verbose('cache file path: ' + this.messageCacheFile);
|
||||
|
||||
this.messageCache = this.loadMessageCache();
|
||||
this.logger.verbose('cache file loaded');
|
||||
this.logger.verbose(this.messageCache);
|
||||
|
||||
this.logger.verbose('check if message is cached');
|
||||
if (this.messageCache.has(body.id.toString())) {
|
||||
this.logger.verbose('message is cached');
|
||||
if (body?.conversation?.messages[0]?.source_id?.substring(0, 5) === 'WAID:') {
|
||||
this.logger.verbose('message sent directly from whatsapp. Webhook ignored.');
|
||||
return { message: 'bot' };
|
||||
}
|
||||
|
||||
this.logger.verbose('clear cache');
|
||||
this.clearMessageCache();
|
||||
|
||||
this.logger.verbose('Format message to send');
|
||||
let formatText: string;
|
||||
const regex = /^▶️.*◀️$/;
|
||||
@ -1197,6 +1235,9 @@ export class ChatwootService {
|
||||
messageId: body.id,
|
||||
inboxId: body.inbox?.id,
|
||||
conversationId: body.conversation?.id,
|
||||
contactInbox: {
|
||||
sourceId: body.conversation?.contact_inbox?.source_id,
|
||||
},
|
||||
},
|
||||
instance,
|
||||
);
|
||||
@ -1228,6 +1269,9 @@ export class ChatwootService {
|
||||
messageId: body.id,
|
||||
inboxId: body.inbox?.id,
|
||||
conversationId: body.conversation?.id,
|
||||
contactInbox: {
|
||||
sourceId: body.conversation?.contact_inbox?.source_id,
|
||||
},
|
||||
},
|
||||
instance,
|
||||
);
|
||||
@ -1396,6 +1440,8 @@ export class ChatwootService {
|
||||
contactsArrayMessage: msg.contactsArrayMessage,
|
||||
locationMessage: msg.locationMessage,
|
||||
liveLocationMessage: msg.liveLocationMessage,
|
||||
listMessage: msg.listMessage,
|
||||
listResponseMessage: msg.listResponseMessage,
|
||||
};
|
||||
|
||||
this.logger.verbose('type message: ' + types);
|
||||
@ -1413,11 +1459,27 @@ export class ChatwootService {
|
||||
const latitude = result.degreesLatitude;
|
||||
const longitude = result.degreesLongitude;
|
||||
|
||||
const formattedLocation = `**Location:**
|
||||
**latitude:** ${latitude}
|
||||
**longitude:** ${longitude}
|
||||
https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}
|
||||
`;
|
||||
const locationName = result?.name || 'Unknown';
|
||||
const locationAddress = result?.address || 'Unknown';
|
||||
|
||||
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);
|
||||
|
||||
@ -1435,19 +1497,17 @@ export class ChatwootService {
|
||||
}
|
||||
});
|
||||
|
||||
let formattedContact = `**Contact:**
|
||||
**name:** ${contactInfo['FN']}`;
|
||||
let formattedContact = '*Contact:*\n\n' + '_Name:_ ' + contactInfo['FN'];
|
||||
|
||||
let numberCount = 1;
|
||||
Object.keys(contactInfo).forEach((key) => {
|
||||
if (key.startsWith('item') && key.includes('TEL')) {
|
||||
const phoneNumber = contactInfo[key];
|
||||
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
|
||||
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||
numberCount++;
|
||||
}
|
||||
if (key.includes('TEL')) {
|
||||
} else if (key.includes('TEL')) {
|
||||
const phoneNumber = contactInfo[key];
|
||||
formattedContact += `\n**number:** ${phoneNumber}`;
|
||||
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||
numberCount++;
|
||||
}
|
||||
});
|
||||
@ -1468,19 +1528,17 @@ export class ChatwootService {
|
||||
}
|
||||
});
|
||||
|
||||
let formattedContact = `**Contact:**
|
||||
**name:** ${contact.displayName}`;
|
||||
let formattedContact = '*Contact:*\n\n' + '_Name:_ ' + contact.displayName;
|
||||
|
||||
let numberCount = 1;
|
||||
Object.keys(contactInfo).forEach((key) => {
|
||||
if (key.startsWith('item') && key.includes('TEL')) {
|
||||
const phoneNumber = contactInfo[key];
|
||||
formattedContact += `\n**number ${numberCount}:** ${phoneNumber}`;
|
||||
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||
numberCount++;
|
||||
}
|
||||
if (key.includes('TEL')) {
|
||||
} else if (key.includes('TEL')) {
|
||||
const phoneNumber = contactInfo[key];
|
||||
formattedContact += `\n**number:** ${phoneNumber}`;
|
||||
formattedContact += '\n_Number (' + numberCount + '):_ ' + phoneNumber;
|
||||
numberCount++;
|
||||
}
|
||||
});
|
||||
@ -1495,6 +1553,62 @@ export class ChatwootService {
|
||||
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);
|
||||
|
||||
return result;
|
||||
@ -1591,8 +1705,21 @@ export class ChatwootService {
|
||||
},
|
||||
});
|
||||
|
||||
const random = Math.random().toString(36).substring(7);
|
||||
const nameFile = `${random}.${mimeTypes.extension(downloadBase64.mimetype)}`;
|
||||
let nameFile: string;
|
||||
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');
|
||||
|
||||
@ -1620,43 +1747,41 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
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) {
|
||||
this.logger.warn('message not sent');
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
|
||||
this.messageCache = this.loadMessageCache();
|
||||
|
||||
this.messageCache.add(send.id.toString());
|
||||
|
||||
this.logger.verbose('save message cache');
|
||||
this.saveMessageCache();
|
||||
|
||||
return send;
|
||||
} else {
|
||||
this.logger.verbose('message is not group');
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.sendData(getConversation, fileName, messageType, bodyMessage, instance, body);
|
||||
const send = await this.sendData(
|
||||
getConversation,
|
||||
fileName,
|
||||
messageType,
|
||||
bodyMessage,
|
||||
instance,
|
||||
body,
|
||||
'WAID:' + body.key.id,
|
||||
);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
|
||||
this.messageCache = this.loadMessageCache();
|
||||
|
||||
this.messageCache.add(send.id.toString());
|
||||
|
||||
this.logger.verbose('save message cache');
|
||||
this.saveMessageCache();
|
||||
|
||||
return send;
|
||||
}
|
||||
}
|
||||
@ -1675,16 +1800,12 @@ export class ChatwootService {
|
||||
{
|
||||
message: { extendedTextMessage: { contextInfo: { stanzaId: reactionMessage.key.id } } },
|
||||
},
|
||||
'WAID:' + body.key.id,
|
||||
);
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
return;
|
||||
}
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
this.messageCache = this.loadMessageCache();
|
||||
this.messageCache.add(send.id.toString());
|
||||
this.logger.verbose('save message cache');
|
||||
this.saveMessageCache();
|
||||
}
|
||||
|
||||
return;
|
||||
@ -1734,6 +1855,7 @@ export class ChatwootService {
|
||||
`${bodyMessage}\n\n\n**${title}**\n${description}\n${adsMessage.sourceUrl}`,
|
||||
instance,
|
||||
body,
|
||||
'WAID:' + body.key.id,
|
||||
);
|
||||
|
||||
if (!send) {
|
||||
@ -1741,15 +1863,6 @@ export class ChatwootService {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1769,43 +1882,43 @@ export class ChatwootService {
|
||||
}
|
||||
|
||||
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) {
|
||||
this.logger.warn('message not sent');
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
|
||||
this.messageCache = this.loadMessageCache();
|
||||
|
||||
this.messageCache.add(send.id.toString());
|
||||
|
||||
this.logger.verbose('save message cache');
|
||||
this.saveMessageCache();
|
||||
|
||||
return send;
|
||||
} else {
|
||||
this.logger.verbose('message is not group');
|
||||
|
||||
this.logger.verbose('send data to chatwoot');
|
||||
const send = await this.createMessage(instance, getConversation, bodyMessage, messageType, false, [], body);
|
||||
const send = await this.createMessage(
|
||||
instance,
|
||||
getConversation,
|
||||
bodyMessage,
|
||||
messageType,
|
||||
false,
|
||||
[],
|
||||
body,
|
||||
'WAID:' + body.key.id,
|
||||
);
|
||||
|
||||
if (!send) {
|
||||
this.logger.warn('message not sent');
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageCacheFile = path.join(ROOT_DIR, 'store', 'chatwoot', `${instance.instanceName}_cache.txt`);
|
||||
|
||||
this.messageCache = this.loadMessageCache();
|
||||
|
||||
this.messageCache.add(send.id.toString());
|
||||
|
||||
this.logger.verbose('save message cache');
|
||||
this.saveMessageCache();
|
||||
|
||||
return send;
|
||||
}
|
||||
}
|
||||
@ -1820,6 +1933,16 @@ export class ChatwootService {
|
||||
|
||||
const message = await this.getMessageByKeyId(instance, body.key.id);
|
||||
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);
|
||||
return await client.messages.delete({
|
||||
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') {
|
||||
this.logger.verbose('event status.instance');
|
||||
const data = body;
|
||||
@ -1854,6 +2015,7 @@ export class ChatwootService {
|
||||
const msgConnection = `🚀 Connection successfully established!`;
|
||||
this.logger.verbose('send message to chatwoot');
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public getNumberFromRemoteJid(remoteJid: string) {
|
||||
return remoteJid.replace(/:\d+/, '').split('@')[0];
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import {
|
||||
WebsocketModel,
|
||||
} from '../models';
|
||||
import { RepositoryBroker } from '../repository/repository.manager';
|
||||
import { CacheService } from './cache.service';
|
||||
import { WAStartupService } from './whatsapp.service';
|
||||
import { WAStartupClass } from '../whatsapp.module';
|
||||
|
||||
@ -35,6 +36,7 @@ export class WAMonitoringService {
|
||||
private readonly configService: ConfigService,
|
||||
private readonly repository: RepositoryBroker,
|
||||
private readonly cache: RedisCache,
|
||||
private readonly chatwootCache: CacheService,
|
||||
) {
|
||||
this.logger.verbose('instance created');
|
||||
|
||||
@ -360,14 +362,13 @@ export class WAMonitoringService {
|
||||
}
|
||||
|
||||
private async setInstance(name: string) {
|
||||
const path = join(INSTANCE_DIR, name);
|
||||
let values: any;
|
||||
if(this.db.ENABLED )
|
||||
values = await this.dbInstance.collection(name).findOne({ _id: 'integration' })
|
||||
else
|
||||
values = JSON.parse(readFileSync(path + '/integration.json', 'utf8'));
|
||||
const instance = new WAStartupClass[values.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 = name;
|
||||
instance.instanceNumber = values.number;
|
||||
instance.instanceToken = values.token;
|
||||
@ -451,6 +452,7 @@ export class WAMonitoringService {
|
||||
this.eventEmitter.on('logout.instance', async (instanceName: string) => {
|
||||
this.logger.verbose('logout instance: ' + instanceName);
|
||||
try {
|
||||
this.waInstances[instanceName]?.clearCacheChatwoot();
|
||||
this.logger.verbose('request cleaning up instance: ' + instanceName);
|
||||
this.cleaningUp(instanceName);
|
||||
} finally {
|
||||
|
@ -27,7 +27,7 @@ export class ProxyService {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { enabled: false, proxy: '' };
|
||||
return { enabled: false, proxy: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -389,6 +389,7 @@ export class TypebotService {
|
||||
input,
|
||||
clientSideActions,
|
||||
this.eventEmitter,
|
||||
applyFormatting,
|
||||
).catch((err) => {
|
||||
console.error('Erro ao processar mensagens:', err);
|
||||
});
|
||||
@ -404,84 +405,70 @@ export class TypebotService {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function processMessages(instance, messages, input, clientSideActions, eventEmitter) {
|
||||
let qtdMessages = 0, buttonText = '';
|
||||
function applyFormatting(element) {
|
||||
let text = '';
|
||||
|
||||
if (element.text) {
|
||||
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) {
|
||||
const wait = findItemAndGetSecondsToWait(clientSideActions, message.id);
|
||||
|
||||
if (message.type === 'text') {
|
||||
let formattedText = '';
|
||||
|
||||
let linkPreview = false;
|
||||
|
||||
for (const richText of message.content.richText) {
|
||||
if (richText.type === 'variable') {
|
||||
for (const child of richText.children) {
|
||||
for (const grandChild of child.children) {
|
||||
formattedText += grandChild.text;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const element of richText.children) {
|
||||
let text = '';
|
||||
|
||||
if (element.type === 'inline-variable') {
|
||||
for (const child of element.children) {
|
||||
for (const grandChild of child.children) {
|
||||
text += grandChild.text;
|
||||
}
|
||||
}
|
||||
} else if (element.text) {
|
||||
text = element.text;
|
||||
}
|
||||
|
||||
// if (element.text) {
|
||||
// text = element.text;
|
||||
// }
|
||||
|
||||
if (element.bold) {
|
||||
text = `*${text}*`;
|
||||
}
|
||||
|
||||
if (element.italic) {
|
||||
text = `_${text}_`;
|
||||
}
|
||||
|
||||
if (element.underline) {
|
||||
text = `*${text}*`;
|
||||
}
|
||||
|
||||
if (element.url) {
|
||||
const linkText = element.children[0].text;
|
||||
text = `[${linkText}](${element.url})`;
|
||||
linkPreview = true;
|
||||
}
|
||||
|
||||
formattedText += text;
|
||||
}
|
||||
for (const element of richText.children) {
|
||||
formattedText += applyFormatting(element);
|
||||
}
|
||||
formattedText += '\n';
|
||||
}
|
||||
|
||||
formattedText = formattedText.replace(/\n$/, '');
|
||||
qtdMessages++;
|
||||
if (instance?.constructor.name == Integration.WABussinessService &&
|
||||
input?.type === 'choice input' && messages.length == qtdMessages) {
|
||||
buttonText = formattedText;
|
||||
} else {
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
linkPreview: linkPreview,
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
formattedText = formattedText.replace(/\*\*/g, '').replace(/__/, '').replace(/~~/, '').replace(/\n$/, '');
|
||||
|
||||
await instance.textMessage({
|
||||
number: remoteJid.split('@')[0],
|
||||
options: {
|
||||
delay: wait ? wait * 1000 : instance.localTypebot.delay_message || 1000,
|
||||
presence: 'composing',
|
||||
},
|
||||
textMessage: {
|
||||
text: formattedText,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (message.type === 'image') {
|
||||
await instance.mediaMessage({
|
||||
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 {
|
||||
eventEmitter.emit('typebot:end', {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -112,9 +112,17 @@ export declare namespace wa {
|
||||
sessions?: Session[];
|
||||
};
|
||||
|
||||
type Proxy = {
|
||||
host?: string;
|
||||
port?: string;
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
export type LocalProxy = {
|
||||
enabled?: boolean;
|
||||
proxy?: string;
|
||||
proxy?: Proxy;
|
||||
};
|
||||
|
||||
export type LocalChamaai = {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { configService } from '../config/env.config';
|
||||
import { eventEmitter } from '../config/event.config';
|
||||
import { Logger } from '../config/logger.config';
|
||||
import { CacheEngine } from '../libs/cacheengine';
|
||||
import { dbserver } from '../libs/db.connect';
|
||||
import { RedisCache } from '../libs/redis.client';
|
||||
import { ChamaaiController } from './controllers/chamaai.controller';
|
||||
@ -48,6 +49,7 @@ import { TypebotRepository } from './repository/typebot.repository';
|
||||
import { WebhookRepository } from './repository/webhook.repository';
|
||||
import { WebsocketRepository } from './repository/websocket.repository';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { CacheService } from './services/cache.service';
|
||||
import { ChamaaiService } from './services/chamaai.service';
|
||||
import { ChatwootService } from './services/chatwoot.service';
|
||||
import { WAMonitoringService } from './services/monitor.service';
|
||||
@ -102,7 +104,9 @@ export const repository = new RepositoryBroker(
|
||||
|
||||
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);
|
||||
|
||||
@ -134,7 +138,7 @@ const sqsService = new SqsService(waMonitor);
|
||||
|
||||
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);
|
||||
|
||||
@ -153,10 +157,10 @@ export const instanceController = new InstanceController(
|
||||
settingsService,
|
||||
websocketService,
|
||||
rabbitmqService,
|
||||
proxyService,
|
||||
sqsService,
|
||||
typebotService,
|
||||
cache,
|
||||
chatwootCache,
|
||||
);
|
||||
export const sendMessageController = new SendMessageController(waMonitor);
|
||||
export const chatController = new ChatController(waMonitor);
|
||||
|
Loading…
Reference in New Issue
Block a user