Compare commits

...

22 Commits
2.2.1 ... 2.2.3

Author SHA1 Message Date
Davidson Gomes
427c994993 Merge branch 'release/2.2.3' 2025-02-03 11:52:49 -03:00
Davidson Gomes
da74611769 version: 2.2.3 2025-02-03 11:52:37 -03:00
Davidson Gomes
3c2ea5c67c feat: Re-enable group metadata caching in Baileys service
- Restore group metadata caching mechanisms
- Uncomment cache-related methods for group updates and participants
- Implement conditional group metadata retrieval based on cache configuration
2025-02-02 16:42:17 -03:00
Davidson Gomes
4a5d7a91e2 chore: Update Baileys package to latest commit hash 2025-02-02 11:39:45 -03:00
Davidson Gomes
9109b140a9 Merge pull request #1192 from tonimoreiraa/fix-dify-truncated-messages
fix(dify-service): Truncated messages (agent bot)
2025-02-01 16:11:07 -03:00
Davidson Gomes
ff5a8adc71 Merge pull request #1190 from GrimBit1/main
Fix Message deletion in Whatsapp Bailey Service
2025-02-01 16:08:56 -03:00
Davidson Gomes
b09638600e chore: Upgrade Baileys to version 6.7.12
- Update Baileys package to latest version
- Bump package version to 2.2.3
2025-02-01 15:39:14 -03:00
Toni Moreira
fc84e0f327 fix: dify truncated messages 2025-02-01 11:47:50 -03:00
Aditya Nandwana
c1494ca035 Refactor logical message deletion in BaileysStartupService 2025-02-01 16:09:08 +05:30
Davidson Gomes
7ea46a05ca version: 2.2.3 2025-01-31 17:45:09 -03:00
Davidson Gomes
f8f1cbf4a2 fix: Disable group metadata caching in Baileys service
- Remove group metadata caching mechanisms
- Modify group-related cache update methods
- Simplify group metadata retrieval process
2025-01-31 17:44:44 -03:00
Davidson Gomes
79b1c6bb1c feat: Add configurable file/cache storage for authentication state
- Update Baileys package to version 6.7.10
- Implement conditional storage mechanism for authentication state
- Add support for file-based or Redis cache storage based on environment configuration
- Re-enable previously commented out file handling utility functions
2025-01-31 17:38:42 -03:00
Davidson Gomes
169d2f797b Merge tag '2.2.2' into develop
v
2025-01-31 07:14:30 -03:00
Davidson Gomes
db9cdbfc38 Merge branch 'release/2.2.2' 2025-01-31 07:14:29 -03:00
Davidson Gomes
6afcc958c5 Merge branch 'develop' of github.com:EvolutionAPI/evolution-api into develop 2025-01-31 07:13:54 -03:00
Davidson Gomes
2166aad1d3 Merge tag '2.2.2' into develop
v
2025-01-31 07:13:30 -03:00
Davidson Gomes
14fea2f5e0 Merge branch 'release/2.2.2' 2025-01-31 07:13:25 -03:00
Davidson Gomes
9122dae262 version: 2.2.2 2025-01-31 07:13:10 -03:00
Davidson Gomes
96549664c9 Merge pull request #1179 from MarceloSoaresJr/develop
bugfix: SQL query column quoting in ChannelStartupService
2025-01-28 18:41:14 -03:00
Davidson Gomes
fa19c7fa89 feat(rabbitmq): Add prefix key configuration for queue names 2025-01-28 18:01:28 -03:00
Marcelo Soares
503728e1e7 fix: SQL query column quoting in ChannelStartupService 2025-01-27 17:42:04 -03:00
Davidson Gomes
f11e3247f0 Merge tag '2.2.1' into develop
* Retry system for send webhooks
* Message filtering to support timestamp range queries
* Chats filtering to support timestamp range queries

* Correction of webhook global
* Fixed send audio with whatsapp cloud api
* Refactor on fetch chats
* Refactor on Evolution Channel
2025-01-22 14:39:40 -03:00
11 changed files with 612 additions and 571 deletions

View File

@@ -49,6 +49,8 @@ RABBITMQ_URI=amqp://localhost
RABBITMQ_EXCHANGE_NAME=evolution
# Global events - By enabling this variable, events from all instances are sent in the same event queue.
RABBITMQ_GLOBAL_ENABLED=false
# Prefix key to queue name
RABBITMQ_PREFIX_KEY=evolution
# Choose the events you want to send to RabbitMQ
RABBITMQ_EVENTS_APPLICATION_STARTUP=false
RABBITMQ_EVENTS_INSTANCE_CREATE=false

View File

@@ -1,3 +1,20 @@
# 2.2.3 (2025-02-03 11:52)
### Fixed
* Fix cache in local file system
* Update Baileys Version
# 2.2.2 (2025-01-31 06:55)
### Features
* Added prefix key to queue name in RabbitMQ
### Fixed
* Update Baileys Version
# 2.2.1 (2025-01-22 14:37)
### Features

View File

@@ -3,7 +3,7 @@ FROM node:20-alpine AS builder
RUN apk update && \
apk add git ffmpeg wget curl bash openssl
LABEL version="2.2.1" description="Api to control whatsapp features through http requests."
LABEL version="2.2.3" description="Api to control whatsapp features through http requests."
LABEL maintainer="Davidson Gomes" git="https://github.com/DavidsonGomes"
LABEL contact="contato@atendai.com"

1002
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "2.2.1",
"version": "2.2.3",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/main.js",
"type": "commonjs",

View File

@@ -2163,6 +2163,7 @@ export class BaileysStartupService extends ChannelStartupService {
const cache = this.configService.get<CacheConf>('CACHE');
if (!cache.REDIS.ENABLED && !cache.LOCAL.ENABLED) group = await this.findGroup({ groupJid: sender }, 'inner');
else group = await this.getGroupMetadataCache(sender);
// group = await this.findGroup({ groupJid: sender }, 'inner');
} catch (error) {
throw new NotFoundException('Group not found');
}
@@ -3551,25 +3552,31 @@ export class BaileysStartupService extends ChannelStartupService {
const messageId = response.message?.protocolMessage?.key?.id;
if (messageId) {
const isLogicalDeleted = configService.get<Database>('DATABASE').DELETE_DATA.LOGICAL_MESSAGE_DELETE;
let message = await this.prismaRepository.message.findUnique({
where: { id: messageId },
let message = await this.prismaRepository.message.findFirst({
where: {
key: {
path: ['id'],
equals: messageId,
},
},
});
if (isLogicalDeleted) {
if (!message) return response;
const existingKey = typeof message?.key === 'object' && message.key !== null ? message.key : {};
message = await this.prismaRepository.message.update({
where: { id: messageId },
where: { id: message.id },
data: {
key: {
...existingKey,
deleted: true,
},
status: 'DELETED',
},
});
} else {
await this.prismaRepository.message.deleteMany({
where: {
id: messageId,
id: message.id,
},
});
}
@@ -3578,7 +3585,7 @@ export class BaileysStartupService extends ChannelStartupService {
instanceId: message.instanceId,
key: message.key,
messageType: message.messageType,
status: message.status,
status: 'DELETED',
source: message.source,
messageTimestamp: message.messageTimestamp,
pushName: message.pushName,

View File

@@ -224,63 +224,43 @@ export class DifyService {
headers: {
Authorization: `Bearer ${dify.apiKey}`,
},
responseType: 'stream',
});
let conversationId;
let answer = '';
const stream = response.data;
const reader = new Readable().wrap(stream);
const data = response.data.replaceAll('data: ', '');
reader.on('data', (chunk) => {
const data = chunk.toString().replace(/data:\s*/g, '');
const events = data.split('\n').filter((line) => line.trim() !== '');
if (data.trim() === '' || !data.startsWith('{')) {
return;
}
for (const eventString of events) {
if (eventString.trim().startsWith('{')) {
const event = JSON.parse(eventString);
try {
const events = data.split('\n').filter((line) => line.trim() !== '');
for (const eventString of events) {
if (eventString.trim().startsWith('{')) {
const event = JSON.parse(eventString);
if (event?.event === 'agent_message') {
console.log('event:', event);
conversationId = conversationId ?? event?.conversation_id;
answer += event?.answer;
}
}
if (event?.event === 'agent_message') {
console.log('event:', event);
conversationId = conversationId ?? event?.conversation_id;
answer += event?.answer;
}
} catch (error) {
console.error('Error parsing stream data:', error);
}
});
}
reader.on('end', async () => {
if (instance.integration === Integration.WHATSAPP_BAILEYS)
await instance.client.sendPresenceUpdate('paused', remoteJid);
if (instance.integration === Integration.WHATSAPP_BAILEYS)
await instance.client.sendPresenceUpdate('paused', remoteJid);
const message = answer;
const message = answer;
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
await this.sendMessageWhatsApp(instance, remoteJid, message, settings);
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: true,
sessionId: conversationId,
},
});
});
reader.on('error', (error) => {
console.error('Error reading stream:', error);
await this.prismaRepository.integrationSession.update({
where: {
id: session.id,
},
data: {
status: 'opened',
awaitUser: true,
sessionId: conversationId,
},
});
return;

View File

@@ -87,6 +87,7 @@ export class RabbitmqController extends EventController implements EventControll
const rabbitmqLocal = instanceRabbitmq?.events;
const rabbitmqGlobal = configService.get<Rabbitmq>('RABBITMQ').GLOBAL_ENABLED;
const rabbitmqEvents = configService.get<Rabbitmq>('RABBITMQ').EVENTS;
const prefixKey = configService.get<Rabbitmq>('RABBITMQ').PREFIX_KEY;
const rabbitmqExchangeName = configService.get<Rabbitmq>('RABBITMQ').EXCHANGE_NAME;
const we = event.replace(/[.-]/gm, '_').toUpperCase();
const logEnabled = configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS');
@@ -159,7 +160,9 @@ export class RabbitmqController extends EventController implements EventControll
autoDelete: false,
});
const queueName = event;
const queueName = prefixKey
? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}`
: event.replace(/_/g, '.').toLowerCase();
await this.amqpChannel.assertQueue(queueName, {
durable: true,
@@ -195,6 +198,7 @@ export class RabbitmqController extends EventController implements EventControll
const rabbitmqExchangeName = configService.get<Rabbitmq>('RABBITMQ').EXCHANGE_NAME;
const events = configService.get<Rabbitmq>('RABBITMQ').EVENTS;
const prefixKey = configService.get<Rabbitmq>('RABBITMQ').PREFIX_KEY;
if (!events) {
this.logger.warn('No events to initialize on AMQP');
@@ -207,7 +211,10 @@ export class RabbitmqController extends EventController implements EventControll
eventKeys.forEach((event) => {
if (events[event] === false) return;
const queueName = `${event.replace(/_/g, '.').toLowerCase()}`;
const queueName =
prefixKey !== ''
? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}`
: `${event.replace(/_/g, '.').toLowerCase()}`;
const exchangeName = rabbitmqExchangeName;
this.amqpChannel.assertExchange(exchangeName, 'topic', {

View File

@@ -732,7 +732,7 @@ export class ChannelStartupService {
"Message"."messageTimestamp" DESC
)
SELECT * FROM rankedMessages
ORDER BY updatedAt DESC NULLS LAST;
ORDER BY "updatedAt" DESC NULLS LAST;
`;
if (results && isArray(results) && results.length > 0) {

View File

@@ -97,6 +97,7 @@ export type Rabbitmq = {
EXCHANGE_NAME: string;
GLOBAL_ENABLED: boolean;
EVENTS: EventsRabbitmq;
PREFIX_KEY: string;
};
export type Sqs = {
@@ -355,6 +356,7 @@ export class ConfigService {
RABBITMQ: {
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
GLOBAL_ENABLED: process.env?.RABBITMQ_GLOBAL_ENABLED === 'true',
PREFIX_KEY: process.env?.RABBITMQ_PREFIX_KEY || 'evolution',
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
URI: process.env.RABBITMQ_URI || '',
EVENTS: {

View File

@@ -5,14 +5,14 @@ import { AuthenticationState, BufferJSON, initAuthCreds, WAProto as proto } from
import fs from 'fs/promises';
import path from 'path';
// const fixFileName = (file: string): string | undefined => {
// if (!file) {
// return undefined;
// }
// const replacedSlash = file.replace(/\//g, '__');
// const replacedColon = replacedSlash.replace(/:/g, '-');
// return replacedColon;
// };
const fixFileName = (file: string): string | undefined => {
if (!file) {
return undefined;
}
const replacedSlash = file.replace(/\//g, '__');
const replacedColon = replacedSlash.replace(/:/g, '-');
return replacedColon;
};
export async function keyExists(sessionId: string): Promise<any> {
try {
@@ -63,14 +63,14 @@ async function deleteAuthKey(sessionId: string): Promise<any> {
}
}
// async function fileExists(file: string): Promise<any> {
// try {
// const stat = await fs.stat(file);
// if (stat.isFile()) return true;
// } catch (error) {
// return;
// }
// }
async function fileExists(file: string): Promise<any> {
try {
const stat = await fs.stat(file);
if (stat.isFile()) return true;
} catch (error) {
return;
}
}
export default async function useMultiFileAuthStatePrisma(
sessionId: string,
@@ -80,16 +80,19 @@ export default async function useMultiFileAuthStatePrisma(
saveCreds: () => Promise<void>;
}> {
const localFolder = path.join(INSTANCE_DIR, sessionId);
// const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');
const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');
await fs.mkdir(localFolder, { recursive: true });
async function writeData(data: any, key: string): Promise<any> {
const dataString = JSON.stringify(data, BufferJSON.replacer);
if (key != 'creds') {
return await cache.hSet(sessionId, key, data);
// await fs.writeFile(localFile(key), dataString);
// return;
if (process.env.CACHE_REDIS_ENABLED === 'true') {
return await cache.hSet(sessionId, key, data);
} else {
await fs.writeFile(localFile(key), dataString);
return;
}
}
await saveKey(sessionId, dataString);
return;
@@ -100,9 +103,13 @@ export default async function useMultiFileAuthStatePrisma(
let rawData;
if (key != 'creds') {
return await cache.hGet(sessionId, key);
// if (!(await fileExists(localFile(key)))) return null;
// rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });
if (process.env.CACHE_REDIS_ENABLED === 'true') {
return await cache.hGet(sessionId, key);
} else {
if (!(await fileExists(localFile(key)))) return null;
rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });
return JSON.parse(rawData, BufferJSON.reviver);
}
} else {
rawData = await getAuthKey(sessionId);
}
@@ -117,8 +124,11 @@ export default async function useMultiFileAuthStatePrisma(
async function removeData(key: string): Promise<any> {
try {
if (key != 'creds') {
return await cache.hDelete(sessionId, key);
// await fs.unlink(localFile(key));
if (process.env.CACHE_REDIS_ENABLED === 'true') {
return await cache.hDelete(sessionId, key);
} else {
await fs.unlink(localFile(key));
}
} else {
await deleteAuthKey(sessionId);
}