@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
@@ -5,12 +5,8 @@ const parseEnvList = (value) =>
5
5
  .filter(Boolean);
6
6
 
7
7
  const VERIFIED_PUBLISHERS = new Set(parseEnvList(process.env.STICKER_CREATOR_VERIFIED_PUBLISHERS).map((entry) => entry.toLowerCase()));
8
- const NSFW_EXPLICIT_THRESHOLD = Number.isFinite(Number(process.env.STICKER_NSFW_EXPLICIT_THRESHOLD))
9
- ? Number(process.env.STICKER_NSFW_EXPLICIT_THRESHOLD)
10
- : 0.78;
11
- const NSFW_SUGGESTIVE_THRESHOLD = Number.isFinite(Number(process.env.STICKER_NSFW_SUGGESTIVE_THRESHOLD))
12
- ? Number(process.env.STICKER_NSFW_SUGGESTIVE_THRESHOLD)
13
- : 0.4;
8
+ const NSFW_EXPLICIT_THRESHOLD = Number.isFinite(Number(process.env.STICKER_NSFW_EXPLICIT_THRESHOLD)) ? Number(process.env.STICKER_NSFW_EXPLICIT_THRESHOLD) : 0.78;
9
+ const NSFW_SUGGESTIVE_THRESHOLD = Number.isFinite(Number(process.env.STICKER_NSFW_SUGGESTIVE_THRESHOLD)) ? Number(process.env.STICKER_NSFW_SUGGESTIVE_THRESHOLD) : 0.4;
14
10
  const AGE_DECAY_DAYS = Math.max(1, Number(process.env.STICKER_PACK_AGE_DECAY_DAYS) || 45);
15
11
 
16
12
  const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
@@ -165,16 +161,7 @@ const computeDuplicatePenalty = ({ itemClassifications = [], duplicateRate = 0 }
165
161
  return Number(clamp(semanticDuplicateRate * 0.7 + safeNumber(duplicateRate) * 0.3, 0, 1).toFixed(6));
166
162
  };
167
163
 
168
- export const computePackSignals = ({
169
- pack,
170
- engagement,
171
- packClassification,
172
- itemClassifications = [],
173
- interactionStats = null,
174
- duplicateRate = 0,
175
- scoringWeights = null,
176
- ageDecayDays = AGE_DECAY_DAYS,
177
- }) => {
164
+ export const computePackSignals = ({ pack, engagement, packClassification, itemClassifications = [], interactionStats = null, duplicateRate = 0, scoringWeights = null, ageDecayDays = AGE_DECAY_DAYS }) => {
178
165
  const resolvedWeights = {
179
166
  classification: clamp(safeNumber(scoringWeights?.classification, 0.4), 0.1, 0.7),
180
167
  engagement: clamp(safeNumber(scoringWeights?.engagement, 0.3), 0.1, 0.7),
@@ -194,12 +181,7 @@ export const computePackSignals = ({
194
181
  const trendScore = computeTrendScore(interactionStats);
195
182
  const nsfwLevel = resolveNsfwLevel(packClassification);
196
183
  const sensitiveContent = nsfwLevel !== 'safe';
197
- const packScoreRaw =
198
- classificationConfidence * resolvedWeights.classification +
199
- engagementScore * resolvedWeights.engagement +
200
- qualityScore * resolvedWeights.quality +
201
- diversityScore * resolvedWeights.diversity -
202
- duplicatePenalty * 0.25;
184
+ const packScoreRaw = classificationConfidence * resolvedWeights.classification + engagementScore * resolvedWeights.engagement + qualityScore * resolvedWeights.quality + diversityScore * resolvedWeights.diversity - duplicatePenalty * 0.25;
203
185
  const packScore = Number(clamp(packScoreRaw, 0, 1.5).toFixed(6));
204
186
  const referenceDate = pack?.updated_at || pack?.created_at || null;
205
187
  const ageMs = referenceDate ? Date.now() - Date.parse(referenceDate) : 0;
@@ -227,8 +209,7 @@ export const computePackSignals = ({
227
209
  };
228
210
  };
229
211
 
230
- const sortByScoreDesc = (list, field) =>
231
- [...list].sort((left, right) => safeNumber(right?.signals?.[field]) - safeNumber(left?.signals?.[field]));
212
+ const sortByScoreDesc = (list, field) => [...list].sort((left, right) => safeNumber(right?.signals?.[field]) - safeNumber(left?.signals?.[field]));
232
213
 
233
214
  const sortByUpdatedDesc = (list) =>
234
215
  [...list].sort((left, right) => {
@@ -246,14 +227,14 @@ export const buildIntentCollections = (entries, { limit = 18 } = {}) => {
246
227
  return {
247
228
  em_alta: pick(sortByScoreDesc(safeOnly, 'ranking_score')),
248
229
  novos: pick(sortByUpdatedDesc(safeOnly)),
249
- crescendo_agora: pick(sortByScoreDesc(all.filter((entry) => entry?.signals?.trending_now), 'trend_score')),
250
- mais_curtidos: pick([...safeOnly].sort((a, b) => safeNumber(b?.engagement?.like_count) - safeNumber(a?.engagement?.like_count))),
251
- melhor_avaliados: pick(
252
- [...safeOnly].sort(
253
- (a, b) =>
254
- safeNumber(b?.engagement?.like_count) - safeNumber(b?.engagement?.dislike_count) - (safeNumber(a?.engagement?.like_count) - safeNumber(a?.engagement?.dislike_count)),
230
+ crescendo_agora: pick(
231
+ sortByScoreDesc(
232
+ all.filter((entry) => entry?.signals?.trending_now),
233
+ 'trend_score',
255
234
  ),
256
235
  ),
236
+ mais_curtidos: pick([...safeOnly].sort((a, b) => safeNumber(b?.engagement?.like_count) - safeNumber(a?.engagement?.like_count))),
237
+ melhor_avaliados: pick([...safeOnly].sort((a, b) => safeNumber(b?.engagement?.like_count) - safeNumber(b?.engagement?.dislike_count) - (safeNumber(a?.engagement?.like_count) - safeNumber(a?.engagement?.dislike_count)))),
257
238
  };
258
239
  };
259
240
 
@@ -313,12 +294,7 @@ export const buildViewerTagAffinity = ({ viewerEntries = [], packClassificationB
313
294
  return affinity;
314
295
  };
315
296
 
316
- export const buildPersonalizedRecommendations = ({
317
- entries = [],
318
- viewerAffinity = new Map(),
319
- excludePackIds = new Set(),
320
- limit = 18,
321
- }) => {
297
+ export const buildPersonalizedRecommendations = ({ entries = [], viewerAffinity = new Map(), excludePackIds = new Set(), limit = 18 }) => {
322
298
  const safeLimit = Math.max(4, Math.min(50, Number(limit) || 18));
323
299
  const ranked = [];
324
300
  for (const entry of entries) {
@@ -17,7 +17,13 @@ const PACK_VISUAL_DIVIDER = '━━━━━━━━━━━━━━━━━
17
17
  * @param {unknown} value Valor de origem.
18
18
  * @returns {string[]} Emojis válidos.
19
19
  */
20
- const normalizeEmojis = (value) => (Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean).slice(0, 8) : []);
20
+ const normalizeEmojis = (value) =>
21
+ Array.isArray(value)
22
+ ? value
23
+ .map((item) => String(item))
24
+ .filter(Boolean)
25
+ .slice(0, 8)
26
+ : [];
21
27
 
22
28
  /**
23
29
  * Renderiza linha textual de um item para o preview fallback.
@@ -45,33 +51,9 @@ const buildPreviewText = ({ pack, items, sentCount }) => {
45
51
  previewLines.push(`... e mais ${items.length - MAX_PREVIEW_LIST_LINES} figurinha(s).`);
46
52
  }
47
53
 
48
- const compatibilityNote =
49
- sentCount < items.length
50
- ? `⚠️ Por compatibilidade, enviei ${sentCount}/${items.length} figurinha(s) neste fallback.`
51
- : `✅ Envio completo no fallback: ${sentCount}/${items.length} figurinha(s).`;
52
-
53
- return [
54
- '📦 *GERENCIADOR DE PACKS DE FIGURINHAS*',
55
- '',
56
- '📤 *ENVIO EM MODO DE COMPATIBILIDADE*',
57
- 'Seu cliente não aceitou o pack nativo, então enviei preview + figurinhas individuais.',
58
- '',
59
- PACK_VISUAL_DIVIDER,
60
- '📌 *RESUMO DO PACK*',
61
- '',
62
- `📛 Nome: *${pack.name}*`,
63
- `👤 Publisher: *${pack.publisher}*`,
64
- `🆔 ID: \`${pack.pack_key}\``,
65
- `🧩 Figurinhas disponíveis: *${items.length}*`,
66
- '',
67
- PACK_VISUAL_DIVIDER,
68
- '🖼 *PRÉVIA DAS FIGURINHAS*',
69
- '',
70
- previewLines.join('\n') || 'Nenhuma figurinha disponível para listar.',
71
- '',
72
- PACK_VISUAL_DIVIDER,
73
- compatibilityNote,
74
- ].join('\n');
54
+ const compatibilityNote = sentCount < items.length ? `⚠️ Por compatibilidade, enviei ${sentCount}/${items.length} figurinha(s) neste fallback.` : `✅ Envio completo no fallback: ${sentCount}/${items.length} figurinha(s).`;
55
+
56
+ return ['📦 *GERENCIADOR DE PACKS DE FIGURINHAS*', '', '📤 *ENVIO EM MODO DE COMPATIBILIDADE*', 'Seu cliente não aceitou o pack nativo, então enviei preview + figurinhas individuais.', '', PACK_VISUAL_DIVIDER, '📌 *RESUMO DO PACK*', '', `📛 Nome: *${pack.name}*`, `👤 Publisher: *${pack.publisher}*`, `🆔 ID: \`${pack.pack_key}\``, `🧩 Figurinhas disponíveis: *${items.length}*`, '', PACK_VISUAL_DIVIDER, '🖼 *PRÉVIA DAS FIGURINHAS*', '', previewLines.join('\n') || 'Nenhuma figurinha disponível para listar.', '', PACK_VISUAL_DIVIDER, compatibilityNote].join('\n');
75
57
  };
76
58
 
77
59
  /**
@@ -156,10 +138,7 @@ export async function buildStickerPackMessage(packDetails) {
156
138
  }
157
139
 
158
140
  if (!preparedItems.length) {
159
- throw new StickerPackError(
160
- STICKER_PACK_ERROR_CODES.STORAGE_ERROR,
161
- 'Nenhuma figurinha do pack está disponível para envio.',
162
- );
141
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.STORAGE_ERROR, 'Nenhuma figurinha do pack está disponível para envio.');
163
142
  }
164
143
 
165
144
  const coverItem = preparedItems.find((item) => item.sticker_id === pack.cover_sticker_id) || preparedItems[0];
@@ -206,14 +185,7 @@ export async function buildStickerPackMessage(packDetails) {
206
185
  * }} params Contexto de envio.
207
186
  * @returns {Promise<{ mode: 'native'|'fallback', sentCount: number, total?: number, nativeError?: string|null }>}
208
187
  */
209
- export async function sendStickerPackWithFallback({
210
- sock,
211
- jid,
212
- messageInfo,
213
- expirationMessage,
214
- packBuild,
215
- fallbackLimit = FALLBACK_SEND_LIMIT,
216
- }) {
188
+ export async function sendStickerPackWithFallback({ sock, jid, messageInfo, expirationMessage, packBuild, fallbackLimit = FALLBACK_SEND_LIMIT }) {
217
189
  const options = {
218
190
  quoted: messageInfo,
219
191
  ephemeralExpiration: expirationMessage,
@@ -86,11 +86,7 @@ export async function findStickerPackByPackKey(packKey, { includeDeleted = false
86
86
  * @param {{ includeDeleted?: boolean, connection?: import('mysql2/promise').PoolConnection|null }} [options]
87
87
  * @returns {Promise<object|null>} Pack encontrado.
88
88
  */
89
- export async function findStickerPackByOwnerAndIdentifier(
90
- ownerJid,
91
- identifier,
92
- { includeDeleted = false, connection = null } = {},
93
- ) {
89
+ export async function findStickerPackByOwnerAndIdentifier(ownerJid, identifier, { includeDeleted = false, connection = null } = {}) {
94
90
  if (!identifier) return null;
95
91
 
96
92
  const idOrPack = await executeQuery(
@@ -133,10 +129,7 @@ export async function findStickerPackByOwnerAndIdentifier(
133
129
  * @param {{ includeDeleted?: boolean, limit?: number, offset?: number, connection?: import('mysql2/promise').PoolConnection|null }} [options]
134
130
  * @returns {Promise<object[]>} Lista de packs.
135
131
  */
136
- export async function listStickerPacksByOwner(
137
- ownerJid,
138
- { includeDeleted = false, limit = 50, offset = 0, connection = null } = {},
139
- ) {
132
+ export async function listStickerPacksByOwner(ownerJid, { includeDeleted = false, limit = 50, offset = 0, connection = null } = {}) {
140
133
  const safeLimit = Math.max(1, Number(limit) || 50);
141
134
  const safeOffset = Math.max(0, Number(offset) || 0);
142
135
 
@@ -167,15 +160,7 @@ export async function listStickerPacksByOwner(
167
160
  * }} [options]
168
161
  * @returns {Promise<object[]>}
169
162
  */
170
- export async function listStickerAutoPacksForCuration({
171
- ownerJids = [],
172
- includeArchived = true,
173
- themeKey = '',
174
- includeLegacyMarkers = true,
175
- limit = 2000,
176
- offset = 0,
177
- connection = null,
178
- } = {}) {
163
+ export async function listStickerAutoPacksForCuration({ ownerJids = [], includeArchived = true, themeKey = '', includeLegacyMarkers = true, limit = 2000, offset = 0, connection = null } = {}) {
179
164
  const owners = Array.from(new Set((Array.isArray(ownerJids) ? ownerJids : []).filter(Boolean)));
180
165
  if (!owners.length) return [];
181
166
 
@@ -183,10 +168,7 @@ export async function listStickerAutoPacksForCuration({
183
168
  const safeOffset = Math.max(0, Number(offset) || 0);
184
169
  const ownerPlaceholders = owners.map(() => '?').join(', ');
185
170
 
186
- const whereClauses = [
187
- 'p.deleted_at IS NULL',
188
- `p.owner_jid IN (${ownerPlaceholders})`,
189
- ];
171
+ const whereClauses = ['p.deleted_at IS NULL', `p.owner_jid IN (${ownerPlaceholders})`];
190
172
  const params = [...owners];
191
173
 
192
174
  if (includeLegacyMarkers) {
@@ -195,9 +177,11 @@ export async function listStickerAutoPacksForCuration({
195
177
  whereClauses.push('p.is_auto_pack = 1');
196
178
  }
197
179
 
198
- const normalizedThemeKey = String(themeKey || '').trim().toLowerCase();
180
+ const normalizedThemeKey = String(themeKey || '')
181
+ .trim()
182
+ .toLowerCase();
199
183
  if (normalizedThemeKey) {
200
- whereClauses.push('LOWER(COALESCE(p.pack_theme_key, \'\')) = ?');
184
+ whereClauses.push("LOWER(COALESCE(p.pack_theme_key, '')) = ?");
201
185
  params.push(normalizedThemeKey);
202
186
  }
203
187
 
@@ -234,39 +218,26 @@ export async function listStickerAutoPacksForCuration({
234
218
  * }} [options] Filtros de listagem.
235
219
  * @returns {Promise<{ packs: object[], hasMore: boolean }>} Resultado paginado.
236
220
  */
237
- export async function listStickerPacksForCatalog({
238
- visibility = 'public',
239
- search = '',
240
- limit = 24,
241
- offset = 0,
242
- connection = null,
243
- } = {}) {
221
+ export async function listStickerPacksForCatalog({ visibility = 'public', search = '', limit = 24, offset = 0, connection = null } = {}) {
244
222
  const safeLimit = Math.max(1, Math.min(60, Number(limit) || 24));
245
223
  const safeOffset = Math.max(0, Number(offset) || 0);
246
224
  const safeLimitWithSentinel = safeLimit + 1;
247
225
 
248
- const normalizedVisibility = String(visibility || 'public').trim().toLowerCase();
249
- const visibilityValues =
250
- normalizedVisibility === 'all'
251
- ? ['public', 'unlisted']
252
- : normalizedVisibility === 'unlisted'
253
- ? ['unlisted']
254
- : ['public'];
226
+ const normalizedVisibility = String(visibility || 'public')
227
+ .trim()
228
+ .toLowerCase();
229
+ const visibilityValues = normalizedVisibility === 'all' ? ['public', 'unlisted'] : normalizedVisibility === 'unlisted' ? ['unlisted'] : ['public'];
255
230
 
256
- const normalizedSearch = String(search || '').trim().toLowerCase().slice(0, 120);
231
+ const normalizedSearch = String(search || '')
232
+ .trim()
233
+ .toLowerCase()
234
+ .slice(0, 120);
257
235
  const params = [...visibilityValues];
258
- const whereClauses = [
259
- 'p.deleted_at IS NULL',
260
- "p.status = 'published'",
261
- "COALESCE(p.pack_status, 'ready') = 'ready'",
262
- `p.visibility IN (${visibilityValues.map(() => '?').join(', ')})`,
263
- ];
236
+ const whereClauses = ['p.deleted_at IS NULL', "p.status = 'published'", "COALESCE(p.pack_status, 'ready') = 'ready'", `p.visibility IN (${visibilityValues.map(() => '?').join(', ')})`];
264
237
 
265
238
  if (normalizedSearch) {
266
239
  const like = `%${normalizedSearch}%`;
267
- whereClauses.push(
268
- '(LOWER(p.name) LIKE ? OR LOWER(p.publisher) LIKE ? OR LOWER(COALESCE(p.description, \'\')) LIKE ? OR LOWER(p.pack_key) LIKE ?)',
269
- );
240
+ whereClauses.push("(LOWER(p.name) LIKE ? OR LOWER(p.publisher) LIKE ? OR LOWER(COALESCE(p.description, '')) LIKE ? OR LOWER(p.pack_key) LIKE ?)");
270
241
  params.push(like, like, like, like);
271
242
  }
272
243
 
@@ -319,23 +290,7 @@ export async function createStickerPack(pack, connection = null) {
319
290
  version
320
291
  )
321
292
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
322
- [
323
- pack.id,
324
- pack.owner_jid,
325
- pack.name,
326
- pack.publisher,
327
- pack.description ?? null,
328
- pack.pack_key,
329
- pack.cover_sticker_id ?? null,
330
- pack.visibility,
331
- pack.status ?? 'published',
332
- pack.pack_status ?? 'ready',
333
- pack.pack_theme_key ?? null,
334
- pack.pack_volume ?? null,
335
- pack.is_auto_pack ? 1 : 0,
336
- pack.last_rebalanced_at ?? null,
337
- pack.version ?? 1,
338
- ],
293
+ [pack.id, pack.owner_jid, pack.name, pack.publisher, pack.description ?? null, pack.pack_key, pack.cover_sticker_id ?? null, pack.visibility, pack.status ?? 'published', pack.pack_status ?? 'ready', pack.pack_theme_key ?? null, pack.pack_volume ?? null, pack.is_auto_pack ? 1 : 0, pack.last_rebalanced_at ?? null, pack.version ?? 1],
339
294
  connection,
340
295
  );
341
296
 
@@ -420,7 +375,9 @@ export async function updateStickerPackFields(packId, fields, connection = null)
420
375
  fields: Object.keys(fields || {}).slice(0, 30),
421
376
  },
422
377
  priority: 70,
423
- idempotencyKey: `pack_updated:${packId}:${Object.keys(fields || {}).sort().join(',')}`,
378
+ idempotencyKey: `pack_updated:${packId}:${Object.keys(fields || {})
379
+ .sort()
380
+ .join(',')}`,
424
381
  },
425
382
  { connection },
426
383
  );
@@ -27,7 +27,9 @@ const clampScore = (value) => {
27
27
  };
28
28
 
29
29
  const normalizeNsfwLevel = (value) => {
30
- const normalized = String(value || '').trim().toLowerCase();
30
+ const normalized = String(value || '')
31
+ .trim()
32
+ .toLowerCase();
31
33
  if (['safe', 'suggestive', 'explicit'].includes(normalized)) return normalized;
32
34
  return 'safe';
33
35
  };
@@ -82,37 +84,23 @@ const normalizeSnapshotInput = (entry) => {
82
84
  nsfw_level: normalizeNsfwLevel(signals.nsfw_level),
83
85
  sticker_count: Math.max(0, Number(entry.sticker_count || 0)),
84
86
  tags: Array.isArray(entry.tags) ? entry.tags.slice(0, 30) : [],
85
- source_version: String(entry.source_version || 'v1').trim().slice(0, 32) || 'v1',
87
+ source_version:
88
+ String(entry.source_version || 'v1')
89
+ .trim()
90
+ .slice(0, 32) || 'v1',
86
91
  scores_json: signals,
87
92
  };
88
93
  };
89
94
 
90
95
  export async function upsertStickerPackScoreSnapshots(entries = [], connection = null) {
91
- const normalized = (Array.isArray(entries) ? entries : [])
92
- .map((entry) => normalizeSnapshotInput(entry))
93
- .filter(Boolean);
96
+ const normalized = (Array.isArray(entries) ? entries : []).map((entry) => normalizeSnapshotInput(entry)).filter(Boolean);
94
97
  if (!normalized.length) return 0;
95
98
 
96
99
  let written = 0;
97
100
  for (let offset = 0; offset < normalized.length; offset += 100) {
98
101
  const chunk = normalized.slice(offset, offset + 100);
99
102
  const placeholders = chunk.map(() => '(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())').join(', ');
100
- const params = chunk.flatMap((entry) => [
101
- entry.pack_id,
102
- entry.ranking_score,
103
- entry.pack_score,
104
- entry.trend_score,
105
- entry.quality_score,
106
- entry.engagement_score,
107
- entry.diversity_score,
108
- entry.cohesion_score,
109
- entry.sensitive_content,
110
- entry.nsfw_level,
111
- entry.sticker_count,
112
- JSON.stringify(entry.tags || []),
113
- JSON.stringify(entry.scores_json || {}),
114
- entry.source_version,
115
- ]);
103
+ const params = chunk.flatMap((entry) => [entry.pack_id, entry.ranking_score, entry.pack_score, entry.trend_score, entry.quality_score, entry.engagement_score, entry.diversity_score, entry.cohesion_score, entry.sensitive_content, entry.nsfw_level, entry.sticker_count, JSON.stringify(entry.tags || []), JSON.stringify(entry.scores_json || {}), entry.source_version]);
116
104
  const result = await executeQuery(
117
105
  `INSERT INTO ${TABLES.STICKER_PACK_SCORE_SNAPSHOT}
118
106
  (
@@ -1,19 +1,13 @@
1
1
  import { executeQuery, TABLES } from '../../../database/index.js';
2
2
  import logger from '../../utils/logger/loggerModule.js';
3
- import {
4
- getEmptyStickerPackEngagement,
5
- getStickerPackEngagementByPackId,
6
- } from './stickerPackEngagementRepository.js';
3
+ import { getEmptyStickerPackEngagement, getStickerPackEngagementByPackId } from './stickerPackEngagementRepository.js';
7
4
  import { listStickerPackItems } from './stickerPackItemRepository.js';
8
5
  import { listStickerClassificationsByAssetIds } from './stickerAssetClassificationRepository.js';
9
6
  import { getPackClassificationSummaryByAssetIds } from './stickerClassificationService.js';
10
7
  import { listStickerPackInteractionStatsByPackIds } from './stickerPackInteractionEventRepository.js';
11
8
  import { getMarketplaceDriftSnapshot } from './stickerMarketplaceDriftService.js';
12
9
  import { computePackSignals } from './stickerPackMarketplaceService.js';
13
- import {
14
- removeSnapshotsForDeletedPacks,
15
- upsertStickerPackScoreSnapshots,
16
- } from './stickerPackScoreSnapshotRepository.js';
10
+ import { removeSnapshotsForDeletedPacks, upsertStickerPackScoreSnapshots } from './stickerPackScoreSnapshotRepository.js';
17
11
  import { setQueueDepth } from '../../observability/metrics.js';
18
12
 
19
13
  const parseEnvBool = (value, fallback) => {
@@ -25,31 +19,13 @@ const parseEnvBool = (value, fallback) => {
25
19
  };
26
20
 
27
21
  const SNAPSHOT_ENABLED = parseEnvBool(process.env.STICKER_SCORE_SNAPSHOT_ENABLED, true);
28
- const SNAPSHOT_STARTUP_DELAY_MS = Math.max(
29
- 1_000,
30
- Number(process.env.STICKER_SCORE_SNAPSHOT_STARTUP_DELAY_MS) || 20_000,
31
- );
32
- const SNAPSHOT_REFRESH_INTERVAL_MS = Math.max(
33
- 30_000,
34
- Number(process.env.STICKER_SCORE_SNAPSHOT_REFRESH_INTERVAL_MS) || 5 * 60_000,
35
- );
36
- const SNAPSHOT_BATCH_SIZE = Math.max(
37
- 10,
38
- Math.min(500, Number(process.env.STICKER_SCORE_SNAPSHOT_BATCH_SIZE) || 120),
39
- );
40
- const SNAPSHOT_TARGETED_BATCH_SIZE = Math.max(
41
- 5,
42
- Math.min(200, Number(process.env.STICKER_SCORE_SNAPSHOT_TARGETED_BATCH_SIZE) || 60),
43
- );
22
+ const SNAPSHOT_STARTUP_DELAY_MS = Math.max(1_000, Number(process.env.STICKER_SCORE_SNAPSHOT_STARTUP_DELAY_MS) || 20_000);
23
+ const SNAPSHOT_REFRESH_INTERVAL_MS = Math.max(30_000, Number(process.env.STICKER_SCORE_SNAPSHOT_REFRESH_INTERVAL_MS) || 5 * 60_000);
24
+ const SNAPSHOT_BATCH_SIZE = Math.max(10, Math.min(500, Number(process.env.STICKER_SCORE_SNAPSHOT_BATCH_SIZE) || 120));
25
+ const SNAPSHOT_TARGETED_BATCH_SIZE = Math.max(5, Math.min(200, Number(process.env.STICKER_SCORE_SNAPSHOT_TARGETED_BATCH_SIZE) || 60));
44
26
  const SNAPSHOT_SOURCE_VERSION = String(process.env.STICKER_SCORE_SNAPSHOT_SOURCE_VERSION || 'v1').trim() || 'v1';
45
- const SNAPSHOT_MAX_PENDING_PACKS = Math.max(
46
- 20,
47
- Math.min(20_000, Number(process.env.STICKER_SCORE_SNAPSHOT_MAX_PENDING_PACKS) || 2_000),
48
- );
49
- const SNAPSHOT_FULL_REBUILD_EVERY_CYCLES = Math.max(
50
- 1,
51
- Math.min(500, Number(process.env.STICKER_SCORE_SNAPSHOT_FULL_REBUILD_EVERY_CYCLES) || 12),
52
- );
27
+ const SNAPSHOT_MAX_PENDING_PACKS = Math.max(20, Math.min(20_000, Number(process.env.STICKER_SCORE_SNAPSHOT_MAX_PENDING_PACKS) || 2_000));
28
+ const SNAPSHOT_FULL_REBUILD_EVERY_CYCLES = Math.max(1, Math.min(500, Number(process.env.STICKER_SCORE_SNAPSHOT_FULL_REBUILD_EVERY_CYCLES) || 12));
53
29
 
54
30
  let startupHandle = null;
55
31
  let cycleHandle = null;
@@ -94,12 +70,7 @@ const listPacksByIdsForSnapshot = async (packIds = []) => {
94
70
  const buildSnapshotForPack = async ({ pack, driftWeights }) => {
95
71
  const items = await listStickerPackItems(pack.id);
96
72
  const stickerIds = items.map((item) => item.sticker_id).filter(Boolean);
97
- const [packClassification, itemClassifications, engagement, interactionStatsByPackId] = await Promise.all([
98
- getPackClassificationSummaryByAssetIds(stickerIds),
99
- stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([]),
100
- getStickerPackEngagementByPackId(pack.id),
101
- listStickerPackInteractionStatsByPackIds([pack.id]),
102
- ]);
73
+ const [packClassification, itemClassifications, engagement, interactionStatsByPackId] = await Promise.all([getPackClassificationSummaryByAssetIds(stickerIds), stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([]), getStickerPackEngagementByPackId(pack.id), listStickerPackInteractionStatsByPackIds([pack.id])]);
103
74
 
104
75
  const byAssetId = new Map((Array.isArray(itemClassifications) ? itemClassifications : []).map((entry) => [entry.asset_id, entry]));
105
76
  const orderedClassifications = stickerIds.map((id) => byAssetId.get(id)).filter(Boolean);
@@ -199,8 +170,7 @@ export const runStickerPackScoreSnapshotCycle = async () => {
199
170
  }
200
171
 
201
172
  cycleCounter += 1;
202
- fullRebuildExecuted =
203
- cycleCounter % SNAPSHOT_FULL_REBUILD_EVERY_CYCLES === 0 || targetedPackIds.length === 0;
173
+ fullRebuildExecuted = cycleCounter % SNAPSHOT_FULL_REBUILD_EVERY_CYCLES === 0 || targetedPackIds.length === 0;
204
174
 
205
175
  if (fullRebuildExecuted) {
206
176
  let offset = 0;