mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-09 01:49:37 -06:00
Merge branch 'develop' into main
This commit is contained in:
commit
ab4bec3b54
@ -99,6 +99,7 @@ SQS_REGION=
|
|||||||
# Websocket - Environment variables
|
# Websocket - Environment variables
|
||||||
WEBSOCKET_ENABLED=false
|
WEBSOCKET_ENABLED=false
|
||||||
WEBSOCKET_GLOBAL_EVENTS=false
|
WEBSOCKET_GLOBAL_EVENTS=false
|
||||||
|
WEBSOCKET_ALLOWED_HOSTS=127.0.0.1,::1,::ffff:127.0.0.1
|
||||||
|
|
||||||
# Pusher - Environment variables
|
# Pusher - Environment variables
|
||||||
PUSHER_ENABLED=false
|
PUSHER_ENABLED=false
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
# Repo
|
||||||
|
Baileys
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|||||||
@ -1,3 +1,9 @@
|
|||||||
|
# 2.3.3 (develop)
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
* Baileys Updates: v7.0.0-rc.3 ([Link](https://github.com/WhiskeySockets/Baileys/releases/tag/v7.0.0-rc.3))
|
||||||
|
|
||||||
# 2.3.2 (2025-09-02)
|
# 2.3.2 (2025-09-02)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
78
CLAUDE.md
Normal file
78
CLAUDE.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Core Commands
|
||||||
|
- **Run development server**: `npm run dev:server` - Starts the server with hot reload using tsx watch
|
||||||
|
- **Build project**: `npm run build` - Runs TypeScript check and builds with tsup
|
||||||
|
- **Start production**: `npm run start:prod` - Runs the compiled application from dist/
|
||||||
|
- **Lint code**: `npm run lint` - Runs ESLint with auto-fix on TypeScript files
|
||||||
|
- **Check lint**: `npm run lint:check` - Runs ESLint without auto-fix
|
||||||
|
|
||||||
|
### Database Commands
|
||||||
|
The project uses Prisma with support for multiple database providers (PostgreSQL, MySQL, psql_bouncer). Commands automatically use the DATABASE_PROVIDER from .env:
|
||||||
|
|
||||||
|
- **Generate Prisma client**: `npm run db:generate`
|
||||||
|
- **Deploy migrations**: `npm run db:deploy` (Unix/Mac) or `npm run db:deploy:win` (Windows)
|
||||||
|
- **Open Prisma Studio**: `npm run db:studio`
|
||||||
|
- **Create new migration**: `npm run db:migrate:dev` (Unix/Mac) or `npm run db:migrate:dev:win` (Windows)
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
Evolution API is a WhatsApp integration platform built with TypeScript and Express, supporting both Baileys (WhatsApp Web) and WhatsApp Cloud API connections.
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
**API Layer** (`src/api/`)
|
||||||
|
- **Controllers**: Handle HTTP requests for different resources (instance, chat, group, sendMessage, etc.)
|
||||||
|
- **Services**: Business logic layer containing auth, cache, channel, monitor, proxy services
|
||||||
|
- **Routes**: RESTful API endpoints with authentication guards
|
||||||
|
- **DTOs**: Data transfer objects for request/response validation using class-validator
|
||||||
|
- **Repository**: Database access layer using Prisma ORM
|
||||||
|
|
||||||
|
**Integrations** (`src/api/integrations/`)
|
||||||
|
- **Chatbot**: Supports multiple chatbot platforms (Typebot, Chatwoot, Dify, OpenAI, Flowise, N8N)
|
||||||
|
- **Event**: WebSocket, RabbitMQ, Amazon SQS event systems
|
||||||
|
- **Storage**: S3/Minio file storage integration
|
||||||
|
- **Channel**: Multi-channel messaging support
|
||||||
|
|
||||||
|
**Configuration** (`src/config/`)
|
||||||
|
- Environment configuration management
|
||||||
|
- Database provider switching (PostgreSQL/MySQL/PgBouncer)
|
||||||
|
- Multi-tenant support via DATABASE_CONNECTION_CLIENT_NAME
|
||||||
|
|
||||||
|
### Key Design Patterns
|
||||||
|
|
||||||
|
1. **Multi-Provider Database**: Uses `runWithProvider.js` to dynamically select database provider and migrations
|
||||||
|
2. **Module System**: Path aliases configured in tsconfig.json (@api, @cache, @config, @utils, @validate)
|
||||||
|
3. **Event-Driven**: EventEmitter2 for internal events, supports multiple external event systems
|
||||||
|
4. **Instance Management**: Each WhatsApp connection is managed as an instance with memory lifecycle (DEL_INSTANCE config)
|
||||||
|
|
||||||
|
### Database Schema
|
||||||
|
- Supports multiple providers with provider-specific schemas in `prisma/`
|
||||||
|
- Separate migration folders for each provider (postgresql-migrations, mysql-migrations)
|
||||||
|
- psql_bouncer uses PostgreSQL migrations but with connection pooling
|
||||||
|
|
||||||
|
### Authentication & Security
|
||||||
|
- JWT-based authentication
|
||||||
|
- API key support
|
||||||
|
- Instance-specific authentication
|
||||||
|
- Configurable CORS settings
|
||||||
|
|
||||||
|
### Messaging Features
|
||||||
|
- WhatsApp Web (Baileys library) and WhatsApp Cloud API support
|
||||||
|
- Message queue support (RabbitMQ, SQS)
|
||||||
|
- Real-time updates via WebSocket
|
||||||
|
- Media file handling with S3/Minio storage
|
||||||
|
- Multiple chatbot integrations with trigger management
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
Critical configuration in `.env`:
|
||||||
|
- SERVER_TYPE, SERVER_PORT, SERVER_URL
|
||||||
|
- DATABASE_PROVIDER and DATABASE_CONNECTION_URI
|
||||||
|
- Log levels and Baileys-specific logging
|
||||||
|
- Instance lifecycle management (DEL_INSTANCE)
|
||||||
|
- Feature toggles for data persistence (DATABASE_SAVE_*)
|
||||||
4138
package-lock.json
generated
4138
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "evolution-api",
|
"name": "evolution-api",
|
||||||
"version": "2.3.2",
|
"version": "2.3.3",
|
||||||
"description": "Rest api for communication with WhatsApp",
|
"description": "Rest api for communication with WhatsApp",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"amqplib": "^0.10.5",
|
"amqplib": "^0.10.5",
|
||||||
"audio-decode": "^2.2.3",
|
"audio-decode": "^2.2.3",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"baileys": "github:WhiskeySockets/Baileys",
|
"baileys": "^7.0.0-rc.3",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.7.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
@ -125,7 +125,7 @@
|
|||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"tsx": "^4.20.3",
|
"tsx": "^4.20.5",
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,7 @@ import makeWASocket, {
|
|||||||
isJidBroadcast,
|
isJidBroadcast,
|
||||||
isJidGroup,
|
isJidGroup,
|
||||||
isJidNewsletter,
|
isJidNewsletter,
|
||||||
isJidUser,
|
isPnUser,
|
||||||
makeCacheableSignalKeyStore,
|
makeCacheableSignalKeyStore,
|
||||||
MessageUpsertType,
|
MessageUpsertType,
|
||||||
MessageUserReceiptUpdate,
|
MessageUserReceiptUpdate,
|
||||||
@ -151,6 +151,19 @@ import { v4 } from 'uuid';
|
|||||||
import { BaileysMessageProcessor } from './baileysMessage.processor';
|
import { BaileysMessageProcessor } from './baileysMessage.processor';
|
||||||
import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys';
|
import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys';
|
||||||
|
|
||||||
|
export interface ExtendedMessageKey extends WAMessageKey {
|
||||||
|
senderPn?: string;
|
||||||
|
previousRemoteJid?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtendedIMessageKey extends proto.IMessageKey {
|
||||||
|
senderPn?: string;
|
||||||
|
remoteJidAlt?: string;
|
||||||
|
participantAlt?: string;
|
||||||
|
server_id?: string;
|
||||||
|
isViewOnce?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
|
||||||
|
|
||||||
// Adicione a função getVideoDuration no início do arquivo
|
// Adicione a função getVideoDuration no início do arquivo
|
||||||
@ -484,9 +497,13 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
|
|
||||||
private async getMessage(key: proto.IMessageKey, full = false) {
|
private async getMessage(key: proto.IMessageKey, full = false) {
|
||||||
try {
|
try {
|
||||||
const webMessageInfo = (await this.prismaRepository.message.findMany({
|
// Use raw SQL to avoid JSON path issues
|
||||||
where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } },
|
const webMessageInfo = (await this.prismaRepository.$queryRaw`
|
||||||
})) as unknown as proto.IWebMessageInfo[];
|
SELECT * FROM "Message"
|
||||||
|
WHERE "instanceId" = ${this.instanceId}
|
||||||
|
AND "key"->>'id' = ${key.id}
|
||||||
|
`) as proto.IWebMessageInfo[];
|
||||||
|
|
||||||
if (full) {
|
if (full) {
|
||||||
return webMessageInfo[0];
|
return webMessageInfo[0];
|
||||||
}
|
}
|
||||||
@ -982,8 +999,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m.key.remoteJid?.includes('@lid') && m.key.senderPn) {
|
if (m.key.remoteJid?.includes('@lid') && (m.key as ExtendedIMessageKey).senderPn) {
|
||||||
m.key.remoteJid = m.key.senderPn;
|
m.key.remoteJid = (m.key as ExtendedIMessageKey).senderPn;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Long.isLong(m?.messageTimestamp)) {
|
if (Long.isLong(m?.messageTimestamp)) {
|
||||||
@ -1048,9 +1065,9 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
for (const received of messages) {
|
for (const received of messages) {
|
||||||
if (received.key.remoteJid?.includes('@lid') && received.key.senderPn) {
|
if (received.key.remoteJid?.includes('@lid') && (received.key as ExtendedMessageKey).senderPn) {
|
||||||
(received.key as { previousRemoteJid?: string | null }).previousRemoteJid = received.key.remoteJid;
|
(received.key as ExtendedMessageKey).previousRemoteJid = received.key.remoteJid;
|
||||||
received.key.remoteJid = received.key.senderPn;
|
received.key.remoteJid = (received.key as ExtendedMessageKey).senderPn;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
received?.messageStubParameters?.some?.((param) =>
|
received?.messageStubParameters?.some?.((param) =>
|
||||||
@ -1407,8 +1424,8 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.remoteJid?.includes('@lid') && key.senderPn) {
|
if (key.remoteJid?.includes('@lid') && key.remoteJidAlt) {
|
||||||
key.remoteJid = key.senderPn;
|
key.remoteJid = key.remoteJidAlt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
const updateKey = `${this.instance.id}_${key.id}_${update.status}`;
|
||||||
@ -1459,9 +1476,14 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
let findMessage: any;
|
let findMessage: any;
|
||||||
const configDatabaseData = this.configService.get<Database>('DATABASE').SAVE_DATA;
|
const configDatabaseData = this.configService.get<Database>('DATABASE').SAVE_DATA;
|
||||||
if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) {
|
if (configDatabaseData.HISTORIC || configDatabaseData.NEW_MESSAGE) {
|
||||||
findMessage = await this.prismaRepository.message.findFirst({
|
// Use raw SQL to avoid JSON path issues
|
||||||
where: { instanceId: this.instanceId, key: { path: ['id'], equals: key.id } },
|
const messages = (await this.prismaRepository.$queryRaw`
|
||||||
});
|
SELECT * FROM "Message"
|
||||||
|
WHERE "instanceId" = ${this.instanceId}
|
||||||
|
AND "key"->>'id' = ${key.id}
|
||||||
|
LIMIT 1
|
||||||
|
`) as any[];
|
||||||
|
findMessage = messages[0] || null;
|
||||||
|
|
||||||
if (findMessage) message.messageId = findMessage.id;
|
if (findMessage) message.messageId = findMessage.id;
|
||||||
}
|
}
|
||||||
@ -1910,7 +1932,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
quoted,
|
quoted,
|
||||||
});
|
});
|
||||||
const id = await this.client.relayMessage(sender, message, { messageId });
|
const id = await this.client.relayMessage(sender, message, { messageId });
|
||||||
m.key = { id: id, remoteJid: sender, participant: isJidUser(sender) ? sender : undefined, fromMe: true };
|
m.key = { id: id, remoteJid: sender, participant: isPnUser(sender) ? sender : undefined, fromMe: true };
|
||||||
for (const [key, value] of Object.entries(m)) {
|
for (const [key, value] of Object.entries(m)) {
|
||||||
if (!value || (isArray(value) && value.length) === 0) {
|
if (!value || (isArray(value) && value.length) === 0) {
|
||||||
delete m[key];
|
delete m[key];
|
||||||
@ -3367,7 +3389,7 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
try {
|
try {
|
||||||
const keys: proto.IMessageKey[] = [];
|
const keys: proto.IMessageKey[] = [];
|
||||||
data.readMessages.forEach((read) => {
|
data.readMessages.forEach((read) => {
|
||||||
if (isJidGroup(read.remoteJid) || isJidUser(read.remoteJid)) {
|
if (isJidGroup(read.remoteJid) || isPnUser(read.remoteJid)) {
|
||||||
keys.push({ remoteJid: read.remoteJid, fromMe: read.fromMe, id: read.id });
|
keys.push({ remoteJid: read.remoteJid, fromMe: read.fromMe, id: read.id });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4299,22 +4321,50 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
throw new Error('Method not available in the Baileys service');
|
throw new Error('Method not available in the Baileys service');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private convertLongToNumber(obj: any): any {
|
||||||
|
if (obj === null || obj === undefined) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Long.isLong(obj)) {
|
||||||
|
return obj.toNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map((item) => this.convertLongToNumber(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
const converted: any = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
converted[key] = this.convertLongToNumber(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
private prepareMessage(message: proto.IWebMessageInfo): any {
|
private prepareMessage(message: proto.IWebMessageInfo): any {
|
||||||
const contentType = getContentType(message.message);
|
const contentType = getContentType(message.message);
|
||||||
const contentMsg = message?.message[contentType] as any;
|
const contentMsg = message?.message[contentType] as any;
|
||||||
|
|
||||||
const messageRaw = {
|
const messageRaw = {
|
||||||
key: message.key,
|
key: message.key, // Save key exactly as it comes from Baileys
|
||||||
pushName:
|
pushName:
|
||||||
message.pushName ||
|
message.pushName ||
|
||||||
(message.key.fromMe
|
(message.key.fromMe
|
||||||
? 'Você'
|
? 'Você'
|
||||||
: message?.participant || (message.key?.participant ? message.key.participant.split('@')[0] : null)),
|
: message?.participant || (message.key?.participant ? message.key.participant.split('@')[0] : null)),
|
||||||
status: status[message.status],
|
status: status[message.status],
|
||||||
message: { ...message.message },
|
message: this.convertLongToNumber({ ...message.message }),
|
||||||
contextInfo: contentMsg?.contextInfo,
|
contextInfo: this.convertLongToNumber(contentMsg?.contextInfo),
|
||||||
messageType: contentType || 'unknown',
|
messageType: contentType || 'unknown',
|
||||||
messageTimestamp: message.messageTimestamp as number,
|
messageTimestamp: Long.isLong(message.messageTimestamp)
|
||||||
|
? message.messageTimestamp.toNumber()
|
||||||
|
: (message.messageTimestamp as number),
|
||||||
instanceId: this.instanceId,
|
instanceId: this.instanceId,
|
||||||
source: getDevice(message.key.id),
|
source: getDevice(message.key.id),
|
||||||
};
|
};
|
||||||
@ -4357,7 +4407,22 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
const prepare = (message: any) => this.prepareMessage(message);
|
const prepare = (message: any) => this.prepareMessage(message);
|
||||||
this.chatwootService.syncLostMessages({ instanceName: this.instance.name }, chatwootConfig, prepare);
|
this.chatwootService.syncLostMessages({ instanceName: this.instance.name }, chatwootConfig, prepare);
|
||||||
|
|
||||||
|
// Generate ID for this cron task and store in cache
|
||||||
|
const cronId = cuid();
|
||||||
|
const cronKey = `chatwoot:syncLostMessages`;
|
||||||
|
await this.chatwootService.getCache()?.hSet(cronKey, this.instance.name, cronId);
|
||||||
|
|
||||||
const task = cron.schedule('0,30 * * * *', async () => {
|
const task = cron.schedule('0,30 * * * *', async () => {
|
||||||
|
// Check ID before executing (only if cache is available)
|
||||||
|
const cache = this.chatwootService.getCache();
|
||||||
|
if (cache) {
|
||||||
|
const storedId = await cache.hGet(cronKey, this.instance.name);
|
||||||
|
if (storedId && storedId !== cronId) {
|
||||||
|
this.logger.info(`Stopping syncChatwootLostMessages cron - ID mismatch: ${cronId} vs ${storedId}`);
|
||||||
|
task.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.chatwootService.syncLostMessages({ instanceName: this.instance.name }, chatwootConfig, prepare);
|
this.chatwootService.syncLostMessages({ instanceName: this.instance.name }, chatwootConfig, prepare);
|
||||||
});
|
});
|
||||||
task.start();
|
task.start();
|
||||||
@ -4367,24 +4432,23 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
private async updateMessagesReadedByTimestamp(remoteJid: string, timestamp?: number): Promise<number> {
|
private async updateMessagesReadedByTimestamp(remoteJid: string, timestamp?: number): Promise<number> {
|
||||||
if (timestamp === undefined || timestamp === null) return 0;
|
if (timestamp === undefined || timestamp === null) return 0;
|
||||||
|
|
||||||
const result = await this.prismaRepository.message.updateMany({
|
// Use raw SQL to avoid JSON path issues
|
||||||
where: {
|
const result = await this.prismaRepository.$executeRaw`
|
||||||
AND: [
|
UPDATE "Message"
|
||||||
{ key: { path: ['remoteJid'], equals: remoteJid } },
|
SET "status" = ${status[4]}
|
||||||
{ key: { path: ['fromMe'], equals: false } },
|
WHERE "instanceId" = ${this.instanceId}
|
||||||
{ messageTimestamp: { lte: timestamp } },
|
AND "key"->>'remoteJid' = ${remoteJid}
|
||||||
{ OR: [{ status: null }, { status: status[3] }] },
|
AND ("key"->>'fromMe')::boolean = false
|
||||||
],
|
AND "messageTimestamp" <= ${timestamp}
|
||||||
},
|
AND ("status" IS NULL OR "status" = ${status[3]})
|
||||||
data: { status: status[4] },
|
`;
|
||||||
});
|
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.count > 0) {
|
if (result > 0) {
|
||||||
this.updateChatUnreadMessages(remoteJid);
|
this.updateChatUnreadMessages(remoteJid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.count;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -4393,15 +4457,14 @@ export class BaileysStartupService extends ChannelStartupService {
|
|||||||
private async updateChatUnreadMessages(remoteJid: string): Promise<number> {
|
private async updateChatUnreadMessages(remoteJid: string): Promise<number> {
|
||||||
const [chat, unreadMessages] = await Promise.all([
|
const [chat, unreadMessages] = await Promise.all([
|
||||||
this.prismaRepository.chat.findFirst({ where: { remoteJid } }),
|
this.prismaRepository.chat.findFirst({ where: { remoteJid } }),
|
||||||
this.prismaRepository.message.count({
|
// Use raw SQL to avoid JSON path issues
|
||||||
where: {
|
this.prismaRepository.$queryRaw`
|
||||||
AND: [
|
SELECT COUNT(*)::int as count FROM "Message"
|
||||||
{ key: { path: ['remoteJid'], equals: remoteJid } },
|
WHERE "instanceId" = ${this.instanceId}
|
||||||
{ key: { path: ['fromMe'], equals: false } },
|
AND "key"->>'remoteJid' = ${remoteJid}
|
||||||
{ status: { equals: status[3] } },
|
AND ("key"->>'fromMe')::boolean = false
|
||||||
],
|
AND "status" = ${status[3]}
|
||||||
},
|
`.then((result: any[]) => result[0]?.count || 0),
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (chat && chat.unreadMessages !== unreadMessages) {
|
if (chat && chat.unreadMessages !== unreadMessages) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { IgnoreJidDto } from '@api/dto/chatbot.dto';
|
|||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
|
import { Events } from '@api/types/wa.types';
|
||||||
import { Logger } from '@config/logger.config';
|
import { Logger } from '@config/logger.config';
|
||||||
import { BadRequestException } from '@exceptions';
|
import { BadRequestException } from '@exceptions';
|
||||||
import { TriggerOperator, TriggerType } from '@prisma/client';
|
import { TriggerOperator, TriggerType } from '@prisma/client';
|
||||||
@ -446,6 +447,16 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
|
|||||||
|
|
||||||
const remoteJid = data.remoteJid;
|
const remoteJid = data.remoteJid;
|
||||||
const status = data.status;
|
const status = data.status;
|
||||||
|
const session = await this.getSession(remoteJid, instance);
|
||||||
|
|
||||||
|
if (this.integrationName === 'Typebot') {
|
||||||
|
const typebotData = {
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
status: status,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
||||||
|
}
|
||||||
|
|
||||||
if (status === 'delete') {
|
if (status === 'delete') {
|
||||||
await this.sessionRepository.deleteMany({
|
await this.sessionRepository.deleteMany({
|
||||||
@ -867,6 +878,16 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
|
|||||||
status: 'paused',
|
status: 'paused',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.integrationName === 'Typebot') {
|
||||||
|
const typebotData = {
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
status: 'paused',
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
this.waMonitor.waInstances[instance.instanceName].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -880,12 +901,6 @@ export abstract class BaseChatbotController<BotType = any, BotData extends BaseC
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if session exists and status is paused
|
|
||||||
if (session && session.status === 'paused') {
|
|
||||||
this.logger.warn(`Session for ${remoteJid} is paused, skipping message processing`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merged settings
|
// Merged settings
|
||||||
const mergedSettings = {
|
const mergedSettings = {
|
||||||
...settings,
|
...settings,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { InstanceDto } from '@api/dto/instance.dto';
|
import { InstanceDto } from '@api/dto/instance.dto';
|
||||||
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '@api/dto/sendMessage.dto';
|
import { Options, Quoted, SendAudioDto, SendMediaDto, SendTextDto } from '@api/dto/sendMessage.dto';
|
||||||
|
import { ExtendedMessageKey } from '@api/integrations/channel/whatsapp/whatsapp.baileys.service';
|
||||||
import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
|
import { ChatwootDto } from '@api/integrations/chatbot/chatwoot/dto/chatwoot.dto';
|
||||||
import { postgresClient } from '@api/integrations/chatbot/chatwoot/libs/postgres.client';
|
import { postgresClient } from '@api/integrations/chatbot/chatwoot/libs/postgres.client';
|
||||||
import { chatwootImport } from '@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper';
|
import { chatwootImport } from '@api/integrations/chatbot/chatwoot/utils/chatwoot-import-helper';
|
||||||
@ -567,13 +568,6 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async createConversation(instance: InstanceDto, body: any) {
|
public async createConversation(instance: InstanceDto, body: any) {
|
||||||
if (!body?.key) {
|
|
||||||
this.logger.warn(
|
|
||||||
`body.key is null or undefined in createConversation. Full body object: ${JSON.stringify(body)}`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLid = body.key.previousRemoteJid?.includes('@lid') && body.key.senderPn;
|
const isLid = body.key.previousRemoteJid?.includes('@lid') && body.key.senderPn;
|
||||||
const remoteJid = body.key.remoteJid;
|
const remoteJid = body.key.remoteJid;
|
||||||
const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`;
|
const cacheKey = `${instance.instanceName}:createConversation-${remoteJid}`;
|
||||||
@ -1291,12 +1285,7 @@ export class ChatwootService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
const key = message.key as {
|
const key = message.key as ExtendedMessageKey;
|
||||||
id: string;
|
|
||||||
remoteJid: string;
|
|
||||||
fromMe: boolean;
|
|
||||||
participant: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
await waInstance?.client.sendMessage(key.remoteJid, { delete: key });
|
await waInstance?.client.sendMessage(key.remoteJid, { delete: key });
|
||||||
|
|
||||||
@ -1494,12 +1483,7 @@ export class ChatwootService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (lastMessage && !lastMessage.chatwootIsRead) {
|
if (lastMessage && !lastMessage.chatwootIsRead) {
|
||||||
const key = lastMessage.key as {
|
const key = lastMessage.key as ExtendedMessageKey;
|
||||||
id: string;
|
|
||||||
fromMe: boolean;
|
|
||||||
remoteJid: string;
|
|
||||||
participant?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
waInstance?.markMessageAsRead({
|
waInstance?.markMessageAsRead({
|
||||||
readMessages: [
|
readMessages: [
|
||||||
@ -1557,33 +1541,24 @@ export class ChatwootService {
|
|||||||
chatwootMessageIds: ChatwootMessage,
|
chatwootMessageIds: ChatwootMessage,
|
||||||
instance: InstanceDto,
|
instance: InstanceDto,
|
||||||
) {
|
) {
|
||||||
const key = message.key as {
|
const key = message.key as ExtendedMessageKey;
|
||||||
id: string;
|
|
||||||
fromMe: boolean;
|
|
||||||
remoteJid: string;
|
|
||||||
participant?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!chatwootMessageIds.messageId || !key?.id) {
|
if (!chatwootMessageIds.messageId || !key?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prismaRepository.message.updateMany({
|
// Use raw SQL to avoid JSON path issues
|
||||||
where: {
|
await this.prismaRepository.$executeRaw`
|
||||||
key: {
|
UPDATE "Message"
|
||||||
path: ['id'],
|
SET
|
||||||
equals: key.id,
|
"chatwootMessageId" = ${chatwootMessageIds.messageId},
|
||||||
},
|
"chatwootConversationId" = ${chatwootMessageIds.conversationId},
|
||||||
instanceId: instance.instanceId,
|
"chatwootInboxId" = ${chatwootMessageIds.inboxId},
|
||||||
},
|
"chatwootContactInboxSourceId" = ${chatwootMessageIds.contactInboxSourceId},
|
||||||
data: {
|
"chatwootIsRead" = ${chatwootMessageIds.isRead || false}
|
||||||
chatwootMessageId: chatwootMessageIds.messageId,
|
WHERE "instanceId" = ${instance.instanceId}
|
||||||
chatwootConversationId: chatwootMessageIds.conversationId,
|
AND "key"->>'id' = ${key.id}
|
||||||
chatwootInboxId: chatwootMessageIds.inboxId,
|
`;
|
||||||
chatwootContactInboxSourceId: chatwootMessageIds.contactInboxSourceId,
|
|
||||||
chatwootIsRead: chatwootMessageIds.isRead,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.isImportHistoryAvailable()) {
|
if (this.isImportHistoryAvailable()) {
|
||||||
chatwootImport.updateMessageSourceID(chatwootMessageIds.messageId, key.id);
|
chatwootImport.updateMessageSourceID(chatwootMessageIds.messageId, key.id);
|
||||||
@ -1591,17 +1566,15 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getMessageByKeyId(instance: InstanceDto, keyId: string): Promise<MessageModel> {
|
private async getMessageByKeyId(instance: InstanceDto, keyId: string): Promise<MessageModel> {
|
||||||
const messages = await this.prismaRepository.message.findFirst({
|
// Use raw SQL query to avoid JSON path issues with Prisma
|
||||||
where: {
|
const messages = await this.prismaRepository.$queryRaw`
|
||||||
key: {
|
SELECT * FROM "Message"
|
||||||
path: ['id'],
|
WHERE "instanceId" = ${instance.instanceId}
|
||||||
equals: keyId,
|
AND "key"->>'id' = ${keyId}
|
||||||
},
|
LIMIT 1
|
||||||
instanceId: instance.instanceId,
|
`;
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return messages || null;
|
return (messages as MessageModel[])[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getReplyToIds(
|
private async getReplyToIds(
|
||||||
@ -1636,12 +1609,7 @@ export class ChatwootService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = message?.key as {
|
const key = message?.key as ExtendedMessageKey;
|
||||||
id: string;
|
|
||||||
fromMe: boolean;
|
|
||||||
remoteJid: string;
|
|
||||||
participant?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (message && key?.id) {
|
if (message && key?.id) {
|
||||||
return {
|
return {
|
||||||
@ -1900,12 +1868,6 @@ export class ChatwootService {
|
|||||||
|
|
||||||
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
|
public async eventWhatsapp(event: string, instance: InstanceDto, body: any) {
|
||||||
try {
|
try {
|
||||||
// Ignore events that are not messages (like EPHEMERAL_SYNC_RESPONSE)
|
|
||||||
if (body?.type && body.type !== 'message' && body.type !== 'conversation') {
|
|
||||||
this.logger.verbose(`Ignoring non-message event type: ${body.type}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
const waInstance = this.waMonitor.waInstances[instance.instanceName];
|
||||||
|
|
||||||
if (!waInstance) {
|
if (!waInstance) {
|
||||||
@ -1951,11 +1913,6 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event === 'messages.upsert' || event === 'send.message') {
|
if (event === 'messages.upsert' || event === 'send.message') {
|
||||||
if (!body?.key) {
|
|
||||||
this.logger.warn(`body.key is null or undefined. Full body object: ${JSON.stringify(body)}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.key.remoteJid === 'status@broadcast') {
|
if (body.key.remoteJid === 'status@broadcast') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2278,29 +2235,17 @@ export class ChatwootService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event === 'messages.edit' || event === 'send.message.update') {
|
if (event === 'messages.edit' || event === 'send.message.update') {
|
||||||
// Ignore events that are not messages (like EPHEMERAL_SYNC_RESPONSE)
|
|
||||||
if (body?.type && body.type !== 'message') {
|
|
||||||
this.logger.verbose(`Ignoring non-message event type: ${body.type}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!body?.key?.id) {
|
|
||||||
this.logger.warn(
|
|
||||||
`body.key.id is null or undefined in messages.edit. Full body object: ${JSON.stringify(body)}`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const editedText = `${
|
const editedText = `${
|
||||||
body?.editedMessage?.conversation || body?.editedMessage?.extendedTextMessage?.text
|
body?.editedMessage?.conversation || body?.editedMessage?.extendedTextMessage?.text
|
||||||
}\n\n_\`${i18next.t('cw.message.edited')}.\`_`;
|
}\n\n_\`${i18next.t('cw.message.edited')}.\`_`;
|
||||||
const message = await this.getMessageByKeyId(instance, body.key.id);
|
const message = await this.getMessageByKeyId(instance, body?.key?.id);
|
||||||
const key = message.key as {
|
|
||||||
id: string;
|
if (!message) {
|
||||||
fromMe: boolean;
|
this.logger.warn('Message not found for edit event');
|
||||||
remoteJid: string;
|
return;
|
||||||
participant?: string;
|
}
|
||||||
};
|
|
||||||
|
const key = message.key as ExtendedMessageKey;
|
||||||
|
|
||||||
const messageType = key?.fromMe ? 'outgoing' : 'incoming';
|
const messageType = key?.fromMe ? 'outgoing' : 'incoming';
|
||||||
|
|
||||||
@ -2578,7 +2523,7 @@ export class ChatwootService {
|
|||||||
const savedMessages = await this.prismaRepository.message.findMany({
|
const savedMessages = await this.prismaRepository.message.findMany({
|
||||||
where: {
|
where: {
|
||||||
Instance: { name: instance.instanceName },
|
Instance: { name: instance.instanceName },
|
||||||
messageTimestamp: { gte: dayjs().subtract(6, 'hours').unix() },
|
messageTimestamp: { gte: Number(dayjs().subtract(6, 'hours').unix()) },
|
||||||
AND: ids.map((id) => ({ key: { path: ['id'], not: id } })),
|
AND: ids.map((id) => ({ key: { path: ['id'], not: id } })),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -106,15 +106,37 @@ export class EvolutionBotService extends BaseChatbotService<EvolutionBot, Evolut
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanitize payload for logging (remove sensitive data)
|
||||||
|
const sanitizedPayload = {
|
||||||
|
...payload,
|
||||||
|
inputs: {
|
||||||
|
...payload.inputs,
|
||||||
|
apiKey: payload.inputs.apiKey ? '[REDACTED]' : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.logger.debug(`[EvolutionBot] Sending request to endpoint: ${endpoint}`);
|
||||||
|
this.logger.debug(`[EvolutionBot] Request payload: ${JSON.stringify(sanitizedPayload, null, 2)}`);
|
||||||
|
|
||||||
const response = await axios.post(endpoint, payload, {
|
const response = await axios.post(endpoint, payload, {
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.debug(`[EvolutionBot] Response received - Status: ${response.status}`);
|
||||||
|
|
||||||
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
if (instance.integration === Integration.WHATSAPP_BAILEYS) {
|
||||||
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
await instance.client.sendPresenceUpdate('paused', remoteJid);
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = response?.data?.message;
|
let message = response?.data?.message;
|
||||||
|
const rawLinkPreview = response?.data?.linkPreview;
|
||||||
|
|
||||||
|
// Validate linkPreview is boolean and default to true for backward compatibility
|
||||||
|
const linkPreview = typeof rawLinkPreview === 'boolean' ? rawLinkPreview : true;
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
`[EvolutionBot] Processing response - Message length: ${message?.length || 0}, LinkPreview: ${linkPreview}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (message && typeof message === 'string' && message.startsWith("'") && message.endsWith("'")) {
|
if (message && typeof message === 'string' && message.startsWith("'") && message.endsWith("'")) {
|
||||||
const innerContent = message.slice(1, -1);
|
const innerContent = message.slice(1, -1);
|
||||||
@ -124,8 +146,19 @@ export class EvolutionBotService extends BaseChatbotService<EvolutionBot, Evolut
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
// Use the base class method to send the message to WhatsApp
|
// Send message directly with validated linkPreview option
|
||||||
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
|
await instance.textMessage(
|
||||||
|
{
|
||||||
|
number: remoteJid.split('@')[0],
|
||||||
|
delay: settings?.delayMessage || 1000,
|
||||||
|
text: message,
|
||||||
|
linkPreview, // Always boolean, defaults to true
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
this.logger.debug(`[EvolutionBot] Message sent successfully with linkPreview: ${linkPreview}`);
|
||||||
|
} else {
|
||||||
|
this.logger.warn(`[EvolutionBot] No message content received from bot response`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send telemetry
|
// Send telemetry
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { PrismaRepository } from '@api/repository/repository.service';
|
import { PrismaRepository } from '@api/repository/repository.service';
|
||||||
import { WAMonitoringService } from '@api/services/monitor.service';
|
import { WAMonitoringService } from '@api/services/monitor.service';
|
||||||
|
import { Events } from '@api/types/wa.types';
|
||||||
import { Auth, ConfigService, HttpServer, Typebot } from '@config/env.config';
|
import { Auth, ConfigService, HttpServer, Typebot } from '@config/env.config';
|
||||||
import { Instance, IntegrationSession, Message, Typebot as TypebotModel } from '@prisma/client';
|
import { Instance, IntegrationSession, Message, Typebot as TypebotModel } from '@prisma/client';
|
||||||
import { getConversationMessage } from '@utils/getConversationMessage';
|
import { getConversationMessage } from '@utils/getConversationMessage';
|
||||||
@ -151,6 +152,14 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typebotData = {
|
||||||
|
remoteJid: data.remoteJid,
|
||||||
|
status: 'opened',
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
this.waMonitor.waInstances[instance.name].sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
||||||
|
|
||||||
return { ...request.data, session };
|
return { ...request.data, session };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error);
|
||||||
@ -399,12 +408,14 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
let statusChange = 'closed';
|
||||||
if (!settings?.keepOpen) {
|
if (!settings?.keepOpen) {
|
||||||
await prismaRepository.integrationSession.deleteMany({
|
await prismaRepository.integrationSession.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
statusChange = 'delete';
|
||||||
} else {
|
} else {
|
||||||
await prismaRepository.integrationSession.update({
|
await prismaRepository.integrationSession.update({
|
||||||
where: {
|
where: {
|
||||||
@ -415,6 +426,13 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typebotData = {
|
||||||
|
remoteJid: session.remoteJid,
|
||||||
|
status: statusChange,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
instance.sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,6 +657,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) {
|
if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) {
|
||||||
|
let statusChange = 'closed';
|
||||||
if (keepOpen) {
|
if (keepOpen) {
|
||||||
await this.prismaRepository.integrationSession.update({
|
await this.prismaRepository.integrationSession.update({
|
||||||
where: {
|
where: {
|
||||||
@ -649,6 +668,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
statusChange = 'delete';
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
await this.prismaRepository.integrationSession.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
botId: findTypebot.id,
|
botId: findTypebot.id,
|
||||||
@ -656,6 +676,14 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typebotData = {
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
status: statusChange,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
waInstance.sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -788,6 +816,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) {
|
if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) {
|
||||||
|
let statusChange = 'closed';
|
||||||
if (keepOpen) {
|
if (keepOpen) {
|
||||||
await this.prismaRepository.integrationSession.update({
|
await this.prismaRepository.integrationSession.update({
|
||||||
where: {
|
where: {
|
||||||
@ -798,6 +827,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
statusChange = 'delete';
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
await this.prismaRepository.integrationSession.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
botId: findTypebot.id,
|
botId: findTypebot.id,
|
||||||
@ -806,6 +836,13 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typebotData = {
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
status: statusChange,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
waInstance.sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -881,6 +918,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) {
|
if (keywordFinish && content.toLowerCase() === keywordFinish.toLowerCase()) {
|
||||||
|
let statusChange = 'closed';
|
||||||
if (keepOpen) {
|
if (keepOpen) {
|
||||||
await this.prismaRepository.integrationSession.update({
|
await this.prismaRepository.integrationSession.update({
|
||||||
where: {
|
where: {
|
||||||
@ -891,6 +929,7 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
statusChange = 'delete';
|
||||||
await this.prismaRepository.integrationSession.deleteMany({
|
await this.prismaRepository.integrationSession.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
botId: findTypebot.id,
|
botId: findTypebot.id,
|
||||||
@ -898,6 +937,15 @@ export class TypebotService extends BaseChatbotService<TypebotModel, any> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typebotData = {
|
||||||
|
remoteJid: remoteJid,
|
||||||
|
status: statusChange,
|
||||||
|
session,
|
||||||
|
};
|
||||||
|
|
||||||
|
waInstance.sendDataWebhook(Events.TYPEBOT_CHANGE_STATUS, typebotData);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,11 +31,12 @@ export class WebsocketController extends EventController implements EventControl
|
|||||||
const params = new URLSearchParams(url.search);
|
const params = new URLSearchParams(url.search);
|
||||||
|
|
||||||
const { remoteAddress } = req.socket;
|
const { remoteAddress } = req.socket;
|
||||||
const isLocalhost =
|
const isAllowedHost = (process.env.WEBSOCKET_ALLOWED_HOSTS || '127.0.0.1,::1,::ffff:127.0.0.1')
|
||||||
remoteAddress === '127.0.0.1' || remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1';
|
.split(',')
|
||||||
|
.map((h) => h.trim())
|
||||||
|
.includes(remoteAddress);
|
||||||
|
|
||||||
// Permite conexões internas do Socket.IO (EIO=4 é o Engine.IO v4)
|
if (params.has('EIO') && isAllowedHost) {
|
||||||
if (params.has('EIO') && isLocalhost) {
|
|
||||||
return callback(null, true);
|
return callback(null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ export class WAMonitoringService {
|
|||||||
|
|
||||||
Object.assign(this.db, configService.get<Database>('DATABASE'));
|
Object.assign(this.db, configService.get<Database>('DATABASE'));
|
||||||
Object.assign(this.redis, configService.get<CacheConf>('CACHE'));
|
Object.assign(this.redis, configService.get<CacheConf>('CACHE'));
|
||||||
|
|
||||||
|
(this as any).providerSession = Object.freeze(configService.get<ProviderSession>('PROVIDER'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly db: Partial<Database> = {};
|
private readonly db: Partial<Database> = {};
|
||||||
@ -37,7 +39,7 @@ export class WAMonitoringService {
|
|||||||
private readonly logger = new Logger('WAMonitoringService');
|
private readonly logger = new Logger('WAMonitoringService');
|
||||||
public readonly waInstances: Record<string, any> = {};
|
public readonly waInstances: Record<string, any> = {};
|
||||||
|
|
||||||
private readonly providerSession = Object.freeze(this.configService.get<ProviderSession>('PROVIDER'));
|
private readonly providerSession: ProviderSession;
|
||||||
|
|
||||||
public delInstanceTime(instance: string) {
|
public delInstanceTime(instance: string) {
|
||||||
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
const time = this.configService.get<DelInstance>('DEL_INSTANCE');
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import fs from 'fs';
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
|
const __dirname = path.resolve(process.cwd(), 'src', 'utils');
|
||||||
|
|
||||||
const languages = ['en', 'pt-BR', 'es'];
|
const languages = ['en', 'pt-BR', 'es'];
|
||||||
const translationsPath = path.join(__dirname, 'translations');
|
const translationsPath = path.join(__dirname, 'translations');
|
||||||
const configService: ConfigService = new ConfigService();
|
const configService: ConfigService = new ConfigService();
|
||||||
@ -12,8 +14,9 @@ const resources: any = {};
|
|||||||
languages.forEach((language) => {
|
languages.forEach((language) => {
|
||||||
const languagePath = path.join(translationsPath, `${language}.json`);
|
const languagePath = path.join(translationsPath, `${language}.json`);
|
||||||
if (fs.existsSync(languagePath)) {
|
if (fs.existsSync(languagePath)) {
|
||||||
|
const translationContent = fs.readFileSync(languagePath, 'utf8');
|
||||||
resources[language] = {
|
resources[language] = {
|
||||||
translation: require(languagePath),
|
translation: JSON.parse(translationContent),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -153,7 +153,7 @@ export default async function useMultiFileAuthStatePrisma(
|
|||||||
ids.map(async (id) => {
|
ids.map(async (id) => {
|
||||||
let value = await readData(`${type}-${id}`);
|
let value = await readData(`${type}-${id}`);
|
||||||
if (type === 'app-state-sync-key' && value) {
|
if (type === 'app-state-sync-key' && value) {
|
||||||
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
value = proto.Message.AppStateSyncKeyData.create(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
data[id] = value;
|
data[id] = value;
|
||||||
|
|||||||
@ -100,7 +100,7 @@ export class AuthStateProvider {
|
|||||||
ids.map(async (id) => {
|
ids.map(async (id) => {
|
||||||
let value = await readData(`${type}-${id}`);
|
let value = await readData(`${type}-${id}`);
|
||||||
if (type === 'app-state-sync-key' && value) {
|
if (type === 'app-state-sync-key' && value) {
|
||||||
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
value = proto.Message.AppStateSyncKeyData.create(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
data[id] = value;
|
data[id] = value;
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export async function useMultiFileAuthStateRedisDb(
|
|||||||
ids.map(async (id) => {
|
ids.map(async (id) => {
|
||||||
let value = await readData(`${type}-${id}`);
|
let value = await readData(`${type}-${id}`);
|
||||||
if (type === 'app-state-sync-key' && value) {
|
if (type === 'app-state-sync-key' && value) {
|
||||||
value = proto.Message.AppStateSyncKeyData.fromObject(value);
|
value = proto.Message.AppStateSyncKeyData.create(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
data[id] = value;
|
data[id] = value;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user