@omnizap-system/omnizap 2.6.2 → 2.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +24 -0
- package/app/config/index.js +4 -0
- package/app/configParts/adminIdentity.js +29 -0
- package/app/configParts/baileysConfig.js +116 -0
- package/app/configParts/groupUtils.js +221 -0
- package/app/configParts/loggerConfig.js +185 -0
- package/app/configParts/messagePersistenceService.js +169 -7
- package/app/configParts/sessionConfig.js +85 -0
- package/app/connection/baileysCompatibility.test.js +9 -0
- package/app/connection/baileysDbAuthState.js +205 -9
- package/app/connection/baileysLibsignalPatch.js +210 -0
- package/app/connection/groupOwnerWriteStateResolver.js +53 -21
- package/app/connection/socketController.js +95 -25
- package/app/connection/socketController.multiSession.test.js +20 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +17 -3
- package/app/controllers/messageProcessingPipeline.js +2 -0
- package/app/controllers/messageProcessingPipeline.test.js +15 -13
- package/app/services/multiSession/assignmentBalancerService.js +1 -6
- package/app/services/multiSession/groupOwnershipRepository.js +9 -44
- package/app/services/multiSession/groupOwnershipService.js +9 -90
- package/app/services/multiSession/groupOwnershipService.test.js +12 -4
- package/app/services/multiSession/sessionRegistryService.js +6 -60
- package/app/utils/antiLink/antiLinkModule.js +54 -24
- package/docs/security/omnizap-static-security-headers.conf +3 -3
- package/package.json +3 -2
- package/public/comandos/commands-catalog.json +1 -1
- package/public/css/payments-react.css +478 -0
- package/public/js/apps/homeReactApp.js +2 -2
- package/public/js/apps/paymentsCancelReactApp.js +45 -0
- package/public/js/apps/paymentsReactApp.js +399 -0
- package/public/js/apps/paymentsSuccessReactApp.js +148 -0
- package/public/pages/pagamentos-cancelado.html +21 -0
- package/public/pages/pagamentos-sucesso.html +21 -0
- package/public/pages/pagamentos.html +30 -0
- package/scripts/deploy.sh +3 -0
- package/scripts/new-whatsapp-session.sh +247 -0
- package/server/controllers/admin/systemAdminController.js +4 -17
- package/server/controllers/payments/paymentsController.js +731 -0
- package/server/controllers/system/systemController.js +4 -30
- package/server/email/emailAutomationRuntime.js +36 -1
- package/server/email/emailAutomationService.js +42 -1
- package/server/email/emailTemplateService.js +137 -31
- package/server/http/httpRequestUtils.js +18 -14
- package/server/middleware/securityHeaders.js +15 -2
- package/server/routes/indexRouter.js +27 -7
- package/server/routes/payments/paymentsRouter.js +47 -0
- package/server/routes/static/staticPageRouter.js +3 -0
- package/vite.config.mjs +3 -0
|
@@ -2,14 +2,50 @@ import pino from 'pino';
|
|
|
2
2
|
import { criarInstanciaLogger } from '@kaikybrofc/logger-module';
|
|
3
3
|
import baseLogger from '#logger';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Label padrão para logger do Baileys.
|
|
7
|
+
* @type {string}
|
|
8
|
+
*/
|
|
5
9
|
const DEFAULT_BAILEYS_LABEL = 'baileys';
|
|
10
|
+
/**
|
|
11
|
+
* Modo padrão de logger raiz.
|
|
12
|
+
* @type {'child'|'instance'}
|
|
13
|
+
*/
|
|
6
14
|
const DEFAULT_BAILEYS_LOGGER_MODE = 'child';
|
|
15
|
+
/**
|
|
16
|
+
* Modos aceitos de logger raiz.
|
|
17
|
+
* @type {Set<string>}
|
|
18
|
+
*/
|
|
7
19
|
const BAILEYS_LOGGER_MODES = new Set(['child', 'instance']);
|
|
20
|
+
/**
|
|
21
|
+
* Modo padrão do logger de socket.
|
|
22
|
+
* @type {'silent'|'pino'|'bridge'}
|
|
23
|
+
*/
|
|
8
24
|
const DEFAULT_BAILEYS_SOCKET_LOGGER_MODE = 'silent';
|
|
25
|
+
/**
|
|
26
|
+
* Modos aceitos de logger de socket.
|
|
27
|
+
* @type {Set<string>}
|
|
28
|
+
*/
|
|
9
29
|
const BAILEYS_SOCKET_LOGGER_MODES = new Set(['silent', 'pino', 'bridge']);
|
|
30
|
+
/**
|
|
31
|
+
* Nível padrão para pino.
|
|
32
|
+
* @type {string}
|
|
33
|
+
*/
|
|
10
34
|
const DEFAULT_PINO_LEVEL = 'info';
|
|
35
|
+
/**
|
|
36
|
+
* Nível pino para suprimir logs.
|
|
37
|
+
* @type {string}
|
|
38
|
+
*/
|
|
11
39
|
const DEFAULT_PINO_SILENT_LEVEL = 'silent';
|
|
40
|
+
/**
|
|
41
|
+
* Conjunto de níveis pino válidos.
|
|
42
|
+
* @type {Set<string>}
|
|
43
|
+
*/
|
|
12
44
|
const PINO_LEVELS = new Set(['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'silent']);
|
|
45
|
+
/**
|
|
46
|
+
* Prioridade numérica dos níveis pino.
|
|
47
|
+
* @type {Readonly<Record<string, number>>}
|
|
48
|
+
*/
|
|
13
49
|
const PINO_LEVEL_PRIORITY = Object.freeze({
|
|
14
50
|
trace: 10,
|
|
15
51
|
debug: 20,
|
|
@@ -19,6 +55,10 @@ const PINO_LEVEL_PRIORITY = Object.freeze({
|
|
|
19
55
|
fatal: 60,
|
|
20
56
|
silent: Number.POSITIVE_INFINITY,
|
|
21
57
|
});
|
|
58
|
+
/**
|
|
59
|
+
* Mapeamento de níveis do bridge pino->winston.
|
|
60
|
+
* @type {Readonly<Record<string, string>>}
|
|
61
|
+
*/
|
|
22
62
|
const BAILEYS_TO_WINSTON_LEVEL = Object.freeze({
|
|
23
63
|
trace: 'debug',
|
|
24
64
|
debug: 'debug',
|
|
@@ -26,6 +66,10 @@ const BAILEYS_TO_WINSTON_LEVEL = Object.freeze({
|
|
|
26
66
|
warn: 'warn',
|
|
27
67
|
error: 'error',
|
|
28
68
|
});
|
|
69
|
+
/**
|
|
70
|
+
* Prioridade por método de log esperado pelo Baileys.
|
|
71
|
+
* @type {Readonly<Record<string, number>>}
|
|
72
|
+
*/
|
|
29
73
|
const BAILEYS_LOG_METHOD_PRIORITY = Object.freeze({
|
|
30
74
|
trace: 10,
|
|
31
75
|
debug: 20,
|
|
@@ -34,6 +78,12 @@ const BAILEYS_LOG_METHOD_PRIORITY = Object.freeze({
|
|
|
34
78
|
error: 50,
|
|
35
79
|
});
|
|
36
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Interpreta valor de ambiente booleano.
|
|
83
|
+
* @param {unknown} value
|
|
84
|
+
* @param {boolean} fallback
|
|
85
|
+
* @returns {boolean}
|
|
86
|
+
*/
|
|
37
87
|
const parseEnvBool = (value, fallback) => {
|
|
38
88
|
if (value === undefined || value === null || value === '') return fallback;
|
|
39
89
|
const normalized = String(value).trim().toLowerCase();
|
|
@@ -42,6 +92,13 @@ const parseEnvBool = (value, fallback) => {
|
|
|
42
92
|
return fallback;
|
|
43
93
|
};
|
|
44
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Faz parse de JSON objeto com fallback seguro.
|
|
97
|
+
* @param {unknown} value
|
|
98
|
+
* @param {Record<string, any>} [fallback={}]
|
|
99
|
+
* @param {string} [context='JSON']
|
|
100
|
+
* @returns {Record<string, any>}
|
|
101
|
+
*/
|
|
45
102
|
const parseJsonObject = (value, fallback = {}, context = 'JSON') => {
|
|
46
103
|
if (value === undefined || value === null || String(value).trim() === '') {
|
|
47
104
|
return { ...fallback };
|
|
@@ -61,6 +118,11 @@ const parseJsonObject = (value, fallback = {}, context = 'JSON') => {
|
|
|
61
118
|
}
|
|
62
119
|
};
|
|
63
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Faz parse de definições de transportes customizados.
|
|
123
|
+
* @param {unknown} value
|
|
124
|
+
* @returns {Array<{type: string, options: Record<string, any>}>|undefined}
|
|
125
|
+
*/
|
|
64
126
|
const parseTransportDefinitions = (value) => {
|
|
65
127
|
if (value === undefined || value === null || String(value).trim() === '') {
|
|
66
128
|
return undefined;
|
|
@@ -80,6 +142,12 @@ const parseTransportDefinitions = (value) => {
|
|
|
80
142
|
}
|
|
81
143
|
};
|
|
82
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Normaliza modo do logger raiz.
|
|
147
|
+
* @param {unknown} value
|
|
148
|
+
* @param {'child'|'instance'} [fallback=DEFAULT_BAILEYS_LOGGER_MODE]
|
|
149
|
+
* @returns {'child'|'instance'}
|
|
150
|
+
*/
|
|
83
151
|
const normalizeLoggerMode = (value, fallback = DEFAULT_BAILEYS_LOGGER_MODE) => {
|
|
84
152
|
const normalized = String(value || '')
|
|
85
153
|
.trim()
|
|
@@ -87,6 +155,12 @@ const normalizeLoggerMode = (value, fallback = DEFAULT_BAILEYS_LOGGER_MODE) => {
|
|
|
87
155
|
return BAILEYS_LOGGER_MODES.has(normalized) ? normalized : fallback;
|
|
88
156
|
};
|
|
89
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Normaliza modo do logger de socket.
|
|
160
|
+
* @param {unknown} value
|
|
161
|
+
* @param {'silent'|'pino'|'bridge'} [fallback=DEFAULT_BAILEYS_SOCKET_LOGGER_MODE]
|
|
162
|
+
* @returns {'silent'|'pino'|'bridge'}
|
|
163
|
+
*/
|
|
90
164
|
const normalizeSocketLoggerMode = (value, fallback = DEFAULT_BAILEYS_SOCKET_LOGGER_MODE) => {
|
|
91
165
|
const normalized = String(value || '')
|
|
92
166
|
.trim()
|
|
@@ -94,6 +168,12 @@ const normalizeSocketLoggerMode = (value, fallback = DEFAULT_BAILEYS_SOCKET_LOGG
|
|
|
94
168
|
return BAILEYS_SOCKET_LOGGER_MODES.has(normalized) ? normalized : fallback;
|
|
95
169
|
};
|
|
96
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Normaliza label de logger.
|
|
173
|
+
* @param {unknown} value
|
|
174
|
+
* @param {string} [fallback=DEFAULT_BAILEYS_LABEL]
|
|
175
|
+
* @returns {string}
|
|
176
|
+
*/
|
|
97
177
|
const normalizeLabel = (value, fallback = DEFAULT_BAILEYS_LABEL) => {
|
|
98
178
|
const normalized = String(value || '')
|
|
99
179
|
.trim()
|
|
@@ -101,6 +181,12 @@ const normalizeLabel = (value, fallback = DEFAULT_BAILEYS_LABEL) => {
|
|
|
101
181
|
return normalized || fallback;
|
|
102
182
|
};
|
|
103
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Normaliza nível pino para um valor aceito.
|
|
186
|
+
* @param {unknown} value
|
|
187
|
+
* @param {string} [fallback=DEFAULT_PINO_LEVEL]
|
|
188
|
+
* @returns {string}
|
|
189
|
+
*/
|
|
104
190
|
const normalizePinoLevel = (value, fallback = DEFAULT_PINO_LEVEL) => {
|
|
105
191
|
const normalized = String(value || '')
|
|
106
192
|
.trim()
|
|
@@ -108,6 +194,11 @@ const normalizePinoLevel = (value, fallback = DEFAULT_PINO_LEVEL) => {
|
|
|
108
194
|
return PINO_LEVELS.has(normalized) ? normalized : fallback;
|
|
109
195
|
};
|
|
110
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Converte nível estilo winston para nível pino.
|
|
199
|
+
* @param {unknown} value
|
|
200
|
+
* @returns {string}
|
|
201
|
+
*/
|
|
111
202
|
const mapWinstonLevelToPinoLevel = (value) => {
|
|
112
203
|
const normalized = String(value || '')
|
|
113
204
|
.trim()
|
|
@@ -123,6 +214,12 @@ const mapWinstonLevelToPinoLevel = (value) => {
|
|
|
123
214
|
return DEFAULT_PINO_LEVEL;
|
|
124
215
|
};
|
|
125
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Verifica se um método de log deve ser emitido no nível atual.
|
|
219
|
+
* @param {string} level
|
|
220
|
+
* @param {string} method
|
|
221
|
+
* @returns {boolean}
|
|
222
|
+
*/
|
|
126
223
|
const shouldEmitByPinoLevel = (level, method) => {
|
|
127
224
|
const normalizedLevel = normalizePinoLevel(level, DEFAULT_PINO_LEVEL);
|
|
128
225
|
const threshold = PINO_LEVEL_PRIORITY[normalizedLevel] ?? PINO_LEVEL_PRIORITY[DEFAULT_PINO_LEVEL];
|
|
@@ -130,11 +227,21 @@ const shouldEmitByPinoLevel = (level, method) => {
|
|
|
130
227
|
return methodPriority >= threshold;
|
|
131
228
|
};
|
|
132
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Resolve nome-base de serviço para metadados de logger.
|
|
232
|
+
* @returns {string}
|
|
233
|
+
*/
|
|
133
234
|
const resolveBaseServiceName = () => {
|
|
134
235
|
const raw = String(process.env.name || process.env.ECOSYSTEM_NAME || '').trim();
|
|
135
236
|
return raw || 'sistema';
|
|
136
237
|
};
|
|
137
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Cria child logger quando suportado.
|
|
241
|
+
* @param {any} logger
|
|
242
|
+
* @param {Record<string, any>} defaultMeta
|
|
243
|
+
* @returns {any}
|
|
244
|
+
*/
|
|
138
245
|
const createLoggerChild = (logger, defaultMeta) => {
|
|
139
246
|
if (logger && typeof logger.child === 'function') {
|
|
140
247
|
return logger.child(defaultMeta);
|
|
@@ -142,6 +249,20 @@ const createLoggerChild = (logger, defaultMeta) => {
|
|
|
142
249
|
return logger || baseLogger;
|
|
143
250
|
};
|
|
144
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Resolve configuração do logger raiz do Baileys.
|
|
254
|
+
* @param {Record<string, any>} [overrides={}]
|
|
255
|
+
* @returns {{
|
|
256
|
+
* mode: 'child'|'instance',
|
|
257
|
+
* level?: string,
|
|
258
|
+
* label: string,
|
|
259
|
+
* service: string,
|
|
260
|
+
* defaultMeta: Record<string, any>,
|
|
261
|
+
* transportDefinitions?: Array<{type: string, options: Record<string, any>}>,
|
|
262
|
+
* transports?: any[],
|
|
263
|
+
* format?: any
|
|
264
|
+
* }}
|
|
265
|
+
*/
|
|
145
266
|
const resolveBaileysLoggerConfig = (overrides = {}) => {
|
|
146
267
|
const mode = normalizeLoggerMode(overrides.mode ?? process.env.BAILEYS_LOGGER_MODE, DEFAULT_BAILEYS_LOGGER_MODE);
|
|
147
268
|
const level = String(overrides.level ?? process.env.BAILEYS_LOGGER_LEVEL ?? '').trim() || undefined;
|
|
@@ -173,6 +294,11 @@ const resolveBaileysLoggerConfig = (overrides = {}) => {
|
|
|
173
294
|
};
|
|
174
295
|
};
|
|
175
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Resolve configuração do logger de socket do Baileys.
|
|
299
|
+
* @param {Record<string, any>} [overrides={}]
|
|
300
|
+
* @returns {{mode: 'silent'|'pino'|'bridge', level: string, base: Record<string, any>, options: Record<string, any>}}
|
|
301
|
+
*/
|
|
176
302
|
const resolveBaileysSocketLoggerConfig = (overrides = {}) => {
|
|
177
303
|
const mode = normalizeSocketLoggerMode(overrides.mode ?? process.env.BAILEYS_SOCKET_LOGGER_MODE, DEFAULT_BAILEYS_SOCKET_LOGGER_MODE);
|
|
178
304
|
const fallbackLevel = mode === 'silent' ? DEFAULT_PINO_SILENT_LEVEL : DEFAULT_PINO_LEVEL;
|
|
@@ -199,6 +325,11 @@ const resolveBaileysSocketLoggerConfig = (overrides = {}) => {
|
|
|
199
325
|
};
|
|
200
326
|
};
|
|
201
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Cria logger raiz conforme configuração resolvida.
|
|
330
|
+
* @param {Record<string, any>} [overrides={}]
|
|
331
|
+
* @returns {any}
|
|
332
|
+
*/
|
|
202
333
|
const createConfiguredBaileysLogger = (overrides = {}) => {
|
|
203
334
|
const config = resolveBaileysLoggerConfig(overrides);
|
|
204
335
|
|
|
@@ -219,12 +350,23 @@ const createConfiguredBaileysLogger = (overrides = {}) => {
|
|
|
219
350
|
return childLogger;
|
|
220
351
|
};
|
|
221
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Serializa erro para metadados seguros de log.
|
|
355
|
+
* @param {any} error
|
|
356
|
+
* @returns {{errorName: string, errorMessage: string, errorStack: string|undefined}}
|
|
357
|
+
*/
|
|
222
358
|
const serializeError = (error) => ({
|
|
223
359
|
errorName: error?.name || 'Error',
|
|
224
360
|
errorMessage: error?.message || String(error),
|
|
225
361
|
errorStack: error?.stack,
|
|
226
362
|
});
|
|
227
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Resolve mensagem e metadados de uma chamada de log.
|
|
366
|
+
* @param {any} obj
|
|
367
|
+
* @param {any} msg
|
|
368
|
+
* @returns {{message: string, metadata?: Record<string, any>}}
|
|
369
|
+
*/
|
|
228
370
|
const resolveLogEntry = (obj, msg) => {
|
|
229
371
|
const providedMessage = typeof msg === 'string' ? msg.trim() : '';
|
|
230
372
|
let message = providedMessage;
|
|
@@ -259,6 +401,14 @@ const resolveLogEntry = (obj, msg) => {
|
|
|
259
401
|
};
|
|
260
402
|
};
|
|
261
403
|
|
|
404
|
+
/**
|
|
405
|
+
* Escreve log no logger alvo mapeando níveis do Baileys.
|
|
406
|
+
* @param {any} targetLogger
|
|
407
|
+
* @param {string} method
|
|
408
|
+
* @param {any} obj
|
|
409
|
+
* @param {any} msg
|
|
410
|
+
* @returns {void}
|
|
411
|
+
*/
|
|
262
412
|
const writeBridgeLog = (targetLogger, method, obj, msg) => {
|
|
263
413
|
const methodName = BAILEYS_TO_WINSTON_LEVEL[method] || 'info';
|
|
264
414
|
const { message, metadata } = resolveLogEntry(obj, msg);
|
|
@@ -282,11 +432,21 @@ const writeBridgeLog = (targetLogger, method, obj, msg) => {
|
|
|
282
432
|
}
|
|
283
433
|
};
|
|
284
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Cria adaptador estilo pino sobre logger base (bridge).
|
|
437
|
+
* @param {any} rootLogger
|
|
438
|
+
* @param {string} level
|
|
439
|
+
* @returns {any}
|
|
440
|
+
*/
|
|
285
441
|
const createBaileysSocketBridgeLogger = (rootLogger, level) => {
|
|
286
442
|
const sharedState = {
|
|
287
443
|
level: normalizePinoLevel(level, mapWinstonLevelToPinoLevel(rootLogger?.level)),
|
|
288
444
|
};
|
|
289
445
|
|
|
446
|
+
/**
|
|
447
|
+
* @param {any} targetLogger
|
|
448
|
+
* @returns {any}
|
|
449
|
+
*/
|
|
290
450
|
const createAdapter = (targetLogger) => ({
|
|
291
451
|
get level() {
|
|
292
452
|
return sharedState.level;
|
|
@@ -327,6 +487,10 @@ const createBaileysSocketBridgeLogger = (rootLogger, level) => {
|
|
|
327
487
|
let cachedBaileysRootLogger = null;
|
|
328
488
|
let cachedBaileysSocketLogger = null;
|
|
329
489
|
|
|
490
|
+
/**
|
|
491
|
+
* Retorna logger raiz default com cache por processo.
|
|
492
|
+
* @returns {any}
|
|
493
|
+
*/
|
|
330
494
|
const getDefaultBaileysRootLogger = () => {
|
|
331
495
|
if (!cachedBaileysRootLogger) {
|
|
332
496
|
cachedBaileysRootLogger = createConfiguredBaileysLogger();
|
|
@@ -334,6 +498,11 @@ const getDefaultBaileysRootLogger = () => {
|
|
|
334
498
|
return cachedBaileysRootLogger;
|
|
335
499
|
};
|
|
336
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Cria logger de socket configurado.
|
|
503
|
+
* @param {Record<string, any>} [overrides={}]
|
|
504
|
+
* @returns {any}
|
|
505
|
+
*/
|
|
337
506
|
const createConfiguredBaileysSocketLogger = (overrides = {}) => {
|
|
338
507
|
const config = resolveBaileysSocketLoggerConfig(overrides);
|
|
339
508
|
const mergedBase = {
|
|
@@ -357,6 +526,11 @@ const createConfiguredBaileysSocketLogger = (overrides = {}) => {
|
|
|
357
526
|
return pino(pinoOptions);
|
|
358
527
|
};
|
|
359
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Retorna logger raiz do Baileys.
|
|
531
|
+
* @param {Record<string, any>} [overrides={}]
|
|
532
|
+
* @returns {any}
|
|
533
|
+
*/
|
|
360
534
|
export const createBaileysLogger = (overrides = {}) => {
|
|
361
535
|
if (!overrides || Object.keys(overrides).length === 0) {
|
|
362
536
|
return getDefaultBaileysRootLogger();
|
|
@@ -364,6 +538,12 @@ export const createBaileysLogger = (overrides = {}) => {
|
|
|
364
538
|
return createConfiguredBaileysLogger(overrides);
|
|
365
539
|
};
|
|
366
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Cria logger filho com escopo e metadados adicionais.
|
|
543
|
+
* @param {string} scope
|
|
544
|
+
* @param {Record<string, any>} [metadata={}]
|
|
545
|
+
* @returns {any}
|
|
546
|
+
*/
|
|
367
547
|
export const createBaileysScopedLogger = (scope, metadata = {}) => {
|
|
368
548
|
const rootLogger = getDefaultBaileysRootLogger();
|
|
369
549
|
const rootLabel = resolveBaileysLoggerConfig().label;
|
|
@@ -376,6 +556,11 @@ export const createBaileysScopedLogger = (scope, metadata = {}) => {
|
|
|
376
556
|
return createLoggerChild(rootLogger, scopedMeta);
|
|
377
557
|
};
|
|
378
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Retorna logger de socket do Baileys.
|
|
561
|
+
* @param {Record<string, any>} [overrides={}]
|
|
562
|
+
* @returns {any}
|
|
563
|
+
*/
|
|
379
564
|
export const createBaileysSocketLogger = (overrides = {}) => {
|
|
380
565
|
if (!overrides || Object.keys(overrides).length === 0) {
|
|
381
566
|
if (!cachedBaileysSocketLogger) {
|
|
@@ -1,30 +1,99 @@
|
|
|
1
1
|
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
2
|
import { baileysConnectionLogger as logger } from './loggerConfig.js';
|
|
3
3
|
import { queueMessageInsert } from '../services/infra/dbWriteQueue.js';
|
|
4
|
-
import { parseEnvBool, parseEnvInt, normalizeJid, isGroupJid, isStatusJid, isBroadcastJid, isNewsletterJid, normalizeWAPresence } from './baileysConfig.js';
|
|
4
|
+
import { parseEnvBool, parseEnvInt, normalizeJid, isGroupJid, isStatusJid, isBroadcastJid, isNewsletterJid, normalizeWAPresence, isLidJid, isWhatsAppJid, normalizePnToJid, resolveUserId } from './baileysConfig.js';
|
|
5
5
|
import { getOwner as getGroupOwner, tryAcquire as tryAcquireGroupOwner } from '../services/multiSession/groupOwnershipService.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Número máximo de tentativas de envio.
|
|
9
|
+
* @type {number}
|
|
10
|
+
*/
|
|
7
11
|
const BAILEYS_SEND_RETRY_ATTEMPTS = parseEnvInt(process.env.BAILEYS_SEND_RETRY_ATTEMPTS, 2, 1, 5);
|
|
12
|
+
/**
|
|
13
|
+
* Atraso base (ms) para backoff exponencial entre retries.
|
|
14
|
+
* @type {number}
|
|
15
|
+
*/
|
|
8
16
|
const BAILEYS_SEND_RETRY_BASE_DELAY_MS = parseEnvInt(process.env.BAILEYS_SEND_RETRY_BASE_DELAY_MS, 600, 100, 10_000);
|
|
17
|
+
/**
|
|
18
|
+
* Timeout de upload de mídia repassado ao Baileys.
|
|
19
|
+
* @type {number}
|
|
20
|
+
*/
|
|
9
21
|
const BAILEYS_SEND_MEDIA_UPLOAD_TIMEOUT_MS = parseEnvInt(process.env.BAILEYS_SEND_MEDIA_UPLOAD_TIMEOUT_MS, 0, 0, 120_000);
|
|
22
|
+
/**
|
|
23
|
+
* Habilita presença automática durante replies.
|
|
24
|
+
* @type {boolean}
|
|
25
|
+
*/
|
|
10
26
|
const BAILEYS_REPLY_PRESENCE_ENABLED = parseEnvBool(process.env.BAILEYS_REPLY_PRESENCE_ENABLED, true);
|
|
27
|
+
/**
|
|
28
|
+
* Define se deve assinar presença antes de enviar update.
|
|
29
|
+
* @type {boolean}
|
|
30
|
+
*/
|
|
11
31
|
const BAILEYS_REPLY_PRESENCE_SUBSCRIBE = parseEnvBool(process.env.BAILEYS_REPLY_PRESENCE_SUBSCRIBE, true);
|
|
32
|
+
/**
|
|
33
|
+
* Delay entre presença "before" e envio (ms).
|
|
34
|
+
* @type {number}
|
|
35
|
+
*/
|
|
12
36
|
const BAILEYS_REPLY_PRESENCE_DELAY_MS = parseEnvInt(process.env.BAILEYS_REPLY_PRESENCE_DELAY_MS, 280, 0, 3_000);
|
|
37
|
+
/**
|
|
38
|
+
* Presença enviada antes do envio.
|
|
39
|
+
* @type {import('@whiskeysockets/baileys').WAPresence}
|
|
40
|
+
*/
|
|
13
41
|
const BAILEYS_REPLY_PRESENCE_BEFORE = normalizeWAPresence(process.env.BAILEYS_REPLY_PRESENCE_BEFORE, 'composing');
|
|
42
|
+
/**
|
|
43
|
+
* Presença enviada após o envio.
|
|
44
|
+
* @type {import('@whiskeysockets/baileys').WAPresence}
|
|
45
|
+
*/
|
|
14
46
|
const BAILEYS_REPLY_PRESENCE_AFTER = normalizeWAPresence(process.env.BAILEYS_REPLY_PRESENCE_AFTER, 'paused');
|
|
47
|
+
/**
|
|
48
|
+
* Prefere enviar para PN quando o destino original for LID.
|
|
49
|
+
* @type {boolean}
|
|
50
|
+
*/
|
|
51
|
+
const BAILEYS_SEND_PREFER_PN_FOR_LID = parseEnvBool(process.env.BAILEYS_SEND_PREFER_PN_FOR_LID, true);
|
|
52
|
+
/**
|
|
53
|
+
* TTL do cache de permissão de escrita em grupo (ms).
|
|
54
|
+
* @type {number}
|
|
55
|
+
*/
|
|
15
56
|
const GROUP_WRITE_PERMISSION_CACHE_TTL_MS = parseEnvInt(process.env.GROUP_OWNER_WRITE_CACHE_TTL_MS, 8_000, 1_000, 60_000);
|
|
16
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Verifica se o valor é um objeto plano.
|
|
60
|
+
* @param {unknown} value
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
17
63
|
const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]';
|
|
18
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Chaves primárias conhecidas de `AnyMessageContent`.
|
|
67
|
+
* @type {Set<string>}
|
|
68
|
+
*/
|
|
19
69
|
const ANY_MESSAGE_CONTENT_PRIMARY_KEYS = new Set(['text', 'image', 'video', 'audio', 'sticker', 'stickerPack', 'stickerPackMessage', 'document', 'event', 'poll', 'contacts', 'location', 'react', 'buttonReply', 'groupInvite', 'listReply', 'pin', 'product', 'sharePhoneNumber', 'requestPhoneNumber', 'forward', 'delete', 'disappearingMessagesInChat', 'limitSharing']);
|
|
70
|
+
/**
|
|
71
|
+
* Conteúdos que não disparam presença de resposta.
|
|
72
|
+
* @type {Set<string>}
|
|
73
|
+
*/
|
|
20
74
|
const PRESENCE_NON_REPLY_CONTENT_KEYS = new Set(['react', 'delete', 'pin', 'disappearingMessagesInChat']);
|
|
75
|
+
/**
|
|
76
|
+
* Cache local de permissão de escrita por `sessionId+groupJid`.
|
|
77
|
+
* @type {Map<string, {allowed: boolean, ownerSessionId: string | null, expiresAtMs: number}>}
|
|
78
|
+
*/
|
|
21
79
|
const groupWritePermissionCache = new Map();
|
|
22
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Normaliza um ID de sessão.
|
|
83
|
+
* @param {unknown} value
|
|
84
|
+
* @returns {string|null}
|
|
85
|
+
*/
|
|
23
86
|
const normalizeSessionId = (value) => {
|
|
24
87
|
const normalized = String(value || '').trim();
|
|
25
88
|
return normalized || null;
|
|
26
89
|
};
|
|
27
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Recupera permissão de escrita de grupo no cache.
|
|
93
|
+
* @param {string} groupJid
|
|
94
|
+
* @param {string} sessionId
|
|
95
|
+
* @returns {{allowed: boolean, ownerSessionId: string | null, expiresAtMs: number} | null}
|
|
96
|
+
*/
|
|
28
97
|
const getCachedGroupWritePermission = (groupJid, sessionId) => {
|
|
29
98
|
const key = `${sessionId}:${groupJid}`;
|
|
30
99
|
const cached = groupWritePermissionCache.get(key);
|
|
@@ -36,6 +105,14 @@ const getCachedGroupWritePermission = (groupJid, sessionId) => {
|
|
|
36
105
|
return cached;
|
|
37
106
|
};
|
|
38
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Salva permissão de escrita de grupo no cache.
|
|
110
|
+
* @param {string} groupJid
|
|
111
|
+
* @param {string} sessionId
|
|
112
|
+
* @param {boolean} allowed
|
|
113
|
+
* @param {string|null} [ownerSessionId=null]
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
39
116
|
const setCachedGroupWritePermission = (groupJid, sessionId, allowed, ownerSessionId = null) => {
|
|
40
117
|
const key = `${sessionId}:${groupJid}`;
|
|
41
118
|
groupWritePermissionCache.set(key, {
|
|
@@ -45,6 +122,12 @@ const setCachedGroupWritePermission = (groupJid, sessionId, allowed, ownerSessio
|
|
|
45
122
|
});
|
|
46
123
|
};
|
|
47
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Resolve se a sessão atual pode escrever em um grupo.
|
|
127
|
+
* @param {string} groupJid
|
|
128
|
+
* @param {string|null} sessionId
|
|
129
|
+
* @returns {Promise<{allowed: boolean, ownerSessionId: string | null, reason: string}>}
|
|
130
|
+
*/
|
|
48
131
|
const resolveGroupWritePermission = async (groupJid, sessionId) => {
|
|
49
132
|
if (!isGroupJid(groupJid) || !sessionId) {
|
|
50
133
|
return {
|
|
@@ -225,6 +308,60 @@ const shouldSendReplyPresence = (jid, content, runtimeOptions) => {
|
|
|
225
308
|
return true;
|
|
226
309
|
};
|
|
227
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Verifica se o JID é de usuário direto (não grupo/broadcast/status/newsletter).
|
|
313
|
+
* @param {string} jid
|
|
314
|
+
* @returns {boolean}
|
|
315
|
+
*/
|
|
316
|
+
const isDirectUserJid = (jid) => {
|
|
317
|
+
if (!jid) return false;
|
|
318
|
+
if (isGroupJid(jid)) return false;
|
|
319
|
+
if (isStatusJid(jid)) return false;
|
|
320
|
+
if (isBroadcastJid(jid)) return false;
|
|
321
|
+
if (isNewsletterJid(jid)) return false;
|
|
322
|
+
return true;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Resolve JID preferencial de envio, convertendo LID para PN quando possível.
|
|
327
|
+
* @param {string} normalizedJid
|
|
328
|
+
* @returns {Promise<string>}
|
|
329
|
+
*/
|
|
330
|
+
const resolvePreferredSendJid = async (normalizedJid) => {
|
|
331
|
+
if (!normalizedJid) return normalizedJid;
|
|
332
|
+
if (!BAILEYS_SEND_PREFER_PN_FOR_LID) return normalizedJid;
|
|
333
|
+
if (!isDirectUserJid(normalizedJid)) return normalizedJid;
|
|
334
|
+
if (!isLidJid(normalizedJid)) return normalizedJid;
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const resolvedIdentity = await resolveUserId({
|
|
338
|
+
lid: normalizedJid,
|
|
339
|
+
jid: normalizedJid,
|
|
340
|
+
});
|
|
341
|
+
const normalizedResolved = normalizeJid(String(resolvedIdentity || '').trim());
|
|
342
|
+
const candidatePnJid = normalizePnToJid(normalizedResolved || String(resolvedIdentity || '').trim());
|
|
343
|
+
if (candidatePnJid && isWhatsAppJid(candidatePnJid)) {
|
|
344
|
+
return candidatePnJid;
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
logger.debug('Falha ao resolver PN para envio com destino LID. Mantendo destino original.', {
|
|
348
|
+
action: 'resolve_preferred_send_jid_failed',
|
|
349
|
+
jid: normalizedJid,
|
|
350
|
+
error: error?.message,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return normalizedJid;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Envia presença no Baileys sem interromper o fluxo principal em caso de erro.
|
|
359
|
+
* @param {import('@whiskeysockets/baileys').WASocket} sock
|
|
360
|
+
* @param {import('@whiskeysockets/baileys').WAPresence} type
|
|
361
|
+
* @param {string} jid
|
|
362
|
+
* @param {boolean} [subscribeFirst=false]
|
|
363
|
+
* @returns {Promise<void>}
|
|
364
|
+
*/
|
|
228
365
|
const sendPresenceSilently = async (sock, type, jid, subscribeFirst = false) => {
|
|
229
366
|
if (!sock || typeof sock.sendPresenceUpdate !== 'function') return;
|
|
230
367
|
try {
|
|
@@ -274,8 +411,18 @@ export const buildMessageData = (msg, senderId, sessionId = null) => ({
|
|
|
274
411
|
timestamp: new Date(resolveMessageTimestampMs(msg)),
|
|
275
412
|
});
|
|
276
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Atrasa execução por `ms`.
|
|
416
|
+
* @param {number} ms
|
|
417
|
+
* @returns {Promise<void>}
|
|
418
|
+
*/
|
|
277
419
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
278
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Detecta se um erro de envio é potencialmente transitório.
|
|
423
|
+
* @param {any} error
|
|
424
|
+
* @returns {boolean}
|
|
425
|
+
*/
|
|
279
426
|
const isTransientSendError = (error) => {
|
|
280
427
|
const statusCode = Number(error?.output?.statusCode || error?.statusCode || 0);
|
|
281
428
|
if ([408, 409, 425, 429, 500, 502, 503, 504].includes(statusCode)) return true;
|
|
@@ -292,6 +439,11 @@ const isTransientSendError = (error) => {
|
|
|
292
439
|
return transientFragments.some((fragment) => rawMessage.includes(fragment));
|
|
293
440
|
};
|
|
294
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Indica se o erro sugere refresh explícito de media connection.
|
|
444
|
+
* @param {any} error
|
|
445
|
+
* @returns {boolean}
|
|
446
|
+
*/
|
|
295
447
|
const shouldRefreshMediaConnection = (error) => {
|
|
296
448
|
const rawMessage = `${error?.message || ''} ${error?.data?.message || ''}`.toLowerCase();
|
|
297
449
|
return rawMessage.includes('media') || rawMessage.includes('directpath') || rawMessage.includes('upload');
|
|
@@ -325,16 +477,16 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
325
477
|
throw new TypeError(`Payload de mensagem inválido. Chaves recebidas: ${payloadKeys.join(', ') || 'nenhuma'}`);
|
|
326
478
|
}
|
|
327
479
|
|
|
328
|
-
const
|
|
480
|
+
const normalizedInputJid = normalizeJid(jid) || String(jid).trim();
|
|
329
481
|
const runtimeOptions = resolveRuntimeSendOptions(options);
|
|
330
482
|
const runtimeSessionId = runtimeOptions.sessionId || normalizeSessionId(sock?.__omnizapSessionId);
|
|
331
483
|
let resolvedGroupWritePermission = null;
|
|
332
484
|
|
|
333
|
-
if (isGroupJid(
|
|
485
|
+
if (isGroupJid(normalizedInputJid)) {
|
|
334
486
|
if (runtimeOptions.allowGroupWrite === false) {
|
|
335
487
|
logger.debug('Envio para grupo ignorado por bloqueio explícito de escrita.', {
|
|
336
488
|
action: 'send_group_blocked_explicit',
|
|
337
|
-
groupJid:
|
|
489
|
+
groupJid: normalizedInputJid,
|
|
338
490
|
sessionId: runtimeSessionId,
|
|
339
491
|
});
|
|
340
492
|
return undefined;
|
|
@@ -347,11 +499,11 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
347
499
|
reason: 'explicit_allow',
|
|
348
500
|
};
|
|
349
501
|
} else {
|
|
350
|
-
resolvedGroupWritePermission = await resolveGroupWritePermission(
|
|
502
|
+
resolvedGroupWritePermission = await resolveGroupWritePermission(normalizedInputJid, runtimeSessionId);
|
|
351
503
|
if (!resolvedGroupWritePermission.allowed) {
|
|
352
504
|
logger.info('Envio para grupo bloqueado por sessão não-owner.', {
|
|
353
505
|
action: 'send_group_blocked_non_owner',
|
|
354
|
-
groupJid:
|
|
506
|
+
groupJid: normalizedInputJid,
|
|
355
507
|
sessionId: runtimeSessionId,
|
|
356
508
|
ownerSessionId: resolvedGroupWritePermission.ownerSessionId,
|
|
357
509
|
reason: resolvedGroupWritePermission.reason,
|
|
@@ -361,6 +513,16 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
361
513
|
}
|
|
362
514
|
}
|
|
363
515
|
|
|
516
|
+
const normalizedJid = await resolvePreferredSendJid(normalizedInputJid);
|
|
517
|
+
if (normalizedJid !== normalizedInputJid) {
|
|
518
|
+
logger.debug('Destino LID convertido para PN antes do envio.', {
|
|
519
|
+
action: 'send_target_lid_to_pn',
|
|
520
|
+
from: normalizedInputJid,
|
|
521
|
+
to: normalizedJid,
|
|
522
|
+
sessionId: runtimeSessionId,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
364
526
|
const normalizedOptions = normalizeSendOptions(runtimeOptions.sendOptions);
|
|
365
527
|
const shouldSendPresence = shouldSendReplyPresence(normalizedJid, content, runtimeOptions);
|
|
366
528
|
|
|
@@ -427,7 +589,7 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
427
589
|
if (sent?.key?.id) {
|
|
428
590
|
try {
|
|
429
591
|
const messageData = buildMessageData(sent, senderId, runtimeSessionId);
|
|
430
|
-
const targetGroupJid = normalizeJid(messageData.chat_id ||
|
|
592
|
+
const targetGroupJid = normalizeJid(messageData.chat_id || normalizedInputJid);
|
|
431
593
|
if (isGroupJid(targetGroupJid)) {
|
|
432
594
|
const allowGroupWrite = runtimeOptions.allowGroupWrite === true || resolvedGroupWritePermission?.allowed === true;
|
|
433
595
|
messageData.allow_group_write = allowGroupWrite;
|