@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.
Files changed (48) hide show
  1. package/.env.example +24 -0
  2. package/app/config/index.js +4 -0
  3. package/app/configParts/adminIdentity.js +29 -0
  4. package/app/configParts/baileysConfig.js +116 -0
  5. package/app/configParts/groupUtils.js +221 -0
  6. package/app/configParts/loggerConfig.js +185 -0
  7. package/app/configParts/messagePersistenceService.js +169 -7
  8. package/app/configParts/sessionConfig.js +85 -0
  9. package/app/connection/baileysCompatibility.test.js +9 -0
  10. package/app/connection/baileysDbAuthState.js +205 -9
  11. package/app/connection/baileysLibsignalPatch.js +210 -0
  12. package/app/connection/groupOwnerWriteStateResolver.js +53 -21
  13. package/app/connection/socketController.js +95 -25
  14. package/app/connection/socketController.multiSession.test.js +20 -0
  15. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +17 -3
  16. package/app/controllers/messageProcessingPipeline.js +2 -0
  17. package/app/controllers/messageProcessingPipeline.test.js +15 -13
  18. package/app/services/multiSession/assignmentBalancerService.js +1 -6
  19. package/app/services/multiSession/groupOwnershipRepository.js +9 -44
  20. package/app/services/multiSession/groupOwnershipService.js +9 -90
  21. package/app/services/multiSession/groupOwnershipService.test.js +12 -4
  22. package/app/services/multiSession/sessionRegistryService.js +6 -60
  23. package/app/utils/antiLink/antiLinkModule.js +54 -24
  24. package/docs/security/omnizap-static-security-headers.conf +3 -3
  25. package/package.json +3 -2
  26. package/public/comandos/commands-catalog.json +1 -1
  27. package/public/css/payments-react.css +478 -0
  28. package/public/js/apps/homeReactApp.js +2 -2
  29. package/public/js/apps/paymentsCancelReactApp.js +45 -0
  30. package/public/js/apps/paymentsReactApp.js +399 -0
  31. package/public/js/apps/paymentsSuccessReactApp.js +148 -0
  32. package/public/pages/pagamentos-cancelado.html +21 -0
  33. package/public/pages/pagamentos-sucesso.html +21 -0
  34. package/public/pages/pagamentos.html +30 -0
  35. package/scripts/deploy.sh +3 -0
  36. package/scripts/new-whatsapp-session.sh +247 -0
  37. package/server/controllers/admin/systemAdminController.js +4 -17
  38. package/server/controllers/payments/paymentsController.js +731 -0
  39. package/server/controllers/system/systemController.js +4 -30
  40. package/server/email/emailAutomationRuntime.js +36 -1
  41. package/server/email/emailAutomationService.js +42 -1
  42. package/server/email/emailTemplateService.js +137 -31
  43. package/server/http/httpRequestUtils.js +18 -14
  44. package/server/middleware/securityHeaders.js +15 -2
  45. package/server/routes/indexRouter.js +27 -7
  46. package/server/routes/payments/paymentsRouter.js +47 -0
  47. package/server/routes/static/staticPageRouter.js +3 -0
  48. 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 normalizedJid = normalizeJid(jid) || String(jid).trim();
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(normalizedJid)) {
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: normalizedJid,
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(normalizedJid, runtimeSessionId);
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: normalizedJid,
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 || normalizedJid);
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;