@kaikybrofc/omnizap-system 2.2.8 → 2.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +194 -0
- package/docs/seo/satellite-page-template.md +89 -0
- package/docs/seo/satellite-pages-phase1.json +486 -0
- package/package.json +3 -1
- package/public/api-docs/index.html +78 -22
- package/public/bot-whatsapp-para-grupo/index.html +276 -0
- package/public/bot-whatsapp-sem-programar/index.html +276 -0
- package/public/comandos/index.html +413 -0
- package/public/como-automatizar-avisos-no-whatsapp/index.html +276 -0
- package/public/como-criar-comandos-whatsapp/index.html +276 -0
- package/public/como-evitar-spam-no-whatsapp/index.html +276 -0
- package/public/como-moderar-grupo-whatsapp/index.html +276 -0
- package/public/como-organizar-comunidade-whatsapp/index.html +276 -0
- package/public/css/github-project-panel.css +8 -8
- package/public/css/stickers-admin.css +31 -31
- package/public/css/styles.css +17 -16
- package/public/index.html +701 -1181
- package/public/js/apps/apiDocsApp.js +39 -6
- package/public/js/apps/homeApp.js +157 -410
- package/public/js/apps/stickersApp.js +42 -0
- package/public/licenca/index.html +9 -9
- package/public/login/index.html +26 -22
- package/public/melhor-bot-whatsapp-para-grupos/index.html +276 -0
- package/public/sitemap.xml +45 -0
- package/public/stickers/create/index.html +7 -6
- package/public/stickers/index.html +72 -5
- package/public/termos-de-uso/index.html +10 -10
- package/public/user/index.html +25 -21
- package/scripts/generate-seo-satellite-pages.mjs +434 -0
- package/server/controllers/stickerCatalogController.js +341 -700
- package/kaikybrofc-omnizap-system-2.2.7.tgz +0 -0
- package/kaikybrofc-omnizap-system-2.2.8.tgz +0 -0
|
@@ -9,85 +9,22 @@ import { getJidUser, normalizeJid, resolveBotJid } from '../../app/config/bailey
|
|
|
9
9
|
import { getAdminPhone, getAdminRawValue, resolveAdminJid } from '../../app/config/adminIdentity.js';
|
|
10
10
|
import { getActiveSocket } from '../../app/services/socketState.js';
|
|
11
11
|
import { extractUserIdInfo, resolveUserId } from '../../app/services/lidMapService.js';
|
|
12
|
-
import {
|
|
13
|
-
resolveWhatsAppOwnerJidFromLoginPayload,
|
|
14
|
-
toWhatsAppOwnerJid,
|
|
15
|
-
toWhatsAppPhoneDigits,
|
|
16
|
-
} from '../../app/services/whatsappLoginLinkService.js';
|
|
12
|
+
import { resolveWhatsAppOwnerJidFromLoginPayload, toWhatsAppOwnerJid, toWhatsAppPhoneDigits } from '../../app/services/whatsappLoginLinkService.js';
|
|
17
13
|
import logger from '../../app/utils/logger/loggerModule.js';
|
|
18
14
|
import { getSystemMetrics } from '../../app/utils/systemMetrics/systemMetricsModule.js';
|
|
19
|
-
import {
|
|
20
|
-
|
|
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();
|
|
@@ -3234,11 +3035,13 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3234
3035
|
const packName = truncateText(packSummary?.name || packSummary?.pack_key || 'Pack', 95);
|
|
3235
3036
|
const packDescription = truncateText(
|
|
3236
3037
|
packSummary?.description
|
|
3237
|
-
|| `Pack de stickers "${packName}" disponível no catálogo OmniZap.`,
|
|
3038
|
+
|| `Pack de stickers "${packName}" disponível no catálogo OmniZap para uso em bots e automações WhatsApp via API.`,
|
|
3238
3039
|
180,
|
|
3239
3040
|
);
|
|
3240
3041
|
const canonicalUrl = toSiteAbsoluteUrl(buildPackWebUrl(packSummary?.pack_key || ''));
|
|
3241
3042
|
const catalogUrl = toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`);
|
|
3043
|
+
const homeUrl = toSiteAbsoluteUrl('/');
|
|
3044
|
+
const apiDocsUrl = toSiteAbsoluteUrl('/api-docs/');
|
|
3242
3045
|
const fallbackCoverUrl = packSummary?.is_nsfw ? NSFW_STICKER_PLACEHOLDER_URL : 'https://iili.io/fSNGag2.png';
|
|
3243
3046
|
const coverUrl = toSiteAbsoluteUrl(packSummary?.cover_url || fallbackCoverUrl);
|
|
3244
3047
|
const publisher = truncateText(packSummary?.publisher || 'Criador OmniZap', 80);
|
|
@@ -3262,13 +3065,47 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3262
3065
|
null,
|
|
3263
3066
|
0,
|
|
3264
3067
|
).replace(/</g, '\\u003c');
|
|
3068
|
+
const faqSchemaJson = JSON.stringify(
|
|
3069
|
+
{
|
|
3070
|
+
'@context': 'https://schema.org',
|
|
3071
|
+
'@type': 'FAQPage',
|
|
3072
|
+
mainEntity: [
|
|
3073
|
+
{
|
|
3074
|
+
'@type': 'Question',
|
|
3075
|
+
name: `Como usar o pack ${packName} no meu bot?`,
|
|
3076
|
+
acceptedAnswer: {
|
|
3077
|
+
'@type': 'Answer',
|
|
3078
|
+
text: `Use o pack ${packName} como recurso de engajamento e consulte exemplos de integração em ${apiDocsUrl}.`,
|
|
3079
|
+
},
|
|
3080
|
+
},
|
|
3081
|
+
{
|
|
3082
|
+
'@type': 'Question',
|
|
3083
|
+
name: 'Onde encontro mais packs de stickers?',
|
|
3084
|
+
acceptedAnswer: {
|
|
3085
|
+
'@type': 'Answer',
|
|
3086
|
+
text: `Veja o catálogo completo em ${catalogUrl}.`,
|
|
3087
|
+
},
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
'@type': 'Question',
|
|
3091
|
+
name: 'Onde vejo a plataforma principal do OmniZap?',
|
|
3092
|
+
acceptedAnswer: {
|
|
3093
|
+
'@type': 'Answer',
|
|
3094
|
+
text: `A página principal do OmniZap está em ${homeUrl}.`,
|
|
3095
|
+
},
|
|
3096
|
+
},
|
|
3097
|
+
],
|
|
3098
|
+
},
|
|
3099
|
+
null,
|
|
3100
|
+
0,
|
|
3101
|
+
).replace(/</g, '\\u003c');
|
|
3265
3102
|
|
|
3266
3103
|
return `<!doctype html>
|
|
3267
3104
|
<html lang="pt-BR">
|
|
3268
3105
|
<head>
|
|
3269
3106
|
<meta charset="utf-8" />
|
|
3270
3107
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
3271
|
-
<title>${escapeHtmlAttribute(`${packName} |
|
|
3108
|
+
<title>${escapeHtmlAttribute(`${packName} | Stickers para Bot WhatsApp OmniZap`)}</title>
|
|
3272
3109
|
<meta name="description" content="${escapeHtmlAttribute(packDescription)}" />
|
|
3273
3110
|
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1" />
|
|
3274
3111
|
<link rel="canonical" href="${escapeHtmlAttribute(canonicalUrl)}" />
|
|
@@ -3286,9 +3123,11 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3286
3123
|
},
|
|
3287
3124
|
colors: {
|
|
3288
3125
|
slateApp: '#0f172a',
|
|
3289
|
-
slateCard: '#
|
|
3290
|
-
borderApp: '
|
|
3291
|
-
accent: '#
|
|
3126
|
+
slateCard: '#1e293b',
|
|
3127
|
+
borderApp: 'rgba(255,255,255,0.05)',
|
|
3128
|
+
accent: '#2563eb',
|
|
3129
|
+
accentTech: '#7c3aed',
|
|
3130
|
+
cta: '#22c55e'
|
|
3292
3131
|
},
|
|
3293
3132
|
boxShadow: {
|
|
3294
3133
|
soft: '0 8px 24px rgba(2, 6, 23, 0.22)'
|
|
@@ -3313,15 +3152,16 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3313
3152
|
<meta name="twitter:image" content="${escapeHtmlAttribute(coverUrl)}" />
|
|
3314
3153
|
|
|
3315
3154
|
<script type="application/ld+json">${schemaJson}</script>
|
|
3155
|
+
<script type="application/ld+json">${faqSchemaJson}</script>
|
|
3316
3156
|
<style>
|
|
3317
|
-
body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif; background: #
|
|
3157
|
+
body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif; background: #0f172a; color: #f8fafc; }
|
|
3318
3158
|
.seo-shell { max-width: 880px; margin: 0 auto; padding: 18px 14px 12px; }
|
|
3319
|
-
.seo-card { border: 1px solid
|
|
3159
|
+
.seo-card { border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; background: #1e293b; padding: 16px; }
|
|
3320
3160
|
.seo-card h1 { margin: 0 0 8px; font-size: 26px; line-height: 1.2; }
|
|
3321
|
-
.seo-card p { margin: 0 0 10px; line-height: 1.55; color: #
|
|
3161
|
+
.seo-card p { margin: 0 0 10px; line-height: 1.55; color: #94a3b8; }
|
|
3322
3162
|
.seo-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
3323
|
-
.seo-row a { color: #
|
|
3324
|
-
.seo-row a:hover { background: #
|
|
3163
|
+
.seo-row a { color: #2563eb; text-decoration: none; border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 8px 10px; }
|
|
3164
|
+
.seo-row a:hover { background: #111827; }
|
|
3325
3165
|
</style>
|
|
3326
3166
|
</head>
|
|
3327
3167
|
<body class="bg-slateApp text-slate-100 font-sans min-h-screen">
|
|
@@ -3331,9 +3171,15 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3331
3171
|
<h1>${escapeHtmlAttribute(packName)}</h1>
|
|
3332
3172
|
<p>${escapeHtmlAttribute(packDescription)}</p>
|
|
3333
3173
|
<p>Criador: <strong>${escapeHtmlAttribute(publisher)}</strong> • Stickers: <strong>${stickerCount}</strong></p>
|
|
3174
|
+
<p>Use este pack como recurso integrado no seu bot. Consulte endpoints e exemplos na área de desenvolvedor da API OmniZap.</p>
|
|
3175
|
+
<h2 style="margin:12px 0 6px;font-size:18px;">FAQ rápido</h2>
|
|
3176
|
+
<p style="margin-bottom:6px;"><strong>Como usar no bot?</strong> Consulte a documentação técnica e exemplos na área de desenvolvedor.</p>
|
|
3177
|
+
<p style="margin-bottom:6px;"><strong>Tem mais packs?</strong> Sim, explore o catálogo completo para encontrar packs relacionados.</p>
|
|
3334
3178
|
<div class="seo-row">
|
|
3335
3179
|
<a href="${escapeHtmlAttribute(canonicalUrl)}">Abrir este pack</a>
|
|
3336
3180
|
<a href="${escapeHtmlAttribute(catalogUrl)}">Voltar ao catálogo</a>
|
|
3181
|
+
<a href="${escapeHtmlAttribute(apiDocsUrl)}">Área de Desenvolvedor</a>
|
|
3182
|
+
<a href="${escapeHtmlAttribute(homeUrl)}">Plataforma OmniZap</a>
|
|
3337
3183
|
</div>
|
|
3338
3184
|
</section>
|
|
3339
3185
|
</main>
|
|
@@ -3362,10 +3208,10 @@ const renderPackNotFoundHtml = (packKey = '') => `<!doctype html>
|
|
|
3362
3208
|
<meta name="robots" content="noindex, nofollow" />
|
|
3363
3209
|
<link rel="canonical" href="${escapeHtmlAttribute(toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`))}" />
|
|
3364
3210
|
<style>
|
|
3365
|
-
body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: #
|
|
3211
|
+
body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: #0f172a; color: #f8fafc; }
|
|
3366
3212
|
main { max-width: 760px; margin: 0 auto; padding: 20px 14px; }
|
|
3367
|
-
article { border: 1px solid
|
|
3368
|
-
a { color: #
|
|
3213
|
+
article { border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; background: #1e293b; padding: 16px; }
|
|
3214
|
+
a { color: #2563eb; text-decoration: none; }
|
|
3369
3215
|
</style>
|
|
3370
3216
|
</head>
|
|
3371
3217
|
<body>
|
|
@@ -3439,8 +3285,17 @@ const buildSitemapXml = async () => {
|
|
|
3439
3285
|
{ loc: toSiteAbsoluteUrl('/'), changefreq: 'daily', priority: '1.0' },
|
|
3440
3286
|
{ loc: toSiteAbsoluteUrl(`${STICKER_WEB_PATH}/`), changefreq: 'hourly', priority: '0.9' },
|
|
3441
3287
|
{ loc: toSiteAbsoluteUrl('/api-docs/'), changefreq: 'weekly', priority: '0.8' },
|
|
3288
|
+
{ loc: toSiteAbsoluteUrl('/comandos/'), changefreq: 'weekly', priority: '0.78' },
|
|
3442
3289
|
{ loc: toSiteAbsoluteUrl('/termos-de-uso/'), changefreq: 'monthly', priority: '0.5' },
|
|
3443
3290
|
{ loc: toSiteAbsoluteUrl('/licenca/'), changefreq: 'monthly', priority: '0.5' },
|
|
3291
|
+
{ loc: toSiteAbsoluteUrl('/bot-whatsapp-para-grupo/'), changefreq: 'weekly', priority: '0.75' },
|
|
3292
|
+
{ loc: toSiteAbsoluteUrl('/como-moderar-grupo-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3293
|
+
{ loc: toSiteAbsoluteUrl('/como-evitar-spam-no-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3294
|
+
{ loc: toSiteAbsoluteUrl('/como-organizar-comunidade-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3295
|
+
{ loc: toSiteAbsoluteUrl('/como-automatizar-avisos-no-whatsapp/'), changefreq: 'weekly', priority: '0.72' },
|
|
3296
|
+
{ loc: toSiteAbsoluteUrl('/como-criar-comandos-whatsapp/'), changefreq: 'weekly', priority: '0.71' },
|
|
3297
|
+
{ loc: toSiteAbsoluteUrl('/melhor-bot-whatsapp-para-grupos/'), changefreq: 'weekly', priority: '0.74' },
|
|
3298
|
+
{ loc: toSiteAbsoluteUrl('/bot-whatsapp-sem-programar/'), changefreq: 'weekly', priority: '0.73' },
|
|
3444
3299
|
];
|
|
3445
3300
|
|
|
3446
3301
|
const packRows = await executeQuery(
|
|
@@ -3496,9 +3351,7 @@ const sendStaticTextFile = async (req, res, filePath, contentType) => {
|
|
|
3496
3351
|
try {
|
|
3497
3352
|
const body = await fs.readFile(filePath, 'utf8');
|
|
3498
3353
|
const hasVersionQuery = /(?:\?|&)v=/.test(String(req.url || ''));
|
|
3499
|
-
const cacheControl = hasVersionQuery
|
|
3500
|
-
? `public, max-age=${IMMUTABLE_ASSET_CACHE_SECONDS}, immutable`
|
|
3501
|
-
: `public, max-age=${STATIC_TEXT_CACHE_SECONDS}, stale-while-revalidate=${Math.min(86400, STATIC_TEXT_CACHE_SECONDS * 4)}`;
|
|
3354
|
+
const cacheControl = hasVersionQuery ? `public, max-age=${IMMUTABLE_ASSET_CACHE_SECONDS}, immutable` : `public, max-age=${STATIC_TEXT_CACHE_SECONDS}, stale-while-revalidate=${Math.min(86400, STATIC_TEXT_CACHE_SECONDS * 4)}`;
|
|
3502
3355
|
res.statusCode = 200;
|
|
3503
3356
|
res.setHeader('Content-Type', contentType);
|
|
3504
3357
|
res.setHeader('Cache-Control', cacheControl);
|
|
@@ -3548,18 +3401,7 @@ const handleListRequest = async (req, res, url) => {
|
|
|
3548
3401
|
const normalizedIntent = normalizeCategoryToken(intent).replace(/-/g, '_');
|
|
3549
3402
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
3550
3403
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
3551
|
-
const cacheKey = buildCacheKey([
|
|
3552
|
-
'list',
|
|
3553
|
-
q,
|
|
3554
|
-
visibility,
|
|
3555
|
-
sort,
|
|
3556
|
-
categories.join(','),
|
|
3557
|
-
normalizedIntent,
|
|
3558
|
-
includeSensitive ? 1 : 0,
|
|
3559
|
-
limit,
|
|
3560
|
-
offset,
|
|
3561
|
-
hasNsfwAccess ? 1 : 0,
|
|
3562
|
-
]);
|
|
3404
|
+
const cacheKey = buildCacheKey(['list', q, visibility, sort, categories.join(','), normalizedIntent, includeSensitive ? 1 : 0, limit, offset, hasNsfwAccess ? 1 : 0]);
|
|
3563
3405
|
const payload = await getCachedSnapshot({
|
|
3564
3406
|
cacheMap: CATALOG_LIST_CACHE,
|
|
3565
3407
|
key: cacheKey,
|
|
@@ -3589,18 +3431,10 @@ const handleListRequest = async (req, res, url) => {
|
|
|
3589
3431
|
if (!packs.length) break;
|
|
3590
3432
|
|
|
3591
3433
|
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3592
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3593
|
-
|
|
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;
|
|
3434
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
|
|
3435
|
+
const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
|
|
3436
|
+
const entriesBySensitivity = includeSensitive ? entriesByCategory : entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
|
|
3437
|
+
const entriesByIntent = intent ? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent) : entriesBySensitivity;
|
|
3604
3438
|
const sortedEntries = [...entriesByIntent].sort((left, right) => {
|
|
3605
3439
|
const completenessDelta = compareEntriesByPackCompleteness(left, right);
|
|
3606
3440
|
if (completenessDelta !== 0) return completenessDelta;
|
|
@@ -3676,12 +3510,8 @@ const handleIntentCollectionsRequest = async (req, res, url) => {
|
|
|
3676
3510
|
});
|
|
3677
3511
|
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
3678
3512
|
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3679
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3680
|
-
|
|
3681
|
-
: entries;
|
|
3682
|
-
const entriesByCategory = categories.length
|
|
3683
|
-
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
3684
|
-
: entriesClassified;
|
|
3513
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
|
|
3514
|
+
const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
|
|
3685
3515
|
const intents = buildIntentCollections(entriesByCategory, { limit });
|
|
3686
3516
|
|
|
3687
3517
|
sendJson(req, res, 200, {
|
|
@@ -3701,12 +3531,7 @@ const handleIntentCollectionsRequest = async (req, res, url) => {
|
|
|
3701
3531
|
});
|
|
3702
3532
|
};
|
|
3703
3533
|
|
|
3704
|
-
const resolveMarketplaceVisibilityValues = (visibility) =>
|
|
3705
|
-
visibility === 'all'
|
|
3706
|
-
? ['public', 'unlisted']
|
|
3707
|
-
: visibility === 'unlisted'
|
|
3708
|
-
? ['unlisted']
|
|
3709
|
-
: ['public'];
|
|
3534
|
+
const resolveMarketplaceVisibilityValues = (visibility) => (visibility === 'all' ? ['public', 'unlisted'] : visibility === 'unlisted' ? ['unlisted'] : ['public']);
|
|
3710
3535
|
|
|
3711
3536
|
const getHomeMarketplaceStatsCacheBucket = (visibility) => {
|
|
3712
3537
|
const key = normalizeCatalogVisibility(visibility);
|
|
@@ -3843,9 +3668,7 @@ const handleCreatePackConfigRequest = async (req, res) => {
|
|
|
3843
3668
|
pack_name_hint: 'Nome livre (espaços e emojis são permitidos).',
|
|
3844
3669
|
visibility_values: ['public', 'unlisted', 'private'],
|
|
3845
3670
|
owner_phone_required: !STICKER_WEB_GOOGLE_AUTH_REQUIRED,
|
|
3846
|
-
owner_phone_hint: STICKER_WEB_GOOGLE_AUTH_REQUIRED
|
|
3847
|
-
? 'Login Google obrigatório para criar packs nesta página.'
|
|
3848
|
-
: 'Informe o número de celular com DDD para vincular o pack ao criador.',
|
|
3671
|
+
owner_phone_hint: STICKER_WEB_GOOGLE_AUTH_REQUIRED ? 'Login Google obrigatório para criar packs nesta página.' : 'Informe o número de celular com DDD para vincular o pack ao criador.',
|
|
3849
3672
|
suggested_tags: ['anime', 'meme', 'game', 'texto', 'nsfw', 'dark', 'cartoon', 'foto-real', 'cyberpunk'],
|
|
3850
3673
|
},
|
|
3851
3674
|
auth: {
|
|
@@ -3936,15 +3759,12 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
|
|
|
3936
3759
|
return;
|
|
3937
3760
|
}
|
|
3938
3761
|
|
|
3939
|
-
const reason = String(linkedOwner.reason || '')
|
|
3762
|
+
const reason = String(linkedOwner.reason || '')
|
|
3763
|
+
.trim()
|
|
3764
|
+
.toLowerCase();
|
|
3940
3765
|
const isUnauthorizedAttempt = ['invalid_signature', 'missing_signature'].includes(reason);
|
|
3941
3766
|
const statusCode = isUnauthorizedAttempt ? 403 : 400;
|
|
3942
|
-
const errorMessage =
|
|
3943
|
-
reason === 'expired'
|
|
3944
|
-
? 'Link de login expirado. Envie "iniciar" novamente no WhatsApp.'
|
|
3945
|
-
: isUnauthorizedAttempt
|
|
3946
|
-
? 'Tentativa de login sem permissao detectada. Gere um novo link enviando "iniciar" no privado do bot.'
|
|
3947
|
-
: 'Link de login invalido. Envie "iniciar" novamente no WhatsApp.';
|
|
3767
|
+
const errorMessage = reason === 'expired' ? 'Link de login expirado. Envie "iniciar" novamente no WhatsApp.' : isUnauthorizedAttempt ? 'Tentativa de login sem permissao detectada. Gere um novo link enviando "iniciar" no privado do bot.' : 'Link de login invalido. Envie "iniciar" novamente no WhatsApp.';
|
|
3948
3768
|
|
|
3949
3769
|
logger.warn('Tentativa de login web bloqueada por validacao do link WhatsApp.', {
|
|
3950
3770
|
action: 'sticker_pack_google_web_login_link_blocked',
|
|
@@ -4255,13 +4075,8 @@ const handleMyProfileRequest = async (req, res, url = null) => {
|
|
|
4255
4075
|
return;
|
|
4256
4076
|
}
|
|
4257
4077
|
|
|
4258
|
-
const ownerPacks = await Promise.all(
|
|
4259
|
-
|
|
4260
|
-
);
|
|
4261
|
-
const includeAutoPacks = parseEnvBool(
|
|
4262
|
-
url?.searchParams?.get('include_auto'),
|
|
4263
|
-
parseEnvBool(process.env.STICKER_WEB_MY_PROFILE_INCLUDE_AUTO_PACKS, false),
|
|
4264
|
-
);
|
|
4078
|
+
const ownerPacks = await Promise.all(ownerCandidates.map((ownerJid) => listStickerPacksByOwner(ownerJid, { limit: 200, offset: 0 })));
|
|
4079
|
+
const includeAutoPacks = parseEnvBool(url?.searchParams?.get('include_auto'), parseEnvBool(process.env.STICKER_WEB_MY_PROFILE_INCLUDE_AUTO_PACKS, false));
|
|
4265
4080
|
|
|
4266
4081
|
const dedupPacks = new Map();
|
|
4267
4082
|
for (const packList of ownerPacks) {
|
|
@@ -4429,11 +4244,7 @@ const cleanupOrphanStickerAssets = async (assetIds, { reason = 'manage_mutation'
|
|
|
4429
4244
|
|
|
4430
4245
|
const deleteManagedPackWithCleanup = async ({ ownerJid, identifier, fallbackPack = null }) => {
|
|
4431
4246
|
const transactionResult = await runSqlTransaction(async (connection) => {
|
|
4432
|
-
const pack =
|
|
4433
|
-
(await findStickerPackByOwnerAndIdentifier(ownerJid, fallbackPack?.id || identifier, { connection })) ||
|
|
4434
|
-
(fallbackPack?.pack_key && fallbackPack?.pack_key !== identifier
|
|
4435
|
-
? await findStickerPackByOwnerAndIdentifier(ownerJid, identifier, { connection })
|
|
4436
|
-
: null);
|
|
4247
|
+
const pack = (await findStickerPackByOwnerAndIdentifier(ownerJid, fallbackPack?.id || identifier, { connection })) || (fallbackPack?.pack_key && fallbackPack?.pack_key !== identifier ? await findStickerPackByOwnerAndIdentifier(ownerJid, identifier, { connection }) : null);
|
|
4437
4248
|
|
|
4438
4249
|
if (!pack) {
|
|
4439
4250
|
return {
|
|
@@ -4567,9 +4378,7 @@ const buildManagedPackAnalytics = async (pack) => {
|
|
|
4567
4378
|
downloads: Number(engagement?.open_count || 0),
|
|
4568
4379
|
likes: Number(engagement?.like_count || 0),
|
|
4569
4380
|
dislikes: Number(engagement?.dislike_count || 0),
|
|
4570
|
-
score:
|
|
4571
|
-
Number(engagement?.score || 0) ||
|
|
4572
|
-
Number(engagement?.like_count || 0) - Number(engagement?.dislike_count || 0),
|
|
4381
|
+
score: Number(engagement?.score || 0) || Number(engagement?.like_count || 0) - Number(engagement?.dislike_count || 0),
|
|
4573
4382
|
engagement: {
|
|
4574
4383
|
open_count: Number(engagement?.open_count || 0),
|
|
4575
4384
|
like_count: Number(engagement?.like_count || 0),
|
|
@@ -4591,12 +4400,7 @@ const buildManagedPackResponseData = async (pack) => {
|
|
|
4591
4400
|
const items = Array.isArray(pack?.items) ? pack.items : [];
|
|
4592
4401
|
const stickerIds = items.map((item) => item.sticker_id).filter(Boolean);
|
|
4593
4402
|
|
|
4594
|
-
const [classifications, packClassification, analytics, publishState] = await Promise.all([
|
|
4595
|
-
stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([]),
|
|
4596
|
-
pack?.classification || (stickerIds.length ? getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null) : null),
|
|
4597
|
-
buildManagedPackAnalytics(pack),
|
|
4598
|
-
buildPackPublishStateData(pack, { includeUploads: true }),
|
|
4599
|
-
]);
|
|
4403
|
+
const [classifications, packClassification, analytics, publishState] = await Promise.all([stickerIds.length ? listStickerClassificationsByAssetIds(stickerIds) : Promise.resolve([]), pack?.classification || (stickerIds.length ? getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null) : null), buildManagedPackAnalytics(pack), buildPackPublishStateData(pack, { includeUploads: true })]);
|
|
4600
4404
|
|
|
4601
4405
|
const byAssetClassification = new Map((Array.isArray(classifications) ? classifications : []).map((entry) => [entry.asset_id, entry]));
|
|
4602
4406
|
|
|
@@ -4735,8 +4539,12 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
4735
4539
|
}
|
|
4736
4540
|
|
|
4737
4541
|
if (hasOwn(payload, 'visibility')) {
|
|
4738
|
-
const nextVisibility = String(payload?.visibility || '')
|
|
4739
|
-
|
|
4542
|
+
const nextVisibility = String(payload?.visibility || '')
|
|
4543
|
+
.trim()
|
|
4544
|
+
.toLowerCase();
|
|
4545
|
+
const currentVisibility = String(updatedPack?.visibility || '')
|
|
4546
|
+
.trim()
|
|
4547
|
+
.toLowerCase();
|
|
4740
4548
|
if (!nextVisibility) {
|
|
4741
4549
|
updatedPack = await stickerPackService.setPackVisibility({
|
|
4742
4550
|
ownerJid: context.ownerJid,
|
|
@@ -4859,9 +4667,7 @@ const handleManagedPackCoverRequest = async (req, res, packKey) => {
|
|
|
4859
4667
|
return;
|
|
4860
4668
|
}
|
|
4861
4669
|
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND) {
|
|
4862
|
-
const fresh = await stickerPackService
|
|
4863
|
-
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4864
|
-
.catch(() => context.pack);
|
|
4670
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
|
|
4865
4671
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
4866
4672
|
pack_key: context.packKey,
|
|
4867
4673
|
sticker_id: sanitizeText(payload?.sticker_id, 36, { allowEmpty: true }) || null,
|
|
@@ -4924,9 +4730,7 @@ const handleManagedPackReorderRequest = async (req, res, packKey) => {
|
|
|
4924
4730
|
return;
|
|
4925
4731
|
}
|
|
4926
4732
|
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.INVALID_INPUT) {
|
|
4927
|
-
const fresh = await stickerPackService
|
|
4928
|
-
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4929
|
-
.catch(() => context.pack);
|
|
4733
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
|
|
4930
4734
|
await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
|
|
4931
4735
|
pack_key: context.packKey,
|
|
4932
4736
|
reason: 'invalid_or_stale_order',
|
|
@@ -4977,9 +4781,7 @@ const handleManagedPackStickerDeleteRequest = async (req, res, packKey, stickerI
|
|
|
4977
4781
|
return;
|
|
4978
4782
|
}
|
|
4979
4783
|
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND) {
|
|
4980
|
-
const fresh = await stickerPackService
|
|
4981
|
-
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4982
|
-
.catch(() => context.pack);
|
|
4784
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => context.pack);
|
|
4983
4785
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
4984
4786
|
pack_key: context.packKey,
|
|
4985
4787
|
sticker_id: sanitizeText(stickerId, 36, { allowEmpty: true }) || null,
|
|
@@ -5218,9 +5020,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
5218
5020
|
}
|
|
5219
5021
|
|
|
5220
5022
|
if (swapResult?.status === 'old_sticker_missing') {
|
|
5221
|
-
const fresh = await stickerPackService
|
|
5222
|
-
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
5223
|
-
.catch(() => originalPack);
|
|
5023
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => originalPack);
|
|
5224
5024
|
await cleanupOrphanStickerAssets(uploadedAssetId ? [uploadedAssetId] : [], { reason: 'replace_sticker_old_missing' });
|
|
5225
5025
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
5226
5026
|
pack_key: context.packKey,
|
|
@@ -5230,9 +5030,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
5230
5030
|
}
|
|
5231
5031
|
|
|
5232
5032
|
if (swapResult?.status === 'duplicate_target') {
|
|
5233
|
-
const fresh = await stickerPackService
|
|
5234
|
-
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
5235
|
-
.catch(() => originalPack);
|
|
5033
|
+
const fresh = await stickerPackService.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey }).catch(() => originalPack);
|
|
5236
5034
|
await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
|
|
5237
5035
|
pack_key: context.packKey,
|
|
5238
5036
|
reason: 'duplicate_target_sticker',
|
|
@@ -5297,8 +5095,7 @@ const handleManagedPackAnalyticsRequest = async (req, res, packKey) => {
|
|
|
5297
5095
|
}
|
|
5298
5096
|
};
|
|
5299
5097
|
|
|
5300
|
-
const normalizeCreatePackName = (value) =>
|
|
5301
|
-
sanitizeText(value, PACK_CREATE_MAX_NAME_LENGTH, { allowEmpty: true }) || '';
|
|
5098
|
+
const normalizeCreatePackName = (value) => sanitizeText(value, PACK_CREATE_MAX_NAME_LENGTH, { allowEmpty: true }) || '';
|
|
5302
5099
|
|
|
5303
5100
|
const mapStickerPackCreateError = (error) => {
|
|
5304
5101
|
if (!(error instanceof StickerPackError)) {
|
|
@@ -5356,7 +5153,9 @@ const handleCreatePackRequest = async (req, res) => {
|
|
|
5356
5153
|
const description = sanitizeText(payload?.description || '', PACK_CREATE_MAX_DESCRIPTION_LENGTH, { allowEmpty: true });
|
|
5357
5154
|
const manualTags = mergeUniqueTags(Array.isArray(payload?.tags) ? payload.tags : []).slice(0, 8);
|
|
5358
5155
|
const persistedDescription = buildPackDescriptionWithTags(description, manualTags);
|
|
5359
|
-
const visibility = String(payload?.visibility || 'public')
|
|
5156
|
+
const visibility = String(payload?.visibility || 'public')
|
|
5157
|
+
.trim()
|
|
5158
|
+
.toLowerCase();
|
|
5360
5159
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
5361
5160
|
if (!googleSession?.ownerJid || !googleSession?.sub) {
|
|
5362
5161
|
sendJson(req, res, 401, {
|
|
@@ -5556,10 +5355,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5556
5355
|
|
|
5557
5356
|
let existingUpload = await findPackWebUploadByUploadId(pack.id, uploadId, connection);
|
|
5558
5357
|
if (existingUpload && existingUpload.sticker_hash !== stickerHash) {
|
|
5559
|
-
throw new StickerPackError(
|
|
5560
|
-
STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
5561
|
-
'upload_id já foi usado para outro arquivo neste pack.',
|
|
5562
|
-
);
|
|
5358
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.INVALID_INPUT, 'upload_id já foi usado para outro arquivo neste pack.');
|
|
5563
5359
|
}
|
|
5564
5360
|
|
|
5565
5361
|
if (!existingUpload) {
|
|
@@ -5585,17 +5381,11 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5585
5381
|
}
|
|
5586
5382
|
|
|
5587
5383
|
if (currentPackStatus === 'published') {
|
|
5588
|
-
throw new StickerPackError(
|
|
5589
|
-
STICKER_PACK_ERROR_CODES.NOT_ALLOWED,
|
|
5590
|
-
'Pack já foi publicado. Crie um novo pack para enviar novos stickers.',
|
|
5591
|
-
);
|
|
5384
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.NOT_ALLOWED, 'Pack já foi publicado. Crie um novo pack para enviar novos stickers.');
|
|
5592
5385
|
}
|
|
5593
5386
|
|
|
5594
5387
|
if (currentPackStatus === 'processing') {
|
|
5595
|
-
throw new StickerPackError(
|
|
5596
|
-
STICKER_PACK_ERROR_CODES.NOT_ALLOWED,
|
|
5597
|
-
'Pack está em finalização. Aguarde e tente novamente.',
|
|
5598
|
-
);
|
|
5388
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.NOT_ALLOWED, 'Pack está em finalização. Aguarde e tente novamente.');
|
|
5599
5389
|
}
|
|
5600
5390
|
|
|
5601
5391
|
if (!existingUpload) {
|
|
@@ -5692,19 +5482,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5692
5482
|
throw new StickerPackError(STICKER_PACK_ERROR_CODES.PACK_NOT_FOUND, 'Pack nao encontrado.');
|
|
5693
5483
|
}
|
|
5694
5484
|
|
|
5695
|
-
const uploadRow =
|
|
5696
|
-
(reservedUpload?.id &&
|
|
5697
|
-
normalizePackWebUploadRow(
|
|
5698
|
-
(
|
|
5699
|
-
await executeQuery(
|
|
5700
|
-
`SELECT * FROM ${TABLES.STICKER_PACK_WEB_UPLOAD} WHERE id = ? LIMIT 1`,
|
|
5701
|
-
[reservedUpload.id],
|
|
5702
|
-
connection,
|
|
5703
|
-
)
|
|
5704
|
-
)?.[0],
|
|
5705
|
-
)) ||
|
|
5706
|
-
(await findPackWebUploadByUploadId(pack.id, uploadId, connection)) ||
|
|
5707
|
-
(await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
|
|
5485
|
+
const uploadRow = (reservedUpload?.id && normalizePackWebUploadRow((await executeQuery(`SELECT * FROM ${TABLES.STICKER_PACK_WEB_UPLOAD} WHERE id = ? LIMIT 1`, [reservedUpload.id], connection))?.[0])) || (await findPackWebUploadByUploadId(pack.id, uploadId, connection)) || (await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
|
|
5708
5486
|
|
|
5709
5487
|
if (!uploadRow) {
|
|
5710
5488
|
throw new StickerPackError(STICKER_PACK_ERROR_CODES.INTERNAL_ERROR, 'Registro de upload não encontrado para finalizar.');
|
|
@@ -5729,11 +5507,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5729
5507
|
packStatusForResponse = 'published';
|
|
5730
5508
|
}
|
|
5731
5509
|
|
|
5732
|
-
const snapshot = await getPackConsistencySnapshot(
|
|
5733
|
-
pack.id,
|
|
5734
|
-
payload?.set_cover === true ? asset.id : lockedPackRow.cover_sticker_id,
|
|
5735
|
-
connection,
|
|
5736
|
-
);
|
|
5510
|
+
const snapshot = await getPackConsistencySnapshot(pack.id, payload?.set_cover === true ? asset.id : lockedPackRow.cover_sticker_id, connection);
|
|
5737
5511
|
responseStickerCount = snapshot.sticker_count;
|
|
5738
5512
|
});
|
|
5739
5513
|
|
|
@@ -5762,9 +5536,7 @@ const handleUploadStickerToPackRequest = async (req, res, packKey) => {
|
|
|
5762
5536
|
} catch (error) {
|
|
5763
5537
|
if (reservedUpload?.id) {
|
|
5764
5538
|
await runSqlTransaction(async (connection) => {
|
|
5765
|
-
const currentUpload =
|
|
5766
|
-
(await findPackWebUploadByUploadId(pack.id, uploadId, connection)) ||
|
|
5767
|
-
(await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
|
|
5539
|
+
const currentUpload = (await findPackWebUploadByUploadId(pack.id, uploadId, connection)) || (await findPackWebUploadByStickerHash(pack.id, stickerHash, connection));
|
|
5768
5540
|
if (currentUpload) {
|
|
5769
5541
|
await updatePackWebUpload(
|
|
5770
5542
|
currentUpload.id,
|
|
@@ -5824,9 +5596,7 @@ const handlePackPublishStateRequest = async (req, res, packKey, url = null) => {
|
|
|
5824
5596
|
}
|
|
5825
5597
|
}
|
|
5826
5598
|
|
|
5827
|
-
const editTokenValue =
|
|
5828
|
-
(req.method === 'GET' || req.method === 'HEAD' ? String(url?.searchParams?.get('edit_token') || '') : '') ||
|
|
5829
|
-
String(payload?.edit_token || '');
|
|
5599
|
+
const editTokenValue = (req.method === 'GET' || req.method === 'HEAD' ? String(url?.searchParams?.get('edit_token') || '') : '') || String(payload?.edit_token || '');
|
|
5830
5600
|
const editToken = resolveWebPackEditToken(editTokenValue);
|
|
5831
5601
|
if (!editToken || editToken.packId !== pack.id || editToken.ownerJid !== pack.owner_jid) {
|
|
5832
5602
|
sendJson(req, res, 403, {
|
|
@@ -5901,12 +5671,7 @@ const handleFinalizePackRequest = async (req, res, packKey) => {
|
|
|
5901
5671
|
await setStickerPackStatus(pack.id, 'processing', connection);
|
|
5902
5672
|
|
|
5903
5673
|
const snapshot = await getPackConsistencySnapshot(pack.id, lockedPackRow.cover_sticker_id, connection);
|
|
5904
|
-
const canPublish =
|
|
5905
|
-
snapshot.sticker_count >= 1 &&
|
|
5906
|
-
snapshot.failed_uploads === 0 &&
|
|
5907
|
-
snapshot.processing_uploads === 0 &&
|
|
5908
|
-
snapshot.pending_uploads === 0 &&
|
|
5909
|
-
snapshot.cover_valid;
|
|
5674
|
+
const canPublish = snapshot.sticker_count >= 1 && snapshot.failed_uploads === 0 && snapshot.processing_uploads === 0 && snapshot.pending_uploads === 0 && snapshot.cover_valid;
|
|
5910
5675
|
|
|
5911
5676
|
if (canPublish) {
|
|
5912
5677
|
await setStickerPackStatus(pack.id, 'published', connection);
|
|
@@ -5922,18 +5687,7 @@ const handleFinalizePackRequest = async (req, res, packKey) => {
|
|
|
5922
5687
|
finalizeResult = {
|
|
5923
5688
|
canPublish: false,
|
|
5924
5689
|
packStatus: 'draft',
|
|
5925
|
-
reason:
|
|
5926
|
-
snapshot.failed_uploads > 0
|
|
5927
|
-
? 'failed_uploads'
|
|
5928
|
-
: snapshot.processing_uploads > 0
|
|
5929
|
-
? 'uploads_processing'
|
|
5930
|
-
: snapshot.pending_uploads > 0
|
|
5931
|
-
? 'uploads_pending'
|
|
5932
|
-
: !snapshot.cover_valid
|
|
5933
|
-
? 'cover_missing'
|
|
5934
|
-
: snapshot.sticker_count < 1
|
|
5935
|
-
? 'not_enough_stickers'
|
|
5936
|
-
: 'inconsistent',
|
|
5690
|
+
reason: snapshot.failed_uploads > 0 ? 'failed_uploads' : snapshot.processing_uploads > 0 ? 'uploads_processing' : snapshot.pending_uploads > 0 ? 'uploads_pending' : !snapshot.cover_valid ? 'cover_missing' : snapshot.sticker_count < 1 ? 'not_enough_stickers' : 'inconsistent',
|
|
5937
5691
|
};
|
|
5938
5692
|
});
|
|
5939
5693
|
} catch (error) {
|
|
@@ -6004,13 +5758,7 @@ const handleCreatorRankingRequest = async (req, res, url) => {
|
|
|
6004
5758
|
const limit = clampInt(url.searchParams.get('limit'), 50, 5, 200);
|
|
6005
5759
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
6006
5760
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
6007
|
-
const cacheKey = buildCacheKey([
|
|
6008
|
-
'creator_ranking',
|
|
6009
|
-
visibility,
|
|
6010
|
-
q,
|
|
6011
|
-
limit,
|
|
6012
|
-
hasNsfwAccess ? 1 : 0,
|
|
6013
|
-
]);
|
|
5761
|
+
const cacheKey = buildCacheKey(['creator_ranking', visibility, q, limit, hasNsfwAccess ? 1 : 0]);
|
|
6014
5762
|
const payload = await getCachedSnapshot({
|
|
6015
5763
|
cacheMap: CATALOG_CREATOR_RANKING_CACHE,
|
|
6016
5764
|
key: cacheKey,
|
|
@@ -6026,20 +5774,11 @@ const handleCreatorRankingRequest = async (req, res, url) => {
|
|
|
6026
5774
|
});
|
|
6027
5775
|
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
6028
5776
|
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
6029
|
-
const ranking = buildCreatorRanking(
|
|
6030
|
-
STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries,
|
|
6031
|
-
{ limit },
|
|
6032
|
-
);
|
|
5777
|
+
const ranking = buildCreatorRanking(STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries, { limit });
|
|
6033
5778
|
|
|
6034
5779
|
return {
|
|
6035
5780
|
data: ranking.map((creator) => ({
|
|
6036
|
-
creator_score: Number(
|
|
6037
|
-
(
|
|
6038
|
-
Number(creator.avg_pack_score || 0) * 0.45 +
|
|
6039
|
-
Number(creator.total_likes || 0) * 0.0008 +
|
|
6040
|
-
Number(creator.total_opens || 0) * 0.00015
|
|
6041
|
-
).toFixed(6),
|
|
6042
|
-
),
|
|
5781
|
+
creator_score: Number((Number(creator.avg_pack_score || 0) * 0.45 + Number(creator.total_likes || 0) * 0.0008 + Number(creator.total_opens || 0) * 0.00015).toFixed(6)),
|
|
6043
5782
|
publisher: creator.publisher,
|
|
6044
5783
|
verified: Boolean(creator.verified),
|
|
6045
5784
|
badges: creator.verified ? ['verified_creator'] : [],
|
|
@@ -6080,12 +5819,8 @@ const handleRecommendationsRequest = async (req, res, url) => {
|
|
|
6080
5819
|
});
|
|
6081
5820
|
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
6082
5821
|
const { entries, packClassificationById } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
6083
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
6084
|
-
|
|
6085
|
-
: entries;
|
|
6086
|
-
const entriesByCategory = categories.length
|
|
6087
|
-
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
6088
|
-
: entriesClassified;
|
|
5822
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries;
|
|
5823
|
+
const entriesByCategory = categories.length ? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories)) : entriesClassified;
|
|
6089
5824
|
|
|
6090
5825
|
const viewerRecentPackIds = viewerKey ? await listViewerRecentPackIds(viewerKey, { days: 45, limit: 160 }) : [];
|
|
6091
5826
|
const viewerAffinity = buildViewerTagAffinity({
|
|
@@ -6130,27 +5865,20 @@ const handleOrphanStickerListRequest = async (req, res, url) => {
|
|
|
6130
5865
|
|
|
6131
5866
|
const { assets, hasMore, total } = categories.length
|
|
6132
5867
|
? await listClassifiedOrphanAssetsByCategories({ search: q, categories, limit, offset })
|
|
6133
|
-
: await (
|
|
6134
|
-
STICKER_CATALOG_ONLY_CLASSIFIED ? listClassifiedStickerAssetsWithoutPack : listStickerAssetsWithoutPack
|
|
6135
|
-
)({
|
|
5868
|
+
: await (STICKER_CATALOG_ONLY_CLASSIFIED ? listClassifiedStickerAssetsWithoutPack : listStickerAssetsWithoutPack)({
|
|
6136
5869
|
search: q,
|
|
6137
5870
|
limit,
|
|
6138
5871
|
offset,
|
|
6139
5872
|
});
|
|
6140
5873
|
const classifications = await listStickerClassificationsByAssetIds(assets.map((asset) => asset.id));
|
|
6141
5874
|
const byAssetId = new Map(classifications.map((entry) => [entry.asset_id, entry]));
|
|
6142
|
-
const filteredAssets = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
6143
|
-
|
|
6144
|
-
: assets;
|
|
6145
|
-
const filteredByCategories = categories.length
|
|
6146
|
-
? filteredAssets.filter((asset) => hasAnyCategory(resolveClassificationTags(byAssetId.get(asset.id)), categories))
|
|
6147
|
-
: filteredAssets;
|
|
5875
|
+
const filteredAssets = STICKER_CATALOG_ONLY_CLASSIFIED ? assets.filter((asset) => isStickerClassified(byAssetId.get(asset.id))) : assets;
|
|
5876
|
+
const filteredByCategories = categories.length ? filteredAssets.filter((asset) => hasAnyCategory(resolveClassificationTags(byAssetId.get(asset.id)), categories)) : filteredAssets;
|
|
6148
5877
|
const currentPage = Math.floor(offset / limit) + 1;
|
|
6149
5878
|
const totalPages = Math.max(1, Math.ceil(total / limit));
|
|
6150
5879
|
|
|
6151
5880
|
sendJson(req, res, 200, {
|
|
6152
|
-
data: filteredByCategories.map((asset) =>
|
|
6153
|
-
mapOrphanStickerAsset(asset, byAssetId.get(asset.id) || null, { hideSensitiveAssets: !hasNsfwAccess })),
|
|
5881
|
+
data: filteredByCategories.map((asset) => mapOrphanStickerAsset(asset, byAssetId.get(asset.id) || null, { hideSensitiveAssets: !hasNsfwAccess })),
|
|
6154
5882
|
pagination: {
|
|
6155
5883
|
limit,
|
|
6156
5884
|
offset,
|
|
@@ -6174,11 +5902,7 @@ const handleDataFileListRequest = async (req, res, url) => {
|
|
|
6174
5902
|
const normalizedQuery = q.toLowerCase();
|
|
6175
5903
|
|
|
6176
5904
|
const allFiles = await listDataImageFiles();
|
|
6177
|
-
const filteredFiles = normalizedQuery
|
|
6178
|
-
? allFiles.filter(
|
|
6179
|
-
(item) => item.name.toLowerCase().includes(normalizedQuery) || item.relative_path.toLowerCase().includes(normalizedQuery),
|
|
6180
|
-
)
|
|
6181
|
-
: allFiles;
|
|
5905
|
+
const filteredFiles = normalizedQuery ? allFiles.filter((item) => item.name.toLowerCase().includes(normalizedQuery) || item.relative_path.toLowerCase().includes(normalizedQuery)) : allFiles;
|
|
6182
5906
|
|
|
6183
5907
|
const page = filteredFiles.slice(offset, offset + limit);
|
|
6184
5908
|
const hasMore = offset + limit < filteredFiles.length;
|
|
@@ -6214,11 +5938,7 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
6214
5938
|
const botJid = resolveBotJid(activeSocket?.user?.id) || null;
|
|
6215
5939
|
const botPhone = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '') || null;
|
|
6216
5940
|
const botConnected = Boolean(botJid) && socketReadyState === 1;
|
|
6217
|
-
const botConnectionStatus = botConnected
|
|
6218
|
-
? 'online'
|
|
6219
|
-
: socketReadyState === 0
|
|
6220
|
-
? 'connecting'
|
|
6221
|
-
: 'offline';
|
|
5941
|
+
const botConnectionStatus = botConnected ? 'online' : socketReadyState === 0 ? 'connecting' : 'offline';
|
|
6222
5942
|
|
|
6223
5943
|
let platform = {
|
|
6224
5944
|
total_users: null,
|
|
@@ -6421,16 +6141,7 @@ const extractCommandsFromMenuLine = (line, commandPrefix) => {
|
|
|
6421
6141
|
};
|
|
6422
6142
|
|
|
6423
6143
|
const collectAvailableMenuCommands = (commandPrefix = README_COMMAND_PREFIX) => {
|
|
6424
|
-
const sections = [
|
|
6425
|
-
buildMenuCaption('OmniZap', commandPrefix),
|
|
6426
|
-
buildStickerMenu(commandPrefix),
|
|
6427
|
-
buildMediaMenu(commandPrefix),
|
|
6428
|
-
buildQuoteMenu(commandPrefix),
|
|
6429
|
-
buildAnimeMenu(commandPrefix),
|
|
6430
|
-
buildAiMenu(commandPrefix),
|
|
6431
|
-
buildStatsMenu(commandPrefix),
|
|
6432
|
-
buildAdminMenu(commandPrefix),
|
|
6433
|
-
];
|
|
6144
|
+
const sections = [buildMenuCaption('OmniZap', commandPrefix), buildStickerMenu(commandPrefix), buildMediaMenu(commandPrefix), buildQuoteMenu(commandPrefix), buildAnimeMenu(commandPrefix), buildAiMenu(commandPrefix), buildStatsMenu(commandPrefix), buildAdminMenu(commandPrefix)];
|
|
6434
6145
|
|
|
6435
6146
|
const commands = new Set();
|
|
6436
6147
|
for (const section of sections) {
|
|
@@ -6446,49 +6157,15 @@ const collectAvailableMenuCommands = (commandPrefix = README_COMMAND_PREFIX) =>
|
|
|
6446
6157
|
};
|
|
6447
6158
|
|
|
6448
6159
|
const renderReadmeSnapshotMarkdown = ({ generatedAt, totals, topMessageTypes, commands }) => {
|
|
6449
|
-
const typeRows = topMessageTypes.length
|
|
6450
|
-
? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`)
|
|
6451
|
-
: ['| `outros` | 0 |'];
|
|
6160
|
+
const typeRows = topMessageTypes.length ? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`) : ['| `outros` | 0 |'];
|
|
6452
6161
|
|
|
6453
6162
|
const commandInline = commands.length ? commands.map((command) => `\`${command}\``).join(' · ') : 'Nenhum comando identificado no menu atual.';
|
|
6454
6163
|
|
|
6455
|
-
return [
|
|
6456
|
-
'### Snapshot do Sistema',
|
|
6457
|
-
'',
|
|
6458
|
-
`> Atualizado em \`${generatedAt}\` | cache \`${README_SUMMARY_CACHE_SECONDS}s\``,
|
|
6459
|
-
'',
|
|
6460
|
-
'| Métrica | Valor |',
|
|
6461
|
-
'| --- | ---: |',
|
|
6462
|
-
`| Usuários (lid_map) | ${formatPtBrInteger(totals.total_users)} |`,
|
|
6463
|
-
`| Grupos | ${formatPtBrInteger(totals.total_groups)} |`,
|
|
6464
|
-
`| Packs | ${formatPtBrInteger(totals.total_packs)} |`,
|
|
6465
|
-
`| Stickers | ${formatPtBrInteger(totals.total_stickers)} |`,
|
|
6466
|
-
`| Mensagens registradas | ${formatPtBrInteger(totals.total_messages)} |`,
|
|
6467
|
-
'',
|
|
6468
|
-
`#### Tipos de mensagem mais usados (amostra: ${formatPtBrInteger(totals.message_types_sample_size)})`,
|
|
6469
|
-
'| Tipo | Total |',
|
|
6470
|
-
'| --- | ---: |',
|
|
6471
|
-
...typeRows,
|
|
6472
|
-
'',
|
|
6473
|
-
`<details><summary>Comandos disponíveis (${formatPtBrInteger(commands.length)})</summary>`,
|
|
6474
|
-
'',
|
|
6475
|
-
commandInline,
|
|
6476
|
-
'',
|
|
6477
|
-
'</details>',
|
|
6478
|
-
'',
|
|
6479
|
-
].join('\n');
|
|
6164
|
+
return ['### Snapshot do Sistema', '', `> Atualizado em \`${generatedAt}\` | cache \`${README_SUMMARY_CACHE_SECONDS}s\``, '', '| Métrica | Valor |', '| --- | ---: |', `| Usuários (lid_map) | ${formatPtBrInteger(totals.total_users)} |`, `| Grupos | ${formatPtBrInteger(totals.total_groups)} |`, `| Packs | ${formatPtBrInteger(totals.total_packs)} |`, `| Stickers | ${formatPtBrInteger(totals.total_stickers)} |`, `| Mensagens registradas | ${formatPtBrInteger(totals.total_messages)} |`, '', `#### Tipos de mensagem mais usados (amostra: ${formatPtBrInteger(totals.message_types_sample_size)})`, '| Tipo | Total |', '| --- | ---: |', ...typeRows, '', `<details><summary>Comandos disponíveis (${formatPtBrInteger(commands.length)})</summary>`, '', commandInline, '', '</details>', ''].join('\n');
|
|
6480
6165
|
};
|
|
6481
6166
|
|
|
6482
6167
|
const buildReadmeSummarySnapshot = async () => {
|
|
6483
|
-
const [
|
|
6484
|
-
lidMapTotalsRows,
|
|
6485
|
-
chatsTotalsRows,
|
|
6486
|
-
groupsMetadataTotalsRows,
|
|
6487
|
-
packTotalsRows,
|
|
6488
|
-
stickerTotalsRows,
|
|
6489
|
-
messageTotalsRows,
|
|
6490
|
-
messageTypeRows,
|
|
6491
|
-
] = await Promise.all([
|
|
6168
|
+
const [lidMapTotalsRows, chatsTotalsRows, groupsMetadataTotalsRows, packTotalsRows, stickerTotalsRows, messageTotalsRows, messageTypeRows] = await Promise.all([
|
|
6492
6169
|
executeQuery(`SELECT COUNT(*) AS total_users FROM ${TABLES.LID_MAP}`),
|
|
6493
6170
|
executeQuery(
|
|
6494
6171
|
`SELECT
|
|
@@ -6648,12 +6325,7 @@ const resolveBotUserCandidates = (activeSocket) => {
|
|
|
6648
6325
|
const botPhoneFromCatalog = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
|
|
6649
6326
|
if (botPhoneFromCatalog) candidates.add(botPhoneFromCatalog);
|
|
6650
6327
|
|
|
6651
|
-
const envCandidates = [
|
|
6652
|
-
process.env.WHATSAPP_BOT_NUMBER,
|
|
6653
|
-
process.env.BOT_NUMBER,
|
|
6654
|
-
process.env.PHONE_NUMBER,
|
|
6655
|
-
process.env.BOT_PHONE_NUMBER,
|
|
6656
|
-
];
|
|
6328
|
+
const envCandidates = [process.env.WHATSAPP_BOT_NUMBER, process.env.BOT_NUMBER, process.env.PHONE_NUMBER, process.env.BOT_PHONE_NUMBER];
|
|
6657
6329
|
|
|
6658
6330
|
for (const candidate of envCandidates) {
|
|
6659
6331
|
const digits = String(candidate || '').replace(/\D+/g, '');
|
|
@@ -6944,15 +6616,7 @@ const buildMarketplaceGlobalStatsSnapshot = async () => {
|
|
|
6944
6616
|
const dayKeys = buildLastSevenUtcDateKeys();
|
|
6945
6617
|
const dayFilterSql = `UTC_DATE() - INTERVAL 6 DAY`;
|
|
6946
6618
|
|
|
6947
|
-
const [
|
|
6948
|
-
packTotalsRows,
|
|
6949
|
-
stickerTotalsRows,
|
|
6950
|
-
stickersWithoutPackRows,
|
|
6951
|
-
engagementTotalsRows,
|
|
6952
|
-
dailyPacksRows,
|
|
6953
|
-
dailyStickersRows,
|
|
6954
|
-
dailyInteractionRows,
|
|
6955
|
-
] = await Promise.all([
|
|
6619
|
+
const [packTotalsRows, stickerTotalsRows, stickersWithoutPackRows, engagementTotalsRows, dailyPacksRows, dailyStickersRows, dailyInteractionRows] = await Promise.all([
|
|
6956
6620
|
executeQuery(
|
|
6957
6621
|
`SELECT
|
|
6958
6622
|
COUNT(*) AS total_packs,
|
|
@@ -7027,7 +6691,9 @@ const buildMarketplaceGlobalStatsSnapshot = async () => {
|
|
|
7027
6691
|
const dailyLikesByDay = new Map();
|
|
7028
6692
|
(Array.isArray(dailyInteractionRows) ? dailyInteractionRows : []).forEach((row) => {
|
|
7029
6693
|
const dayKey = toUtcDayKey(row?.day_key);
|
|
7030
|
-
const interaction = String(row?.interaction || '')
|
|
6694
|
+
const interaction = String(row?.interaction || '')
|
|
6695
|
+
.trim()
|
|
6696
|
+
.toLowerCase();
|
|
7031
6697
|
const total = Number(row?.total || 0);
|
|
7032
6698
|
if (!dayKey) return;
|
|
7033
6699
|
if (interaction === 'open') dailyOpensByDay.set(dayKey, total);
|
|
@@ -7161,13 +6827,16 @@ const handlePublicDataAssetRequest = async (req, res, pathname) => {
|
|
|
7161
6827
|
return true;
|
|
7162
6828
|
}
|
|
7163
6829
|
|
|
7164
|
-
const decodedSegments = suffix
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
6830
|
+
const decodedSegments = suffix
|
|
6831
|
+
.split('/')
|
|
6832
|
+
.filter(Boolean)
|
|
6833
|
+
.map((segment) => {
|
|
6834
|
+
try {
|
|
6835
|
+
return decodeURIComponent(segment);
|
|
6836
|
+
} catch {
|
|
6837
|
+
return segment;
|
|
6838
|
+
}
|
|
6839
|
+
});
|
|
7171
6840
|
|
|
7172
6841
|
const relativePath = normalizeRelativePath(decodedSegments.join('/'));
|
|
7173
6842
|
if (!relativePath || relativePath.includes('..') || !isAllowedDataImageFile(relativePath)) {
|
|
@@ -7215,11 +6884,7 @@ const fetchPublicPackPayload = async (normalizedPackKey) => {
|
|
|
7215
6884
|
|
|
7216
6885
|
const items = await listStickerPackItems(pack.id);
|
|
7217
6886
|
const stickerIds = items.map((item) => item.sticker_id);
|
|
7218
|
-
const [classifications, packClassification, engagement] = await Promise.all([
|
|
7219
|
-
listStickerClassificationsByAssetIds(stickerIds),
|
|
7220
|
-
getPackClassificationSummaryByAssetIds(stickerIds),
|
|
7221
|
-
getStickerPackEngagementByPackId(pack.id),
|
|
7222
|
-
]);
|
|
6887
|
+
const [classifications, packClassification, engagement] = await Promise.all([listStickerClassificationsByAssetIds(stickerIds), getPackClassificationSummaryByAssetIds(stickerIds), getStickerPackEngagementByPackId(pack.id)]);
|
|
7223
6888
|
|
|
7224
6889
|
if (STICKER_CATALOG_ONLY_CLASSIFIED && !isPackClassified(packClassification)) {
|
|
7225
6890
|
return null;
|
|
@@ -7275,12 +6940,8 @@ const handleDetailsRequest = async (req, res, packKey, url) => {
|
|
|
7275
6940
|
}
|
|
7276
6941
|
|
|
7277
6942
|
const { pack, items, byAssetClassification, packClassification, engagement, signals } = packPayload;
|
|
7278
|
-
const visibleItems = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
7279
|
-
|
|
7280
|
-
: items;
|
|
7281
|
-
const visibleItemsByCategories = categories.length
|
|
7282
|
-
? visibleItems.filter((item) => hasAnyCategory(resolveClassificationTags(byAssetClassification.get(item.sticker_id)), categories))
|
|
7283
|
-
: visibleItems;
|
|
6943
|
+
const visibleItems = STICKER_CATALOG_ONLY_CLASSIFIED ? items.filter((item) => isStickerClassified(byAssetClassification.get(item.sticker_id))) : items;
|
|
6944
|
+
const visibleItemsByCategories = categories.length ? visibleItems.filter((item) => hasAnyCategory(resolveClassificationTags(byAssetClassification.get(item.sticker_id)), categories)) : visibleItems;
|
|
7284
6945
|
|
|
7285
6946
|
sendJson(req, res, 200, {
|
|
7286
6947
|
data: mapPackDetails(pack, visibleItemsByCategories, {
|
|
@@ -7320,9 +6981,7 @@ const handleAssetRequest = async (req, res, packKey, stickerToken) => {
|
|
|
7320
6981
|
try {
|
|
7321
6982
|
const buffer = await readStickerAssetBuffer(item.asset);
|
|
7322
6983
|
const classification = await findStickerClassificationByAssetId(normalizedStickerId).catch(() => null);
|
|
7323
|
-
const packClassification = stickerIds.length
|
|
7324
|
-
? await getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null)
|
|
7325
|
-
: null;
|
|
6984
|
+
const packClassification = stickerIds.length ? await getPackClassificationSummaryByAssetIds(stickerIds).catch(() => null) : null;
|
|
7326
6985
|
if (STICKER_CATALOG_ONLY_CLASSIFIED && !isStickerClassified(classification)) {
|
|
7327
6986
|
sendJson(req, res, 404, { error: 'Sticker nao encontrado.' });
|
|
7328
6987
|
return;
|
|
@@ -7653,23 +7312,7 @@ const handleAdminOverviewRequest = async (req, res) => {
|
|
|
7653
7312
|
return;
|
|
7654
7313
|
}
|
|
7655
7314
|
|
|
7656
|
-
const [
|
|
7657
|
-
marketplaceStats,
|
|
7658
|
-
activeSessions,
|
|
7659
|
-
knownUsers,
|
|
7660
|
-
bans,
|
|
7661
|
-
packsCountRows,
|
|
7662
|
-
stickersCountRows,
|
|
7663
|
-
recentPacks,
|
|
7664
|
-
] = await Promise.all([
|
|
7665
|
-
getMarketplaceGlobalStatsCached().catch(() => null),
|
|
7666
|
-
listAdminActiveGoogleWebSessions({ limit: 50 }),
|
|
7667
|
-
listAdminKnownGoogleUsers({ limit: 50 }),
|
|
7668
|
-
listAdminBans({ activeOnly: true, limit: 50 }),
|
|
7669
|
-
executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_PACK} WHERE deleted_at IS NULL`),
|
|
7670
|
-
executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_ASSET}`),
|
|
7671
|
-
listAdminPacks({ searchParams: new URLSearchParams([['limit', '20']]) }),
|
|
7672
|
-
]);
|
|
7315
|
+
const [marketplaceStats, activeSessions, knownUsers, bans, packsCountRows, stickersCountRows, recentPacks] = await Promise.all([getMarketplaceGlobalStatsCached().catch(() => null), listAdminActiveGoogleWebSessions({ limit: 50 }), listAdminKnownGoogleUsers({ limit: 50 }), listAdminBans({ activeOnly: true, limit: 50 }), executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_PACK} WHERE deleted_at IS NULL`), executeQuery(`SELECT COUNT(*) AS total FROM ${TABLES.STICKER_ASSET}`), listAdminPacks({ searchParams: new URLSearchParams([['limit', '20']]) })]);
|
|
7673
7316
|
|
|
7674
7317
|
sendJson(req, res, 200, {
|
|
7675
7318
|
data: {
|
|
@@ -7698,10 +7341,7 @@ const handleAdminUsersRequest = async (req, res, url) => {
|
|
|
7698
7341
|
return;
|
|
7699
7342
|
}
|
|
7700
7343
|
const limit = Math.max(1, Math.min(500, Number(url?.searchParams?.get('limit') || 200)));
|
|
7701
|
-
const [activeSessions, users] = await Promise.all([
|
|
7702
|
-
listAdminActiveGoogleWebSessions({ limit }),
|
|
7703
|
-
listAdminKnownGoogleUsers({ limit }),
|
|
7704
|
-
]);
|
|
7344
|
+
const [activeSessions, users] = await Promise.all([listAdminActiveGoogleWebSessions({ limit }), listAdminKnownGoogleUsers({ limit })]);
|
|
7705
7345
|
sendJson(req, res, 200, { data: { active_sessions: activeSessions, users } });
|
|
7706
7346
|
};
|
|
7707
7347
|
|
|
@@ -8022,8 +7662,7 @@ const catalogApiRouter = createCatalogApiRouter({
|
|
|
8022
7662
|
},
|
|
8023
7663
|
});
|
|
8024
7664
|
|
|
8025
|
-
const handleCatalogApiRequest = async (req, res, pathname, url) =>
|
|
8026
|
-
catalogApiRouter({ req, res, pathname, url });
|
|
7665
|
+
const handleCatalogApiRequest = async (req, res, pathname, url) => catalogApiRouter({ req, res, pathname, url });
|
|
8027
7666
|
|
|
8028
7667
|
const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
8029
7668
|
const normalizedPath = pathname.length > 1 ? pathname.replace(/\/+$/, '') : pathname;
|
|
@@ -8082,7 +7721,9 @@ const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
|
8082
7721
|
}
|
|
8083
7722
|
|
|
8084
7723
|
const initialPackKey = extractPackKeyFromWebPath(pathname);
|
|
8085
|
-
const initialPackKeyNormalized = String(initialPackKey || '')
|
|
7724
|
+
const initialPackKeyNormalized = String(initialPackKey || '')
|
|
7725
|
+
.trim()
|
|
7726
|
+
.toLowerCase();
|
|
8086
7727
|
const shouldRenderPackSeoPage = Boolean(initialPackKey && !PACK_PAGE_ROUTE_EXCLUSIONS.has(initialPackKeyNormalized));
|
|
8087
7728
|
|
|
8088
7729
|
if (shouldRenderPackSeoPage) {
|