@kaikybrofc/omnizap-system 2.2.10 → 2.3.1
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 +13 -13
- 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 +40 -39
- 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/index.js +1 -0
- package/database/init.js +1 -8
- package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
- package/docker-compose.yml +27 -27
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
- package/docs/seo/satellite-page-template.md +2 -0
- package/docs/seo/satellite-pages-phase1.json +40 -177
- 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 +6 -3
- package/public/api-docs/index.html +220 -193
- package/public/bot-whatsapp-para-grupo/index.html +291 -261
- package/public/bot-whatsapp-sem-programar/index.html +291 -261
- package/public/comandos/index.html +421 -406
- package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
- package/public/como-criar-comandos-whatsapp/index.html +291 -261
- package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
- package/public/como-moderar-grupo-whatsapp/index.html +291 -261
- package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
- package/public/css/github-project-panel.css +13 -8
- package/public/css/stickers-admin.css +25 -9
- package/public/css/styles.css +23 -16
- package/public/index.html +1106 -993
- package/public/js/apps/apiDocsApp.js +17 -167
- package/public/js/apps/createPackApp.js +69 -332
- package/public/js/apps/homeApp.js +274 -101
- package/public/js/apps/loginApp.js +3 -12
- package/public/js/apps/stickersAdminApp.js +190 -181
- package/public/js/apps/stickersApp.js +482 -1411
- package/public/js/apps/userApp.js +217 -1
- 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 +200 -86
- package/public/login/index.html +315 -325
- package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
- package/public/stickers/admin/index.html +14 -19
- package/public/stickers/create/index.html +39 -44
- package/public/stickers/index.html +96 -107
- package/public/termos-de-uso/index.html +369 -122
- package/public/user/index.html +527 -350
- package/scripts/cache-bust.mjs +5 -24
- package/scripts/generate-seo-satellite-pages.mjs +10 -13
- 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/auth/googleWebAuth/googleWebAuthService.js +614 -0
- package/server/controllers/stickerCatalogController.js +297 -632
- 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
|
@@ -187,28 +187,9 @@ export function createStickerPackService(options = {}) {
|
|
|
187
187
|
const hasOwnerPackLimit = Number.isFinite(maxPacksPerOwner);
|
|
188
188
|
const runInTransaction = options.runInTransaction || withTransaction;
|
|
189
189
|
|
|
190
|
-
const requiredPackMethods = [
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
'findStickerPackByOwnerAndIdentifier',
|
|
194
|
-
'findStickerPackByPackKey',
|
|
195
|
-
'updateStickerPackFields',
|
|
196
|
-
'softDeleteStickerPack',
|
|
197
|
-
'ensureUniquePackKey',
|
|
198
|
-
'bumpStickerPackVersion',
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
const requiredItemMethods = [
|
|
202
|
-
'listStickerPackItems',
|
|
203
|
-
'countStickerPackItems',
|
|
204
|
-
'getMaxStickerPackPosition',
|
|
205
|
-
'createStickerPackItem',
|
|
206
|
-
'getStickerPackItemByStickerId',
|
|
207
|
-
'getStickerPackItemByPosition',
|
|
208
|
-
'removeStickerPackItemByStickerId',
|
|
209
|
-
'shiftStickerPackPositionsAfter',
|
|
210
|
-
'bulkUpdateStickerPackPositions',
|
|
211
|
-
];
|
|
190
|
+
const requiredPackMethods = ['createStickerPack', 'listStickerPacksByOwner', 'findStickerPackByOwnerAndIdentifier', 'findStickerPackByPackKey', 'updateStickerPackFields', 'softDeleteStickerPack', 'ensureUniquePackKey', 'bumpStickerPackVersion'];
|
|
191
|
+
|
|
192
|
+
const requiredItemMethods = ['listStickerPackItems', 'countStickerPackItems', 'getMaxStickerPackPosition', 'createStickerPackItem', 'getStickerPackItemByStickerId', 'getStickerPackItemByPosition', 'removeStickerPackItemByStickerId', 'shiftStickerPackPositionsAfter', 'bulkUpdateStickerPackPositions'];
|
|
212
193
|
|
|
213
194
|
for (const methodName of requiredPackMethods) {
|
|
214
195
|
if (typeof deps.packRepository[methodName] !== 'function') {
|
|
@@ -224,11 +205,7 @@ export function createStickerPackService(options = {}) {
|
|
|
224
205
|
|
|
225
206
|
const runAction = async (action, context, handler, { expectedErrorCodes = [] } = {}) => {
|
|
226
207
|
const start = process.hrtime.bigint();
|
|
227
|
-
const expectedCodesSet = new Set(
|
|
228
|
-
Array.isArray(expectedErrorCodes)
|
|
229
|
-
? expectedErrorCodes.map((code) => String(code || '').trim()).filter(Boolean)
|
|
230
|
-
: [],
|
|
231
|
-
);
|
|
208
|
+
const expectedCodesSet = new Set(Array.isArray(expectedErrorCodes) ? expectedErrorCodes.map((code) => String(code || '').trim()).filter(Boolean) : []);
|
|
232
209
|
|
|
233
210
|
try {
|
|
234
211
|
const result = await handler();
|
|
@@ -343,9 +320,7 @@ export function createStickerPackService(options = {}) {
|
|
|
343
320
|
const loadPackDetails = async (pack, { connection = null } = {}) => {
|
|
344
321
|
const items = await deps.itemRepository.listStickerPackItems(pack.id, connection);
|
|
345
322
|
const coverItem = items.find((item) => item.sticker_id === pack.cover_sticker_id);
|
|
346
|
-
const packClassification = await getPackClassificationSummaryByAssetIds(items.map((item) => item.sticker_id)).catch(
|
|
347
|
-
() => null,
|
|
348
|
-
);
|
|
323
|
+
const packClassification = await getPackClassificationSummaryByAssetIds(items.map((item) => item.sticker_id)).catch(() => null);
|
|
349
324
|
|
|
350
325
|
return {
|
|
351
326
|
...pack,
|
|
@@ -373,23 +348,9 @@ export function createStickerPackService(options = {}) {
|
|
|
373
348
|
};
|
|
374
349
|
};
|
|
375
350
|
|
|
376
|
-
const createPack = async ({
|
|
377
|
-
ownerJid,
|
|
378
|
-
name,
|
|
379
|
-
publisher,
|
|
380
|
-
description,
|
|
381
|
-
visibility = 'public',
|
|
382
|
-
status = 'published',
|
|
383
|
-
packStatus = undefined,
|
|
384
|
-
packThemeKey = undefined,
|
|
385
|
-
packVolume = undefined,
|
|
386
|
-
isAutoPack = undefined,
|
|
387
|
-
lastRebalancedAt = undefined,
|
|
388
|
-
}) => {
|
|
351
|
+
const createPack = async ({ ownerJid, name, publisher, description, visibility = 'public', status = 'published', packStatus = undefined, packThemeKey = undefined, packVolume = undefined, isAutoPack = undefined, lastRebalancedAt = undefined }) => {
|
|
389
352
|
const owner = resolveOwner(ownerJid);
|
|
390
|
-
const normalizedStatus = PACK_STATUS_VALUES.has(String(status || '').toLowerCase())
|
|
391
|
-
? String(status).toLowerCase()
|
|
392
|
-
: 'published';
|
|
353
|
+
const normalizedStatus = PACK_STATUS_VALUES.has(String(status || '').toLowerCase()) ? String(status).toLowerCase() : 'published';
|
|
393
354
|
|
|
394
355
|
return runAction('create_pack', { owner_jid: owner }, async () => {
|
|
395
356
|
return runInTransaction(async (connection) => {
|
|
@@ -400,11 +361,7 @@ export function createStickerPackService(options = {}) {
|
|
|
400
361
|
connection,
|
|
401
362
|
});
|
|
402
363
|
|
|
403
|
-
ensureValue(
|
|
404
|
-
existing.length < maxPacksPerOwner,
|
|
405
|
-
STICKER_PACK_ERROR_CODES.PACK_LIMIT_REACHED,
|
|
406
|
-
`Limite de packs atingido (${maxPacksPerOwner}).`,
|
|
407
|
-
);
|
|
364
|
+
ensureValue(existing.length < maxPacksPerOwner, STICKER_PACK_ERROR_CODES.PACK_LIMIT_REACHED, `Limite de packs atingido (${maxPacksPerOwner}).`);
|
|
408
365
|
}
|
|
409
366
|
|
|
410
367
|
const packKey = await ensurePackKey(owner, metadata.name, connection);
|
|
@@ -507,11 +464,7 @@ export function createStickerPackService(options = {}) {
|
|
|
507
464
|
const owner = resolveOwner(ownerJid);
|
|
508
465
|
const normalizedVisibility = toVisibility(visibility, null);
|
|
509
466
|
|
|
510
|
-
ensureValue(
|
|
511
|
-
normalizedVisibility,
|
|
512
|
-
STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
513
|
-
'Visibilidade inválida. Use: private, public ou unlisted.',
|
|
514
|
-
);
|
|
467
|
+
ensureValue(normalizedVisibility, STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'Visibilidade inválida. Use: private, public ou unlisted.');
|
|
515
468
|
|
|
516
469
|
return runAction('set_visibility', { owner_jid: owner }, async () => {
|
|
517
470
|
const pack = await resolveOwnedPack(owner, identifier);
|
|
@@ -542,14 +495,7 @@ export function createStickerPackService(options = {}) {
|
|
|
542
495
|
});
|
|
543
496
|
};
|
|
544
497
|
|
|
545
|
-
const addStickerToPack = async ({
|
|
546
|
-
ownerJid,
|
|
547
|
-
identifier,
|
|
548
|
-
asset,
|
|
549
|
-
emojis = [],
|
|
550
|
-
accessibilityLabel = null,
|
|
551
|
-
expectedErrorCodes = [],
|
|
552
|
-
}) => {
|
|
498
|
+
const addStickerToPack = async ({ ownerJid, identifier, asset, emojis = [], accessibilityLabel = null, expectedErrorCodes = [] }) => {
|
|
553
499
|
const owner = resolveOwner(ownerJid);
|
|
554
500
|
|
|
555
501
|
ensureValue(asset?.id, STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND, 'Nenhuma figurinha válida foi encontrada.');
|
|
@@ -558,57 +504,54 @@ export function createStickerPackService(options = {}) {
|
|
|
558
504
|
allowEmpty: true,
|
|
559
505
|
});
|
|
560
506
|
|
|
561
|
-
return runAction(
|
|
562
|
-
|
|
507
|
+
return runAction(
|
|
508
|
+
'add_sticker',
|
|
509
|
+
{ owner_jid: owner },
|
|
510
|
+
async () => {
|
|
511
|
+
const pack = await resolveOwnedPack(owner, identifier);
|
|
563
512
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
!existingItem,
|
|
568
|
-
STICKER_PACK_ERROR_CODES.DUPLICATE_STICKER,
|
|
569
|
-
'Essa figurinha já está no pack.',
|
|
570
|
-
);
|
|
513
|
+
return runInTransaction(async (connection) => {
|
|
514
|
+
const existingItem = await deps.itemRepository.getStickerPackItemByStickerId(pack.id, asset.id, connection);
|
|
515
|
+
ensureValue(!existingItem, STICKER_PACK_ERROR_CODES.DUPLICATE_STICKER, 'Essa figurinha já está no pack.');
|
|
571
516
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
total < maxStickersPerPack,
|
|
575
|
-
STICKER_PACK_ERROR_CODES.PACK_LIMIT_REACHED,
|
|
576
|
-
`O pack atingiu o limite de ${maxStickersPerPack} figurinhas.`,
|
|
577
|
-
);
|
|
517
|
+
const total = await deps.itemRepository.countStickerPackItems(pack.id, connection);
|
|
518
|
+
ensureValue(total < maxStickersPerPack, STICKER_PACK_ERROR_CODES.PACK_LIMIT_REACHED, `O pack atingiu o limite de ${maxStickersPerPack} figurinhas.`);
|
|
578
519
|
|
|
579
|
-
|
|
520
|
+
const maxPosition = await deps.itemRepository.getMaxStickerPackPosition(pack.id, connection);
|
|
580
521
|
|
|
581
|
-
|
|
582
|
-
{
|
|
583
|
-
id: randomUUID(),
|
|
584
|
-
pack_id: pack.id,
|
|
585
|
-
sticker_id: asset.id,
|
|
586
|
-
position: maxPosition + 1,
|
|
587
|
-
emojis: parseEmojiList(emojis),
|
|
588
|
-
accessibility_label: normalizedLabel || null,
|
|
589
|
-
},
|
|
590
|
-
connection,
|
|
591
|
-
);
|
|
592
|
-
|
|
593
|
-
if (!pack.cover_sticker_id) {
|
|
594
|
-
await deps.packRepository.updateStickerPackFields(
|
|
595
|
-
pack.id,
|
|
522
|
+
await deps.itemRepository.createStickerPackItem(
|
|
596
523
|
{
|
|
597
|
-
|
|
524
|
+
id: randomUUID(),
|
|
525
|
+
pack_id: pack.id,
|
|
526
|
+
sticker_id: asset.id,
|
|
527
|
+
position: maxPosition + 1,
|
|
528
|
+
emojis: parseEmojiList(emojis),
|
|
529
|
+
accessibility_label: normalizedLabel || null,
|
|
598
530
|
},
|
|
599
531
|
connection,
|
|
600
532
|
);
|
|
601
|
-
} else {
|
|
602
|
-
await deps.packRepository.bumpStickerPackVersion(pack.id, connection);
|
|
603
|
-
}
|
|
604
533
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
534
|
+
if (!pack.cover_sticker_id) {
|
|
535
|
+
await deps.packRepository.updateStickerPackFields(
|
|
536
|
+
pack.id,
|
|
537
|
+
{
|
|
538
|
+
cover_sticker_id: asset.id,
|
|
539
|
+
},
|
|
540
|
+
connection,
|
|
541
|
+
);
|
|
542
|
+
} else {
|
|
543
|
+
await deps.packRepository.bumpStickerPackVersion(pack.id, connection);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const reloaded = await deps.packRepository.findStickerPackByOwnerAndIdentifier(owner, pack.id, {
|
|
547
|
+
connection,
|
|
548
|
+
});
|
|
608
549
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
550
|
+
return loadPackDetails(reloaded, { connection });
|
|
551
|
+
});
|
|
552
|
+
},
|
|
553
|
+
{ expectedErrorCodes },
|
|
554
|
+
);
|
|
612
555
|
};
|
|
613
556
|
|
|
614
557
|
const removeStickerFromPack = async ({ ownerJid, identifier, selector }) => {
|
|
@@ -665,11 +608,7 @@ export function createStickerPackService(options = {}) {
|
|
|
665
608
|
|
|
666
609
|
const reorderPackItems = async ({ ownerJid, identifier, orderStickerIds }) => {
|
|
667
610
|
const owner = resolveOwner(ownerJid);
|
|
668
|
-
ensureValue(
|
|
669
|
-
Array.isArray(orderStickerIds) && orderStickerIds.length > 0,
|
|
670
|
-
STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
671
|
-
'Envie a nova ordem de figurinhas.',
|
|
672
|
-
);
|
|
611
|
+
ensureValue(Array.isArray(orderStickerIds) && orderStickerIds.length > 0, STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'Envie a nova ordem de figurinhas.');
|
|
673
612
|
|
|
674
613
|
return runAction('reorder_pack', { owner_jid: owner }, async () => {
|
|
675
614
|
const pack = await resolveOwnedPack(owner, identifier);
|
|
@@ -690,11 +629,7 @@ export function createStickerPackService(options = {}) {
|
|
|
690
629
|
seen.add(id);
|
|
691
630
|
}
|
|
692
631
|
|
|
693
|
-
ensureValue(
|
|
694
|
-
requestedIds.length > 0,
|
|
695
|
-
STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
696
|
-
'A ordem enviada não corresponde às figurinhas do pack.',
|
|
697
|
-
);
|
|
632
|
+
ensureValue(requestedIds.length > 0, STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'A ordem enviada não corresponde às figurinhas do pack.');
|
|
698
633
|
|
|
699
634
|
const finalOrder = [...requestedIds, ...currentIds.filter((id) => !seen.has(id))];
|
|
700
635
|
|
|
@@ -1,25 +1,6 @@
|
|
|
1
1
|
import { createStickerPackService } from './stickerPackService.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
createStickerPack,
|
|
5
|
-
ensureUniquePackKey,
|
|
6
|
-
findStickerPackByPackKey,
|
|
7
|
-
findStickerPackByOwnerAndIdentifier,
|
|
8
|
-
listStickerPacksByOwner,
|
|
9
|
-
softDeleteStickerPack,
|
|
10
|
-
updateStickerPackFields,
|
|
11
|
-
} from './stickerPackRepository.js';
|
|
12
|
-
import {
|
|
13
|
-
bulkUpdateStickerPackPositions,
|
|
14
|
-
countStickerPackItems,
|
|
15
|
-
createStickerPackItem,
|
|
16
|
-
getMaxStickerPackPosition,
|
|
17
|
-
getStickerPackItemByPosition,
|
|
18
|
-
getStickerPackItemByStickerId,
|
|
19
|
-
listStickerPackItems,
|
|
20
|
-
removeStickerPackItemByStickerId,
|
|
21
|
-
shiftStickerPackPositionsAfter,
|
|
22
|
-
} from './stickerPackItemRepository.js';
|
|
2
|
+
import { bumpStickerPackVersion, createStickerPack, ensureUniquePackKey, findStickerPackByPackKey, findStickerPackByOwnerAndIdentifier, listStickerPacksByOwner, softDeleteStickerPack, updateStickerPackFields } from './stickerPackRepository.js';
|
|
3
|
+
import { bulkUpdateStickerPackPositions, countStickerPackItems, createStickerPackItem, getMaxStickerPackPosition, getStickerPackItemByPosition, getStickerPackItemByStickerId, listStickerPackItems, removeStickerPackItemByStickerId, shiftStickerPackPositionsAfter } from './stickerPackItemRepository.js';
|
|
23
4
|
|
|
24
5
|
/**
|
|
25
6
|
* Serviço principal de sticker pack com dependências concretas de runtime.
|
|
@@ -54,7 +54,11 @@ export const slugify = (value, { fallback = 'pack', maxLength = 32 } = {}) => {
|
|
|
54
54
|
* @param {number} [size=8] Quantidade de caracteres retornados.
|
|
55
55
|
* @returns {string} Hash hexadecimal truncado.
|
|
56
56
|
*/
|
|
57
|
-
export const shortHash = (value, size = 8) =>
|
|
57
|
+
export const shortHash = (value, size = 8) =>
|
|
58
|
+
createHash('sha256')
|
|
59
|
+
.update(String(value ?? ''))
|
|
60
|
+
.digest('hex')
|
|
61
|
+
.slice(0, size);
|
|
58
62
|
|
|
59
63
|
/**
|
|
60
64
|
* Normaliza o JID do dono mantendo fallback para o valor original.
|
|
@@ -72,7 +76,9 @@ export const normalizeOwnerJid = (jid) => normalizeJid(jid || '') || jid || '';
|
|
|
72
76
|
* @returns {'private'|'public'|'unlisted'|null} Valor de visibilidade válido.
|
|
73
77
|
*/
|
|
74
78
|
export const toVisibility = (value, fallback = 'private') => {
|
|
75
|
-
const normalized = String(value || '')
|
|
79
|
+
const normalized = String(value || '')
|
|
80
|
+
.trim()
|
|
81
|
+
.toLowerCase();
|
|
76
82
|
if (normalized === 'public' || normalized === 'unlisted' || normalized === 'private') {
|
|
77
83
|
return normalized;
|
|
78
84
|
}
|
|
@@ -87,7 +93,11 @@ export const toVisibility = (value, fallback = 'private') => {
|
|
|
87
93
|
*/
|
|
88
94
|
export const parseEmojiList = (value) => {
|
|
89
95
|
if (!value) return [];
|
|
90
|
-
if (Array.isArray(value))
|
|
96
|
+
if (Array.isArray(value))
|
|
97
|
+
return value
|
|
98
|
+
.map((item) => String(item))
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.slice(0, 8);
|
|
91
101
|
|
|
92
102
|
return String(value)
|
|
93
103
|
.split(',')
|
|
@@ -5,22 +5,11 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
5
5
|
import logger from '../../utils/logger/loggerModule.js';
|
|
6
6
|
import { downloadMediaMessage, extractMediaDetails } from '../../config/baileysConfig.js';
|
|
7
7
|
import { isFeatureEnabled } from '../../services/featureFlagService.js';
|
|
8
|
-
import {
|
|
9
|
-
createStickerAsset,
|
|
10
|
-
findLatestStickerAssetByOwner,
|
|
11
|
-
findStickerAssetById,
|
|
12
|
-
findStickerAssetBySha256,
|
|
13
|
-
updateStickerAssetStoragePath,
|
|
14
|
-
} from './stickerAssetRepository.js';
|
|
8
|
+
import { createStickerAsset, findLatestStickerAssetByOwner, findStickerAssetById, findStickerAssetBySha256, updateStickerAssetStoragePath } from './stickerAssetRepository.js';
|
|
15
9
|
import { ensureStickerAssetClassified } from './stickerClassificationService.js';
|
|
16
10
|
import { STICKER_PACK_ERROR_CODES, StickerPackError } from './stickerPackErrors.js';
|
|
17
11
|
import { normalizeOwnerJid } from './stickerPackUtils.js';
|
|
18
|
-
import {
|
|
19
|
-
getStickerObjectStorageUrl,
|
|
20
|
-
isStickerObjectStorageEnabled,
|
|
21
|
-
readStickerFromObjectStorage,
|
|
22
|
-
uploadStickerToObjectStorage,
|
|
23
|
-
} from './stickerObjectStorageService.js';
|
|
12
|
+
import { getStickerObjectStorageUrl, isStickerObjectStorageEnabled, readStickerFromObjectStorage, uploadStickerToObjectStorage } from './stickerObjectStorageService.js';
|
|
24
13
|
|
|
25
14
|
/**
|
|
26
15
|
* Camada de storage local para assets de figurinha do sistema de packs.
|
|
@@ -30,10 +19,7 @@ const TEMP_ROOT = path.join(process.cwd(), 'temp', 'sticker-pack-assets');
|
|
|
30
19
|
const DEFAULT_MAX_STICKER_BYTES = 2 * 1024 * 1024;
|
|
31
20
|
const MAX_STICKER_BYTES = Math.max(64 * 1024, Number(process.env.STICKER_PACK_MAX_STICKER_BYTES) || DEFAULT_MAX_STICKER_BYTES);
|
|
32
21
|
const LAST_STICKER_TTL_MS = Math.max(60_000, Number(process.env.STICKER_PACK_LAST_CACHE_TTL_MS) || 6 * 60 * 60 * 1000);
|
|
33
|
-
const OBJECT_STORAGE_UPLOAD_TIMEOUT_MS = Math.max(
|
|
34
|
-
1_000,
|
|
35
|
-
Number(process.env.STICKER_OBJECT_STORAGE_UPLOAD_TIMEOUT_MS) || 10_000,
|
|
36
|
-
);
|
|
22
|
+
const OBJECT_STORAGE_UPLOAD_TIMEOUT_MS = Math.max(1_000, Number(process.env.STICKER_OBJECT_STORAGE_UPLOAD_TIMEOUT_MS) || 10_000);
|
|
37
23
|
|
|
38
24
|
const lastStickerCache = new Map();
|
|
39
25
|
|
|
@@ -61,7 +47,9 @@ const isObjectStorageDeliveryEnabled = async (subjectKey = '') =>
|
|
|
61
47
|
*/
|
|
62
48
|
const safeOwnerToken = (ownerJid) => {
|
|
63
49
|
const normalized = normalizeOwnerJid(ownerJid);
|
|
64
|
-
const token = String(normalized || 'unknown')
|
|
50
|
+
const token = String(normalized || 'unknown')
|
|
51
|
+
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
52
|
+
.slice(0, 100);
|
|
65
53
|
return token || 'unknown';
|
|
66
54
|
};
|
|
67
55
|
|
|
@@ -263,17 +251,11 @@ const validateStickerBuffer = (buffer) => {
|
|
|
263
251
|
}
|
|
264
252
|
|
|
265
253
|
if (buffer.length > MAX_STICKER_BYTES) {
|
|
266
|
-
throw new StickerPackError(
|
|
267
|
-
STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
268
|
-
`Figurinha excede o limite de ${(MAX_STICKER_BYTES / (1024 * 1024)).toFixed(1)} MB.`,
|
|
269
|
-
);
|
|
254
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, `Figurinha excede o limite de ${(MAX_STICKER_BYTES / (1024 * 1024)).toFixed(1)} MB.`);
|
|
270
255
|
}
|
|
271
256
|
|
|
272
257
|
if (!isLikelyWebp(buffer)) {
|
|
273
|
-
throw new StickerPackError(
|
|
274
|
-
STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
275
|
-
'A mídia precisa estar no formato WEBP para entrar no pack.',
|
|
276
|
-
);
|
|
258
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'A mídia precisa estar no formato WEBP para entrar no pack.');
|
|
277
259
|
}
|
|
278
260
|
};
|
|
279
261
|
|
|
@@ -390,10 +372,7 @@ async function persistStickerAssetFromDetails({ mediaDetails, ownerJid }) {
|
|
|
390
372
|
const mediaSize = Number(mediaDetails?.details?.fileLength || mediaDetails?.mediaKey?.fileLength || 0);
|
|
391
373
|
|
|
392
374
|
if (mediaSize > MAX_STICKER_BYTES) {
|
|
393
|
-
throw new StickerPackError(
|
|
394
|
-
STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
395
|
-
`Figurinha excede o limite de ${(MAX_STICKER_BYTES / (1024 * 1024)).toFixed(1)} MB.`,
|
|
396
|
-
);
|
|
375
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, `Figurinha excede o limite de ${(MAX_STICKER_BYTES / (1024 * 1024)).toFixed(1)} MB.`);
|
|
397
376
|
}
|
|
398
377
|
|
|
399
378
|
const tempOwnerDir = path.join(TEMP_ROOT, safeOwnerToken(normalizedOwner));
|
|
@@ -404,10 +383,7 @@ async function persistStickerAssetFromDetails({ mediaDetails, ownerJid }) {
|
|
|
404
383
|
try {
|
|
405
384
|
downloadedPath = await downloadMediaMessage(mediaDetails.mediaKey, 'sticker', tempOwnerDir);
|
|
406
385
|
if (!downloadedPath) {
|
|
407
|
-
throw new StickerPackError(
|
|
408
|
-
STICKER_PACK_ERROR_CODES.STORAGE_ERROR,
|
|
409
|
-
'Não foi possível baixar a figurinha para armazenamento.',
|
|
410
|
-
);
|
|
386
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.STORAGE_ERROR, 'Não foi possível baixar a figurinha para armazenamento.');
|
|
411
387
|
}
|
|
412
388
|
|
|
413
389
|
const buffer = await fs.readFile(downloadedPath);
|
|
@@ -427,11 +403,7 @@ async function persistStickerAssetFromDetails({ mediaDetails, ownerJid }) {
|
|
|
427
403
|
owner_jid: normalizedOwner,
|
|
428
404
|
error: error.message,
|
|
429
405
|
});
|
|
430
|
-
throw new StickerPackError(
|
|
431
|
-
STICKER_PACK_ERROR_CODES.STORAGE_ERROR,
|
|
432
|
-
'Falha ao salvar figurinha no servidor.',
|
|
433
|
-
error,
|
|
434
|
-
);
|
|
406
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.STORAGE_ERROR, 'Falha ao salvar figurinha no servidor.', error);
|
|
435
407
|
} finally {
|
|
436
408
|
if (downloadedPath) {
|
|
437
409
|
await fs.unlink(downloadedPath).catch(() => {});
|
|
@@ -461,11 +433,7 @@ export async function saveStickerAssetFromBuffer({ ownerJid, buffer, mimetype =
|
|
|
461
433
|
error: error.message,
|
|
462
434
|
});
|
|
463
435
|
|
|
464
|
-
throw new StickerPackError(
|
|
465
|
-
STICKER_PACK_ERROR_CODES.STORAGE_ERROR,
|
|
466
|
-
'Falha ao salvar figurinha gerada no servidor.',
|
|
467
|
-
error,
|
|
468
|
-
);
|
|
436
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.STORAGE_ERROR, 'Falha ao salvar figurinha gerada no servidor.', error);
|
|
469
437
|
}
|
|
470
438
|
}
|
|
471
439
|
|
|
@@ -525,12 +493,7 @@ export async function getLastStickerAssetForOwner(ownerJid) {
|
|
|
525
493
|
* }} params Contexto de resolução.
|
|
526
494
|
* @returns {Promise<object|null>} Asset resolvido.
|
|
527
495
|
*/
|
|
528
|
-
export async function resolveStickerAssetForCommand({
|
|
529
|
-
messageInfo,
|
|
530
|
-
ownerJid,
|
|
531
|
-
includeQuoted = true,
|
|
532
|
-
fallbackToLast = true,
|
|
533
|
-
}) {
|
|
496
|
+
export async function resolveStickerAssetForCommand({ messageInfo, ownerJid, includeQuoted = true, fallbackToLast = true }) {
|
|
534
497
|
const mediaDetails = resolveStickerMediaDetails(messageInfo, { includeQuoted });
|
|
535
498
|
|
|
536
499
|
if (mediaDetails) {
|
|
@@ -560,11 +523,7 @@ export async function readStickerAssetBuffer(asset) {
|
|
|
560
523
|
if (Buffer.isBuffer(externalBuffer) && externalBuffer.length) {
|
|
561
524
|
return externalBuffer;
|
|
562
525
|
}
|
|
563
|
-
throw new StickerPackError(
|
|
564
|
-
STICKER_PACK_ERROR_CODES.STORAGE_ERROR,
|
|
565
|
-
`Não foi possível ler a figurinha em disco (${asset.storage_path}).`,
|
|
566
|
-
error,
|
|
567
|
-
);
|
|
526
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.STORAGE_ERROR, `Não foi possível ler a figurinha em disco (${asset.storage_path}).`, error);
|
|
568
527
|
}
|
|
569
528
|
}
|
|
570
529
|
|
|
@@ -576,17 +535,9 @@ export async function readStickerAssetBuffer(asset) {
|
|
|
576
535
|
throw new StickerPackError(STICKER_PACK_ERROR_CODES.STORAGE_ERROR, 'Caminho do sticker não encontrado no storage.');
|
|
577
536
|
}
|
|
578
537
|
|
|
579
|
-
export async function getStickerAssetExternalUrl(
|
|
580
|
-
asset,
|
|
581
|
-
{
|
|
582
|
-
secure = true,
|
|
583
|
-
expiresInSeconds = 300,
|
|
584
|
-
} = {},
|
|
585
|
-
) {
|
|
538
|
+
export async function getStickerAssetExternalUrl(asset, { secure = true, expiresInSeconds = 300 } = {}) {
|
|
586
539
|
if (!asset) return null;
|
|
587
|
-
const canUseExternalDelivery = await isObjectStorageDeliveryEnabled(
|
|
588
|
-
normalizeOwnerJid(asset?.owner_jid || '') || String(asset?.id || asset?.sha256 || ''),
|
|
589
|
-
);
|
|
540
|
+
const canUseExternalDelivery = await isObjectStorageDeliveryEnabled(normalizeOwnerJid(asset?.owner_jid || '') || String(asset?.id || asset?.sha256 || ''));
|
|
590
541
|
if (!canUseExternalDelivery) return null;
|
|
591
542
|
return getStickerObjectStorageUrl(asset, {
|
|
592
543
|
secure,
|
|
@@ -3,14 +3,7 @@ import { setQueueDepth } from '../../observability/metrics.js';
|
|
|
3
3
|
import { isFeatureEnabled } from '../../services/featureFlagService.js';
|
|
4
4
|
import { runStickerClassificationCycle } from './stickerClassificationBackgroundRuntime.js';
|
|
5
5
|
import { runStickerAutoPackByTagsCycle } from './stickerAutoPackByTagsRuntime.js';
|
|
6
|
-
import {
|
|
7
|
-
claimWorkerTask,
|
|
8
|
-
completeWorkerTask,
|
|
9
|
-
countWorkerTasksByStatus,
|
|
10
|
-
enqueueWorkerTask,
|
|
11
|
-
failWorkerTask,
|
|
12
|
-
hasPendingWorkerTask,
|
|
13
|
-
} from './stickerWorkerTaskQueueRepository.js';
|
|
6
|
+
import { claimWorkerTask, completeWorkerTask, countWorkerTasksByStatus, enqueueWorkerTask, failWorkerTask, hasPendingWorkerTask } from './stickerWorkerTaskQueueRepository.js';
|
|
14
7
|
|
|
15
8
|
const parseEnvBool = (value, fallback) => {
|
|
16
9
|
if (value === undefined || value === null || value === '') return fallback;
|
|
@@ -39,10 +32,7 @@ const TASK_PRIORITY = {
|
|
|
39
32
|
curation_cycle: 55,
|
|
40
33
|
rebuild_cycle: 50,
|
|
41
34
|
};
|
|
42
|
-
const PIPELINE_PROCESS_COHORT_KEY =
|
|
43
|
-
String(process.env.STICKER_WORKER_PIPELINE_COHORT_KEY || process.env.HOSTNAME || process.pid)
|
|
44
|
-
.trim()
|
|
45
|
-
|| 'pipeline';
|
|
35
|
+
const PIPELINE_PROCESS_COHORT_KEY = String(process.env.STICKER_WORKER_PIPELINE_COHORT_KEY || process.env.HOSTNAME || process.pid).trim() || 'pipeline';
|
|
46
36
|
|
|
47
37
|
let startupHandle = null;
|
|
48
38
|
let schedulerHandle = null;
|
|
@@ -59,11 +49,7 @@ const taskHandlers = {
|
|
|
59
49
|
|
|
60
50
|
const refreshQueueDepthMetrics = async () => {
|
|
61
51
|
if (!taskQueueAvailable) return;
|
|
62
|
-
const [pending, processing, failed] = await Promise.all([
|
|
63
|
-
countWorkerTasksByStatus('pending'),
|
|
64
|
-
countWorkerTasksByStatus('processing'),
|
|
65
|
-
countWorkerTasksByStatus('failed'),
|
|
66
|
-
]);
|
|
52
|
+
const [pending, processing, failed] = await Promise.all([countWorkerTasksByStatus('pending'), countWorkerTasksByStatus('processing'), countWorkerTasksByStatus('failed')]);
|
|
67
53
|
setQueueDepth('sticker_worker_tasks_pending', pending);
|
|
68
54
|
setQueueDepth('sticker_worker_tasks_processing', processing);
|
|
69
55
|
setQueueDepth('sticker_worker_tasks_failed', failed);
|
|
@@ -107,11 +93,7 @@ const schedulerTick = async () => {
|
|
|
107
93
|
if (!PIPELINE_ENABLED) return;
|
|
108
94
|
|
|
109
95
|
try {
|
|
110
|
-
await Promise.all([
|
|
111
|
-
scheduleTaskIfNeeded('classification_cycle'),
|
|
112
|
-
scheduleTaskIfNeeded('curation_cycle'),
|
|
113
|
-
scheduleTaskIfNeeded('rebuild_cycle'),
|
|
114
|
-
]);
|
|
96
|
+
await Promise.all([scheduleTaskIfNeeded('classification_cycle'), scheduleTaskIfNeeded('curation_cycle'), scheduleTaskIfNeeded('rebuild_cycle')]);
|
|
115
97
|
await refreshQueueDepthMetrics();
|
|
116
98
|
} catch (error) {
|
|
117
99
|
if (error?.code === 'ER_NO_SUCH_TABLE') {
|
|
@@ -3,13 +3,17 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
4
4
|
|
|
5
5
|
const normalizeTaskType = (value) => {
|
|
6
|
-
const normalized = String(value || '')
|
|
6
|
+
const normalized = String(value || '')
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase();
|
|
7
9
|
if (['classification_cycle', 'curation_cycle', 'rebuild_cycle'].includes(normalized)) return normalized;
|
|
8
10
|
return null;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
const normalizeStatus = (value) => {
|
|
12
|
-
const normalized = String(value || '')
|
|
14
|
+
const normalized = String(value || '')
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase();
|
|
13
17
|
if (['pending', 'processing', 'completed', 'failed'].includes(normalized)) return normalized;
|
|
14
18
|
return null;
|
|
15
19
|
};
|
|
@@ -20,12 +24,7 @@ const clampInt = (value, fallback, min, max) => {
|
|
|
20
24
|
return Math.max(min, Math.min(max, Math.floor(numeric)));
|
|
21
25
|
};
|
|
22
26
|
|
|
23
|
-
const CLAIM_LOCK_TIMEOUT_SECONDS = clampInt(
|
|
24
|
-
process.env.STICKER_WORKER_TASK_LOCK_TIMEOUT_SECONDS,
|
|
25
|
-
15 * 60,
|
|
26
|
-
30,
|
|
27
|
-
24 * 60 * 60,
|
|
28
|
-
);
|
|
27
|
+
const CLAIM_LOCK_TIMEOUT_SECONDS = clampInt(process.env.STICKER_WORKER_TASK_LOCK_TIMEOUT_SECONDS, 15 * 60, 30, 24 * 60 * 60);
|
|
29
28
|
|
|
30
29
|
const normalizeIdempotencyKey = (value) =>
|
|
31
30
|
String(value || '')
|
|
@@ -76,10 +75,7 @@ const normalizeRow = (row) => {
|
|
|
76
75
|
};
|
|
77
76
|
};
|
|
78
77
|
|
|
79
|
-
export async function enqueueWorkerTask(
|
|
80
|
-
{ taskType, payload = {}, priority = 50, scheduledAt = null, maxAttempts = 5, idempotencyKey = '' },
|
|
81
|
-
connection = null,
|
|
82
|
-
) {
|
|
78
|
+
export async function enqueueWorkerTask({ taskType, payload = {}, priority = 50, scheduledAt = null, maxAttempts = 5, idempotencyKey = '' }, connection = null) {
|
|
83
79
|
const normalizedTaskType = normalizeTaskType(taskType);
|
|
84
80
|
if (!normalizedTaskType) return false;
|
|
85
81
|
|
|
@@ -99,14 +95,7 @@ export async function enqueueWorkerTask(
|
|
|
99
95
|
scheduled_at = LEAST(scheduled_at, VALUES(scheduled_at)),
|
|
100
96
|
status = IF(status = 'failed' AND attempts < max_attempts, 'pending', status),
|
|
101
97
|
updated_at = UTC_TIMESTAMP()`,
|
|
102
|
-
[
|
|
103
|
-
normalizedTaskType,
|
|
104
|
-
normalizedIdempotencyKey,
|
|
105
|
-
JSON.stringify(payload || {}),
|
|
106
|
-
safePriority,
|
|
107
|
-
scheduledValue,
|
|
108
|
-
safeMaxAttempts,
|
|
109
|
-
],
|
|
98
|
+
[normalizedTaskType, normalizedIdempotencyKey, JSON.stringify(payload || {}), safePriority, scheduledValue, safeMaxAttempts],
|
|
110
99
|
connection,
|
|
111
100
|
);
|
|
112
101
|
return true;
|
|
@@ -196,17 +185,13 @@ export async function completeWorkerTask(taskId, connection = null) {
|
|
|
196
185
|
return true;
|
|
197
186
|
}
|
|
198
187
|
|
|
199
|
-
export async function failWorkerTask(
|
|
200
|
-
taskId,
|
|
201
|
-
{
|
|
202
|
-
error = null,
|
|
203
|
-
retryDelaySeconds = 0,
|
|
204
|
-
} = {},
|
|
205
|
-
connection = null,
|
|
206
|
-
) {
|
|
188
|
+
export async function failWorkerTask(taskId, { error = null, retryDelaySeconds = 0 } = {}, connection = null) {
|
|
207
189
|
if (!taskId) return false;
|
|
208
190
|
const safeDelay = clampInt(retryDelaySeconds, 0, 0, 86400 * 7);
|
|
209
|
-
const message =
|
|
191
|
+
const message =
|
|
192
|
+
String(error || '')
|
|
193
|
+
.trim()
|
|
194
|
+
.slice(0, 255) || null;
|
|
210
195
|
|
|
211
196
|
await executeQuery(
|
|
212
197
|
`UPDATE ${TABLES.STICKER_WORKER_TASK_QUEUE}
|