@kaikybrofc/omnizap-system 2.2.9 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -18
- package/app/config/adminIdentity.js +1 -3
- package/app/connection/socketController.js +10 -20
- package/app/controllers/messageController.js +7 -28
- package/app/modules/aiModule/catCommand.js +29 -192
- package/app/modules/broadcastModule/noticeCommand.js +28 -97
- package/app/modules/gameModule/diceCommand.js +6 -32
- package/app/modules/playModule/playCommand.js +57 -258
- package/app/modules/quoteModule/quoteCommand.js +2 -4
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
- package/app/modules/statsModule/noMessageCommand.js +16 -84
- package/app/modules/statsModule/rankingCommand.js +5 -25
- package/app/modules/statsModule/rankingCommon.js +1 -9
- package/app/modules/stickerModule/convertToWebp.js +4 -27
- package/app/modules/stickerModule/stickerCommand.js +13 -24
- package/app/modules/stickerModule/stickerTextCommand.js +13 -25
- package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
- package/app/modules/stickerPackModule/domainEvents.js +2 -11
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
- package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
- package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +13 -34
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
- package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
- package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
- package/app/modules/stickerPackModule/stickerPackService.js +50 -115
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
- package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
- package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
- package/app/modules/systemMetricsModule/pingCommand.js +9 -39
- package/app/modules/tiktokModule/tiktokCommand.js +17 -109
- package/app/modules/userModule/userCommand.js +2 -88
- package/app/observability/metrics.js +5 -16
- package/app/services/captchaService.js +1 -6
- package/app/services/dbWriteQueue.js +3 -18
- package/app/services/featureFlagService.js +2 -8
- package/app/services/newsBroadcastService.js +0 -1
- package/app/services/queueUtils.js +2 -4
- package/app/services/whatsappLoginLinkService.js +7 -9
- package/app/store/premiumUserStore.js +1 -2
- package/app/utils/antiLink/antiLinkModule.js +3 -233
- package/app/utils/logger/loggerModule.js +9 -34
- package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
- package/database/init.js +1 -8
- package/docker-compose.yml +27 -27
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +220 -0
- package/docs/seo/satellite-page-template.md +91 -0
- package/docs/seo/satellite-pages-phase1.json +349 -0
- package/eslint.config.js +2 -15
- package/index.js +8 -36
- package/ml/clip_classifier/README.md +4 -6
- package/observability/alert-rules.yml +12 -12
- package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
- package/package.json +8 -3
- package/public/api-docs/index.html +224 -141
- package/public/bot-whatsapp-para-grupo/index.html +306 -0
- package/public/bot-whatsapp-sem-programar/index.html +306 -0
- package/public/comandos/index.html +428 -0
- package/public/como-automatizar-avisos-no-whatsapp/index.html +306 -0
- package/public/como-criar-comandos-whatsapp/index.html +306 -0
- package/public/como-evitar-spam-no-whatsapp/index.html +306 -0
- package/public/como-moderar-grupo-whatsapp/index.html +306 -0
- package/public/como-organizar-comunidade-whatsapp/index.html +306 -0
- package/public/css/github-project-panel.css +20 -15
- package/public/css/stickers-admin.css +55 -39
- package/public/css/styles.css +37 -29
- package/public/index.html +1060 -1417
- package/public/js/apps/apiDocsApp.js +36 -153
- package/public/js/apps/createPackApp.js +69 -332
- package/public/js/apps/homeApp.js +201 -434
- package/public/js/apps/loginApp.js +3 -12
- package/public/js/apps/stickersAdminApp.js +190 -181
- package/public/js/apps/stickersApp.js +507 -1366
- package/public/js/catalog.js +11 -74
- package/public/js/github-panel/components/ErrorState.js +1 -8
- package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
- package/public/js/github-panel/components/SkeletonPanel.js +1 -11
- package/public/js/github-panel/components/StatCard.js +1 -7
- package/public/js/github-panel/vendor/react.js +1 -9
- package/public/js/runtime/react-runtime.js +1 -9
- package/public/licenca/index.html +104 -86
- package/public/login/index.html +315 -321
- package/public/melhor-bot-whatsapp-para-grupos/index.html +306 -0
- package/public/sitemap.xml +45 -0
- package/public/stickers/admin/index.html +14 -19
- package/public/stickers/create/index.html +39 -43
- package/public/stickers/index.html +97 -41
- package/public/termos-de-uso/index.html +142 -115
- package/public/user/index.html +347 -346
- package/scripts/cache-bust.mjs +5 -24
- package/scripts/generate-seo-satellite-pages.mjs +431 -0
- package/scripts/run-prettier-all.mjs +25 -0
- package/scripts/sticker-catalog-loadtest.mjs +13 -11
- package/scripts/sticker-worker-task.mjs +1 -4
- package/scripts/sync-readme-snapshot.mjs +3 -2
- package/server/controllers/stickerCatalogController.js +407 -704
- package/server/http/httpServer.js +2 -10
- package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
- package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
- package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
- package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
- package/server/routes/stickerCatalog/catalogRouter.js +11 -13
- package/kaikybrofc-omnizap-system-2.2.9.tgz +0 -0
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 || '')
|
|
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 || '')
|
|
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) =>
|
|
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
|
-
|
|
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 || '')
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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 =
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 || '')
|
|
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 (
|
|
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'] || '')
|
|
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 || '')
|
|
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 || '')
|
|
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) =>
|
|
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 || '')
|
|
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) =>
|
|
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 || '')
|
|
1040
|
-
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 || '')
|
|
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) =>
|
|
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
|
|
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 || '')
|
|
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 =
|
|
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):[^\]]+\]/
|
|
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
|
-
|
|
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 =
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
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 || '')
|
|
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
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
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, '"')
|
|
3123
2926
|
.replace(/'/g, ''');
|
|
3124
2927
|
|
|
3125
|
-
const normalizeWhitespace = (value) =>
|
|
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();
|
|
@@ -3232,13 +3033,11 @@ const renderCatalogHtml = async ({ initialPackKey }) => {
|
|
|
3232
3033
|
|
|
3233
3034
|
const renderPackSeoHtml = ({ packSummary }) => {
|
|
3234
3035
|
const packName = truncateText(packSummary?.name || packSummary?.pack_key || 'Pack', 95);
|
|
3235
|
-
const packDescription = truncateText(
|
|
3236
|
-
packSummary?.description
|
|
3237
|
-
|| `Pack de stickers "${packName}" disponível no catálogo OmniZap.`,
|
|
3238
|
-
180,
|
|
3239
|
-
);
|
|
3036
|
+
const packDescription = truncateText(packSummary?.description || `Pack de stickers "${packName}" disponível no catálogo OmniZap para uso em bots e automações WhatsApp via API.`, 180);
|
|
3240
3037
|
const canonicalUrl = toSiteAbsoluteUrl(buildPackWebUrl(packSummary?.pack_key || ''));
|
|
3241
3038
|
const catalogUrl = toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`);
|
|
3039
|
+
const homeUrl = toSiteAbsoluteUrl('/');
|
|
3040
|
+
const apiDocsUrl = toSiteAbsoluteUrl('/api-docs/');
|
|
3242
3041
|
const fallbackCoverUrl = packSummary?.is_nsfw ? NSFW_STICKER_PLACEHOLDER_URL : 'https://iili.io/fSNGag2.png';
|
|
3243
3042
|
const coverUrl = toSiteAbsoluteUrl(packSummary?.cover_url || fallbackCoverUrl);
|
|
3244
3043
|
const publisher = truncateText(packSummary?.publisher || 'Criador OmniZap', 80);
|
|
@@ -3262,13 +3061,47 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3262
3061
|
null,
|
|
3263
3062
|
0,
|
|
3264
3063
|
).replace(/</g, '\\u003c');
|
|
3064
|
+
const faqSchemaJson = JSON.stringify(
|
|
3065
|
+
{
|
|
3066
|
+
'@context': 'https://schema.org',
|
|
3067
|
+
'@type': 'FAQPage',
|
|
3068
|
+
mainEntity: [
|
|
3069
|
+
{
|
|
3070
|
+
'@type': 'Question',
|
|
3071
|
+
name: `Como usar o pack ${packName} no meu bot?`,
|
|
3072
|
+
acceptedAnswer: {
|
|
3073
|
+
'@type': 'Answer',
|
|
3074
|
+
text: `Use o pack ${packName} como recurso de engajamento e consulte exemplos de integração em ${apiDocsUrl}.`,
|
|
3075
|
+
},
|
|
3076
|
+
},
|
|
3077
|
+
{
|
|
3078
|
+
'@type': 'Question',
|
|
3079
|
+
name: 'Onde encontro mais packs de stickers?',
|
|
3080
|
+
acceptedAnswer: {
|
|
3081
|
+
'@type': 'Answer',
|
|
3082
|
+
text: `Veja o catálogo completo em ${catalogUrl}.`,
|
|
3083
|
+
},
|
|
3084
|
+
},
|
|
3085
|
+
{
|
|
3086
|
+
'@type': 'Question',
|
|
3087
|
+
name: 'Onde vejo a plataforma principal do OmniZap?',
|
|
3088
|
+
acceptedAnswer: {
|
|
3089
|
+
'@type': 'Answer',
|
|
3090
|
+
text: `A página principal do OmniZap está em ${homeUrl}.`,
|
|
3091
|
+
},
|
|
3092
|
+
},
|
|
3093
|
+
],
|
|
3094
|
+
},
|
|
3095
|
+
null,
|
|
3096
|
+
0,
|
|
3097
|
+
).replace(/</g, '\\u003c');
|
|
3265
3098
|
|
|
3266
3099
|
return `<!doctype html>
|
|
3267
3100
|
<html lang="pt-BR">
|
|
3268
3101
|
<head>
|
|
3269
3102
|
<meta charset="utf-8" />
|
|
3270
3103
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3271
|
-
<title>${escapeHtmlAttribute(`${packName} |
|
|
3104
|
+
<title>${escapeHtmlAttribute(`${packName} | Stickers para Bot WhatsApp OmniZap`)}</title>
|
|
3272
3105
|
<meta name="description" content="${escapeHtmlAttribute(packDescription)}" />
|
|
3273
3106
|
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1" />
|
|
3274
3107
|
<link rel="canonical" href="${escapeHtmlAttribute(canonicalUrl)}" />
|
|
@@ -3286,9 +3119,11 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3286
3119
|
},
|
|
3287
3120
|
colors: {
|
|
3288
3121
|
slateApp: '#0f172a',
|
|
3289
|
-
slateCard: '#
|
|
3290
|
-
borderApp: '
|
|
3291
|
-
accent: '#
|
|
3122
|
+
slateCard: '#1e293b',
|
|
3123
|
+
borderApp: 'rgba(255,255,255,0.05)',
|
|
3124
|
+
accent: '#2563eb',
|
|
3125
|
+
accentTech: '#7c3aed',
|
|
3126
|
+
cta: '#22c55e'
|
|
3292
3127
|
},
|
|
3293
3128
|
boxShadow: {
|
|
3294
3129
|
soft: '0 8px 24px rgba(2, 6, 23, 0.22)'
|
|
@@ -3313,15 +3148,16 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3313
3148
|
<meta name="twitter:image" content="${escapeHtmlAttribute(coverUrl)}" />
|
|
3314
3149
|
|
|
3315
3150
|
<script type="application/ld+json">${schemaJson}</script>
|
|
3151
|
+
<script type="application/ld+json">${faqSchemaJson}</script>
|
|
3316
3152
|
<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: #
|
|
3153
|
+
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
3154
|
.seo-shell { max-width: 880px; margin: 0 auto; padding: 18px 14px 12px; }
|
|
3319
|
-
.seo-card { border: 1px solid
|
|
3155
|
+
.seo-card { border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; background: #1e293b; padding: 16px; }
|
|
3320
3156
|
.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: #
|
|
3157
|
+
.seo-card p { margin: 0 0 10px; line-height: 1.55; color: #94a3b8; }
|
|
3322
3158
|
.seo-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
3323
|
-
.seo-row a { color: #
|
|
3324
|
-
.seo-row a:hover { background: #
|
|
3159
|
+
.seo-row a { color: #2563eb; text-decoration: none; border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 8px 10px; }
|
|
3160
|
+
.seo-row a:hover { background: #111827; }
|
|
3325
3161
|
</style>
|
|
3326
3162
|
</head>
|
|
3327
3163
|
<body class="bg-slateApp text-slate-100 font-sans min-h-screen">
|
|
@@ -3331,9 +3167,15 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3331
3167
|
<h1>${escapeHtmlAttribute(packName)}</h1>
|
|
3332
3168
|
<p>${escapeHtmlAttribute(packDescription)}</p>
|
|
3333
3169
|
<p>Criador: <strong>${escapeHtmlAttribute(publisher)}</strong> • Stickers: <strong>${stickerCount}</strong></p>
|
|
3170
|
+
<p>Use este pack como recurso integrado no seu bot. Consulte endpoints e exemplos na área de desenvolvedor da API OmniZap.</p>
|
|
3171
|
+
<h2 style="margin:12px 0 6px;font-size:18px;">FAQ rápido</h2>
|
|
3172
|
+
<p style="margin-bottom:6px;"><strong>Como usar no bot?</strong> Consulte a documentação técnica e exemplos na área de desenvolvedor.</p>
|
|
3173
|
+
<p style="margin-bottom:6px;"><strong>Tem mais packs?</strong> Sim, explore o catálogo completo para encontrar packs relacionados.</p>
|
|
3334
3174
|
<div class="seo-row">
|
|
3335
3175
|
<a href="${escapeHtmlAttribute(canonicalUrl)}">Abrir este pack</a>
|
|
3336
3176
|
<a href="${escapeHtmlAttribute(catalogUrl)}">Voltar ao catálogo</a>
|
|
3177
|
+
<a href="${escapeHtmlAttribute(apiDocsUrl)}">Área de Desenvolvedor</a>
|
|
3178
|
+
<a href="${escapeHtmlAttribute(homeUrl)}">Plataforma OmniZap</a>
|
|
3337
3179
|
</div>
|
|
3338
3180
|
</section>
|
|
3339
3181
|
</main>
|
|
@@ -3362,10 +3204,10 @@ const renderPackNotFoundHtml = (packKey = '') => `<!doctype html>
|
|
|
3362
3204
|
<meta name="robots" content="noindex, nofollow" />
|
|
3363
3205
|
<link rel="canonical" href="${escapeHtmlAttribute(toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`))}" />
|
|
3364
3206
|
<style>
|
|
3365
|
-
body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: #
|
|
3207
|
+
body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: #0f172a; color: #f8fafc; }
|
|
3366
3208
|
main { max-width: 760px; margin: 0 auto; padding: 20px 14px; }
|
|
3367
|
-
article { border: 1px solid
|
|
3368
|
-
a { color: #
|
|
3209
|
+
article { border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; background: #1e293b; padding: 16px; }
|
|
3210
|
+
a { color: #2563eb; text-decoration: none; }
|
|
3369
3211
|
</style>
|
|
3370
3212
|
</head>
|
|
3371
3213
|
<body>
|
|
@@ -3439,8 +3281,17 @@ const buildSitemapXml = async () => {
|
|
|
3439
3281
|
{ loc: toSiteAbsoluteUrl('/'), changefreq: 'daily', priority: '1.0' },
|
|
3440
3282
|
{ loc: toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`), changefreq: 'hourly', priority: '0.9' },
|
|
3441
3283
|
{ loc: toSiteAbsoluteUrl('/api-docs/'), changefreq: 'weekly', priority: '0.8' },
|
|
3284
|
+
{ loc: toSiteAbsoluteUrl('/comandos/'), changefreq: 'weekly', priority: '0.78' },
|
|
3442
3285
|
{ loc: toSiteAbsoluteUrl('/termos-de-uso/'), changefreq: 'monthly', priority: '0.5' },
|
|
3443
3286
|
{ loc: toSiteAbsoluteUrl('/licenca/'), changefreq: 'monthly', priority: '0.5' },
|
|
3287
|
+
{ loc: toSiteAbsoluteUrl('/bot-whatsapp-para-grupo/'), changefreq: 'weekly', priority: '0.75' },
|
|
3288
|
+
{ loc: toSiteAbsoluteUrl('/como-moderar-grupo-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3289
|
+
{ loc: toSiteAbsoluteUrl('/como-evitar-spam-no-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3290
|
+
{ loc: toSiteAbsoluteUrl('/como-organizar-comunidade-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3291
|
+
{ loc: toSiteAbsoluteUrl('/como-automatizar-avisos-no-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3292
|
+
{ loc: toSiteAbsoluteUrl('/como-criar-comandos-whatsapp/'), changefreq: 'weekly', priority: '0.71' },
|
|
3293
|
+
{ loc: toSiteAbsoluteUrl('/melhor-bot-whatsapp-para-grupos/'), changefreq: 'weekly', priority: '0.74' },
|
|
3294
|
+
{ loc: toSiteAbsoluteUrl('/bot-whatsapp-sem-programar/'), changefreq: 'weekly', priority: '0.73' },
|
|
3444
3295
|
];
|
|
3445
3296
|
|
|
3446
3297
|
const packRows = await executeQuery(
|
|
@@ -3496,9 +3347,7 @@ const sendStaticTextFile = async (req, res, filePath, contentType) => {
|
|
|
3496
3347
|
try {
|
|
3497
3348
|
const body = await fs.readFile(filePath, 'utf8');
|
|
3498
3349
|
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)}`;
|
|
3350
|
+
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
3351
|
res.statusCode = 200;
|
|
3503
3352
|
res.setHeader('Content-Type', contentType);
|
|
3504
3353
|
res.setHeader('Cache-Control', cacheControl);
|
|
@@ -3548,18 +3397,7 @@ const handleListRequest = async (req, res, url) => {
|
|
|
3548
3397
|
const normalizedIntent = normalizeCategoryToken(intent).replace(/-/g, '_');
|
|
3549
3398
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
3550
3399
|
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
|
-
]);
|
|
3400
|
+
const cacheKey = buildCacheKey(['list', q, visibility, sort, categories.join(','), normalizedIntent, includeSensitive ? 1 : 0, limit, offset, hasNsfwAccess ? 1 : 0]);
|
|
3563
3401
|
const payload = await getCachedSnapshot({
|
|
3564
3402
|
cacheMap: CATALOG_LIST_CACHE,
|
|
3565
3403
|
key: cacheKey,
|
|
@@ -3589,18 +3427,10 @@ const handleListRequest = async (req, res, url) => {
|
|
|
3589
3427
|
if (!packs.length) break;
|
|
3590
3428
|
|
|
3591
3429
|
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3592
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
const
|
|
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;
|
|
3430
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
|
|
3431
|
+
const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
|
|
3432
|
+
const entriesBySensitivity = includeSensitive ? entriesByCategory : entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
|
|
3433
|
+
const entriesByIntent = intent ? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent) : entriesBySensitivity;
|
|
3604
3434
|
const sortedEntries = [...entriesByIntent].sort((left, right) => {
|
|
3605
3435
|
const completenessDelta = compareEntriesByPackCompleteness(left, right);
|
|
3606
3436
|
if (completenessDelta !== 0) return completenessDelta;
|
|
@@ -3676,12 +3506,8 @@ const handleIntentCollectionsRequest = async (req, res, url) => {
|
|
|
3676
3506
|
});
|
|
3677
3507
|
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
3678
3508
|
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3679
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3680
|
-
|
|
3681
|
-
: entries;
|
|
3682
|
-
const entriesByCategory = categories.length
|
|
3683
|
-
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
3684
|
-
: entriesClassified;
|
|
3509
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
|
|
3510
|
+
const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
|
|
3685
3511
|
const intents = buildIntentCollections(entriesByCategory, { limit });
|
|
3686
3512
|
|
|
3687
3513
|
sendJson(req, res, 200, {
|
|
@@ -3701,12 +3527,7 @@ const handleIntentCollectionsRequest = async (req, res, url) => {
|
|
|
3701
3527
|
});
|
|
3702
3528
|
};
|
|
3703
3529
|
|
|
3704
|
-
const resolveMarketplaceVisibilityValues = (visibility) =>
|
|
3705
|
-
visibility === 'all'
|
|
3706
|
-
? ['public', 'unlisted']
|
|
3707
|
-
: visibility === 'unlisted'
|
|
3708
|
-
? ['unlisted']
|
|
3709
|
-
: ['public'];
|
|
3530
|
+
const resolveMarketplaceVisibilityValues = (visibility) => (visibility === 'all' ? ['public', 'unlisted'] : visibility === 'unlisted' ? ['unlisted'] : ['public']);
|
|
3710
3531
|
|
|
3711
3532
|
const getHomeMarketplaceStatsCacheBucket = (visibility) => {
|
|
3712
3533
|
const key = normalizeCatalogVisibility(visibility);
|
|
@@ -3823,6 +3644,71 @@ const handleMarketplaceStatsRequest = async (req, res, url) => {
|
|
|
3823
3644
|
}
|
|
3824
3645
|
};
|
|
3825
3646
|
|
|
3647
|
+
const handleHomeBootstrapRequest = async (req, res, url) => {
|
|
3648
|
+
const visibility = normalizeCatalogVisibility(url?.searchParams?.get('visibility'));
|
|
3649
|
+
const fetchTimeoutMs = {
|
|
3650
|
+
support: 450,
|
|
3651
|
+
session: 450,
|
|
3652
|
+
stats: 700,
|
|
3653
|
+
system_summary: 700,
|
|
3654
|
+
};
|
|
3655
|
+
const errors = [];
|
|
3656
|
+
|
|
3657
|
+
const [supportResult, sessionResult, statsResult, systemSummaryResult] = await Promise.allSettled([withTimeout(buildSupportInfo(), fetchTimeoutMs.support), STICKER_WEB_GOOGLE_CLIENT_ID ? withTimeout(resolveGoogleWebSessionFromRequest(req), fetchTimeoutMs.session) : Promise.resolve(null), withTimeout(getMarketplaceStatsCached(visibility), fetchTimeoutMs.stats), withTimeout(getSystemSummaryCached(), fetchTimeoutMs.system_summary)]);
|
|
3658
|
+
|
|
3659
|
+
const support = supportResult.status === 'fulfilled' ? supportResult.value || null : null;
|
|
3660
|
+
if (supportResult.status !== 'fulfilled') {
|
|
3661
|
+
errors.push({
|
|
3662
|
+
source: 'support',
|
|
3663
|
+
message: supportResult.reason?.message || 'support_unavailable',
|
|
3664
|
+
});
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
const session = sessionResult.status === 'fulfilled' ? sessionResult.value || null : null;
|
|
3668
|
+
if (sessionResult.status !== 'fulfilled') {
|
|
3669
|
+
errors.push({
|
|
3670
|
+
source: 'session',
|
|
3671
|
+
message: sessionResult.reason?.message || 'session_unavailable',
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
const statsPayload = statsResult.status === 'fulfilled' ? statsResult.value || null : null;
|
|
3676
|
+
if (statsResult.status !== 'fulfilled') {
|
|
3677
|
+
errors.push({
|
|
3678
|
+
source: 'stats',
|
|
3679
|
+
message: statsResult.reason?.message || 'stats_unavailable',
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
const systemSummaryPayload = systemSummaryResult.status === 'fulfilled' ? systemSummaryResult.value || null : null;
|
|
3684
|
+
if (systemSummaryResult.status !== 'fulfilled') {
|
|
3685
|
+
errors.push({
|
|
3686
|
+
source: 'system_summary',
|
|
3687
|
+
message: systemSummaryResult.reason?.message || 'system_summary_unavailable',
|
|
3688
|
+
});
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
sendJson(req, res, 200, {
|
|
3692
|
+
data: {
|
|
3693
|
+
support,
|
|
3694
|
+
session: mapGoogleSessionResponseData(session),
|
|
3695
|
+
stats: statsPayload?.data || null,
|
|
3696
|
+
stats_filters: statsPayload?.filters || null,
|
|
3697
|
+
system_summary: systemSummaryPayload?.data || null,
|
|
3698
|
+
},
|
|
3699
|
+
meta: {
|
|
3700
|
+
visibility,
|
|
3701
|
+
cache_seconds: {
|
|
3702
|
+
stats: HOME_MARKETPLACE_STATS_CACHE_SECONDS,
|
|
3703
|
+
system_summary: SYSTEM_SUMMARY_CACHE_SECONDS,
|
|
3704
|
+
},
|
|
3705
|
+
timeouts_ms: fetchTimeoutMs,
|
|
3706
|
+
partial: errors.length > 0,
|
|
3707
|
+
errors,
|
|
3708
|
+
},
|
|
3709
|
+
});
|
|
3710
|
+
};
|
|
3711
|
+
|
|
3826
3712
|
const handleCreatePackConfigRequest = async (req, res) => {
|
|
3827
3713
|
triggerStaleDraftCleanup();
|
|
3828
3714
|
sendJson(req, res, 200, {
|
|
@@ -3843,9 +3729,7 @@ const handleCreatePackConfigRequest = async (req, res) => {
|
|
|
3843
3729
|
pack_name_hint: 'Nome livre (espaços e emojis são permitidos).',
|
|
3844
3730
|
visibility_values: ['public', 'unlisted', 'private'],
|
|
3845
3731
|
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.',
|
|
3732
|
+
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
3733
|
suggested_tags: ['anime', 'meme', 'game', 'texto', 'nsfw', 'dark', 'cartoon', 'foto-real', 'cyberpunk'],
|
|
3850
3734
|
},
|
|
3851
3735
|
auth: {
|
|
@@ -3936,15 +3820,12 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
|
|
|
3936
3820
|
return;
|
|
3937
3821
|
}
|
|
3938
3822
|
|
|
3939
|
-
const reason = String(linkedOwner.reason || '')
|
|
3823
|
+
const reason = String(linkedOwner.reason || '')
|
|
3824
|
+
.trim()
|
|
3825
|
+
.toLowerCase();
|
|
3940
3826
|
const isUnauthorizedAttempt = ['invalid_signature', 'missing_signature'].includes(reason);
|
|
3941
3827
|
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.';
|
|
3828
|
+
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
3829
|
|
|
3949
3830
|
logger.warn('Tentativa de login web bloqueada por validacao do link WhatsApp.', {
|
|
3950
3831
|
action: 'sticker_pack_google_web_login_link_blocked',
|
|
@@ -4255,13 +4136,8 @@ const handleMyProfileRequest = async (req, res, url = null) => {
|
|
|
4255
4136
|
return;
|
|
4256
4137
|
}
|
|
4257
4138
|
|
|
4258
|
-
const ownerPacks = await Promise.all(
|
|
4259
|
-
|
|
4260
|
-
);
|
|
4261
|
-
const includeAutoPacks = parseEnvBool(
|
|
4262
|
-
url?.searchParams?.get('include_auto'),
|
|
4263
|
-
parseEnvBool(process.env.STICKER_WEB_MY_PROFILE_INCLUDE_AUTO_PACKS, false),
|
|
4264
|
-
);
|
|
4139
|
+
const ownerPacks = await Promise.all(ownerCandidates.map((ownerJid) => listStickerPacksByOwner(ownerJid, { limit: 200, offset: 0 })));
|
|
4140
|
+
const includeAutoPacks = parseEnvBool(url?.searchParams?.get('include_auto'), parseEnvBool(process.env.STICKER_WEB_MY_PROFILE_INCLUDE_AUTO_PACKS, false));
|
|
4265
4141
|
|
|
4266
4142
|
const dedupPacks = new Map();
|
|
4267
4143
|
for (const packList of ownerPacks) {
|
|
@@ -4429,11 +4305,7 @@ const cleanupOrphanStickerAssets = async (assetIds, { reason = 'manage_mutation'
|
|
|
4429
4305
|
|
|
4430
4306
|
const deleteManagedPackWithCleanup = async ({ ownerJid, identifier, fallbackPack = null }) => {
|
|
4431
4307
|
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);
|
|
4308
|
+
const pack = (await findStickerPackByOwnerAndIdentifier(ownerJid, fallbackPack?.id || identifier, { connection })) || (fallbackPack?.pack_key && fallbackPack?.pack_key !== identifier ? await findStickerPackByOwnerAndIdentifier(ownerJid, identifier, { connection }) : null);
|
|
4437
4309
|
|
|
4438
4310
|
if (!pack) {
|
|
4439
4311
|
return {
|
|
@@ -4567,9 +4439,7 @@ const buildManagedPackAnalytics = async (pack) => {
|
|
|
4567
4439
|
downloads: Number(engagement?.open_count || 0),
|
|
4568
4440
|
likes: Number(engagement?.like_count || 0),
|
|
4569
4441
|
dislikes: Number(engagement?.dislike_count || 0),
|
|
4570
|
-
score:
|
|
4571
|
-
Number(engagement?.score || 0) ||
|
|
4572
|
-
Number(engagement?.like_count || 0) - Number(engagement?.dislike_count || 0),
|
|
4442
|
+
score: Number(engagement?.score || 0) || Number(engagement?.like_count || 0) - Number(engagement?.dislike_count || 0),
|
|
4573
4443
|
engagement: {
|
|
4574
4444
|
open_count: Number(engagement?.open_count || 0),
|
|
4575
4445
|
like_count: Number(engagement?.like_count || 0),
|
|
@@ -4591,12 +4461,7 @@ const buildManagedPackResponseData = async (pack) => {
|
|
|
4591
4461
|
const items = Array.isArray(pack?.items) ? pack.items : [];
|
|
4592
4462
|
const stickerIds = items.map((item) => item.sticker_id).filter(Boolean);
|
|
4593
4463
|
|
|
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
|
-
]);
|
|
4464
|
+
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
4465
|
|
|
4601
4466
|
const byAssetClassification = new Map((Array.isArray(classifications) ? classifications : []).map((entry) => [entry.asset_id, entry]));
|
|
4602
4467
|
|
|
@@ -4735,8 +4600,12 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
4735
4600
|
}
|
|
4736
4601
|
|
|
4737
4602
|
if (hasOwn(payload, 'visibility')) {
|
|
4738
|
-
const nextVisibility = String(payload?.visibility || '')
|
|
4739
|
-
|
|
4603
|
+
const nextVisibility = String(payload?.visibility || '')
|
|
4604
|
+
.trim()
|
|
4605
|
+
.toLowerCase();
|
|
4606
|
+
const currentVisibility = String(updatedPack?.visibility || '')
|
|
4607
|
+
.trim()
|
|
4608
|
+
.toLowerCase();
|
|
4740
4609
|
if (!nextVisibility) {
|
|
4741
4610
|
updatedPack = await stickerPackService.setPackVisibility({
|
|
4742
4611
|
ownerJid: context.ownerJid,
|
|
@@ -4859,9 +4728,7 @@ const handleManagedPackCoverRequest = async (req, res, packKey) => {
|
|
|
4859
4728
|
return;
|
|
4860
4729
|
}
|
|
4861
4730
|
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);
|
|
4731
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
|
|
4865
4732
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
4866
4733
|
pack_key: context.packKey,
|
|
4867
4734
|
sticker_id: sanitizeText(payload?.sticker_id, 36, { allowEmpty: true }) || null,
|
|
@@ -4924,9 +4791,7 @@ const handleManagedPackReorderRequest = async (req, res, packKey) => {
|
|
|
4924
4791
|
return;
|
|
4925
4792
|
}
|
|
4926
4793
|
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);
|
|
4794
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
|
|
4930
4795
|
await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
|
|
4931
4796
|
pack_key: context.packKey,
|
|
4932
4797
|
reason: 'invalid_or_stale_order',
|
|
@@ -4977,9 +4842,7 @@ const handleManagedPackStickerDeleteRequest = async (req, res, packKey, stickerI
|
|
|
4977
4842
|
return;
|
|
4978
4843
|
}
|
|
4979
4844
|
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);
|
|
4845
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
|
|
4983
4846
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
4984
4847
|
pack_key: context.packKey,
|
|
4985
4848
|
sticker_id: sanitizeText(stickerId, 36, { allowEmpty: true }) || null,
|
|
@@ -5218,9 +5081,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
5218
5081
|
}
|
|
5219
5082
|
|
|
5220
5083
|
if (swapResult?.status === 'old_sticker_missing') {
|
|
5221
|
-
const fresh = await stickerPackService
|
|
5222
|
-
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
5223
|
-
.catch(() => originalPack);
|
|
5084
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => originalPack);
|
|
5224
5085
|
await cleanupOrphanStickerAssets(uploadedAssetId ? [uploadedAssetId] : [], { reason: 'replace_sticker_old_missing' });
|
|
5225
5086
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
5226
5087
|
pack_key: context.packKey,
|
|
@@ -5230,9 +5091,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
5230
5091
|
}
|
|
5231
5092
|
|
|
5232
5093
|
if (swapResult?.status === 'duplicate_target') {
|
|
5233
|
-
const fresh = await stickerPackService
|
|
5234
|
-
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
5235
|
-
.catch(() => originalPack);
|
|
5094
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => originalPack);
|
|
5236
5095
|
await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
|
|
5237
5096
|
pack_key: context.packKey,
|
|
5238
5097
|
reason: 'duplicate_target_sticker',
|
|
@@ -5297,8 +5156,7 @@ const handleManagedPackAnalyticsRequest = async (req, res, packKey) => {
|
|
|
5297
5156
|
}
|
|
5298
5157
|
};
|
|
5299
5158
|
|
|
5300
|
-
const normalizeCreatePackName = (value) =>
|
|
5301
|
-
sanitizeText(value, PACK_CREATE_MAX_NAME_LENGTH, { allowEmpty: true }) || '';
|
|
5159
|
+
const normalizeCreatePackName = (value) => sanitizeText(value, PACK_CREATE_MAX_NAME_LENGTH, { allowEmpty: true }) || '';
|
|
5302
5160
|
|
|
5303
5161
|
const mapStickerPackCreateError = (error) => {
|
|
5304
5162
|
if (!(error instanceof StickerPackError)) {
|
|
@@ -5356,7 +5214,9 @@ const handleCreatePackRequest = async (req, res) => {
|
|
|
5356
5214
|
const description = sanitizeText(payload?.description || '', PACK_CREATE_MAX_DESCRIPTION_LENGTH, { allowEmpty: true });
|
|
5357
5215
|
const manualTags = mergeUniqueTags(Array.isArray(payload?.tags) ? payload.tags : []).slice(0, 8);
|
|
5358
5216
|
const persistedDescription = buildPackDescriptionWithTags(description, manualTags);
|
|
5359
|
-
const visibility = String(payload?.visibility || 'public')
|
|
5217
|
+
const visibility = String(payload?.visibility || 'public')
|
|
5218
|
+
.trim()
|
|
5219
|
+
.toLowerCase();
|
|
5360
5220
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
5361
5221
|
if (!googleSession?.ownerJid || !googleSession?.sub) {
|
|
5362
5222
|
sendJson(req, res, 401, {
|
|
@@ -5556,10 +5416,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5556
5416
|
|
|
5557
5417
|
let existingUpload = await findPackWebUploadByUploadId(pack.id, uploadId, connection);
|
|
5558
5418
|
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
|
-
);
|
|
5419
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'upload_id já foi usado para outro arquivo neste pack.');
|
|
5563
5420
|
}
|
|
5564
5421
|
|
|
5565
5422
|
if (!existingUpload) {
|
|
@@ -5585,17 +5442,11 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5585
5442
|
}
|
|
5586
5443
|
|
|
5587
5444
|
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
|
-
);
|
|
5445
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.NOT_ALLOWED, 'Pack já foi publicado. Crie um novo pack para enviar novos stickers.');
|
|
5592
5446
|
}
|
|
5593
5447
|
|
|
5594
5448
|
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
|
-
);
|
|
5449
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.NOT_ALLOWED, 'Pack está em finalização. Aguarde e tente novamente.');
|
|
5599
5450
|
}
|
|
5600
5451
|
|
|
5601
5452
|
if (!existingUpload) {
|
|
@@ -5692,19 +5543,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5692
5543
|
throw new StickerPackError(STICKER_PACK_ERROR_CODES.PACK_NOT_FOUND, 'Pack nao encontrado.');
|
|
5693
5544
|
}
|
|
5694
5545
|
|
|
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));
|
|
5546
|
+
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
5547
|
|
|
5709
5548
|
if (!uploadRow) {
|
|
5710
5549
|
throw new StickerPackError(STICKER_PACK_ERROR_CODES.INTERNAL_ERROR, 'Registro de upload não encontrado para finalizar.');
|
|
@@ -5729,11 +5568,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5729
5568
|
packStatusForResponse = 'published';
|
|
5730
5569
|
}
|
|
5731
5570
|
|
|
5732
|
-
const snapshot = await getPackConsistencySnapshot(
|
|
5733
|
-
pack.id,
|
|
5734
|
-
payload?.set_cover === true ? asset.id : lockedPackRow.cover_sticker_id,
|
|
5735
|
-
connection,
|
|
5736
|
-
);
|
|
5571
|
+
const snapshot = await getPackConsistencySnapshot(pack.id, payload?.set_cover === true ? asset.id : lockedPackRow.cover_sticker_id, connection);
|
|
5737
5572
|
responseStickerCount = snapshot.sticker_count;
|
|
5738
5573
|
});
|
|
5739
5574
|
|
|
@@ -5762,9 +5597,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5762
5597
|
} catch (error) {
|
|
5763
5598
|
if (reservedUpload?.id) {
|
|
5764
5599
|
await runSqlTransaction(async (connection) => {
|
|
5765
|
-
const currentUpload =
|
|
5766
|
-
(await findPackWebUploadByUploadId(pack.id, uploadId, connection)) ||
|
|
5767
|
-
(await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
|
|
5600
|
+
const currentUpload = (await findPackWebUploadByUploadId(pack.id, uploadId, connection)) || (await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
|
|
5768
5601
|
if (currentUpload) {
|
|
5769
5602
|
await updatePackWebUpload(
|
|
5770
5603
|
currentUpload.id,
|
|
@@ -5824,9 +5657,7 @@ const handlePackPublishStateRequest = async (req, res, packKey, url = null) => {
|
|
|
5824
5657
|
}
|
|
5825
5658
|
}
|
|
5826
5659
|
|
|
5827
|
-
const editTokenValue =
|
|
5828
|
-
(req.method === 'GET' || req.method === 'HEAD' ? String(url?.searchParams?.get('edit_token') || '') : '') ||
|
|
5829
|
-
String(payload?.edit_token || '');
|
|
5660
|
+
const editTokenValue = (req.method === 'GET' || req.method === 'HEAD' ? String(url?.searchParams?.get('edit_token') || '') : '') || String(payload?.edit_token || '');
|
|
5830
5661
|
const editToken = resolveWebPackEditToken(editTokenValue);
|
|
5831
5662
|
if (!editToken || editToken.packId !== pack.id || editToken.ownerJid !== pack.owner_jid) {
|
|
5832
5663
|
sendJson(req, res, 403, {
|
|
@@ -5901,12 +5732,7 @@ const handleFinalizePackRequest = async (req, res, packKey) => {
|
|
|
5901
5732
|
await setStickerPackStatus(pack.id, 'processing', connection);
|
|
5902
5733
|
|
|
5903
5734
|
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;
|
|
5735
|
+
const canPublish = snapshot.sticker_count >= 1 && snapshot.failed_uploads === 0 && snapshot.processing_uploads === 0 && snapshot.pending_uploads === 0 && snapshot.cover_valid;
|
|
5910
5736
|
|
|
5911
5737
|
if (canPublish) {
|
|
5912
5738
|
await setStickerPackStatus(pack.id, 'published', connection);
|
|
@@ -5922,18 +5748,7 @@ const handleFinalizePackRequest = async (req, res, packKey) => {
|
|
|
5922
5748
|
finalizeResult = {
|
|
5923
5749
|
canPublish: false,
|
|
5924
5750
|
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',
|
|
5751
|
+
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
5752
|
};
|
|
5938
5753
|
});
|
|
5939
5754
|
} catch (error) {
|
|
@@ -6004,13 +5819,7 @@ const handleCreatorRankingRequest = async (req, res, url) => {
|
|
|
6004
5819
|
const limit = clampInt(url.searchParams.get('limit'), 50, 5, 200);
|
|
6005
5820
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
6006
5821
|
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
|
-
]);
|
|
5822
|
+
const cacheKey = buildCacheKey(['creator_ranking', visibility, q, limit, hasNsfwAccess ? 1 : 0]);
|
|
6014
5823
|
const payload = await getCachedSnapshot({
|
|
6015
5824
|
cacheMap: CATALOG_CREATOR_RANKING_CACHE,
|
|
6016
5825
|
key: cacheKey,
|
|
@@ -6026,20 +5835,11 @@ const handleCreatorRankingRequest = async (req, res, url) => {
|
|
|
6026
5835
|
});
|
|
6027
5836
|
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
6028
5837
|
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
|
-
);
|
|
5838
|
+
const ranking = buildCreatorRanking(STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries, { limit });
|
|
6033
5839
|
|
|
6034
5840
|
return {
|
|
6035
5841
|
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
|
-
),
|
|
5842
|
+
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
5843
|
publisher: creator.publisher,
|
|
6044
5844
|
verified: Boolean(creator.verified),
|
|
6045
5845
|
badges: creator.verified ? ['verified_creator'] : [],
|
|
@@ -6080,12 +5880,8 @@ const handleRecommendationsRequest = async (req, res, url) => {
|
|
|
6080
5880
|
});
|
|
6081
5881
|
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
6082
5882
|
const { entries, packClassificationById } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
6083
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
6084
|
-
|
|
6085
|
-
: entries;
|
|
6086
|
-
const entriesByCategory = categories.length
|
|
6087
|
-
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
6088
|
-
: entriesClassified;
|
|
5883
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
|
|
5884
|
+
const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
|
|
6089
5885
|
|
|
6090
5886
|
const viewerRecentPackIds = viewerKey ? await listViewerRecentPackIds(viewerKey, { days: 45, limit: 160 }) : [];
|
|
6091
5887
|
const viewerAffinity = buildViewerTagAffinity({
|
|
@@ -6130,27 +5926,20 @@ const handleOrphanStickerListRequest = async (req, res, url) => {
|
|
|
6130
5926
|
|
|
6131
5927
|
const { assets, hasMore, total } = categories.length
|
|
6132
5928
|
? await listClassifiedOrphanAssetsByCategories({ search: q, categories, limit, offset })
|
|
6133
|
-
: await (
|
|
6134
|
-
STICKER_CATALOG_ONLY_CLASSIFIED ? listClassifiedStickerAssetsWithoutPack : listStickerAssetsWithoutPack
|
|
6135
|
-
)({
|
|
5929
|
+
: await (STICKER_CATALOG_ONLY_CLASSIFIED ? listClassifiedStickerAssetsWithoutPack : listStickerAssetsWithoutPack)({
|
|
6136
5930
|
search: q,
|
|
6137
5931
|
limit,
|
|
6138
5932
|
offset,
|
|
6139
5933
|
});
|
|
6140
5934
|
const classifications = await listStickerClassificationsByAssetIds(assets.map((asset) => asset.id));
|
|
6141
5935
|
const byAssetId = new Map(classifications.map((entry) => [entry.asset_id, entry]));
|
|
6142
|
-
const filteredAssets = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
6143
|
-
|
|
6144
|
-
: assets;
|
|
6145
|
-
const filteredByCategories = categories.length
|
|
6146
|
-
? filteredAssets.filter((asset) => hasAnyCategory(resolveClassificationTags(byAssetId.get(asset.id)), categories))
|
|
6147
|
-
: filteredAssets;
|
|
5936
|
+
const filteredAssets = STICKER_CATALOG_ONLY_CLASSIFIED ? assets.filter((asset) => isStickerClassified(byAssetId.get(asset.id))) : assets;
|
|
5937
|
+
const filteredByCategories = categories.length ? filteredAssets.filter((asset) => hasAnyCategory(resolveClassificationTags(byAssetId.get(asset.id)), categories)) : filteredAssets;
|
|
6148
5938
|
const currentPage = Math.floor(offset / limit) + 1;
|
|
6149
5939
|
const totalPages = Math.max(1, Math.ceil(total / limit));
|
|
6150
5940
|
|
|
6151
5941
|
sendJson(req, res, 200, {
|
|
6152
|
-
data: filteredByCategories.map((asset) =>
|
|
6153
|
-
mapOrphanStickerAsset(asset, byAssetId.get(asset.id) || null, { hideSensitiveAssets: !hasNsfwAccess })),
|
|
5942
|
+
data: filteredByCategories.map((asset) => mapOrphanStickerAsset(asset, byAssetId.get(asset.id) || null, { hideSensitiveAssets: !hasNsfwAccess })),
|
|
6154
5943
|
pagination: {
|
|
6155
5944
|
limit,
|
|
6156
5945
|
offset,
|
|
@@ -6174,11 +5963,7 @@ const handleDataFileListRequest = async (req, res, url) => {
|
|
|
6174
5963
|
const normalizedQuery = q.toLowerCase();
|
|
6175
5964
|
|
|
6176
5965
|
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;
|
|
5966
|
+
const filteredFiles = normalizedQuery ? allFiles.filter((item) => item.name.toLowerCase().includes(normalizedQuery) || item.relative_path.toLowerCase().includes(normalizedQuery)) : allFiles;
|
|
6182
5967
|
|
|
6183
5968
|
const page = filteredFiles.slice(offset, offset + limit);
|
|
6184
5969
|
const hasMore = offset + limit < filteredFiles.length;
|
|
@@ -6214,11 +5999,7 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
6214
5999
|
const botJid = resolveBotJid(activeSocket?.user?.id) || null;
|
|
6215
6000
|
const botPhone = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '') || null;
|
|
6216
6001
|
const botConnected = Boolean(botJid) && socketReadyState === 1;
|
|
6217
|
-
const botConnectionStatus = botConnected
|
|
6218
|
-
? 'online'
|
|
6219
|
-
: socketReadyState === 0
|
|
6220
|
-
? 'connecting'
|
|
6221
|
-
: 'offline';
|
|
6002
|
+
const botConnectionStatus = botConnected ? 'online' : socketReadyState === 0 ? 'connecting' : 'offline';
|
|
6222
6003
|
|
|
6223
6004
|
let platform = {
|
|
6224
6005
|
total_users: null,
|
|
@@ -6421,16 +6202,7 @@ const extractCommandsFromMenuLine = (line, commandPrefix) => {
|
|
|
6421
6202
|
};
|
|
6422
6203
|
|
|
6423
6204
|
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
|
-
];
|
|
6205
|
+
const sections = [buildMenuCaption('OmniZap', commandPrefix), buildStickerMenu(commandPrefix), buildMediaMenu(commandPrefix), buildQuoteMenu(commandPrefix), buildAnimeMenu(commandPrefix), buildAiMenu(commandPrefix), buildStatsMenu(commandPrefix), buildAdminMenu(commandPrefix)];
|
|
6434
6206
|
|
|
6435
6207
|
const commands = new Set();
|
|
6436
6208
|
for (const section of sections) {
|
|
@@ -6446,49 +6218,15 @@ const collectAvailableMenuCommands = (commandPrefix = README_COMMAND_PREFIX) =>
|
|
|
6446
6218
|
};
|
|
6447
6219
|
|
|
6448
6220
|
const renderReadmeSnapshotMarkdown = ({ generatedAt, totals, topMessageTypes, commands }) => {
|
|
6449
|
-
const typeRows = topMessageTypes.length
|
|
6450
|
-
? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`)
|
|
6451
|
-
: ['| `outros` | 0 |'];
|
|
6221
|
+
const typeRows = topMessageTypes.length ? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`) : ['| `outros` | 0 |'];
|
|
6452
6222
|
|
|
6453
6223
|
const commandInline = commands.length ? commands.map((command) => `\`${command}\``).join(' · ') : 'Nenhum comando identificado no menu atual.';
|
|
6454
6224
|
|
|
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');
|
|
6225
|
+
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
6226
|
};
|
|
6481
6227
|
|
|
6482
6228
|
const buildReadmeSummarySnapshot = async () => {
|
|
6483
|
-
const [
|
|
6484
|
-
lidMapTotalsRows,
|
|
6485
|
-
chatsTotalsRows,
|
|
6486
|
-
groupsMetadataTotalsRows,
|
|
6487
|
-
packTotalsRows,
|
|
6488
|
-
stickerTotalsRows,
|
|
6489
|
-
messageTotalsRows,
|
|
6490
|
-
messageTypeRows,
|
|
6491
|
-
] = await Promise.all([
|
|
6229
|
+
const [lidMapTotalsRows, chatsTotalsRows, groupsMetadataTotalsRows, packTotalsRows, stickerTotalsRows, messageTotalsRows, messageTypeRows] = await Promise.all([
|
|
6492
6230
|
executeQuery(`SELECT COUNT(*) AS total_users FROM ${TABLES.LID_MAP}`),
|
|
6493
6231
|
executeQuery(
|
|
6494
6232
|
`SELECT
|
|
@@ -6648,12 +6386,7 @@ const resolveBotUserCandidates = (activeSocket) => {
|
|
|
6648
6386
|
const botPhoneFromCatalog = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
|
|
6649
6387
|
if (botPhoneFromCatalog) candidates.add(botPhoneFromCatalog);
|
|
6650
6388
|
|
|
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
|
-
];
|
|
6389
|
+
const envCandidates = [process.env.WHATSAPP_BOT_NUMBER, process.env.BOT_NUMBER, process.env.PHONE_NUMBER, process.env.BOT_PHONE_NUMBER];
|
|
6657
6390
|
|
|
6658
6391
|
for (const candidate of envCandidates) {
|
|
6659
6392
|
const digits = String(candidate || '').replace(/\D+/g, '');
|
|
@@ -6944,15 +6677,7 @@ const buildMarketplaceGlobalStatsSnapshot = async () => {
|
|
|
6944
6677
|
const dayKeys = buildLastSevenUtcDateKeys();
|
|
6945
6678
|
const dayFilterSql = `UTC_DATE() - INTERVAL 6 DAY`;
|
|
6946
6679
|
|
|
6947
|
-
const [
|
|
6948
|
-
packTotalsRows,
|
|
6949
|
-
stickerTotalsRows,
|
|
6950
|
-
stickersWithoutPackRows,
|
|
6951
|
-
engagementTotalsRows,
|
|
6952
|
-
dailyPacksRows,
|
|
6953
|
-
dailyStickersRows,
|
|
6954
|
-
dailyInteractionRows,
|
|
6955
|
-
] = await Promise.all([
|
|
6680
|
+
const [packTotalsRows, stickerTotalsRows, stickersWithoutPackRows, engagementTotalsRows, dailyPacksRows, dailyStickersRows, dailyInteractionRows] = await Promise.all([
|
|
6956
6681
|
executeQuery(
|
|
6957
6682
|
`SELECT
|
|
6958
6683
|
COUNT(*) AS total_packs,
|
|
@@ -7027,7 +6752,9 @@ const buildMarketplaceGlobalStatsSnapshot = async () => {
|
|
|
7027
6752
|
const dailyLikesByDay = new Map();
|
|
7028
6753
|
(Array.isArray(dailyInteractionRows) ? dailyInteractionRows : []).forEach((row) => {
|
|
7029
6754
|
const dayKey = toUtcDayKey(row?.day_key);
|
|
7030
|
-
const interaction = String(row?.interaction || '')
|
|
6755
|
+
const interaction = String(row?.interaction || '')
|
|
6756
|
+
.trim()
|
|
6757
|
+
.toLowerCase();
|
|
7031
6758
|
const total = Number(row?.total || 0);
|
|
7032
6759
|
if (!dayKey) return;
|
|
7033
6760
|
if (interaction === 'open') dailyOpensByDay.set(dayKey, total);
|
|
@@ -7161,13 +6888,16 @@ const handlePublicDataAssetRequest = async (req, res, pathname) => {
|
|
|
7161
6888
|
return true;
|
|
7162
6889
|
}
|
|
7163
6890
|
|
|
7164
|
-
const decodedSegments = suffix
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
6891
|
+
const decodedSegments = suffix
|
|
6892
|
+
.split('/')
|
|
6893
|
+
.filter(Boolean)
|
|
6894
|
+
.map((segment) => {
|
|
6895
|
+
try {
|
|
6896
|
+
return decodeURIComponent(segment);
|
|
6897
|
+
} catch {
|
|
6898
|
+
return segment;
|
|
6899
|
+
}
|
|
6900
|
+
});
|
|
7171
6901
|
|
|
7172
6902
|
const relativePath = normalizeRelativePath(decodedSegments.join('/'));
|
|
7173
6903
|
if (!relativePath || relativePath.includes('..') || !isAllowedDataImageFile(relativePath)) {
|
|
@@ -7215,11 +6945,7 @@ const fetchPublicPackPayload = async (normalizedPackKey) => {
|
|
|
7215
6945
|
|
|
7216
6946
|
const items = await listStickerPackItems(pack.id);
|
|
7217
6947
|
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
|
-
]);
|
|
6948
|
+
const [classifications, packClassification, engagement] = await Promise.all([listStickerClassificationsByAssetIds(stickerIds), getPackClassificationSummaryByAssetIds(stickerIds), getStickerPackEngagementByPackId(pack.id)]);
|
|
7223
6949
|
|
|
7224
6950
|
if (STICKER_CATALOG_ONLY_CLASSIFIED && !isPackClassified(packClassification)) {
|
|
7225
6951
|
return null;
|
|
@@ -7275,12 +7001,8 @@ const handleDetailsRequest = async (req, res, packKey, url) => {
|
|
|
7275
7001
|
}
|
|
7276
7002
|
|
|
7277
7003
|
const { pack, items, byAssetClassification, packClassification, engagement, signals } = packPayload;
|
|
7278
|
-
const visibleItems = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
7279
|
-
|
|
7280
|
-
: items;
|
|
7281
|
-
const visibleItemsByCategories = categories.length
|
|
7282
|
-
? visibleItems.filter((item) => hasAnyCategory(resolveClassificationTags(byAssetClassification.get(item.sticker_id)), categories))
|
|
7283
|
-
: visibleItems;
|
|
7004
|
+
const visibleItems = STICKER_CATALOG_ONLY_CLASSIFIED ? items.filter((item) => isStickerClassified(byAssetClassification.get(item.sticker_id))) : items;
|
|
7005
|
+
const visibleItemsByCategories = categories.length ? visibleItems.filter((item) => hasAnyCategory(resolveClassificationTags(byAssetClassification.get(item.sticker_id)), categories)) : visibleItems;
|
|
7284
7006
|
|
|
7285
7007
|
sendJson(req, res, 200, {
|
|
7286
7008
|
data: mapPackDetails(pack, visibleItemsByCategories, {
|
|
@@ -7320,9 +7042,7 @@ const handleAssetRequest = async (req, res, packKey, stickerToken) => {
|
|
|
7320
7042
|
try {
|
|
7321
7043
|
const buffer = await readStickerAssetBuffer(item.asset);
|
|
7322
7044
|
const classification = await findStickerClassificationByAssetId(normalizedStickerId).catch(() => null);
|
|
7323
|
-
const packClassification = stickerIds.length
|
|
7324
|
-
? await getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null)
|
|
7325
|
-
: null;
|
|
7045
|
+
const packClassification = stickerIds.length ? await getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null) : null;
|
|
7326
7046
|
if (STICKER_CATALOG_ONLY_CLASSIFIED && !isStickerClassified(classification)) {
|
|
7327
7047
|
sendJson(req, res, 404, { error: 'Sticker nao encontrado.' });
|
|
7328
7048
|
return;
|
|
@@ -7653,23 +7373,7 @@ const handleAdminOverviewRequest = async (req, res) => {
|
|
|
7653
7373
|
return;
|
|
7654
7374
|
}
|
|
7655
7375
|
|
|
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
|
-
]);
|
|
7376
|
+
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
7377
|
|
|
7674
7378
|
sendJson(req, res, 200, {
|
|
7675
7379
|
data: {
|
|
@@ -7698,10 +7402,7 @@ const handleAdminUsersRequest = async (req, res, url) => {
|
|
|
7698
7402
|
return;
|
|
7699
7403
|
}
|
|
7700
7404
|
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
|
-
]);
|
|
7405
|
+
const [activeSessions, users] = await Promise.all([listAdminActiveGoogleWebSessions({ limit }), listAdminKnownGoogleUsers({ limit })]);
|
|
7705
7406
|
sendJson(req, res, 200, { data: { active_sessions: activeSessions, users } });
|
|
7706
7407
|
};
|
|
7707
7408
|
|
|
@@ -7984,6 +7685,7 @@ const catalogApiRouter = createCatalogApiRouter({
|
|
|
7984
7685
|
handleCreatorRankingRequest,
|
|
7985
7686
|
handleRecommendationsRequest,
|
|
7986
7687
|
handleMarketplaceStatsRequest,
|
|
7688
|
+
handleHomeBootstrapRequest,
|
|
7987
7689
|
handleCreatePackConfigRequest,
|
|
7988
7690
|
handleOrphanStickerListRequest,
|
|
7989
7691
|
handleDataFileListRequest,
|
|
@@ -8022,8 +7724,7 @@ const catalogApiRouter = createCatalogApiRouter({
|
|
|
8022
7724
|
},
|
|
8023
7725
|
});
|
|
8024
7726
|
|
|
8025
|
-
const handleCatalogApiRequest = async (req, res, pathname, url) =>
|
|
8026
|
-
catalogApiRouter({ req, res, pathname, url });
|
|
7727
|
+
const handleCatalogApiRequest = async (req, res, pathname, url) => catalogApiRouter({ req, res, pathname, url });
|
|
8027
7728
|
|
|
8028
7729
|
const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
8029
7730
|
const normalizedPath = pathname.length > 1 ? pathname.replace(/\/+$/, '') : pathname;
|
|
@@ -8082,7 +7783,9 @@ const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
|
8082
7783
|
}
|
|
8083
7784
|
|
|
8084
7785
|
const initialPackKey = extractPackKeyFromWebPath(pathname);
|
|
8085
|
-
const initialPackKeyNormalized = String(initialPackKey || '')
|
|
7786
|
+
const initialPackKeyNormalized = String(initialPackKey || '')
|
|
7787
|
+
.trim()
|
|
7788
|
+
.toLowerCase();
|
|
8086
7789
|
const shouldRenderPackSeoPage = Boolean(initialPackKey && !PACK_PAGE_ROUTE_EXCLUSIONS.has(initialPackKeyNormalized));
|
|
8087
7790
|
|
|
8088
7791
|
if (shouldRenderPackSeoPage) {
|