@kaikybrofc/omnizap-system 2.2.10 → 2.3.0

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 (119) hide show
  1. package/README.md +20 -18
  2. package/app/config/adminIdentity.js +1 -3
  3. package/app/connection/socketController.js +10 -20
  4. package/app/controllers/messageController.js +7 -28
  5. package/app/modules/aiModule/catCommand.js +29 -192
  6. package/app/modules/broadcastModule/noticeCommand.js +28 -97
  7. package/app/modules/gameModule/diceCommand.js +6 -32
  8. package/app/modules/playModule/playCommand.js +57 -258
  9. package/app/modules/quoteModule/quoteCommand.js +2 -4
  10. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
  11. package/app/modules/statsModule/noMessageCommand.js +16 -84
  12. package/app/modules/statsModule/rankingCommand.js +5 -25
  13. package/app/modules/statsModule/rankingCommon.js +1 -9
  14. package/app/modules/stickerModule/convertToWebp.js +4 -27
  15. package/app/modules/stickerModule/stickerCommand.js +13 -24
  16. package/app/modules/stickerModule/stickerTextCommand.js +13 -25
  17. package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
  18. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
  19. package/app/modules/stickerPackModule/domainEvents.js +2 -11
  20. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
  21. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
  22. package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
  23. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
  24. package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
  25. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
  26. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
  27. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
  28. package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
  29. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
  30. package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
  31. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +13 -34
  32. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
  33. package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
  34. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
  35. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
  36. package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
  37. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
  38. package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
  39. package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
  40. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
  41. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
  42. package/app/modules/stickerPackModule/stickerPackService.js +50 -115
  43. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
  44. package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
  45. package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
  46. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
  47. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
  48. package/app/modules/systemMetricsModule/pingCommand.js +9 -39
  49. package/app/modules/tiktokModule/tiktokCommand.js +17 -109
  50. package/app/modules/userModule/userCommand.js +2 -88
  51. package/app/observability/metrics.js +5 -16
  52. package/app/services/captchaService.js +1 -6
  53. package/app/services/dbWriteQueue.js +3 -18
  54. package/app/services/featureFlagService.js +2 -8
  55. package/app/services/newsBroadcastService.js +0 -1
  56. package/app/services/queueUtils.js +2 -4
  57. package/app/services/whatsappLoginLinkService.js +7 -9
  58. package/app/store/premiumUserStore.js +1 -2
  59. package/app/utils/antiLink/antiLinkModule.js +3 -233
  60. package/app/utils/logger/loggerModule.js +9 -34
  61. package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
  62. package/database/init.js +1 -8
  63. package/docker-compose.yml +27 -27
  64. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
  65. package/docs/seo/satellite-page-template.md +2 -0
  66. package/docs/seo/satellite-pages-phase1.json +40 -177
  67. package/eslint.config.js +2 -15
  68. package/index.js +8 -36
  69. package/ml/clip_classifier/README.md +4 -6
  70. package/observability/alert-rules.yml +12 -12
  71. package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
  72. package/package.json +6 -3
  73. package/public/api-docs/index.html +220 -193
  74. package/public/bot-whatsapp-para-grupo/index.html +291 -261
  75. package/public/bot-whatsapp-sem-programar/index.html +291 -261
  76. package/public/comandos/index.html +421 -406
  77. package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
  78. package/public/como-criar-comandos-whatsapp/index.html +291 -261
  79. package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
  80. package/public/como-moderar-grupo-whatsapp/index.html +291 -261
  81. package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
  82. package/public/css/github-project-panel.css +13 -8
  83. package/public/css/stickers-admin.css +25 -9
  84. package/public/css/styles.css +23 -16
  85. package/public/index.html +1117 -994
  86. package/public/js/apps/apiDocsApp.js +17 -167
  87. package/public/js/apps/createPackApp.js +69 -332
  88. package/public/js/apps/homeApp.js +94 -74
  89. package/public/js/apps/loginApp.js +3 -12
  90. package/public/js/apps/stickersAdminApp.js +190 -181
  91. package/public/js/apps/stickersApp.js +496 -1397
  92. package/public/js/catalog.js +11 -74
  93. package/public/js/github-panel/components/ErrorState.js +1 -8
  94. package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
  95. package/public/js/github-panel/components/SkeletonPanel.js +1 -11
  96. package/public/js/github-panel/components/StatCard.js +1 -7
  97. package/public/js/github-panel/vendor/react.js +1 -9
  98. package/public/js/runtime/react-runtime.js +1 -9
  99. package/public/licenca/index.html +104 -86
  100. package/public/login/index.html +315 -325
  101. package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
  102. package/public/stickers/admin/index.html +14 -19
  103. package/public/stickers/create/index.html +39 -44
  104. package/public/stickers/index.html +96 -107
  105. package/public/termos-de-uso/index.html +142 -115
  106. package/public/user/index.html +347 -350
  107. package/scripts/cache-bust.mjs +5 -24
  108. package/scripts/generate-seo-satellite-pages.mjs +10 -13
  109. package/scripts/run-prettier-all.mjs +25 -0
  110. package/scripts/sticker-catalog-loadtest.mjs +13 -11
  111. package/scripts/sticker-worker-task.mjs +1 -4
  112. package/scripts/sync-readme-snapshot.mjs +3 -2
  113. package/server/controllers/stickerCatalogController.js +67 -5
  114. package/server/http/httpServer.js +2 -10
  115. package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
  116. package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
  117. package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
  118. package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
  119. package/server/routes/stickerCatalog/catalogRouter.js +11 -13
@@ -2,16 +2,10 @@ import { executeQuery } from '../../../database/index.js';
2
2
  import logger from '../../utils/logger/loggerModule.js';
3
3
  import { getGroupParticipants, isUserAdmin } from '../../config/groupUtils.js';
4
4
  import { resolveBotJid } from '../../config/baileysConfig.js';
5
- import {
6
- primeLidCache,
7
- resolveUserIdCached,
8
- isLidUserId,
9
- isWhatsAppUserId,
10
- } from '../../services/lidMapService.js';
5
+ import { primeLidCache, resolveUserIdCached, isLidUserId, isWhatsAppUserId } from '../../services/lidMapService.js';
11
6
  import { sendAndStore } from '../../services/messagePersistenceService.js';
12
7
 
13
- const getParticipantJid = (participant) =>
14
- participant?.id || participant?.jid || participant?.lid || null;
8
+ const getParticipantJid = (participant) => participant?.id || participant?.jid || participant?.lid || null;
15
9
 
16
10
  const MAX_MENTIONS_PER_MESSAGE = 80;
17
11
  const BATCH_DELAY_MS = 400;
@@ -71,13 +65,8 @@ const normalizeParticipant = (participant, sock) => {
71
65
  jid: rawId,
72
66
  participantAlt,
73
67
  });
74
- const contact =
75
- (canonical && sock?.contacts?.[canonical]) ||
76
- (participantAlt && sock?.contacts?.[participantAlt]) ||
77
- (rawId && sock?.contacts?.[rawId]) ||
78
- null;
79
- const displayName =
80
- participant?.notify || participant?.name || contact?.notify || contact?.name || contact?.short;
68
+ const contact = (canonical && sock?.contacts?.[canonical]) || (participantAlt && sock?.contacts?.[participantAlt]) || (rawId && sock?.contacts?.[rawId]) || null;
69
+ const displayName = participant?.notify || participant?.name || contact?.notify || contact?.name || contact?.short;
81
70
  return {
82
71
  rawId,
83
72
  participantAlt,
@@ -86,28 +75,9 @@ const normalizeParticipant = (participant, sock) => {
86
75
  };
87
76
  };
88
77
 
89
- const buildNoMessageText = ({
90
- minMessages,
91
- periodLabel,
92
- totalParticipants,
93
- totalListed,
94
- batchIndex = 1,
95
- batchTotal = 1,
96
- batchSize = 0,
97
- }) => {
98
- const title =
99
- minMessages <= 1 ? '🔇 *Membros sem mensagens no grupo*' : '🔇 *Membros abaixo do mínimo*';
100
- const lines = [
101
- title,
102
- '',
103
- `• Mínimo de mensagens: ${minMessages}`,
104
- `• Período: ${periodLabel}`,
105
- `• Participantes: ${totalParticipants}`,
106
- `• Abaixo do mínimo: ${totalListed}`,
107
- ...(batchTotal > 1
108
- ? [`• Parte: ${batchIndex}/${batchTotal}`, `• Notificados nesta mensagem: ${batchSize}`]
109
- : []),
110
- ];
78
+ const buildNoMessageText = ({ minMessages, periodLabel, totalParticipants, totalListed, batchIndex = 1, batchTotal = 1, batchSize = 0 }) => {
79
+ const title = minMessages <= 1 ? '🔇 *Membros sem mensagens no grupo*' : '🔇 *Membros abaixo do mínimo*';
80
+ const lines = [title, '', `• Mínimo de mensagens: ${minMessages}`, `• Período: ${periodLabel}`, `• Participantes: ${totalParticipants}`, `• Abaixo do mínimo: ${totalListed}`, ...(batchTotal > 1 ? [`• Parte: ${batchIndex}/${batchTotal}`, `• Notificados nesta mensagem: ${batchSize}`] : [])];
111
81
 
112
82
  if (!totalListed) {
113
83
  lines.push('', '✅ Todos os membros atingiram o mínimo.');
@@ -139,40 +109,20 @@ const splitEntriesByMentions = (entries, maxMentions) => {
139
109
  return batches;
140
110
  };
141
111
 
142
- export async function handleNoMessageCommand({
143
- sock,
144
- remoteJid,
145
- messageInfo,
146
- expirationMessage,
147
- isGroupMessage,
148
- senderJid,
149
- text,
150
- }) {
112
+ export async function handleNoMessageCommand({ sock, remoteJid, messageInfo, expirationMessage, isGroupMessage, senderJid, text }) {
151
113
  if (!isGroupMessage) {
152
- await sendAndStore(sock,
153
- remoteJid,
154
- { text: 'Este comando so pode ser usado em grupos.' },
155
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
156
- );
114
+ await sendAndStore(sock, remoteJid, { text: 'Este comando so pode ser usado em grupos.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
157
115
  return;
158
116
  }
159
117
  if (!(await isUserAdmin(remoteJid, senderJid))) {
160
- await sendAndStore(sock,
161
- remoteJid,
162
- { text: 'Você não tem permissão para usar este comando.' },
163
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
164
- );
118
+ await sendAndStore(sock, remoteJid, { text: 'Você não tem permissão para usar este comando.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
165
119
  return;
166
120
  }
167
121
 
168
122
  try {
169
123
  const participants = await getGroupParticipants(remoteJid);
170
124
  if (!participants || participants.length === 0) {
171
- await sendAndStore(sock,
172
- remoteJid,
173
- { text: 'Nao foi possivel obter os participantes do grupo.' },
174
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
175
- );
125
+ await sendAndStore(sock, remoteJid, { text: 'Nao foi possivel obter os participantes do grupo.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
176
126
  return;
177
127
  }
178
128
 
@@ -224,11 +174,7 @@ export async function handleNoMessageCommand({
224
174
  if (botCanonical && canonical === botCanonical) return;
225
175
  const total = countsByCanonical.get(canonical) || 0;
226
176
  if (total >= minMessages) return;
227
- const mentionJid = isWhatsAppUserId(canonical)
228
- ? canonical
229
- : isWhatsAppUserId(participant.participantAlt)
230
- ? participant.participantAlt
231
- : null;
177
+ const mentionJid = isWhatsAppUserId(canonical) ? canonical : isWhatsAppUserId(participant.participantAlt) ? participant.participantAlt : null;
232
178
  entries.push({
233
179
  canonical,
234
180
  rawId: participant.rawId,
@@ -246,19 +192,13 @@ export async function handleNoMessageCommand({
246
192
  totalParticipants,
247
193
  totalListed,
248
194
  });
249
- await sendAndStore(sock,
250
- remoteJid,
251
- { text: responseText },
252
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
253
- );
195
+ await sendAndStore(sock, remoteJid, { text: responseText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
254
196
  return;
255
197
  }
256
198
 
257
199
  for (let index = 0; index < batches.length; index += 1) {
258
200
  const batch = batches[index];
259
- const batchMentions = Array.from(
260
- new Set(batch.map((entry) => entry.mentionJid).filter(Boolean)),
261
- );
201
+ const batchMentions = Array.from(new Set(batch.map((entry) => entry.mentionJid).filter(Boolean)));
262
202
  const responseText = buildNoMessageText({
263
203
  minMessages,
264
204
  periodLabel,
@@ -268,21 +208,13 @@ export async function handleNoMessageCommand({
268
208
  batchTotal: batches.length,
269
209
  batchSize: batch.length,
270
210
  });
271
- await sendAndStore(sock,
272
- remoteJid,
273
- { text: responseText, mentions: batchMentions },
274
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
275
- );
211
+ await sendAndStore(sock, remoteJid, { text: responseText, mentions: batchMentions }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
276
212
  if (index < batches.length - 1) {
277
213
  await sleep(BATCH_DELAY_MS);
278
214
  }
279
215
  }
280
216
  } catch (error) {
281
217
  logger.error('Erro ao buscar membros sem mensagens:', { error: error.message });
282
- await sendAndStore(sock,
283
- remoteJid,
284
- { text: `Erro ao buscar membros sem mensagens: ${error.message}` },
285
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
286
- );
218
+ await sendAndStore(sock, remoteJid, { text: `Erro ao buscar membros sem mensagens: ${error.message}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
287
219
  }
288
220
  }
@@ -6,19 +6,9 @@ import { sendAndStore } from '../../services/messagePersistenceService.js';
6
6
 
7
7
  const RANKING_LIMIT = 5;
8
8
 
9
- export async function handleRankingCommand({
10
- sock,
11
- remoteJid,
12
- messageInfo,
13
- expirationMessage,
14
- isGroupMessage,
15
- }) {
9
+ export async function handleRankingCommand({ sock, remoteJid, messageInfo, expirationMessage, isGroupMessage }) {
16
10
  if (!isGroupMessage) {
17
- await sendAndStore(sock,
18
- remoteJid,
19
- { text: 'Este comando so pode ser usado em grupos.' },
20
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
21
- );
11
+ await sendAndStore(sock, remoteJid, { text: 'Este comando so pode ser usado em grupos.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
22
12
  return;
23
13
  }
24
14
 
@@ -31,9 +21,7 @@ export async function handleRankingCommand({
31
21
  limit: RANKING_LIMIT,
32
22
  });
33
23
  const text = buildRankingMessage({ scope: 'group', limit: RANKING_LIMIT, ...report });
34
- const mentions = report.rows
35
- .map((row) => row.mention_id)
36
- .filter((jid) => isWhatsAppUserId(jid));
24
+ const mentions = report.rows.map((row) => row.mention_id).filter((jid) => isWhatsAppUserId(jid));
37
25
 
38
26
  const imageBuffer = await renderRankingImage({
39
27
  sock,
@@ -44,17 +32,9 @@ export async function handleRankingCommand({
44
32
  scope: 'group',
45
33
  limit: RANKING_LIMIT,
46
34
  });
47
- await sendAndStore(sock,
48
- remoteJid,
49
- { image: imageBuffer, caption: text, ...(mentions.length ? { mentions } : {}) },
50
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
51
- );
35
+ await sendAndStore(sock, remoteJid, { image: imageBuffer, caption: text, ...(mentions.length ? { mentions } : {}) }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
52
36
  } catch (error) {
53
37
  logger.error('Erro ao gerar ranking do grupo:', { error: error.message });
54
- await sendAndStore(sock,
55
- remoteJid,
56
- { text: `Erro ao gerar ranking: ${error.message}` },
57
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
58
- );
38
+ await sendAndStore(sock, remoteJid, { text: `Erro ao gerar ranking: ${error.message}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
59
39
  }
60
40
  }
@@ -545,15 +545,7 @@ export const enrichRankingRows = async ({ rows, scope, remoteJid, botJid }) => {
545
545
  * @param {{scope: 'group'|'global', remoteJid?: string|null, botJid?: string|null, limit?: number|null, includeTopType?: boolean, includeDbStart?: boolean, enrichRows?: boolean}} params
546
546
  * @returns {Promise<{rows: Array<any>, totalMessages: number, topType: {label: string, count: number}|null, topTotal: number, dbStart: any}>}
547
547
  */
548
- export const getRankingReport = async ({
549
- scope,
550
- remoteJid,
551
- botJid,
552
- limit = null,
553
- includeTopType = true,
554
- includeDbStart = true,
555
- enrichRows = true,
556
- }) => {
548
+ export const getRankingReport = async ({ scope, remoteJid, botJid, limit = null, includeTopType = true, includeDbStart = true, enrichRows = true }) => {
557
549
  const totalMessages = await getTotalMessages({ scope, remoteJid, botJid });
558
550
  const topType = includeTopType ? await getTopMessageType({ scope, remoteJid, botJid }) : null;
559
551
  const { rows } = await getRankingBase({ scope, remoteJid, botJid, limit });
@@ -224,9 +224,7 @@ function runProcess(command, args, { timeoutMs }) {
224
224
  }
225
225
 
226
226
  if (code !== 0) {
227
- const processError = new Error(
228
- `${command} finalizou com código ${code}${signal ? ` (sinal: ${signal})` : ''}.`,
229
- );
227
+ const processError = new Error(`${command} finalizou com código ${code}${signal ? ` (sinal: ${signal})` : ''}.`);
230
228
  processError.code = code;
231
229
  processError.signal = signal;
232
230
  processError.stderr = stderr;
@@ -277,17 +275,7 @@ export async function convertToWebp(inputPath, mediaType, userId, uniqueId, opti
277
275
  const sanitizedUserId = String(userId || 'anon').replace(/[^a-zA-Z0-9._-]/g, '_');
278
276
  const userStickerDir = path.join(TEMP_DIR, sanitizedUserId);
279
277
  const outputPath = path.join(userStickerDir, `sticker_${uniqueId}.webp`);
280
- const {
281
- stretch = true,
282
- videoMaxDurationSeconds = DEFAULT_VIDEO_MAX_DURATION_SECONDS,
283
- videoFps = DEFAULT_VIDEO_FPS,
284
- videoQuality = DEFAULT_VIDEO_QUALITY,
285
- videoCompressionLevel = DEFAULT_VIDEO_COMPRESSION_LEVEL,
286
- maxOutputSizeBytes,
287
- maxOutputSizeBytesByType = DEFAULT_MAX_OUTPUT_SIZE_BYTES_BY_TYPE,
288
- timeoutMsByType = DEFAULT_TIMEOUT_MS_BY_TYPE,
289
- timeoutMs,
290
- } = options;
278
+ const { stretch = true, videoMaxDurationSeconds = DEFAULT_VIDEO_MAX_DURATION_SECONDS, videoFps = DEFAULT_VIDEO_FPS, videoQuality = DEFAULT_VIDEO_QUALITY, videoCompressionLevel = DEFAULT_VIDEO_COMPRESSION_LEVEL, maxOutputSizeBytes, maxOutputSizeBytesByType = DEFAULT_MAX_OUTPUT_SIZE_BYTES_BY_TYPE, timeoutMsByType = DEFAULT_TIMEOUT_MS_BY_TYPE, timeoutMs } = options;
291
279
  const maxOutputLimit = resolveMaxOutputLimit(mediaType, maxOutputSizeBytes, maxOutputSizeBytesByType);
292
280
 
293
281
  try {
@@ -309,9 +297,7 @@ export async function convertToWebp(inputPath, mediaType, userId, uniqueId, opti
309
297
  return outputPath;
310
298
  }
311
299
 
312
- const normalizedDuration = Math.trunc(
313
- clampNumber(videoMaxDurationSeconds, 1, 30, DEFAULT_VIDEO_MAX_DURATION_SECONDS),
314
- );
300
+ const normalizedDuration = Math.trunc(clampNumber(videoMaxDurationSeconds, 1, 30, DEFAULT_VIDEO_MAX_DURATION_SECONDS));
315
301
  const normalizedFps = Math.trunc(clampNumber(videoFps, 1, 30, DEFAULT_VIDEO_FPS));
316
302
  const normalizedQuality = Math.trunc(clampNumber(videoQuality, 0, 100, DEFAULT_VIDEO_QUALITY));
317
303
  const normalizedCompression = Math.trunc(clampNumber(videoCompressionLevel, 0, 6, DEFAULT_VIDEO_COMPRESSION_LEVEL));
@@ -331,16 +317,7 @@ export async function convertToWebp(inputPath, mediaType, userId, uniqueId, opti
331
317
  ffmpegArgs.push('-vcodec', 'libwebp', '-loop', '0', '-preset', 'default', '-an');
332
318
 
333
319
  if (mediaType === 'video') {
334
- ffmpegArgs.push(
335
- '-vsync',
336
- '0',
337
- '-lossless',
338
- '0',
339
- '-q:v',
340
- String(normalizedQuality),
341
- '-compression_level',
342
- String(normalizedCompression),
343
- );
320
+ ffmpegArgs.push('-vsync', '0', '-lossless', '0', '-q:v', String(normalizedQuality), '-compression_level', String(normalizedCompression));
344
321
  } else {
345
322
  ffmpegArgs.push('-lossless', '1');
346
323
  }
@@ -24,10 +24,7 @@ const STICKER_WEB_ORIGIN = resolveStickerWebOrigin();
24
24
  function normalizeBasePath(value, fallback) {
25
25
  const raw = String(value || '').trim() || fallback;
26
26
  const withLeadingSlash = raw.startsWith('/') ? raw : `/${raw}`;
27
- const withoutTrailingSlash =
28
- withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/')
29
- ? withLeadingSlash.slice(0, -1)
30
- : withLeadingSlash;
27
+ const withoutTrailingSlash = withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/') ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
31
28
  return withoutTrailingSlash || fallback;
32
29
  }
33
30
 
@@ -45,14 +42,7 @@ function normalizeOrigin(value) {
45
42
  }
46
43
 
47
44
  function resolveStickerWebOrigin() {
48
- const candidates = [
49
- process.env.STICKER_WEB_ORIGIN,
50
- process.env.APP_BASE_URL,
51
- process.env.PUBLIC_BASE_URL,
52
- process.env.SITE_URL,
53
- process.env.WEB_URL,
54
- process.env.BASE_URL,
55
- ];
45
+ const candidates = [process.env.STICKER_WEB_ORIGIN, process.env.APP_BASE_URL, process.env.PUBLIC_BASE_URL, process.env.SITE_URL, process.env.WEB_URL, process.env.BASE_URL];
56
46
 
57
47
  for (const candidate of candidates) {
58
48
  const normalized = normalizeOrigin(candidate);
@@ -71,9 +61,15 @@ function buildPackWebUrl(packKey) {
71
61
  }
72
62
 
73
63
  function isPackPubliclyVisible(pack) {
74
- const visibility = String(pack?.visibility || '').trim().toLowerCase();
75
- const status = String(pack?.status || 'published').trim().toLowerCase();
76
- const packStatus = String(pack?.pack_status || 'ready').trim().toLowerCase();
64
+ const visibility = String(pack?.visibility || '')
65
+ .trim()
66
+ .toLowerCase();
67
+ const status = String(pack?.status || 'published')
68
+ .trim()
69
+ .toLowerCase();
70
+ const packStatus = String(pack?.pack_status || 'ready')
71
+ .trim()
72
+ .toLowerCase();
77
73
  return (visibility === 'public' || visibility === 'unlisted') && status === 'published' && packStatus === 'ready';
78
74
  }
79
75
 
@@ -218,10 +214,7 @@ function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX)
218
214
  const countLabel = itemCount > 0 ? ` (${itemCount}/${AUTO_PACK_MAX_ITEMS})` : '';
219
215
 
220
216
  if (result.status === 'duplicate') {
221
- const duplicateLines = [
222
- `ℹ️ Essa figurinha já estava no pack automático *${packName}*.`,
223
- `Use *${commandPrefix}pack info ${packIdentifier}* para ver o pack ou *${commandPrefix}pack send ${packIdentifier}* para enviar.`,
224
- ];
217
+ const duplicateLines = [`ℹ️ Essa figurinha já estava no pack automático *${packName}*.`, `Use *${commandPrefix}pack info ${packIdentifier}* para ver o pack ou *${commandPrefix}pack send ${packIdentifier}* para enviar.`];
225
218
  if (packWebUrl) {
226
219
  duplicateLines.push(`🌐 Link do pack no site: ${packWebUrl}`);
227
220
  } else {
@@ -230,11 +223,7 @@ function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX)
230
223
  return duplicateLines.join('\n');
231
224
  }
232
225
 
233
- const savedLines = [
234
- `📦 Figurinha salva automaticamente no pack *${packName}*${countLabel}.\n\n`,
235
- `Dica: use *${commandPrefix}pack list* para gerenciar seus packs.`,
236
- `Para enviar agora: *${commandPrefix}pack send ${packCommandTarget}*.`,
237
- ];
226
+ const savedLines = [`📦 Figurinha salva automaticamente no pack *${packName}*${countLabel}.\n\n`, `Dica: use *${commandPrefix}pack list* para gerenciar seus packs.`, `Para enviar agora: *${commandPrefix}pack send ${packCommandTarget}*.`];
238
227
  if (packWebUrl) {
239
228
  savedLines.push(`🌐 Abrir no site: ${packWebUrl}`);
240
229
  } else {
@@ -32,10 +32,7 @@ const STICKER_WEB_ORIGIN = resolveStickerWebOrigin();
32
32
  function normalizeBasePath(value, fallback) {
33
33
  const raw = String(value || '').trim() || fallback;
34
34
  const withLeadingSlash = raw.startsWith('/') ? raw : `/${raw}`;
35
- const withoutTrailingSlash =
36
- withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/')
37
- ? withLeadingSlash.slice(0, -1)
38
- : withLeadingSlash;
35
+ const withoutTrailingSlash = withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/') ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
39
36
  return withoutTrailingSlash || fallback;
40
37
  }
41
38
 
@@ -53,15 +50,7 @@ function normalizeOrigin(value) {
53
50
  }
54
51
 
55
52
  function resolveStickerWebOrigin() {
56
- const candidates = [
57
- process.env.STICKER_WEB_ORIGIN,
58
- process.env.APP_BASE_URL,
59
- process.env.PUBLIC_BASE_URL,
60
- process.env.SITE_URL,
61
- process.env.WEB_URL,
62
- process.env.BASE_URL,
63
- process.env.STICKER_DEFAULT_PACK_NAME,
64
- ];
53
+ const candidates = [process.env.STICKER_WEB_ORIGIN, process.env.APP_BASE_URL, process.env.PUBLIC_BASE_URL, process.env.SITE_URL, process.env.WEB_URL, process.env.BASE_URL, process.env.STICKER_DEFAULT_PACK_NAME];
65
54
 
66
55
  for (const candidate of candidates) {
67
56
  const normalized = normalizeOrigin(candidate);
@@ -79,9 +68,15 @@ function buildPackWebUrl(packKey) {
79
68
  }
80
69
 
81
70
  function isPackPubliclyVisible(pack) {
82
- const visibility = String(pack?.visibility || '').trim().toLowerCase();
83
- const status = String(pack?.status || 'published').trim().toLowerCase();
84
- const packStatus = String(pack?.pack_status || 'ready').trim().toLowerCase();
71
+ const visibility = String(pack?.visibility || '')
72
+ .trim()
73
+ .toLowerCase();
74
+ const status = String(pack?.status || 'published')
75
+ .trim()
76
+ .toLowerCase();
77
+ const packStatus = String(pack?.pack_status || 'ready')
78
+ .trim()
79
+ .toLowerCase();
85
80
  return (visibility === 'public' || visibility === 'unlisted') && status === 'published' && packStatus === 'ready';
86
81
  }
87
82
 
@@ -146,10 +141,7 @@ function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX)
146
141
  const countLabel = itemCount > 0 ? ` (${itemCount}/${AUTO_PACK_MAX_ITEMS})` : '';
147
142
 
148
143
  if (result.status === 'duplicate') {
149
- const duplicateLines = [
150
- `ℹ️ Essa figurinha já estava no pack automático *${packName}*.`,
151
- `Use *${commandPrefix}pack info ${packIdentifier}* para ver o pack ou *${commandPrefix}pack send ${packIdentifier}* para enviar.`,
152
- ];
144
+ const duplicateLines = [`ℹ️ Essa figurinha já estava no pack automático *${packName}*.`, `Use *${commandPrefix}pack info ${packIdentifier}* para ver o pack ou *${commandPrefix}pack send ${packIdentifier}* para enviar.`];
153
145
  if (packWebUrl) {
154
146
  duplicateLines.push(`🌐 Link do pack no site: ${packWebUrl}`);
155
147
  } else {
@@ -158,11 +150,7 @@ function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX)
158
150
  return duplicateLines.join('\n');
159
151
  }
160
152
 
161
- const savedLines = [
162
- `📦 Figurinha salva automaticamente no pack *${packName}*${countLabel}.\n\n`,
163
- `Dica: use *${commandPrefix}pack list* para gerenciar seus packs.`,
164
- `Para enviar agora: *${commandPrefix}pack send ${packCommandTarget}*.`,
165
- ];
153
+ const savedLines = [`📦 Figurinha salva automaticamente no pack *${packName}*${countLabel}.\n\n`, `Dica: use *${commandPrefix}pack list* para gerenciar seus packs.`, `Para enviar agora: *${commandPrefix}pack send ${packCommandTarget}*.`];
166
154
  if (packWebUrl) {
167
155
  savedLines.push(`🌐 Abrir no site: ${packWebUrl}`);
168
156
  } else {
@@ -10,7 +10,10 @@ const AUTO_COLLECT_ENABLED = process.env.STICKER_PACK_AUTO_COLLECT_ENABLED !== '
10
10
  const AUTO_PACK_NAME_MAX_LENGTH = 120;
11
11
  const AUTO_PACK_DESCRIPTION_MARKER = '[auto-pack:collector]';
12
12
  const AUTO_PACK_DESCRIPTION_TEXT = 'Coleção automática de figurinhas criadas pelo usuário.';
13
- const normalizeVisibility = (value) => String(value || '').trim().toLowerCase();
13
+ const normalizeVisibility = (value) =>
14
+ String(value || '')
15
+ .trim()
16
+ .toLowerCase();
14
17
 
15
18
  /**
16
19
  * Escapa texto para uso seguro em RegExp.
@@ -26,7 +29,10 @@ const escapeRegex = (value) => String(value || '').replace(/[.*+?^${}()|[\]\\]/g
26
29
  * @param {unknown} value Valor de entrada.
27
30
  * @returns {string} Texto sem diacríticos.
28
31
  */
29
- const removeDiacritics = (value) => String(value || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '');
32
+ const removeDiacritics = (value) =>
33
+ String(value || '')
34
+ .normalize('NFD')
35
+ .replace(/[\u0300-\u036f]/g, '');
30
36
 
31
37
  /**
32
38
  * Gera nome de pack automático válido no padrão aceito.
@@ -51,7 +57,13 @@ const isThemeCurationAutoPack = (pack) => {
51
57
  if (!pack || typeof pack !== 'object') return false;
52
58
  const description = String(pack.description || '').toLowerCase();
53
59
  if (description.includes('[auto-theme:') || description.includes('[auto-tag:')) return true;
54
- if (String(pack.name || '').trim().toLowerCase().startsWith('[auto]')) return true;
60
+ if (
61
+ String(pack.name || '')
62
+ .trim()
63
+ .toLowerCase()
64
+ .startsWith('[auto]')
65
+ )
66
+ return true;
55
67
  return Boolean(String(pack.pack_theme_key || '').trim());
56
68
  };
57
69
 
@@ -225,10 +237,7 @@ export function createAutoPackCollector(options = {}) {
225
237
  }
226
238
 
227
239
  const managedAutoPacks = packs.filter((entry) => isAutoCollectorPack(entry));
228
- const preferredPack =
229
- managedAutoPacks.find((entry) => normalizeVisibility(entry?.visibility) === AUTO_PACK_TARGET_VISIBILITY)
230
- || managedAutoPacks[0]
231
- || null;
240
+ const preferredPack = managedAutoPacks.find((entry) => normalizeVisibility(entry?.visibility) === AUTO_PACK_TARGET_VISIBILITY) || managedAutoPacks[0] || null;
232
241
 
233
242
  if (preferredPack) {
234
243
  const ensuredPack = await ensureAutoPackVisibility({ ownerJid, pack: preferredPack });
@@ -4,7 +4,9 @@ import { executeQuery, TABLES } from '../../../database/index.js';
4
4
  import { normalizeDomainEventPayload } from './domainEvents.js';
5
5
 
6
6
  const normalizeStatus = (value) => {
7
- const normalized = String(value || '').trim().toLowerCase();
7
+ const normalized = String(value || '')
8
+ .trim()
9
+ .toLowerCase();
8
10
  if (['pending', 'processing', 'completed', 'failed'].includes(normalized)) return normalized;
9
11
  return null;
10
12
  };
@@ -35,12 +37,7 @@ const clampInt = (value, fallback, min, max) => {
35
37
  return Math.max(min, Math.min(max, Math.floor(numeric)));
36
38
  };
37
39
 
38
- const CLAIM_LOCK_TIMEOUT_SECONDS = clampInt(
39
- process.env.DOMAIN_EVENT_OUTBOX_LOCK_TIMEOUT_SECONDS,
40
- 15 * 60,
41
- 30,
42
- 24 * 60 * 60,
43
- );
40
+ const CLAIM_LOCK_TIMEOUT_SECONDS = clampInt(process.env.DOMAIN_EVENT_OUTBOX_LOCK_TIMEOUT_SECONDS, 15 * 60, 30, 24 * 60 * 60);
44
41
 
45
42
  const normalizeRow = (row) => {
46
43
  if (!row) return null;
@@ -88,28 +85,13 @@ export async function enqueueDomainEvent(eventPayload, connection = null) {
88
85
  priority = GREATEST(priority, VALUES(priority)),
89
86
  available_at = LEAST(available_at, VALUES(available_at)),
90
87
  updated_at = CURRENT_TIMESTAMP`,
91
- [
92
- normalized.event_type,
93
- normalized.aggregate_type,
94
- normalized.aggregate_id,
95
- JSON.stringify(normalized.payload ?? {}),
96
- normalized.priority,
97
- normalized.idempotency_key,
98
- normalized.available_at,
99
- normalized.max_attempts,
100
- ],
88
+ [normalized.event_type, normalized.aggregate_type, normalized.aggregate_id, JSON.stringify(normalized.payload ?? {}), normalized.priority, normalized.idempotency_key, normalized.available_at, normalized.max_attempts],
101
89
  connection,
102
90
  );
103
91
  return true;
104
92
  }
105
93
 
106
- export async function claimDomainEvent(
107
- {
108
- eventTypes = [],
109
- allowRetryFailed = true,
110
- } = {},
111
- connection = null,
112
- ) {
94
+ export async function claimDomainEvent({ eventTypes = [], allowRetryFailed = true } = {}, connection = null) {
113
95
  const workerToken = randomUUID();
114
96
  const statusClause = allowRetryFailed
115
97
  ? `(status = 'pending'
@@ -119,9 +101,15 @@ export async function claimDomainEvent(
119
101
  OR (status = 'processing' AND locked_at <= (UTC_TIMESTAMP() - INTERVAL ${CLAIM_LOCK_TIMEOUT_SECONDS} SECOND)))`;
120
102
 
121
103
  const normalizedTypes = Array.from(
122
- new Set((Array.isArray(eventTypes) ? eventTypes : [])
123
- .map((type) => String(type || '').trim().toUpperCase())
124
- .filter(Boolean)),
104
+ new Set(
105
+ (Array.isArray(eventTypes) ? eventTypes : [])
106
+ .map((type) =>
107
+ String(type || '')
108
+ .trim()
109
+ .toUpperCase(),
110
+ )
111
+ .filter(Boolean),
112
+ ),
125
113
  );
126
114
 
127
115
  let eventTypeClause = '';
@@ -184,18 +172,14 @@ export async function completeDomainEvent(eventId, connection = null) {
184
172
  return true;
185
173
  }
186
174
 
187
- export async function failDomainEvent(
188
- eventId,
189
- {
190
- error = null,
191
- retryDelaySeconds = 0,
192
- } = {},
193
- connection = null,
194
- ) {
175
+ export async function failDomainEvent(eventId, { error = null, retryDelaySeconds = 0 } = {}, connection = null) {
195
176
  if (!eventId) return false;
196
177
 
197
178
  const safeDelay = clampInt(retryDelaySeconds, 0, 0, 86400 * 7);
198
- const message = String(error || '').trim().slice(0, 255) || null;
179
+ const message =
180
+ String(error || '')
181
+ .trim()
182
+ .slice(0, 255) || null;
199
183
 
200
184
  await executeQuery(
201
185
  `UPDATE ${TABLES.DOMAIN_EVENT_OUTBOX}
@@ -32,16 +32,7 @@ const normalizeIdempotencyKey = (value) =>
32
32
  .replace(/[^a-zA-Z0-9_:-]/g, '')
33
33
  .slice(0, 180);
34
34
 
35
- export const normalizeDomainEventPayload = ({
36
- eventType,
37
- aggregateType,
38
- aggregateId,
39
- payload = null,
40
- priority = 50,
41
- availableAt = null,
42
- idempotencyKey = '',
43
- maxAttempts = 10,
44
- } = {}) => {
35
+ export const normalizeDomainEventPayload = ({ eventType, aggregateType, aggregateId, payload = null, priority = 50, availableAt = null, idempotencyKey = '', maxAttempts = 10 } = {}) => {
45
36
  const normalizedType = normalizeType(eventType);
46
37
  if (!normalizedType) return null;
47
38
  const normalizedAggregateType = normalizeAggregateType(aggregateType);
@@ -52,7 +43,7 @@ export const normalizeDomainEventPayload = ({
52
43
  event_type: normalizedType,
53
44
  aggregate_type: normalizedAggregateType,
54
45
  aggregate_id: normalizedAggregateId,
55
- payload: payload && typeof payload === 'object' ? payload : payload ?? null,
46
+ payload: payload && typeof payload === 'object' ? payload : (payload ?? null),
56
47
  priority: Math.max(1, Math.min(100, Number(priority) || 50)),
57
48
  available_at: availableAt ? new Date(availableAt) : null,
58
49
  idempotency_key: normalizeIdempotencyKey(idempotencyKey) || null,