Compare commits

...

30 Commits
1.8.2 ... v1.8

Author SHA1 Message Date
Davidson Gomes
5d91876974 feat: Restart WebSocket connection on Wavoip token update
- Added logic to restart the WebSocket connection in `ChannelStartupService` when a valid `wavoipToken` is set, ensuring the client reconnects with updated settings.
2025-05-29 19:43:54 -03:00
Davidson Gomes
5043ce8405 feat: Integrate Wavoip for voice call functionality
- Added Wavoip integration to support voice calls within the application.
- Introduced `wavoipToken` in various DTOs and models to manage authentication.
- Updated `ChannelStartupService` to handle Wavoip settings and events.
- Enhanced `BaileysStartupService` to utilize Wavoip for call signaling.
- Updated CHANGELOG.md to version 1.8.7.
2025-05-29 19:20:09 -03:00
Davidson Gomes
8e65526ce9 Merge branch 'v1.8' of github.com:EvolutionAPI/evolution-api into v1.8 2025-05-22 16:53:28 -03:00
Davidson Gomes
cae016f40a fix: Prevent duplicate contact processing in ChannelStartupService
- Added a Set to track seen contact IDs, ensuring that each contact is processed only once when fetching messages. This change improves efficiency and prevents redundant database queries.
2025-05-22 16:53:18 -03:00
Davidson Gomes
78150d0fc6 Merge pull request #1489 from gomessguii/v1.8
fix: Update message model and enhance media handling in Baileys service
2025-05-22 16:51:39 -03:00
Guilherme Gomes
f95727edbf fix: Update message model and enhance media handling in Baileys service
- Changed the type of `message` in `MessageRaw` from `object` to `any` for better flexibility.
- Improved media message handling in `BaileysStartupService` to support base64 encoding for various media types, enhancing webhook functionality.
- Added logging for media message detection to aid in debugging.
2025-05-22 16:50:42 -03:00
Davidson Gomes
2d9ca15d74 docs(changelog): Update CHANGELOG.md 2025-05-12 12:48:52 -03:00
Davidson Gomes
cee6498ea0 feat: Adds method to fetch contacts with last message
- Implements the `fetchContactsWithLastMessage` method in `ChatController` to retrieve contacts and their last messages.
- Updates `ChatRouter` to include the new route that calls the above method.
- Adds logic in `ChannelStartupService` to fetch contacts and their last messages, improving contact management functionality.
2025-05-12 12:48:05 -03:00
Davidson Gomes
86c603b3a1 feat: Updates RabbitMQ with new optimizations and configurations
- Implements Retry and Reconnect system in the integration with RabbitMQ.
- Adds optimizations with parameterized configurations via environment variables (MESSAGE_TTL, MAX_LENGTH and MAX_LENGTH_BYTES).
- Introduces non-persistent messages to reduce disk usage and automatic cleaning of expired messages in queues.
- Updates the version to 1.8.6 in package.json and CHANGELOG.md.
2025-04-30 15:44:12 -03:00
Davidson Gomes
960efcecd5 fix: Retry and Reconnect system in rabbitmq integration 2025-04-28 17:11:19 -03:00
Davidson Gomes
b36c37bf33 version: 1.8.5 2025-02-03 12:32:36 -03:00
Davidson Gomes
cc6adf0ee2 chore: Update Baileys version and re-enable instance management methods
- Switched Baileys dependency to EvolutionAPI repository
- Re-enabled delInstanceTime and deleteTempInstances methods in monitoring services
- Updated CHANGELOG.md to version 1.8.5
2025-01-31 13:52:02 -03:00
Davidson Gomes
6990a2c9c0 test: delete instances 2025-01-29 15:24:40 -03:00
Davidson Gomes
b60215100e feat: Implement retry mechanism for webhook requests in ChannelStartupService
- Added a new private method `retryWebhookRequest` to handle retries for webhook requests, improving reliability in case of failures.
- Updated `sendDataWebhook` method to utilize the new retry mechanism for both local and global webhooks, enhancing error handling and logging.
- Improved logging to provide clearer messages on retry attempts and final error reporting.

These changes enhance the robustness of webhook handling in the application.
2025-01-17 16:04:41 -03:00
Davidson Gomes
b6506dc661 chore: Update Baileys dependency to a new GitHub repository
- Changed the Baileys dependency in package.json from the EvolutionAPI repository to the renatoiub repository.

This update ensures that the project uses the latest version of Baileys from the specified GitHub source.
2025-01-17 15:46:55 -03:00
Davidson Gomes
03ee40388c chore: Bump version to 1.8.4 and update logging in services
- Updated package version from 1.8.2 to 1.8.4 in package.json.
- Refactored instance.controller.ts to allow logout for both 'connecting' and 'open' states, improving instance management.
- Commented out unnecessary logging in channel.service.ts to enhance code clarity.
- Enhanced logging in whatsapp.baileys.service.ts to include instance details in CONNECTION_UPDATE events, improving webhook data sent during connection state changes.

These changes improve versioning, code clarity, and logging functionality across the application.
2025-01-17 15:40:36 -03:00
Davidson Gomes
6ddad8e85a refactor: Simplify queue name generation and update AMQP publish method
- Refactored the queue name generation logic in `channel.service.ts` to improve readability by using a conditional operator.
- Updated the `bindQueue` method to use `queueName` instead of `event` for better clarity in `channel.service.ts`.
- Commented out unused phone number parsing logic in `whatsapp.baileys.service.ts` to clean up the code and improve maintainability.
- Made `isLatest` property optional in the type definition for better flexibility.

These changes enhance code clarity and maintainability across the services.
2024-12-19 11:12:11 -03:00
Davidson Gomes
555fa606ea fix: Update PrivacySetting groupadd type and enhance error handling in fetchInstances route
- Changed the type of `groupadd` in `PrivacySetting` from `WAPrivacyValue` to `any` to allow for more flexible data handling.
- Wrapped the `fetchInstances` route in a try-catch block to improve error handling and logging, ensuring that any errors are logged and a proper error response is returned to the client.

These changes enhance the robustness of the API and improve type flexibility in the DTO.
2024-12-11 15:53:58 -03:00
Davidson Gomes
ca474236b0 feat: Add PREFIX_KEY configuration for RabbitMQ integration
- Introduced a new optional PREFIX_KEY in the RabbitMQ configuration to allow custom queue naming.
- Updated the AMQP server and channel service to utilize PREFIX_KEY when initializing queues.
- Modified the dev environment configuration to include PREFIX_KEY.

This enhancement improves the flexibility of queue naming in RabbitMQ, facilitating better organization and management of queues.
2024-12-11 15:33:01 -03:00
Davidson Gomes
ebd70fe454 Merge pull request #753 from raimartinsb/develop
fix: Correction of media as attachments in chatwoot when using a Meta API Instance and not Baileys
2024-08-12 13:22:12 -03:00
raimartinsb
91f009a617 fix: Correção das mídias como anexo no chatwoot quando usamos uma Instance da Meta e enviando nome do arquivo para a meta 2024-08-12 10:52:56 -03:00
Davidson Gomes
293f6a39c5 chore: Update changelog with fixed issues and maintain compatibility
- Updated CHANGELOG.md with fixed issues in version 1.8.3
- Fixed issue sending group messages when ignore groups enabled
- Fixed groups\_ignore in /instance/create and maintained compatibility
2024-07-12 18:29:35 -03:00
Davidson Gomes
38bf859f43 Fix: Resolve issue with group messages when ignore groups is enabled
This commit resolves an issue where group messages were not being sent when the 'ignore groups' feature was enabled. The modification in the `whatsapp.baileys.service.ts` file ensures that group messages are sent even if the 'ignore groups' feature is enabled. Additionally, the `CHANGELOG.md` file has been updated to reflect this fix in version 1.8.3.

Changes:
- src/api/services/channels/whatsapp.baileys.service.ts
- CHANGELOG.md
2024-07-12 18:27:24 -03:00
Davidson Gomes
1d81c79fe6 Merge pull request #682 from jtapeg/fix-groups-ignore
Fix: groups_ignore in /instance/create and maintaining compatibility
2024-07-12 13:59:20 -03:00
Joao Pedro
c55885b366 Fix: groups_ignore in /instance/create and maintaining compatibility 2024-07-06 21:59:41 -03:00
Davidson Gomes
549ecd8801 Merge pull request #676 from EvolutionAPI/DavidsonGomes-patch-2
Update publish_docker_image_v2.yml
2024-07-03 19:17:56 -03:00
Davidson Gomes
5c6b70f372 Update publish_docker_image_v2.yml 2024-07-03 19:17:47 -03:00
Davidson Gomes
418ca971fa Merge pull request #675 from EvolutionAPI/DavidsonGomes-patch-1
Update publish_docker_image_v2.yml
2024-07-03 18:04:24 -03:00
Davidson Gomes
3d6209618b Update publish_docker_image_v2.yml 2024-07-03 18:04:01 -03:00
Davidson Gomes
05eb58be0e Merge tag '1.8.2' into develop
* Corretion in globall rabbitmq queue name
* Improvement in the use of mongodb database for credentials
* Fixed base64 in webhook for documentWithCaption
* Fixed Generate pairing code
2024-07-03 16:10:42 -03:00
23 changed files with 782 additions and 145 deletions

View File

@@ -1,3 +1,44 @@
# 1.8.7
### Features
* Wavoip integration
# 1.8.6
### Features
* Adds method to fetch contacts with last message
### Fixed
* Retry and Reconnect system in rabbitmq integration
### Feature
* RabbitMQ optimization with parameterized settings via environment variables (MESSAGE_TTL, MAX_LENGTH and MAX_LENGTH_BYTES)
* Non-persistent messages to reduce disk usage
* Automatic cleanup of expired messages in queues
# 1.8.5 (2025-02-03 12:32)
### Fixed
* Update Baileys Version
# 1.8.4 (2025-01-31 10:00)
### Features
* Added prefix key to queue name in RabbitMQ
# 1.8.3 (2024-11-29 10:00)
### Fixed
* Fixed issue sending group messages when ignore groups enabled
* Fixed groups_ignore in /instance/create and maintaining compatibility
# 1.8.2 (2024-07-03 13:50)
### Fixed

View File

@@ -1,6 +1,6 @@
{
"name": "evolution-api",
"version": "1.8.2",
"version": "1.8.6",
"description": "Rest api for communication with WhatsApp",
"main": "./dist/src/main.js",
"scripts": {
@@ -42,12 +42,12 @@
"homepage": "https://github.com/EvolutionAPI/evolution-api#readme",
"dependencies": {
"@adiwajshing/keyed-db": "^0.2.4",
"@aws-sdk/client-sqs": "^3.569.0",
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@figuro/chatwoot-sdk": "^1.1.16",
"@hapi/boom": "^10.0.1",
"@sentry/node": "^7.59.2",
"amqplib": "^0.10.3",
"@aws-sdk/client-sqs": "^3.569.0",
"axios": "^1.6.5",
"baileys": "github:EvolutionAPI/Baileys",
"class-validator": "^0.14.1",
@@ -84,6 +84,7 @@
"redis": "^4.6.5",
"sharp": "^0.32.2",
"socket.io": "^4.7.1",
"socket.io-client": "^4.8.1",
"socks-proxy-agent": "^8.0.1",
"swagger-ui-express": "^5.0.0",
"uuid": "^9.0.0",

View File

@@ -86,6 +86,11 @@ export class ChatController {
return await this.waMonitor.waInstances[instanceName].fetchChats();
}
public async fetchContactsWithLastMessage({ instanceName }: InstanceDto) {
logger.verbose('requested fetchContactsWithLastMessage from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].fetchContactsWithLastMessage();
}
public async sendPresence({ instanceName }: InstanceDto, data: SendPresenceDto) {
logger.verbose('requested sendPresence from ' + instanceName + ' instance');
return await this.waMonitor.waInstances[instanceName].sendPresence(data);

View File

@@ -78,6 +78,7 @@ export class InstanceController {
read_messages,
read_status,
sync_full_history,
wavoipToken,
websocket_enabled,
websocket_events,
rabbitmq_enabled,
@@ -396,11 +397,12 @@ export class InstanceController {
const settings: wa.LocalSettings = {
reject_call: reject_call || false,
msg_call: msg_call || '',
groups_ignore: groups_ignore || true,
groups_ignore: groups_ignore === undefined ? true : groups_ignore || false,
always_online: always_online || false,
read_messages: read_messages || false,
read_status: read_status || false,
sync_full_history: sync_full_history ?? false,
wavoipToken: wavoipToken ?? '',
};
this.logger.verbose('settings: ' + JSON.stringify(settings));
@@ -737,16 +739,11 @@ export class InstanceController {
this.logger.verbose('requested deleteInstance from ' + instanceName + ' instance');
const { instance } = await this.connectionState({ instanceName });
if (instance.state === 'open') {
throw new BadRequestException('The "' + instanceName + '" instance needs to be disconnected');
}
try {
this.waMonitor.waInstances[instanceName]?.removeRabbitmqQueues();
this.waMonitor.waInstances[instanceName]?.clearCacheChatwoot();
if (instance.state === 'connecting') {
this.logger.verbose('logging out instance: ' + instanceName);
if (instance.state === 'connecting' || instance.state === 'open') {
await this.logout({ instanceName });
}

View File

@@ -84,7 +84,7 @@ class PrivacySetting {
status: WAPrivacyValue;
online: WAPrivacyOnlineValue;
last: WAPrivacyValue;
groupadd: WAPrivacyValue;
groupadd: any;
}
export class PrivacySettingDto {

View File

@@ -21,6 +21,7 @@ export class InstanceDto {
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
wavoipToken?: string;
chatwoot_account_id?: string;
chatwoot_token?: string;
chatwoot_url?: string;

View File

@@ -6,4 +6,5 @@ export class SettingsDto {
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
wavoipToken?: string;
}

View File

@@ -741,7 +741,6 @@ export class ChatwootService {
findByName = inbox.payload.find((inbox) => inbox.name === this.getClientCwConfig().name_inbox.split('-cwId-')[0]);
}
if (!findByName) {
this.logger.warn('inbox not found');
return null;
@@ -1907,7 +1906,8 @@ export class ChatwootService {
let nameFile: string;
const messageBody = body?.message[body?.messageType];
const originalFilename = messageBody?.fileName || messageBody?.message?.documentMessage?.fileName;
const originalFilename =
messageBody?.fileName || messageBody?.filename || messageBody?.message?.documentMessage?.fileName;
if (originalFilename) {
const parsedFile = path.parse(originalFilename);
if (parsedFile.name && parsedFile.ext) {

View File

@@ -6,45 +6,133 @@ import { Logger } from '../../../../config/logger.config';
const logger = new Logger('AMQP');
let amqpChannel: amqp.Channel | null = null;
let amqpConnection: amqp.Connection | null = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 10;
const reconnectInterval = 5000; // 5 segundos
type ResolveCallback = () => void;
type RejectCallback = (error: Error) => void;
export const initAMQP = () => {
return new Promise<void>((resolve, reject) => {
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
amqp.connect(uri, (error, connection) => {
if (error) {
reject(error);
return;
}
connection.createChannel((channelError, channel) => {
if (channelError) {
reject(channelError);
return;
}
const exchangeName = 'evolution_exchange';
channel.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
amqpChannel = channel;
logger.info('AMQP initialized');
resolve();
});
});
connectToRabbitMQ(resolve, reject);
});
};
const connectToRabbitMQ = (resolve?: ResolveCallback, reject?: RejectCallback) => {
const uri = configService.get<Rabbitmq>('RABBITMQ').URI;
amqp.connect(uri, (error, connection) => {
if (error) {
logger.error(`Failed to connect to RabbitMQ: ${error.message}`);
handleConnectionError(error, resolve, reject);
return;
}
reconnectAttempts = 0;
amqpConnection = connection;
connection.on('error', (err) => {
logger.error(`RabbitMQ connection error: ${err.message}`);
scheduleReconnect();
});
connection.on('close', () => {
logger.warn('RabbitMQ connection closed unexpectedly');
scheduleReconnect();
});
createChannel(connection, resolve, reject);
});
};
const createChannel = (connection: amqp.Connection, resolve?: ResolveCallback, reject?: RejectCallback) => {
connection.createChannel((channelError, channel) => {
if (channelError) {
logger.error(`Failed to create channel: ${channelError.message}`);
if (reject) {
reject(channelError);
}
return;
}
const exchangeName = 'evolution_exchange';
channel.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
channel.on('error', (err) => {
logger.error(`RabbitMQ channel error: ${err.message}`);
amqpChannel = null;
createChannel(connection);
});
channel.on('close', () => {
logger.warn('RabbitMQ channel closed');
amqpChannel = null;
createChannel(connection);
});
amqpChannel = channel;
logger.info('AMQP initialized');
if (resolve) {
resolve();
}
});
};
const scheduleReconnect = () => {
if (reconnectAttempts >= maxReconnectAttempts) {
logger.error(`Exceeded maximum ${maxReconnectAttempts} reconnection attempts to RabbitMQ`);
return;
}
amqpChannel = null;
if (amqpConnection) {
try {
amqpConnection.close();
} catch (err) {
// Ignora erro ao fechar conexão que já pode estar fechada
}
amqpConnection = null;
}
reconnectAttempts++;
const delay = reconnectInterval * Math.pow(1.5, reconnectAttempts - 1); // Backoff exponencial
logger.info(`Reconnection attempt ${reconnectAttempts} to RabbitMQ in ${delay}ms`);
setTimeout(() => {
connectToRabbitMQ();
}, delay);
};
const handleConnectionError = (error: Error, resolve?: ResolveCallback, reject?: RejectCallback) => {
if (reject && reconnectAttempts === 0) {
// Na inicialização, rejeitar a Promise se for a primeira tentativa
reject(error);
return;
}
scheduleReconnect();
};
export const getAMQP = (): amqp.Channel | null => {
return amqpChannel;
};
export const initGlobalQueues = () => {
logger.info('Initializing global queues');
const events = configService.get<Rabbitmq>('RABBITMQ').EVENTS;
const rabbitmqConfig = configService.get<Rabbitmq>('RABBITMQ');
const events = rabbitmqConfig.EVENTS;
const prefixKey = rabbitmqConfig.PREFIX_KEY;
const messageTtl = rabbitmqConfig.MESSAGE_TTL;
const maxLength = rabbitmqConfig.MAX_LENGTH;
const maxLengthBytes = rabbitmqConfig.MAX_LENGTH_BYTES;
if (!events) {
logger.warn('No events to initialize on AMQP');
@@ -54,9 +142,15 @@ export const initGlobalQueues = () => {
const eventKeys = Object.keys(events);
eventKeys.forEach((event) => {
if (events[event] === false) return;
if (events[event] === false) {
return;
}
const queueName =
prefixKey !== ''
? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}`
: `${event.replace(/_/g, '.').toLowerCase()}`;
const queueName = `${event.replace(/_/g, '.').toLowerCase()}`;
const amqp = getAMQP();
const exchangeName = 'evolution_exchange';
@@ -70,6 +164,10 @@ export const initGlobalQueues = () => {
autoDelete: false,
arguments: {
'x-queue-type': 'quorum',
'x-message-ttl': messageTtl,
'x-max-length': maxLength,
'x-max-length-bytes': maxLengthBytes,
'x-overflow': 'reject-publish',
},
});
@@ -78,7 +176,14 @@ export const initGlobalQueues = () => {
};
export const initQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
if (!events || !events.length) {
return;
}
const rabbitmqConfig = configService.get<Rabbitmq>('RABBITMQ');
const messageTtl = rabbitmqConfig.MESSAGE_TTL;
const maxLength = rabbitmqConfig.MAX_LENGTH;
const maxLengthBytes = rabbitmqConfig.MAX_LENGTH_BYTES;
const queues = events.map((event) => {
return `${event.replace(/_/g, '.').toLowerCase()}`;
@@ -100,6 +205,10 @@ export const initQueues = (instanceName: string, events: string[]) => {
autoDelete: false,
arguments: {
'x-queue-type': 'quorum',
'x-message-ttl': messageTtl,
'x-max-length': maxLength,
'x-max-length-bytes': maxLengthBytes,
'x-overflow': 'reject-publish',
},
});
@@ -108,7 +217,9 @@ export const initQueues = (instanceName: string, events: string[]) => {
};
export const removeQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return;
if (!events || !events.length) {
return;
}
const channel = getAMQP();

View File

@@ -8,13 +8,17 @@ const logger = new Logger('Socket');
let io: SocketIO;
const cors = configService.get<Cors>('CORS').ORIGIN;
const origin = configService.get<Cors>('CORS').ORIGIN;
const methods = configService.get<Cors>('CORS').METHODS;
const credentials = configService.get<Cors>('CORS').CREDENTIALS;
export const initIO = (httpServer: Server) => {
if (configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
io = new SocketIO(httpServer, {
cors: {
origin: cors,
origin,
methods,
credentials,
},
});

View File

@@ -1,3 +1,4 @@
import Long from 'long';
import { Schema } from 'mongoose';
import { dbserver } from '../../libs/db.connect';
@@ -23,9 +24,9 @@ export class MessageRaw {
key?: Key;
pushName?: string;
participant?: string;
message?: object;
message?: any;
messageType?: string;
messageTimestamp?: number | Long.Long;
messageTimestamp?: number | Long;
owner: string;
source?: 'android' | 'web' | 'ios' | 'unknown' | 'desktop';
source_id?: string;

View File

@@ -11,6 +11,7 @@ export class SettingsRaw {
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
wavoipToken?: string;
}
const settingsSchema = new Schema<SettingsRaw>({
@@ -22,6 +23,7 @@ const settingsSchema = new Schema<SettingsRaw>({
read_messages: { type: Boolean, required: true },
read_status: { type: Boolean, required: true },
sync_full_history: { type: Boolean, required: true },
wavoipToken: { type: String, required: true },
});
export const SettingsModel = dbserver?.model(SettingsRaw.name, settingsSchema, 'settings');

View File

@@ -253,6 +253,23 @@ export class ChatRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchContactsWithLastMessage'), ...guards, async (req, res) => {
logger.verbose('request received in fetchContactsWithLastMessage');
logger.verbose('request body: ');
logger.verbose(req.body);
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
ClassRef: InstanceDto,
execute: (instance) => chatController.fetchContactsWithLastMessage(instance),
});
return res.status(HttpStatus.OK).json(response);
})
.post(this.routerPath('sendPresence'), ...guards, async (req, res) => {
logger.verbose('request received in sendPresence');
logger.verbose('request body: ');

View File

@@ -99,22 +99,28 @@ export class InstanceRouter extends RouterBroker {
return res.status(HttpStatus.OK).json(response);
})
.get(this.routerPath('fetchInstances', false), ...guards, async (req, res) => {
logger.verbose('request received in fetchInstances');
logger.verbose('request body: ');
logger.verbose(req.body);
try {
logger.verbose('request received in fetchInstances');
logger.verbose('request body: ');
logger.verbose(req.body);
const key = req.get('apikey');
const key = req.get('apikey');
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
ClassRef: InstanceDto,
execute: (instance) => instanceController.fetchInstances(instance, key),
});
logger.verbose('request query: ');
logger.verbose(req.query);
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: null,
ClassRef: InstanceDto,
execute: (instance) => instanceController.fetchInstances(instance, key),
});
return res.status(HttpStatus.OK).json(response);
return res.status(HttpStatus.OK).json(response);
} catch (error) {
logger.error('fetchInstances');
logger.error(error);
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: true, message: error.message });
}
})
.post(this.routerPath('setPresence'), ...guards, async (req, res) => {
logger.verbose('request received in setPresence');

View File

@@ -181,6 +181,9 @@ export class ChannelStartupService {
this.localSettings.sync_full_history = data?.sync_full_history;
this.logger.verbose(`Settings sync_full_history: ${this.localSettings.sync_full_history}`);
this.localSettings.wavoipToken = data?.wavoipToken;
this.logger.verbose(`Settings wavoipToken: ${this.localSettings.wavoipToken}`);
this.logger.verbose('Settings loaded');
}
@@ -194,8 +197,15 @@ export class ChannelStartupService {
this.logger.verbose(`Settings read_messages: ${data.read_messages}`);
this.logger.verbose(`Settings read_status: ${data.read_status}`);
this.logger.verbose(`Settings sync_full_history: ${data.sync_full_history}`);
this.logger.verbose(`Settings wavoipToken: ${data.wavoipToken}`);
Object.assign(this.localSettings, data);
this.logger.verbose('Settings set');
// restart instance
if (this.localSettings.wavoipToken && this.localSettings.wavoipToken.length > 0) {
this.client.ws.close();
this.client.ws.connect();
}
}
public async findSettings() {
@@ -214,6 +224,7 @@ export class ChannelStartupService {
this.logger.verbose(`Settings read_messages: ${data.read_messages}`);
this.logger.verbose(`Settings read_status: ${data.read_status}`);
this.logger.verbose(`Settings sync_full_history: ${data.sync_full_history}`);
this.logger.verbose(`Settings wavoipToken: ${data.wavoipToken}`);
return {
reject_call: data.reject_call,
msg_call: data.msg_call,
@@ -222,6 +233,7 @@ export class ChannelStartupService {
read_messages: data.read_messages,
read_status: data.read_status,
sync_full_history: data.sync_full_history,
wavoipToken: data.wavoipToken,
};
}
@@ -686,7 +698,45 @@ export class ChannelStartupService {
});
};
public async sendDataWebhook<T = any>(event: Events, data: T, local = true) {
private async retryWebhookRequest(
httpService: any,
postData: any,
baseURL: string,
isGlobal = false,
maxRetries = 10,
delaySeconds = 30,
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await httpService.post('', postData);
if (attempt > 1) {
this.logger.verbose(`Webhook ${isGlobal ? 'global' : 'local'} enviado com sucesso na tentativa ${attempt}`);
}
return;
} catch (error) {
if (attempt === maxRetries) {
throw error; // Propaga o erro após todas as tentativas
}
this.logger.warn({
local: `${ChannelStartupService.name}.retryWebhookRequest-${isGlobal ? 'global' : 'local'}`,
message: `Tentativa ${attempt}/${maxRetries} falhou. Próxima tentativa em ${delaySeconds} segundos`,
error: error?.message,
url: baseURL,
});
// Aguarda o delay antes da próxima tentativa
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
}
}
}
public async sendDataWebhook<T = any>(
event: Events,
data: T,
local = true,
integration = ['websocket', 'rabbitmq', 'sqs', 'webhook'],
) {
const webhookGlobal = this.configService.get<Webhook>('WEBHOOK');
const webhookLocal = this.localWebhook.events;
const websocketLocal = this.localWebsocket.events;
@@ -706,7 +756,7 @@ export class ChannelStartupService {
const tokenStore = await this.repository.auth.find(this.instanceName);
const instanceApikey = tokenStore?.apikey || 'Apikey not found';
if (rabbitmqEnabled) {
if (rabbitmqEnabled && integration.includes('rabbitmq')) {
const amqp = getAMQP();
if (this.localRabbitmq.enabled && amqp) {
if (Array.isArray(rabbitmqLocal) && rabbitmqLocal.includes(we)) {
@@ -748,7 +798,10 @@ export class ChannelStartupService {
message['apikey'] = instanceApikey;
}
await amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message)));
await amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message)), {
persistent: false,
expiration: this.configService.get<Rabbitmq>('RABBITMQ').MESSAGE_TTL.toString(),
});
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
const logData = {
@@ -778,6 +831,7 @@ export class ChannelStartupService {
if (rabbitmqGlobal && rabbitmqEvents[we] && amqp) {
const exchangeName = 'evolution_exchange';
const prefixKey = this.configService.get<Rabbitmq>('RABBITMQ').PREFIX_KEY;
let retry = 0;
@@ -788,7 +842,9 @@ export class ChannelStartupService {
autoDelete: false,
});
const queueName = event;
const queueName = prefixKey
? `${prefixKey}.${event.replace(/_/g, '.').toLowerCase()}`
: event.replace(/_/g, '.').toLowerCase();
await amqp.assertQueue(queueName, {
durable: true,
@@ -798,7 +854,7 @@ export class ChannelStartupService {
},
});
await amqp.bindQueue(queueName, exchangeName, event);
await amqp.bindQueue(queueName, exchangeName, queueName);
const message = {
event,
@@ -812,7 +868,11 @@ export class ChannelStartupService {
if (expose && instanceApikey) {
message['apikey'] = instanceApikey;
}
await amqp.publish(exchangeName, event, Buffer.from(JSON.stringify(message)));
await amqp.publish(exchangeName, queueName, Buffer.from(JSON.stringify(message)), {
persistent: false,
expiration: this.configService.get<Rabbitmq>('RABBITMQ').MESSAGE_TTL.toString(),
});
if (this.configService.get<Log>('LOG').LEVEL.includes('WEBHOOKS')) {
const logData = {
@@ -841,7 +901,7 @@ export class ChannelStartupService {
}
}
if (this.localSqs.enabled) {
if (this.localSqs.enabled && integration.includes('sqs')) {
const sqs = getSQS();
if (sqs) {
@@ -911,7 +971,7 @@ export class ChannelStartupService {
}
}
if (this.configService.get<Websocket>('WEBSOCKET')?.ENABLED) {
if (this.configService.get<Websocket>('WEBSOCKET')?.ENABLED && integration.includes('websocket')) {
this.logger.verbose('Sending data to websocket on channel: ' + this.instance.name);
const io = getIO();
@@ -985,7 +1045,7 @@ export class ChannelStartupService {
const globalApiKey = this.configService.get<Auth>('AUTHENTICATION').API_KEY.KEY;
if (local) {
if (Array.isArray(webhookLocal) && webhookLocal.includes(we)) {
if (Array.isArray(webhookLocal) && webhookLocal.includes(we) && integration.includes('webhook')) {
this.logger.verbose('Sending data to webhook local');
let baseURL: string;
@@ -1033,12 +1093,13 @@ export class ChannelStartupService {
postData['apikey'] = instanceApikey;
}
await httpService.post('', postData);
await this.retryWebhookRequest(httpService, postData, baseURL);
}
} catch (error) {
this.logger.error({
local: ChannelStartupService.name + '.sendDataWebhook-local',
message: error?.message,
message: 'Todas as tentativas de envio do webhook local falharam',
lastError: error?.message,
hostName: error?.hostname,
syscall: error?.syscall,
code: error?.code,
@@ -1052,7 +1113,7 @@ export class ChannelStartupService {
}
}
if (webhookGlobal.GLOBAL?.ENABLED) {
if (webhookGlobal.GLOBAL?.ENABLED && integration.includes('webhook')) {
if (webhookGlobal.EVENTS[we]) {
this.logger.verbose('Sending data to webhook global');
const globalWebhook = this.configService.get<Webhook>('WEBHOOK').GLOBAL;
@@ -1104,12 +1165,13 @@ export class ChannelStartupService {
postData['apikey'] = globalApiKey;
}
await httpService.post('', postData);
await this.retryWebhookRequest(httpService, postData, globalURL, true);
}
} catch (error) {
this.logger.error({
local: ChannelStartupService.name + '.sendDataWebhook-global',
message: error?.message,
message: 'Todas as tentativas de envio do webhook global falharam',
lastError: error?.message,
hostName: error?.hostname,
syscall: error?.syscall,
code: error?.code,
@@ -1283,4 +1345,36 @@ export class ChannelStartupService {
this.logger.verbose('Fetching chats');
return await this.repository.chat.find({ where: { owner: this.instance.name } });
}
public async fetchContactsWithLastMessage() {
this.logger.verbose('Searching for contacts with last message');
const contacts = await this.repository.contact.find({ where: { owner: this.instance.name } });
const result = [];
const seenIds = new Set();
for (const contact of contacts) {
if (seenIds.has(contact.id)) {
continue;
}
seenIds.add(contact.id);
const messages = await this.repository.message.find({
where: {
owner: this.instance.name,
key: { remoteJid: contact.id },
},
limit: 1,
});
if (messages && messages.length > 0) {
result.push({
id: contact.id,
pushName: contact?.pushName ?? null,
profilePictureUrl: contact?.profilePictureUrl ?? null,
owner: contact.owner,
lastMessage: messages[0],
});
}
}
return result;
}
}

View File

@@ -0,0 +1,78 @@
import { BinaryNode, Contact, JidWithDevice, proto, WAConnectionState } from 'baileys';
export interface ServerToClientEvents {
withAck: (d: string, callback: (e: number) => void) => void;
onWhatsApp: onWhatsAppType;
profilePictureUrl: ProfilePictureUrlType;
assertSessions: AssertSessionsType;
createParticipantNodes: CreateParticipantNodesType;
getUSyncDevices: GetUSyncDevicesType;
generateMessageTag: GenerateMessageTagType;
sendNode: SendNodeType;
'signalRepository:decryptMessage': SignalRepositoryDecryptMessageType;
}
export interface ClientToServerEvents {
init: (
me: Contact | undefined,
account: proto.IADVSignedDeviceIdentity | undefined,
status: WAConnectionState,
) => void;
'CB:call': (packet: any) => void;
'CB:ack,class:call': (packet: any) => void;
'connection.update:status': (
me: Contact | undefined,
account: proto.IADVSignedDeviceIdentity | undefined,
status: WAConnectionState,
) => void;
'connection.update:qr': (qr: string) => void;
}
export type onWhatsAppType = (jid: string, callback: onWhatsAppCallback) => void;
export type onWhatsAppCallback = (
response: {
exists: boolean;
jid: string;
}[],
) => void;
export type ProfilePictureUrlType = (
jid: string,
type: 'image' | 'preview',
timeoutMs: number | undefined,
callback: ProfilePictureUrlCallback,
) => void;
export type ProfilePictureUrlCallback = (response: string | undefined) => void;
export type AssertSessionsType = (jids: string[], force: boolean, callback: AssertSessionsCallback) => void;
export type AssertSessionsCallback = (response: boolean) => void;
export type CreateParticipantNodesType = (
jids: string[],
message: any,
extraAttrs: any,
callback: CreateParticipantNodesCallback,
) => void;
export type CreateParticipantNodesCallback = (nodes: any, shouldIncludeDeviceIdentity: boolean) => void;
export type GetUSyncDevicesType = (
jids: string[],
useCache: boolean,
ignoreZeroDevices: boolean,
callback: GetUSyncDevicesTypeCallback,
) => void;
export type GetUSyncDevicesTypeCallback = (jids: JidWithDevice[]) => void;
export type GenerateMessageTagType = (callback: GenerateMessageTagTypeCallback) => void;
export type GenerateMessageTagTypeCallback = (response: string) => void;
export type SendNodeType = (stanza: BinaryNode, callback: SendNodeTypeCallback) => void;
export type SendNodeTypeCallback = (response: boolean) => void;
export type SignalRepositoryDecryptMessageType = (
jid: string,
type: 'pkmsg' | 'msg',
ciphertext: Buffer,
callback: SignalRepositoryDecryptMessageCallback,
) => void;
export type SignalRepositoryDecryptMessageCallback = (response: any) => void;

View File

@@ -0,0 +1,181 @@
import { ConnectionState, WAConnectionState, WASocket } from 'baileys';
import { io, Socket } from 'socket.io-client';
import { ClientToServerEvents, ServerToClientEvents } from './transport.type';
let baileys_connection_state: WAConnectionState = 'close';
export const useVoiceCallsBaileys = async (
wavoip_token: string,
baileys_sock: WASocket,
status?: WAConnectionState,
logger?: boolean,
) => {
baileys_connection_state = status ?? 'close';
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io('https://devices.wavoip.com/baileys', {
transports: ['websocket'],
path: `/${wavoip_token}/websocket`,
});
socket.on('connect', () => {
if (logger) console.log('[*] - Wavoip connected', socket.id);
socket.emit(
'init',
baileys_sock.authState.creds.me,
baileys_sock.authState.creds.account,
baileys_connection_state,
);
});
socket.on('disconnect', () => {
if (logger) console.log('[*] - Wavoip disconnect');
});
socket.on('connect_error', (error) => {
if (socket.active) {
if (logger)
console.log(
'[*] - Wavoip connection error temporary failure, the socket will automatically try to reconnect',
error,
);
} else {
if (logger) console.log('[*] - Wavoip connection error', error.message);
}
});
socket.on('onWhatsApp', async (jid, callback) => {
try {
const response: any = await baileys_sock.onWhatsApp(jid);
callback(response);
if (logger) console.log('[*] Success on call onWhatsApp function', response, jid);
} catch (error) {
if (logger) console.error('[*] Error on call onWhatsApp function', error);
}
});
socket.on('profilePictureUrl', async (jid, type, timeoutMs, callback) => {
try {
const response = await baileys_sock.profilePictureUrl(jid, type, timeoutMs);
callback(response);
if (logger) console.log('[*] Success on call profilePictureUrl function', response);
} catch (error) {
if (logger) console.error('[*] Error on call profilePictureUrl function', error);
}
});
socket.on('assertSessions', async (jids, force, callback) => {
try {
const response = await baileys_sock.assertSessions(jids, force);
callback(response);
if (logger) console.log('[*] Success on call assertSessions function', response);
} catch (error) {
if (logger) console.error('[*] Error on call assertSessions function', error);
}
});
socket.on('createParticipantNodes', async (jids, message, extraAttrs, callback) => {
try {
const response = await baileys_sock.createParticipantNodes(jids, message, extraAttrs);
callback(response, true);
if (logger) console.log('[*] Success on call createParticipantNodes function', response);
} catch (error) {
if (logger) console.error('[*] Error on call createParticipantNodes function', error);
}
});
socket.on('getUSyncDevices', async (jids, useCache, ignoreZeroDevices, callback) => {
try {
const response = await baileys_sock.getUSyncDevices(jids, useCache, ignoreZeroDevices);
callback(response);
if (logger) console.log('[*] Success on call getUSyncDevices function', response);
} catch (error) {
if (logger) console.error('[*] Error on call getUSyncDevices function', error);
}
});
socket.on('generateMessageTag', async (callback) => {
try {
const response = await baileys_sock.generateMessageTag();
callback(response);
if (logger) console.log('[*] Success on call generateMessageTag function', response);
} catch (error) {
if (logger) console.error('[*] Error on call generateMessageTag function', error);
}
});
socket.on('sendNode', async (stanza, callback) => {
try {
console.log('sendNode', JSON.stringify(stanza));
const response = await baileys_sock.sendNode(stanza);
callback(true);
if (logger) console.log('[*] Success on call sendNode function', response);
} catch (error) {
if (logger) console.error('[*] Error on call sendNode function', error);
}
});
socket.on('signalRepository:decryptMessage', async (jid, type, ciphertext, callback) => {
try {
const response = await baileys_sock.signalRepository.decryptMessage({
jid: jid,
type: type,
ciphertext: ciphertext,
});
callback(response);
if (logger) console.log('[*] Success on call signalRepository:decryptMessage function', response);
} catch (error) {
if (logger) console.error('[*] Error on call signalRepository:decryptMessage function', error);
}
});
// we only use this connection data to inform the webphone that the device is connected and creeds account to generate e2e whatsapp key for make call packets
baileys_sock.ev.on('connection.update', (update: Partial<ConnectionState>) => {
const { connection } = update;
if (connection) {
baileys_connection_state = connection;
socket
.timeout(1000)
.emit(
'connection.update:status',
baileys_sock.authState.creds.me,
baileys_sock.authState.creds.account,
connection,
);
}
if (update.qr) {
socket.timeout(1000).emit('connection.update:qr', update.qr);
}
});
baileys_sock.ws.on('CB:call', (packet) => {
if (logger) console.log('[*] Signling received');
socket.volatile.timeout(1000).emit('CB:call', packet);
});
baileys_sock.ws.on('CB:ack,class:call', (packet) => {
if (logger) console.log('[*] Signling ack received');
socket.volatile.timeout(1000).emit('CB:ack,class:call', packet);
});
return socket;
};

View File

@@ -26,7 +26,6 @@ import makeWASocket, {
MessageUpsertType,
MiscMessageGenerationOptions,
ParticipantAction,
PHONENUMBER_MCC,
prepareWAMessageMedia,
proto,
useMultiFileAuthState,
@@ -45,7 +44,6 @@ import { isBase64, isURL } from 'class-validator';
import EventEmitter2 from 'eventemitter2';
// import ffmpeg from 'fluent-ffmpeg';
import fs, { existsSync, readFileSync } from 'fs';
import { parsePhoneNumber } from 'libphonenumber-js';
import Long from 'long';
import NodeCache from 'node-cache';
import { getMIMEType } from 'node-mime-types';
@@ -66,6 +64,7 @@ import {
Log,
ProviderSession,
QrCode,
Websocket,
} from '../../../config/env.config';
import { INSTANCE_DIR } from '../../../config/path.config';
import { BadRequestException, InternalServerErrorException, NotFoundException } from '../../../exceptions';
@@ -132,6 +131,7 @@ import { waMonitor } from '../../server.module';
import { Events, MessageSubtype, TypeMediaMessage, wa } from '../../types/wa.types';
import { CacheService } from './../cache.service';
import { ChannelStartupService } from './../channel.service';
import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys';
const groupMetadataCache = new CacheService(new CacheEngine(configService, 'groups').getEngine());
@@ -381,12 +381,6 @@ export class BaileysStartupService extends ChannelStartupService {
state: connection,
statusReason: (lastDisconnect?.error as Boom)?.output?.statusCode ?? 200,
};
this.logger.verbose('Sending data to webhook in event CONNECTION_UPDATE');
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
instance: this.instance.name,
...this.stateConnection,
});
}
if (connection === 'close') {
@@ -419,6 +413,15 @@ export class BaileysStartupService extends ChannelStartupService {
this.client?.ws?.close();
this.client.end(new Error('Close connection'));
this.logger.verbose('Connection closed');
this.logger.verbose('Sending data to webhook in event CONNECTION_UPDATE');
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
instance: this.instance.name,
wuid: this.instance.wuid,
profileName: await this.getProfileName(),
profilePictureUrl: this.instance.profilePictureUrl,
...this.stateConnection,
});
}
}
@@ -448,13 +451,34 @@ export class BaileysStartupService extends ChannelStartupService {
{
instance: this.instance.name,
status: 'open',
wuid: this.instance.wuid,
profileName: await this.getProfileName(),
profilePictureUrl: this.instance.profilePictureUrl,
},
);
}
this.logger.verbose('Sending data to webhook in event CONNECTION_UPDATE');
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
instance: this.instance.name,
wuid: this.instance.wuid,
profileName: await this.getProfileName(),
profilePictureUrl: this.instance.profilePictureUrl,
...this.stateConnection,
});
}
if (connection === 'connecting') {
if (this.mobile) this.sendMobileCode();
this.logger.verbose('Sending data to webhook in event CONNECTION_UPDATE');
this.sendDataWebhook(Events.CONNECTION_UPDATE, {
instance: this.instance.name,
wuid: this.instance.wuid,
profileName: await this.getProfileName(),
profilePictureUrl: this.instance.profilePictureUrl,
...this.stateConnection,
});
}
}
@@ -646,10 +670,32 @@ export class BaileysStartupService extends ChannelStartupService {
this.logger.verbose('Socket created');
if (this.localSettings.wavoipToken && this.localSettings.wavoipToken.length > 0) {
useVoiceCallsBaileys(this.localSettings.wavoipToken, this.client, this.connectionStatus.state as any, true);
}
this.eventHandler();
this.logger.verbose('Socket event handler initialized');
this.client.ws.on('CB:call', (packet) => {
console.log('CB:call', packet);
const payload = {
event: 'CB:call',
packet: packet,
};
this.sendDataWebhook(Events.CALL, payload, true, ['websocket']);
});
this.client.ws.on('CB:ack,class:call', (packet) => {
console.log('CB:ack,class:call', packet);
const payload = {
event: 'CB:ack,class:call',
packet: packet,
};
this.sendDataWebhook(Events.CALL, payload, true, ['websocket']);
});
this.phoneNumber = number;
return this.client;
@@ -676,61 +722,51 @@ export class BaileysStartupService extends ChannelStartupService {
}
private async sendMobileCode() {
const { registration } = this.client.authState.creds || null;
let phoneNumber = registration.phoneNumber || this.phoneNumber;
if (!phoneNumber.startsWith('+')) {
phoneNumber = '+' + phoneNumber;
}
if (!phoneNumber) {
this.logger.error('Phone number not found');
return;
}
const parsedPhoneNumber = parsePhoneNumber(phoneNumber);
if (!parsedPhoneNumber?.isValid()) {
this.logger.error('Phone number invalid');
return;
}
registration.phoneNumber = parsedPhoneNumber.format('E.164');
registration.phoneNumberCountryCode = parsedPhoneNumber.countryCallingCode;
registration.phoneNumberNationalNumber = parsedPhoneNumber.nationalNumber;
const mcc = await PHONENUMBER_MCC[parsedPhoneNumber.countryCallingCode];
if (!mcc) {
this.logger.error('MCC not found');
return;
}
registration.phoneNumberMobileCountryCode = mcc;
registration.method = 'sms';
try {
const response = await this.client.requestRegistrationCode(registration);
if (['ok', 'sent'].includes(response?.status)) {
this.logger.verbose('Registration code sent successfully');
return response;
}
} catch (error) {
this.logger.error(error);
}
// const { registration } = this.client.authState.creds || null;
// let phoneNumber = registration.phoneNumber || this.phoneNumber;
// if (!phoneNumber.startsWith('+')) {
// phoneNumber = '+' + phoneNumber;
// }
// if (!phoneNumber) {
// this.logger.error('Phone number not found');
// return;
// }
// const parsedPhoneNumber = parsePhoneNumber(phoneNumber);
// if (!parsedPhoneNumber?.isValid()) {
// this.logger.error('Phone number invalid');
// return;
// }
// registration.phoneNumber = parsedPhoneNumber.format('E.164');
// registration.phoneNumberCountryCode = parsedPhoneNumber.countryCallingCode;
// registration.phoneNumberNationalNumber = parsedPhoneNumber.nationalNumber;
// const mcc = await PHONENUMBER_MCC[parsedPhoneNumber.countryCallingCode];
// if (!mcc) {
// this.logger.error('MCC not found');
// return;
// }
// registration.phoneNumberMobileCountryCode = mcc;
// registration.method = 'sms';
// try {
// const response = await this.client.requestRegistrationCode(registration);
// if (['ok', 'sent'].includes(response?.status)) {
// this.logger.verbose('Registration code sent successfully');
// return response;
// }
// } catch (error) {
// this.logger.error(error);
// }
}
public async receiveMobileCode(code: string) {
await this.client
.register(code.replace(/["']/g, '').trim().toLowerCase())
.then(async () => {
this.logger.verbose('Registration code received successfully');
})
.catch((error) => {
this.logger.error(error);
});
console.log(code);
// await this.client
// .register(code.replace(/["']/g, '').trim().toLowerCase())
// .then(async () => {
// this.logger.verbose('Registration code received successfully');
// })
// .catch((error) => {
// this.logger.error(error);
// });
}
public async reloadConnection(): Promise<WASocket> {
@@ -897,7 +933,7 @@ export class BaileysStartupService extends ChannelStartupService {
chats: Chat[];
contacts: Contact[];
messages: proto.IWebMessageInfo[];
isLatest: boolean;
isLatest?: boolean;
},
database: Database,
) => {
@@ -1017,7 +1053,7 @@ export class BaileysStartupService extends ChannelStartupService {
await this.contactHandle['contacts.upsert'](
contacts
.filter((c) => !!c.notify ?? !!c.name)
.filter((c) => !!c.notify || !!c.name)
.map((c) => ({
id: c.id,
name: c.name ?? c.notify,
@@ -1103,7 +1139,12 @@ export class BaileysStartupService extends ChannelStartupService {
const contentMsg = received?.message[getContentType(received.message)] as any;
if (this.localWebhook.webhook_base64 === true && isMedia) {
if (
(this.localWebhook.webhook_base64 === true ||
(this.configService.get<Websocket>('WEBSOCKET').GLOBAL_EVENTS === true &&
this.configService.get<Websocket>('WEBSOCKET').ENABLED === true)) &&
isMedia
) {
const buffer = await downloadMediaMessage(
{ key: received.key, message: received?.message },
'buffer',
@@ -1944,6 +1985,32 @@ export class BaileysStartupService extends ChannelStartupService {
source: getDevice(messageSent.key.id),
};
const isMedia =
messageRaw.messageType === 'imageMessage' ||
messageRaw.messageType === 'videoMessage' ||
messageRaw.messageType === 'documentMessage' ||
messageRaw.messageType === 'audioMessage';
console.log('isMedia', isMedia);
if (
(this.localWebhook.webhook_base64 === true ||
(this.configService.get<Websocket>('WEBSOCKET').GLOBAL_EVENTS === true &&
this.configService.get<Websocket>('WEBSOCKET').ENABLED === true)) &&
isMedia
) {
const buffer = await downloadMediaMessage(
{ key: messageRaw.key, message: messageRaw?.message },
'buffer',
{},
{
logger: P({ level: 'error' }) as any,
reuploadRequest: this.client.updateMediaMessage,
},
);
messageRaw.message.base64 = buffer ? buffer.toString('base64') : undefined;
}
this.logger.log(messageRaw);
this.logger.verbose('Sending data to webhook in event SEND_MESSAGE');
@@ -2590,7 +2657,7 @@ export class BaileysStartupService extends ChannelStartupService {
const group = await this.findGroup({ groupJid: jid }, 'inner');
if (!group) {
new OnWhatsAppDto(jid, false, number);
return new OnWhatsAppDto(jid, false, number);
}
return new OnWhatsAppDto(group.id, !!group?.id, number, group?.subject);
@@ -3260,10 +3327,6 @@ export class BaileysStartupService extends ChannelStartupService {
}
public async findGroup(id: GroupJid, reply: 'inner' | 'out' = 'out') {
if (this.localSettings.groups_ignore === true) {
return;
}
this.logger.verbose('Fetching group');
try {
const group = await this.client.groupMetadata(id.groupJid);

View File

@@ -743,6 +743,7 @@ export class BusinessStartupService extends ChannelStartupService {
[message['type']]: message['id'],
preview_url: linkPreview,
caption: message['caption'],
filename: message['fileName'],
},
};
quoted ? (content.context = { message_id: quoted.id }) : content;
@@ -1212,7 +1213,7 @@ export class BusinessStartupService extends ChannelStartupService {
try {
const msg = data.message;
this.logger.verbose('Getting base64 from media message');
const messageType = msg.messageType + 'Message';
const messageType = msg.messageType.includes('Message') ? msg.messageType : msg.messageType + 'Message';
const mediaMessage = msg.message[messageType];
this.logger.verbose('Media message downloaded');

View File

@@ -83,6 +83,7 @@ export declare namespace wa {
read_messages?: boolean;
read_status?: boolean;
sync_full_history?: boolean;
wavoipToken?: string;
};
export type LocalWebsocket = {

View File

@@ -104,7 +104,11 @@ export type Rabbitmq = {
ENABLED: boolean;
URI: string;
EXCHANGE_NAME: string;
PREFIX_KEY?: string;
GLOBAL_ENABLED: boolean;
MESSAGE_TTL: number;
MAX_LENGTH: number;
MAX_LENGTH_BYTES: number;
EVENTS: EventsRabbitmq;
};
@@ -323,7 +327,11 @@ export class ConfigService {
ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
GLOBAL_ENABLED: process.env?.RABBITMQ_GLOBAL_ENABLED === 'true',
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
PREFIX_KEY: process.env?.RABBITMQ_PREFIX_KEY || '',
URI: process.env.RABBITMQ_URI || '',
MESSAGE_TTL: Number.parseInt(process.env?.RABBITMQ_MESSAGE_TTL) || 604800,
MAX_LENGTH: Number.parseInt(process.env?.RABBITMQ_MAX_LENGTH) || 10000,
MAX_LENGTH_BYTES: Number.parseInt(process.env?.RABBITMQ_MAX_LENGTH_BYTES) || 8192,
EVENTS: {
APPLICATION_STARTUP: process.env?.RABBITMQ_EVENTS_APPLICATION_STARTUP === 'true',
INSTANCE_CREATE: process.env?.RABBITMQ_EVENTS_INSTANCE_CREATE === 'true',

View File

@@ -89,7 +89,14 @@ RABBITMQ:
ENABLED: false
URI: "amqp://guest:guest@localhost:5672"
EXCHANGE_NAME: evolution_exchange
PREFIX_KEY: evolution
GLOBAL_ENABLED: true
# Tempo de vida das mensagens: 1 hora em milissegundos (3600000 = 60 * 60 * 1000)
MESSAGE_TTL: 3600000
# Limite máximo de mensagens por fila (quando atingido, novas mensagens são rejeitadas)
MAX_LENGTH: 1000
# Tamanho máximo em bytes permitido para filas: 10MB (10485760 = 10 * 1024 * 1024)
MAX_LENGTH_BYTES: 10485760
EVENTS:
APPLICATION_STARTUP: false
INSTANCE_CREATE: false

View File

@@ -1002,9 +1002,26 @@ export const settingsSchema: JSONSchema7 = {
read_messages: { type: 'boolean', enum: [true, false] },
read_status: { type: 'boolean', enum: [true, false] },
sync_full_history: { type: 'boolean', enum: [true, false] },
wavoipToken: { type: 'string' },
},
required: ['reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'],
...isNotEmpty('reject_call', 'groups_ignore', 'always_online', 'read_messages', 'read_status', 'sync_full_history'),
required: [
'reject_call',
'groups_ignore',
'always_online',
'read_messages',
'read_status',
'sync_full_history',
'wavoipToken',
],
...isNotEmpty(
'reject_call',
'groups_ignore',
'always_online',
'read_messages',
'read_status',
'sync_full_history',
'wavoipToken',
),
};
export const websocketSchema: JSONSchema7 = {