@kaikybrofc/omnizap-system 2.2.8 → 2.2.10

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 (33) hide show
  1. package/README.md +1 -1
  2. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +194 -0
  3. package/docs/seo/satellite-page-template.md +89 -0
  4. package/docs/seo/satellite-pages-phase1.json +486 -0
  5. package/package.json +3 -1
  6. package/public/api-docs/index.html +78 -22
  7. package/public/bot-whatsapp-para-grupo/index.html +276 -0
  8. package/public/bot-whatsapp-sem-programar/index.html +276 -0
  9. package/public/comandos/index.html +413 -0
  10. package/public/como-automatizar-avisos-no-whatsapp/index.html +276 -0
  11. package/public/como-criar-comandos-whatsapp/index.html +276 -0
  12. package/public/como-evitar-spam-no-whatsapp/index.html +276 -0
  13. package/public/como-moderar-grupo-whatsapp/index.html +276 -0
  14. package/public/como-organizar-comunidade-whatsapp/index.html +276 -0
  15. package/public/css/github-project-panel.css +8 -8
  16. package/public/css/stickers-admin.css +31 -31
  17. package/public/css/styles.css +17 -16
  18. package/public/index.html +701 -1181
  19. package/public/js/apps/apiDocsApp.js +39 -6
  20. package/public/js/apps/homeApp.js +157 -410
  21. package/public/js/apps/stickersApp.js +42 -0
  22. package/public/licenca/index.html +9 -9
  23. package/public/login/index.html +26 -22
  24. package/public/melhor-bot-whatsapp-para-grupos/index.html +276 -0
  25. package/public/sitemap.xml +45 -0
  26. package/public/stickers/create/index.html +7 -6
  27. package/public/stickers/index.html +72 -5
  28. package/public/termos-de-uso/index.html +10 -10
  29. package/public/user/index.html +25 -21
  30. package/scripts/generate-seo-satellite-pages.mjs +434 -0
  31. package/server/controllers/stickerCatalogController.js +341 -700
  32. package/kaikybrofc-omnizap-system-2.2.7.tgz +0 -0
  33. package/kaikybrofc-omnizap-system-2.2.8.tgz +0 -0
@@ -9,85 +9,22 @@ import { getJidUser, normalizeJid, resolveBotJid } from '../../app/config/bailey
9
9
  import { getAdminPhone, getAdminRawValue, resolveAdminJid } from '../../app/config/adminIdentity.js';
10
10
  import { getActiveSocket } from '../../app/services/socketState.js';
11
11
  import { extractUserIdInfo, resolveUserId } from '../../app/services/lidMapService.js';
12
- import {
13
- resolveWhatsAppOwnerJidFromLoginPayload,
14
- toWhatsAppOwnerJid,
15
- toWhatsAppPhoneDigits,
16
- } from '../../app/services/whatsappLoginLinkService.js';
12
+ import { resolveWhatsAppOwnerJidFromLoginPayload, toWhatsAppOwnerJid, toWhatsAppPhoneDigits } from '../../app/services/whatsappLoginLinkService.js';
17
13
  import logger from '../../app/utils/logger/loggerModule.js';
18
14
  import { getSystemMetrics } from '../../app/utils/systemMetrics/systemMetricsModule.js';
19
- import {
20
- listStickerPacksForCatalog,
21
- findStickerPackByPackKey,
22
- listStickerPacksByOwner,
23
- bumpStickerPackVersion,
24
- findStickerPackByOwnerAndIdentifier,
25
- softDeleteStickerPack,
26
- updateStickerPackFields,
27
- } from '../../app/modules/stickerPackModule/stickerPackRepository.js';
28
- import {
29
- listStickerPackItems,
30
- countStickerPackItemRefsByStickerId,
31
- createStickerPackItem,
32
- getStickerPackItemByStickerId,
33
- removeStickerPackItemByStickerId,
34
- removeStickerPackItemsByPackId,
35
- } from '../../app/modules/stickerPackModule/stickerPackItemRepository.js';
36
- import {
37
- listClassifiedStickerAssetsWithoutPack,
38
- listStickerAssetsWithoutPack,
39
- deleteStickerAssetById,
40
- findStickerAssetsByIds,
41
- } from '../../app/modules/stickerPackModule/stickerAssetRepository.js';
42
- import {
43
- deleteStickerAssetClassificationByAssetId,
44
- findStickerClassificationByAssetId,
45
- listStickerClassificationsByAssetIds,
46
- } from '../../app/modules/stickerPackModule/stickerAssetClassificationRepository.js';
47
- import {
48
- decoratePackClassificationSummary,
49
- decorateStickerClassification,
50
- getPackClassificationSummaryByAssetIds,
51
- } from '../../app/modules/stickerPackModule/stickerClassificationService.js';
52
- import {
53
- getEmptyStickerPackEngagement,
54
- getStickerPackEngagementByPackId,
55
- incrementStickerPackDislike,
56
- incrementStickerPackLike,
57
- incrementStickerPackOpen,
58
- listStickerPackEngagementByPackIds,
59
- } from '../../app/modules/stickerPackModule/stickerPackEngagementRepository.js';
60
- import {
61
- createStickerPackInteractionEvent,
62
- listStickerPackInteractionStatsByPackIds,
63
- listViewerRecentPackIds,
64
- } from '../../app/modules/stickerPackModule/stickerPackInteractionEventRepository.js';
65
- import {
66
- buildCreatorRanking,
67
- buildIntentCollections,
68
- buildPersonalizedRecommendations,
69
- buildViewerTagAffinity,
70
- computePackSignals,
71
- } from '../../app/modules/stickerPackModule/stickerPackMarketplaceService.js';
15
+ import { listStickerPacksForCatalog, findStickerPackByPackKey, listStickerPacksByOwner, bumpStickerPackVersion, findStickerPackByOwnerAndIdentifier, softDeleteStickerPack, updateStickerPackFields } from '../../app/modules/stickerPackModule/stickerPackRepository.js';
16
+ import { listStickerPackItems, countStickerPackItemRefsByStickerId, createStickerPackItem, getStickerPackItemByStickerId, removeStickerPackItemByStickerId, removeStickerPackItemsByPackId } from '../../app/modules/stickerPackModule/stickerPackItemRepository.js';
17
+ import { listClassifiedStickerAssetsWithoutPack, listStickerAssetsWithoutPack, deleteStickerAssetById, findStickerAssetsByIds } from '../../app/modules/stickerPackModule/stickerAssetRepository.js';
18
+ import { deleteStickerAssetClassificationByAssetId, findStickerClassificationByAssetId, listStickerClassificationsByAssetIds } from '../../app/modules/stickerPackModule/stickerAssetClassificationRepository.js';
19
+ import { decoratePackClassificationSummary, decorateStickerClassification, getPackClassificationSummaryByAssetIds } from '../../app/modules/stickerPackModule/stickerClassificationService.js';
20
+ import { getEmptyStickerPackEngagement, getStickerPackEngagementByPackId, incrementStickerPackDislike, incrementStickerPackLike, incrementStickerPackOpen, listStickerPackEngagementByPackIds } from '../../app/modules/stickerPackModule/stickerPackEngagementRepository.js';
21
+ import { createStickerPackInteractionEvent, listStickerPackInteractionStatsByPackIds, listViewerRecentPackIds } from '../../app/modules/stickerPackModule/stickerPackInteractionEventRepository.js';
22
+ import { buildCreatorRanking, buildIntentCollections, buildPersonalizedRecommendations, buildViewerTagAffinity, computePackSignals } from '../../app/modules/stickerPackModule/stickerPackMarketplaceService.js';
72
23
  import { listStickerPackScoreSnapshotsByPackIds } from '../../app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js';
73
24
  import { createCatalogApiRouter } from '../routes/stickerCatalog/catalogRouter.js';
74
- import {
75
- buildAdminMenu,
76
- buildAiMenu,
77
- buildAnimeMenu,
78
- buildMediaMenu,
79
- buildMenuCaption,
80
- buildQuoteMenu,
81
- buildStatsMenu,
82
- buildStickerMenu,
83
- } from '../../app/modules/menuModule/common.js';
25
+ import { buildAdminMenu, buildAiMenu, buildAnimeMenu, buildMediaMenu, buildMenuCaption, buildQuoteMenu, buildStatsMenu, buildStickerMenu } from '../../app/modules/menuModule/common.js';
84
26
  import { getMarketplaceDriftSnapshot } from '../../app/modules/stickerPackModule/stickerMarketplaceDriftService.js';
85
- import {
86
- getStickerAssetExternalUrl,
87
- getStickerStorageConfig,
88
- readStickerAssetBuffer,
89
- saveStickerAssetFromBuffer,
90
- } from '../../app/modules/stickerPackModule/stickerStorageService.js';
27
+ import { getStickerAssetExternalUrl, getStickerStorageConfig, readStickerAssetBuffer, saveStickerAssetFromBuffer } from '../../app/modules/stickerPackModule/stickerStorageService.js';
91
28
  import { convertToWebp } from '../../app/modules/stickerModule/convertToWebp.js';
92
29
  import { sanitizeText } from '../../app/modules/stickerPackModule/stickerPackUtils.js';
93
30
  import stickerPackService from '../../app/modules/stickerPackModule/stickerPackServiceRuntime.js';
@@ -105,22 +42,23 @@ const parseEnvBool = (value, fallback) => {
105
42
  export const normalizeBasePath = (value, fallback) => {
106
43
  const raw = String(value || '').trim() || fallback;
107
44
  const withLeadingSlash = raw.startsWith('/') ? raw : `/${raw}`;
108
- const withoutTrailingSlash =
109
- withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/')
110
- ? withLeadingSlash.slice(0, -1)
111
- : withLeadingSlash;
45
+ const withoutTrailingSlash = withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/') ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
112
46
  return withoutTrailingSlash || fallback;
113
47
  };
114
48
 
115
49
  export const normalizeCatalogVisibility = (value) => {
116
- const normalized = String(value || '').trim().toLowerCase();
50
+ const normalized = String(value || '')
51
+ .trim()
52
+ .toLowerCase();
117
53
  if (normalized === 'all') return 'all';
118
54
  if (normalized === 'unlisted') return 'unlisted';
119
55
  return 'public';
120
56
  };
121
57
 
122
58
  const normalizeCatalogSortParam = (value) => {
123
- const normalized = String(value || '').trim().toLowerCase();
59
+ const normalized = String(value || '')
60
+ .trim()
61
+ .toLowerCase();
124
62
  if (normalized === 'new') return 'recent';
125
63
  if (normalized === 'liked') return 'likes';
126
64
  if (normalized === 'popular') return 'trending';
@@ -128,7 +66,10 @@ const normalizeCatalogSortParam = (value) => {
128
66
  return 'popular';
129
67
  };
130
68
 
131
- export const stripWebpExtension = (value) => String(value || '').trim().replace(/\.webp$/i, '');
69
+ export const stripWebpExtension = (value) =>
70
+ String(value || '')
71
+ .trim()
72
+ .replace(/\.webp$/i, '');
132
73
 
133
74
  const clampInt = (value, fallback, min, max) => {
134
75
  const parsed = Number(value);
@@ -176,22 +117,10 @@ const MAX_ORPHAN_LIST_LIMIT = clampInt(process.env.STICKER_ORPHAN_LIST_MAX_LIMIT
176
117
  const DEFAULT_DATA_LIST_LIMIT = clampInt(process.env.STICKER_DATA_LIST_LIMIT, 50, 1, 200);
177
118
  const MAX_DATA_LIST_LIMIT = clampInt(process.env.STICKER_DATA_LIST_MAX_LIMIT, 200, 1, 500);
178
119
  const MAX_DATA_SCAN_FILES = clampInt(process.env.STICKER_DATA_SCAN_MAX_FILES, 10000, 100, 50000);
179
- const ASSET_CACHE_SECONDS = clampInt(
180
- process.env.STICKER_WEB_ASSET_CACHE_SECONDS,
181
- 60 * 60 * 24 * 30,
182
- 60 * 60,
183
- 60 * 60 * 24 * 365,
184
- );
120
+ const ASSET_CACHE_SECONDS = clampInt(process.env.STICKER_WEB_ASSET_CACHE_SECONDS, 60 * 60 * 24 * 30, 60 * 60, 60 * 60 * 24 * 365);
185
121
  const STATIC_TEXT_CACHE_SECONDS = clampInt(process.env.STICKER_WEB_STATIC_TEXT_CACHE_SECONDS, 60 * 60, 60, 60 * 60 * 24 * 30);
186
- const IMMUTABLE_ASSET_CACHE_SECONDS = clampInt(
187
- process.env.STICKER_WEB_IMMUTABLE_ASSET_CACHE_SECONDS,
188
- 60 * 60 * 24 * 365,
189
- 60 * 60,
190
- 60 * 60 * 24 * 365,
191
- );
192
- const STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE =
193
- String(process.env.STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE || '/pack send {{pack_key}}').trim() ||
194
- '/pack send {{pack_key}}';
122
+ const IMMUTABLE_ASSET_CACHE_SECONDS = clampInt(process.env.STICKER_WEB_IMMUTABLE_ASSET_CACHE_SECONDS, 60 * 60 * 24 * 365, 60 * 60, 60 * 60 * 24 * 365);
123
+ const STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE = String(process.env.STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE || '/pack send {{pack_key}}').trim() || '/pack send {{pack_key}}';
195
124
  const PACK_COMMAND_PREFIX = String(process.env.COMMAND_PREFIX || '/').trim() || '/';
196
125
  const PACK_CREATE_NAME_REGEX = '^[\\s\\S]+$';
197
126
  const PACK_CREATE_MAX_NAME_LENGTH = 120;
@@ -201,47 +130,25 @@ const PACK_CREATE_MAX_ITEMS = Math.max(1, Number(process.env.STICKER_PACK_MAX_IT
201
130
  const PACK_CREATE_MAX_PACKS_PER_OWNER = parseMaxPacksPerOwnerLimit(process.env.STICKER_PACK_MAX_PACKS_PER_OWNER, 50);
202
131
  const PACK_WEB_EDIT_TOKEN_TTL_MS = Math.max(60_000, Number(process.env.STICKER_WEB_EDIT_TOKEN_TTL_MS) || 6 * 60 * 60 * 1000);
203
132
  const STICKER_WEB_GOOGLE_CLIENT_ID = String(process.env.STICKER_WEB_GOOGLE_CLIENT_ID || '').trim();
204
- const ADMIN_PANEL_EMAIL = String(process.env.ADM_EMAIL || '').trim().toLowerCase();
133
+ const ADMIN_PANEL_EMAIL = String(process.env.ADM_EMAIL || '')
134
+ .trim()
135
+ .toLowerCase();
205
136
  const ADMIN_PANEL_PASSWORD = String(process.env.ADM_PANEL_PASSWORD || process.env.ADM_PANEL || '').trim();
206
137
  const ADMIN_PANEL_ENABLED = Boolean(ADMIN_PANEL_EMAIL && ADMIN_PANEL_PASSWORD);
207
- const ADMIN_PANEL_SESSION_TTL_MS = Math.max(
208
- 10 * 60 * 1000,
209
- Number(process.env.ADM_PANEL_SESSION_TTL_MS) || 12 * 60 * 60 * 1000,
210
- );
211
- const ADMIN_MODERATOR_PASSWORD_MIN_LENGTH = Math.max(
212
- 6,
213
- Number(process.env.ADM_MODERATOR_PASSWORD_MIN_LENGTH) || 8,
214
- );
215
- const STICKER_WEB_GOOGLE_AUTH_REQUIRED = parseEnvBool(
216
- process.env.STICKER_WEB_GOOGLE_AUTH_REQUIRED,
217
- Boolean(STICKER_WEB_GOOGLE_CLIENT_ID),
218
- );
219
- const STICKER_WEB_GOOGLE_SESSION_TTL_MS = Math.max(
220
- 5 * 60 * 1000,
221
- Number(process.env.STICKER_WEB_GOOGLE_SESSION_TTL_MS) || 7 * 24 * 60 * 60 * 1000,
222
- );
138
+ const ADMIN_PANEL_SESSION_TTL_MS = Math.max(10 * 60 * 1000, Number(process.env.ADM_PANEL_SESSION_TTL_MS) || 12 * 60 * 60 * 1000);
139
+ const ADMIN_MODERATOR_PASSWORD_MIN_LENGTH = Math.max(6, Number(process.env.ADM_MODERATOR_PASSWORD_MIN_LENGTH) || 8);
140
+ const STICKER_WEB_GOOGLE_AUTH_REQUIRED = parseEnvBool(process.env.STICKER_WEB_GOOGLE_AUTH_REQUIRED, Boolean(STICKER_WEB_GOOGLE_CLIENT_ID));
141
+ const STICKER_WEB_GOOGLE_SESSION_TTL_MS = Math.max(5 * 60 * 1000, Number(process.env.STICKER_WEB_GOOGLE_SESSION_TTL_MS) || 7 * 24 * 60 * 60 * 1000);
223
142
  const STICKER_CATALOG_ONLY_CLASSIFIED = parseEnvBool(process.env.STICKER_CATALOG_ONLY_CLASSIFIED, true);
224
- const METRICS_ENDPOINT =
225
- process.env.METRICS_ENDPOINT ||
226
- `http://127.0.0.1:${process.env.METRICS_PORT || 9102}${process.env.METRICS_PATH || '/metrics'}`;
143
+ const METRICS_ENDPOINT = process.env.METRICS_ENDPOINT || `http://127.0.0.1:${process.env.METRICS_PORT || 9102}${process.env.METRICS_PATH || '/metrics'}`;
227
144
  const METRICS_SUMMARY_TIMEOUT_MS = clampInt(process.env.STICKER_SYSTEM_METRICS_TIMEOUT_MS, 1200, 300, 5000);
228
145
  const GITHUB_REPOSITORY = String(process.env.GITHUB_REPOSITORY || 'Kaikygr/omnizap-system').trim();
229
146
  const GITHUB_TOKEN = String(process.env.GITHUB_TOKEN || '').trim();
230
147
  const GITHUB_PROJECT_CACHE_SECONDS = clampInt(process.env.GITHUB_PROJECT_CACHE_SECONDS, 300, 30, 3600);
231
148
  const GLOBAL_RANK_REFRESH_SECONDS = clampInt(process.env.GLOBAL_RANK_REFRESH_SECONDS, 600, 60, 3600);
232
149
  const CATALOG_LIST_CACHE_SECONDS = clampInt(process.env.STICKER_CATALOG_LIST_CACHE_SECONDS, 90, 15, 900);
233
- const CATALOG_CREATOR_RANKING_CACHE_SECONDS = clampInt(
234
- process.env.STICKER_CATALOG_CREATOR_RANKING_CACHE_SECONDS,
235
- 120,
236
- 15,
237
- 900,
238
- );
239
- const CATALOG_PACK_PAYLOAD_CACHE_SECONDS = clampInt(
240
- process.env.STICKER_CATALOG_PACK_PAYLOAD_CACHE_SECONDS,
241
- 300,
242
- 30,
243
- 1800,
244
- );
150
+ const CATALOG_CREATOR_RANKING_CACHE_SECONDS = clampInt(process.env.STICKER_CATALOG_CREATOR_RANKING_CACHE_SECONDS, 120, 15, 900);
151
+ const CATALOG_PACK_PAYLOAD_CACHE_SECONDS = clampInt(process.env.STICKER_CATALOG_PACK_PAYLOAD_CACHE_SECONDS, 300, 30, 1800);
245
152
  const MARKETPLACE_GLOBAL_STATS_API_PATH = '/api/marketplace/stats';
246
153
  const MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS = clampInt(process.env.MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS, 45, 30, 60);
247
154
  const HOME_MARKETPLACE_STATS_CACHE_SECONDS = clampInt(process.env.HOME_MARKETPLACE_STATS_CACHE_SECONDS, 45, 10, 300);
@@ -249,10 +156,16 @@ const SYSTEM_SUMMARY_CACHE_SECONDS = clampInt(process.env.SYSTEM_SUMMARY_CACHE_S
249
156
  const README_SUMMARY_CACHE_SECONDS = clampInt(process.env.README_SUMMARY_CACHE_SECONDS, 60 * 30, 60, 60 * 60 * 6);
250
157
  const README_MESSAGE_TYPE_SAMPLE_LIMIT = clampInt(process.env.README_MESSAGE_TYPE_SAMPLE_LIMIT, 25000, 500, 250000);
251
158
  const README_COMMAND_PREFIX = String(process.env.README_COMMAND_PREFIX || PACK_COMMAND_PREFIX).trim() || PACK_COMMAND_PREFIX;
252
- const SITE_CANONICAL_HOST = String(process.env.SITE_CANONICAL_HOST || 'omnizap.shop').trim().toLowerCase() || 'omnizap.shop';
253
- const SITE_CANONICAL_SCHEME = String(process.env.SITE_CANONICAL_SCHEME || 'https').trim().toLowerCase() === 'http'
254
- ? 'http'
255
- : 'https';
159
+ const SITE_CANONICAL_HOST =
160
+ String(process.env.SITE_CANONICAL_HOST || 'omnizap.shop')
161
+ .trim()
162
+ .toLowerCase() || 'omnizap.shop';
163
+ const SITE_CANONICAL_SCHEME =
164
+ String(process.env.SITE_CANONICAL_SCHEME || 'https')
165
+ .trim()
166
+ .toLowerCase() === 'http'
167
+ ? 'http'
168
+ : 'https';
256
169
  const SITE_CANONICAL_REDIRECT_ENABLED = parseEnvBool(process.env.SITE_CANONICAL_REDIRECT_ENABLED, true);
257
170
  const SITE_ORIGIN = String(process.env.SITE_ORIGIN || `${SITE_CANONICAL_SCHEME}://${SITE_CANONICAL_HOST}`)
258
171
  .trim()
@@ -273,41 +186,21 @@ const PACK_PAGE_ROUTE_EXCLUSIONS = new Set(['profile', 'perfil', 'creators', 'cr
273
186
  const NSFW_STICKER_PLACEHOLDER_URL = String(process.env.NSFW_STICKER_PLACEHOLDER_URL || 'https://iili.io/qfhwS6u.jpg').trim();
274
187
  const DATA_IMAGE_EXTENSIONS = new Set(['.webp', '.png', '.jpg', '.jpeg', '.gif', '.avif', '.bmp']);
275
188
  const { maxStickerBytes: MAX_STICKER_UPLOAD_BYTES } = getStickerStorageConfig();
276
- const MAX_STICKER_SOURCE_UPLOAD_BYTES = Math.max(
277
- MAX_STICKER_UPLOAD_BYTES,
278
- Number(process.env.STICKER_WEB_UPLOAD_SOURCE_MAX_BYTES) || 20 * 1024 * 1024,
279
- );
280
- const ALLOWED_WEB_UPLOAD_VIDEO_MIMETYPES = new Set([
281
- 'video/mp4',
282
- 'video/webm',
283
- 'video/quicktime',
284
- 'video/x-m4v',
285
- ]);
189
+ const MAX_STICKER_SOURCE_UPLOAD_BYTES = Math.max(MAX_STICKER_UPLOAD_BYTES, Number(process.env.STICKER_WEB_UPLOAD_SOURCE_MAX_BYTES) || 20 * 1024 * 1024);
190
+ const ALLOWED_WEB_UPLOAD_VIDEO_MIMETYPES = new Set(['video/mp4', 'video/webm', 'video/quicktime', 'video/x-m4v']);
286
191
  const webPackEditTokenMap = new Map();
287
192
  const webGoogleSessionMap = new Map();
288
193
  const adminPanelSessionMap = new Map();
289
194
  const GOOGLE_WEB_SESSION_COOKIE_NAME = 'omnizap_google_session';
290
195
  const ADMIN_PANEL_SESSION_COOKIE_NAME = 'omnizap_admin_panel_session';
291
- const GOOGLE_WEB_SESSION_DB_TOUCH_INTERVAL_MS = Math.max(
292
- 30_000,
293
- Number(process.env.STICKER_WEB_GOOGLE_SESSION_DB_TOUCH_INTERVAL_MS) || 60_000,
294
- );
295
- const GOOGLE_WEB_SESSION_DB_PRUNE_INTERVAL_MS = Math.max(
296
- 5 * 60 * 1000,
297
- Number(process.env.STICKER_WEB_GOOGLE_SESSION_DB_PRUNE_INTERVAL_MS) || 60 * 60 * 1000,
298
- );
196
+ const GOOGLE_WEB_SESSION_DB_TOUCH_INTERVAL_MS = Math.max(30_000, Number(process.env.STICKER_WEB_GOOGLE_SESSION_DB_TOUCH_INTERVAL_MS) || 60_000);
197
+ const GOOGLE_WEB_SESSION_DB_PRUNE_INTERVAL_MS = Math.max(5 * 60 * 1000, Number(process.env.STICKER_WEB_GOOGLE_SESSION_DB_PRUNE_INTERVAL_MS) || 60 * 60 * 1000);
299
198
  const PACK_WEB_STATUS_VALUES = new Set(['draft', 'uploading', 'processing', 'published', 'failed']);
300
199
  const PACK_WEB_UPLOAD_STATUS_VALUES = new Set(['pending', 'processing', 'done', 'failed']);
301
200
  const WEB_UPLOAD_ERROR_MESSAGE_MAX = 255;
302
201
  const WEB_UPLOAD_MAX_CONCURRENCY = Math.max(1, Math.min(6, Number(process.env.STICKER_WEB_UPLOAD_CONCURRENCY) || 3));
303
- const WEB_DRAFT_CLEANUP_TTL_MS = Math.max(
304
- 60 * 60 * 1000,
305
- Number(process.env.STICKER_WEB_DRAFT_CLEANUP_TTL_MS) || 24 * 60 * 60 * 1000,
306
- );
307
- const WEB_DRAFT_CLEANUP_RUN_INTERVAL_MS = Math.max(
308
- 60 * 1000,
309
- Number(process.env.STICKER_WEB_DRAFT_CLEANUP_RUN_INTERVAL_MS) || 15 * 60 * 1000,
310
- );
202
+ const WEB_DRAFT_CLEANUP_TTL_MS = Math.max(60 * 60 * 1000, Number(process.env.STICKER_WEB_DRAFT_CLEANUP_TTL_MS) || 24 * 60 * 60 * 1000);
203
+ const WEB_DRAFT_CLEANUP_RUN_INTERVAL_MS = Math.max(60 * 1000, Number(process.env.STICKER_WEB_DRAFT_CLEANUP_RUN_INTERVAL_MS) || 15 * 60 * 1000);
311
204
  const WEB_UPLOAD_ID_MAX_LENGTH = 120;
312
205
  let staleDraftCleanupState = {
313
206
  running: false,
@@ -389,14 +282,7 @@ const getCacheBucket = (cacheMap, key) => {
389
282
  return bucket;
390
283
  };
391
284
 
392
- const getCachedSnapshot = async ({
393
- cacheMap,
394
- key,
395
- ttlSeconds,
396
- staleWhileRefresh = true,
397
- staleOnError = true,
398
- load,
399
- }) => {
285
+ const getCachedSnapshot = async ({ cacheMap, key, ttlSeconds, staleWhileRefresh = true, staleOnError = true, load }) => {
400
286
  const bucket = getCacheBucket(cacheMap, key);
401
287
  const now = Date.now();
402
288
  const hasValue = bucket.value !== null;
@@ -476,7 +362,9 @@ const toRequestHost = (req) =>
476
362
  .split(':')[0];
477
363
 
478
364
  const isIpLiteralHost = (value) => {
479
- const host = String(value || '').trim().toLowerCase();
365
+ const host = String(value || '')
366
+ .trim()
367
+ .toLowerCase();
480
368
  if (!host) return false;
481
369
  if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) return true;
482
370
  return host.includes(':');
@@ -543,8 +431,8 @@ const getCookieValuesFromRequest = (req, cookieName) => {
543
431
  let decodedValue = encodedValue;
544
432
  try {
545
433
  decodedValue = decodeURIComponent(encodedValue);
546
- } catch (_error) {
547
- decodedValue = encodedValue;
434
+ } catch (error) {
435
+ if (error) decodedValue = encodedValue;
548
436
  }
549
437
  const normalizedValue = String(decodedValue || '').trim();
550
438
  if (!normalizedValue) continue;
@@ -554,7 +442,10 @@ const getCookieValuesFromRequest = (req, cookieName) => {
554
442
  };
555
443
 
556
444
  const isRequestSecure = (req) => {
557
- const proto = String(req?.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
445
+ const proto = String(req?.headers?.['x-forwarded-proto'] || '')
446
+ .split(',')[0]
447
+ .trim()
448
+ .toLowerCase();
558
449
  if (proto) return proto === 'https';
559
450
  return Boolean(req?.socket?.encrypted);
560
451
  };
@@ -582,17 +473,25 @@ const logPackWebFlow = (level, phase, payload = {}) => {
582
473
  };
583
474
 
584
475
  const normalizePackWebStatus = (value, fallback = 'draft') => {
585
- const normalized = String(value || '').trim().toLowerCase();
476
+ const normalized = String(value || '')
477
+ .trim()
478
+ .toLowerCase();
586
479
  return PACK_WEB_STATUS_VALUES.has(normalized) ? normalized : fallback;
587
480
  };
588
481
 
589
482
  const normalizePackWebUploadStatus = (value, fallback = 'pending') => {
590
- const normalized = String(value || '').trim().toLowerCase();
483
+ const normalized = String(value || '')
484
+ .trim()
485
+ .toLowerCase();
591
486
  return PACK_WEB_UPLOAD_STATUS_VALUES.has(normalized) ? normalized : fallback;
592
487
  };
593
488
 
594
489
  const sha256Hex = (buffer) => createHash('sha256').update(buffer).digest('hex');
595
- const normalizeEmail = (value) => String(value || '').trim().toLowerCase().slice(0, 255);
490
+ const normalizeEmail = (value) =>
491
+ String(value || '')
492
+ .trim()
493
+ .toLowerCase()
494
+ .slice(0, 255);
596
495
  const constantTimeStringEqual = (a, b) => {
597
496
  const left = Buffer.from(String(a || ''), 'utf8');
598
497
  const right = Buffer.from(String(b || ''), 'utf8');
@@ -604,7 +503,9 @@ const constantTimeStringEqual = (a, b) => {
604
503
  }
605
504
  };
606
505
  const normalizeAdminPanelRole = (value, fallback = 'owner') => {
607
- const normalized = String(value || '').trim().toLowerCase();
506
+ const normalized = String(value || '')
507
+ .trim()
508
+ .toLowerCase();
608
509
  if (normalized === 'moderator') return 'moderator';
609
510
  if (normalized === 'owner') return 'owner';
610
511
  return fallback;
@@ -661,10 +562,7 @@ const runSqlTransaction = async (handler) => {
661
562
  const buildCookieString = (name, value, req, options = {}) => {
662
563
  const parts = [`${name}=${encodeURIComponent(String(value ?? ''))}`];
663
564
  parts.push(`Path=${options.path || '/'}`);
664
- const cookieDomain =
665
- options.domain === false
666
- ? ''
667
- : String(options.domain || resolveCookieDomainForRequest(req)).trim();
565
+ const cookieDomain = options.domain === false ? '' : String(options.domain || resolveCookieDomainForRequest(req)).trim();
668
566
  if (cookieDomain) parts.push(`Domain=${cookieDomain}`);
669
567
  if (options.httpOnly !== false) parts.push('HttpOnly');
670
568
  parts.push(`SameSite=${options.sameSite || 'Lax'}`);
@@ -785,19 +683,7 @@ const createPackWebUpload = async (entry, connection = null) => {
785
683
  `INSERT INTO ${TABLES.STICKER_PACK_WEB_UPLOAD}
786
684
  (id, pack_id, upload_id, sticker_hash, source_mimetype, upload_status, sticker_id, error_code, error_message, attempt_count, last_attempt_at)
787
685
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
788
- [
789
- entry.id,
790
- entry.pack_id,
791
- entry.upload_id,
792
- entry.sticker_hash,
793
- entry.source_mimetype ?? null,
794
- normalizePackWebUploadStatus(entry.upload_status, 'pending'),
795
- entry.sticker_id ?? null,
796
- entry.error_code ?? null,
797
- clampUploadErrorMessage(entry.error_message),
798
- Math.max(0, Number(entry.attempt_count || 0)),
799
- entry.last_attempt_at ?? null,
800
- ],
686
+ [entry.id, entry.pack_id, entry.upload_id, entry.sticker_hash, entry.source_mimetype ?? null, normalizePackWebUploadStatus(entry.upload_status, 'pending'), entry.sticker_id ?? null, entry.error_code ?? null, clampUploadErrorMessage(entry.error_message), Math.max(0, Number(entry.attempt_count || 0)), entry.last_attempt_at ?? null],
801
687
  connection,
802
688
  );
803
689
  return findPackWebUploadByUploadId(entry.pack_id, entry.upload_id, connection);
@@ -833,11 +719,7 @@ const updatePackWebUpload = async (uploadIdPk, fields, connection = null) => {
833
719
  connection,
834
720
  );
835
721
 
836
- const rows = await executeQuery(
837
- `SELECT * FROM ${TABLES.STICKER_PACK_WEB_UPLOAD} WHERE id = ? LIMIT 1`,
838
- [uploadIdPk],
839
- connection,
840
- );
722
+ const rows = await executeQuery(`SELECT * FROM ${TABLES.STICKER_PACK_WEB_UPLOAD} WHERE id = ? LIMIT 1`, [uploadIdPk], connection);
841
723
  return normalizePackWebUploadRow(rows?.[0] || null);
842
724
  };
843
725
 
@@ -922,12 +804,7 @@ const buildPackPublishStateData = async (pack, { includeUploads = true, connecti
922
804
  failed_uploads: snapshot.failed_uploads,
923
805
  processing_uploads: snapshot.processing_uploads,
924
806
  pending_uploads: snapshot.pending_uploads,
925
- can_publish:
926
- snapshot.sticker_count >= 1 &&
927
- snapshot.failed_uploads === 0 &&
928
- snapshot.processing_uploads === 0 &&
929
- snapshot.pending_uploads === 0 &&
930
- snapshot.cover_valid,
807
+ can_publish: snapshot.sticker_count >= 1 && snapshot.failed_uploads === 0 && snapshot.processing_uploads === 0 && snapshot.pending_uploads === 0 && snapshot.cover_valid,
931
808
  },
932
809
  uploads: uploads.map((entry) => ({
933
810
  upload_id: entry.upload_id,
@@ -995,7 +872,11 @@ const triggerStaleDraftCleanup = () => {
995
872
 
996
873
  const GOOGLE_TOKENINFO_URL = 'https://oauth2.googleapis.com/tokeninfo';
997
874
 
998
- const normalizeGoogleSubject = (value) => String(value || '').trim().replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 80);
875
+ const normalizeGoogleSubject = (value) =>
876
+ String(value || '')
877
+ .trim()
878
+ .replace(/[^a-zA-Z0-9_-]/g, '')
879
+ .slice(0, 80);
999
880
 
1000
881
  const buildGoogleOwnerJid = (googleSub) => {
1001
882
  const normalizedSub = normalizeGoogleSubject(googleSub);
@@ -1036,8 +917,12 @@ const verifyGoogleIdToken = async (idToken) => {
1036
917
  const aud = String(claims.aud || '').trim();
1037
918
  const iss = String(claims.iss || '').trim();
1038
919
  const sub = normalizeGoogleSubject(claims.sub);
1039
- const email = String(claims.email || '').trim().toLowerCase();
1040
- const emailVerified = String(claims.email_verified || '').trim().toLowerCase();
920
+ const email = String(claims.email || '')
921
+ .trim()
922
+ .toLowerCase();
923
+ const emailVerified = String(claims.email_verified || '')
924
+ .trim()
925
+ .toLowerCase();
1041
926
 
1042
927
  if (STICKER_WEB_GOOGLE_CLIENT_ID && aud !== STICKER_WEB_GOOGLE_CLIENT_ID) {
1043
928
  const error = new Error('Login Google não pertence a este aplicativo.');
@@ -1100,7 +985,10 @@ const normalizeGoogleWebSessionRow = (row) => {
1100
985
  return {
1101
986
  token,
1102
987
  sub,
1103
- email: String(row.email || '').trim().toLowerCase() || null,
988
+ email:
989
+ String(row.email || '')
990
+ .trim()
991
+ .toLowerCase() || null,
1104
992
  name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
1105
993
  picture: String(row.picture_url || '').trim() || null,
1106
994
  ownerJid,
@@ -1134,9 +1022,15 @@ const upsertGoogleWebUserRecord = async (user, connection = null) => {
1134
1022
  const ownerJid = normalizeJid(user?.ownerJid) || '';
1135
1023
  if (!sub || !ownerJid) return;
1136
1024
  const ownerPhone = toWhatsAppPhoneDigits(ownerJid) || null;
1137
- const email = String(user?.email || '').trim().toLowerCase() || null;
1025
+ const email =
1026
+ String(user?.email || '')
1027
+ .trim()
1028
+ .toLowerCase() || null;
1138
1029
  const name = sanitizeText(user?.name || '', 120, { allowEmpty: true }) || null;
1139
- const pictureUrl = String(user?.picture || '').trim().slice(0, 1024) || null;
1030
+ const pictureUrl =
1031
+ String(user?.picture || '')
1032
+ .trim()
1033
+ .slice(0, 1024) || null;
1140
1034
 
1141
1035
  // owner_jid e unico; removemos vinculos antigos desse numero antes do upsert para manter 1:1.
1142
1036
  await executeQuery(
@@ -1179,9 +1073,15 @@ const upsertGoogleWebSessionRecord = async (session, connection = null) => {
1179
1073
  const ownerPhone = toWhatsAppPhoneDigits(session?.ownerPhone || ownerJid) || null;
1180
1074
  const expiresAt = Number(session?.expiresAt || 0);
1181
1075
  if (!token || !sub || !ownerJid || !Number.isFinite(expiresAt) || expiresAt <= 0) return;
1182
- const email = String(session?.email || '').trim().toLowerCase() || null;
1076
+ const email =
1077
+ String(session?.email || '')
1078
+ .trim()
1079
+ .toLowerCase() || null;
1183
1080
  const name = sanitizeText(session?.name || '', 120, { allowEmpty: true }) || null;
1184
- const pictureUrl = String(session?.picture || '').trim().slice(0, 1024) || null;
1081
+ const pictureUrl =
1082
+ String(session?.picture || '')
1083
+ .trim()
1084
+ .slice(0, 1024) || null;
1185
1085
 
1186
1086
  await executeQuery(
1187
1087
  `INSERT INTO ${TABLES.STICKER_WEB_GOOGLE_SESSION}
@@ -1305,10 +1205,7 @@ const touchGoogleWebUserSeenInDb = async (googleSub) => {
1305
1205
  const deleteGoogleWebSessionFromDb = async (sessionToken) => {
1306
1206
  const token = String(sessionToken || '').trim();
1307
1207
  if (!token) return 0;
1308
- const result = await executeQuery(
1309
- `DELETE FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION} WHERE session_token = ?`,
1310
- [token],
1311
- );
1208
+ const result = await executeQuery(`DELETE FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION} WHERE session_token = ?`, [token]);
1312
1209
  return Number(result?.affectedRows || 0);
1313
1210
  };
1314
1211
 
@@ -1393,15 +1290,7 @@ const createAdminBanRecord = async ({ googleSub = '', email = '', ownerJid = '',
1393
1290
  `INSERT INTO ${TABLES.STICKER_WEB_ADMIN_BAN}
1394
1291
  (id, google_sub, email, owner_jid, reason, created_by_google_sub, created_by_email)
1395
1292
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
1396
- [
1397
- banId,
1398
- normalizedSub || null,
1399
- normalizedEmail || null,
1400
- normalizedOwnerJid || null,
1401
- sanitizeText(reason || '', 255, { allowEmpty: true }) || null,
1402
- normalizeGoogleSubject(adminSession?.googleSub) || null,
1403
- normalizeEmail(adminSession?.email) || null,
1404
- ],
1293
+ [banId, normalizedSub || null, normalizedEmail || null, normalizedOwnerJid || null, sanitizeText(reason || '', 255, { allowEmpty: true }) || null, normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null],
1405
1294
  );
1406
1295
 
1407
1296
  if (normalizedSub || normalizedEmail || normalizedOwnerJid) {
@@ -1431,11 +1320,7 @@ const createAdminBanRecord = async ({ googleSub = '', email = '', ownerJid = '',
1431
1320
  const sessionSub = normalizeGoogleSubject(session.sub);
1432
1321
  const sessionEmail = normalizeEmail(session.email);
1433
1322
  const sessionOwner = normalizeJid(session.ownerJid) || '';
1434
- if (
1435
- (normalizedSub && sessionSub === normalizedSub) ||
1436
- (normalizedEmail && sessionEmail === normalizedEmail) ||
1437
- (normalizedOwnerJid && sessionOwner === normalizedOwnerJid)
1438
- ) {
1323
+ if ((normalizedSub && sessionSub === normalizedSub) || (normalizedEmail && sessionEmail === normalizedEmail) || (normalizedOwnerJid && sessionOwner === normalizedOwnerJid)) {
1439
1324
  webGoogleSessionMap.delete(token);
1440
1325
  }
1441
1326
  }
@@ -1605,11 +1490,7 @@ const pruneModeratorAdminPanelSessions = ({ googleSub = '', email = '', ownerJid
1605
1490
  const sessionSub = normalizeGoogleSubject(session.googleSub);
1606
1491
  const sessionEmail = normalizeEmail(session.email);
1607
1492
  const sessionOwner = normalizeJid(session.ownerJid) || '';
1608
- if (
1609
- (normalizedSub && sessionSub === normalizedSub) ||
1610
- (normalizedEmail && sessionEmail === normalizedEmail) ||
1611
- (normalizedOwnerJid && sessionOwner === normalizedOwnerJid)
1612
- ) {
1493
+ if ((normalizedSub && sessionSub === normalizedSub) || (normalizedEmail && sessionEmail === normalizedEmail) || (normalizedOwnerJid && sessionOwner === normalizedOwnerJid)) {
1613
1494
  adminPanelSessionMap.delete(token);
1614
1495
  }
1615
1496
  }
@@ -1644,17 +1525,7 @@ const upsertAdminModeratorRecord = async ({ googleSub = '', email = '', ownerJid
1644
1525
  updated_by_google_sub = VALUES(updated_by_google_sub),
1645
1526
  updated_by_email = VALUES(updated_by_email),
1646
1527
  revoked_at = NULL`,
1647
- [
1648
- knownUser.google_sub,
1649
- knownUser.email,
1650
- knownUser.owner_jid,
1651
- knownUser.name || null,
1652
- passwordHash,
1653
- normalizeGoogleSubject(adminSession?.googleSub) || null,
1654
- normalizeEmail(adminSession?.email) || null,
1655
- normalizeGoogleSubject(adminSession?.googleSub) || null,
1656
- normalizeEmail(adminSession?.email) || null,
1657
- ],
1528
+ [knownUser.google_sub, knownUser.email, knownUser.owner_jid, knownUser.name || null, passwordHash, normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null, normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null],
1658
1529
  );
1659
1530
 
1660
1531
  pruneModeratorAdminPanelSessions({
@@ -1684,11 +1555,7 @@ const revokeAdminModeratorRecord = async (googleSub, adminSession = null) => {
1684
1555
  updated_by_google_sub = ?,
1685
1556
  updated_by_email = ?
1686
1557
  WHERE google_sub = ?`,
1687
- [
1688
- normalizeGoogleSubject(adminSession?.googleSub) || null,
1689
- normalizeEmail(adminSession?.email) || null,
1690
- normalizedSub,
1691
- ],
1558
+ [normalizeGoogleSubject(adminSession?.googleSub) || null, normalizeEmail(adminSession?.email) || null, normalizedSub],
1692
1559
  );
1693
1560
 
1694
1561
  const fresh = await findAdminModeratorByGoogleSub(normalizedSub);
@@ -1719,14 +1586,7 @@ const touchAdminModeratorLastLogin = async (moderatorRow, googleSession) => {
1719
1586
  updated_by_google_sub = ?,
1720
1587
  updated_by_email = ?
1721
1588
  WHERE google_sub = ?`,
1722
- [
1723
- normalizeEmail(googleSession?.email || moderatorRow?.email) || null,
1724
- normalizeJid(googleSession?.ownerJid || moderatorRow?.owner_jid) || null,
1725
- sanitizeText(googleSession?.name || moderatorRow?.name || '', 120, { allowEmpty: true }) || null,
1726
- normalizeGoogleSubject(googleSession?.sub || moderatorRow?.google_sub) || null,
1727
- normalizeEmail(googleSession?.email || moderatorRow?.email) || null,
1728
- normalizedSub,
1729
- ],
1589
+ [normalizeEmail(googleSession?.email || moderatorRow?.email) || null, normalizeJid(googleSession?.ownerJid || moderatorRow?.owner_jid) || null, sanitizeText(googleSession?.name || moderatorRow?.name || '', 120, { allowEmpty: true }) || null, normalizeGoogleSubject(googleSession?.sub || moderatorRow?.google_sub) || null, normalizeEmail(googleSession?.email || moderatorRow?.email) || null, normalizedSub],
1730
1590
  ).catch(() => {});
1731
1591
  };
1732
1592
 
@@ -1996,10 +1856,7 @@ const sendAsset = (req, res, buffer, mimetype = 'image/webp') => {
1996
1856
  res.statusCode = 200;
1997
1857
  res.setHeader('Content-Type', mimetype);
1998
1858
  res.setHeader('Content-Length', String(buffer.length));
1999
- res.setHeader(
2000
- 'Cache-Control',
2001
- `public, max-age=${maxAgeSeconds}, stale-while-revalidate=${staleWhileRevalidateSeconds}`,
2002
- );
1859
+ res.setHeader('Cache-Control', `public, max-age=${maxAgeSeconds}, stale-while-revalidate=${staleWhileRevalidateSeconds}`);
2003
1860
  if (req.method === 'HEAD') {
2004
1861
  res.end();
2005
1862
  return;
@@ -2008,7 +1865,10 @@ const sendAsset = (req, res, buffer, mimetype = 'image/webp') => {
2008
1865
  };
2009
1866
 
2010
1867
  const normalizeGitHubRepo = (value) => {
2011
- const raw = String(value || '').trim().replace(/^https?:\/\/github\.com\//i, '').replace(/\.git$/i, '');
1868
+ const raw = String(value || '')
1869
+ .trim()
1870
+ .replace(/^https?:\/\/github\.com\//i, '')
1871
+ .replace(/\.git$/i, '');
2012
1872
  const [owner, repo] = raw.split('/').filter(Boolean);
2013
1873
  if (!owner || !repo) return null;
2014
1874
  return {
@@ -2108,33 +1968,17 @@ const fetchGitHubProjectSummary = async () => {
2108
1968
  return GITHUB_PROJECT_CACHE.value;
2109
1969
  }
2110
1970
 
2111
- const repoUrl = `https://api.github.com/repos/${encodeURIComponent(GITHUB_REPO_INFO.owner)}/${encodeURIComponent(
2112
- GITHUB_REPO_INFO.repo,
2113
- )}`;
1971
+ const repoUrl = `https://api.github.com/repos/${encodeURIComponent(GITHUB_REPO_INFO.owner)}/${encodeURIComponent(GITHUB_REPO_INFO.repo)}`;
2114
1972
  const releasesUrl = `${repoUrl}/releases?per_page=5`;
2115
1973
  const commitsUrl = `${repoUrl}/commits?per_page=5`;
2116
1974
  const languagesUrl = `${repoUrl}/languages`;
2117
- const openPrsUrl = `https://api.github.com/search/issues?q=${encodeURIComponent(
2118
- `repo:${GITHUB_REPO_INFO.fullName} is:pr is:open`,
2119
- )}&per_page=1`;
1975
+ const openPrsUrl = `https://api.github.com/search/issues?q=${encodeURIComponent(`repo:${GITHUB_REPO_INFO.fullName} is:pr is:open`)}&per_page=1`;
2120
1976
 
2121
1977
  const repoData = await githubFetchJson(repoUrl);
2122
- const [releasesData, commitsData, languagesData, openPrsData] = await Promise.all([
2123
- githubFetchJsonSafe(releasesUrl, []),
2124
- githubFetchJsonSafe(commitsUrl, []),
2125
- githubFetchJsonSafe(languagesUrl, {}),
2126
- githubFetchJsonSafe(openPrsUrl, { total_count: null }),
2127
- ]);
1978
+ const [releasesData, commitsData, languagesData, openPrsData] = await Promise.all([githubFetchJsonSafe(releasesUrl, []), githubFetchJsonSafe(commitsUrl, []), githubFetchJsonSafe(languagesUrl, {}), githubFetchJsonSafe(openPrsUrl, { total_count: null })]);
2128
1979
 
2129
1980
  const latestReleaseData = Array.isArray(releasesData) ? releasesData[0] || null : null;
2130
- const summary = mapGitHubProjectSummary(
2131
- repoData,
2132
- latestReleaseData,
2133
- releasesData,
2134
- commitsData,
2135
- languagesData,
2136
- openPrsData?.total_count,
2137
- );
1981
+ const summary = mapGitHubProjectSummary(repoData, latestReleaseData, releasesData, commitsData, languagesData, openPrsData?.total_count);
2138
1982
  GITHUB_PROJECT_CACHE.value = summary;
2139
1983
  GITHUB_PROJECT_CACHE.expiresAt = now + GITHUB_PROJECT_CACHE_SECONDS * 1000;
2140
1984
  return summary;
@@ -2234,14 +2078,12 @@ const fetchPrometheusSummary = async () => {
2234
2078
 
2235
2079
  const buildPackApiUrl = (packKey) => `${STICKER_API_BASE_PATH}/${encodeURIComponent(packKey)}`;
2236
2080
  const buildPackWebUrl = (packKey) => `${STICKER_WEB_PATH}/${encodeURIComponent(packKey)}`;
2237
- const buildStickerAssetUrl = (packKey, stickerId) =>
2238
- `${STICKER_API_BASE_PATH}/${encodeURIComponent(packKey)}/stickers/${encodeURIComponent(stickerId)}.webp`;
2081
+ const buildStickerAssetUrl = (packKey, stickerId) => `${STICKER_API_BASE_PATH}/${encodeURIComponent(packKey)}/stickers/${encodeURIComponent(stickerId)}.webp`;
2239
2082
  const buildOrphanStickersApiUrl = () => STICKER_ORPHAN_API_PATH;
2240
2083
  const buildDataAssetApiBaseUrl = () => `${STICKER_API_BASE_PATH}/data-files`;
2241
2084
  const CATALOG_STYLES_WEB_PATH = `${STICKER_WEB_PATH}/assets/styles.css`;
2242
2085
  const CATALOG_SCRIPT_WEB_PATH = `${STICKER_WEB_PATH}/assets/catalog.js`;
2243
- const appendAssetVersionQuery = (assetPath) =>
2244
- STICKER_WEB_ASSET_VERSION ? `${assetPath}?v=${encodeURIComponent(STICKER_WEB_ASSET_VERSION)}` : assetPath;
2086
+ const appendAssetVersionQuery = (assetPath) => (STICKER_WEB_ASSET_VERSION ? `${assetPath}?v=${encodeURIComponent(STICKER_WEB_ASSET_VERSION)}` : assetPath);
2245
2087
  const buildCatalogStylesUrl = () => appendAssetVersionQuery(CATALOG_STYLES_WEB_PATH);
2246
2088
  const buildCatalogScriptUrl = () => appendAssetVersionQuery(CATALOG_SCRIPT_WEB_PATH);
2247
2089
  const buildDataAssetUrl = (relativePath) =>
@@ -2250,10 +2092,13 @@ const buildDataAssetUrl = (relativePath) =>
2250
2092
  .map((segment) => encodeURIComponent(segment))
2251
2093
  .join('/')}`;
2252
2094
 
2253
- const normalizeRelativePath = (value) => String(value || '').split(path.sep).join('/').replace(/^\/+/, '');
2095
+ const normalizeRelativePath = (value) =>
2096
+ String(value || '')
2097
+ .split(path.sep)
2098
+ .join('/')
2099
+ .replace(/^\/+/, '');
2254
2100
  const isAllowedDataImageFile = (filePath) => DATA_IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
2255
- const isInsideDataPublicRoot = (targetPath) =>
2256
- targetPath === STICKER_DATA_PUBLIC_DIR || targetPath.startsWith(`${STICKER_DATA_PUBLIC_DIR}${path.sep}`);
2101
+ const isInsideDataPublicRoot = (targetPath) => targetPath === STICKER_DATA_PUBLIC_DIR || targetPath.startsWith(`${STICKER_DATA_PUBLIC_DIR}${path.sep}`);
2257
2102
 
2258
2103
  const toPublicDataUrlFromStoragePath = (storagePath) => {
2259
2104
  if (!storagePath) return null;
@@ -2284,12 +2129,7 @@ const resolveCatalogBotPhone = () => {
2284
2129
  const fromSocket = normalizePhoneDigits(jidUser);
2285
2130
  if (fromSocket) return fromSocket;
2286
2131
 
2287
- const envCandidates = [
2288
- process.env.WHATSAPP_BOT_NUMBER,
2289
- process.env.BOT_NUMBER,
2290
- process.env.PHONE_NUMBER,
2291
- process.env.BOT_PHONE_NUMBER,
2292
- ];
2132
+ const envCandidates = [process.env.WHATSAPP_BOT_NUMBER, process.env.BOT_NUMBER, process.env.PHONE_NUMBER, process.env.BOT_PHONE_NUMBER];
2293
2133
 
2294
2134
  for (const candidate of envCandidates) {
2295
2135
  const normalized = normalizePhoneDigits(candidate);
@@ -2299,10 +2139,7 @@ const resolveCatalogBotPhone = () => {
2299
2139
  return '';
2300
2140
  };
2301
2141
 
2302
- const buildPackWhatsAppText = (pack) =>
2303
- STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE
2304
- .replaceAll('{{pack_key}}', String(pack?.pack_key || ''))
2305
- .replaceAll('{{pack_name}}', String(pack?.name || ''));
2142
+ const buildPackWhatsAppText = (pack) => STICKER_WEB_WHATSAPP_MESSAGE_TEMPLATE.replaceAll('{{pack_key}}', String(pack?.pack_key || '')).replaceAll('{{pack_name}}', String(pack?.name || ''));
2306
2143
 
2307
2144
  const buildPackWhatsAppInfo = (pack) => {
2308
2145
  const phone = resolveCatalogBotPhone();
@@ -2351,12 +2188,7 @@ const resolveWebCreateOwnerJid = async (explicitOwner = '') => {
2351
2188
  // Ignore fallback errors while resolving owner identity.
2352
2189
  }
2353
2190
 
2354
- const adminCandidates = [
2355
- getAdminRawValue(),
2356
- getAdminPhone(),
2357
- process.env.USER_ADMIN,
2358
- process.env.WHATSAPP_SUPPORT_NUMBER,
2359
- ];
2191
+ const adminCandidates = [getAdminRawValue(), getAdminPhone(), process.env.USER_ADMIN, process.env.WHATSAPP_SUPPORT_NUMBER];
2360
2192
  for (const candidate of adminCandidates) {
2361
2193
  const normalized = toOwnerJid(candidate);
2362
2194
  if (normalized) return normalized;
@@ -2394,7 +2226,11 @@ const decodeStickerBase64Payload = (value) => {
2394
2226
 
2395
2227
  const dataUrlMatch = raw.match(/^data:([^;]+);base64,([\s\S]+)$/i);
2396
2228
  const base64Value = dataUrlMatch ? dataUrlMatch[2] : raw;
2397
- const mimetype = dataUrlMatch ? String(dataUrlMatch[1] || '').trim().toLowerCase() : 'image/webp';
2229
+ const mimetype = dataUrlMatch
2230
+ ? String(dataUrlMatch[1] || '')
2231
+ .trim()
2232
+ .toLowerCase()
2233
+ : 'image/webp';
2398
2234
  const cleaned = base64Value.replace(/\s+/g, '');
2399
2235
  if (!cleaned) return null;
2400
2236
 
@@ -2406,14 +2242,12 @@ const decodeStickerBase64Payload = (value) => {
2406
2242
  };
2407
2243
  };
2408
2244
 
2409
- const isLikelyWebpBuffer = (buffer) =>
2410
- Buffer.isBuffer(buffer) &&
2411
- buffer.length >= 16 &&
2412
- buffer.subarray(0, 4).toString('ascii') === 'RIFF' &&
2413
- buffer.subarray(8, 12).toString('ascii') === 'WEBP';
2245
+ const isLikelyWebpBuffer = (buffer) => Buffer.isBuffer(buffer) && buffer.length >= 16 && buffer.subarray(0, 4).toString('ascii') === 'RIFF' && buffer.subarray(8, 12).toString('ascii') === 'WEBP';
2414
2246
 
2415
2247
  const resolveExtensionFromMimetype = (mimetype) => {
2416
- const normalized = String(mimetype || '').trim().toLowerCase();
2248
+ const normalized = String(mimetype || '')
2249
+ .trim()
2250
+ .toLowerCase();
2417
2251
  if (normalized === 'image/jpeg') return 'jpg';
2418
2252
  if (normalized === 'image/jpg') return 'jpg';
2419
2253
  if (normalized === 'image/png') return 'png';
@@ -2433,15 +2267,15 @@ const resolveExtensionFromMimetype = (mimetype) => {
2433
2267
  };
2434
2268
 
2435
2269
  const convertUploadMediaToWebp = async ({ ownerJid, buffer, mimetype }) => {
2436
- const normalizedMimetype = String(mimetype || '').trim().toLowerCase() || 'image/webp';
2270
+ const normalizedMimetype =
2271
+ String(mimetype || '')
2272
+ .trim()
2273
+ .toLowerCase() || 'image/webp';
2437
2274
  const isVideo = normalizedMimetype.startsWith('video/');
2438
2275
  const isImage = normalizedMimetype.startsWith('image/');
2439
2276
 
2440
2277
  if (!isVideo && !isImage) {
2441
- throw new StickerPackError(
2442
- STICKER_PACK_ERROR_CODES.INVALID_INPUT,
2443
- 'Formato não suportado. Envie imagem ou vídeo.',
2444
- );
2278
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'Formato não suportado. Envie imagem ou vídeo.');
2445
2279
  }
2446
2280
 
2447
2281
  if (isLikelyWebpBuffer(buffer) && buffer.length <= MAX_STICKER_UPLOAD_BYTES) {
@@ -2449,20 +2283,11 @@ const convertUploadMediaToWebp = async ({ ownerJid, buffer, mimetype }) => {
2449
2283
  }
2450
2284
 
2451
2285
  if (isVideo && !ALLOWED_WEB_UPLOAD_VIDEO_MIMETYPES.has(normalizedMimetype) && normalizedMimetype !== 'video/mp4') {
2452
- throw new StickerPackError(
2453
- STICKER_PACK_ERROR_CODES.INVALID_INPUT,
2454
- 'Formato de vídeo não suportado. Use mp4/webm/mov/m4v.',
2455
- );
2286
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'Formato de vídeo não suportado. Use mp4/webm/mov/m4v.');
2456
2287
  }
2457
2288
 
2458
2289
  const uniqueId = randomUUID();
2459
- const inputPath = path.join(
2460
- process.cwd(),
2461
- 'temp',
2462
- 'stickers',
2463
- 'web-create',
2464
- `${uniqueId}.${resolveExtensionFromMimetype(normalizedMimetype)}`,
2465
- );
2290
+ const inputPath = path.join(process.cwd(), 'temp', 'stickers', 'web-create', `${uniqueId}.${resolveExtensionFromMimetype(normalizedMimetype)}`);
2466
2291
 
2467
2292
  await fs.mkdir(path.dirname(inputPath), { recursive: true });
2468
2293
  await fs.writeFile(inputPath, buffer);
@@ -2504,11 +2329,7 @@ const convertUploadMediaToWebp = async ({ ownerJid, buffer, mimetype }) => {
2504
2329
  await fs.unlink(inputPath).catch(() => {});
2505
2330
  }
2506
2331
 
2507
- throw new StickerPackError(
2508
- STICKER_PACK_ERROR_CODES.INVALID_INPUT,
2509
- `Não foi possível converter a mídia para sticker no limite de ${Math.round(MAX_STICKER_UPLOAD_BYTES / 1024)}KB.`,
2510
- lastError,
2511
- );
2332
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, `Não foi possível converter a mídia para sticker no limite de ${Math.round(MAX_STICKER_UPLOAD_BYTES / 1024)}KB.`, lastError);
2512
2333
  };
2513
2334
 
2514
2335
  const resolveSupportAdminPhone = async () => {
@@ -2564,10 +2385,7 @@ const buildBotContactInfo = () => {
2564
2385
  if (!phone) return null;
2565
2386
  const loginText = String(process.env.WHATSAPP_LOGIN_TRIGGER || 'iniciar').trim() || 'iniciar';
2566
2387
  const menuText = `${PACK_COMMAND_PREFIX}menu`;
2567
- const buildUrl = (text) =>
2568
- `https://api.whatsapp.com/send/?phone=${encodeURIComponent(phone)}&text=${encodeURIComponent(
2569
- String(text || '').trim(),
2570
- )}&type=custom_url&app_absent=0`;
2388
+ const buildUrl = (text) => `https://api.whatsapp.com/send/?phone=${encodeURIComponent(phone)}&text=${encodeURIComponent(String(text || '').trim())}&type=custom_url&app_absent=0`;
2571
2389
 
2572
2390
  return {
2573
2391
  phone,
@@ -2639,11 +2457,10 @@ const listDataImageFiles = async () => {
2639
2457
  };
2640
2458
 
2641
2459
  const PACK_TAG_MARKER_REGEX = /\[pack-tags:([^\]]+)\]/i;
2642
- const AUTO_PACK_MARKER_REGEX = /\[(?:auto-theme|auto-tag):[^\]]+\]/ig;
2460
+ const AUTO_PACK_MARKER_REGEX = /\[(?:auto-theme|auto-tag):[^\]]+\]/gi;
2643
2461
  const AUTO_PACK_MARKER_TEST_REGEX = /\[(?:auto-theme|auto-tag):[^\]]+\]/i;
2644
- const AUTO_PACK_DESCRIPTION_PREFIX_REGEX =
2645
- /^curadoria automática por tema\.\s*tema:\s*[^.]+\.?\s*(?:score\s*=\s*-?\d+(?:\.\d+)?\.?\s*)?/i;
2646
- const AUTO_PACK_SCORE_FRAGMENT_REGEX = /\bscore\s*=\s*-?\d+(?:\.\d+)?\.?/ig;
2462
+ const AUTO_PACK_DESCRIPTION_PREFIX_REGEX = /^curadoria automática por tema\.\s*tema:\s*[^.]+\.?\s*(?:score\s*=\s*-?\d+(?:\.\d+)?\.?\s*)?/i;
2463
+ const AUTO_PACK_SCORE_FRAGMENT_REGEX = /\bscore\s*=\s*-?\d+(?:\.\d+)?\.?/gi;
2647
2464
  const normalizePackTag = (value) =>
2648
2465
  String(value || '')
2649
2466
  .trim()
@@ -2685,12 +2502,13 @@ const parsePackDescriptionMetadata = (description) => {
2685
2502
  cleanDescription = cleanDescription.replace(AUTO_PACK_MARKER_REGEX, '').trim() || null;
2686
2503
  }
2687
2504
  if (cleanDescription && hasAutoPackMarker) {
2688
- cleanDescription = cleanDescription
2689
- .replace(AUTO_PACK_DESCRIPTION_PREFIX_REGEX, '')
2690
- .replace(AUTO_PACK_SCORE_FRAGMENT_REGEX, '')
2691
- .replace(/\s{2,}/g, ' ')
2692
- .replace(/^[\s.:-]+/, '')
2693
- .trim() || null;
2505
+ cleanDescription =
2506
+ cleanDescription
2507
+ .replace(AUTO_PACK_DESCRIPTION_PREFIX_REGEX, '')
2508
+ .replace(AUTO_PACK_SCORE_FRAGMENT_REGEX, '')
2509
+ .replace(/\s{2,}/g, ' ')
2510
+ .replace(/^[\s.:-]+/, '')
2511
+ .trim() || null;
2694
2512
  }
2695
2513
 
2696
2514
  return {
@@ -2732,9 +2550,7 @@ const mapPackSummary = (pack, engagement = null, signals = null) => {
2732
2550
  open_count: Number(safeEngagement.open_count || 0),
2733
2551
  like_count: Number(safeEngagement.like_count || 0),
2734
2552
  dislike_count: Number(safeEngagement.dislike_count || 0),
2735
- score:
2736
- Number(safeEngagement.score || 0) ||
2737
- Number(safeEngagement.like_count || 0) - Number(safeEngagement.dislike_count || 0),
2553
+ score: Number(safeEngagement.score || 0) || Number(safeEngagement.like_count || 0) - Number(safeEngagement.dislike_count || 0),
2738
2554
  updated_at: toIsoOrNull(safeEngagement.updated_at),
2739
2555
  },
2740
2556
  signals: signals || null,
@@ -2777,7 +2593,9 @@ const isClassificationMarkedNsfw = (classification) => {
2777
2593
  };
2778
2594
 
2779
2595
  const isSignalMarkedNsfw = (signals) => {
2780
- const level = String(signals?.nsfw_level || '').trim().toLowerCase();
2596
+ const level = String(signals?.nsfw_level || '')
2597
+ .trim()
2598
+ .toLowerCase();
2781
2599
  if (signals?.sensitive_content === true) return true;
2782
2600
  if (!level) return false;
2783
2601
  return level !== 'safe';
@@ -2794,27 +2612,21 @@ const isPackSummaryMarkedNsfw = (packSummary) => {
2794
2612
  return false;
2795
2613
  };
2796
2614
 
2797
- const mapPackDetails = (
2798
- pack,
2799
- items,
2800
- {
2801
- byAssetClassification = new Map(),
2802
- packClassification = null,
2803
- engagement = null,
2804
- signals = null,
2805
- hideSensitiveAssets = false,
2806
- } = {},
2807
- ) => {
2615
+ const mapPackDetails = (pack, items, { byAssetClassification = new Map(), packClassification = null, engagement = null, signals = null, hideSensitiveAssets = false } = {}) => {
2808
2616
  const coverStickerId = pack.cover_sticker_id || items[0]?.sticker_id || null;
2809
2617
  const metadata = parsePackDescriptionMetadata(pack.description);
2810
2618
  const decoratedClassification = decoratePackClassificationSummary(packClassification);
2811
2619
  const mergedTags = mergeUniqueTags(decoratedClassification?.tags || [], metadata.tags);
2812
- const summary = mapPackSummary({
2813
- ...pack,
2814
- description: metadata.cleanDescription,
2815
- cover_sticker_id: coverStickerId,
2816
- sticker_count: items.length,
2817
- }, engagement, signals);
2620
+ const summary = mapPackSummary(
2621
+ {
2622
+ ...pack,
2623
+ description: metadata.cleanDescription,
2624
+ cover_sticker_id: coverStickerId,
2625
+ sticker_count: items.length,
2626
+ },
2627
+ engagement,
2628
+ signals,
2629
+ );
2818
2630
  const packPreview = {
2819
2631
  ...summary,
2820
2632
  classification: {
@@ -2824,9 +2636,7 @@ const mapPackDetails = (
2824
2636
  tags: mergedTags,
2825
2637
  };
2826
2638
  const packIsNsfw = isPackSummaryMarkedNsfw(packPreview);
2827
- const safeSummary = hideSensitiveAssets && packIsNsfw
2828
- ? { ...summary, cover_url: null }
2829
- : summary;
2639
+ const safeSummary = hideSensitiveAssets && packIsNsfw ? { ...summary, cover_url: null } : summary;
2830
2640
 
2831
2641
  return {
2832
2642
  ...safeSummary,
@@ -2852,8 +2662,7 @@ const mapPackDetails = (
2852
2662
  is_animated: Boolean(item.asset.is_animated),
2853
2663
  width: item.asset.width !== null && item.asset.width !== undefined ? Number(item.asset.width) : null,
2854
2664
  height: item.asset.height !== null && item.asset.height !== undefined ? Number(item.asset.height) : null,
2855
- size_bytes:
2856
- item.asset.size_bytes !== null && item.asset.size_bytes !== undefined ? Number(item.asset.size_bytes) : 0,
2665
+ size_bytes: item.asset.size_bytes !== null && item.asset.size_bytes !== undefined ? Number(item.asset.size_bytes) : 0,
2857
2666
  classification: decoratedItemClassification,
2858
2667
  }
2859
2668
  : null,
@@ -2933,9 +2742,7 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
2933
2742
  const engagementByPackId = await listStickerPackEngagementByPackIds(packIds);
2934
2743
  const interactionStatsByPackId = await listStickerPackInteractionStatsByPackIds(packIds);
2935
2744
  const useSnapshot = await canUseRankingSnapshotRead(`hydrate:${packIds.length}:${includeItems ? 1 : 0}`);
2936
- const snapshotByPackId = useSnapshot
2937
- ? await listStickerPackScoreSnapshotsByPackIds(packIds).catch(() => new Map())
2938
- : new Map();
2745
+ const snapshotByPackId = useSnapshot ? await listStickerPackScoreSnapshotsByPackIds(packIds).catch(() => new Map()) : new Map();
2939
2746
 
2940
2747
  const entries = [];
2941
2748
  const packClassificationById = new Map();
@@ -2943,10 +2750,7 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
2943
2750
  for (const pack of packs) {
2944
2751
  const items = includeItems ? await listStickerPackItems(pack.id) : [];
2945
2752
  const stickerIds = items.map((item) => item.sticker_id);
2946
- const [packClassification, itemClassifications] = await Promise.all([
2947
- getPackClassificationSummaryByAssetIds(stickerIds),
2948
- stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([]),
2949
- ]);
2753
+ const [packClassification, itemClassifications] = await Promise.all([getPackClassificationSummaryByAssetIds(stickerIds), stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([])]);
2950
2754
  const byAssetClassification = new Map(itemClassifications.map((classification) => [classification.asset_id, classification]));
2951
2755
  const orderedClassifications = stickerIds.map((stickerId) => byAssetClassification.get(stickerId)).filter(Boolean);
2952
2756
  const engagement = engagementByPackId.get(pack.id) || getEmptyStickerPackEngagement();
@@ -2999,8 +2803,7 @@ const isStickerClassified = (classification) => {
2999
2803
  return false;
3000
2804
  };
3001
2805
 
3002
- const isPackClassified = (classificationSummary) =>
3003
- Boolean(classificationSummary && Number(classificationSummary.classified_items || 0) > 0);
2806
+ const isPackClassified = (classificationSummary) => Boolean(classificationSummary && Number(classificationSummary.classified_items || 0) > 0);
3004
2807
 
3005
2808
  const normalizeCategoryToken = (value) =>
3006
2809
  String(value || '')
@@ -3122,7 +2925,10 @@ const escapeXml = (value) =>
3122
2925
  .replace(/"/g, '&quot;')
3123
2926
  .replace(/'/g, '&apos;');
3124
2927
 
3125
- const normalizeWhitespace = (value) => String(value || '').replace(/\s+/g, ' ').trim();
2928
+ const normalizeWhitespace = (value) =>
2929
+ String(value || '')
2930
+ .replace(/\s+/g, ' ')
2931
+ .trim();
3126
2932
 
3127
2933
  const truncateText = (value, maxLength = 160) => {
3128
2934
  const normalized = normalizeWhitespace(value);
@@ -3150,9 +2956,7 @@ const buildCatalogDiscoveryLinksHtml = async () => {
3150
2956
  offset: 0,
3151
2957
  });
3152
2958
 
3153
- const links = (Array.isArray(packs) ? packs : [])
3154
- .filter((pack) => pack?.pack_key && isPackPubliclyVisible(pack))
3155
- .slice(0, SEO_DISCOVERY_LINK_LIMIT);
2959
+ const links = (Array.isArray(packs) ? packs : []).filter((pack) => pack?.pack_key && isPackPubliclyVisible(pack)).slice(0, SEO_DISCOVERY_LINK_LIMIT);
3156
2960
 
3157
2961
  if (!links.length) {
3158
2962
  SEO_DISCOVERY_CACHE.expiresAt = Date.now() + SEO_DISCOVERY_CACHE_SECONDS * 1000;
@@ -3216,10 +3020,7 @@ const renderCatalogHtml = async ({ initialPackKey }) => {
3216
3020
  html = html.replace(/data-initial-pack-key="[^"]*"/i, initialPackKeyAttr);
3217
3021
 
3218
3022
  if (!/rel="canonical"/i.test(html)) {
3219
- html = html.replace(
3220
- '</head>',
3221
- ` <link rel="canonical" href="${escapeHtmlAttribute(toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`))}" />\n</head>`,
3222
- );
3023
+ html = html.replace('</head>', ` <link rel="canonical" href="${escapeHtmlAttribute(toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`))}" />\n</head>`);
3223
3024
  }
3224
3025
 
3225
3026
  const discoveryLinks = await buildCatalogDiscoveryLinksHtml();
@@ -3234,11 +3035,13 @@ const renderPackSeoHtml = ({ packSummary }) => {
3234
3035
  const packName = truncateText(packSummary?.name || packSummary?.pack_key || 'Pack', 95);
3235
3036
  const packDescription = truncateText(
3236
3037
  packSummary?.description
3237
- || `Pack de stickers "${packName}" disponível no catálogo OmniZap.`,
3038
+ || `Pack de stickers "${packName}" disponível no catálogo OmniZap para uso em bots e automações WhatsApp via API.`,
3238
3039
  180,
3239
3040
  );
3240
3041
  const canonicalUrl = toSiteAbsoluteUrl(buildPackWebUrl(packSummary?.pack_key || ''));
3241
3042
  const catalogUrl = toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`);
3043
+ const homeUrl = toSiteAbsoluteUrl('/');
3044
+ const apiDocsUrl = toSiteAbsoluteUrl('/api-docs/');
3242
3045
  const fallbackCoverUrl = packSummary?.is_nsfw ? NSFW_STICKER_PLACEHOLDER_URL : 'https://iili.io/fSNGag2.png';
3243
3046
  const coverUrl = toSiteAbsoluteUrl(packSummary?.cover_url || fallbackCoverUrl);
3244
3047
  const publisher = truncateText(packSummary?.publisher || 'Criador OmniZap', 80);
@@ -3262,13 +3065,47 @@ const renderPackSeoHtml = ({ packSummary }) => {
3262
3065
  null,
3263
3066
  0,
3264
3067
  ).replace(/</g, '\\u003c');
3068
+ const faqSchemaJson = JSON.stringify(
3069
+ {
3070
+ '@context': 'https://schema.org',
3071
+ '@type': 'FAQPage',
3072
+ mainEntity: [
3073
+ {
3074
+ '@type': 'Question',
3075
+ name: `Como usar o pack ${packName} no meu bot?`,
3076
+ acceptedAnswer: {
3077
+ '@type': 'Answer',
3078
+ text: `Use o pack ${packName} como recurso de engajamento e consulte exemplos de integração em ${apiDocsUrl}.`,
3079
+ },
3080
+ },
3081
+ {
3082
+ '@type': 'Question',
3083
+ name: 'Onde encontro mais packs de stickers?',
3084
+ acceptedAnswer: {
3085
+ '@type': 'Answer',
3086
+ text: `Veja o catálogo completo em ${catalogUrl}.`,
3087
+ },
3088
+ },
3089
+ {
3090
+ '@type': 'Question',
3091
+ name: 'Onde vejo a plataforma principal do OmniZap?',
3092
+ acceptedAnswer: {
3093
+ '@type': 'Answer',
3094
+ text: `A página principal do OmniZap está em ${homeUrl}.`,
3095
+ },
3096
+ },
3097
+ ],
3098
+ },
3099
+ null,
3100
+ 0,
3101
+ ).replace(/</g, '\\u003c');
3265
3102
 
3266
3103
  return `<!doctype html>
3267
3104
  <html lang="pt-BR">
3268
3105
  <head>
3269
3106
  <meta charset="utf-8" />
3270
3107
  <meta name="viewport" content="width=device-width, initial-scale=1" />
3271
- <title>${escapeHtmlAttribute(`${packName} | OmniZap Sticker Pack`)}</title>
3108
+ <title>${escapeHtmlAttribute(`${packName} | Stickers para Bot WhatsApp OmniZap`)}</title>
3272
3109
  <meta name="description" content="${escapeHtmlAttribute(packDescription)}" />
3273
3110
  <meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1" />
3274
3111
  <link rel="canonical" href="${escapeHtmlAttribute(canonicalUrl)}" />
@@ -3286,9 +3123,11 @@ const renderPackSeoHtml = ({ packSummary }) => {
3286
3123
  },
3287
3124
  colors: {
3288
3125
  slateApp: '#0f172a',
3289
- slateCard: '#111827',
3290
- borderApp: '#22304a',
3291
- accent: '#22c55e'
3126
+ slateCard: '#1e293b',
3127
+ borderApp: 'rgba(255,255,255,0.05)',
3128
+ accent: '#2563eb',
3129
+ accentTech: '#7c3aed',
3130
+ cta: '#22c55e'
3292
3131
  },
3293
3132
  boxShadow: {
3294
3133
  soft: '0 8px 24px rgba(2, 6, 23, 0.22)'
@@ -3313,15 +3152,16 @@ const renderPackSeoHtml = ({ packSummary }) => {
3313
3152
  <meta name="twitter:image" content="${escapeHtmlAttribute(coverUrl)}" />
3314
3153
 
3315
3154
  <script type="application/ld+json">${schemaJson}</script>
3155
+ <script type="application/ld+json">${faqSchemaJson}</script>
3316
3156
  <style>
3317
- body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif; background: #020617; color: #e5e7eb; }
3157
+ body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif; background: #0f172a; color: #f8fafc; }
3318
3158
  .seo-shell { max-width: 880px; margin: 0 auto; padding: 18px 14px 12px; }
3319
- .seo-card { border: 1px solid #1e293b; border-radius: 12px; background: #0b1220; padding: 16px; }
3159
+ .seo-card { border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; background: #1e293b; padding: 16px; }
3320
3160
  .seo-card h1 { margin: 0 0 8px; font-size: 26px; line-height: 1.2; }
3321
- .seo-card p { margin: 0 0 10px; line-height: 1.55; color: #cbd5e1; }
3161
+ .seo-card p { margin: 0 0 10px; line-height: 1.55; color: #94a3b8; }
3322
3162
  .seo-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
3323
- .seo-row a { color: #a5f3fc; text-decoration: none; border: 1px solid #334155; border-radius: 8px; padding: 8px 10px; }
3324
- .seo-row a:hover { background: #0f172a; }
3163
+ .seo-row a { color: #2563eb; text-decoration: none; border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 8px 10px; }
3164
+ .seo-row a:hover { background: #111827; }
3325
3165
  </style>
3326
3166
  </head>
3327
3167
  <body class="bg-slateApp text-slate-100 font-sans min-h-screen">
@@ -3331,9 +3171,15 @@ const renderPackSeoHtml = ({ packSummary }) => {
3331
3171
  <h1>${escapeHtmlAttribute(packName)}</h1>
3332
3172
  <p>${escapeHtmlAttribute(packDescription)}</p>
3333
3173
  <p>Criador: <strong>${escapeHtmlAttribute(publisher)}</strong> • Stickers: <strong>${stickerCount}</strong></p>
3174
+ <p>Use este pack como recurso integrado no seu bot. Consulte endpoints e exemplos na área de desenvolvedor da API OmniZap.</p>
3175
+ <h2 style="margin:12px 0 6px;font-size:18px;">FAQ rápido</h2>
3176
+ <p style="margin-bottom:6px;"><strong>Como usar no bot?</strong> Consulte a documentação técnica e exemplos na área de desenvolvedor.</p>
3177
+ <p style="margin-bottom:6px;"><strong>Tem mais packs?</strong> Sim, explore o catálogo completo para encontrar packs relacionados.</p>
3334
3178
  <div class="seo-row">
3335
3179
  <a href="${escapeHtmlAttribute(canonicalUrl)}">Abrir este pack</a>
3336
3180
  <a href="${escapeHtmlAttribute(catalogUrl)}">Voltar ao catálogo</a>
3181
+ <a href="${escapeHtmlAttribute(apiDocsUrl)}">Área de Desenvolvedor</a>
3182
+ <a href="${escapeHtmlAttribute(homeUrl)}">Plataforma OmniZap</a>
3337
3183
  </div>
3338
3184
  </section>
3339
3185
  </main>
@@ -3362,10 +3208,10 @@ const renderPackNotFoundHtml = (packKey = '') => `<!doctype html>
3362
3208
  <meta name="robots" content="noindex, nofollow" />
3363
3209
  <link rel="canonical" href="${escapeHtmlAttribute(toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`))}" />
3364
3210
  <style>
3365
- body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: #020617; color: #e5e7eb; }
3211
+ body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: #0f172a; color: #f8fafc; }
3366
3212
  main { max-width: 760px; margin: 0 auto; padding: 20px 14px; }
3367
- article { border: 1px solid #1e293b; border-radius: 12px; background: #0b1220; padding: 16px; }
3368
- a { color: #67e8f9; text-decoration: none; }
3213
+ article { border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; background: #1e293b; padding: 16px; }
3214
+ a { color: #2563eb; text-decoration: none; }
3369
3215
  </style>
3370
3216
  </head>
3371
3217
  <body>
@@ -3439,8 +3285,17 @@ const buildSitemapXml = async () => {
3439
3285
  { loc: toSiteAbsoluteUrl('/'), changefreq: 'daily', priority: '1.0' },
3440
3286
  { loc: toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`), changefreq: 'hourly', priority: '0.9' },
3441
3287
  { loc: toSiteAbsoluteUrl('/api-docs/'), changefreq: 'weekly', priority: '0.8' },
3288
+ { loc: toSiteAbsoluteUrl('/comandos/'), changefreq: 'weekly', priority: '0.78' },
3442
3289
  { loc: toSiteAbsoluteUrl('/termos-de-uso/'), changefreq: 'monthly', priority: '0.5' },
3443
3290
  { loc: toSiteAbsoluteUrl('/licenca/'), changefreq: 'monthly', priority: '0.5' },
3291
+ { loc: toSiteAbsoluteUrl('/bot-whatsapp-para-grupo/'), changefreq: 'weekly', priority: '0.75' },
3292
+ { loc: toSiteAbsoluteUrl('/como-moderar-grupo-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
3293
+ { loc: toSiteAbsoluteUrl('/como-evitar-spam-no-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
3294
+ { loc: toSiteAbsoluteUrl('/como-organizar-comunidade-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
3295
+ { loc: toSiteAbsoluteUrl('/como-automatizar-avisos-no-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
3296
+ { loc: toSiteAbsoluteUrl('/como-criar-comandos-whatsapp/'), changefreq: 'weekly', priority: '0.71' },
3297
+ { loc: toSiteAbsoluteUrl('/melhor-bot-whatsapp-para-grupos/'), changefreq: 'weekly', priority: '0.74' },
3298
+ { loc: toSiteAbsoluteUrl('/bot-whatsapp-sem-programar/'), changefreq: 'weekly', priority: '0.73' },
3444
3299
  ];
3445
3300
 
3446
3301
  const packRows = await executeQuery(
@@ -3496,9 +3351,7 @@ const sendStaticTextFile = async (req, res, filePath, contentType) => {
3496
3351
  try {
3497
3352
  const body = await fs.readFile(filePath, 'utf8');
3498
3353
  const hasVersionQuery = /(?:\?|&)v=/.test(String(req.url || ''));
3499
- const cacheControl = hasVersionQuery
3500
- ? `public, max-age=${IMMUTABLE_ASSET_CACHE_SECONDS}, immutable`
3501
- : `public, max-age=${STATIC_TEXT_CACHE_SECONDS}, stale-while-revalidate=${Math.min(86400, STATIC_TEXT_CACHE_SECONDS * 4)}`;
3354
+ const cacheControl = hasVersionQuery ? `public, max-age=${IMMUTABLE_ASSET_CACHE_SECONDS}, immutable` : `public, max-age=${STATIC_TEXT_CACHE_SECONDS}, stale-while-revalidate=${Math.min(86400, STATIC_TEXT_CACHE_SECONDS * 4)}`;
3502
3355
  res.statusCode = 200;
3503
3356
  res.setHeader('Content-Type', contentType);
3504
3357
  res.setHeader('Cache-Control', cacheControl);
@@ -3548,18 +3401,7 @@ const handleListRequest = async (req, res, url) => {
3548
3401
  const normalizedIntent = normalizeCategoryToken(intent).replace(/-/g, '_');
3549
3402
  const googleSession = await resolveGoogleWebSessionFromRequest(req);
3550
3403
  const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
3551
- const cacheKey = buildCacheKey([
3552
- 'list',
3553
- q,
3554
- visibility,
3555
- sort,
3556
- categories.join(','),
3557
- normalizedIntent,
3558
- includeSensitive ? 1 : 0,
3559
- limit,
3560
- offset,
3561
- hasNsfwAccess ? 1 : 0,
3562
- ]);
3404
+ const cacheKey = buildCacheKey(['list', q, visibility, sort, categories.join(','), normalizedIntent, includeSensitive ? 1 : 0, limit, offset, hasNsfwAccess ? 1 : 0]);
3563
3405
  const payload = await getCachedSnapshot({
3564
3406
  cacheMap: CATALOG_LIST_CACHE,
3565
3407
  key: cacheKey,
@@ -3589,18 +3431,10 @@ const handleListRequest = async (req, res, url) => {
3589
3431
  if (!packs.length) break;
3590
3432
 
3591
3433
  const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
3592
- const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
3593
- ? entries.filter((entry) => isPackClassified(entry.packClassification))
3594
- : entries;
3595
- const entriesByCategory = categories.length
3596
- ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
3597
- : entriesClassified;
3598
- const entriesBySensitivity = includeSensitive
3599
- ? entriesByCategory
3600
- : entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
3601
- const entriesByIntent = intent
3602
- ? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent)
3603
- : entriesBySensitivity;
3434
+ const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
3435
+ const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
3436
+ const entriesBySensitivity = includeSensitive ? entriesByCategory : entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
3437
+ const entriesByIntent = intent ? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent) : entriesBySensitivity;
3604
3438
  const sortedEntries = [...entriesByIntent].sort((left, right) => {
3605
3439
  const completenessDelta = compareEntriesByPackCompleteness(left, right);
3606
3440
  if (completenessDelta !== 0) return completenessDelta;
@@ -3676,12 +3510,8 @@ const handleIntentCollectionsRequest = async (req, res, url) => {
3676
3510
  });
3677
3511
  const driftSnapshot = await getMarketplaceDriftSnapshot();
3678
3512
  const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
3679
- const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
3680
- ? entries.filter((entry) => isPackClassified(entry.packClassification))
3681
- : entries;
3682
- const entriesByCategory = categories.length
3683
- ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
3684
- : entriesClassified;
3513
+ const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
3514
+ const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
3685
3515
  const intents = buildIntentCollections(entriesByCategory, { limit });
3686
3516
 
3687
3517
  sendJson(req, res, 200, {
@@ -3701,12 +3531,7 @@ const handleIntentCollectionsRequest = async (req, res, url) => {
3701
3531
  });
3702
3532
  };
3703
3533
 
3704
- const resolveMarketplaceVisibilityValues = (visibility) =>
3705
- visibility === 'all'
3706
- ? ['public', 'unlisted']
3707
- : visibility === 'unlisted'
3708
- ? ['unlisted']
3709
- : ['public'];
3534
+ const resolveMarketplaceVisibilityValues = (visibility) => (visibility === 'all' ? ['public', 'unlisted'] : visibility === 'unlisted' ? ['unlisted'] : ['public']);
3710
3535
 
3711
3536
  const getHomeMarketplaceStatsCacheBucket = (visibility) => {
3712
3537
  const key = normalizeCatalogVisibility(visibility);
@@ -3843,9 +3668,7 @@ const handleCreatePackConfigRequest = async (req, res) => {
3843
3668
  pack_name_hint: 'Nome livre (espaços e emojis são permitidos).',
3844
3669
  visibility_values: ['public', 'unlisted', 'private'],
3845
3670
  owner_phone_required: !STICKER_WEB_GOOGLE_AUTH_REQUIRED,
3846
- owner_phone_hint: STICKER_WEB_GOOGLE_AUTH_REQUIRED
3847
- ? 'Login Google obrigatório para criar packs nesta página.'
3848
- : 'Informe o número de celular com DDD para vincular o pack ao criador.',
3671
+ owner_phone_hint: STICKER_WEB_GOOGLE_AUTH_REQUIRED ? 'Login Google obrigatório para criar packs nesta página.' : 'Informe o número de celular com DDD para vincular o pack ao criador.',
3849
3672
  suggested_tags: ['anime', 'meme', 'game', 'texto', 'nsfw', 'dark', 'cartoon', 'foto-real', 'cyberpunk'],
3850
3673
  },
3851
3674
  auth: {
@@ -3936,15 +3759,12 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
3936
3759
  return;
3937
3760
  }
3938
3761
 
3939
- const reason = String(linkedOwner.reason || '').trim().toLowerCase();
3762
+ const reason = String(linkedOwner.reason || '')
3763
+ .trim()
3764
+ .toLowerCase();
3940
3765
  const isUnauthorizedAttempt = ['invalid_signature', 'missing_signature'].includes(reason);
3941
3766
  const statusCode = isUnauthorizedAttempt ? 403 : 400;
3942
- const errorMessage =
3943
- reason === 'expired'
3944
- ? 'Link de login expirado. Envie "iniciar" novamente no WhatsApp.'
3945
- : isUnauthorizedAttempt
3946
- ? 'Tentativa de login sem permissao detectada. Gere um novo link enviando "iniciar" no privado do bot.'
3947
- : 'Link de login invalido. Envie "iniciar" novamente no WhatsApp.';
3767
+ const errorMessage = reason === 'expired' ? 'Link de login expirado. Envie "iniciar" novamente no WhatsApp.' : isUnauthorizedAttempt ? 'Tentativa de login sem permissao detectada. Gere um novo link enviando "iniciar" no privado do bot.' : 'Link de login invalido. Envie "iniciar" novamente no WhatsApp.';
3948
3768
 
3949
3769
  logger.warn('Tentativa de login web bloqueada por validacao do link WhatsApp.', {
3950
3770
  action: 'sticker_pack_google_web_login_link_blocked',
@@ -4255,13 +4075,8 @@ const handleMyProfileRequest = async (req, res, url = null) => {
4255
4075
  return;
4256
4076
  }
4257
4077
 
4258
- const ownerPacks = await Promise.all(
4259
- ownerCandidates.map((ownerJid) => listStickerPacksByOwner(ownerJid, { limit: 200, offset: 0 })),
4260
- );
4261
- const includeAutoPacks = parseEnvBool(
4262
- url?.searchParams?.get('include_auto'),
4263
- parseEnvBool(process.env.STICKER_WEB_MY_PROFILE_INCLUDE_AUTO_PACKS, false),
4264
- );
4078
+ const ownerPacks = await Promise.all(ownerCandidates.map((ownerJid) => listStickerPacksByOwner(ownerJid, { limit: 200, offset: 0 })));
4079
+ const includeAutoPacks = parseEnvBool(url?.searchParams?.get('include_auto'), parseEnvBool(process.env.STICKER_WEB_MY_PROFILE_INCLUDE_AUTO_PACKS, false));
4265
4080
 
4266
4081
  const dedupPacks = new Map();
4267
4082
  for (const packList of ownerPacks) {
@@ -4429,11 +4244,7 @@ const cleanupOrphanStickerAssets = async (assetIds, { reason = 'manage_mutation'
4429
4244
 
4430
4245
  const deleteManagedPackWithCleanup = async ({ ownerJid, identifier, fallbackPack = null }) => {
4431
4246
  const transactionResult = await runSqlTransaction(async (connection) => {
4432
- const pack =
4433
- (await findStickerPackByOwnerAndIdentifier(ownerJid, fallbackPack?.id || identifier, { connection })) ||
4434
- (fallbackPack?.pack_key && fallbackPack?.pack_key !== identifier
4435
- ? await findStickerPackByOwnerAndIdentifier(ownerJid, identifier, { connection })
4436
- : null);
4247
+ const pack = (await findStickerPackByOwnerAndIdentifier(ownerJid, fallbackPack?.id || identifier, { connection })) || (fallbackPack?.pack_key && fallbackPack?.pack_key !== identifier ? await findStickerPackByOwnerAndIdentifier(ownerJid, identifier, { connection }) : null);
4437
4248
 
4438
4249
  if (!pack) {
4439
4250
  return {
@@ -4567,9 +4378,7 @@ const buildManagedPackAnalytics = async (pack) => {
4567
4378
  downloads: Number(engagement?.open_count || 0),
4568
4379
  likes: Number(engagement?.like_count || 0),
4569
4380
  dislikes: Number(engagement?.dislike_count || 0),
4570
- score:
4571
- Number(engagement?.score || 0) ||
4572
- Number(engagement?.like_count || 0) - Number(engagement?.dislike_count || 0),
4381
+ score: Number(engagement?.score || 0) || Number(engagement?.like_count || 0) - Number(engagement?.dislike_count || 0),
4573
4382
  engagement: {
4574
4383
  open_count: Number(engagement?.open_count || 0),
4575
4384
  like_count: Number(engagement?.like_count || 0),
@@ -4591,12 +4400,7 @@ const buildManagedPackResponseData = async (pack) => {
4591
4400
  const items = Array.isArray(pack?.items) ? pack.items : [];
4592
4401
  const stickerIds = items.map((item) => item.sticker_id).filter(Boolean);
4593
4402
 
4594
- const [classifications, packClassification, analytics, publishState] = await Promise.all([
4595
- stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([]),
4596
- pack?.classification || (stickerIds.length ? getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null) : null),
4597
- buildManagedPackAnalytics(pack),
4598
- buildPackPublishStateData(pack, { includeUploads: true }),
4599
- ]);
4403
+ const [classifications, packClassification, analytics, publishState] = await Promise.all([stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([]), pack?.classification || (stickerIds.length ? getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null) : null), buildManagedPackAnalytics(pack), buildPackPublishStateData(pack, { includeUploads: true })]);
4600
4404
 
4601
4405
  const byAssetClassification = new Map((Array.isArray(classifications) ? classifications : []).map((entry) => [entry.asset_id, entry]));
4602
4406
 
@@ -4735,8 +4539,12 @@ const handleManagedPackRequest = async (req, res, packKey) => {
4735
4539
  }
4736
4540
 
4737
4541
  if (hasOwn(payload, 'visibility')) {
4738
- const nextVisibility = String(payload?.visibility || '').trim().toLowerCase();
4739
- const currentVisibility = String(updatedPack?.visibility || '').trim().toLowerCase();
4542
+ const nextVisibility = String(payload?.visibility || '')
4543
+ .trim()
4544
+ .toLowerCase();
4545
+ const currentVisibility = String(updatedPack?.visibility || '')
4546
+ .trim()
4547
+ .toLowerCase();
4740
4548
  if (!nextVisibility) {
4741
4549
  updatedPack = await stickerPackService.setPackVisibility({
4742
4550
  ownerJid: context.ownerJid,
@@ -4859,9 +4667,7 @@ const handleManagedPackCoverRequest = async (req, res, packKey) => {
4859
4667
  return;
4860
4668
  }
4861
4669
  if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND) {
4862
- const fresh = await stickerPackService
4863
- .getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
4864
- .catch(() => context.pack);
4670
+ const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
4865
4671
  await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
4866
4672
  pack_key: context.packKey,
4867
4673
  sticker_id: sanitizeText(payload?.sticker_id, 36, { allowEmpty: true }) || null,
@@ -4924,9 +4730,7 @@ const handleManagedPackReorderRequest = async (req, res, packKey) => {
4924
4730
  return;
4925
4731
  }
4926
4732
  if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.INVALID_INPUT) {
4927
- const fresh = await stickerPackService
4928
- .getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
4929
- .catch(() => context.pack);
4733
+ const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
4930
4734
  await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
4931
4735
  pack_key: context.packKey,
4932
4736
  reason: 'invalid_or_stale_order',
@@ -4977,9 +4781,7 @@ const handleManagedPackStickerDeleteRequest = async (req, res, packKey, stickerI
4977
4781
  return;
4978
4782
  }
4979
4783
  if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND) {
4980
- const fresh = await stickerPackService
4981
- .getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
4982
- .catch(() => context.pack);
4784
+ const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
4983
4785
  await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
4984
4786
  pack_key: context.packKey,
4985
4787
  sticker_id: sanitizeText(stickerId, 36, { allowEmpty: true }) || null,
@@ -5218,9 +5020,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
5218
5020
  }
5219
5021
 
5220
5022
  if (swapResult?.status === 'old_sticker_missing') {
5221
- const fresh = await stickerPackService
5222
- .getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
5223
- .catch(() => originalPack);
5023
+ const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => originalPack);
5224
5024
  await cleanupOrphanStickerAssets(uploadedAssetId ? [uploadedAssetId] : [], { reason: 'replace_sticker_old_missing' });
5225
5025
  await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
5226
5026
  pack_key: context.packKey,
@@ -5230,9 +5030,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
5230
5030
  }
5231
5031
 
5232
5032
  if (swapResult?.status === 'duplicate_target') {
5233
- const fresh = await stickerPackService
5234
- .getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
5235
- .catch(() => originalPack);
5033
+ const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => originalPack);
5236
5034
  await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
5237
5035
  pack_key: context.packKey,
5238
5036
  reason: 'duplicate_target_sticker',
@@ -5297,8 +5095,7 @@ const handleManagedPackAnalyticsRequest = async (req, res, packKey) => {
5297
5095
  }
5298
5096
  };
5299
5097
 
5300
- const normalizeCreatePackName = (value) =>
5301
- sanitizeText(value, PACK_CREATE_MAX_NAME_LENGTH, { allowEmpty: true }) || '';
5098
+ const normalizeCreatePackName = (value) => sanitizeText(value, PACK_CREATE_MAX_NAME_LENGTH, { allowEmpty: true }) || '';
5302
5099
 
5303
5100
  const mapStickerPackCreateError = (error) => {
5304
5101
  if (!(error instanceof StickerPackError)) {
@@ -5356,7 +5153,9 @@ const handleCreatePackRequest = async (req, res) => {
5356
5153
  const description = sanitizeText(payload?.description || '', PACK_CREATE_MAX_DESCRIPTION_LENGTH, { allowEmpty: true });
5357
5154
  const manualTags = mergeUniqueTags(Array.isArray(payload?.tags) ? payload.tags : []).slice(0, 8);
5358
5155
  const persistedDescription = buildPackDescriptionWithTags(description, manualTags);
5359
- const visibility = String(payload?.visibility || 'public').trim().toLowerCase();
5156
+ const visibility = String(payload?.visibility || 'public')
5157
+ .trim()
5158
+ .toLowerCase();
5360
5159
  const googleSession = await resolveGoogleWebSessionFromRequest(req);
5361
5160
  if (!googleSession?.ownerJid || !googleSession?.sub) {
5362
5161
  sendJson(req, res, 401, {
@@ -5556,10 +5355,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
5556
5355
 
5557
5356
  let existingUpload = await findPackWebUploadByUploadId(pack.id, uploadId, connection);
5558
5357
  if (existingUpload && existingUpload.sticker_hash !== stickerHash) {
5559
- throw new StickerPackError(
5560
- STICKER_PACK_ERROR_CODES.INVALID_INPUT,
5561
- 'upload_id já foi usado para outro arquivo neste pack.',
5562
- );
5358
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'upload_id já foi usado para outro arquivo neste pack.');
5563
5359
  }
5564
5360
 
5565
5361
  if (!existingUpload) {
@@ -5585,17 +5381,11 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
5585
5381
  }
5586
5382
 
5587
5383
  if (currentPackStatus === 'published') {
5588
- throw new StickerPackError(
5589
- STICKER_PACK_ERROR_CODES.NOT_ALLOWED,
5590
- 'Pack já foi publicado. Crie um novo pack para enviar novos stickers.',
5591
- );
5384
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.NOT_ALLOWED, 'Pack já foi publicado. Crie um novo pack para enviar novos stickers.');
5592
5385
  }
5593
5386
 
5594
5387
  if (currentPackStatus === 'processing') {
5595
- throw new StickerPackError(
5596
- STICKER_PACK_ERROR_CODES.NOT_ALLOWED,
5597
- 'Pack está em finalização. Aguarde e tente novamente.',
5598
- );
5388
+ throw new StickerPackError(STICKER_PACK_ERROR_CODES.NOT_ALLOWED, 'Pack está em finalização. Aguarde e tente novamente.');
5599
5389
  }
5600
5390
 
5601
5391
  if (!existingUpload) {
@@ -5692,19 +5482,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
5692
5482
  throw new StickerPackError(STICKER_PACK_ERROR_CODES.PACK_NOT_FOUND, 'Pack nao encontrado.');
5693
5483
  }
5694
5484
 
5695
- const uploadRow =
5696
- (reservedUpload?.id &&
5697
- normalizePackWebUploadRow(
5698
- (
5699
- await executeQuery(
5700
- `SELECT * FROM ${TABLES.STICKER_PACK_WEB_UPLOAD} WHERE id = ? LIMIT 1`,
5701
- [reservedUpload.id],
5702
- connection,
5703
- )
5704
- )?.[0],
5705
- )) ||
5706
- (await findPackWebUploadByUploadId(pack.id, uploadId, connection)) ||
5707
- (await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
5485
+ const uploadRow = (reservedUpload?.id && normalizePackWebUploadRow((await executeQuery(`SELECT * FROM ${TABLES.STICKER_PACK_WEB_UPLOAD} WHERE id = ? LIMIT 1`, [reservedUpload.id], connection))?.[0])) || (await findPackWebUploadByUploadId(pack.id, uploadId, connection)) || (await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
5708
5486
 
5709
5487
  if (!uploadRow) {
5710
5488
  throw new StickerPackError(STICKER_PACK_ERROR_CODES.INTERNAL_ERROR, 'Registro de upload não encontrado para finalizar.');
@@ -5729,11 +5507,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
5729
5507
  packStatusForResponse = 'published';
5730
5508
  }
5731
5509
 
5732
- const snapshot = await getPackConsistencySnapshot(
5733
- pack.id,
5734
- payload?.set_cover === true ? asset.id : lockedPackRow.cover_sticker_id,
5735
- connection,
5736
- );
5510
+ const snapshot = await getPackConsistencySnapshot(pack.id, payload?.set_cover === true ? asset.id : lockedPackRow.cover_sticker_id, connection);
5737
5511
  responseStickerCount = snapshot.sticker_count;
5738
5512
  });
5739
5513
 
@@ -5762,9 +5536,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
5762
5536
  } catch (error) {
5763
5537
  if (reservedUpload?.id) {
5764
5538
  await runSqlTransaction(async (connection) => {
5765
- const currentUpload =
5766
- (await findPackWebUploadByUploadId(pack.id, uploadId, connection)) ||
5767
- (await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
5539
+ const currentUpload = (await findPackWebUploadByUploadId(pack.id, uploadId, connection)) || (await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
5768
5540
  if (currentUpload) {
5769
5541
  await updatePackWebUpload(
5770
5542
  currentUpload.id,
@@ -5824,9 +5596,7 @@ const handlePackPublishStateRequest = async (req, res, packKey, url = null) => {
5824
5596
  }
5825
5597
  }
5826
5598
 
5827
- const editTokenValue =
5828
- (req.method === 'GET' || req.method === 'HEAD' ? String(url?.searchParams?.get('edit_token') || '') : '') ||
5829
- String(payload?.edit_token || '');
5599
+ const editTokenValue = (req.method === 'GET' || req.method === 'HEAD' ? String(url?.searchParams?.get('edit_token') || '') : '') || String(payload?.edit_token || '');
5830
5600
  const editToken = resolveWebPackEditToken(editTokenValue);
5831
5601
  if (!editToken || editToken.packId !== pack.id || editToken.ownerJid !== pack.owner_jid) {
5832
5602
  sendJson(req, res, 403, {
@@ -5901,12 +5671,7 @@ const handleFinalizePackRequest = async (req, res, packKey) => {
5901
5671
  await setStickerPackStatus(pack.id, 'processing', connection);
5902
5672
 
5903
5673
  const snapshot = await getPackConsistencySnapshot(pack.id, lockedPackRow.cover_sticker_id, connection);
5904
- const canPublish =
5905
- snapshot.sticker_count >= 1 &&
5906
- snapshot.failed_uploads === 0 &&
5907
- snapshot.processing_uploads === 0 &&
5908
- snapshot.pending_uploads === 0 &&
5909
- snapshot.cover_valid;
5674
+ const canPublish = snapshot.sticker_count >= 1 && snapshot.failed_uploads === 0 && snapshot.processing_uploads === 0 && snapshot.pending_uploads === 0 && snapshot.cover_valid;
5910
5675
 
5911
5676
  if (canPublish) {
5912
5677
  await setStickerPackStatus(pack.id, 'published', connection);
@@ -5922,18 +5687,7 @@ const handleFinalizePackRequest = async (req, res, packKey) => {
5922
5687
  finalizeResult = {
5923
5688
  canPublish: false,
5924
5689
  packStatus: 'draft',
5925
- reason:
5926
- snapshot.failed_uploads > 0
5927
- ? 'failed_uploads'
5928
- : snapshot.processing_uploads > 0
5929
- ? 'uploads_processing'
5930
- : snapshot.pending_uploads > 0
5931
- ? 'uploads_pending'
5932
- : !snapshot.cover_valid
5933
- ? 'cover_missing'
5934
- : snapshot.sticker_count < 1
5935
- ? 'not_enough_stickers'
5936
- : 'inconsistent',
5690
+ reason: snapshot.failed_uploads > 0 ? 'failed_uploads' : snapshot.processing_uploads > 0 ? 'uploads_processing' : snapshot.pending_uploads > 0 ? 'uploads_pending' : !snapshot.cover_valid ? 'cover_missing' : snapshot.sticker_count < 1 ? 'not_enough_stickers' : 'inconsistent',
5937
5691
  };
5938
5692
  });
5939
5693
  } catch (error) {
@@ -6004,13 +5758,7 @@ const handleCreatorRankingRequest = async (req, res, url) => {
6004
5758
  const limit = clampInt(url.searchParams.get('limit'), 50, 5, 200);
6005
5759
  const googleSession = await resolveGoogleWebSessionFromRequest(req);
6006
5760
  const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
6007
- const cacheKey = buildCacheKey([
6008
- 'creator_ranking',
6009
- visibility,
6010
- q,
6011
- limit,
6012
- hasNsfwAccess ? 1 : 0,
6013
- ]);
5761
+ const cacheKey = buildCacheKey(['creator_ranking', visibility, q, limit, hasNsfwAccess ? 1 : 0]);
6014
5762
  const payload = await getCachedSnapshot({
6015
5763
  cacheMap: CATALOG_CREATOR_RANKING_CACHE,
6016
5764
  key: cacheKey,
@@ -6026,20 +5774,11 @@ const handleCreatorRankingRequest = async (req, res, url) => {
6026
5774
  });
6027
5775
  const driftSnapshot = await getMarketplaceDriftSnapshot();
6028
5776
  const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
6029
- const ranking = buildCreatorRanking(
6030
- STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries,
6031
- { limit },
6032
- );
5777
+ const ranking = buildCreatorRanking(STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries, { limit });
6033
5778
 
6034
5779
  return {
6035
5780
  data: ranking.map((creator) => ({
6036
- creator_score: Number(
6037
- (
6038
- Number(creator.avg_pack_score || 0) * 0.45 +
6039
- Number(creator.total_likes || 0) * 0.0008 +
6040
- Number(creator.total_opens || 0) * 0.00015
6041
- ).toFixed(6),
6042
- ),
5781
+ creator_score: Number((Number(creator.avg_pack_score || 0) * 0.45 + Number(creator.total_likes || 0) * 0.0008 + Number(creator.total_opens || 0) * 0.00015).toFixed(6)),
6043
5782
  publisher: creator.publisher,
6044
5783
  verified: Boolean(creator.verified),
6045
5784
  badges: creator.verified ? ['verified_creator'] : [],
@@ -6080,12 +5819,8 @@ const handleRecommendationsRequest = async (req, res, url) => {
6080
5819
  });
6081
5820
  const driftSnapshot = await getMarketplaceDriftSnapshot();
6082
5821
  const { entries, packClassificationById } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
6083
- const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
6084
- ? entries.filter((entry) => isPackClassified(entry.packClassification))
6085
- : entries;
6086
- const entriesByCategory = categories.length
6087
- ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
6088
- : entriesClassified;
5822
+ const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
5823
+ const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
6089
5824
 
6090
5825
  const viewerRecentPackIds = viewerKey ? await listViewerRecentPackIds(viewerKey, { days: 45, limit: 160 }) : [];
6091
5826
  const viewerAffinity = buildViewerTagAffinity({
@@ -6130,27 +5865,20 @@ const handleOrphanStickerListRequest = async (req, res, url) => {
6130
5865
 
6131
5866
  const { assets, hasMore, total } = categories.length
6132
5867
  ? await listClassifiedOrphanAssetsByCategories({ search: q, categories, limit, offset })
6133
- : await (
6134
- STICKER_CATALOG_ONLY_CLASSIFIED ? listClassifiedStickerAssetsWithoutPack : listStickerAssetsWithoutPack
6135
- )({
5868
+ : await (STICKER_CATALOG_ONLY_CLASSIFIED ? listClassifiedStickerAssetsWithoutPack : listStickerAssetsWithoutPack)({
6136
5869
  search: q,
6137
5870
  limit,
6138
5871
  offset,
6139
5872
  });
6140
5873
  const classifications = await listStickerClassificationsByAssetIds(assets.map((asset) => asset.id));
6141
5874
  const byAssetId = new Map(classifications.map((entry) => [entry.asset_id, entry]));
6142
- const filteredAssets = STICKER_CATALOG_ONLY_CLASSIFIED
6143
- ? assets.filter((asset) => isStickerClassified(byAssetId.get(asset.id)))
6144
- : assets;
6145
- const filteredByCategories = categories.length
6146
- ? filteredAssets.filter((asset) => hasAnyCategory(resolveClassificationTags(byAssetId.get(asset.id)), categories))
6147
- : filteredAssets;
5875
+ const filteredAssets = STICKER_CATALOG_ONLY_CLASSIFIED ? assets.filter((asset) => isStickerClassified(byAssetId.get(asset.id))) : assets;
5876
+ const filteredByCategories = categories.length ? filteredAssets.filter((asset) => hasAnyCategory(resolveClassificationTags(byAssetId.get(asset.id)), categories)) : filteredAssets;
6148
5877
  const currentPage = Math.floor(offset / limit) + 1;
6149
5878
  const totalPages = Math.max(1, Math.ceil(total / limit));
6150
5879
 
6151
5880
  sendJson(req, res, 200, {
6152
- data: filteredByCategories.map((asset) =>
6153
- mapOrphanStickerAsset(asset, byAssetId.get(asset.id) || null, { hideSensitiveAssets: !hasNsfwAccess })),
5881
+ data: filteredByCategories.map((asset) => mapOrphanStickerAsset(asset, byAssetId.get(asset.id) || null, { hideSensitiveAssets: !hasNsfwAccess })),
6154
5882
  pagination: {
6155
5883
  limit,
6156
5884
  offset,
@@ -6174,11 +5902,7 @@ const handleDataFileListRequest = async (req, res, url) => {
6174
5902
  const normalizedQuery = q.toLowerCase();
6175
5903
 
6176
5904
  const allFiles = await listDataImageFiles();
6177
- const filteredFiles = normalizedQuery
6178
- ? allFiles.filter(
6179
- (item) => item.name.toLowerCase().includes(normalizedQuery) || item.relative_path.toLowerCase().includes(normalizedQuery),
6180
- )
6181
- : allFiles;
5905
+ const filteredFiles = normalizedQuery ? allFiles.filter((item) => item.name.toLowerCase().includes(normalizedQuery) || item.relative_path.toLowerCase().includes(normalizedQuery)) : allFiles;
6182
5906
 
6183
5907
  const page = filteredFiles.slice(offset, offset + limit);
6184
5908
  const hasMore = offset + limit < filteredFiles.length;
@@ -6214,11 +5938,7 @@ const buildSystemSummarySnapshot = async () => {
6214
5938
  const botJid = resolveBotJid(activeSocket?.user?.id) || null;
6215
5939
  const botPhone = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '') || null;
6216
5940
  const botConnected = Boolean(botJid) && socketReadyState === 1;
6217
- const botConnectionStatus = botConnected
6218
- ? 'online'
6219
- : socketReadyState === 0
6220
- ? 'connecting'
6221
- : 'offline';
5941
+ const botConnectionStatus = botConnected ? 'online' : socketReadyState === 0 ? 'connecting' : 'offline';
6222
5942
 
6223
5943
  let platform = {
6224
5944
  total_users: null,
@@ -6421,16 +6141,7 @@ const extractCommandsFromMenuLine = (line, commandPrefix) => {
6421
6141
  };
6422
6142
 
6423
6143
  const collectAvailableMenuCommands = (commandPrefix = README_COMMAND_PREFIX) => {
6424
- const sections = [
6425
- buildMenuCaption('OmniZap', commandPrefix),
6426
- buildStickerMenu(commandPrefix),
6427
- buildMediaMenu(commandPrefix),
6428
- buildQuoteMenu(commandPrefix),
6429
- buildAnimeMenu(commandPrefix),
6430
- buildAiMenu(commandPrefix),
6431
- buildStatsMenu(commandPrefix),
6432
- buildAdminMenu(commandPrefix),
6433
- ];
6144
+ const sections = [buildMenuCaption('OmniZap', commandPrefix), buildStickerMenu(commandPrefix), buildMediaMenu(commandPrefix), buildQuoteMenu(commandPrefix), buildAnimeMenu(commandPrefix), buildAiMenu(commandPrefix), buildStatsMenu(commandPrefix), buildAdminMenu(commandPrefix)];
6434
6145
 
6435
6146
  const commands = new Set();
6436
6147
  for (const section of sections) {
@@ -6446,49 +6157,15 @@ const collectAvailableMenuCommands = (commandPrefix = README_COMMAND_PREFIX) =>
6446
6157
  };
6447
6158
 
6448
6159
  const renderReadmeSnapshotMarkdown = ({ generatedAt, totals, topMessageTypes, commands }) => {
6449
- const typeRows = topMessageTypes.length
6450
- ? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`)
6451
- : ['| `outros` | 0 |'];
6160
+ const typeRows = topMessageTypes.length ? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`) : ['| `outros` | 0 |'];
6452
6161
 
6453
6162
  const commandInline = commands.length ? commands.map((command) => `\`${command}\``).join(' · ') : 'Nenhum comando identificado no menu atual.';
6454
6163
 
6455
- return [
6456
- '### Snapshot do Sistema',
6457
- '',
6458
- `> Atualizado em \`${generatedAt}\` | cache \`${README_SUMMARY_CACHE_SECONDS}s\``,
6459
- '',
6460
- '| Métrica | Valor |',
6461
- '| --- | ---: |',
6462
- `| Usuários (lid_map) | ${formatPtBrInteger(totals.total_users)} |`,
6463
- `| Grupos | ${formatPtBrInteger(totals.total_groups)} |`,
6464
- `| Packs | ${formatPtBrInteger(totals.total_packs)} |`,
6465
- `| Stickers | ${formatPtBrInteger(totals.total_stickers)} |`,
6466
- `| Mensagens registradas | ${formatPtBrInteger(totals.total_messages)} |`,
6467
- '',
6468
- `#### Tipos de mensagem mais usados (amostra: ${formatPtBrInteger(totals.message_types_sample_size)})`,
6469
- '| Tipo | Total |',
6470
- '| --- | ---: |',
6471
- ...typeRows,
6472
- '',
6473
- `<details><summary>Comandos disponíveis (${formatPtBrInteger(commands.length)})</summary>`,
6474
- '',
6475
- commandInline,
6476
- '',
6477
- '</details>',
6478
- '',
6479
- ].join('\n');
6164
+ return ['### Snapshot do Sistema', '', `> Atualizado em \`${generatedAt}\` | cache \`${README_SUMMARY_CACHE_SECONDS}s\``, '', '| Métrica | Valor |', '| --- | ---: |', `| Usuários (lid_map) | ${formatPtBrInteger(totals.total_users)} |`, `| Grupos | ${formatPtBrInteger(totals.total_groups)} |`, `| Packs | ${formatPtBrInteger(totals.total_packs)} |`, `| Stickers | ${formatPtBrInteger(totals.total_stickers)} |`, `| Mensagens registradas | ${formatPtBrInteger(totals.total_messages)} |`, '', `#### Tipos de mensagem mais usados (amostra: ${formatPtBrInteger(totals.message_types_sample_size)})`, '| Tipo | Total |', '| --- | ---: |', ...typeRows, '', `<details><summary>Comandos disponíveis (${formatPtBrInteger(commands.length)})</summary>`, '', commandInline, '', '</details>', ''].join('\n');
6480
6165
  };
6481
6166
 
6482
6167
  const buildReadmeSummarySnapshot = async () => {
6483
- const [
6484
- lidMapTotalsRows,
6485
- chatsTotalsRows,
6486
- groupsMetadataTotalsRows,
6487
- packTotalsRows,
6488
- stickerTotalsRows,
6489
- messageTotalsRows,
6490
- messageTypeRows,
6491
- ] = await Promise.all([
6168
+ const [lidMapTotalsRows, chatsTotalsRows, groupsMetadataTotalsRows, packTotalsRows, stickerTotalsRows, messageTotalsRows, messageTypeRows] = await Promise.all([
6492
6169
  executeQuery(`SELECT COUNT(*) AS total_users FROM ${TABLES.LID_MAP}`),
6493
6170
  executeQuery(
6494
6171
  `SELECT
@@ -6648,12 +6325,7 @@ const resolveBotUserCandidates = (activeSocket) => {
6648
6325
  const botPhoneFromCatalog = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
6649
6326
  if (botPhoneFromCatalog) candidates.add(botPhoneFromCatalog);
6650
6327
 
6651
- const envCandidates = [
6652
- process.env.WHATSAPP_BOT_NUMBER,
6653
- process.env.BOT_NUMBER,
6654
- process.env.PHONE_NUMBER,
6655
- process.env.BOT_PHONE_NUMBER,
6656
- ];
6328
+ const envCandidates = [process.env.WHATSAPP_BOT_NUMBER, process.env.BOT_NUMBER, process.env.PHONE_NUMBER, process.env.BOT_PHONE_NUMBER];
6657
6329
 
6658
6330
  for (const candidate of envCandidates) {
6659
6331
  const digits = String(candidate || '').replace(/\D+/g, '');
@@ -6944,15 +6616,7 @@ const buildMarketplaceGlobalStatsSnapshot = async () => {
6944
6616
  const dayKeys = buildLastSevenUtcDateKeys();
6945
6617
  const dayFilterSql = `UTC_DATE() - INTERVAL 6 DAY`;
6946
6618
 
6947
- const [
6948
- packTotalsRows,
6949
- stickerTotalsRows,
6950
- stickersWithoutPackRows,
6951
- engagementTotalsRows,
6952
- dailyPacksRows,
6953
- dailyStickersRows,
6954
- dailyInteractionRows,
6955
- ] = await Promise.all([
6619
+ const [packTotalsRows, stickerTotalsRows, stickersWithoutPackRows, engagementTotalsRows, dailyPacksRows, dailyStickersRows, dailyInteractionRows] = await Promise.all([
6956
6620
  executeQuery(
6957
6621
  `SELECT
6958
6622
  COUNT(*) AS total_packs,
@@ -7027,7 +6691,9 @@ const buildMarketplaceGlobalStatsSnapshot = async () => {
7027
6691
  const dailyLikesByDay = new Map();
7028
6692
  (Array.isArray(dailyInteractionRows) ? dailyInteractionRows : []).forEach((row) => {
7029
6693
  const dayKey = toUtcDayKey(row?.day_key);
7030
- const interaction = String(row?.interaction || '').trim().toLowerCase();
6694
+ const interaction = String(row?.interaction || '')
6695
+ .trim()
6696
+ .toLowerCase();
7031
6697
  const total = Number(row?.total || 0);
7032
6698
  if (!dayKey) return;
7033
6699
  if (interaction === 'open') dailyOpensByDay.set(dayKey, total);
@@ -7161,13 +6827,16 @@ const handlePublicDataAssetRequest = async (req, res, pathname) => {
7161
6827
  return true;
7162
6828
  }
7163
6829
 
7164
- const decodedSegments = suffix.split('/').filter(Boolean).map((segment) => {
7165
- try {
7166
- return decodeURIComponent(segment);
7167
- } catch {
7168
- return segment;
7169
- }
7170
- });
6830
+ const decodedSegments = suffix
6831
+ .split('/')
6832
+ .filter(Boolean)
6833
+ .map((segment) => {
6834
+ try {
6835
+ return decodeURIComponent(segment);
6836
+ } catch {
6837
+ return segment;
6838
+ }
6839
+ });
7171
6840
 
7172
6841
  const relativePath = normalizeRelativePath(decodedSegments.join('/'));
7173
6842
  if (!relativePath || relativePath.includes('..') || !isAllowedDataImageFile(relativePath)) {
@@ -7215,11 +6884,7 @@ const fetchPublicPackPayload = async (normalizedPackKey) => {
7215
6884
 
7216
6885
  const items = await listStickerPackItems(pack.id);
7217
6886
  const stickerIds = items.map((item) => item.sticker_id);
7218
- const [classifications, packClassification, engagement] = await Promise.all([
7219
- listStickerClassificationsByAssetIds(stickerIds),
7220
- getPackClassificationSummaryByAssetIds(stickerIds),
7221
- getStickerPackEngagementByPackId(pack.id),
7222
- ]);
6887
+ const [classifications, packClassification, engagement] = await Promise.all([listStickerClassificationsByAssetIds(stickerIds), getPackClassificationSummaryByAssetIds(stickerIds), getStickerPackEngagementByPackId(pack.id)]);
7223
6888
 
7224
6889
  if (STICKER_CATALOG_ONLY_CLASSIFIED && !isPackClassified(packClassification)) {
7225
6890
  return null;
@@ -7275,12 +6940,8 @@ const handleDetailsRequest = async (req, res, packKey, url) => {
7275
6940
  }
7276
6941
 
7277
6942
  const { pack, items, byAssetClassification, packClassification, engagement, signals } = packPayload;
7278
- const visibleItems = STICKER_CATALOG_ONLY_CLASSIFIED
7279
- ? items.filter((item) => isStickerClassified(byAssetClassification.get(item.sticker_id)))
7280
- : items;
7281
- const visibleItemsByCategories = categories.length
7282
- ? visibleItems.filter((item) => hasAnyCategory(resolveClassificationTags(byAssetClassification.get(item.sticker_id)), categories))
7283
- : visibleItems;
6943
+ const visibleItems = STICKER_CATALOG_ONLY_CLASSIFIED ? items.filter((item) => isStickerClassified(byAssetClassification.get(item.sticker_id))) : items;
6944
+ const visibleItemsByCategories = categories.length ? visibleItems.filter((item) => hasAnyCategory(resolveClassificationTags(byAssetClassification.get(item.sticker_id)), categories)) : visibleItems;
7284
6945
 
7285
6946
  sendJson(req, res, 200, {
7286
6947
  data: mapPackDetails(pack, visibleItemsByCategories, {
@@ -7320,9 +6981,7 @@ const handleAssetRequest = async (req, res, packKey, stickerToken) => {
7320
6981
  try {
7321
6982
  const buffer = await readStickerAssetBuffer(item.asset);
7322
6983
  const classification = await findStickerClassificationByAssetId(normalizedStickerId).catch(() => null);
7323
- const packClassification = stickerIds.length
7324
- ? await getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null)
7325
- : null;
6984
+ const packClassification = stickerIds.length ? await getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null) : null;
7326
6985
  if (STICKER_CATALOG_ONLY_CLASSIFIED && !isStickerClassified(classification)) {
7327
6986
  sendJson(req, res, 404, { error: 'Sticker nao encontrado.' });
7328
6987
  return;
@@ -7653,23 +7312,7 @@ const handleAdminOverviewRequest = async (req, res) => {
7653
7312
  return;
7654
7313
  }
7655
7314
 
7656
- const [
7657
- marketplaceStats,
7658
- activeSessions,
7659
- knownUsers,
7660
- bans,
7661
- packsCountRows,
7662
- stickersCountRows,
7663
- recentPacks,
7664
- ] = await Promise.all([
7665
- getMarketplaceGlobalStatsCached().catch(() => null),
7666
- listAdminActiveGoogleWebSessions({ limit: 50 }),
7667
- listAdminKnownGoogleUsers({ limit: 50 }),
7668
- listAdminBans({ activeOnly: true, limit: 50 }),
7669
- executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_PACK} WHERE deleted_at IS NULL`),
7670
- executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_ASSET}`),
7671
- listAdminPacks({ searchParams: new URLSearchParams([['limit', '20']]) }),
7672
- ]);
7315
+ const [marketplaceStats, activeSessions, knownUsers, bans, packsCountRows, stickersCountRows, recentPacks] = await Promise.all([getMarketplaceGlobalStatsCached().catch(() => null), listAdminActiveGoogleWebSessions({ limit: 50 }), listAdminKnownGoogleUsers({ limit: 50 }), listAdminBans({ activeOnly: true, limit: 50 }), executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_PACK} WHERE deleted_at IS NULL`), executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_ASSET}`), listAdminPacks({ searchParams: new URLSearchParams([['limit', '20']]) })]);
7673
7316
 
7674
7317
  sendJson(req, res, 200, {
7675
7318
  data: {
@@ -7698,10 +7341,7 @@ const handleAdminUsersRequest = async (req, res, url) => {
7698
7341
  return;
7699
7342
  }
7700
7343
  const limit = Math.max(1, Math.min(500, Number(url?.searchParams?.get('limit') || 200)));
7701
- const [activeSessions, users] = await Promise.all([
7702
- listAdminActiveGoogleWebSessions({ limit }),
7703
- listAdminKnownGoogleUsers({ limit }),
7704
- ]);
7344
+ const [activeSessions, users] = await Promise.all([listAdminActiveGoogleWebSessions({ limit }), listAdminKnownGoogleUsers({ limit })]);
7705
7345
  sendJson(req, res, 200, { data: { active_sessions: activeSessions, users } });
7706
7346
  };
7707
7347
 
@@ -8022,8 +7662,7 @@ const catalogApiRouter = createCatalogApiRouter({
8022
7662
  },
8023
7663
  });
8024
7664
 
8025
- const handleCatalogApiRequest = async (req, res, pathname, url) =>
8026
- catalogApiRouter({ req, res, pathname, url });
7665
+ const handleCatalogApiRequest = async (req, res, pathname, url) => catalogApiRouter({ req, res, pathname, url });
8027
7666
 
8028
7667
  const handleCatalogPageRequest = async (req, res, pathname) => {
8029
7668
  const normalizedPath = pathname.length > 1 ? pathname.replace(/\/+$/, '') : pathname;
@@ -8082,7 +7721,9 @@ const handleCatalogPageRequest = async (req, res, pathname) => {
8082
7721
  }
8083
7722
 
8084
7723
  const initialPackKey = extractPackKeyFromWebPath(pathname);
8085
- const initialPackKeyNormalized = String(initialPackKey || '').trim().toLowerCase();
7724
+ const initialPackKeyNormalized = String(initialPackKey || '')
7725
+ .trim()
7726
+ .toLowerCase();
8086
7727
  const shouldRenderPackSeoPage = Boolean(initialPackKey && !PACK_PAGE_ROUTE_EXCLUSIONS.has(initialPackKeyNormalized));
8087
7728
 
8088
7729
  if (shouldRenderPackSeoPage) {