Improvements in RabbitMQ, added 2 new modes

This commit is contained in:
Judson Cairo 2024-03-04 20:24:15 -03:00
parent 7dd589fb6c
commit ac6e9ae994
7 changed files with 148 additions and 55 deletions

View File

@ -47,7 +47,8 @@ REDIS_URI=redis://redis:6379
REDIS_PREFIX_KEY=evdocker REDIS_PREFIX_KEY=evdocker
RABBITMQ_ENABLED=false RABBITMQ_ENABLED=false
RABBITMQ_GLOBAL_EVENT_QUEUE=false RABBITMQ_RABBITMQ_MODE=isolated
RABBITMQ_EXCHANGE_NAME=evolution_exchange
RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672 RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
WEBSOCKET_ENABLED=false WEBSOCKET_ENABLED=false

View File

@ -62,7 +62,8 @@ ENV REDIS_URI=redis://redis:6379
ENV REDIS_PREFIX_KEY=evolution ENV REDIS_PREFIX_KEY=evolution
ENV RABBITMQ_ENABLED=false ENV RABBITMQ_ENABLED=false
ENV RABBITMQ_GLOBAL_EVENT_QUEUE=false ENV RABBITMQ_MODE=isolated
ENV RABBITMQ_EXCHANGE_NAME=evolution_exchange
ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672 ENV RABBITMQ_URI=amqp://guest:guest@rabbitmq:5672
ENV WEBSOCKET_ENABLED=false ENV WEBSOCKET_ENABLED=false

View File

@ -91,6 +91,7 @@
"yamljs": "^0.3.0" "yamljs": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"@types/amqplib": "^0.10.5",
"@types/compression": "^1.7.2", "@types/compression": "^1.7.2",
"@types/cors": "^2.8.13", "@types/cors": "^2.8.13",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",

View File

@ -71,7 +71,8 @@ export type Redis = {
export type Rabbitmq = { export type Rabbitmq = {
ENABLED: boolean; ENABLED: boolean;
GLOBAL_EVENT_QUEUE: boolean; MODE: 'isolated' | 'global' | 'single';
EXCHANGE_NAME: string; // available for global and single, isolated mode will use instance name as exchange
URI: string; URI: string;
}; };
@ -283,7 +284,8 @@ export class ConfigService {
}, },
RABBITMQ: { RABBITMQ: {
ENABLED: process.env?.RABBITMQ_ENABLED === 'true', ENABLED: process.env?.RABBITMQ_ENABLED === 'true',
GLOBAL_EVENT_QUEUE: process.env?.RABBITMQ_GLOBAL_EVENT_QUEUE === 'true', MODE: (process.env?.RABBITMQ_MODE as Rabbitmq['MODE']) || 'single',
EXCHANGE_NAME: process.env?.RABBITMQ_EXCHANGE_NAME || 'evolution_exchange',
URI: process.env.RABBITMQ_URI || '', URI: process.env.RABBITMQ_URI || '',
}, },
SQS: { SQS: {

View File

@ -83,7 +83,8 @@ REDIS:
RABBITMQ: RABBITMQ:
ENABLED: false ENABLED: false
GLOBAL_EVENT_QUEUE: false MODE: "global"
EXCHANGE_NAME: "evolution_exchange"
URI: "amqp://guest:guest@localhost:5672" URI: "amqp://guest:guest@localhost:5672"
SQS: SQS:

View File

@ -1,16 +1,35 @@
import * as amqp from 'amqplib/callback_api'; import { Channel, connect } from 'amqplib/callback_api';
import { configService, HttpServer, Rabbitmq } from '../config/env.config'; import { configService, HttpServer, Rabbitmq } from '../config/env.config';
import { Logger } from '../config/logger.config'; import { Logger } from '../config/logger.config';
import { Events } from '../whatsapp/types/wa.types';
const logger = new Logger('AMQP'); const logger = new Logger('AMQP');
let amqpChannel: amqp.Channel | null = null; const parseEvtName = (evt: string) => evt.replace(/_/g, '.').toLowerCase();
const globalQueues: { [key: string]: Events[] } = {
contacts: [Events.CONTACTS_SET, Events.CONTACTS_UPDATE, Events.CONTACTS_UPSERT],
messages: [
Events.MESSAGES_DELETE,
Events.MESSAGES_SET,
Events.MESSAGES_UPDATE,
Events.MESSAGES_UPSERT,
Events.MESSAGING_HISTORY_SET,
Events.SEND_MESSAGE,
],
chats: [Events.CHATS_DELETE, Events.CHATS_SET, Events.CHATS_UPDATE, Events.CHATS_UPSERT],
groups: [Events.GROUPS_UPDATE, Events.GROUPS_UPSERT, Events.GROUP_PARTICIPANTS_UPDATE],
others: [], // All other events not included in the above categories
};
let amqpChannel: Channel | null = null;
export const initAMQP = () => { export const initAMQP = () => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ'); const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ');
amqp.connect(rabbitConfig.URI, (error, connection) => { console.log(rabbitConfig);
connect(rabbitConfig.URI, (error, connection) => {
if (error) { if (error) {
reject(error); reject(error);
return; return;
@ -22,12 +41,9 @@ export const initAMQP = () => {
return; return;
} }
const exchangeName = 'evolution_exchange'; channel.assertExchange(rabbitConfig.EXCHANGE_NAME, 'topic', {
channel.assertExchange(exchangeName, 'topic', {
durable: true, durable: true,
autoDelete: false, autoDelete: false,
assert: true,
}); });
amqpChannel = channel; amqpChannel = channel;
@ -39,69 +55,131 @@ export const initAMQP = () => {
}); });
}; };
export const getAMQP = (): amqp.Channel | null => { export const getAMQP = (): Channel | null => {
return amqpChannel; return amqpChannel;
}; };
export const initQueues = (instanceName: string, events: string[]) => { export const initQueues = (instanceName: string, events: string[]) => {
if (!instanceName || !events || !events.length) return; if (!instanceName || !events || !events.length) return;
const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ'); const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ');
const TWO_DAYS_IN_MS = 2 * 24 * 60 * 60 * 1000;
const amqp = getAMQP();
const queues = events.map((event) => { let exchangeName = rabbitConfig.EXCHANGE_NAME;
return `${event.replace(/_/g, '.').toLowerCase()}`;
});
queues.forEach((event) => { const receivedEvents = events.map(parseEvtName);
const amqp = getAMQP(); if (rabbitConfig.MODE === 'isolated') {
const exchangeName = instanceName ?? 'evolution_exchange'; exchangeName = instanceName;
receivedEvents.forEach((event) => {
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queueName = event;
amqp.assertQueue(queueName, {
durable: true,
autoDelete: false,
messageTtl: TWO_DAYS_IN_MS,
arguments: {
'x-queue-type': 'quorum',
},
});
amqp.bindQueue(queueName, exchangeName, event);
});
} else if (rabbitConfig.MODE === 'single') {
amqp.assertExchange(exchangeName, 'topic', { amqp.assertExchange(exchangeName, 'topic', {
durable: true, durable: true,
autoDelete: false, autoDelete: false,
assert: true,
}); });
const queueName = rabbitConfig.GLOBAL_EVENT_QUEUE ? event : `${instanceName}.${event}`; const queueName = 'evolution';
amqp.assertQueue(queueName, { amqp.assertQueue(queueName, {
durable: true, durable: true,
autoDelete: false, autoDelete: false,
messageTtl: TWO_DAYS_IN_MS,
arguments: { arguments: {
'x-queue-type': 'quorum', 'x-queue-type': 'quorum',
}, },
}); });
amqp.bindQueue(queueName, exchangeName, event); receivedEvents.forEach((event) => {
}); amqp.bindQueue(queueName, exchangeName, event);
});
} else if (rabbitConfig.MODE === 'global') {
const queues = Object.keys(globalQueues);
const addQueues = queues.filter((evt) => {
if (evt === 'others') {
return receivedEvents.some(
(e) =>
!Object.values(globalQueues)
.flat()
.includes(e as Events),
);
}
return globalQueues[evt].some((e) => receivedEvents.includes(e));
});
addQueues.forEach((event) => {
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queueName = event;
amqp.assertQueue(queueName, {
durable: true,
autoDelete: false,
messageTtl: TWO_DAYS_IN_MS,
arguments: {
'x-queue-type': 'quorum',
},
});
if (globalQueues[event].length === 0) {
// Other events
const otherEvents = Object.values(globalQueues).flat();
for (const subEvent in Events) {
const eventCode = Events[subEvent];
if (otherEvents.includes(eventCode)) continue;
if (!receivedEvents.includes(eventCode)) continue;
amqp.bindQueue(queueName, exchangeName, eventCode);
}
} else {
globalQueues[event].forEach((subEvent) => {
amqp.bindQueue(queueName, exchangeName, subEvent);
});
}
});
} else {
throw new Error('Invalid RabbitMQ mode');
}
}; };
export const removeQueues = (instanceName: string, events: string[]) => { export const removeQueues = (instanceName: string, events: string[]) => {
if (!events || !events.length) return; if (!events || !events.length) return;
const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ'); const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ');
let exchangeName = rabbitConfig.EXCHANGE_NAME;
const amqp = getAMQP();
const channel = getAMQP(); const receivedEvents = events.map(parseEvtName);
if (rabbitConfig.MODE === 'isolated') {
exchangeName = instanceName;
receivedEvents.forEach((event) => {
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
});
const queues = events.map((event) => { const queueName = event;
return `${event.replace(/_/g, '.').toLowerCase()}`;
});
const exchangeName = instanceName ?? 'evolution_exchange'; amqp.unbindQueue(queueName, exchangeName, event);
queues.forEach((event) => {
const amqp = getAMQP();
amqp.assertExchange(exchangeName, 'topic', {
durable: true,
autoDelete: false,
assert: true,
}); });
amqp.deleteExchange(instanceName);
const queueName = rabbitConfig.GLOBAL_EVENT_QUEUE ? event : `${instanceName}.${event}`; }
amqp.deleteQueue(queueName);
});
channel.deleteExchange(exchangeName);
}; };
interface SendEventData { interface SendEventData {
@ -113,30 +191,40 @@ interface SendEventData {
} }
export const sendEventData = ({ data, event, wuid, apiKey, instanceName }: SendEventData) => { export const sendEventData = ({ data, event, wuid, apiKey, instanceName }: SendEventData) => {
const exchangeName = instanceName ?? 'evolution_exchange'; const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ');
let exchangeName = rabbitConfig.EXCHANGE_NAME;
if (rabbitConfig.MODE === 'isolated') exchangeName = instanceName;
amqpChannel.assertExchange(exchangeName, 'topic', { amqpChannel.assertExchange(exchangeName, 'topic', {
durable: true, durable: true,
autoDelete: false, autoDelete: false,
assert: true,
}); });
let queueName = event;
const rabbitConfig = configService.get<Rabbitmq>('RABBITMQ'); if (rabbitConfig.MODE === 'single') {
const queueName = rabbitConfig.GLOBAL_EVENT_QUEUE ? event : `${instanceName}.${event}`; queueName = 'evolution';
} else if (rabbitConfig.MODE === 'global') {
let eventName = '';
Object.keys(globalQueues).forEach((key) => {
if (globalQueues[key].includes(event as Events)) {
eventName = key;
}
if (eventName === '' && key === 'others') {
eventName = key;
}
});
queueName = eventName;
}
amqpChannel.assertQueue(queueName, { amqpChannel.assertQueue(queueName, {
durable: true, durable: true,
autoDelete: false, autoDelete: false,
arguments: { 'x-queue-type': 'quorum' }, arguments: { 'x-queue-type': 'quorum' },
}); });
amqpChannel.bindQueue(queueName, exchangeName, event); amqpChannel.bindQueue(queueName, exchangeName, event);
const serverUrl = configService.get<HttpServer>('SERVER').URL; const serverUrl = configService.get<HttpServer>('SERVER').URL;
const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds const tzoffset = new Date().getTimezoneOffset() * 60000; //offset in milliseconds
const localISOTime = new Date(Date.now() - tzoffset).toISOString(); const localISOTime = new Date(Date.now() - tzoffset).toISOString();
const now = localISOTime; const now = localISOTime;
const message = { const message = {
event, event,
instance: instanceName, instance: instanceName,
@ -145,11 +233,9 @@ export const sendEventData = ({ data, event, wuid, apiKey, instanceName }: SendE
date_time: now, date_time: now,
sender: wuid, sender: wuid,
}; };
if (apiKey) { if (apiKey) {
message['apikey'] = apiKey; message['apikey'] = apiKey;
} }
logger.log({ logger.log({
queueName, queueName,
exchangeName, exchangeName,

View File

@ -1124,7 +1124,8 @@ export class BaileysStartupService extends WAStartupService {
5: 'PLAYED', 5: 'PLAYED',
}; };
for await (const { key, update } of args) { for await (const { key, update } of args) {
if (settings?.groups_ignore && key.remoteJid.includes('@g.us')) { console.log(key);
if (settings?.groups_ignore && key.remoteJid?.includes('@g.us')) {
this.logger.verbose('group ignored'); this.logger.verbose('group ignored');
return; return;
} }