@kaikybrofc/omnizap-system 2.2.9 → 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 (121) 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 +220 -0
  65. package/docs/seo/satellite-page-template.md +91 -0
  66. package/docs/seo/satellite-pages-phase1.json +349 -0
  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 +8 -3
  73. package/public/api-docs/index.html +224 -141
  74. package/public/bot-whatsapp-para-grupo/index.html +306 -0
  75. package/public/bot-whatsapp-sem-programar/index.html +306 -0
  76. package/public/comandos/index.html +428 -0
  77. package/public/como-automatizar-avisos-no-whatsapp/index.html +306 -0
  78. package/public/como-criar-comandos-whatsapp/index.html +306 -0
  79. package/public/como-evitar-spam-no-whatsapp/index.html +306 -0
  80. package/public/como-moderar-grupo-whatsapp/index.html +306 -0
  81. package/public/como-organizar-comunidade-whatsapp/index.html +306 -0
  82. package/public/css/github-project-panel.css +20 -15
  83. package/public/css/stickers-admin.css +55 -39
  84. package/public/css/styles.css +37 -29
  85. package/public/index.html +1060 -1417
  86. package/public/js/apps/apiDocsApp.js +36 -153
  87. package/public/js/apps/createPackApp.js +69 -332
  88. package/public/js/apps/homeApp.js +201 -434
  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 +507 -1366
  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 -321
  101. package/public/melhor-bot-whatsapp-para-grupos/index.html +306 -0
  102. package/public/sitemap.xml +45 -0
  103. package/public/stickers/admin/index.html +14 -19
  104. package/public/stickers/create/index.html +39 -43
  105. package/public/stickers/index.html +97 -41
  106. package/public/termos-de-uso/index.html +142 -115
  107. package/public/user/index.html +347 -346
  108. package/scripts/cache-bust.mjs +5 -24
  109. package/scripts/generate-seo-satellite-pages.mjs +431 -0
  110. package/scripts/run-prettier-all.mjs +25 -0
  111. package/scripts/sticker-catalog-loadtest.mjs +13 -11
  112. package/scripts/sticker-worker-task.mjs +1 -4
  113. package/scripts/sync-readme-snapshot.mjs +3 -2
  114. package/server/controllers/stickerCatalogController.js +407 -704
  115. package/server/http/httpServer.js +2 -10
  116. package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
  117. package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
  118. package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
  119. package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
  120. package/server/routes/stickerCatalog/catalogRouter.js +11 -13
  121. package/kaikybrofc-omnizap-system-2.2.9.tgz +0 -0
@@ -2,9 +2,7 @@ import logger from '../../utils/logger/loggerModule.js';
2
2
  import { getSystemMetrics } from '../../utils/systemMetrics/systemMetricsModule.js';
3
3
  import { sendAndStore } from '../../services/messagePersistenceService.js';
4
4
 
5
- const METRICS_ENDPOINT =
6
- process.env.METRICS_ENDPOINT ||
7
- `http://localhost:${process.env.METRICS_PORT || 9102}${process.env.METRICS_PATH || '/metrics'}`;
5
+ const METRICS_ENDPOINT = process.env.METRICS_ENDPOINT || `http://localhost:${process.env.METRICS_PORT || 9102}${process.env.METRICS_PATH || '/metrics'}`;
8
6
  const METRICS_TIMEOUT_MS = Number(process.env.METRICS_PING_TIMEOUT_MS || 1500);
9
7
 
10
8
  const formatLoadAverage = (values) => values.map((value) => value.toFixed(2)).join(' | ');
@@ -51,10 +49,7 @@ const formatStatusLevel = (status) => `${status.emoji} ${status.label}`;
51
49
 
52
50
  const padNumber = (value) => String(value).padStart(2, '0');
53
51
 
54
- const formatDateTime = (date = new Date()) =>
55
- `${padNumber(date.getDate())}/${padNumber(date.getMonth() + 1)}/${date.getFullYear()} ${padNumber(
56
- date.getHours(),
57
- )}:${padNumber(date.getMinutes())}:${padNumber(date.getSeconds())}`;
52
+ const formatDateTime = (date = new Date()) => `${padNumber(date.getDate())}/${padNumber(date.getMonth() + 1)}/${date.getFullYear()} ${padNumber(date.getHours())}:${padNumber(date.getMinutes())}:${padNumber(date.getSeconds())}`;
58
53
 
59
54
  const parseLabels = (raw) => {
60
55
  if (!raw) return {};
@@ -115,14 +110,7 @@ const getLabelValue = (series, name, labelKey) => {
115
110
  return entry ? entry.labels[labelKey] : null;
116
111
  };
117
112
 
118
- const buildPingMessage = ({
119
- systemMetrics,
120
- metricsSummary,
121
- metricsOk,
122
- metricsError,
123
- latencyMs,
124
- generatedAt,
125
- }) => {
113
+ const buildPingMessage = ({ systemMetrics, metricsSummary, metricsOk, metricsError, latencyMs, generatedAt }) => {
126
114
  const responseTime = Number.isFinite(latencyMs) ? `${Math.max(0, Math.round(latencyMs))}ms` : 'n/a';
127
115
 
128
116
  const hostCpuStatus = getStatusLevel(systemMetrics.usoCpuPercentual, 65, 85);
@@ -177,9 +165,7 @@ ${systemPart}
177
165
  const dbStatus = getStatusLevel(slowRate, 5, 15);
178
166
  const slowRateText = slowRate === null ? 'n/a' : `${slowRate.toFixed(2)}%`;
179
167
 
180
- const queueValues = [metricsSummary.queues.messages, metricsSummary.queues.chats, metricsSummary.queues.lid_map]
181
- .map((value) => parseMetricNumber(value))
182
- .filter((value) => value !== null);
168
+ const queueValues = [metricsSummary.queues.messages, metricsSummary.queues.chats, metricsSummary.queues.lid_map].map((value) => parseMetricNumber(value)).filter((value) => value !== null);
183
169
  const queuePeak = queueValues.length ? Math.max(...queueValues) : null;
184
170
  const queueStatus = getStatusLevel(queuePeak, 30, 120);
185
171
  const queuePeakText = queuePeak === null ? 'n/a' : String(Math.round(queuePeak));
@@ -252,10 +238,7 @@ ${glossaryPart}
252
238
  };
253
239
 
254
240
  const fetchMetricsSnapshot = async () => {
255
- const controller =
256
- typeof globalThis.AbortController === 'function'
257
- ? new globalThis.AbortController()
258
- : null;
241
+ const controller = typeof globalThis.AbortController === 'function' ? new globalThis.AbortController() : null;
259
242
  const timeout = setTimeout(() => controller?.abort(), METRICS_TIMEOUT_MS);
260
243
  try {
261
244
  if (typeof globalThis.fetch !== 'function') {
@@ -329,8 +312,7 @@ const fetchMetricsSnapshot = async () => {
329
312
  upsertMessages[type] = (upsertMessages[type] || 0) + entry.value;
330
313
  });
331
314
 
332
- const formatNumber = (value, digits = 2) =>
333
- Number.isFinite(value) ? value.toFixed(digits) : 'n/a';
315
+ const formatNumber = (value, digits = 2) => (Number.isFinite(value) ? value.toFixed(digits) : 'n/a');
334
316
 
335
317
  return {
336
318
  processUptime,
@@ -356,9 +338,7 @@ const fetchMetricsSnapshot = async () => {
356
338
  lastQuery: {
357
339
  messages: Number.isFinite(lastQuery.messages) ? lastQuery.messages.toFixed(2) : 'n/a',
358
340
  lid_map: Number.isFinite(lastQuery.lid_map) ? lastQuery.lid_map.toFixed(2) : 'n/a',
359
- groups_metadata: Number.isFinite(lastQuery.groups_metadata)
360
- ? lastQuery.groups_metadata.toFixed(2)
361
- : 'n/a',
341
+ groups_metadata: Number.isFinite(lastQuery.groups_metadata) ? lastQuery.groups_metadata.toFixed(2) : 'n/a',
362
342
  },
363
343
  queues: {
364
344
  messages: Math.round(queues.messages || 0),
@@ -403,19 +383,9 @@ export async function handlePingCommand({ sock, remoteJid, messageInfo, expirati
403
383
  latencyMs: Date.now() - startedAt,
404
384
  generatedAt: new Date(),
405
385
  });
406
- await sendAndStore(
407
- sock,
408
- remoteJid,
409
- { text },
410
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
411
- );
386
+ await sendAndStore(sock, remoteJid, { text }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
412
387
  } catch (error) {
413
388
  logger.error('Erro ao gerar status do sistema:', { error: error.message });
414
- await sendAndStore(
415
- sock,
416
- remoteJid,
417
- { text: 'Erro ao obter informações do sistema.' },
418
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
419
- );
389
+ await sendAndStore(sock, remoteJid, { text: 'Erro ao obter informações do sistema.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
420
390
  }
421
391
  }
@@ -22,25 +22,11 @@ const CAPTION_MAX_CHARS = 950;
22
22
 
23
23
  const TEMP_DIR = path.join(os.tmpdir(), 'omnizap-tiktok');
24
24
  const URL_REGEX = /https?:\/\/[^\s<>"']+/gi;
25
- const IMAGE_PATH_HINTS = [
26
- 'image',
27
- 'images',
28
- 'img',
29
- 'photo',
30
- 'photos',
31
- 'pic',
32
- 'pics',
33
- 'slide',
34
- 'slideshow',
35
- 'gallery',
36
- 'carousel',
37
- 'album',
38
- ];
25
+ const IMAGE_PATH_HINTS = ['image', 'images', 'img', 'photo', 'photos', 'pic', 'pics', 'slide', 'slideshow', 'gallery', 'carousel', 'album'];
39
26
  const ALBUM_KIND_HINTS = ['slide', 'album', 'image', 'images', 'photo', 'carousel'];
40
27
 
41
28
  const HEADERS = {
42
- 'User-Agent':
43
- 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
29
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
44
30
  Accept: '*/*',
45
31
  };
46
32
 
@@ -121,25 +107,9 @@ const buildCaption = ({ requestedUrl, video, tiktok, mediaType = 'video' }) => {
121
107
  const likes = formatStat(video?.stats?.likes);
122
108
  const comments = formatStat(video?.stats?.comments);
123
109
  const shares = formatStat(video?.stats?.shares);
124
- const title =
125
- mediaType === 'images'
126
- ? "┏━〔 🖼️ TikTok Imagens Sem Marca d'Água 〕━⬣"
127
- : "┏━〔 🎬 TikTok Sem Marca d'Água 〕━⬣";
128
-
129
- const lines = [
130
- title,
131
- `┃ 👤 Autor: *${author}*`,
132
- username ? `┃ 🆔 Perfil: *@${username.replace(/^@+/, '')}*` : null,
133
- `┃ ❤️ Curtidas: *${likes}*`,
134
- `┃ 💬 Comentários: *${comments}*`,
135
- `┃ 🔁 Compart.: *${shares}*`,
136
- '┗━━━━━━━━━━━━━━━⬣',
137
- '',
138
- '📝 *Descrição*',
139
- description,
140
- '',
141
- `🔗 ${requestedUrl}`,
142
- ].filter(Boolean);
110
+ const title = mediaType === 'images' ? "┏━〔 🖼️ TikTok Imagens Sem Marca d'Água 〕━⬣" : "┏━〔 🎬 TikTok Sem Marca d'Água 〕━⬣";
111
+
112
+ const lines = [title, `┃ 👤 Autor: *${author}*`, username ? `┃ 🆔 Perfil: *@${username.replace(/^@+/, '')}*` : null, `┃ ❤️ Curtidas: *${likes}*`, `┃ 💬 Comentários: *${comments}*`, `┃ 🔁 Compart.: *${shares}*`, '┗━━━━━━━━━━━━━━━⬣', '', '📝 *Descrição*', description, '', `🔗 ${requestedUrl}`].filter(Boolean);
143
113
 
144
114
  return truncate(lines.join('\n'), CAPTION_MAX_CHARS);
145
115
  };
@@ -277,14 +247,7 @@ const isAlbumPayload = (payload) => {
277
247
  const postType = payload?.tiktok?.post_type;
278
248
  const slideButtonAvailable = Boolean(payload?.download_buttons?.slide?.available);
279
249
 
280
- return (
281
- albumCount > 0 ||
282
- slideImageCount > 0 ||
283
- slideDownloadCount > 0 ||
284
- isAlbumKind(preferredKind) ||
285
- isAlbumKind(postType) ||
286
- slideButtonAvailable
287
- );
250
+ return albumCount > 0 || slideImageCount > 0 || slideDownloadCount > 0 || isAlbumKind(preferredKind) || isAlbumKind(postType) || slideButtonAvailable;
288
251
  };
289
252
 
290
253
  const hasImagePathHint = (pathSegments) => {
@@ -343,14 +306,7 @@ const collectImageCandidates = (payload) => {
343
306
  return filtered;
344
307
  };
345
308
 
346
- const sendImageCollection = async ({
347
- sock,
348
- remoteJid,
349
- messageInfo,
350
- expirationMessage,
351
- imageUrls,
352
- caption,
353
- }) => {
309
+ const sendImageCollection = async ({ sock, remoteJid, messageInfo, expirationMessage, imageUrls, caption }) => {
354
310
  const maxImages = toPositiveInt(TIKTOK_MAX_IMAGES_PER_POST, 10);
355
311
  const selected = imageUrls.slice(0, maxImages);
356
312
  const skipped = Math.max(0, imageUrls.length - selected.length);
@@ -359,10 +315,7 @@ const sendImageCollection = async ({
359
315
  for (let index = 0; index < selected.length; index += 1) {
360
316
  const imageUrl = selected[index];
361
317
  const indexLabel = `🖼️ Imagem ${index + 1}/${selected.length}`;
362
- const firstCaption =
363
- skipped > 0
364
- ? `${caption}\n\n${indexLabel}\n⚠️ Mostrando ${selected.length}/${imageUrls.length} imagens.`
365
- : `${caption}\n\n${indexLabel}`;
318
+ const firstCaption = skipped > 0 ? `${caption}\n\n${indexLabel}\n⚠️ Mostrando ${selected.length}/${imageUrls.length} imagens.` : `${caption}\n\n${indexLabel}`;
366
319
 
367
320
  try {
368
321
  await sendAndStore(
@@ -408,8 +361,7 @@ const downloadToTempFile = async (url) => {
408
361
  });
409
362
 
410
363
  const contentType = `${response.headers?.['content-type'] || ''}`.toLowerCase();
411
- const looksLikeMedia =
412
- !contentType || contentType.includes('video') || contentType.includes('octet-stream') || contentType.includes('mp4');
364
+ const looksLikeMedia = !contentType || contentType.includes('video') || contentType.includes('octet-stream') || contentType.includes('mp4');
413
365
  if (!looksLikeMedia) {
414
366
  throw new Error(`Content-Type inválido para vídeo: ${contentType || 'desconhecido'}`);
415
367
  }
@@ -471,14 +423,7 @@ const tryDownloadCandidates = async (candidates) => {
471
423
  throw new Error(detailed || 'Nenhum link de download disponível.');
472
424
  };
473
425
 
474
- const trySendDirectCandidates = async ({
475
- sock,
476
- remoteJid,
477
- messageInfo,
478
- expirationMessage,
479
- caption,
480
- candidates,
481
- }) => {
426
+ const trySendDirectCandidates = async ({ sock, remoteJid, messageInfo, expirationMessage, caption, candidates }) => {
482
427
  const failures = [];
483
428
 
484
429
  for (const candidate of candidates) {
@@ -518,26 +463,12 @@ const sendUsage = async ({ sock, remoteJid, messageInfo, expirationMessage, comm
518
463
  sock,
519
464
  remoteJid,
520
465
  {
521
- text: [
522
- '🎬 *TikTok Downloader*',
523
- '',
524
- `Uso: *${commandPrefix}tiktok <link1> [link2 ...]*`,
525
- '',
526
- `Exemplo: *${commandPrefix}tiktok https://www.tiktok.com/@usuario/video/123*`,
527
- '',
528
- "✅ Suporta múltiplos links e posts de imagem (carrossel).",
529
- ].join('\n'),
466
+ text: ['🎬 *TikTok Downloader*', '', `Uso: *${commandPrefix}tiktok <link1> [link2 ...]*`, '', `Exemplo: *${commandPrefix}tiktok https://www.tiktok.com/@usuario/video/123*`, '', '✅ Suporta múltiplos links e posts de imagem (carrossel).'].join('\n'),
530
467
  },
531
468
  { quoted: messageInfo, ephemeralExpiration: expirationMessage },
532
469
  );
533
470
 
534
- const processTikTokUrl = async ({
535
- sock,
536
- remoteJid,
537
- messageInfo,
538
- expirationMessage,
539
- inputUrl,
540
- }) => {
471
+ const processTikTokUrl = async ({ sock, remoteJid, messageInfo, expirationMessage, inputUrl }) => {
541
472
  let tempFilePath = null;
542
473
  try {
543
474
  const payload = await requestExtract(inputUrl);
@@ -675,14 +606,7 @@ const processTikTokUrl = async ({
675
606
  }
676
607
  };
677
608
 
678
- export async function handleTikTokCommand({
679
- sock,
680
- remoteJid,
681
- messageInfo,
682
- expirationMessage,
683
- text,
684
- commandPrefix = DEFAULT_COMMAND_PREFIX,
685
- }) {
609
+ export async function handleTikTokCommand({ sock, remoteJid, messageInfo, expirationMessage, text, commandPrefix = DEFAULT_COMMAND_PREFIX }) {
686
610
  const inputUrls = collectInputUrls({ text, messageInfo });
687
611
  if (!inputUrls.length) {
688
612
  await sendUsage({ sock, remoteJid, messageInfo, expirationMessage, commandPrefix });
@@ -694,19 +618,13 @@ export async function handleTikTokCommand({
694
618
  const ignoredCount = Math.max(0, inputUrls.length - urls.length);
695
619
 
696
620
  try {
697
- const startText =
698
- urls.length === 1
699
- ? "⏳ Baixando TikTok sem marca d'água, aguarde..."
700
- : `⏳ Processando ${urls.length} links do TikTok...`;
621
+ const startText = urls.length === 1 ? "⏳ Baixando TikTok sem marca d'água, aguarde..." : `⏳ Processando ${urls.length} links do TikTok...`;
701
622
 
702
623
  await sendAndStore(
703
624
  sock,
704
625
  remoteJid,
705
626
  {
706
- text:
707
- ignoredCount > 0
708
- ? `${startText}\n⚠️ Limite por comando: ${maxUrls}. Ignorados: ${ignoredCount}.`
709
- : startText,
627
+ text: ignoredCount > 0 ? `${startText}\n⚠️ Limite por comando: ${maxUrls}. Ignorados: ${ignoredCount}.` : startText,
710
628
  },
711
629
  { quoted: messageInfo, ephemeralExpiration: expirationMessage },
712
630
  );
@@ -761,23 +679,13 @@ export async function handleTikTokCommand({
761
679
  throw new Error(failureDetails || 'Nenhum link foi processado com sucesso.');
762
680
  }
763
681
 
764
- const summaryLines = [
765
- '✅ Processamento do TikTok concluído.',
766
- `• Itens enviados: ${deliveredCount}/${urls.length}`,
767
- `• Vídeos: ${videosSent}`,
768
- `• Posts de imagem: ${imagesSent}`,
769
- ];
682
+ const summaryLines = ['✅ Processamento do TikTok concluído.', `• Itens enviados: ${deliveredCount}/${urls.length}`, `• Vídeos: ${videosSent}`, `• Posts de imagem: ${imagesSent}`];
770
683
 
771
684
  if (failures.length > 0) {
772
685
  summaryLines.push(`• Falhas: ${failures.length}`);
773
686
  }
774
687
 
775
- await sendAndStore(
776
- sock,
777
- remoteJid,
778
- { text: summaryLines.join('\n') },
779
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
780
- );
688
+ await sendAndStore(sock, remoteJid, { text: summaryLines.join('\n') }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
781
689
  }
782
690
  } catch (error) {
783
691
  logger.error('tiktok: falha ao processar comando.', {
@@ -979,86 +979,7 @@ const withVerticalSpacing = (lines = []) => lines.flatMap((line, index) => (inde
979
979
  * @param {object} data Dados agregados do usuário para renderização.
980
980
  * @returns {string} Texto completo enviado no comando de perfil.
981
981
  */
982
- const buildProfileMessage = ({
983
- mentionLabel,
984
- displayName,
985
- phone,
986
- canonicalTarget,
987
- status,
988
- firstMessage,
989
- tempoDeCasa,
990
- lastInteraction,
991
- diasSemFalar,
992
- totalMessages,
993
- rankingLabel,
994
- trendLabel,
995
- avgPerDay,
996
- activeDays,
997
- streakDays,
998
- activeHourLabel,
999
- favoriteTypeLabel,
1000
- dominantTypeByPeriodLabel,
1001
- socialScore,
1002
- socialSent,
1003
- socialReceived,
1004
- responseRateLabel,
1005
- socialPartners,
1006
- topPartnerLabel,
1007
- topPartnersLabel,
1008
- topGroupsLabel,
1009
- globalShareLabel,
1010
- groupShareLabel,
1011
- tags,
1012
- }) =>
1013
- [
1014
- '👤 *PERFIL DO USUÁRIO*',
1015
- '━━━━━━━━━━━━━━━━━━━━',
1016
- '',
1017
- '🧾 *Identificação*',
1018
- ...withVerticalSpacing([
1019
- `• Usuário: ${mentionLabel}`,
1020
- `• Nome: ${displayName}`,
1021
- `• Número: ${phone}`,
1022
- `• ID: ${canonicalTarget || 'N/D'}`,
1023
- `• Status: *${status}*`,
1024
- ]),
1025
- '',
1026
- '📈 *Mensagens e Ranking*',
1027
- ...withVerticalSpacing([
1028
- `• Primeira mensagem: ${firstMessage}`,
1029
- `• Tempo de casa no bot: ${tempoDeCasa}`,
1030
- `• Última interação: ${lastInteraction}`,
1031
- `• Dias sem falar: ${diasSemFalar}`,
1032
- `• Mensagens gerais registradas: ${totalMessages}`,
1033
- `• Participação global: ${globalShareLabel}`,
1034
- `• Participação no grupo atual: ${groupShareLabel}`,
1035
- `• Posição no ranking (mensagens): ${rankingLabel}`,
1036
- `• Tendência de mensagens: ${trendLabel}`,
1037
- `• Média/dia (global): ${avgPerDay}`,
1038
- `• Dias ativos (global): ${activeDays}`,
1039
- `• Streak (global): ${streakDays} dia(s)`,
1040
- `• Horário mais ativo: ${activeHourLabel}`,
1041
- `• Tipo favorito (global): ${favoriteTypeLabel}`,
1042
- `• Tipo dominante por período: ${dominantTypeByPeriodLabel}`,
1043
- ]),
1044
- '',
1045
- '🌐 *Interações Sociais*',
1046
- ...withVerticalSpacing([
1047
- `• Interações sociais (${SOCIAL_RECENT_DAYS}d): ${socialScore}`,
1048
- `• Respostas enviadas (${SOCIAL_RECENT_DAYS}d): ${socialSent}`,
1049
- `• Respostas recebidas (${SOCIAL_RECENT_DAYS}d): ${socialReceived}`,
1050
- `• Taxa de resposta (${SOCIAL_RECENT_DAYS}d): ${responseRateLabel}`,
1051
- `• Parceiros sociais (${SOCIAL_RECENT_DAYS}d): ${socialPartners}`,
1052
- `• Parceiro principal (${SOCIAL_RECENT_DAYS}d): ${topPartnerLabel}`,
1053
- `• Top 3 parceiros (${SOCIAL_RECENT_DAYS}d):\n${topPartnersLabel}`,
1054
- ]),
1055
- '',
1056
- '🏘️ *Presença em Grupos*',
1057
- ...withVerticalSpacing([`• Top grupos onde fala:\n${topGroupsLabel}`]),
1058
- '',
1059
- '🏷️ *Contexto*',
1060
- ...withVerticalSpacing([`• Tags: ${tags.length ? tags.join(', ') : 'sem tags'}`]),
1061
- ].join('\n');
982
+ const buildProfileMessage = ({ mentionLabel, displayName, phone, canonicalTarget, status, firstMessage, tempoDeCasa, lastInteraction, diasSemFalar, totalMessages, rankingLabel, trendLabel, avgPerDay, activeDays, streakDays, activeHourLabel, favoriteTypeLabel, dominantTypeByPeriodLabel, socialScore, socialSent, socialReceived, responseRateLabel, socialPartners, topPartnerLabel, topPartnersLabel, topGroupsLabel, globalShareLabel, groupShareLabel, tags }) => ['👤 *PERFIL DO USUÁRIO*', '━━━━━━━━━━━━━━━━━━━━', '', '🧾 *Identificação*', ...withVerticalSpacing([`• Usuário: ${mentionLabel}`, `• Nome: ${displayName}`, `• Número: ${phone}`, `• ID: ${canonicalTarget || 'N/D'}`, `• Status: *${status}*`]), '', '📈 *Mensagens e Ranking*', ...withVerticalSpacing([`• Primeira mensagem: ${firstMessage}`, `• Tempo de casa no bot: ${tempoDeCasa}`, `• Última interação: ${lastInteraction}`, `• Dias sem falar: ${diasSemFalar}`, `• Mensagens gerais registradas: ${totalMessages}`, `• Participação global: ${globalShareLabel}`, `• Participação no grupo atual: ${groupShareLabel}`, `• Posição no ranking (mensagens): ${rankingLabel}`, `• Tendência de mensagens: ${trendLabel}`, `• Média/dia (global): ${avgPerDay}`, `• Dias ativos (global): ${activeDays}`, `• Streak (global): ${streakDays} dia(s)`, `• Horário mais ativo: ${activeHourLabel}`, `• Tipo favorito (global): ${favoriteTypeLabel}`, `• Tipo dominante por período: ${dominantTypeByPeriodLabel}`]), '', '🌐 *Interações Sociais*', ...withVerticalSpacing([`• Interações sociais (${SOCIAL_RECENT_DAYS}d): ${socialScore}`, `• Respostas enviadas (${SOCIAL_RECENT_DAYS}d): ${socialSent}`, `• Respostas recebidas (${SOCIAL_RECENT_DAYS}d): ${socialReceived}`, `• Taxa de resposta (${SOCIAL_RECENT_DAYS}d): ${responseRateLabel}`, `• Parceiros sociais (${SOCIAL_RECENT_DAYS}d): ${socialPartners}`, `• Parceiro principal (${SOCIAL_RECENT_DAYS}d): ${topPartnerLabel}`, `• Top 3 parceiros (${SOCIAL_RECENT_DAYS}d):\n${topPartnersLabel}`]), '', '🏘️ *Presença em Grupos*', ...withVerticalSpacing([`• Top grupos onde fala:\n${topGroupsLabel}`]), '', '🏷️ *Contexto*', ...withVerticalSpacing([`• Tags: ${tags.length ? tags.join(', ') : 'sem tags'}`])].join('\n');
1062
983
 
1063
984
  /**
1064
985
  * Seleciona o primeiro ID de usuário válido dentro de uma lista.
@@ -1106,14 +1027,7 @@ export async function handleUserCommand({ sock, remoteJid, messageInfo, expirati
1106
1027
  const senderCanonical = resolveUserIdCached({ jid: senderJid, lid: senderJid, participantAlt: null });
1107
1028
  const rankingTargetId = mentionJid || canonicalTarget;
1108
1029
 
1109
- const [stats, ranking, latestPushName, premiumUsers, blocked, groupAdmin] = await Promise.all([
1110
- fetchUserStats({ canonicalId: rankingTargetId, senderIds: normalizedTargetIds }),
1111
- fetchUserRanking(rankingTargetId),
1112
- fetchLatestPushName(normalizedTargetIds),
1113
- premiumUserStore.getPremiumUsers(),
1114
- isTargetBlocked(sock, normalizedTargetIds),
1115
- isGroupMessage ? isUserAdmin(remoteJid, mentionJid || canonicalTarget) : Promise.resolve(false),
1116
- ]);
1030
+ const [stats, ranking, latestPushName, premiumUsers, blocked, groupAdmin] = await Promise.all([fetchUserStats({ canonicalId: rankingTargetId, senderIds: normalizedTargetIds }), fetchUserRanking(rankingTargetId), fetchLatestPushName(normalizedTargetIds), premiumUserStore.getPremiumUsers(), isTargetBlocked(sock, normalizedTargetIds), isGroupMessage ? isUserAdmin(remoteJid, mentionJid || canonicalTarget) : Promise.resolve(false)]);
1117
1031
  const [globalInsights, socialInsights, trendInsights, activeHourInsights, dominantTypeByPeriod, topGroups, participationInsights] = await Promise.all([
1118
1032
  fetchUserGlobalRankingInsights({
1119
1033
  canonicalId: rankingTargetId,
@@ -42,7 +42,9 @@ const normalizeLabel = (value, fallback = 'unknown') => {
42
42
  };
43
43
 
44
44
  const normalizeHttpMethod = (method) => {
45
- const normalized = String(method || '').trim().toUpperCase();
45
+ const normalized = String(method || '')
46
+ .trim()
47
+ .toUpperCase();
46
48
  if (!normalized) return 'UNKNOWN';
47
49
  if (normalized.length > 12) return normalized.slice(0, 12);
48
50
  return normalized;
@@ -700,14 +702,7 @@ export const recordSocialXpCapHit = ({ scope = 'earn' } = {}) => {
700
702
  m.socialXpCapHitsTotal.inc({ scope: normalizeLabel(scope, 'earn') });
701
703
  };
702
704
 
703
- export const recordStickerAutoPackCycle = ({
704
- durationMs,
705
- assetsScanned = 0,
706
- assetsAdded = 0,
707
- duplicateRate = null,
708
- rejectionRate = null,
709
- fillRate = null,
710
- } = {}) => {
705
+ export const recordStickerAutoPackCycle = ({ durationMs, assetsScanned = 0, assetsAdded = 0, duplicateRate = null, rejectionRate = null, fillRate = null } = {}) => {
711
706
  const m = ensureMetrics();
712
707
  if (!m) return;
713
708
 
@@ -742,13 +737,7 @@ export const recordStickerAutoPackCycle = ({
742
737
  }
743
738
  };
744
739
 
745
- export const recordStickerClassificationCycle = ({
746
- status = 'ok',
747
- durationMs,
748
- processed = 0,
749
- classified = 0,
750
- failed = 0,
751
- } = {}) => {
740
+ export const recordStickerClassificationCycle = ({ status = 'ok', durationMs, processed = 0, classified = 0, failed = 0 } = {}) => {
752
741
  const m = ensureMetrics();
753
742
  if (!m) return;
754
743
 
@@ -11,12 +11,7 @@ const CAPTCHA_OK_EMOJI = process.env.CAPTCHA_OK_EMOJI || '✅';
11
11
 
12
12
  const pendingCaptchas = new Map();
13
13
  const captchaMessageState = new Map();
14
- const NON_HUMAN_CAPTCHA_MESSAGE_TEXTS = new Set([
15
- 'Mensagem vazia',
16
- 'Tipo de mensagem não suportado ou sem conteúdo.',
17
- '[Histórico de mensagens]',
18
- '[Aviso de histórico de mensagens]',
19
- ]);
14
+ const NON_HUMAN_CAPTCHA_MESSAGE_TEXTS = new Set(['Mensagem vazia', 'Tipo de mensagem não suportado ou sem conteúdo.', '[Histórico de mensagens]', '[Aviso de histórico de mensagens]']);
20
15
 
21
16
  const buildMessageStateKey = (groupId, messageId) => `${groupId}:${messageId}`;
22
17
  const normalizeMessageText = (messageText) => (typeof messageText === 'string' ? messageText.trim() : '');
@@ -23,10 +23,7 @@ const parseNumber = (value, fallback) => {
23
23
  *
24
24
  * @type {number}
25
25
  */
26
- const FLUSH_INTERVAL_MS = Math.min(
27
- 3000,
28
- Math.max(1000, parseNumber(process.env.DB_WRITE_FLUSH_MS, 1500)),
29
- );
26
+ const FLUSH_INTERVAL_MS = Math.min(3000, Math.max(1000, parseNumber(process.env.DB_WRITE_FLUSH_MS, 1500)));
30
27
 
31
28
  /**
32
29
  * Tamanho máximo do batch de mensagens por INSERT.
@@ -56,10 +53,7 @@ const CHAT_COOLDOWN_MS = Math.max(1000, Math.floor(parseNumber(process.env.DB_CH
56
53
  *
57
54
  * @type {number}
58
55
  */
59
- const MESSAGE_QUEUE_MAX = Math.max(
60
- MESSAGE_BATCH_SIZE * 5,
61
- Math.floor(parseNumber(process.env.DB_MESSAGE_QUEUE_MAX, 5000)),
62
- );
56
+ const MESSAGE_QUEUE_MAX = Math.max(MESSAGE_BATCH_SIZE * 5, Math.floor(parseNumber(process.env.DB_MESSAGE_QUEUE_MAX, 5000)));
63
57
 
64
58
  /**
65
59
  * Regex de erro para payload JSON inválido no MySQL.
@@ -85,7 +79,6 @@ const messageQueue = [];
85
79
  */
86
80
  const messagePendingIds = new Set();
87
81
 
88
-
89
82
  /**
90
83
  * Fila (por chat.id) com atualizações pendentes de chats.
91
84
  * @type {Map<string, {id:string, name:(string|null), raw:(Object|null), rawHash:string, queuedAt:number, nextAllowedAt:number}>}
@@ -106,7 +99,6 @@ const chatQueue = new Map();
106
99
  */
107
100
  const chatCache = new Map();
108
101
 
109
-
110
102
  /**
111
103
  * Indica se já há um flush agendado via setImmediate.
112
104
  * @type {boolean}
@@ -237,14 +229,7 @@ const insertMessageBatch = async (batch) => {
237
229
  const placeholders = buildPlaceholders(batch.length, 6);
238
230
  const params = [];
239
231
  for (const message of batch) {
240
- params.push(
241
- message.message_id,
242
- message.chat_id,
243
- message.sender_id,
244
- message.content,
245
- message.raw_message,
246
- message.timestamp,
247
- );
232
+ params.push(message.message_id, message.chat_id, message.sender_id, message.content, message.raw_message, message.timestamp);
248
233
  }
249
234
 
250
235
  const sql = `INSERT IGNORE INTO ${TABLES.MESSAGES}
@@ -3,10 +3,7 @@ import { createHash } from 'node:crypto';
3
3
  import logger from '../utils/logger/loggerModule.js';
4
4
  import { executeQuery, TABLES } from '../../database/index.js';
5
5
 
6
- const FEATURE_FLAG_CACHE_TTL_MS = Math.max(
7
- 5_000,
8
- Number(process.env.FEATURE_FLAG_CACHE_TTL_MS) || 30_000,
9
- );
6
+ const FEATURE_FLAG_CACHE_TTL_MS = Math.max(5_000, Number(process.env.FEATURE_FLAG_CACHE_TTL_MS) || 30_000);
10
7
 
11
8
  let cacheState = {
12
9
  loadedAt: 0,
@@ -106,10 +103,7 @@ const resolveEnvFallback = (flagName, fallback) => {
106
103
  return toBool(process.env[envKey], fallback);
107
104
  };
108
105
 
109
- export const isFeatureEnabled = async (
110
- flagName,
111
- { fallback = false, subjectKey = '' } = {},
112
- ) => {
106
+ export const isFeatureEnabled = async (flagName, { fallback = false, subjectKey = '' } = {}) => {
113
107
  const normalizedFlagName = normalizeFlagName(flagName);
114
108
  if (!normalizedFlagName) return Boolean(fallback);
115
109
 
@@ -153,7 +153,6 @@ const scheduleNextRun = (groupId, delayMs) => {
153
153
  }, delayMs);
154
154
  };
155
155
 
156
-
157
156
  const stopGroupLoopInternal = (groupId) => {
158
157
  const state = groupLoops.get(groupId);
159
158
  if (!state) return;
@@ -4,8 +4,7 @@
4
4
  * @param {number} cols
5
5
  * @returns {string}
6
6
  */
7
- export const buildPlaceholders = (rows, cols) =>
8
- Array.from({ length: rows }, () => `(${Array(cols).fill('?').join(', ')})`).join(', ');
7
+ export const buildPlaceholders = (rows, cols) => Array.from({ length: rows }, () => `(${Array(cols).fill('?').join(', ')})`).join(', ');
9
8
 
10
9
  /**
11
10
  * Cria placeholders repetindo um template por linha.
@@ -13,8 +12,7 @@ export const buildPlaceholders = (rows, cols) =>
13
12
  * @param {string} rowTemplate
14
13
  * @returns {string}
15
14
  */
16
- export const buildRowPlaceholders = (rows, rowTemplate) =>
17
- Array.from({ length: rows }, () => rowTemplate).join(', ');
15
+ export const buildRowPlaceholders = (rows, rowTemplate) => Array.from({ length: rows }, () => rowTemplate).join(', ');
18
16
 
19
17
  /**
20
18
  * Cria um executor de flush com controle de concorrencia e re-try imediato.
@@ -45,8 +45,12 @@ function buildHintSignature(phoneDigits, tsSeconds) {
45
45
  }
46
46
 
47
47
  function safeHexCompare(left, right) {
48
- const leftHex = String(left || '').trim().toLowerCase();
49
- const rightHex = String(right || '').trim().toLowerCase();
48
+ const leftHex = String(left || '')
49
+ .trim()
50
+ .toLowerCase();
51
+ const rightHex = String(right || '')
52
+ .trim()
53
+ .toLowerCase();
50
54
  if (!leftHex || !rightHex || leftHex.length !== rightHex.length) return false;
51
55
 
52
56
  try {
@@ -60,13 +64,7 @@ function safeHexCompare(left, right) {
60
64
  }
61
65
 
62
66
  function resolveLoginBaseUrl(explicitBaseUrl = '') {
63
- const candidates = [
64
- explicitBaseUrl,
65
- process.env.WHATSAPP_LOGIN_BASE_URL,
66
- process.env.SITE_ORIGIN,
67
- process.env.PUBLIC_WEB_BASE_URL,
68
- DEFAULT_LOGIN_BASE_URL,
69
- ];
67
+ const candidates = [explicitBaseUrl, process.env.WHATSAPP_LOGIN_BASE_URL, process.env.SITE_ORIGIN, process.env.PUBLIC_WEB_BASE_URL, DEFAULT_LOGIN_BASE_URL];
70
68
 
71
69
  for (const candidate of candidates) {
72
70
  const raw = String(candidate || '').trim();
@@ -2,8 +2,7 @@ import groupConfigStore from './groupConfigStore.js';
2
2
 
3
3
  const PREMIUM_CONFIG_ID = 'system:premium_users';
4
4
 
5
- const normalizeList = (list) =>
6
- Array.from(new Set((Array.isArray(list) ? list : []).filter(Boolean)));
5
+ const normalizeList = (list) => Array.from(new Set((Array.isArray(list) ? list : []).filter(Boolean)));
7
6
 
8
7
  const premiumUserStore = {
9
8
  getPremiumUsers: async function () {