@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
@@ -14,37 +14,21 @@ import { getAdminJid } from '../../config/adminIdentity.js';
14
14
 
15
15
  const adminJid = getAdminJid();
16
16
  const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
17
- const YTDLS_BASE_URL = (
18
- process.env.YTDLS_BASE_URL ||
19
- process.env.YT_DLS_BASE_URL ||
20
- 'http://127.0.0.1:3000'
21
- ).replace(/\/$/, '');
17
+ const YTDLS_BASE_URL = (process.env.YTDLS_BASE_URL || process.env.YT_DLS_BASE_URL || 'http://127.0.0.1:3000').replace(/\/$/, '');
22
18
 
23
19
  const DEFAULT_TIMEOUT_MS = Number.parseInt(process.env.PLAY_API_TIMEOUT_MS || '900000', 10);
24
- const DOWNLOAD_API_TIMEOUT_MS = Number.parseInt(
25
- process.env.PLAY_API_DOWNLOAD_TIMEOUT_MS || '1800000',
26
- 10,
27
- );
28
- const QUEUE_STATUS_TIMEOUT_MS = Number.parseInt(
29
- process.env.PLAY_QUEUE_STATUS_TIMEOUT_MS || '8000',
30
- 10,
31
- );
20
+ const DOWNLOAD_API_TIMEOUT_MS = Number.parseInt(process.env.PLAY_API_DOWNLOAD_TIMEOUT_MS || '1800000', 10);
21
+ const QUEUE_STATUS_TIMEOUT_MS = Number.parseInt(process.env.PLAY_QUEUE_STATUS_TIMEOUT_MS || '8000', 10);
32
22
 
33
23
  const MAX_MEDIA_MB = Number.parseInt(process.env.PLAY_MAX_MB || '100', 10);
34
- const MAX_MEDIA_BYTES = Number.isFinite(MAX_MEDIA_MB)
35
- ? MAX_MEDIA_MB * 1024 * 1024
36
- : 100 * 1024 * 1024;
24
+ const MAX_MEDIA_BYTES = Number.isFinite(MAX_MEDIA_MB) ? MAX_MEDIA_MB * 1024 * 1024 : 100 * 1024 * 1024;
37
25
  const MAX_MEDIA_MB_LABEL = Number.isFinite(MAX_MEDIA_MB) ? MAX_MEDIA_MB : 100;
38
26
 
39
27
  const QUICK_QUEUE_LOOKUP_MS = 1500;
40
28
  const THUMBNAIL_TIMEOUT_MS = 15000;
41
29
  const MAX_THUMB_BYTES = 5 * 1024 * 1024;
42
- const VIDEO_PROCESS_TIMEOUT_MS = Number.parseInt(
43
- process.env.PLAY_VIDEO_PROCESS_TIMEOUT_MS || '420000',
44
- 10,
45
- );
46
- const VIDEO_FORCE_TRANSCODE =
47
- String(process.env.PLAY_VIDEO_FORCE_TRANSCODE || 'true').toLowerCase() !== 'false';
30
+ const VIDEO_PROCESS_TIMEOUT_MS = Number.parseInt(process.env.PLAY_VIDEO_PROCESS_TIMEOUT_MS || '420000', 10);
31
+ const VIDEO_FORCE_TRANSCODE = String(process.env.PLAY_VIDEO_FORCE_TRANSCODE || 'true').toLowerCase() !== 'false';
48
32
  const FFMPEG_BIN = (process.env.FFMPEG_PATH || 'ffmpeg').trim();
49
33
  const FFPROBE_BIN = (process.env.FFPROBE_PATH || 'ffprobe').trim();
50
34
  const SEARCH_CACHE_TTL_MS = 60 * 1000;
@@ -104,8 +88,7 @@ const withErrorMeta = (error, meta) => {
104
88
  return error;
105
89
  };
106
90
 
107
- const isAbortError = (error) =>
108
- error?.name === 'AbortError' || error?.code === 'ABORT_ERR' || error?.code === 'ECONNABORTED';
91
+ const isAbortError = (error) => error?.name === 'AbortError' || error?.code === 'ABORT_ERR' || error?.code === 'ECONNABORTED';
109
92
 
110
93
  const normalizeRequestError = (error, { timeoutMessage, fallbackMessage, fallbackCode }) => {
111
94
  if (KNOWN_ERROR_CODES.has(error?.code) && error?.message) return error;
@@ -207,16 +190,7 @@ const formatVideoInfo = (videoInfo) => {
207
190
  const getThumbnailUrl = (videoInfo) => {
208
191
  if (!videoInfo || typeof videoInfo !== 'object') return null;
209
192
 
210
- const direct = pickFirstString(videoInfo, [
211
- 'thumbnail',
212
- 'thumb',
213
- 'thumbnail_url',
214
- 'thumbnailUrl',
215
- 'thumb_url',
216
- 'image',
217
- 'cover',
218
- 'artwork',
219
- ]);
193
+ const direct = pickFirstString(videoInfo, ['thumbnail', 'thumb', 'thumbnail_url', 'thumbnailUrl', 'thumb_url', 'image', 'cover', 'artwork']);
220
194
  const directUrl = ensureHttpUrl(direct);
221
195
  if (directUrl) return directUrl;
222
196
 
@@ -304,10 +278,7 @@ const getHeaderValue = (headers, key) => {
304
278
  return normalizeHeaderValue(raw);
305
279
  };
306
280
 
307
- const hasHeader = (headers, name) =>
308
- headers && typeof headers === 'object'
309
- ? Object.keys(headers).some((headerName) => headerName.toLowerCase() === name.toLowerCase())
310
- : false;
281
+ const hasHeader = (headers, name) => (headers && typeof headers === 'object' ? Object.keys(headers).some((headerName) => headerName.toLowerCase() === name.toLowerCase()) : false);
311
282
 
312
283
  const normalizeMimeType = (value) => {
313
284
  if (typeof value !== 'string' || !value.trim()) return null;
@@ -319,15 +290,11 @@ const resolveMediaMimeType = (type, contentType) => {
319
290
  const normalized = normalizeMimeType(contentType);
320
291
 
321
292
  if (type === 'audio') {
322
- return normalized && normalized.startsWith('audio/')
323
- ? normalized
324
- : TYPE_CONFIG.audio.mimeFallback;
293
+ return normalized && normalized.startsWith('audio/') ? normalized : TYPE_CONFIG.audio.mimeFallback;
325
294
  }
326
295
 
327
296
  if (type === 'video') {
328
- return normalized && normalized.startsWith('video/')
329
- ? normalized
330
- : TYPE_CONFIG.video.mimeFallback;
297
+ return normalized && normalized.startsWith('video/') ? normalized : TYPE_CONFIG.video.mimeFallback;
331
298
  }
332
299
 
333
300
  return normalized || 'application/octet-stream';
@@ -402,10 +369,7 @@ const runBinaryCommand = (command, args, { timeoutMs = VIDEO_PROCESS_TIMEOUT_MS
402
369
  });
403
370
  });
404
371
 
405
- const normalizeBinaryError = (
406
- error,
407
- { timeoutMessage, fallbackMessage, endpoint, requestId, command, outputPath },
408
- ) => {
372
+ const normalizeBinaryError = (error, { timeoutMessage, fallbackMessage, endpoint, requestId, command, outputPath }) => {
409
373
  if (KNOWN_ERROR_CODES.has(error?.code) && error?.message) return error;
410
374
  if (error?.code === 'ETIMEDOUT') {
411
375
  return createError(ERROR_CODES.TIMEOUT, timeoutMessage, {
@@ -429,14 +393,7 @@ const normalizeBinaryError = (
429
393
 
430
394
  const probeVideoStreams = async (filePath, requestId, endpoint) => {
431
395
  try {
432
- const result = await runBinaryCommand(FFPROBE_BIN, [
433
- '-v',
434
- 'error',
435
- '-print_format',
436
- 'json',
437
- '-show_streams',
438
- filePath,
439
- ]);
396
+ const result = await runBinaryCommand(FFPROBE_BIN, ['-v', 'error', '-print_format', 'json', '-show_streams', filePath]);
440
397
  const parsed = JSON.parse(result.stdout || '{}');
441
398
  const streams = Array.isArray(parsed?.streams) ? parsed.streams : [];
442
399
  const videoStream = streams.find((stream) => stream?.codec_type === 'video') || null;
@@ -466,36 +423,7 @@ const transcodeVideoForWhatsapp = async (filePath, requestId, endpoint) => {
466
423
  try {
467
424
  await safeUnlink(outputPath);
468
425
 
469
- await runBinaryCommand(
470
- FFMPEG_BIN,
471
- [
472
- '-y',
473
- '-i',
474
- filePath,
475
- '-map',
476
- '0:v:0',
477
- '-map',
478
- '0:a:0?',
479
- '-c:v',
480
- 'libx264',
481
- '-preset',
482
- 'veryfast',
483
- '-pix_fmt',
484
- 'yuv420p',
485
- '-movflags',
486
- '+faststart',
487
- '-c:a',
488
- 'aac',
489
- '-b:a',
490
- '128k',
491
- '-ar',
492
- '44100',
493
- '-ac',
494
- '2',
495
- outputPath,
496
- ],
497
- { timeoutMs: VIDEO_PROCESS_TIMEOUT_MS },
498
- );
426
+ await runBinaryCommand(FFMPEG_BIN, ['-y', '-i', filePath, '-map', '0:v:0', '-map', '0:a:0?', '-c:v', 'libx264', '-preset', 'veryfast', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', '-c:a', 'aac', '-b:a', '128k', '-ar', '44100', '-ac', '2', outputPath], { timeoutMs: VIDEO_PROCESS_TIMEOUT_MS });
499
427
 
500
428
  const stats = await fs.promises.stat(outputPath);
501
429
  const transcodedBytes = Number(stats?.size || 0);
@@ -509,15 +437,11 @@ const transcodeVideoForWhatsapp = async (filePath, requestId, endpoint) => {
509
437
  }
510
438
 
511
439
  if (transcodedBytes > MAX_MEDIA_BYTES) {
512
- throw createError(
513
- ERROR_CODES.TOO_BIG,
514
- `O arquivo excede o limite permitido de ${MAX_MEDIA_MB_LABEL} MB.`,
515
- {
516
- endpoint,
517
- requestId,
518
- bytes: transcodedBytes,
519
- },
520
- );
440
+ throw createError(ERROR_CODES.TOO_BIG, `O arquivo excede o limite permitido de ${MAX_MEDIA_MB_LABEL} MB.`, {
441
+ endpoint,
442
+ requestId,
443
+ bytes: transcodedBytes,
444
+ });
521
445
  }
522
446
 
523
447
  await fs.promises.rename(outputPath, filePath);
@@ -538,8 +462,7 @@ const transcodeVideoForWhatsapp = async (filePath, requestId, endpoint) => {
538
462
 
539
463
  const resolveHttpModule = (urlObj) => (urlObj.protocol === 'https:' ? https : http);
540
464
 
541
- const shouldFollowRedirect = (status, location, redirectCount, maxRedirects) =>
542
- status >= 300 && status < 400 && Boolean(location) && redirectCount < maxRedirects;
465
+ const shouldFollowRedirect = (status, location, redirectCount, maxRedirects) => status >= 300 && status < 400 && Boolean(location) && redirectCount < maxRedirects;
543
466
 
544
467
  const preparePayload = (body, headers) => {
545
468
  if (body === null || body === undefined) return null;
@@ -567,11 +490,7 @@ const readResponseBuffer = async (stream, { maxBytes = Infinity, tooBigMessage }
567
490
 
568
491
  if (Number.isFinite(maxBytes) && total > maxBytes) {
569
492
  stream.destroy();
570
- throw createError(
571
- ERROR_CODES.TOO_BIG,
572
- tooBigMessage || 'Conteúdo excede o limite permitido.',
573
- { bytes: total },
574
- );
493
+ throw createError(ERROR_CODES.TOO_BIG, tooBigMessage || 'Conteúdo excede o limite permitido.', { bytes: total });
575
494
  }
576
495
 
577
496
  chunks.push(current);
@@ -655,19 +574,7 @@ const createByteLimitTransform = (maxBytes, tooBigMessage) => {
655
574
  };
656
575
  };
657
576
 
658
- const httpRequest = ({
659
- method,
660
- url,
661
- headers = {},
662
- body = null,
663
- timeoutMs = DEFAULT_TIMEOUT_MS,
664
- maxRedirects = 0,
665
- redirectCount = 0,
666
- endpoint = 'unknown',
667
- timeoutMessage = 'Timeout ao comunicar com a API yt-dls.',
668
- fallbackMessage = 'Falha ao comunicar com a API yt-dls.',
669
- onResponse,
670
- }) =>
577
+ const httpRequest = ({ method, url, headers = {}, body = null, timeoutMs = DEFAULT_TIMEOUT_MS, maxRedirects = 0, redirectCount = 0, endpoint = 'unknown', timeoutMessage = 'Timeout ao comunicar com a API yt-dls.', fallbackMessage = 'Falha ao comunicar com a API yt-dls.', onResponse }) =>
671
578
  new Promise((resolve, reject) => {
672
579
  const urlObj = new URL(url);
673
580
  const requestHeaders = { ...headers };
@@ -799,12 +706,7 @@ const requestJson = async ({ method, url, body = null, timeoutMs = DEFAULT_TIMEO
799
706
  },
800
707
  });
801
708
 
802
- const requestBuffer = async ({
803
- url,
804
- timeoutMs = THUMBNAIL_TIMEOUT_MS,
805
- maxBytes = MAX_THUMB_BYTES,
806
- endpoint = YTDLS_ENDPOINTS.thumbnail,
807
- }) =>
709
+ const requestBuffer = async ({ url, timeoutMs = THUMBNAIL_TIMEOUT_MS, maxBytes = MAX_THUMB_BYTES, endpoint = YTDLS_ENDPOINTS.thumbnail }) =>
808
710
  httpRequest({
809
711
  method: 'GET',
810
712
  url,
@@ -891,9 +793,7 @@ const pruneSearchCache = () => {
891
793
  return;
892
794
  }
893
795
 
894
- const ordered = [...searchCache.entries()].sort(
895
- (a, b) => (a[1]?.createdAt || 0) - (b[1]?.createdAt || 0),
896
- );
796
+ const ordered = [...searchCache.entries()].sort((a, b) => (a[1]?.createdAt || 0) - (b[1]?.createdAt || 0));
897
797
  const toRemove = searchCache.size - MAX_SEARCH_CACHE_ENTRIES;
898
798
  for (let i = 0; i < toRemove; i += 1) {
899
799
  searchCache.delete(ordered[i][0]);
@@ -935,11 +835,7 @@ const buildYtdlsUrl = (endpoint, queryParams = null) => {
935
835
  const fetchSearchResult = async (query) => {
936
836
  const normalized = typeof query === 'string' ? query.trim() : '';
937
837
  if (!normalized) {
938
- throw createError(
939
- ERROR_CODES.INVALID_INPUT,
940
- 'Você precisa informar um link do YouTube ou termo de busca.',
941
- { endpoint: YTDLS_ENDPOINTS.search },
942
- );
838
+ throw createError(ERROR_CODES.INVALID_INPUT, 'Você precisa informar um link do YouTube ou termo de busca.', { endpoint: YTDLS_ENDPOINTS.search });
943
839
  }
944
840
 
945
841
  const cacheKey = normalized.toLowerCase();
@@ -960,11 +856,7 @@ const fetchSearchResult = async (query) => {
960
856
  });
961
857
 
962
858
  if (!payload?.sucesso) {
963
- throw createError(
964
- ERROR_CODES.API,
965
- payload?.mensagem || 'Não foi possível buscar o vídeo agora.',
966
- { endpoint },
967
- );
859
+ throw createError(ERROR_CODES.API, payload?.mensagem || 'Não foi possível buscar o vídeo agora.', { endpoint });
968
860
  }
969
861
 
970
862
  return payload;
@@ -991,11 +883,7 @@ const resolveYoutubeLink = async (query) => {
991
883
  const normalized = query ? query.trim() : '';
992
884
 
993
885
  if (!normalized) {
994
- throw createError(
995
- ERROR_CODES.INVALID_INPUT,
996
- 'Você precisa informar um link do YouTube ou termo de busca.',
997
- { endpoint: YTDLS_ENDPOINTS.search },
998
- );
886
+ throw createError(ERROR_CODES.INVALID_INPUT, 'Você precisa informar um link do YouTube ou termo de busca.', { endpoint: YTDLS_ENDPOINTS.search });
999
887
  }
1000
888
 
1001
889
  if (/^https?:\/\//i.test(normalized)) {
@@ -1092,15 +980,11 @@ const requestDownloadToFile = async (link, type, requestId) => {
1092
980
 
1093
981
  if (contentLength !== null && contentLength > MAX_MEDIA_BYTES) {
1094
982
  res.resume();
1095
- throw createError(
1096
- ERROR_CODES.TOO_BIG,
1097
- `O arquivo excede o limite permitido de ${MAX_MEDIA_MB_LABEL} MB.`,
1098
- {
1099
- endpoint: currentEndpoint,
1100
- status,
1101
- bytes: contentLength,
1102
- },
1103
- );
983
+ throw createError(ERROR_CODES.TOO_BIG, `O arquivo excede o limite permitido de ${MAX_MEDIA_MB_LABEL} MB.`, {
984
+ endpoint: currentEndpoint,
985
+ status,
986
+ bytes: contentLength,
987
+ });
1104
988
  }
1105
989
 
1106
990
  if (status < 200 || status >= 300) {
@@ -1114,10 +998,7 @@ const requestDownloadToFile = async (link, type, requestId) => {
1114
998
  }
1115
999
 
1116
1000
  writeStream = fs.createWriteStream(filePath);
1117
- const limiter = createByteLimitTransform(
1118
- MAX_MEDIA_BYTES,
1119
- `O arquivo excede o limite permitido de ${MAX_MEDIA_MB_LABEL} MB.`,
1120
- );
1001
+ const limiter = createByteLimitTransform(MAX_MEDIA_BYTES, `O arquivo excede o limite permitido de ${MAX_MEDIA_MB_LABEL} MB.`);
1121
1002
 
1122
1003
  try {
1123
1004
  await pipeline(res, limiter.stream, writeStream);
@@ -1138,10 +1019,7 @@ const requestDownloadToFile = async (link, type, requestId) => {
1138
1019
  if (!streamInfo.hasVideo) {
1139
1020
  if (streamInfo.hasAudio) {
1140
1021
  finalMediaType = 'audio';
1141
- finalMimeType =
1142
- normalizedContentType === 'video/mp4'
1143
- ? 'audio/mp4'
1144
- : resolveMediaMimeType('audio', contentType);
1022
+ finalMimeType = normalizedContentType === 'video/mp4' ? 'audio/mp4' : resolveMediaMimeType('audio', contentType);
1145
1023
 
1146
1024
  logger.warn('Play vídeo: fonte retornou somente áudio, fallback ativado.', {
1147
1025
  requestId,
@@ -1151,27 +1029,19 @@ const requestDownloadToFile = async (link, type, requestId) => {
1151
1029
  audioCodec: streamInfo.audioCodec || null,
1152
1030
  });
1153
1031
  } else {
1154
- throw createError(
1155
- ERROR_CODES.API,
1156
- 'Não foi possível enviar como vídeo: a mídia não possui faixa de vídeo nem áudio.',
1157
- {
1158
- endpoint: currentEndpoint,
1159
- status,
1160
- requestId,
1161
- hasAudio: streamInfo.hasAudio,
1162
- videoCodec: streamInfo.videoCodec,
1163
- audioCodec: streamInfo.audioCodec,
1164
- },
1165
- );
1032
+ throw createError(ERROR_CODES.API, 'Não foi possível enviar como vídeo: a mídia não possui faixa de vídeo nem áudio.', {
1033
+ endpoint: currentEndpoint,
1034
+ status,
1035
+ requestId,
1036
+ hasAudio: streamInfo.hasAudio,
1037
+ videoCodec: streamInfo.videoCodec,
1038
+ audioCodec: streamInfo.audioCodec,
1039
+ });
1166
1040
  }
1167
1041
  }
1168
1042
 
1169
1043
  if (finalMediaType === 'video') {
1170
- if (
1171
- VIDEO_FORCE_TRANSCODE ||
1172
- streamInfo.videoCodec !== 'h264' ||
1173
- (streamInfo.hasAudio && streamInfo.audioCodec !== 'aac')
1174
- ) {
1044
+ if (VIDEO_FORCE_TRANSCODE || streamInfo.videoCodec !== 'h264' || (streamInfo.hasAudio && streamInfo.audioCodec !== 'aac')) {
1175
1045
  finalBytes = await transcodeVideoForWhatsapp(filePath, requestId, currentEndpoint);
1176
1046
  finalMimeType = TYPE_CONFIG.video.mimeFallback;
1177
1047
  logger.info('Play vídeo normalizado para compatibilidade.', {
@@ -1271,32 +1141,16 @@ const getUserErrorMessage = (error) => {
1271
1141
  const notifyFailure = async (sock, remoteJid, messageInfo, expirationMessage, error, context) => {
1272
1142
  const errorMessage = getUserErrorMessage(error);
1273
1143
 
1274
- await sendAndStore(
1275
- sock,
1276
- remoteJid,
1277
- { text: `❌ Erro: ${errorMessage}` },
1278
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
1279
- );
1144
+ await sendAndStore(sock, remoteJid, { text: `❌ Erro: ${errorMessage}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
1280
1145
 
1281
1146
  if (adminJid) {
1282
1147
  await sendAndStore(sock, adminJid, {
1283
- text: `Erro no módulo play.\nChat: ${remoteJid}\nRequest: ${
1284
- context?.requestId || 'n/a'
1285
- }\nTipo: ${context?.type || 'n/a'}\nEndpoint: ${error?.meta?.endpoint || 'n/a'}\nStatus: ${
1286
- error?.meta?.status || 'n/a'
1287
- }\nErro: ${errorMessage}\nCode: ${error?.code || 'n/a'}`,
1148
+ text: `Erro no módulo play.\nChat: ${remoteJid}\nRequest: ${context?.requestId || 'n/a'}\nTipo: ${context?.type || 'n/a'}\nEndpoint: ${error?.meta?.endpoint || 'n/a'}\nStatus: ${error?.meta?.status || 'n/a'}\nErro: ${errorMessage}\nCode: ${error?.code || 'n/a'}`,
1288
1149
  });
1289
1150
  }
1290
1151
  };
1291
1152
 
1292
- const processPlayRequest = async ({
1293
- sock,
1294
- remoteJid,
1295
- messageInfo,
1296
- expirationMessage,
1297
- text,
1298
- type,
1299
- }) => {
1153
+ const processPlayRequest = async ({ sock, remoteJid, messageInfo, expirationMessage, text, type }) => {
1300
1154
  const startTime = Date.now();
1301
1155
  const requestId = buildRequestId();
1302
1156
  const config = TYPE_CONFIG[type];
@@ -1320,16 +1174,9 @@ const processPlayRequest = async ({
1320
1174
  const queueStatusPromise = ytdlsClient.fetchQueueStatus(requestId);
1321
1175
  const queueStatus = await Promise.race([queueStatusPromise, delay(QUICK_QUEUE_LOOKUP_MS)]);
1322
1176
  const queueText = formatters.buildQueueStatusText(queueStatus);
1323
- const waitText = queueText
1324
- ? `${config.queueWaitText || config.waitText}\n${queueText}`
1325
- : config.waitText;
1177
+ const waitText = queueText ? `${config.queueWaitText || config.waitText}\n${queueText}` : config.waitText;
1326
1178
 
1327
- await sendAndStore(
1328
- sock,
1329
- remoteJid,
1330
- { text: waitText },
1331
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
1332
- );
1179
+ await sendAndStore(sock, remoteJid, { text: waitText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
1333
1180
 
1334
1181
  if (queueStatus?.fila) {
1335
1182
  logger.info('Play fila consultada.', {
@@ -1342,10 +1189,7 @@ const processPlayRequest = async ({
1342
1189
  });
1343
1190
  }
1344
1191
 
1345
- const [downloadResult, videoInfo] = await Promise.all([
1346
- ytdlsClient.requestDownloadToFile(link, type, requestId),
1347
- ytdlsClient.fetchVideoInfo(text, link),
1348
- ]);
1192
+ const [downloadResult, videoInfo] = await Promise.all([ytdlsClient.requestDownloadToFile(link, type, requestId), ytdlsClient.fetchVideoInfo(text, link)]);
1349
1193
 
1350
1194
  filePath = downloadResult.filePath;
1351
1195
  const deliveredType = downloadResult.mediaType || type;
@@ -1364,12 +1208,7 @@ const processPlayRequest = async ({
1364
1208
  });
1365
1209
 
1366
1210
  if (fallbackToAudio) {
1367
- await sendAndStore(
1368
- sock,
1369
- remoteJid,
1370
- { text: '⚠️ Este link retornou somente áudio. Enviando no formato de áudio.' },
1371
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
1372
- );
1211
+ await sendAndStore(sock, remoteJid, { text: '⚠️ Este link retornou somente áudio. Enviando no formato de áudio.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
1373
1212
  }
1374
1213
 
1375
1214
  if (deliveredType === 'audio') {
@@ -1399,12 +1238,7 @@ const processPlayRequest = async ({
1399
1238
 
1400
1239
  if (thumbBuffer) {
1401
1240
  try {
1402
- await sendAndStore(
1403
- sock,
1404
- remoteJid,
1405
- { image: thumbBuffer, caption },
1406
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
1407
- );
1241
+ await sendAndStore(sock, remoteJid, { image: thumbBuffer, caption }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
1408
1242
  previewDelivered = true;
1409
1243
  } catch (error) {
1410
1244
  logger.warn('Falha ao enviar thumbnail de áudio.', {
@@ -1421,12 +1255,7 @@ const processPlayRequest = async ({
1421
1255
 
1422
1256
  if (!previewDelivered && caption) {
1423
1257
  try {
1424
- await sendAndStore(
1425
- sock,
1426
- remoteJid,
1427
- { text: caption },
1428
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
1429
- );
1258
+ await sendAndStore(sock, remoteJid, { text: caption }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
1430
1259
  previewDelivered = true;
1431
1260
  } catch (error) {
1432
1261
  logger.warn('Falha ao enviar preview textual do áudio.', {
@@ -1519,28 +1348,12 @@ const playService = {
1519
1348
  processPlayRequest,
1520
1349
  };
1521
1350
 
1522
- const handleTypedPlayCommand = async ({
1523
- sock,
1524
- remoteJid,
1525
- messageInfo,
1526
- expirationMessage,
1527
- text,
1528
- commandPrefix,
1529
- type,
1530
- }) => {
1351
+ const handleTypedPlayCommand = async ({ sock, remoteJid, messageInfo, expirationMessage, text, commandPrefix, type }) => {
1531
1352
  try {
1532
1353
  if (!text?.trim()) {
1533
- const usageText =
1534
- type === 'audio'
1535
- ? `🎵 Uso: ${commandPrefix}play <link do YouTube ou termo de busca>`
1536
- : `🎬 Uso: ${commandPrefix}playvid <link do YouTube ou termo de busca>`;
1354
+ const usageText = type === 'audio' ? `🎵 Uso: ${commandPrefix}play <link do YouTube ou termo de busca>` : `🎬 Uso: ${commandPrefix}playvid <link do YouTube ou termo de busca>`;
1537
1355
 
1538
- await sendAndStore(
1539
- sock,
1540
- remoteJid,
1541
- { text: usageText },
1542
- { quoted: messageInfo, ephemeralExpiration: expirationMessage },
1543
- );
1356
+ await sendAndStore(sock, remoteJid, { text: usageText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
1544
1357
  return;
1545
1358
  }
1546
1359
 
@@ -1569,14 +1382,7 @@ const handleTypedPlayCommand = async ({
1569
1382
  * @param {string} text
1570
1383
  * @returns {Promise<void>}
1571
1384
  */
1572
- export const handlePlayCommand = async (
1573
- sock,
1574
- remoteJid,
1575
- messageInfo,
1576
- expirationMessage,
1577
- text,
1578
- commandPrefix = DEFAULT_COMMAND_PREFIX,
1579
- ) =>
1385
+ export const handlePlayCommand = async (sock, remoteJid, messageInfo, expirationMessage, text, commandPrefix = DEFAULT_COMMAND_PREFIX) =>
1580
1386
  handleTypedPlayCommand({
1581
1387
  sock,
1582
1388
  remoteJid,
@@ -1596,14 +1402,7 @@ export const handlePlayCommand = async (
1596
1402
  * @param {string} text
1597
1403
  * @returns {Promise<void>}
1598
1404
  */
1599
- export const handlePlayVidCommand = async (
1600
- sock,
1601
- remoteJid,
1602
- messageInfo,
1603
- expirationMessage,
1604
- text,
1605
- commandPrefix = DEFAULT_COMMAND_PREFIX,
1606
- ) =>
1405
+ export const handlePlayVidCommand = async (sock, remoteJid, messageInfo, expirationMessage, text, commandPrefix = DEFAULT_COMMAND_PREFIX) =>
1607
1406
  handleTypedPlayCommand({
1608
1407
  sock,
1609
1408
  remoteJid,
@@ -17,8 +17,7 @@ const QUOTE_NAME_COLOR = process.env.QUOTE_NAME_COLOR || '#facc01';
17
17
  const QUOTE_TEXT_COLOR = process.env.QUOTE_TEXT_COLOR || '#e8eef6';
18
18
  const QUOTE_TIMEOUT_MS = Number.parseInt(process.env.QUOTE_TIMEOUT_MS || '10000', 10);
19
19
  const QUOTE_EMOJI_TIMEOUT_MS = Number.parseInt(process.env.QUOTE_EMOJI_TIMEOUT_MS || '4000', 10);
20
- const QUOTE_EMOJI_BASE_URL =
21
- process.env.QUOTE_EMOJI_BASE_URL || 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/png/128';
20
+ const QUOTE_EMOJI_BASE_URL = process.env.QUOTE_EMOJI_BASE_URL || 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/png/128';
22
21
  const QUOTE_FONT_FAMILY = process.env.QUOTE_FONT_FAMILY || '"Noto Sans","Segoe UI","Arial","Noto Color Emoji","Apple Color Emoji","Segoe UI Emoji",sans-serif';
23
22
 
24
23
  const QUOTE_CANVAS_MAX_WIDTH = 920;
@@ -245,8 +244,7 @@ const buildEmojiAssetKeys = (segment) => {
245
244
  * @param {string} assetKey Chave de code points.
246
245
  * @returns {string} URL final para download.
247
246
  */
248
- const getEmojiAssetUrl = (assetKey) =>
249
- `${QUOTE_EMOJI_BASE_URL.replace(/\/+$/, '')}/emoji_u${assetKey}.png`;
247
+ const getEmojiAssetUrl = (assetKey) => `${QUOTE_EMOJI_BASE_URL.replace(/\/+$/, '')}/emoji_u${assetKey}.png`;
250
248
 
251
249
  /**
252
250
  * Busca um emoji do cache com TTL para sucesso/falha.
@@ -1165,19 +1165,7 @@ export const getPvpWeeklyRankByOwner = async (weekRefDate, ownerJid, connection
1165
1165
  OR (points = ? AND wins = ? AND matches_played > ?)
1166
1166
  OR (points = ? AND wins = ? AND matches_played = ? AND owner_jid < ?)
1167
1167
  )`,
1168
- [
1169
- weekRefDate,
1170
- stats.points,
1171
- stats.points,
1172
- stats.wins,
1173
- stats.points,
1174
- stats.wins,
1175
- stats.matches_played,
1176
- stats.points,
1177
- stats.wins,
1178
- stats.matches_played,
1179
- ownerJid,
1180
- ],
1168
+ [weekRefDate, stats.points, stats.points, stats.wins, stats.points, stats.wins, stats.matches_played, stats.points, stats.wins, stats.matches_played, ownerJid],
1181
1169
  connection,
1182
1170
  );
1183
1171