@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.
- package/README.md +20 -18
- package/app/config/adminIdentity.js +1 -3
- package/app/connection/socketController.js +10 -20
- package/app/controllers/messageController.js +7 -28
- package/app/modules/aiModule/catCommand.js +29 -192
- package/app/modules/broadcastModule/noticeCommand.js +28 -97
- package/app/modules/gameModule/diceCommand.js +6 -32
- package/app/modules/playModule/playCommand.js +57 -258
- package/app/modules/quoteModule/quoteCommand.js +2 -4
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
- package/app/modules/statsModule/noMessageCommand.js +16 -84
- package/app/modules/statsModule/rankingCommand.js +5 -25
- package/app/modules/statsModule/rankingCommon.js +1 -9
- package/app/modules/stickerModule/convertToWebp.js +4 -27
- package/app/modules/stickerModule/stickerCommand.js +13 -24
- package/app/modules/stickerModule/stickerTextCommand.js +13 -25
- package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
- package/app/modules/stickerPackModule/domainEvents.js +2 -11
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
- package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
- package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +13 -34
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
- package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
- package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
- package/app/modules/stickerPackModule/stickerPackService.js +50 -115
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
- package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
- package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
- package/app/modules/systemMetricsModule/pingCommand.js +9 -39
- package/app/modules/tiktokModule/tiktokCommand.js +17 -109
- package/app/modules/userModule/userCommand.js +2 -88
- package/app/observability/metrics.js +5 -16
- package/app/services/captchaService.js +1 -6
- package/app/services/dbWriteQueue.js +3 -18
- package/app/services/featureFlagService.js +2 -8
- package/app/services/newsBroadcastService.js +0 -1
- package/app/services/queueUtils.js +2 -4
- package/app/services/whatsappLoginLinkService.js +7 -9
- package/app/store/premiumUserStore.js +1 -2
- package/app/utils/antiLink/antiLinkModule.js +3 -233
- package/app/utils/logger/loggerModule.js +9 -34
- package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
- package/database/init.js +1 -8
- package/docker-compose.yml +27 -27
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +220 -0
- package/docs/seo/satellite-page-template.md +91 -0
- package/docs/seo/satellite-pages-phase1.json +349 -0
- package/eslint.config.js +2 -15
- package/index.js +8 -36
- package/ml/clip_classifier/README.md +4 -6
- package/observability/alert-rules.yml +12 -12
- package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
- package/package.json +8 -3
- package/public/api-docs/index.html +224 -141
- package/public/bot-whatsapp-para-grupo/index.html +306 -0
- package/public/bot-whatsapp-sem-programar/index.html +306 -0
- package/public/comandos/index.html +428 -0
- package/public/como-automatizar-avisos-no-whatsapp/index.html +306 -0
- package/public/como-criar-comandos-whatsapp/index.html +306 -0
- package/public/como-evitar-spam-no-whatsapp/index.html +306 -0
- package/public/como-moderar-grupo-whatsapp/index.html +306 -0
- package/public/como-organizar-comunidade-whatsapp/index.html +306 -0
- package/public/css/github-project-panel.css +20 -15
- package/public/css/stickers-admin.css +55 -39
- package/public/css/styles.css +37 -29
- package/public/index.html +1060 -1417
- package/public/js/apps/apiDocsApp.js +36 -153
- package/public/js/apps/createPackApp.js +69 -332
- package/public/js/apps/homeApp.js +201 -434
- package/public/js/apps/loginApp.js +3 -12
- package/public/js/apps/stickersAdminApp.js +190 -181
- package/public/js/apps/stickersApp.js +507 -1366
- package/public/js/catalog.js +11 -74
- package/public/js/github-panel/components/ErrorState.js +1 -8
- package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
- package/public/js/github-panel/components/SkeletonPanel.js +1 -11
- package/public/js/github-panel/components/StatCard.js +1 -7
- package/public/js/github-panel/vendor/react.js +1 -9
- package/public/js/runtime/react-runtime.js +1 -9
- package/public/licenca/index.html +104 -86
- package/public/login/index.html +315 -321
- package/public/melhor-bot-whatsapp-para-grupos/index.html +306 -0
- package/public/sitemap.xml +45 -0
- package/public/stickers/admin/index.html +14 -19
- package/public/stickers/create/index.html +39 -43
- package/public/stickers/index.html +97 -41
- package/public/termos-de-uso/index.html +142 -115
- package/public/user/index.html +347 -346
- package/scripts/cache-bust.mjs +5 -24
- package/scripts/generate-seo-satellite-pages.mjs +431 -0
- package/scripts/run-prettier-all.mjs +25 -0
- package/scripts/sticker-catalog-loadtest.mjs +13 -11
- package/scripts/sticker-worker-task.mjs +1 -4
- package/scripts/sync-readme-snapshot.mjs +3 -2
- package/server/controllers/stickerCatalogController.js +407 -704
- package/server/http/httpServer.js +2 -10
- package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
- package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
- package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
- package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
- package/server/routes/stickerCatalog/catalogRouter.js +11 -13
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
|