@omnizap-system/omnizap 2.6.1 → 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 (172) hide show
  1. package/.env.example +78 -9
  2. package/.github/workflows/ci.yml +3 -3
  3. package/.github/workflows/security-runner-hardening.yml +1 -1
  4. package/.github/workflows/security-zap-full-scan.yml +1 -0
  5. package/app/config/index.js +6 -0
  6. package/app/configParts/adminIdentity.js +36 -7
  7. package/app/configParts/baileysConfig.js +343 -56
  8. package/app/configParts/groupUtils.js +226 -0
  9. package/app/configParts/loggerConfig.js +185 -0
  10. package/app/configParts/messagePersistenceService.js +307 -5
  11. package/app/configParts/sessionConfig.js +242 -0
  12. package/app/connection/baileysCompatibility.test.js +10 -1
  13. package/app/connection/baileysDbAuthState.js +205 -9
  14. package/app/connection/baileysLibsignalPatch.js +210 -0
  15. package/app/connection/groupOwnerWriteStateResolver.js +141 -0
  16. package/app/connection/socketController.js +694 -123
  17. package/app/connection/socketController.multiSession.test.js +128 -0
  18. package/app/controllers/messageController.js +1 -1
  19. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  20. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  21. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  22. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +96 -4
  23. package/app/controllers/messageProcessingPipeline.js +90 -9
  24. package/app/controllers/messageProcessingPipeline.test.js +202 -0
  25. package/app/modules/adminModule/AGENT.md +1 -1
  26. package/app/modules/adminModule/commandConfig.json +3318 -1347
  27. package/app/modules/adminModule/groupCommandHandlers.js +856 -14
  28. package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
  29. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  30. package/app/modules/aiModule/AGENT.md +47 -30
  31. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  32. package/app/modules/aiModule/catCommand.js +132 -25
  33. package/app/modules/aiModule/commandConfig.json +114 -28
  34. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  35. package/app/modules/gameModule/AGENT.md +1 -1
  36. package/app/modules/gameModule/commandConfig.json +29 -0
  37. package/app/modules/menuModule/AGENT.md +1 -1
  38. package/app/modules/menuModule/commandConfig.json +45 -10
  39. package/app/modules/menuModule/menuCatalogService.js +190 -0
  40. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  41. package/app/modules/menuModule/menuDynamicService.js +511 -0
  42. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  43. package/app/modules/menuModule/menus.js +36 -5
  44. package/app/modules/playModule/AGENT.md +10 -5
  45. package/app/modules/playModule/commandConfig.json +74 -16
  46. package/app/modules/playModule/playCommandConstants.js +13 -7
  47. package/app/modules/playModule/playCommandCore.js +4 -6
  48. package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
  49. package/app/modules/playModule/playConfigRuntime.js +5 -6
  50. package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
  51. package/app/modules/quoteModule/AGENT.md +1 -1
  52. package/app/modules/quoteModule/commandConfig.json +29 -0
  53. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  54. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  55. package/app/modules/statsModule/AGENT.md +1 -1
  56. package/app/modules/statsModule/commandConfig.json +58 -0
  57. package/app/modules/stickerModule/AGENT.md +1 -1
  58. package/app/modules/stickerModule/commandConfig.json +145 -0
  59. package/app/modules/stickerPackModule/AGENT.md +1 -1
  60. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  61. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  62. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
  63. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
  64. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  65. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  66. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  67. package/app/modules/tiktokModule/AGENT.md +1 -1
  68. package/app/modules/tiktokModule/commandConfig.json +29 -0
  69. package/app/modules/userModule/AGENT.md +1 -1
  70. package/app/modules/userModule/commandConfig.json +29 -0
  71. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  72. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  73. package/app/observability/metrics.js +136 -0
  74. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  75. package/app/services/ai/geminiService.js +131 -7
  76. package/app/services/ai/geminiService.test.js +59 -2
  77. package/app/services/ai/moduleAiHelpCoreService.js +33 -4
  78. package/app/services/group/groupMetadataService.js +24 -1
  79. package/app/services/infra/dbWriteQueue.js +51 -21
  80. package/app/services/messaging/newsBroadcastService.js +843 -27
  81. package/app/services/multiSession/assignmentBalancerService.js +452 -0
  82. package/app/services/multiSession/groupOwnershipRepository.js +346 -0
  83. package/app/services/multiSession/groupOwnershipService.js +809 -0
  84. package/app/services/multiSession/groupOwnershipService.test.js +317 -0
  85. package/app/services/multiSession/sessionRegistryService.js +239 -0
  86. package/app/store/aiPromptStore.js +36 -19
  87. package/app/store/groupConfigStore.js +41 -5
  88. package/app/store/premiumUserStore.js +21 -7
  89. package/app/utils/antiLink/antiLinkModule.js +391 -25
  90. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  91. package/database/index.js +6 -0
  92. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  93. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  94. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  95. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  96. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  97. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  98. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  99. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  100. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  101. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  102. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  103. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  104. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  105. package/database/schema.sql +102 -1
  106. package/docker-compose.yml +4 -1
  107. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  108. package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
  109. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  110. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  111. package/docs/security/omnizap-static-security-headers.conf +25 -0
  112. package/ecosystem.prod.config.cjs +31 -11
  113. package/index.js +52 -18
  114. package/observability/alert-rules.yml +20 -0
  115. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  116. package/observability/mysql-setup.sql +4 -4
  117. package/observability/system-admin-observability.md +26 -0
  118. package/package.json +14 -6
  119. package/public/comandos/commands-catalog.json +2253 -78
  120. package/public/css/payments-react.css +478 -0
  121. package/public/js/apps/commandsReactApp.js +267 -87
  122. package/public/js/apps/createPackApp.js +3 -3
  123. package/public/js/apps/homeReactApp.js +2 -2
  124. package/public/js/apps/paymentsCancelReactApp.js +45 -0
  125. package/public/js/apps/paymentsReactApp.js +399 -0
  126. package/public/js/apps/paymentsSuccessReactApp.js +148 -0
  127. package/public/js/apps/stickersApp.js +255 -103
  128. package/public/js/apps/termsReactApp.js +57 -8
  129. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  130. package/public/js/apps/userReactApp.js +96 -47
  131. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  132. package/public/pages/pagamentos-cancelado.html +21 -0
  133. package/public/pages/pagamentos-sucesso.html +21 -0
  134. package/public/pages/pagamentos.html +30 -0
  135. package/public/pages/politica-de-privacidade.html +1 -1
  136. package/public/pages/stickers.html +5 -5
  137. package/public/pages/termos-de-uso-texto-integral.html +1 -1
  138. package/public/pages/termos-de-uso.html +1 -1
  139. package/public/pages/user-password-reset.html +3 -4
  140. package/public/pages/user-systemadm.html +8 -462
  141. package/public/pages/user.html +1 -1
  142. package/scripts/clear-whatsapp-session.sh +123 -0
  143. package/scripts/core-ai-mode.mjs +163 -0
  144. package/scripts/deploy.sh +13 -0
  145. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  146. package/scripts/generate-commands-catalog.mjs +155 -0
  147. package/scripts/new-whatsapp-session.sh +564 -0
  148. package/scripts/security-web-surface-check.mjs +218 -0
  149. package/server/controllers/admin/adminPanelHandlers.js +253 -3
  150. package/server/controllers/admin/systemAdminController.js +254 -0
  151. package/server/controllers/payments/paymentsController.js +731 -0
  152. package/server/controllers/sticker/stickerCatalogController.js +9 -23
  153. package/server/controllers/system/contactController.js +9 -17
  154. package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
  155. package/server/controllers/system/systemController.js +228 -1
  156. package/server/controllers/userController.js +6 -0
  157. package/server/email/emailAutomationRuntime.js +36 -1
  158. package/server/email/emailAutomationService.js +42 -1
  159. package/server/email/emailTemplateService.js +140 -33
  160. package/server/http/httpRequestUtils.js +18 -14
  161. package/server/http/httpServer.js +8 -4
  162. package/server/middleware/securityHeaders.js +35 -3
  163. package/server/routes/admin/systemAdminRouter.js +6 -0
  164. package/server/routes/indexRouter.js +50 -6
  165. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  166. package/server/routes/payments/paymentsRouter.js +47 -0
  167. package/server/routes/static/staticPageRouter.js +30 -1
  168. package/server/utils/publicContact.js +31 -0
  169. package/utils/whatsapp/contactEnv.js +39 -0
  170. package/vite.config.mjs +5 -1
  171. package/app/modules/playModule/local/installYtDlp.js +0 -25
  172. package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
@@ -13,20 +13,177 @@ import path from 'node:path';
13
13
  import { pipeline } from 'node:stream/promises';
14
14
  import { Readable } from 'node:stream';
15
15
  import { fileURLToPath } from 'node:url';
16
-
16
+ import { getMultiSessionRuntimeConfig } from './sessionConfig.js';
17
+
18
+ /**
19
+ * Utilitários centrais de integração com Baileys.
20
+ *
21
+ * Responsabilidades deste módulo:
22
+ * - validar/normalizar JIDs e tipos de presença;
23
+ * - encapsular chamadas seguras no socket ativo;
24
+ * - extrair/detectar mídia em mensagens;
25
+ * - resolver mapeamento LID <-> JID com cache e persistência.
26
+ */
27
+
28
+ /**
29
+ * @typedef {import('@whiskeysockets/baileys').WASocket} BaileysSocket
30
+ * @typedef {import('@whiskeysockets/baileys').WAMessage} BaileysMessage
31
+ * @typedef {import('@whiskeysockets/baileys').WAProto.IMessage} BaileysProtoMessage
32
+ * @typedef {'lid'|'pn'} AddressingMode
33
+ * @typedef {{includeAllTypes?: boolean, includeQuoted?: boolean, includeUnknown?: boolean}} MediaExtractionOptions
34
+ * @typedef {{
35
+ * mediaType: string,
36
+ * mediaKey: Record<string, any>,
37
+ * messageKey: string,
38
+ * isQuoted: boolean,
39
+ * isBinary: boolean,
40
+ * isUnknownType?: boolean,
41
+ * hasUrl: boolean,
42
+ * hasDirectPath: boolean,
43
+ * hasMediaKey: boolean,
44
+ * hasFileEncSha256: boolean,
45
+ * mimetype: string|null,
46
+ * fileLength: number|string|null,
47
+ * fileName: string|null,
48
+ * caption: string|null
49
+ * }} MediaEntry
50
+ * @typedef {{
51
+ * mediaType: string,
52
+ * mediaKey: Record<string, any>,
53
+ * isQuoted: boolean,
54
+ * details: {
55
+ * messageKey: string,
56
+ * isBinary: boolean,
57
+ * isUnknownType?: boolean,
58
+ * hasUrl: boolean,
59
+ * hasDirectPath: boolean,
60
+ * hasMediaKey: boolean,
61
+ * hasFileEncSha256: boolean,
62
+ * mimetype: string|null,
63
+ * fileLength: number|string|null,
64
+ * fileName: string|null,
65
+ * caption: string|null,
66
+ * allMediaFound: MediaEntry[]|null
67
+ * }
68
+ * }} MediaExtractionResult
69
+ * @typedef {{lid?: string|null, jid?: string|null, participantAlt?: string|null}} IdentityParams
70
+ * @typedef {{jid: string|null, expiresAt: number, lastStoredAt: number|null}} LidCacheEntry
71
+ * @typedef {{
72
+ * lid: string|null,
73
+ * jid: string|null,
74
+ * participantAlt: string|null,
75
+ * remoteJid: string|null,
76
+ * remoteJidAlt: string|null,
77
+ * groupMessage: boolean
78
+ * }} SenderInfo
79
+ */
80
+
81
+ /**
82
+ * Versão local de fallback do Baileys.
83
+ * @type {number[]}
84
+ */
17
85
  const DEFAULT_BAILEYS_VERSION = [7, 0, 0];
86
+ const multiSessionRuntimeConfig = getMultiSessionRuntimeConfig();
87
+ const PRIMARY_BAILEYS_SESSION_ID = String(multiSessionRuntimeConfig?.primarySessionId || process.env.BAILEYS_AUTH_SESSION_ID || 'default').trim() || 'default';
88
+
89
+ /**
90
+ * Normaliza ID de sessão com fallback para sessão primária.
91
+ * @param {string|null|undefined} sessionId
92
+ * @returns {string}
93
+ */
94
+ const normalizeSessionId = (sessionId) => {
95
+ const normalized = String(sessionId || '').trim();
96
+ return normalized || PRIMARY_BAILEYS_SESSION_ID;
97
+ };
18
98
 
99
+ /**
100
+ * Mapa de sockets ativos por sessão.
101
+ * @type {Map<string, BaileysSocket>}
102
+ */
103
+ const sessionSocketMap = new Map();
19
104
  let activeSocket = null;
20
105
 
21
- export const setActiveSocket = (socket) => {
22
- activeSocket = socket;
106
+ /**
107
+ * Atualiza a referência global do socket ativo e o registro por sessão.
108
+ * Mantém compatibilidade com chamadas legadas que não informam `sessionId`.
109
+ * @param {BaileysSocket|null} socket Instância conectada ou `null` para limpar.
110
+ * @param {string} [sessionId=PRIMARY_BAILEYS_SESSION_ID] Sessão da conexão.
111
+ * @returns {void}
112
+ */
113
+ export const setActiveSocket = (socket, sessionId = PRIMARY_BAILEYS_SESSION_ID) => {
114
+ const safeSessionId = normalizeSessionId(sessionId);
115
+ const previousSocket = sessionSocketMap.get(safeSessionId) || null;
116
+
117
+ if (socket) {
118
+ sessionSocketMap.set(safeSessionId, socket);
119
+ } else {
120
+ sessionSocketMap.delete(safeSessionId);
121
+ }
122
+
123
+ if (safeSessionId === PRIMARY_BAILEYS_SESSION_ID) {
124
+ activeSocket = socket || null;
125
+ } else if (!socket && previousSocket && activeSocket === previousSocket) {
126
+ activeSocket = null;
127
+ }
128
+
129
+ if (!activeSocket) {
130
+ activeSocket = sessionSocketMap.get(PRIMARY_BAILEYS_SESSION_ID) || sessionSocketMap.values().next()?.value || null;
131
+ }
132
+ };
133
+
134
+ /**
135
+ * Atualiza o socket ativo de uma sessão específica.
136
+ * @param {string} sessionId Sessão alvo.
137
+ * @param {BaileysSocket|null} socket Instância conectada ou `null`.
138
+ * @returns {void}
139
+ */
140
+ export const setSocketBySession = (sessionId, socket) => {
141
+ setActiveSocket(socket, sessionId);
142
+ };
143
+
144
+ /**
145
+ * Retorna a instância de socket ativa no processo.
146
+ * @returns {BaileysSocket|null}
147
+ */
148
+ export const getActiveSocket = () => {
149
+ if (isSocketOpen(activeSocket)) return activeSocket;
150
+
151
+ const primarySocket = sessionSocketMap.get(PRIMARY_BAILEYS_SESSION_ID) || null;
152
+ if (isSocketOpen(primarySocket)) {
153
+ activeSocket = primarySocket;
154
+ return activeSocket;
155
+ }
156
+
157
+ for (const socket of sessionSocketMap.values()) {
158
+ if (isSocketOpen(socket)) {
159
+ activeSocket = socket;
160
+ return activeSocket;
161
+ }
162
+ }
163
+
164
+ activeSocket = null;
165
+ return activeSocket;
166
+ };
167
+
168
+ /**
169
+ * Retorna o socket ativo associado a uma sessão.
170
+ * @param {string} [sessionId=PRIMARY_BAILEYS_SESSION_ID]
171
+ * @returns {BaileysSocket|null}
172
+ */
173
+ export const getSocketBySession = (sessionId = PRIMARY_BAILEYS_SESSION_ID) => {
174
+ const safeSessionId = normalizeSessionId(sessionId);
175
+ return sessionSocketMap.get(safeSessionId) || null;
23
176
  };
24
177
 
25
- export const getActiveSocket = () => activeSocket;
178
+ /**
179
+ * Snapshot dos sockets ativos por sessão.
180
+ * @returns {Map<string, BaileysSocket>}
181
+ */
182
+ export const getActiveSocketsBySession = () => new Map(sessionSocketMap);
26
183
 
27
184
  /**
28
185
  * Indica se uma instância de socket está aberta para operações.
29
- * @param {object|null|undefined} socket Instância de socket.
186
+ * @param {BaileysSocket|null|undefined} socket Instância de socket.
30
187
  * @returns {boolean}
31
188
  */
32
189
  export const isSocketOpen = (socket) => {
@@ -43,7 +200,7 @@ export const isActiveSocketOpen = () => isSocketOpen(activeSocket);
43
200
 
44
201
  /**
45
202
  * Executa um método em uma instância de socket validando disponibilidade.
46
- * @param {object|null|undefined} socket Instância de socket.
203
+ * @param {BaileysSocket|null|undefined} socket Instância de socket.
47
204
  * @param {string} methodName Nome do método no socket.
48
205
  * @param {...any} args Argumentos do método.
49
206
  * @returns {Promise<any>}
@@ -69,9 +226,18 @@ export const runSocketMethod = async (socket, methodName, ...args) => {
69
226
  */
70
227
  export const runActiveSocketMethod = async (methodName, ...args) => runSocketMethod(activeSocket, methodName, ...args);
71
228
 
229
+ /**
230
+ * Executa um método no socket de uma sessão específica.
231
+ * @param {string} sessionId Sessão alvo.
232
+ * @param {string} methodName Nome do método no socket.
233
+ * @param {...any} args Argumentos do método.
234
+ * @returns {Promise<any>}
235
+ */
236
+ export const runSessionSocketMethod = async (sessionId, methodName, ...args) => runSocketMethod(getSocketBySession(sessionId), methodName, ...args);
237
+
72
238
  /**
73
239
  * Recupera a blocklist da conta conectada.
74
- * @returns {Promise<(string|undefined)[]>}
240
+ * @returns {Promise<string[]>}
75
241
  */
76
242
  export const fetchBlocklistFromActiveSocket = async () => runActiveSocketMethod('fetchBlocklist');
77
243
 
@@ -112,6 +278,10 @@ export const WHATSAPP_USER_JID_SERVERS = new Set(['s.whatsapp.net', 'c.us', 'hos
112
278
  */
113
279
  export const LID_USER_JID_SERVERS = new Set(['lid', 'hosted.lid']);
114
280
 
281
+ /**
282
+ * Decode de JID com cache simples do último valor.
283
+ * @type {(jid: string) => ({user?: string, server?: string, domainType?: number, device?: number}|null)}
284
+ */
115
285
  const decodeJidParts = (() => {
116
286
  let lastJid = null;
117
287
  let lastDecoded = null;
@@ -128,7 +298,8 @@ const decodeJidParts = (() => {
128
298
 
129
299
  /**
130
300
  * Tipos de mensagem conhecidos do Baileys
131
- * Mapeamento de chaves do proto.Message para tipos normalizados
301
+ * (mapeamento de chaves do proto.Message para tipos normalizados).
302
+ * @type {Record<string, string>}
132
303
  */
133
304
  export const MEDIA_TYPE_MAPPING = {
134
305
  conversation: 'text',
@@ -182,14 +353,25 @@ export const MEDIA_TYPE_MAPPING = {
182
353
  };
183
354
 
184
355
  /**
185
- * Tipos de midia que contem conteudo binario/arquivo
356
+ * Tipos de mídia que carregam conteúdo binário/arquivo.
357
+ * @type {Set<string>}
186
358
  */
187
359
  export const BINARY_MEDIA_TYPES = new Set(['image', 'video', 'videoNote', 'audio', 'voice', 'document', 'sticker']);
188
360
 
361
+ /**
362
+ * Aplica normalização nativa do Baileys no payload de mensagem.
363
+ * @param {Record<string, any>} message
364
+ * @returns {Record<string, any>}
365
+ */
189
366
  const normalizeMessage = (message) => normalizeMessageContent(message) || message;
190
367
 
191
368
  const MESSAGE_CONTENT_WRAPPER_KEYS = ['ephemeralMessage', 'viewOnceMessage', 'viewOnceMessageV2', 'viewOnceMessageV2Extension', 'deviceSentMessage', 'documentWithCaptionMessage', 'botInvokeMessage', 'editedMessage', 'keepInChatMessage'];
192
369
 
370
+ /**
371
+ * Resolve wrapper de mensagem de um nó com chave única.
372
+ * @param {Record<string, any>} node
373
+ * @returns {Record<string, any>|null}
374
+ */
193
375
  const resolveSingleWrapperMessage = (node) => {
194
376
  if (!node || typeof node !== 'object') return null;
195
377
 
@@ -204,6 +386,12 @@ const resolveSingleWrapperMessage = (node) => {
204
386
  return null;
205
387
  };
206
388
 
389
+ /**
390
+ * Remove camadas de wrappers (`ephemeral`, `viewOnce`, etc.) até o payload real.
391
+ * @param {Record<string, any>} message
392
+ * @param {number} [maxDepth=8]
393
+ * @returns {Record<string, any>}
394
+ */
207
395
  const unwrapMessageContent = (message, maxDepth = 8) => {
208
396
  let current = normalizeMessage(message);
209
397
  const visited = new Set();
@@ -237,6 +425,11 @@ const unwrapMessageContent = (message, maxDepth = 8) => {
237
425
  return current || message;
238
426
  };
239
427
 
428
+ /**
429
+ * Verifica se uma mediaKey está presente e não vazia.
430
+ * @param {unknown} mediaKey
431
+ * @returns {boolean}
432
+ */
240
433
  const hasNonEmptyMediaKey = (mediaKey) => {
241
434
  if (!mediaKey) return false;
242
435
 
@@ -262,6 +455,15 @@ const hasNonEmptyMediaKey = (mediaKey) => {
262
455
  return Boolean(mediaKey);
263
456
  };
264
457
 
458
+ /**
459
+ * Constrói entrada padronizada de mídia detectada.
460
+ * @param {string} mediaType
461
+ * @param {string} messageKey
462
+ * @param {Record<string, any>} value
463
+ * @param {boolean} isQuoted
464
+ * @param {Partial<MediaEntry>} [overrides={}]
465
+ * @returns {MediaEntry}
466
+ */
265
467
  const buildMediaEntry = (mediaType, messageKey, value, isQuoted, overrides = {}) => ({
266
468
  mediaType,
267
469
  mediaKey: value,
@@ -279,6 +481,12 @@ const buildMediaEntry = (mediaType, messageKey, value, isQuoted, overrides = {})
279
481
  ...overrides,
280
482
  });
281
483
 
484
+ /**
485
+ * Coleta mídias do corpo principal e, opcionalmente, de mensagem citada.
486
+ * @param {BaileysMessage|{message?: Record<string, any>}|Record<string, any>} message
487
+ * @param {{includeQuoted?: boolean}} [options]
488
+ * @returns {MediaEntry[]}
489
+ */
282
490
  const collectMediaFromMessage = (message, { includeQuoted = true } = {}) => {
283
491
  if (!message || !message.message) {
284
492
  return [];
@@ -297,6 +505,12 @@ const collectMediaFromMessage = (message, { includeQuoted = true } = {}) => {
297
505
  return allMedia;
298
506
  };
299
507
 
508
+ /**
509
+ * Filtra lista de mídia por tipo binário e inclusão de tipos desconhecidos.
510
+ * @param {MediaEntry[]} media
511
+ * @param {{includeAllTypes?: boolean, includeUnknown?: boolean}} [options]
512
+ * @returns {MediaEntry[]}
513
+ */
300
514
  const filterMedia = (media, { includeAllTypes = false, includeUnknown = false } = {}) => {
301
515
  let filtered = media;
302
516
 
@@ -311,6 +525,11 @@ const filterMedia = (media, { includeAllTypes = false, includeUnknown = false }
311
525
  return filtered;
312
526
  };
313
527
 
528
+ /**
529
+ * Procura o campo `contextInfo.expiration` de forma recursiva no payload.
530
+ * @param {Record<string, any>} root
531
+ * @returns {number|null}
532
+ */
314
533
  const findExpiration = (root) => {
315
534
  if (!root || typeof root !== 'object') return null;
316
535
 
@@ -336,6 +555,11 @@ const findExpiration = (root) => {
336
555
  return null;
337
556
  };
338
557
 
558
+ /**
559
+ * Resolve extensão padrão por tipo de mídia.
560
+ * @param {string} type
561
+ * @returns {string}
562
+ */
339
563
  const getMediaExtension = (type) => {
340
564
  if (type === 'image') return 'jpeg';
341
565
  if (type === 'video') return 'mp4';
@@ -343,6 +567,11 @@ const getMediaExtension = (type) => {
343
567
  return 'bin';
344
568
  };
345
569
 
570
+ /**
571
+ * Detecta erros de decrypt inválido (OpenSSL).
572
+ * @param {any} error
573
+ * @returns {boolean}
574
+ */
346
575
  const isBadDecryptError = (error) => {
347
576
  if (!error || typeof error !== 'object') return false;
348
577
  if (error.code === 'ERR_OSSL_BAD_DECRYPT') return true;
@@ -521,13 +750,22 @@ export function isWhatsAppJid(jid) {
521
750
  return Boolean(server && WHATSAPP_USER_JID_SERVERS.has(server));
522
751
  }
523
752
 
753
+ /**
754
+ * Modo de endereçamento por LID.
755
+ * @type {'lid'}
756
+ */
524
757
  export const ADDRESSING_MODE_LID = 'lid';
758
+
759
+ /**
760
+ * Modo de endereçamento por PN/JID canônico.
761
+ * @type {'pn'}
762
+ */
525
763
  export const ADDRESSING_MODE_PN = 'pn';
526
764
 
527
765
  /**
528
766
  * Normaliza um modo de endereçamento (lid/pn).
529
767
  * @param {unknown} value
530
- * @returns {'lid'|'pn'|undefined}
768
+ * @returns {AddressingMode|undefined}
531
769
  */
532
770
  export const normalizeAddressingMode = (value) => {
533
771
  if (value === undefined || value === null) return undefined;
@@ -540,8 +778,8 @@ export const normalizeAddressingMode = (value) => {
540
778
  /**
541
779
  * Resolve modo de endereçamento a partir da chave da mensagem.
542
780
  * @param {object} [key={}]
543
- * @param {object} [senderInfo={}]
544
- * @returns {'lid'|'pn'|undefined}
781
+ * @param {SenderInfo|Record<string, any>} [senderInfo={}]
782
+ * @returns {AddressingMode|undefined}
545
783
  */
546
784
  export const resolveAddressingModeFromMessageKey = (key = {}, senderInfo = {}) => {
547
785
  const explicit = normalizeAddressingMode(key?.addressingMode);
@@ -560,7 +798,7 @@ export const resolveAddressingModeFromMessageKey = (key = {}, senderInfo = {}) =
560
798
 
561
799
  /**
562
800
  * Resolve JID canônico de usuário WhatsApp a partir de candidatos.
563
- * @param {...string} candidates
801
+ * @param {...(string|null|undefined)} candidates
564
802
  * @returns {string}
565
803
  */
566
804
  export const resolveCanonicalWhatsAppJid = (...candidates) => {
@@ -716,7 +954,7 @@ export async function resolveBaileysVersion() {
716
954
 
717
955
  /**
718
956
  * Baixa a foto de perfil associada à mensagem recebida.
719
- * @param {import('@whiskeysockets/baileys').WASocket} sock - Instância conectada do socket.
957
+ * @param {BaileysSocket} sock - Instância conectada do socket.
720
958
  * @param {import('@whiskeysockets/baileys').proto.IWebMessageInfo} msg - Mensagem usada para resolver o JID.
721
959
  * @returns {Promise<Buffer|null>} Buffer da imagem ou `null` se indisponível.
722
960
  */
@@ -740,17 +978,17 @@ export async function getProfilePicBuffer(sock, msg) {
740
978
 
741
979
  /**
742
980
  * Extrai o valor de expiração de uma mensagem do WhatsApp, ou retorna 24 horas (em segundos) por padrão.
743
- * @param {{message?: object}|null|undefined} sock - Estrutura contendo a propriedade `message`.
981
+ * @param {{message?: Record<string, any>}|null|undefined} messageInfo - Estrutura contendo a propriedade `message`.
744
982
  * @returns {number} Tempo de expiração em segundos.
745
983
  */
746
- export function getExpiration(sock) {
984
+ export function getExpiration(messageInfo) {
747
985
  const DEFAULT_EXPIRATION_SECONDS = 24 * 60 * 60;
748
986
 
749
- if (!sock || typeof sock !== 'object' || !sock.message) {
987
+ if (!messageInfo || typeof messageInfo !== 'object' || !messageInfo.message) {
750
988
  return DEFAULT_EXPIRATION_SECONDS;
751
989
  }
752
990
 
753
- const normalizedMessage = normalizeMessage(sock.message);
991
+ const normalizedMessage = normalizeMessage(messageInfo.message);
754
992
  const expiration = findExpiration(normalizedMessage);
755
993
 
756
994
  return typeof expiration === 'number' ? expiration : DEFAULT_EXPIRATION_SECONDS;
@@ -758,7 +996,7 @@ export function getExpiration(sock) {
758
996
 
759
997
  /**
760
998
  * Extrai o conteúdo de texto de uma mensagem do WhatsApp.
761
- * @param {{message?: object}} messageInfo - Objeto que contém o payload da mensagem.
999
+ * @param {{message?: Record<string, any>}} messageInfo - Objeto que contém o payload da mensagem.
762
1000
  * @returns {string} Conteúdo textual extraído ou descrição do tipo de mensagem.
763
1001
  */
764
1002
  export const extractMessageContent = ({ message }) => {
@@ -810,7 +1048,7 @@ export const extractMessageContent = ({ message }) => {
810
1048
 
811
1049
  /**
812
1050
  * Faz o download de mídia a partir de uma mensagem do Baileys.
813
- * @param {import('@whiskeysockets/baileys').WAProto.IMessage} message - Objeto da mídia a ser baixada.
1051
+ * @param {BaileysProtoMessage} message - Objeto da mídia a ser baixada.
814
1052
  * @param {string} type - Tipo de mídia (ex.: `image`, `video`, `audio`, `document`).
815
1053
  * @param {string} outputPath - Diretório onde o arquivo será salvo.
816
1054
  * @returns {Promise<string|null>} Caminho do arquivo salvo ou `null` em caso de falha.
@@ -867,10 +1105,10 @@ export const downloadMediaMessage = async (message, type, outputPath) => {
867
1105
  };
868
1106
 
869
1107
  /**
870
- * Detecta dinamicamente todos os tipos de midia em um objeto de mensagem
871
- * @param {object} messageContent - Conteudo da mensagem
872
- * @param {boolean} isQuoted - Se e de uma mensagem citada
873
- * @returns {Array} Array de objetos com detalhes da midia encontrada
1108
+ * Detecta dinamicamente os tipos de mídia presentes em um payload de mensagem.
1109
+ * @param {Record<string, any>} messageContent Conteúdo da mensagem (normal ou quoted).
1110
+ * @param {boolean} [isQuoted=false] Sinaliza se o payload veio de uma citação.
1111
+ * @returns {MediaEntry[]} Lista de mídias detectadas (pode estar vazia).
874
1112
  */
875
1113
  export function detectAllMediaTypes(messageContent, isQuoted = false) {
876
1114
  if (!messageContent || typeof messageContent !== 'object') {
@@ -914,13 +1152,10 @@ export function detectAllMediaTypes(messageContent, isQuoted = false) {
914
1152
  }
915
1153
 
916
1154
  /**
917
- * Extrai detalhes da midia da mensagem de forma dinamica
918
- * @param {object} message - O objeto da mensagem
919
- * @param {object} options - Opcoes de configuracao
920
- * @param {boolean} options.includeAllTypes - Se deve incluir todos os tipos, nao apenas binarios
921
- * @param {boolean} options.includeQuoted - Se deve incluir midia de mensagens citadas
922
- * @param {boolean} options.includeUnknown - Se deve incluir tipos desconhecidos
923
- * @returns {{mediaType: string, mediaKey: object, details: object}|null} - Detalhes da midia ou null se nao encontrada
1155
+ * Extrai a mídia primária de uma mensagem com filtros configuráveis.
1156
+ * @param {BaileysMessage|{message?: Record<string, any>}|Record<string, any>} message Objeto da mensagem.
1157
+ * @param {MediaExtractionOptions} [options={}] Opções de filtragem.
1158
+ * @returns {MediaExtractionResult|null} Estrutura da mídia primária ou `null` quando não houver mídia.
924
1159
  */
925
1160
  export function extractMediaDetails(message, options = {}) {
926
1161
  const { includeAllTypes = false, includeQuoted = true, includeUnknown = false } = options;
@@ -955,10 +1190,10 @@ export function extractMediaDetails(message, options = {}) {
955
1190
  }
956
1191
 
957
1192
  /**
958
- * Extrai todos os tipos de midia de uma mensagem
959
- * @param {object} message - O objeto da mensagem
960
- * @param {object} options - Opcoes de configuracao
961
- * @returns {Array} Array com todos os tipos de midia encontrados
1193
+ * Extrai todas as mídias detectadas de uma mensagem.
1194
+ * @param {BaileysMessage|{message?: Record<string, any>}|Record<string, any>} message Objeto da mensagem.
1195
+ * @param {MediaExtractionOptions} [options={}] Opções de filtragem.
1196
+ * @returns {MediaEntry[]} Array com todas as mídias encontradas.
962
1197
  */
963
1198
  export function extractAllMediaDetails(message, options = {}) {
964
1199
  const { includeAllTypes = true, includeQuoted = true, includeUnknown = true } = options;
@@ -968,10 +1203,10 @@ export function extractAllMediaDetails(message, options = {}) {
968
1203
  }
969
1204
 
970
1205
  /**
971
- * Verifica se uma mensagem contem midia
972
- * @param {object} message - O objeto da mensagem
973
- * @param {string} specificType - Tipo especifico para verificar (opcional)
974
- * @returns {boolean} True se contem midia
1206
+ * Verifica se uma mensagem contém mídia.
1207
+ * @param {BaileysMessage|{message?: Record<string, any>}|Record<string, any>} message Objeto da mensagem.
1208
+ * @param {string|null} [specificType=null] Tipo específico para filtrar (ex.: `image`).
1209
+ * @returns {boolean} `true` quando ao menos uma mídia compatível é encontrada.
975
1210
  */
976
1211
  export function hasMedia(message, specificType = null) {
977
1212
  const allMedia = collectMediaFromMessage(message, { includeQuoted: true });
@@ -989,8 +1224,13 @@ export function hasMedia(message, specificType = null) {
989
1224
  }
990
1225
 
991
1226
  /**
992
- * Obtem informacoes sobre os tipos de midia suportados
993
- * @returns {object} Informacoes sobre tipos de midia
1227
+ * Obtém metadados dos tipos de mídia suportados.
1228
+ * @returns {{
1229
+ * knownTypes: string[],
1230
+ * binaryTypes: string[],
1231
+ * typeMapping: Record<string, string>,
1232
+ * totalKnownTypes: number
1233
+ * }}
994
1234
  */
995
1235
  export function getMediaTypeInfo() {
996
1236
  return {
@@ -1002,9 +1242,9 @@ export function getMediaTypeInfo() {
1002
1242
  }
1003
1243
 
1004
1244
  /**
1005
- * ===============================
1245
+ * ===========================
1006
1246
  * LID Map Utilities
1007
- * ===============================
1247
+ * ===========================
1008
1248
  */
1009
1249
  const CACHE_TTL_MS = 20 * 60 * 1000;
1010
1250
  const NEGATIVE_TTL_MS = 5 * 60 * 1000;
@@ -1015,10 +1255,7 @@ const BACKFILL_SOURCE = 'backfill';
1015
1255
  const __filename = fileURLToPath(import.meta.url);
1016
1256
  const __dirname = path.dirname(__filename);
1017
1257
  const BAILEYS_AUTH_DIR = path.resolve(__dirname, '../connection/auth');
1018
- const BAILEYS_AUTH_SESSION_ID = (() => {
1019
- const raw = String(process.env.BAILEYS_AUTH_SESSION_ID || '').trim();
1020
- return raw || 'default';
1021
- })();
1258
+ const BAILEYS_AUTH_SESSION_ID = PRIMARY_BAILEYS_SESSION_ID;
1022
1259
 
1023
1260
  const lidCache = new Map();
1024
1261
  const lidWriteBuffer = new Map();
@@ -1026,6 +1263,10 @@ const authReverseLidCache = new Map();
1026
1263
 
1027
1264
  let backfillPromise = null;
1028
1265
 
1266
+ /**
1267
+ * Atualiza métrica de profundidade da fila `lid_map`.
1268
+ * @returns {void}
1269
+ */
1029
1270
  const updateLidQueueMetric = () => {
1030
1271
  setQueueDepth('lid_map', lidWriteBuffer.size);
1031
1272
  };
@@ -1036,20 +1277,40 @@ const updateLidQueueMetric = () => {
1036
1277
  */
1037
1278
  const now = () => __timeNowMs();
1038
1279
 
1280
+ /**
1281
+ * Normaliza um identificador LID.
1282
+ * @param {string|null|undefined} lid
1283
+ * @returns {string|null}
1284
+ */
1039
1285
  const normalizeLid = (lid) => {
1040
1286
  if (!lid || !isLidJid(lid)) return null;
1041
1287
  const normalized = normalizeJid(lid);
1042
1288
  return normalized || null;
1043
1289
  };
1044
1290
 
1291
+ /**
1292
+ * Normaliza JID de usuário WhatsApp.
1293
+ * @param {string|null|undefined} jid
1294
+ * @returns {string|null}
1295
+ */
1045
1296
  const normalizeWhatsAppJid = (jid) => {
1046
1297
  if (!jid || !isWhatsAppJid(jid)) return null;
1047
1298
  const normalized = normalizeJid(jid);
1048
1299
  return normalized || null;
1049
1300
  };
1050
1301
 
1302
+ /**
1303
+ * Mantém apenas dígitos de um valor.
1304
+ * @param {unknown} value
1305
+ * @returns {string}
1306
+ */
1051
1307
  const toDigits = (value) => String(value || '').replace(/\D+/g, '');
1052
1308
 
1309
+ /**
1310
+ * Extrai telefone (dígitos) de payload reverso LID.
1311
+ * @param {unknown} content
1312
+ * @returns {string}
1313
+ */
1053
1314
  const parseReverseMappingPhoneDigits = (content) => {
1054
1315
  const raw = String(content || '').trim();
1055
1316
  if (!raw) return '';
@@ -1065,6 +1326,11 @@ const parseReverseMappingPhoneDigits = (content) => {
1065
1326
  return digits.length >= 10 && digits.length <= 15 ? digits : '';
1066
1327
  };
1067
1328
 
1329
+ /**
1330
+ * Resolve JID a partir de LID consultando auth state (MySQL/arquivos).
1331
+ * @param {string} lid
1332
+ * @returns {Promise<string|null>}
1333
+ */
1068
1334
  const resolveAuthStoreJidByLid = async (lid) => {
1069
1335
  const normalizedLid = normalizeLid(lid);
1070
1336
  if (!normalizedLid) return null;
@@ -1132,7 +1398,7 @@ const maskJid = (jid) => {
1132
1398
  /**
1133
1399
  * Busca entrada do cache (com expiração).
1134
1400
  * @param {string|null|undefined} lid
1135
- * @returns {{jid: string|null, expiresAt: number, lastStoredAt: number|null}|null}
1401
+ * @returns {LidCacheEntry|null}
1136
1402
  */
1137
1403
  const getCacheEntry = (lid) => {
1138
1404
  if (!lid) return null;
@@ -1194,9 +1460,10 @@ export const getCachedJidForLid = (lid) => {
1194
1460
 
1195
1461
  /**
1196
1462
  * Divide lista em batches.
1197
- * @param {Array<any>} items
1463
+ * @template T
1464
+ * @param {T[]} items
1198
1465
  * @param {number} [limit=BATCH_LIMIT]
1199
- * @returns {Array<Array<any>>}
1466
+ * @returns {T[][]}
1200
1467
  */
1201
1468
  const buildChunks = (items, limit = BATCH_LIMIT) => {
1202
1469
  const chunks = [];
@@ -1326,7 +1593,7 @@ const buildServerLikeFilter = (column, servers) => {
1326
1593
  /**
1327
1594
  * Resolve candidatos principais de identidade de usuário.
1328
1595
  * Centraliza regra usada por `resolveUserIdCached` e `resolveUserId`.
1329
- * @param {{lid?: string|null, jid?: string|null, participantAlt?: string|null}} [params]
1596
+ * @param {IdentityParams} [params]
1330
1597
  * @returns {{directJid: string|null, lidValue: string|null, fallback: string|null}}
1331
1598
  */
1332
1599
  const resolveIdentityCandidates = ({ lid, jid, participantAlt } = {}) => {
@@ -1355,6 +1622,11 @@ const resolveIdentityCandidates = ({ lid, jid, participantAlt } = {}) => {
1355
1622
  };
1356
1623
  };
1357
1624
 
1625
+ /**
1626
+ * Monta SQL de upsert para lote do `lid_map`.
1627
+ * @param {number} rows
1628
+ * @returns {string}
1629
+ */
1358
1630
  const buildLidUpsertSql = (rows) => {
1359
1631
  const placeholders = buildRowPlaceholders(rows, '(?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)');
1360
1632
  return `
@@ -1431,7 +1703,7 @@ export const queueLidUpdate = (lid, jid, source = 'message') => {
1431
1703
 
1432
1704
  /**
1433
1705
  * Resolve ID canônico usando apenas cache.
1434
- * @param {{lid?: string|null, jid?: string|null, participantAlt?: string|null}} [params]
1706
+ * @param {IdentityParams} [params]
1435
1707
  * @returns {string|null}
1436
1708
  */
1437
1709
  export const resolveUserIdCached = ({ lid, jid, participantAlt } = {}) => {
@@ -1445,9 +1717,9 @@ export const resolveUserIdCached = ({ lid, jid, participantAlt } = {}) => {
1445
1717
  };
1446
1718
 
1447
1719
  /**
1448
- * Extrai informacoes do remetente a partir de uma mensagem do Baileys.
1449
- * @param {import('@whiskeysockets/baileys').WAMessage} msg
1450
- * @returns {{lid: string|null, jid: string|null, participantAlt: string|null, remoteJid: string|null, remoteJidAlt: string|null, groupMessage: boolean}}
1720
+ * Extrai informações de identidade do remetente a partir da mensagem.
1721
+ * @param {BaileysMessage} msg
1722
+ * @returns {SenderInfo}
1451
1723
  */
1452
1724
  export const extractSenderInfoFromMessage = (msg) => {
1453
1725
  const remoteJid = normalizeJid(msg?.key?.remoteJid || '') || null;
@@ -1556,7 +1828,7 @@ const fetchJidByLid = async (lid) => {
1556
1828
 
1557
1829
  /**
1558
1830
  * Resolve ID canônico consultando banco se necessário.
1559
- * @param {{lid?: string|null, jid?: string|null, participantAlt?: string|null}} [params]
1831
+ * @param {IdentityParams} [params]
1560
1832
  * @returns {Promise<string|null>}
1561
1833
  */
1562
1834
  export const resolveUserId = async ({ lid, jid, participantAlt } = {}) => {
@@ -1604,6 +1876,10 @@ export const reconcileLidToJid = async ({ lid, jid, source = 'map' } = {}) => {
1604
1876
  return { updated };
1605
1877
  };
1606
1878
 
1879
+ /**
1880
+ * Executa flush em lote do buffer de atualizações LID->JID.
1881
+ * @returns {Promise<void>}
1882
+ */
1607
1883
  const flushLidQueueCore = async () => {
1608
1884
  if (lidWriteBuffer.size === 0) return;
1609
1885
  const entries = Array.from(lidWriteBuffer.values());
@@ -1669,6 +1945,13 @@ export const flushLidQueue = async () => {
1669
1945
  await lidFlushRunner.run();
1670
1946
  };
1671
1947
 
1948
+ /**
1949
+ * Enfileira atualização de mapa LID/JID e retorna estado simplificado.
1950
+ * @param {string} lid
1951
+ * @param {string|null} jid
1952
+ * @param {string} [source='message']
1953
+ * @returns {Promise<{stored: boolean, reconciled: boolean}>}
1954
+ */
1672
1955
  export const maybeStoreLidMap = async (lid, jid, source = 'message') => {
1673
1956
  const result = queueLidUpdate(lid, jid, source);
1674
1957
  return { stored: result.queued, reconciled: result.reconciled };
@@ -1690,6 +1973,10 @@ export const extractUserIdInfo = (value) => {
1690
1973
  };
1691
1974
  }
1692
1975
 
1976
+ /**
1977
+ * @param {unknown} entry
1978
+ * @returns {string|null}
1979
+ */
1693
1980
  const readJid = (entry) => (typeof entry === 'string' ? normalizeJid(entry) || null : null);
1694
1981
 
1695
1982
  const participantAlt = readJid(value.participantAlt);