@kaikybrofc/omnizap-system 2.2.3 → 2.2.5
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/.env.example +13 -0
- package/README.md +29 -85
- package/app/controllers/messageController.js +133 -1
- package/app/modules/stickerPackModule/catalogHandlers/catalogAdminHttp.js +68 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogAuthHttp.js +34 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogPublicHttp.js +179 -0
- package/app/modules/stickerPackModule/catalogHandlers/catalogUploadHttp.js +92 -0
- package/app/modules/stickerPackModule/catalogRouter.js +79 -0
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +243 -0
- package/app/modules/stickerPackModule/domainEvents.js +61 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +21 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +19 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +55 -15
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +238 -0
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +71 -0
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +198 -0
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +1090 -659
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +19 -1
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +44 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +18 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +51 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +191 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +301 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +111 -10
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +21 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +59 -7
- package/app/observability/metrics.js +169 -0
- package/app/services/featureFlagService.js +137 -0
- package/app/services/lidMapService.js +4 -1
- package/app/services/whatsappLoginLinkService.js +232 -0
- package/database/index.js +5 -0
- package/database/migrations/20260228_0021_sticker_web_google_owner_phone.sql +83 -0
- package/database/migrations/20260228_0022_sticker_scale_indexes.sql +16 -0
- package/database/migrations/20260228_0023_sticker_pack_score_snapshot.sql +25 -0
- package/database/migrations/20260228_0024_domain_event_outbox.sql +42 -0
- package/database/migrations/20260228_0025_sticker_worker_task_idempotency_dlq.sql +23 -0
- package/database/migrations/20260228_0026_feature_flags.sql +21 -0
- package/ecosystem.prod.config.cjs +70 -9
- package/index.js +26 -0
- package/package.json +5 -1
- package/public/index.html +128 -10
- package/public/js/apps/createPackApp.js +59 -272
- package/public/js/apps/homeApp.js +106 -0
- package/public/js/apps/loginApp.js +459 -0
- package/public/js/apps/stickersApp.js +34 -37
- package/public/js/apps/userApp.js +244 -0
- package/public/js/runtime/react-runtime.js +1 -0
- package/public/login/index.html +333 -0
- package/public/stickers/create/index.html +2 -1
- package/public/stickers/index.html +2 -1
- package/public/user/index.html +367 -0
- package/scripts/cache-bust.mjs +65 -11
- package/scripts/sticker-catalog-loadtest.mjs +208 -0
- package/scripts/sticker-worker-task.mjs +122 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
4
|
+
import { URL, URLSearchParams } from 'node:url';
|
|
4
5
|
import axios from 'axios';
|
|
5
6
|
|
|
6
7
|
import { executeQuery, pool, TABLES } from '../../../database/index.js';
|
|
@@ -8,6 +9,7 @@ import { getJidUser, normalizeJid, resolveBotJid } from '../../config/baileysCon
|
|
|
8
9
|
import { getAdminPhone, getAdminRawValue, resolveAdminJid } from '../../config/adminIdentity.js';
|
|
9
10
|
import { getActiveSocket } from '../../services/socketState.js';
|
|
10
11
|
import { extractUserIdInfo, resolveUserId } from '../../services/lidMapService.js';
|
|
12
|
+
import { resolveWhatsAppOwnerJidFromLoginPayload, toWhatsAppOwnerJid, toWhatsAppPhoneDigits } from '../../services/whatsappLoginLinkService.js';
|
|
11
13
|
import logger from '../../utils/logger/loggerModule.js';
|
|
12
14
|
import { getSystemMetrics } from '../../utils/systemMetrics/systemMetricsModule.js';
|
|
13
15
|
import {
|
|
@@ -63,6 +65,8 @@ import {
|
|
|
63
65
|
buildViewerTagAffinity,
|
|
64
66
|
computePackSignals,
|
|
65
67
|
} from './stickerPackMarketplaceService.js';
|
|
68
|
+
import { listStickerPackScoreSnapshotsByPackIds } from './stickerPackScoreSnapshotRepository.js';
|
|
69
|
+
import { createCatalogApiRouter } from './catalogRouter.js';
|
|
66
70
|
import {
|
|
67
71
|
buildAdminMenu,
|
|
68
72
|
buildAiMenu,
|
|
@@ -74,11 +78,17 @@ import {
|
|
|
74
78
|
buildStickerMenu,
|
|
75
79
|
} from '../menuModule/common.js';
|
|
76
80
|
import { getMarketplaceDriftSnapshot } from './stickerMarketplaceDriftService.js';
|
|
77
|
-
import {
|
|
81
|
+
import {
|
|
82
|
+
getStickerAssetExternalUrl,
|
|
83
|
+
getStickerStorageConfig,
|
|
84
|
+
readStickerAssetBuffer,
|
|
85
|
+
saveStickerAssetFromBuffer,
|
|
86
|
+
} from './stickerStorageService.js';
|
|
78
87
|
import { convertToWebp } from '../stickerModule/convertToWebp.js';
|
|
79
88
|
import { sanitizeText } from './stickerPackUtils.js';
|
|
80
89
|
import stickerPackService from './stickerPackServiceRuntime.js';
|
|
81
90
|
import { STICKER_PACK_ERROR_CODES, StickerPackError } from './stickerPackErrors.js';
|
|
91
|
+
import { isFeatureEnabled } from '../../services/featureFlagService.js';
|
|
82
92
|
|
|
83
93
|
const parseEnvBool = (value, fallback) => {
|
|
84
94
|
if (value === undefined || value === null || value === '') return fallback;
|
|
@@ -143,12 +153,15 @@ const STICKER_API_BASE_PATH = normalizeBasePath(process.env.STICKER_API_BASE_PAT
|
|
|
143
153
|
const STICKER_ORPHAN_API_PATH = `${STICKER_API_BASE_PATH}/orphan-stickers`;
|
|
144
154
|
const STICKER_CREATE_WEB_PATH = `${STICKER_WEB_PATH}/create`;
|
|
145
155
|
const STICKER_ADMIN_WEB_PATH = `${STICKER_WEB_PATH}/admin`;
|
|
156
|
+
const STICKER_LOGIN_WEB_PATH = normalizeBasePath(process.env.STICKER_LOGIN_WEB_PATH, '/login');
|
|
157
|
+
const USER_PROFILE_WEB_PATH = normalizeBasePath(process.env.USER_PROFILE_WEB_PATH, '/user');
|
|
146
158
|
const STICKER_DATA_PUBLIC_PATH = normalizeBasePath(process.env.STICKER_DATA_PUBLIC_PATH, '/data');
|
|
147
159
|
const STICKER_DATA_PUBLIC_DIR = path.resolve(process.env.STICKER_DATA_PUBLIC_DIR || path.join(process.cwd(), 'data'));
|
|
148
160
|
const CATALOG_PUBLIC_DIR = path.resolve(process.cwd(), 'public');
|
|
149
161
|
const CATALOG_TEMPLATE_PATH = path.join(CATALOG_PUBLIC_DIR, 'stickers', 'index.html');
|
|
150
162
|
const CREATE_PACK_TEMPLATE_PATH = path.join(CATALOG_PUBLIC_DIR, 'stickers', 'create', 'index.html');
|
|
151
163
|
const ADMIN_PANEL_TEMPLATE_PATH = path.join(CATALOG_PUBLIC_DIR, 'stickers', 'admin', 'index.html');
|
|
164
|
+
const USER_DASHBOARD_TEMPLATE_PATH = path.join(CATALOG_PUBLIC_DIR, 'user', 'index.html');
|
|
152
165
|
const CATALOG_STYLES_FILE_PATH = path.join(CATALOG_PUBLIC_DIR, 'css', 'styles.css');
|
|
153
166
|
const CATALOG_SCRIPT_FILE_PATH = path.join(CATALOG_PUBLIC_DIR, 'js', 'catalog.js');
|
|
154
167
|
const DEFAULT_LIST_LIMIT = clampInt(process.env.STICKER_WEB_LIST_LIMIT, 24, 1, 60);
|
|
@@ -199,6 +212,19 @@ const GITHUB_REPOSITORY = String(process.env.GITHUB_REPOSITORY || 'Kaikygr/omniz
|
|
|
199
212
|
const GITHUB_TOKEN = String(process.env.GITHUB_TOKEN || '').trim();
|
|
200
213
|
const GITHUB_PROJECT_CACHE_SECONDS = clampInt(process.env.GITHUB_PROJECT_CACHE_SECONDS, 300, 30, 3600);
|
|
201
214
|
const GLOBAL_RANK_REFRESH_SECONDS = clampInt(process.env.GLOBAL_RANK_REFRESH_SECONDS, 600, 60, 3600);
|
|
215
|
+
const CATALOG_LIST_CACHE_SECONDS = clampInt(process.env.STICKER_CATALOG_LIST_CACHE_SECONDS, 90, 15, 900);
|
|
216
|
+
const CATALOG_CREATOR_RANKING_CACHE_SECONDS = clampInt(
|
|
217
|
+
process.env.STICKER_CATALOG_CREATOR_RANKING_CACHE_SECONDS,
|
|
218
|
+
120,
|
|
219
|
+
15,
|
|
220
|
+
900,
|
|
221
|
+
);
|
|
222
|
+
const CATALOG_PACK_PAYLOAD_CACHE_SECONDS = clampInt(
|
|
223
|
+
process.env.STICKER_CATALOG_PACK_PAYLOAD_CACHE_SECONDS,
|
|
224
|
+
300,
|
|
225
|
+
30,
|
|
226
|
+
1800,
|
|
227
|
+
);
|
|
202
228
|
const MARKETPLACE_GLOBAL_STATS_API_PATH = '/api/marketplace/stats';
|
|
203
229
|
const MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS = clampInt(process.env.MARKETPLACE_GLOBAL_STATS_CACHE_SECONDS, 45, 30, 60);
|
|
204
230
|
const HOME_MARKETPLACE_STATS_CACHE_SECONDS = clampInt(process.env.HOME_MARKETPLACE_STATS_CACHE_SECONDS, 45, 10, 300);
|
|
@@ -214,6 +240,14 @@ const SITE_CANONICAL_REDIRECT_ENABLED = parseEnvBool(process.env.SITE_CANONICAL_
|
|
|
214
240
|
const SITE_ORIGIN = String(process.env.SITE_ORIGIN || `${SITE_CANONICAL_SCHEME}://${SITE_CANONICAL_HOST}`)
|
|
215
241
|
.trim()
|
|
216
242
|
.replace(/\/+$/, '');
|
|
243
|
+
const SITE_COOKIE_DOMAIN = String(process.env.SITE_COOKIE_DOMAIN || SITE_CANONICAL_HOST)
|
|
244
|
+
.trim()
|
|
245
|
+
.toLowerCase()
|
|
246
|
+
.replace(/^https?:\/\//, '')
|
|
247
|
+
.split('/')[0]
|
|
248
|
+
.split(':')[0]
|
|
249
|
+
.replace(/^\.+/, '')
|
|
250
|
+
.replace(/\.+$/, '');
|
|
217
251
|
const SITEMAP_MAX_PACKS = clampInt(process.env.STICKER_SITEMAP_MAX_PACKS, 45000, 100, 50000);
|
|
218
252
|
const SITEMAP_CACHE_SECONDS = clampInt(process.env.STICKER_SITEMAP_CACHE_SECONDS, 180, 30, 3600);
|
|
219
253
|
const SEO_DISCOVERY_LINK_LIMIT = clampInt(process.env.STICKER_SEO_DISCOVERY_LINK_LIMIT, 60, 10, 200);
|
|
@@ -291,6 +325,9 @@ const MARKETPLACE_GLOBAL_STATS_CACHE = {
|
|
|
291
325
|
pending: null,
|
|
292
326
|
};
|
|
293
327
|
const HOME_MARKETPLACE_STATS_CACHE = new Map();
|
|
328
|
+
const CATALOG_LIST_CACHE = new Map();
|
|
329
|
+
const CATALOG_CREATOR_RANKING_CACHE = new Map();
|
|
330
|
+
const CATALOG_PACK_PAYLOAD_CACHE = new Map();
|
|
294
331
|
const SYSTEM_SUMMARY_CACHE = {
|
|
295
332
|
expiresAt: 0,
|
|
296
333
|
value: null,
|
|
@@ -320,6 +357,69 @@ const formatDuration = (totalSeconds) => {
|
|
|
320
357
|
return days > 0 ? `${days}d ${hhmmss}` : hhmmss;
|
|
321
358
|
};
|
|
322
359
|
|
|
360
|
+
const buildCacheKey = (parts) => JSON.stringify(parts);
|
|
361
|
+
|
|
362
|
+
const getCacheBucket = (cacheMap, key) => {
|
|
363
|
+
let bucket = cacheMap.get(key);
|
|
364
|
+
if (!bucket) {
|
|
365
|
+
bucket = {
|
|
366
|
+
expiresAt: 0,
|
|
367
|
+
value: null,
|
|
368
|
+
pending: null,
|
|
369
|
+
};
|
|
370
|
+
cacheMap.set(key, bucket);
|
|
371
|
+
}
|
|
372
|
+
return bucket;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const getCachedSnapshot = async ({
|
|
376
|
+
cacheMap,
|
|
377
|
+
key,
|
|
378
|
+
ttlSeconds,
|
|
379
|
+
staleWhileRefresh = true,
|
|
380
|
+
staleOnError = true,
|
|
381
|
+
load,
|
|
382
|
+
}) => {
|
|
383
|
+
const bucket = getCacheBucket(cacheMap, key);
|
|
384
|
+
const now = Date.now();
|
|
385
|
+
const hasValue = bucket.value !== null;
|
|
386
|
+
const hasFreshValue = hasValue && now < bucket.expiresAt;
|
|
387
|
+
|
|
388
|
+
if (hasFreshValue) {
|
|
389
|
+
return bucket.value;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!bucket.pending) {
|
|
393
|
+
bucket.pending = Promise.resolve()
|
|
394
|
+
.then(load)
|
|
395
|
+
.then((value) => {
|
|
396
|
+
bucket.value = value;
|
|
397
|
+
bucket.expiresAt = Date.now() + ttlSeconds * 1000;
|
|
398
|
+
return value;
|
|
399
|
+
})
|
|
400
|
+
.finally(() => {
|
|
401
|
+
bucket.pending = null;
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (hasValue && staleWhileRefresh) {
|
|
406
|
+
return bucket.value;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
return await bucket.pending;
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (hasValue && staleOnError) return bucket.value;
|
|
413
|
+
throw error;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const canUseRankingSnapshotRead = async (subjectKey = 'catalog') =>
|
|
418
|
+
isFeatureEnabled('enable_ranking_snapshot_read', {
|
|
419
|
+
fallback: true,
|
|
420
|
+
subjectKey,
|
|
421
|
+
});
|
|
422
|
+
|
|
323
423
|
const sendJson = (req, res, statusCode, payload) => {
|
|
324
424
|
const body = JSON.stringify(payload);
|
|
325
425
|
res.statusCode = statusCode;
|
|
@@ -358,6 +458,22 @@ const toRequestHost = (req) =>
|
|
|
358
458
|
.replace(/\.$/, '')
|
|
359
459
|
.split(':')[0];
|
|
360
460
|
|
|
461
|
+
const isIpLiteralHost = (value) => {
|
|
462
|
+
const host = String(value || '').trim().toLowerCase();
|
|
463
|
+
if (!host) return false;
|
|
464
|
+
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) return true;
|
|
465
|
+
return host.includes(':');
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const resolveCookieDomainForRequest = (req) => {
|
|
469
|
+
if (!SITE_COOKIE_DOMAIN || isIpLiteralHost(SITE_COOKIE_DOMAIN)) return '';
|
|
470
|
+
const requestHost = toRequestHost(req);
|
|
471
|
+
if (!requestHost || isIpLiteralHost(requestHost) || requestHost === 'localhost') return '';
|
|
472
|
+
if (requestHost === SITE_COOKIE_DOMAIN) return SITE_COOKIE_DOMAIN;
|
|
473
|
+
if (requestHost.endsWith(`.${SITE_COOKIE_DOMAIN}`)) return SITE_COOKIE_DOMAIN;
|
|
474
|
+
return '';
|
|
475
|
+
};
|
|
476
|
+
|
|
361
477
|
const maybeRedirectToCanonicalHost = (req, res, url) => {
|
|
362
478
|
if (!SITE_CANONICAL_REDIRECT_ENABLED) return false;
|
|
363
479
|
if (!['GET', 'HEAD'].includes(req.method || '')) return false;
|
|
@@ -391,6 +507,33 @@ const parseCookies = (req) => {
|
|
|
391
507
|
}, {});
|
|
392
508
|
};
|
|
393
509
|
|
|
510
|
+
const getCookieValuesFromRequest = (req, cookieName) => {
|
|
511
|
+
const target = String(cookieName || '').trim();
|
|
512
|
+
if (!target) return [];
|
|
513
|
+
const raw = String(req?.headers?.cookie || '');
|
|
514
|
+
if (!raw) return [];
|
|
515
|
+
|
|
516
|
+
const values = [];
|
|
517
|
+
for (const chunk of raw.split(';')) {
|
|
518
|
+
const trimmed = String(chunk || '').trim();
|
|
519
|
+
if (!trimmed) continue;
|
|
520
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
521
|
+
if (separatorIndex <= 0) continue;
|
|
522
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
523
|
+
if (key !== target) continue;
|
|
524
|
+
const encodedValue = trimmed.slice(separatorIndex + 1).trim();
|
|
525
|
+
if (!encodedValue) continue;
|
|
526
|
+
let decodedValue = encodedValue;
|
|
527
|
+
try {
|
|
528
|
+
decodedValue = decodeURIComponent(encodedValue);
|
|
529
|
+
} catch {}
|
|
530
|
+
const normalizedValue = String(decodedValue || '').trim();
|
|
531
|
+
if (!normalizedValue) continue;
|
|
532
|
+
if (!values.includes(normalizedValue)) values.push(normalizedValue);
|
|
533
|
+
}
|
|
534
|
+
return values;
|
|
535
|
+
};
|
|
536
|
+
|
|
394
537
|
const isRequestSecure = (req) => {
|
|
395
538
|
const proto = String(req?.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
|
|
396
539
|
if (proto) return proto === 'https';
|
|
@@ -499,6 +642,11 @@ const runSqlTransaction = async (handler) => {
|
|
|
499
642
|
const buildCookieString = (name, value, req, options = {}) => {
|
|
500
643
|
const parts = [`${name}=${encodeURIComponent(String(value ?? ''))}`];
|
|
501
644
|
parts.push(`Path=${options.path || '/'}`);
|
|
645
|
+
const cookieDomain =
|
|
646
|
+
options.domain === false
|
|
647
|
+
? ''
|
|
648
|
+
: String(options.domain || resolveCookieDomainForRequest(req)).trim();
|
|
649
|
+
if (cookieDomain) parts.push(`Domain=${cookieDomain}`);
|
|
502
650
|
if (options.httpOnly !== false) parts.push('HttpOnly');
|
|
503
651
|
parts.push(`SameSite=${options.sameSite || 'Lax'}`);
|
|
504
652
|
if (isRequestSecure(req)) parts.push('Secure');
|
|
@@ -910,16 +1058,22 @@ const pruneExpiredGoogleSessions = () => {
|
|
|
910
1058
|
}
|
|
911
1059
|
};
|
|
912
1060
|
|
|
913
|
-
const
|
|
1061
|
+
const getGoogleWebSessionTokensFromRequest = (req) => {
|
|
1062
|
+
const direct = getCookieValuesFromRequest(req, GOOGLE_WEB_SESSION_COOKIE_NAME);
|
|
1063
|
+
if (direct.length > 0) return direct;
|
|
914
1064
|
const cookies = parseCookies(req);
|
|
915
|
-
|
|
1065
|
+
const fallback = String(cookies[GOOGLE_WEB_SESSION_COOKIE_NAME] || '').trim();
|
|
1066
|
+
return fallback ? [fallback] : [];
|
|
916
1067
|
};
|
|
917
1068
|
|
|
1069
|
+
const getGoogleWebSessionTokenFromRequest = (req) => getGoogleWebSessionTokensFromRequest(req)[0] || '';
|
|
1070
|
+
|
|
918
1071
|
const normalizeGoogleWebSessionRow = (row) => {
|
|
919
1072
|
if (!row || typeof row !== 'object') return null;
|
|
920
1073
|
const token = String(row.session_token || '').trim();
|
|
921
1074
|
const sub = normalizeGoogleSubject(row.google_sub);
|
|
922
1075
|
const ownerJid = normalizeJid(row.owner_jid) || '';
|
|
1076
|
+
const ownerPhone = toWhatsAppPhoneDigits(row.owner_phone || ownerJid) || '';
|
|
923
1077
|
const expiresAt = Number(new Date(row.expires_at || 0));
|
|
924
1078
|
if (!token || !sub || !ownerJid || !Number.isFinite(expiresAt)) return null;
|
|
925
1079
|
const createdAtRaw = Number(new Date(row.created_at || 0));
|
|
@@ -931,6 +1085,7 @@ const normalizeGoogleWebSessionRow = (row) => {
|
|
|
931
1085
|
name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
|
|
932
1086
|
picture: String(row.picture_url || '').trim() || null,
|
|
933
1087
|
ownerJid,
|
|
1088
|
+
ownerPhone,
|
|
934
1089
|
createdAt: Number.isFinite(createdAtRaw) ? createdAtRaw : Date.now(),
|
|
935
1090
|
expiresAt,
|
|
936
1091
|
lastSeenAt: Number.isFinite(lastSeenAtRaw) ? lastSeenAtRaw : 0,
|
|
@@ -959,10 +1114,20 @@ const upsertGoogleWebUserRecord = async (user, connection = null) => {
|
|
|
959
1114
|
const sub = normalizeGoogleSubject(user?.sub);
|
|
960
1115
|
const ownerJid = normalizeJid(user?.ownerJid) || '';
|
|
961
1116
|
if (!sub || !ownerJid) return;
|
|
1117
|
+
const ownerPhone = toWhatsAppPhoneDigits(ownerJid) || null;
|
|
962
1118
|
const email = String(user?.email || '').trim().toLowerCase() || null;
|
|
963
1119
|
const name = sanitizeText(user?.name || '', 120, { allowEmpty: true }) || null;
|
|
964
1120
|
const pictureUrl = String(user?.picture || '').trim().slice(0, 1024) || null;
|
|
965
1121
|
|
|
1122
|
+
// owner_jid e unico; removemos vinculos antigos desse numero antes do upsert para manter 1:1.
|
|
1123
|
+
await executeQuery(
|
|
1124
|
+
`DELETE FROM ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
1125
|
+
WHERE owner_jid = ?
|
|
1126
|
+
AND google_sub <> ?`,
|
|
1127
|
+
[ownerJid, sub],
|
|
1128
|
+
connection,
|
|
1129
|
+
);
|
|
1130
|
+
|
|
966
1131
|
await executeQuery(
|
|
967
1132
|
`INSERT INTO ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
968
1133
|
(google_sub, owner_jid, email, name, picture_url, last_login_at, last_seen_at)
|
|
@@ -977,12 +1142,22 @@ const upsertGoogleWebUserRecord = async (user, connection = null) => {
|
|
|
977
1142
|
[sub, ownerJid, email, name, pictureUrl],
|
|
978
1143
|
connection,
|
|
979
1144
|
);
|
|
1145
|
+
|
|
1146
|
+
// Persistimos o telefone normalizado do owner para facilitar consultas administrativas.
|
|
1147
|
+
await executeQuery(
|
|
1148
|
+
`UPDATE ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
1149
|
+
SET owner_phone = COALESCE(?, owner_phone)
|
|
1150
|
+
WHERE google_sub = ?`,
|
|
1151
|
+
[ownerPhone, sub],
|
|
1152
|
+
connection,
|
|
1153
|
+
).catch(() => {});
|
|
980
1154
|
};
|
|
981
1155
|
|
|
982
1156
|
const upsertGoogleWebSessionRecord = async (session, connection = null) => {
|
|
983
1157
|
const token = String(session?.token || '').trim();
|
|
984
1158
|
const sub = normalizeGoogleSubject(session?.sub);
|
|
985
1159
|
const ownerJid = normalizeJid(session?.ownerJid) || '';
|
|
1160
|
+
const ownerPhone = toWhatsAppPhoneDigits(session?.ownerPhone || ownerJid) || null;
|
|
986
1161
|
const expiresAt = Number(session?.expiresAt || 0);
|
|
987
1162
|
if (!token || !sub || !ownerJid || !Number.isFinite(expiresAt) || expiresAt <= 0) return;
|
|
988
1163
|
const email = String(session?.email || '').trim().toLowerCase() || null;
|
|
@@ -1005,6 +1180,42 @@ const upsertGoogleWebSessionRecord = async (session, connection = null) => {
|
|
|
1005
1180
|
[token, sub, ownerJid, email, name, pictureUrl, new Date(expiresAt)],
|
|
1006
1181
|
connection,
|
|
1007
1182
|
);
|
|
1183
|
+
|
|
1184
|
+
await executeQuery(
|
|
1185
|
+
`UPDATE ${TABLES.STICKER_WEB_GOOGLE_SESSION}
|
|
1186
|
+
SET owner_phone = COALESCE(?, owner_phone)
|
|
1187
|
+
WHERE session_token = ?`,
|
|
1188
|
+
[ownerPhone, token],
|
|
1189
|
+
connection,
|
|
1190
|
+
).catch(() => {});
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
const deleteOtherGoogleWebSessionsInDb = async ({ token = '', ownerJid = '', sub = '' } = {}, connection = null) => {
|
|
1194
|
+
const sessionToken = String(token || '').trim();
|
|
1195
|
+
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
1196
|
+
const normalizedSub = normalizeGoogleSubject(sub);
|
|
1197
|
+
if (!sessionToken || (!normalizedOwnerJid && !normalizedSub)) return 0;
|
|
1198
|
+
|
|
1199
|
+
const clauses = [];
|
|
1200
|
+
const params = [];
|
|
1201
|
+
if (normalizedOwnerJid) {
|
|
1202
|
+
clauses.push('owner_jid = ?');
|
|
1203
|
+
params.push(normalizedOwnerJid);
|
|
1204
|
+
}
|
|
1205
|
+
if (normalizedSub) {
|
|
1206
|
+
clauses.push('google_sub = ?');
|
|
1207
|
+
params.push(normalizedSub);
|
|
1208
|
+
}
|
|
1209
|
+
if (!clauses.length) return 0;
|
|
1210
|
+
|
|
1211
|
+
const result = await executeQuery(
|
|
1212
|
+
`DELETE FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION}
|
|
1213
|
+
WHERE session_token <> ?
|
|
1214
|
+
AND (${clauses.join(' OR ')})`,
|
|
1215
|
+
[sessionToken, ...params],
|
|
1216
|
+
connection,
|
|
1217
|
+
);
|
|
1218
|
+
return Number(result?.affectedRows || 0);
|
|
1008
1219
|
};
|
|
1009
1220
|
|
|
1010
1221
|
const persistGoogleWebSessionToDb = async (session) => {
|
|
@@ -1021,6 +1232,14 @@ const persistGoogleWebSessionToDb = async (session) => {
|
|
|
1021
1232
|
},
|
|
1022
1233
|
connection,
|
|
1023
1234
|
);
|
|
1235
|
+
await deleteOtherGoogleWebSessionsInDb(
|
|
1236
|
+
{
|
|
1237
|
+
token: session.token,
|
|
1238
|
+
ownerJid: session.ownerJid,
|
|
1239
|
+
sub: session.sub,
|
|
1240
|
+
},
|
|
1241
|
+
connection,
|
|
1242
|
+
);
|
|
1024
1243
|
await upsertGoogleWebSessionRecord(session, connection);
|
|
1025
1244
|
});
|
|
1026
1245
|
};
|
|
@@ -1030,7 +1249,7 @@ const findGoogleWebSessionInDbByToken = async (sessionToken) => {
|
|
|
1030
1249
|
if (!token) return null;
|
|
1031
1250
|
await maybePruneExpiredGoogleSessionsFromDb();
|
|
1032
1251
|
const rows = await executeQuery(
|
|
1033
|
-
`SELECT session_token, google_sub, owner_jid, email, name, picture_url, created_at, expires_at, last_seen_at
|
|
1252
|
+
`SELECT session_token, google_sub, owner_jid, owner_phone, email, name, picture_url, created_at, expires_at, last_seen_at
|
|
1034
1253
|
FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION}
|
|
1035
1254
|
WHERE session_token = ?
|
|
1036
1255
|
AND revoked_at IS NULL
|
|
@@ -1504,6 +1723,8 @@ const pruneExpiredAdminPanelSessions = () => {
|
|
|
1504
1723
|
};
|
|
1505
1724
|
|
|
1506
1725
|
const getAdminPanelSessionTokenFromRequest = (req) => {
|
|
1726
|
+
const direct = getCookieValuesFromRequest(req, ADMIN_PANEL_SESSION_COOKIE_NAME);
|
|
1727
|
+
if (direct.length > 0) return direct[0];
|
|
1507
1728
|
const cookies = parseCookies(req);
|
|
1508
1729
|
return String(cookies[ADMIN_PANEL_SESSION_COOKIE_NAME] || '').trim();
|
|
1509
1730
|
};
|
|
@@ -1515,6 +1736,14 @@ const clearAdminPanelSessionCookie = (req, res) => {
|
|
|
1515
1736
|
maxAgeSeconds: 0,
|
|
1516
1737
|
}),
|
|
1517
1738
|
);
|
|
1739
|
+
// Also clear host-only variant (legacy cookie written without Domain).
|
|
1740
|
+
appendSetCookie(
|
|
1741
|
+
res,
|
|
1742
|
+
buildCookieString(ADMIN_PANEL_SESSION_COOKIE_NAME, '', req, {
|
|
1743
|
+
maxAgeSeconds: 0,
|
|
1744
|
+
domain: false,
|
|
1745
|
+
}),
|
|
1746
|
+
);
|
|
1518
1747
|
};
|
|
1519
1748
|
|
|
1520
1749
|
const createAdminPanelSession = (googleSession, { role = 'owner' } = {}) => {
|
|
@@ -1624,83 +1853,105 @@ const requireOwnerAdminPanelSession = (req, res) => {
|
|
|
1624
1853
|
return session;
|
|
1625
1854
|
};
|
|
1626
1855
|
|
|
1627
|
-
const createGoogleWebSession = (claims) => {
|
|
1856
|
+
const createGoogleWebSession = (claims, { ownerJid } = {}) => {
|
|
1628
1857
|
pruneExpiredGoogleSessions();
|
|
1629
1858
|
const token = randomUUID();
|
|
1630
1859
|
const now = Date.now();
|
|
1860
|
+
const resolvedOwnerJid = normalizeJid(ownerJid) || buildGoogleOwnerJid(claims.sub);
|
|
1861
|
+
const resolvedOwnerPhone = toWhatsAppPhoneDigits(resolvedOwnerJid) || '';
|
|
1631
1862
|
const session = {
|
|
1632
1863
|
token,
|
|
1633
1864
|
sub: claims.sub,
|
|
1634
1865
|
email: claims.email || null,
|
|
1635
1866
|
name: claims.name || null,
|
|
1636
1867
|
picture: claims.picture || null,
|
|
1637
|
-
ownerJid:
|
|
1868
|
+
ownerJid: resolvedOwnerJid,
|
|
1869
|
+
ownerPhone: resolvedOwnerPhone,
|
|
1638
1870
|
createdAt: now,
|
|
1639
1871
|
expiresAt: now + STICKER_WEB_GOOGLE_SESSION_TTL_MS,
|
|
1640
1872
|
lastSeenAt: now,
|
|
1641
1873
|
lastDbTouchAt: 0,
|
|
1642
1874
|
};
|
|
1643
|
-
webGoogleSessionMap.set(token, session);
|
|
1644
1875
|
return session;
|
|
1645
1876
|
};
|
|
1646
1877
|
|
|
1878
|
+
const activateGoogleWebSession = (session) => {
|
|
1879
|
+
if (!session?.token) return;
|
|
1880
|
+
pruneExpiredGoogleSessions();
|
|
1881
|
+
webGoogleSessionMap.set(session.token, session);
|
|
1882
|
+
for (const [token, existing] of webGoogleSessionMap.entries()) {
|
|
1883
|
+
if (!existing || token === session.token) continue;
|
|
1884
|
+
const sameOwner = normalizeJid(existing.ownerJid) === normalizeJid(session.ownerJid);
|
|
1885
|
+
const sameSub = normalizeGoogleSubject(existing.sub) === normalizeGoogleSubject(session.sub);
|
|
1886
|
+
if (sameOwner || sameSub) {
|
|
1887
|
+
webGoogleSessionMap.delete(token);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1647
1892
|
const resolveGoogleWebSessionFromRequest = async (req) => {
|
|
1648
1893
|
pruneExpiredGoogleSessions();
|
|
1649
|
-
const
|
|
1650
|
-
if (!
|
|
1651
|
-
|
|
1652
|
-
|
|
1894
|
+
const sessionTokens = getGoogleWebSessionTokensFromRequest(req);
|
|
1895
|
+
if (!sessionTokens.length) return null;
|
|
1896
|
+
|
|
1897
|
+
for (const sessionToken of sessionTokens) {
|
|
1898
|
+
const session = webGoogleSessionMap.get(sessionToken);
|
|
1899
|
+
if (!session) continue;
|
|
1653
1900
|
if (Number(session.expiresAt || 0) <= Date.now()) {
|
|
1654
1901
|
webGoogleSessionMap.delete(sessionToken);
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
void touchGoogleWebUserSeenInDb(session.sub).catch(() => {});
|
|
1667
|
-
}
|
|
1668
|
-
try {
|
|
1669
|
-
await assertGoogleIdentityNotBanned({
|
|
1670
|
-
sub: session.sub,
|
|
1671
|
-
email: session.email,
|
|
1672
|
-
ownerJid: session.ownerJid,
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
const now = Date.now();
|
|
1906
|
+
session.lastSeenAt = now;
|
|
1907
|
+
if (now - Number(session.lastDbTouchAt || 0) >= GOOGLE_WEB_SESSION_DB_TOUCH_INTERVAL_MS) {
|
|
1908
|
+
session.lastDbTouchAt = now;
|
|
1909
|
+
void touchGoogleWebSessionSeenInDb(sessionToken).catch((error) => {
|
|
1910
|
+
logger.warn('Falha ao atualizar last_seen da sessão Google web.', {
|
|
1911
|
+
action: 'sticker_pack_google_web_session_touch_failed',
|
|
1912
|
+
error: error?.message,
|
|
1673
1913
|
});
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
void deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1677
|
-
return null;
|
|
1678
|
-
}
|
|
1679
|
-
return session;
|
|
1914
|
+
});
|
|
1915
|
+
void touchGoogleWebUserSeenInDb(session.sub).catch(() => {});
|
|
1680
1916
|
}
|
|
1681
|
-
}
|
|
1682
|
-
try {
|
|
1683
|
-
const persistedSession = await findGoogleWebSessionInDbByToken(sessionToken);
|
|
1684
|
-
if (!persistedSession) return null;
|
|
1685
1917
|
try {
|
|
1686
1918
|
await assertGoogleIdentityNotBanned({
|
|
1687
|
-
sub:
|
|
1688
|
-
email:
|
|
1689
|
-
ownerJid:
|
|
1919
|
+
sub: session.sub,
|
|
1920
|
+
email: session.email,
|
|
1921
|
+
ownerJid: session.ownerJid,
|
|
1690
1922
|
});
|
|
1923
|
+
return session;
|
|
1691
1924
|
} catch {
|
|
1692
|
-
|
|
1693
|
-
|
|
1925
|
+
webGoogleSessionMap.delete(sessionToken);
|
|
1926
|
+
void deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
for (const sessionToken of sessionTokens) {
|
|
1931
|
+
try {
|
|
1932
|
+
const persistedSession = await findGoogleWebSessionInDbByToken(sessionToken);
|
|
1933
|
+
if (!persistedSession) continue;
|
|
1934
|
+
try {
|
|
1935
|
+
await assertGoogleIdentityNotBanned({
|
|
1936
|
+
sub: persistedSession.sub,
|
|
1937
|
+
email: persistedSession.email,
|
|
1938
|
+
ownerJid: persistedSession.ownerJid,
|
|
1939
|
+
});
|
|
1940
|
+
} catch {
|
|
1941
|
+
await deleteGoogleWebSessionFromDb(sessionToken).catch(() => {});
|
|
1942
|
+
continue;
|
|
1943
|
+
}
|
|
1944
|
+
webGoogleSessionMap.set(sessionToken, persistedSession);
|
|
1945
|
+
return persistedSession;
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
logger.warn('Falha ao resolver sessão Google web no banco.', {
|
|
1948
|
+
action: 'sticker_pack_google_web_session_db_resolve_failed',
|
|
1949
|
+
error: error?.message,
|
|
1950
|
+
});
|
|
1694
1951
|
}
|
|
1695
|
-
webGoogleSessionMap.set(sessionToken, persistedSession);
|
|
1696
|
-
return persistedSession;
|
|
1697
|
-
} catch (error) {
|
|
1698
|
-
logger.warn('Falha ao resolver sessão Google web no banco.', {
|
|
1699
|
-
action: 'sticker_pack_google_web_session_db_resolve_failed',
|
|
1700
|
-
error: error?.message,
|
|
1701
|
-
});
|
|
1702
|
-
return null;
|
|
1703
1952
|
}
|
|
1953
|
+
|
|
1954
|
+
return null;
|
|
1704
1955
|
};
|
|
1705
1956
|
|
|
1706
1957
|
const clearGoogleWebSessionCookie = (req, res) => {
|
|
@@ -1710,6 +1961,14 @@ const clearGoogleWebSessionCookie = (req, res) => {
|
|
|
1710
1961
|
maxAgeSeconds: 0,
|
|
1711
1962
|
}),
|
|
1712
1963
|
);
|
|
1964
|
+
// Also clear host-only variant (legacy cookie written without Domain).
|
|
1965
|
+
appendSetCookie(
|
|
1966
|
+
res,
|
|
1967
|
+
buildCookieString(GOOGLE_WEB_SESSION_COOKIE_NAME, '', req, {
|
|
1968
|
+
maxAgeSeconds: 0,
|
|
1969
|
+
domain: false,
|
|
1970
|
+
}),
|
|
1971
|
+
);
|
|
1713
1972
|
};
|
|
1714
1973
|
|
|
1715
1974
|
const sendAsset = (req, res, buffer, mimetype = 'image/webp') => {
|
|
@@ -2060,7 +2319,9 @@ const resolveWebCreateOwnerJid = async (explicitOwner = '') => {
|
|
|
2060
2319
|
const resolvedAdminJid = await resolveAdminJid();
|
|
2061
2320
|
const fromAdmin = toOwnerJid(resolvedAdminJid);
|
|
2062
2321
|
if (fromAdmin) return fromAdmin;
|
|
2063
|
-
} catch {
|
|
2322
|
+
} catch {
|
|
2323
|
+
// Ignore fallback errors while resolving owner identity.
|
|
2324
|
+
}
|
|
2064
2325
|
|
|
2065
2326
|
const adminCandidates = [
|
|
2066
2327
|
getAdminRawValue(),
|
|
@@ -2230,14 +2491,18 @@ const resolveSupportAdminPhone = async () => {
|
|
|
2230
2491
|
const resolvedFromLidMap = await resolveUserId(extractUserIdInfo(adminRaw));
|
|
2231
2492
|
const resolvedPhoneFromLidMap = isPlausibleWhatsAppPhone(getJidUser(resolvedFromLidMap || ''));
|
|
2232
2493
|
if (resolvedPhoneFromLidMap) return resolvedPhoneFromLidMap;
|
|
2233
|
-
} catch {
|
|
2494
|
+
} catch {
|
|
2495
|
+
// Ignore and fallback to other admin sources.
|
|
2496
|
+
}
|
|
2234
2497
|
}
|
|
2235
2498
|
|
|
2236
2499
|
try {
|
|
2237
2500
|
const resolvedAdminJid = await resolveAdminJid();
|
|
2238
2501
|
const resolvedPhone = isPlausibleWhatsAppPhone(getJidUser(resolvedAdminJid || ''));
|
|
2239
2502
|
if (resolvedPhone) return resolvedPhone;
|
|
2240
|
-
} catch {
|
|
2503
|
+
} catch {
|
|
2504
|
+
// Ignore and fallback to static admin phone sources.
|
|
2505
|
+
}
|
|
2241
2506
|
|
|
2242
2507
|
const rawPhone = isPlausibleWhatsAppPhone(getJidUser(adminRaw) || adminRaw);
|
|
2243
2508
|
if (rawPhone) return rawPhone;
|
|
@@ -2266,6 +2531,27 @@ const buildSupportInfo = async () => {
|
|
|
2266
2531
|
};
|
|
2267
2532
|
};
|
|
2268
2533
|
|
|
2534
|
+
const buildBotContactInfo = () => {
|
|
2535
|
+
const phone = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
|
|
2536
|
+
if (!phone) return null;
|
|
2537
|
+
const loginText = String(process.env.WHATSAPP_LOGIN_TRIGGER || 'iniciar').trim() || 'iniciar';
|
|
2538
|
+
const menuText = `${PACK_COMMAND_PREFIX}menu`;
|
|
2539
|
+
const buildUrl = (text) =>
|
|
2540
|
+
`https://api.whatsapp.com/send/?phone=${encodeURIComponent(phone)}&text=${encodeURIComponent(
|
|
2541
|
+
String(text || '').trim(),
|
|
2542
|
+
)}&type=custom_url&app_absent=0`;
|
|
2543
|
+
|
|
2544
|
+
return {
|
|
2545
|
+
phone,
|
|
2546
|
+
login_text: loginText,
|
|
2547
|
+
menu_text: menuText,
|
|
2548
|
+
urls: {
|
|
2549
|
+
login: buildUrl(loginText),
|
|
2550
|
+
menu: buildUrl(menuText),
|
|
2551
|
+
},
|
|
2552
|
+
};
|
|
2553
|
+
};
|
|
2554
|
+
|
|
2269
2555
|
const listDataImageFiles = async () => {
|
|
2270
2556
|
const files = [];
|
|
2271
2557
|
const queue = [STICKER_DATA_PUBLIC_DIR];
|
|
@@ -2618,6 +2904,10 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
|
|
|
2618
2904
|
const packIds = packs.map((pack) => pack.id);
|
|
2619
2905
|
const engagementByPackId = await listStickerPackEngagementByPackIds(packIds);
|
|
2620
2906
|
const interactionStatsByPackId = await listStickerPackInteractionStatsByPackIds(packIds);
|
|
2907
|
+
const useSnapshot = await canUseRankingSnapshotRead(`hydrate:${packIds.length}:${includeItems ? 1 : 0}`);
|
|
2908
|
+
const snapshotByPackId = useSnapshot
|
|
2909
|
+
? await listStickerPackScoreSnapshotsByPackIds(packIds).catch(() => new Map())
|
|
2910
|
+
: new Map();
|
|
2621
2911
|
|
|
2622
2912
|
const entries = [];
|
|
2623
2913
|
const packClassificationById = new Map();
|
|
@@ -2636,14 +2926,24 @@ const hydrateMarketplaceEntries = async (packs, { includeItems = true, driftSnap
|
|
|
2636
2926
|
const packMetadata = parsePackDescriptionMetadata(pack.description);
|
|
2637
2927
|
const decoratedClassification = decoratePackClassificationSummary(packClassification);
|
|
2638
2928
|
const mergedPackTags = mergeUniqueTags(decoratedClassification?.tags || [], packMetadata.tags);
|
|
2639
|
-
const
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2929
|
+
const snapshot = snapshotByPackId.get(pack.id);
|
|
2930
|
+
const signals = snapshot?.signals
|
|
2931
|
+
? {
|
|
2932
|
+
...snapshot.signals,
|
|
2933
|
+
ranking_score: Number(snapshot?.signals?.ranking_score || 0),
|
|
2934
|
+
pack_score: Number(snapshot?.signals?.pack_score || 0),
|
|
2935
|
+
trend_score: Number(snapshot?.signals?.trend_score || 0),
|
|
2936
|
+
nsfw_level: String(snapshot?.signals?.nsfw_level || 'safe'),
|
|
2937
|
+
sensitive_content: Boolean(snapshot?.signals?.sensitive_content),
|
|
2938
|
+
}
|
|
2939
|
+
: computePackSignals({
|
|
2940
|
+
pack: { ...pack, items },
|
|
2941
|
+
engagement,
|
|
2942
|
+
packClassification,
|
|
2943
|
+
itemClassifications: orderedClassifications,
|
|
2944
|
+
interactionStats,
|
|
2945
|
+
scoringWeights: driftSnapshot?.weights || null,
|
|
2946
|
+
});
|
|
2647
2947
|
|
|
2648
2948
|
const entry = {
|
|
2649
2949
|
pack,
|
|
@@ -2869,6 +3169,7 @@ const renderCatalogHtml = async ({ initialPackKey }) => {
|
|
|
2869
3169
|
__STICKER_WEB_PATH__: escapeHtmlAttribute(STICKER_WEB_PATH),
|
|
2870
3170
|
__STICKER_API_BASE_PATH__: escapeHtmlAttribute(STICKER_API_BASE_PATH),
|
|
2871
3171
|
__STICKER_ORPHAN_API_PATH__: escapeHtmlAttribute(buildOrphanStickersApiUrl()),
|
|
3172
|
+
__STICKER_LOGIN_WEB_PATH__: escapeHtmlAttribute(STICKER_LOGIN_WEB_PATH),
|
|
2872
3173
|
__STICKER_DATA_PUBLIC_PATH__: escapeHtmlAttribute(STICKER_DATA_PUBLIC_PATH),
|
|
2873
3174
|
__DEFAULT_LIST_LIMIT__: String(DEFAULT_LIST_LIMIT),
|
|
2874
3175
|
__DEFAULT_ORPHAN_LIST_LIMIT__: String(DEFAULT_ORPHAN_LIST_LIMIT),
|
|
@@ -3014,11 +3315,12 @@ const renderPackSeoHtml = ({ packSummary }) => {
|
|
|
3014
3315
|
data-web-path="${escapeHtmlAttribute(STICKER_WEB_PATH)}"
|
|
3015
3316
|
data-api-base-path="${escapeHtmlAttribute(STICKER_API_BASE_PATH)}"
|
|
3016
3317
|
data-orphan-api-path="${escapeHtmlAttribute(STICKER_ORPHAN_API_PATH)}"
|
|
3318
|
+
data-login-path="${escapeHtmlAttribute(STICKER_LOGIN_WEB_PATH)}"
|
|
3017
3319
|
data-default-limit="${DEFAULT_LIST_LIMIT}"
|
|
3018
3320
|
data-default-orphan-limit="${DEFAULT_ORPHAN_LIST_LIMIT}"
|
|
3019
3321
|
data-initial-pack-key="${escapeHtmlAttribute(packSummary?.pack_key || '')}"
|
|
3020
3322
|
></div>
|
|
3021
|
-
<script type="module" src="/js/apps/stickersApp.js?v=
|
|
3323
|
+
<script type="module" src="/js/apps/stickersApp.js?v=20260228-login-redirect-my-packs1"></script>
|
|
3022
3324
|
</body>
|
|
3023
3325
|
</html>`;
|
|
3024
3326
|
};
|
|
@@ -3054,6 +3356,7 @@ const renderCreatePackHtml = async () => {
|
|
|
3054
3356
|
const replacements = {
|
|
3055
3357
|
__STICKER_WEB_PATH__: escapeHtmlAttribute(STICKER_WEB_PATH),
|
|
3056
3358
|
__STICKER_CREATE_WEB_PATH__: escapeHtmlAttribute(STICKER_CREATE_WEB_PATH),
|
|
3359
|
+
__STICKER_LOGIN_WEB_PATH__: escapeHtmlAttribute(STICKER_LOGIN_WEB_PATH),
|
|
3057
3360
|
__STICKER_API_BASE_PATH__: escapeHtmlAttribute(STICKER_API_BASE_PATH),
|
|
3058
3361
|
__PACK_COMMAND_PREFIX__: escapeHtmlAttribute(PACK_COMMAND_PREFIX),
|
|
3059
3362
|
__CURRENT_YEAR__: String(new Date().getFullYear()),
|
|
@@ -3082,6 +3385,23 @@ const renderAdminPanelHtml = async () => {
|
|
|
3082
3385
|
return html;
|
|
3083
3386
|
};
|
|
3084
3387
|
|
|
3388
|
+
const renderUserDashboardHtml = async () => {
|
|
3389
|
+
const template = await fs.readFile(USER_DASHBOARD_TEMPLATE_PATH, 'utf8');
|
|
3390
|
+
const replacements = {
|
|
3391
|
+
__STICKER_WEB_PATH__: escapeHtmlAttribute(STICKER_WEB_PATH),
|
|
3392
|
+
__STICKER_LOGIN_WEB_PATH__: escapeHtmlAttribute(STICKER_LOGIN_WEB_PATH),
|
|
3393
|
+
__STICKER_API_BASE_PATH__: escapeHtmlAttribute(STICKER_API_BASE_PATH),
|
|
3394
|
+
__USER_PROFILE_WEB_PATH__: escapeHtmlAttribute(USER_PROFILE_WEB_PATH),
|
|
3395
|
+
__CURRENT_YEAR__: String(new Date().getFullYear()),
|
|
3396
|
+
};
|
|
3397
|
+
|
|
3398
|
+
let html = template;
|
|
3399
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
3400
|
+
html = html.replaceAll(token, value);
|
|
3401
|
+
}
|
|
3402
|
+
return html;
|
|
3403
|
+
};
|
|
3404
|
+
|
|
3085
3405
|
const buildSitemapXml = async () => {
|
|
3086
3406
|
if (SITEMAP_CACHE.expiresAt > Date.now() && SITEMAP_CACHE.xml) {
|
|
3087
3407
|
return SITEMAP_CACHE.xml;
|
|
@@ -3194,95 +3514,118 @@ const handleListRequest = async (req, res, url) => {
|
|
|
3194
3514
|
const limit = clampInt(url.searchParams.get('limit'), DEFAULT_LIST_LIMIT, 1, MAX_LIST_LIMIT);
|
|
3195
3515
|
const offset = clampInt(url.searchParams.get('offset'), 0, 0, 100000);
|
|
3196
3516
|
const normalizedIntent = normalizeCategoryToken(intent).replace(/-/g, '_');
|
|
3197
|
-
const batchLimit = Math.max(limit, Math.min(MAX_LIST_LIMIT, 24));
|
|
3198
|
-
const maxPagesToScan = 8;
|
|
3199
3517
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
3200
3518
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
3201
|
-
const
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3519
|
+
const cacheKey = buildCacheKey([
|
|
3520
|
+
'list',
|
|
3521
|
+
q,
|
|
3522
|
+
visibility,
|
|
3523
|
+
sort,
|
|
3524
|
+
categories.join(','),
|
|
3525
|
+
normalizedIntent,
|
|
3526
|
+
includeSensitive ? 1 : 0,
|
|
3527
|
+
limit,
|
|
3528
|
+
offset,
|
|
3529
|
+
hasNsfwAccess ? 1 : 0,
|
|
3530
|
+
]);
|
|
3531
|
+
const payload = await getCachedSnapshot({
|
|
3532
|
+
cacheMap: CATALOG_LIST_CACHE,
|
|
3533
|
+
key: cacheKey,
|
|
3534
|
+
ttlSeconds: CATALOG_LIST_CACHE_SECONDS,
|
|
3535
|
+
staleWhileRefresh: true,
|
|
3536
|
+
staleOnError: true,
|
|
3537
|
+
load: async () => {
|
|
3538
|
+
const batchLimit = Math.max(limit, Math.min(MAX_LIST_LIMIT, 24));
|
|
3539
|
+
const maxPagesToScan = 8;
|
|
3540
|
+
const seenPackIds = new Set();
|
|
3541
|
+
const collectedEntries = [];
|
|
3542
|
+
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
3543
|
+
let sourceHasMore = true;
|
|
3544
|
+
let cursorOffset = offset;
|
|
3545
|
+
let pagesScanned = 0;
|
|
3546
|
+
|
|
3547
|
+
while (collectedEntries.length < limit && sourceHasMore && pagesScanned < maxPagesToScan) {
|
|
3548
|
+
pagesScanned += 1;
|
|
3549
|
+
const { packs, hasMore } = await listStickerPacksForCatalog({
|
|
3550
|
+
visibility,
|
|
3551
|
+
search: q,
|
|
3552
|
+
limit: batchLimit,
|
|
3553
|
+
offset: cursorOffset,
|
|
3554
|
+
});
|
|
3555
|
+
sourceHasMore = hasMore;
|
|
3556
|
+
cursorOffset += batchLimit;
|
|
3557
|
+
if (!packs.length) break;
|
|
3558
|
+
|
|
3559
|
+
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3560
|
+
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3561
|
+
? entries.filter((entry) => isPackClassified(entry.packClassification))
|
|
3562
|
+
: entries;
|
|
3563
|
+
const entriesByCategory = categories.length
|
|
3564
|
+
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
3565
|
+
: entriesClassified;
|
|
3566
|
+
const entriesBySensitivity = includeSensitive
|
|
3567
|
+
? entriesByCategory
|
|
3568
|
+
: entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
|
|
3569
|
+
const entriesByIntent = intent
|
|
3570
|
+
? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent)
|
|
3571
|
+
: entriesBySensitivity;
|
|
3572
|
+
const sortedEntries = [...entriesByIntent].sort((left, right) => {
|
|
3573
|
+
const completenessDelta = compareEntriesByPackCompleteness(left, right);
|
|
3574
|
+
if (completenessDelta !== 0) return completenessDelta;
|
|
3575
|
+
if (sort === 'recent') {
|
|
3576
|
+
return Date.parse(right?.pack?.created_at || right?.pack?.updated_at || 0) - Date.parse(left?.pack?.created_at || left?.pack?.updated_at || 0);
|
|
3577
|
+
}
|
|
3578
|
+
if (sort === 'likes') {
|
|
3579
|
+
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3580
|
+
}
|
|
3581
|
+
if (sort === 'downloads') {
|
|
3582
|
+
return Number(right?.engagement?.open_count || 0) - Number(left?.engagement?.open_count || 0);
|
|
3583
|
+
}
|
|
3584
|
+
if (sort === 'comments') {
|
|
3585
|
+
const commentDelta = Number(right?.engagement?.comment_count || 0) - Number(left?.engagement?.comment_count || 0);
|
|
3586
|
+
if (commentDelta !== 0) return commentDelta;
|
|
3587
|
+
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3588
|
+
}
|
|
3589
|
+
if (sort === 'trending') {
|
|
3590
|
+
const trendDelta = Number(right?.signals?.trend_score || 0) - Number(left?.signals?.trend_score || 0);
|
|
3591
|
+
if (trendDelta !== 0) return trendDelta;
|
|
3592
|
+
}
|
|
3593
|
+
const leftScore = Number(left?.signals?.ranking_score || 0);
|
|
3594
|
+
const rightScore = Number(right?.signals?.ranking_score || 0);
|
|
3595
|
+
if (rightScore !== leftScore) return rightScore - leftScore;
|
|
3596
|
+
return Date.parse(right?.pack?.updated_at || 0) - Date.parse(left?.pack?.updated_at || 0);
|
|
3597
|
+
});
|
|
3207
3598
|
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
});
|
|
3216
|
-
sourceHasMore = hasMore;
|
|
3217
|
-
cursorOffset += batchLimit;
|
|
3218
|
-
if (!packs.length) break;
|
|
3219
|
-
|
|
3220
|
-
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
3221
|
-
const entriesClassified = STICKER_CATALOG_ONLY_CLASSIFIED
|
|
3222
|
-
? entries.filter((entry) => isPackClassified(entry.packClassification))
|
|
3223
|
-
: entries;
|
|
3224
|
-
const entriesByCategory = categories.length
|
|
3225
|
-
? entriesClassified.filter((entry) => hasAnyCategory(entry.packClassification?.tags || [], categories))
|
|
3226
|
-
: entriesClassified;
|
|
3227
|
-
const entriesBySensitivity = includeSensitive
|
|
3228
|
-
? entriesByCategory
|
|
3229
|
-
: entriesByCategory.filter((entry) => entry.signals?.nsfw_level === 'safe');
|
|
3230
|
-
const entriesByIntent = intent
|
|
3231
|
-
? entriesBySensitivity.filter((entry) => classifyPackIntent(entry) === normalizedIntent)
|
|
3232
|
-
: entriesBySensitivity;
|
|
3233
|
-
const sortedEntries = [...entriesByIntent].sort((left, right) => {
|
|
3234
|
-
const completenessDelta = compareEntriesByPackCompleteness(left, right);
|
|
3235
|
-
if (completenessDelta !== 0) return completenessDelta;
|
|
3236
|
-
if (sort === 'recent') {
|
|
3237
|
-
return Date.parse(right?.pack?.created_at || right?.pack?.updated_at || 0) - Date.parse(left?.pack?.created_at || left?.pack?.updated_at || 0);
|
|
3238
|
-
}
|
|
3239
|
-
if (sort === 'likes') {
|
|
3240
|
-
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3241
|
-
}
|
|
3242
|
-
if (sort === 'downloads') {
|
|
3243
|
-
return Number(right?.engagement?.open_count || 0) - Number(left?.engagement?.open_count || 0);
|
|
3244
|
-
}
|
|
3245
|
-
if (sort === 'comments') {
|
|
3246
|
-
const commentDelta = Number(right?.engagement?.comment_count || 0) - Number(left?.engagement?.comment_count || 0);
|
|
3247
|
-
if (commentDelta !== 0) return commentDelta;
|
|
3248
|
-
return Number(right?.engagement?.like_count || 0) - Number(left?.engagement?.like_count || 0);
|
|
3249
|
-
}
|
|
3250
|
-
if (sort === 'trending') {
|
|
3251
|
-
const trendDelta = Number(right?.signals?.trend_score || 0) - Number(left?.signals?.trend_score || 0);
|
|
3252
|
-
if (trendDelta !== 0) return trendDelta;
|
|
3599
|
+
for (const entry of sortedEntries) {
|
|
3600
|
+
if (!entry?.pack?.id) continue;
|
|
3601
|
+
if (seenPackIds.has(entry.pack.id)) continue;
|
|
3602
|
+
seenPackIds.add(entry.pack.id);
|
|
3603
|
+
collectedEntries.push(entry);
|
|
3604
|
+
if (collectedEntries.length >= limit) break;
|
|
3605
|
+
}
|
|
3253
3606
|
}
|
|
3254
|
-
const leftScore = Number(left?.signals?.ranking_score || 0);
|
|
3255
|
-
const rightScore = Number(right?.signals?.ranking_score || 0);
|
|
3256
|
-
if (rightScore !== leftScore) return rightScore - leftScore;
|
|
3257
|
-
return Date.parse(right?.pack?.updated_at || 0) - Date.parse(left?.pack?.updated_at || 0);
|
|
3258
|
-
});
|
|
3259
|
-
|
|
3260
|
-
for (const entry of sortedEntries) {
|
|
3261
|
-
if (!entry?.pack?.id) continue;
|
|
3262
|
-
if (seenPackIds.has(entry.pack.id)) continue;
|
|
3263
|
-
seenPackIds.add(entry.pack.id);
|
|
3264
|
-
collectedEntries.push(entry);
|
|
3265
|
-
if (collectedEntries.length >= limit) break;
|
|
3266
|
-
}
|
|
3267
|
-
}
|
|
3268
3607
|
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3608
|
+
return {
|
|
3609
|
+
data: collectedEntries.map((entry) => toSummaryEntry(entry, { hideSensitiveCover: !hasNsfwAccess })),
|
|
3610
|
+
pagination: {
|
|
3611
|
+
limit,
|
|
3612
|
+
offset,
|
|
3613
|
+
has_more: sourceHasMore,
|
|
3614
|
+
next_offset: sourceHasMore ? cursorOffset : null,
|
|
3615
|
+
},
|
|
3616
|
+
filters: {
|
|
3617
|
+
q,
|
|
3618
|
+
visibility,
|
|
3619
|
+
sort,
|
|
3620
|
+
categories,
|
|
3621
|
+
intent: intent || null,
|
|
3622
|
+
include_sensitive: includeSensitive,
|
|
3623
|
+
},
|
|
3624
|
+
};
|
|
3284
3625
|
},
|
|
3285
3626
|
});
|
|
3627
|
+
|
|
3628
|
+
sendJson(req, res, 200, payload);
|
|
3286
3629
|
};
|
|
3287
3630
|
|
|
3288
3631
|
const handleIntentCollectionsRequest = async (req, res, url) => {
|
|
@@ -3514,32 +3857,15 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
|
|
|
3514
3857
|
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
3515
3858
|
const session = await resolveGoogleWebSessionFromRequest(req);
|
|
3516
3859
|
sendJson(req, res, 200, {
|
|
3517
|
-
data: session
|
|
3518
|
-
? {
|
|
3519
|
-
authenticated: true,
|
|
3520
|
-
provider: 'google',
|
|
3521
|
-
user: {
|
|
3522
|
-
sub: session.sub,
|
|
3523
|
-
email: session.email,
|
|
3524
|
-
name: session.name,
|
|
3525
|
-
picture: session.picture,
|
|
3526
|
-
},
|
|
3527
|
-
expires_at: toIsoOrNull(session.expiresAt),
|
|
3528
|
-
}
|
|
3529
|
-
: {
|
|
3530
|
-
authenticated: false,
|
|
3531
|
-
provider: 'google',
|
|
3532
|
-
user: null,
|
|
3533
|
-
expires_at: null,
|
|
3534
|
-
},
|
|
3860
|
+
data: mapGoogleSessionResponseData(session),
|
|
3535
3861
|
});
|
|
3536
3862
|
return;
|
|
3537
3863
|
}
|
|
3538
3864
|
|
|
3539
3865
|
if (req.method === 'DELETE') {
|
|
3540
|
-
const
|
|
3541
|
-
|
|
3542
|
-
|
|
3866
|
+
const tokens = getGoogleWebSessionTokensFromRequest(req);
|
|
3867
|
+
for (const token of tokens) {
|
|
3868
|
+
webGoogleSessionMap.delete(token);
|
|
3543
3869
|
await deleteGoogleWebSessionFromDb(token).catch((error) => {
|
|
3544
3870
|
logger.warn('Falha ao remover sessão Google web do banco.', {
|
|
3545
3871
|
action: 'sticker_pack_google_web_session_db_delete_failed',
|
|
@@ -3567,20 +3893,57 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
|
|
|
3567
3893
|
|
|
3568
3894
|
try {
|
|
3569
3895
|
const claims = await verifyGoogleIdToken(payload?.google_id_token || payload?.id_token);
|
|
3896
|
+
const linkedOwner = resolveWhatsAppOwnerJidFromLoginPayload(payload);
|
|
3897
|
+
if (!linkedOwner.ownerJid) {
|
|
3898
|
+
if (!linkedOwner.hasPayload) {
|
|
3899
|
+
sendJson(req, res, 400, {
|
|
3900
|
+
error: 'Abra esta pagina pelo link enviado no WhatsApp. Envie "iniciar" no bot para gerar o link de login.',
|
|
3901
|
+
code: 'WHATSAPP_LOGIN_LINK_REQUIRED',
|
|
3902
|
+
reason: 'missing_link',
|
|
3903
|
+
});
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
const reason = String(linkedOwner.reason || '').trim().toLowerCase();
|
|
3908
|
+
const isUnauthorizedAttempt = ['invalid_signature', 'missing_signature'].includes(reason);
|
|
3909
|
+
const statusCode = isUnauthorizedAttempt ? 403 : 400;
|
|
3910
|
+
const errorMessage =
|
|
3911
|
+
reason === 'expired'
|
|
3912
|
+
? 'Link de login expirado. Envie "iniciar" novamente no WhatsApp.'
|
|
3913
|
+
: isUnauthorizedAttempt
|
|
3914
|
+
? 'Tentativa de login sem permissao detectada. Gere um novo link enviando "iniciar" no privado do bot.'
|
|
3915
|
+
: 'Link de login invalido. Envie "iniciar" novamente no WhatsApp.';
|
|
3916
|
+
|
|
3917
|
+
logger.warn('Tentativa de login web bloqueada por validacao do link WhatsApp.', {
|
|
3918
|
+
action: 'sticker_pack_google_web_login_link_blocked',
|
|
3919
|
+
reason: reason || 'unknown',
|
|
3920
|
+
remote_ip: req.socket?.remoteAddress || null,
|
|
3921
|
+
user_agent: req.headers?.['user-agent'] || null,
|
|
3922
|
+
});
|
|
3923
|
+
|
|
3924
|
+
sendJson(req, res, statusCode, {
|
|
3925
|
+
error: errorMessage,
|
|
3926
|
+
code: 'WHATSAPP_LOGIN_LINK_INVALID',
|
|
3927
|
+
reason: reason || 'invalid_link',
|
|
3928
|
+
});
|
|
3929
|
+
return;
|
|
3930
|
+
}
|
|
3931
|
+
const ownerJid = linkedOwner.ownerJid;
|
|
3932
|
+
|
|
3570
3933
|
await assertGoogleIdentityNotBanned({
|
|
3571
3934
|
sub: claims.sub,
|
|
3572
3935
|
email: claims.email,
|
|
3573
|
-
ownerJid
|
|
3936
|
+
ownerJid,
|
|
3574
3937
|
});
|
|
3575
|
-
const session = createGoogleWebSession(claims);
|
|
3938
|
+
const session = createGoogleWebSession(claims, { ownerJid });
|
|
3576
3939
|
if (!session.ownerJid) {
|
|
3577
3940
|
sendJson(req, res, 400, { error: 'Nao foi possivel vincular a conta Google.' });
|
|
3578
3941
|
return;
|
|
3579
3942
|
}
|
|
3580
3943
|
try {
|
|
3581
3944
|
await persistGoogleWebSessionToDb(session);
|
|
3945
|
+
activateGoogleWebSession(session);
|
|
3582
3946
|
} catch (persistError) {
|
|
3583
|
-
webGoogleSessionMap.delete(session.token);
|
|
3584
3947
|
logger.error('Falha ao persistir sessão Google web no banco.', {
|
|
3585
3948
|
action: 'sticker_pack_google_web_session_db_persist_failed',
|
|
3586
3949
|
error: persistError?.message,
|
|
@@ -3596,17 +3959,7 @@ const handleGoogleAuthSessionRequest = async (req, res) => {
|
|
|
3596
3959
|
}),
|
|
3597
3960
|
);
|
|
3598
3961
|
sendJson(req, res, 200, {
|
|
3599
|
-
data:
|
|
3600
|
-
authenticated: true,
|
|
3601
|
-
provider: 'google',
|
|
3602
|
-
user: {
|
|
3603
|
-
sub: session.sub,
|
|
3604
|
-
email: session.email,
|
|
3605
|
-
name: session.name,
|
|
3606
|
-
picture: session.picture,
|
|
3607
|
-
},
|
|
3608
|
-
expires_at: toIsoOrNull(session.expiresAt),
|
|
3609
|
-
},
|
|
3962
|
+
data: mapGoogleSessionResponseData(session),
|
|
3610
3963
|
});
|
|
3611
3964
|
} catch (error) {
|
|
3612
3965
|
sendJson(req, res, Number(error?.statusCode || 401), {
|
|
@@ -3627,20 +3980,199 @@ const mapGoogleSessionResponseData = (session) =>
|
|
|
3627
3980
|
name: session.name,
|
|
3628
3981
|
picture: session.picture,
|
|
3629
3982
|
},
|
|
3983
|
+
owner_jid: session.ownerJid,
|
|
3984
|
+
owner_phone: toWhatsAppPhoneDigits(session.ownerPhone || session.ownerJid) || null,
|
|
3630
3985
|
expires_at: toIsoOrNull(session.expiresAt),
|
|
3631
3986
|
}
|
|
3632
3987
|
: {
|
|
3633
3988
|
authenticated: false,
|
|
3634
3989
|
provider: 'google',
|
|
3635
3990
|
user: null,
|
|
3991
|
+
owner_jid: null,
|
|
3992
|
+
owner_phone: null,
|
|
3636
3993
|
expires_at: null,
|
|
3637
3994
|
};
|
|
3638
3995
|
|
|
3639
|
-
const
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3996
|
+
const buildOwnerLookupJids = (value) => {
|
|
3997
|
+
const normalized = normalizeJid(value) || '';
|
|
3998
|
+
if (!normalized || !normalized.includes('@')) return [];
|
|
3999
|
+
const lookup = new Set([normalized]);
|
|
4000
|
+
const phoneDigits = toWhatsAppPhoneDigits(normalized);
|
|
4001
|
+
if (!phoneDigits) return Array.from(lookup);
|
|
4002
|
+
lookup.add(normalizeJid(`${phoneDigits}@s.whatsapp.net`) || '');
|
|
4003
|
+
lookup.add(normalizeJid(`${phoneDigits}@c.us`) || '');
|
|
4004
|
+
lookup.add(normalizeJid(`${phoneDigits}@hosted`) || '');
|
|
4005
|
+
return Array.from(lookup).filter(Boolean);
|
|
4006
|
+
};
|
|
4007
|
+
|
|
4008
|
+
const appendMyProfileOwnerCandidate = (candidateSet, lookupSet, value) => {
|
|
4009
|
+
const normalized = normalizeJid(value) || '';
|
|
4010
|
+
if (!normalized || !normalized.includes('@')) return;
|
|
4011
|
+
|
|
4012
|
+
candidateSet.add(normalized);
|
|
4013
|
+
for (const lookupJid of buildOwnerLookupJids(normalized)) {
|
|
4014
|
+
lookupSet.add(lookupJid);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
const phoneOwner = toWhatsAppOwnerJid(value);
|
|
4018
|
+
if (phoneOwner) {
|
|
4019
|
+
candidateSet.add(phoneOwner);
|
|
4020
|
+
for (const lookupJid of buildOwnerLookupJids(phoneOwner)) {
|
|
4021
|
+
lookupSet.add(lookupJid);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
};
|
|
4025
|
+
|
|
4026
|
+
const buildPhoneSet = (...values) => {
|
|
4027
|
+
const set = new Set();
|
|
4028
|
+
for (const value of values) {
|
|
4029
|
+
const digits = toWhatsAppPhoneDigits(value);
|
|
4030
|
+
if (digits) set.add(digits);
|
|
4031
|
+
}
|
|
4032
|
+
return set;
|
|
4033
|
+
};
|
|
4034
|
+
|
|
4035
|
+
const resolveMyProfileOwnerCandidates = async (session) => {
|
|
4036
|
+
const candidates = new Set();
|
|
4037
|
+
const lookupByJid = new Set();
|
|
4038
|
+
const lidCandidates = new Set();
|
|
4039
|
+
const appendCandidate = (value) => appendMyProfileOwnerCandidate(candidates, lookupByJid, value);
|
|
4040
|
+
const trustedPhones = new Set();
|
|
4041
|
+
const blockedJids = new Set();
|
|
4042
|
+
const blockedPhones = new Set();
|
|
4043
|
+
|
|
4044
|
+
appendCandidate(session?.ownerJid);
|
|
4045
|
+
appendCandidate(toWhatsAppOwnerJid(session?.ownerPhone || session?.ownerJid));
|
|
4046
|
+
for (const phone of buildPhoneSet(session?.ownerPhone, session?.ownerJid)) {
|
|
4047
|
+
trustedPhones.add(phone);
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
const activeSocket = getActiveSocket();
|
|
4051
|
+
const botJid = normalizeJid(resolveBotJid(activeSocket?.user?.id || '') || '');
|
|
4052
|
+
if (botJid) {
|
|
4053
|
+
blockedJids.add(botJid);
|
|
4054
|
+
for (const phone of buildPhoneSet(botJid)) {
|
|
4055
|
+
blockedPhones.add(phone);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
|
|
4059
|
+
const legacyGoogleOwner = buildGoogleOwnerJid(session?.sub);
|
|
4060
|
+
if (legacyGoogleOwner) appendCandidate(legacyGoogleOwner);
|
|
4061
|
+
|
|
4062
|
+
const sessionResolved = await resolveUserId(extractUserIdInfo(session?.ownerJid || session?.ownerPhone || null)).catch(() => null);
|
|
4063
|
+
if (sessionResolved) {
|
|
4064
|
+
appendCandidate(sessionResolved);
|
|
4065
|
+
for (const phone of buildPhoneSet(sessionResolved)) {
|
|
4066
|
+
trustedPhones.add(phone);
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
const normalizedSub = normalizeGoogleSubject(session?.sub);
|
|
4071
|
+
if (normalizedSub) {
|
|
4072
|
+
try {
|
|
4073
|
+
const rows = await executeQuery(
|
|
4074
|
+
`SELECT owner_jid, owner_phone
|
|
4075
|
+
FROM ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
4076
|
+
WHERE google_sub = ?
|
|
4077
|
+
LIMIT 1`,
|
|
4078
|
+
[normalizedSub],
|
|
4079
|
+
);
|
|
4080
|
+
const row = Array.isArray(rows) ? rows[0] : null;
|
|
4081
|
+
appendCandidate(row?.owner_jid || '');
|
|
4082
|
+
appendCandidate(row?.owner_phone || '');
|
|
4083
|
+
const mappedResolved = await resolveUserId(extractUserIdInfo(row?.owner_jid || row?.owner_phone || null)).catch(() => null);
|
|
4084
|
+
if (mappedResolved) appendCandidate(mappedResolved);
|
|
4085
|
+
} catch (error) {
|
|
4086
|
+
logger.warn('Falha ao resolver owners para perfil web.', {
|
|
4087
|
+
action: 'sticker_pack_my_profile_owner_candidates_failed',
|
|
4088
|
+
google_sub: normalizedSub,
|
|
4089
|
+
error: error?.message,
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
for (const ownerJid of Array.from(candidates)) {
|
|
4095
|
+
const identity = extractUserIdInfo(ownerJid);
|
|
4096
|
+
if (identity?.lid) lidCandidates.add(identity.lid);
|
|
4097
|
+
if (!identity?.lid && !identity?.jid) continue;
|
|
4098
|
+
const resolved = await resolveUserId(identity).catch(() => null);
|
|
4099
|
+
if (!resolved) continue;
|
|
4100
|
+
appendCandidate(resolved);
|
|
4101
|
+
const resolvedIdentity = extractUserIdInfo(resolved);
|
|
4102
|
+
if (resolvedIdentity?.lid) lidCandidates.add(resolvedIdentity.lid);
|
|
4103
|
+
}
|
|
4104
|
+
|
|
4105
|
+
const lookupValues = Array.from(lookupByJid).filter(Boolean);
|
|
4106
|
+
for (let offset = 0; offset < lookupValues.length; offset += 200) {
|
|
4107
|
+
const chunk = lookupValues.slice(offset, offset + 200);
|
|
4108
|
+
if (!chunk.length) continue;
|
|
4109
|
+
const placeholders = chunk.map(() => '?').join(', ');
|
|
4110
|
+
const rows = await executeQuery(
|
|
4111
|
+
`SELECT lid, jid
|
|
4112
|
+
FROM ${TABLES.LID_MAP}
|
|
4113
|
+
WHERE jid IN (${placeholders})
|
|
4114
|
+
ORDER BY last_seen DESC
|
|
4115
|
+
LIMIT 500`,
|
|
4116
|
+
chunk,
|
|
4117
|
+
).catch(() => []);
|
|
4118
|
+
|
|
4119
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
4120
|
+
appendCandidate(row?.jid || '');
|
|
4121
|
+
const resolvedLid = normalizeJid(row?.lid || '');
|
|
4122
|
+
if (resolvedLid) lidCandidates.add(resolvedLid);
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
for (const lid of lidCandidates) {
|
|
4127
|
+
const resolved = await resolveUserId(extractUserIdInfo(lid)).catch(() => null);
|
|
4128
|
+
if (resolved) {
|
|
4129
|
+
appendCandidate(resolved);
|
|
4130
|
+
appendCandidate(lid);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
const filtered = [];
|
|
4135
|
+
for (const candidate of Array.from(candidates)) {
|
|
4136
|
+
const normalized = normalizeJid(candidate) || '';
|
|
4137
|
+
if (!normalized || !normalized.includes('@')) continue;
|
|
4138
|
+
if (blockedJids.has(normalized)) continue;
|
|
4139
|
+
|
|
4140
|
+
const directPhone = toWhatsAppPhoneDigits(normalized);
|
|
4141
|
+
if (directPhone && blockedPhones.has(directPhone)) continue;
|
|
4142
|
+
|
|
4143
|
+
const isGoogleOwner = normalized.endsWith('@google.oauth');
|
|
4144
|
+
if (trustedPhones.size === 0) {
|
|
4145
|
+
filtered.push(normalized);
|
|
4146
|
+
continue;
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
if (directPhone) {
|
|
4150
|
+
if (!trustedPhones.has(directPhone)) continue;
|
|
4151
|
+
filtered.push(normalized);
|
|
4152
|
+
continue;
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
const resolved = await resolveUserId(extractUserIdInfo(normalized)).catch(() => null);
|
|
4156
|
+
const resolvedPhone = toWhatsAppPhoneDigits(resolved || '');
|
|
4157
|
+
if (resolvedPhone) {
|
|
4158
|
+
if (!trustedPhones.has(resolvedPhone) || blockedPhones.has(resolvedPhone)) continue;
|
|
4159
|
+
filtered.push(normalized);
|
|
4160
|
+
continue;
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
if (isGoogleOwner) {
|
|
4164
|
+
filtered.push(normalized);
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
return Array.from(new Set(filtered));
|
|
4169
|
+
};
|
|
4170
|
+
|
|
4171
|
+
const handleMyProfileRequest = async (req, res, url = null) => {
|
|
4172
|
+
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
4173
|
+
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
3644
4176
|
|
|
3645
4177
|
const session = await resolveGoogleWebSessionFromRequest(req);
|
|
3646
4178
|
const authGoogle = {
|
|
@@ -3669,7 +4201,65 @@ const handleMyProfileRequest = async (req, res) => {
|
|
|
3669
4201
|
return;
|
|
3670
4202
|
}
|
|
3671
4203
|
|
|
3672
|
-
const
|
|
4204
|
+
const ownerCandidates = await resolveMyProfileOwnerCandidates(session);
|
|
4205
|
+
if (!ownerCandidates.length) {
|
|
4206
|
+
sendJson(req, res, 200, {
|
|
4207
|
+
data: {
|
|
4208
|
+
auth: { google: authGoogle },
|
|
4209
|
+
session: mapGoogleSessionResponseData(session),
|
|
4210
|
+
owner_jid: session.ownerJid,
|
|
4211
|
+
owner_jids: [],
|
|
4212
|
+
packs: [],
|
|
4213
|
+
stats: {
|
|
4214
|
+
total: 0,
|
|
4215
|
+
published: 0,
|
|
4216
|
+
drafts: 0,
|
|
4217
|
+
private: 0,
|
|
4218
|
+
unlisted: 0,
|
|
4219
|
+
public: 0,
|
|
4220
|
+
},
|
|
4221
|
+
},
|
|
4222
|
+
});
|
|
4223
|
+
return;
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
const ownerPacks = await Promise.all(
|
|
4227
|
+
ownerCandidates.map((ownerJid) => listStickerPacksByOwner(ownerJid, { limit: 200, offset: 0 })),
|
|
4228
|
+
);
|
|
4229
|
+
const includeAutoPacks = parseEnvBool(
|
|
4230
|
+
url?.searchParams?.get('include_auto'),
|
|
4231
|
+
parseEnvBool(process.env.STICKER_WEB_MY_PROFILE_INCLUDE_AUTO_PACKS, false),
|
|
4232
|
+
);
|
|
4233
|
+
|
|
4234
|
+
const dedupPacks = new Map();
|
|
4235
|
+
for (const packList of ownerPacks) {
|
|
4236
|
+
for (const pack of Array.isArray(packList) ? packList : []) {
|
|
4237
|
+
if (!pack?.id) continue;
|
|
4238
|
+
if (!includeAutoPacks && pack?.is_auto_pack === true) continue;
|
|
4239
|
+
const existing = dedupPacks.get(pack.id);
|
|
4240
|
+
if (!existing) {
|
|
4241
|
+
dedupPacks.set(pack.id, pack);
|
|
4242
|
+
continue;
|
|
4243
|
+
}
|
|
4244
|
+
const currentUpdatedAt = Date.parse(String(pack.updated_at || pack.created_at || ''));
|
|
4245
|
+
const existingUpdatedAt = Date.parse(String(existing.updated_at || existing.created_at || ''));
|
|
4246
|
+
if (Number.isFinite(currentUpdatedAt) && (!Number.isFinite(existingUpdatedAt) || currentUpdatedAt > existingUpdatedAt)) {
|
|
4247
|
+
dedupPacks.set(pack.id, pack);
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
|
|
4252
|
+
const packs = Array.from(dedupPacks.values())
|
|
4253
|
+
.sort((a, b) => {
|
|
4254
|
+
const aUpdatedAt = Date.parse(String(a?.updated_at || a?.created_at || ''));
|
|
4255
|
+
const bUpdatedAt = Date.parse(String(b?.updated_at || b?.created_at || ''));
|
|
4256
|
+
if (!Number.isFinite(aUpdatedAt) && !Number.isFinite(bUpdatedAt)) return 0;
|
|
4257
|
+
if (!Number.isFinite(aUpdatedAt)) return 1;
|
|
4258
|
+
if (!Number.isFinite(bUpdatedAt)) return -1;
|
|
4259
|
+
return bUpdatedAt - aUpdatedAt;
|
|
4260
|
+
})
|
|
4261
|
+
.slice(0, 300);
|
|
4262
|
+
|
|
3673
4263
|
const engagementByPackId = await listStickerPackEngagementByPackIds(packs.map((pack) => pack.id));
|
|
3674
4264
|
|
|
3675
4265
|
const mappedPacks = packs.map((pack) => {
|
|
@@ -3702,6 +4292,7 @@ const handleMyProfileRequest = async (req, res) => {
|
|
|
3702
4292
|
auth: { google: authGoogle },
|
|
3703
4293
|
session: mapGoogleSessionResponseData(session),
|
|
3704
4294
|
owner_jid: session.ownerJid,
|
|
4295
|
+
owner_jids: ownerCandidates,
|
|
3705
4296
|
packs: mappedPacks,
|
|
3706
4297
|
stats,
|
|
3707
4298
|
},
|
|
@@ -3715,6 +4306,9 @@ const invalidateStickerCatalogDerivedCaches = () => {
|
|
|
3715
4306
|
GLOBAL_RANK_CACHE.value = null;
|
|
3716
4307
|
GLOBAL_RANK_CACHE.pending = null;
|
|
3717
4308
|
HOME_MARKETPLACE_STATS_CACHE.clear();
|
|
4309
|
+
CATALOG_LIST_CACHE.clear();
|
|
4310
|
+
CATALOG_CREATOR_RANKING_CACHE.clear();
|
|
4311
|
+
CATALOG_PACK_PAYLOAD_CACHE.clear();
|
|
3718
4312
|
SYSTEM_SUMMARY_CACHE.expiresAt = 0;
|
|
3719
4313
|
SYSTEM_SUMMARY_CACHE.value = null;
|
|
3720
4314
|
SYSTEM_SUMMARY_CACHE.pending = null;
|
|
@@ -3891,23 +4485,39 @@ const loadOwnedPackForWebManagement = async (req, res, packKey, { allowMissing =
|
|
|
3891
4485
|
return null;
|
|
3892
4486
|
}
|
|
3893
4487
|
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
4488
|
+
const ownerCandidatesRaw = await resolveMyProfileOwnerCandidates(session).catch(() => []);
|
|
4489
|
+
const ownerCandidates = Array.from(new Set([normalizeJid(session.ownerJid) || '', ...ownerCandidatesRaw].filter(Boolean)));
|
|
4490
|
+
const fallbackOwnerJid = ownerCandidates[0] || normalizeJid(session.ownerJid) || '';
|
|
4491
|
+
|
|
4492
|
+
for (const ownerJid of ownerCandidates) {
|
|
4493
|
+
try {
|
|
4494
|
+
const pack = await stickerPackService.getPackInfo({
|
|
4495
|
+
ownerJid,
|
|
4496
|
+
identifier: normalizedPackKey,
|
|
4497
|
+
});
|
|
4498
|
+
return { session, ownerJid, ownerCandidates, packKey: normalizedPackKey, pack };
|
|
4499
|
+
} catch (error) {
|
|
4500
|
+
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.PACK_NOT_FOUND) {
|
|
4501
|
+
continue;
|
|
4502
|
+
}
|
|
4503
|
+
const mapped = mapStickerPackWebManageError(error);
|
|
4504
|
+
sendJson(req, res, mapped.statusCode, {
|
|
4505
|
+
error: mapped.message,
|
|
4506
|
+
code: mapped.code,
|
|
4507
|
+
});
|
|
4508
|
+
return null;
|
|
3903
4509
|
}
|
|
3904
|
-
const mapped = mapStickerPackWebManageError(error);
|
|
3905
|
-
sendJson(req, res, mapped.statusCode, {
|
|
3906
|
-
error: mapped.message,
|
|
3907
|
-
code: mapped.code,
|
|
3908
|
-
});
|
|
3909
|
-
return null;
|
|
3910
4510
|
}
|
|
4511
|
+
|
|
4512
|
+
if (allowMissing) {
|
|
4513
|
+
return { session, ownerJid: fallbackOwnerJid, ownerCandidates, packKey: normalizedPackKey, pack: null, missing: true };
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
sendJson(req, res, 404, {
|
|
4517
|
+
error: 'Pack nao encontrado para este usuario.',
|
|
4518
|
+
code: STICKER_PACK_ERROR_CODES.PACK_NOT_FOUND,
|
|
4519
|
+
});
|
|
4520
|
+
return null;
|
|
3911
4521
|
};
|
|
3912
4522
|
|
|
3913
4523
|
const buildManagedPackAnalytics = async (pack) => {
|
|
@@ -3990,7 +4600,7 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
3990
4600
|
const isMutableMethod = req.method === 'PATCH' || req.method === 'DELETE';
|
|
3991
4601
|
const context = await loadOwnedPackForWebManagement(req, res, packKey, { allowMissing: isMutableMethod });
|
|
3992
4602
|
if (!context) return;
|
|
3993
|
-
const {
|
|
4603
|
+
const { packKey: normalizedPackKey } = context;
|
|
3994
4604
|
|
|
3995
4605
|
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
3996
4606
|
await sendManagedPackResponse(req, res, context.pack);
|
|
@@ -4008,7 +4618,7 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
4008
4618
|
|
|
4009
4619
|
try {
|
|
4010
4620
|
const result = await deleteManagedPackWithCleanup({
|
|
4011
|
-
ownerJid:
|
|
4621
|
+
ownerJid: context.ownerJid,
|
|
4012
4622
|
identifier: normalizedPackKey,
|
|
4013
4623
|
fallbackPack: context.pack,
|
|
4014
4624
|
});
|
|
@@ -4059,13 +4669,13 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
4059
4669
|
const currentName = sanitizeText(updatedPack?.name, PACK_CREATE_MAX_NAME_LENGTH, { allowEmpty: false });
|
|
4060
4670
|
if (!nextName) {
|
|
4061
4671
|
updatedPack = await stickerPackService.renamePack({
|
|
4062
|
-
ownerJid:
|
|
4672
|
+
ownerJid: context.ownerJid,
|
|
4063
4673
|
identifier: normalizedPackKey,
|
|
4064
4674
|
name: payload.name,
|
|
4065
4675
|
});
|
|
4066
4676
|
} else if (nextName !== currentName) {
|
|
4067
4677
|
updatedPack = await stickerPackService.renamePack({
|
|
4068
|
-
ownerJid:
|
|
4678
|
+
ownerJid: context.ownerJid,
|
|
4069
4679
|
identifier: normalizedPackKey,
|
|
4070
4680
|
name: payload.name,
|
|
4071
4681
|
});
|
|
@@ -4078,13 +4688,13 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
4078
4688
|
const currentPublisher = sanitizeText(updatedPack?.publisher, PACK_CREATE_MAX_PUBLISHER_LENGTH, { allowEmpty: false });
|
|
4079
4689
|
if (!nextPublisher) {
|
|
4080
4690
|
updatedPack = await stickerPackService.setPackPublisher({
|
|
4081
|
-
ownerJid:
|
|
4691
|
+
ownerJid: context.ownerJid,
|
|
4082
4692
|
identifier: normalizedPackKey,
|
|
4083
4693
|
publisher: payload.publisher,
|
|
4084
4694
|
});
|
|
4085
4695
|
} else if (nextPublisher !== currentPublisher) {
|
|
4086
4696
|
updatedPack = await stickerPackService.setPackPublisher({
|
|
4087
|
-
ownerJid:
|
|
4697
|
+
ownerJid: context.ownerJid,
|
|
4088
4698
|
identifier: normalizedPackKey,
|
|
4089
4699
|
publisher: payload.publisher,
|
|
4090
4700
|
});
|
|
@@ -4097,13 +4707,13 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
4097
4707
|
const currentVisibility = String(updatedPack?.visibility || '').trim().toLowerCase();
|
|
4098
4708
|
if (!nextVisibility) {
|
|
4099
4709
|
updatedPack = await stickerPackService.setPackVisibility({
|
|
4100
|
-
ownerJid:
|
|
4710
|
+
ownerJid: context.ownerJid,
|
|
4101
4711
|
identifier: normalizedPackKey,
|
|
4102
4712
|
visibility: payload.visibility,
|
|
4103
4713
|
});
|
|
4104
4714
|
} else if (nextVisibility !== currentVisibility) {
|
|
4105
4715
|
updatedPack = await stickerPackService.setPackVisibility({
|
|
4106
|
-
ownerJid:
|
|
4716
|
+
ownerJid: context.ownerJid,
|
|
4107
4717
|
identifier: normalizedPackKey,
|
|
4108
4718
|
visibility: payload.visibility,
|
|
4109
4719
|
});
|
|
@@ -4119,7 +4729,7 @@ const handleManagedPackRequest = async (req, res, packKey) => {
|
|
|
4119
4729
|
const currentDescriptionWithTags = buildPackDescriptionWithTags(currentMeta.cleanDescription || '', currentMeta.tags);
|
|
4120
4730
|
if (String(descriptionWithTags || '') !== String(currentDescriptionWithTags || '')) {
|
|
4121
4731
|
updatedPack = await stickerPackService.setPackDescription({
|
|
4122
|
-
ownerJid:
|
|
4732
|
+
ownerJid: context.ownerJid,
|
|
4123
4733
|
identifier: normalizedPackKey,
|
|
4124
4734
|
description: descriptionWithTags || '',
|
|
4125
4735
|
});
|
|
@@ -4167,7 +4777,7 @@ const handleManagedPackCloneRequest = async (req, res, packKey) => {
|
|
|
4167
4777
|
|
|
4168
4778
|
try {
|
|
4169
4779
|
const cloned = await stickerPackService.clonePack({
|
|
4170
|
-
ownerJid: context.
|
|
4780
|
+
ownerJid: context.ownerJid,
|
|
4171
4781
|
identifier: context.packKey,
|
|
4172
4782
|
newName,
|
|
4173
4783
|
});
|
|
@@ -4205,7 +4815,7 @@ const handleManagedPackCoverRequest = async (req, res, packKey) => {
|
|
|
4205
4815
|
|
|
4206
4816
|
try {
|
|
4207
4817
|
const updated = await stickerPackService.setPackCover({
|
|
4208
|
-
ownerJid: context.
|
|
4818
|
+
ownerJid: context.ownerJid,
|
|
4209
4819
|
identifier: context.packKey,
|
|
4210
4820
|
stickerId: payload?.sticker_id,
|
|
4211
4821
|
});
|
|
@@ -4218,7 +4828,7 @@ const handleManagedPackCoverRequest = async (req, res, packKey) => {
|
|
|
4218
4828
|
}
|
|
4219
4829
|
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND) {
|
|
4220
4830
|
const fresh = await stickerPackService
|
|
4221
|
-
.getPackInfo({ ownerJid: context.
|
|
4831
|
+
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4222
4832
|
.catch(() => context.pack);
|
|
4223
4833
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
4224
4834
|
pack_key: context.packKey,
|
|
@@ -4270,7 +4880,7 @@ const handleManagedPackReorderRequest = async (req, res, packKey) => {
|
|
|
4270
4880
|
|
|
4271
4881
|
try {
|
|
4272
4882
|
const updated = await stickerPackService.reorderPackItems({
|
|
4273
|
-
ownerJid: context.
|
|
4883
|
+
ownerJid: context.ownerJid,
|
|
4274
4884
|
identifier: context.packKey,
|
|
4275
4885
|
orderStickerIds: requestedOrderIds,
|
|
4276
4886
|
});
|
|
@@ -4283,7 +4893,7 @@ const handleManagedPackReorderRequest = async (req, res, packKey) => {
|
|
|
4283
4893
|
}
|
|
4284
4894
|
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.INVALID_INPUT) {
|
|
4285
4895
|
const fresh = await stickerPackService
|
|
4286
|
-
.getPackInfo({ ownerJid: context.
|
|
4896
|
+
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4287
4897
|
.catch(() => context.pack);
|
|
4288
4898
|
await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
|
|
4289
4899
|
pack_key: context.packKey,
|
|
@@ -4313,7 +4923,7 @@ const handleManagedPackStickerDeleteRequest = async (req, res, packKey, stickerI
|
|
|
4313
4923
|
|
|
4314
4924
|
try {
|
|
4315
4925
|
const result = await stickerPackService.removeStickerFromPack({
|
|
4316
|
-
ownerJid: context.
|
|
4926
|
+
ownerJid: context.ownerJid,
|
|
4317
4927
|
identifier: context.packKey,
|
|
4318
4928
|
selector: stickerId,
|
|
4319
4929
|
});
|
|
@@ -4336,7 +4946,7 @@ const handleManagedPackStickerDeleteRequest = async (req, res, packKey, stickerI
|
|
|
4336
4946
|
}
|
|
4337
4947
|
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND) {
|
|
4338
4948
|
const fresh = await stickerPackService
|
|
4339
|
-
.getPackInfo({ ownerJid: context.
|
|
4949
|
+
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4340
4950
|
.catch(() => context.pack);
|
|
4341
4951
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
4342
4952
|
pack_key: context.packKey,
|
|
@@ -4390,19 +5000,19 @@ const handleManagedPackStickerCreateRequest = async (req, res, packKey) => {
|
|
|
4390
5000
|
let uploadedAssetId = '';
|
|
4391
5001
|
try {
|
|
4392
5002
|
const normalizedUpload = await convertUploadMediaToWebp({
|
|
4393
|
-
ownerJid: context.
|
|
5003
|
+
ownerJid: context.ownerJid,
|
|
4394
5004
|
buffer: decoded.buffer,
|
|
4395
5005
|
mimetype: decoded.mimetype || 'image/webp',
|
|
4396
5006
|
});
|
|
4397
5007
|
const asset = await saveStickerAssetFromBuffer({
|
|
4398
|
-
ownerJid: context.
|
|
5008
|
+
ownerJid: context.ownerJid,
|
|
4399
5009
|
buffer: normalizedUpload.buffer,
|
|
4400
5010
|
mimetype: normalizedUpload.mimetype || 'image/webp',
|
|
4401
5011
|
});
|
|
4402
5012
|
uploadedAssetId = String(asset?.id || '').trim();
|
|
4403
5013
|
|
|
4404
5014
|
let updatedPack = await stickerPackService.addStickerToPack({
|
|
4405
|
-
ownerJid: context.
|
|
5015
|
+
ownerJid: context.ownerJid,
|
|
4406
5016
|
identifier: context.packKey,
|
|
4407
5017
|
asset: { id: uploadedAssetId },
|
|
4408
5018
|
emojis: [],
|
|
@@ -4411,7 +5021,7 @@ const handleManagedPackStickerCreateRequest = async (req, res, packKey) => {
|
|
|
4411
5021
|
|
|
4412
5022
|
if (payload?.set_cover === true) {
|
|
4413
5023
|
updatedPack = await stickerPackService.setPackCover({
|
|
4414
|
-
ownerJid: context.
|
|
5024
|
+
ownerJid: context.ownerJid,
|
|
4415
5025
|
identifier: context.packKey,
|
|
4416
5026
|
stickerId: uploadedAssetId,
|
|
4417
5027
|
});
|
|
@@ -4490,7 +5100,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
4490
5100
|
let uploadedAssetId = '';
|
|
4491
5101
|
try {
|
|
4492
5102
|
const originalPack = await stickerPackService.getPackInfo({
|
|
4493
|
-
ownerJid: context.
|
|
5103
|
+
ownerJid: context.ownerJid,
|
|
4494
5104
|
identifier: context.packKey,
|
|
4495
5105
|
});
|
|
4496
5106
|
const originalItems = Array.isArray(originalPack?.items) ? originalPack.items : [];
|
|
@@ -4504,12 +5114,12 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
4504
5114
|
}
|
|
4505
5115
|
|
|
4506
5116
|
const normalizedUpload = await convertUploadMediaToWebp({
|
|
4507
|
-
ownerJid: context.
|
|
5117
|
+
ownerJid: context.ownerJid,
|
|
4508
5118
|
buffer: decoded.buffer,
|
|
4509
5119
|
mimetype: decoded.mimetype || 'image/webp',
|
|
4510
5120
|
});
|
|
4511
5121
|
const asset = await saveStickerAssetFromBuffer({
|
|
4512
|
-
ownerJid: context.
|
|
5122
|
+
ownerJid: context.ownerJid,
|
|
4513
5123
|
buffer: normalizedUpload.buffer,
|
|
4514
5124
|
mimetype: normalizedUpload.mimetype || 'image/webp',
|
|
4515
5125
|
});
|
|
@@ -4525,7 +5135,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
4525
5135
|
}
|
|
4526
5136
|
|
|
4527
5137
|
const swapResult = await runSqlTransaction(async (connection) => {
|
|
4528
|
-
const packRow = await findStickerPackByOwnerAndIdentifier(context.
|
|
5138
|
+
const packRow = await findStickerPackByOwnerAndIdentifier(context.ownerJid, context.packKey, { connection });
|
|
4529
5139
|
if (!packRow) return { status: 'pack_missing' };
|
|
4530
5140
|
|
|
4531
5141
|
const liveOldItem = await getStickerPackItemByStickerId(packRow.id, normalizedStickerId, connection);
|
|
@@ -4577,7 +5187,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
4577
5187
|
|
|
4578
5188
|
if (swapResult?.status === 'old_sticker_missing') {
|
|
4579
5189
|
const fresh = await stickerPackService
|
|
4580
|
-
.getPackInfo({ ownerJid: context.
|
|
5190
|
+
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4581
5191
|
.catch(() => originalPack);
|
|
4582
5192
|
await cleanupOrphanStickerAssets(uploadedAssetId ? [uploadedAssetId] : [], { reason: 'replace_sticker_old_missing' });
|
|
4583
5193
|
await sendManagedPackMutationStatus(req, res, 'already_deleted', fresh, {
|
|
@@ -4589,7 +5199,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
4589
5199
|
|
|
4590
5200
|
if (swapResult?.status === 'duplicate_target') {
|
|
4591
5201
|
const fresh = await stickerPackService
|
|
4592
|
-
.getPackInfo({ ownerJid: context.
|
|
5202
|
+
.getPackInfo({ ownerJid: context.ownerJid, identifier: context.packKey })
|
|
4593
5203
|
.catch(() => originalPack);
|
|
4594
5204
|
await sendManagedPackMutationStatus(req, res, 'noop', fresh, {
|
|
4595
5205
|
pack_key: context.packKey,
|
|
@@ -4602,7 +5212,7 @@ const handleManagedPackStickerReplaceRequest = async (req, res, packKey, sticker
|
|
|
4602
5212
|
|
|
4603
5213
|
invalidateStickerCatalogDerivedCaches();
|
|
4604
5214
|
const finalPack = await stickerPackService.getPackInfo({
|
|
4605
|
-
ownerJid: context.
|
|
5215
|
+
ownerJid: context.ownerJid,
|
|
4606
5216
|
identifier: context.packKey,
|
|
4607
5217
|
});
|
|
4608
5218
|
await cleanupOrphanStickerAssets([normalizedStickerId], { reason: 'replace_sticker_old_cleanup' });
|
|
@@ -4715,55 +5325,37 @@ const handleCreatePackRequest = async (req, res) => {
|
|
|
4715
5325
|
const manualTags = mergeUniqueTags(Array.isArray(payload?.tags) ? payload.tags : []).slice(0, 8);
|
|
4716
5326
|
const persistedDescription = buildPackDescriptionWithTags(description, manualTags);
|
|
4717
5327
|
const visibility = String(payload?.visibility || 'public').trim().toLowerCase();
|
|
4718
|
-
const explicitOwnerJid = toOwnerJid(payload?.owner_jid);
|
|
4719
5328
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
4720
|
-
|
|
5329
|
+
if (!googleSession?.ownerJid || !googleSession?.sub) {
|
|
5330
|
+
sendJson(req, res, 401, {
|
|
5331
|
+
error: 'Sessão expirada ou ausente. Faça login novamente para criar packs.',
|
|
5332
|
+
code: STICKER_PACK_ERROR_CODES.NOT_ALLOWED,
|
|
5333
|
+
});
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
4721
5336
|
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
ownerJid: googleSession.ownerJid,
|
|
5337
|
+
try {
|
|
5338
|
+
await assertGoogleIdentityNotBanned({
|
|
4725
5339
|
sub: googleSession.sub,
|
|
4726
5340
|
email: googleSession.email,
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
const googleOwnerJid = buildGoogleOwnerJid(googleClaims.sub);
|
|
4734
|
-
await assertGoogleIdentityNotBanned({
|
|
4735
|
-
sub: googleClaims.sub,
|
|
4736
|
-
email: googleClaims.email,
|
|
4737
|
-
ownerJid: googleOwnerJid,
|
|
4738
|
-
});
|
|
4739
|
-
if (!googleOwnerJid) {
|
|
4740
|
-
sendJson(req, res, 400, {
|
|
4741
|
-
error: 'Não foi possível vincular a conta Google ao criador.',
|
|
4742
|
-
code: STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
4743
|
-
});
|
|
4744
|
-
return;
|
|
4745
|
-
}
|
|
4746
|
-
googleCreator = {
|
|
4747
|
-
ownerJid: googleOwnerJid,
|
|
4748
|
-
...googleClaims,
|
|
4749
|
-
};
|
|
4750
|
-
} catch (error) {
|
|
4751
|
-
sendJson(req, res, Number(error?.statusCode || 401), {
|
|
4752
|
-
error: error?.message || 'Login Google inválido.',
|
|
4753
|
-
code: STICKER_PACK_ERROR_CODES.NOT_ALLOWED,
|
|
4754
|
-
});
|
|
4755
|
-
return;
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
|
|
4759
|
-
if (STICKER_WEB_GOOGLE_AUTH_REQUIRED && !googleCreator) {
|
|
4760
|
-
sendJson(req, res, 400, {
|
|
4761
|
-
error: 'Faça login com Google para criar packs nesta página.',
|
|
4762
|
-
code: STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
5341
|
+
ownerJid: googleSession.ownerJid,
|
|
5342
|
+
});
|
|
5343
|
+
} catch (error) {
|
|
5344
|
+
sendJson(req, res, Number(error?.statusCode || 403), {
|
|
5345
|
+
error: error?.message || 'Conta sem permissão para criar packs.',
|
|
5346
|
+
code: STICKER_PACK_ERROR_CODES.NOT_ALLOWED,
|
|
4763
5347
|
});
|
|
4764
5348
|
return;
|
|
4765
5349
|
}
|
|
4766
5350
|
|
|
5351
|
+
const googleCreator = {
|
|
5352
|
+
ownerJid: googleSession.ownerJid,
|
|
5353
|
+
sub: googleSession.sub,
|
|
5354
|
+
email: googleSession.email,
|
|
5355
|
+
name: googleSession.name,
|
|
5356
|
+
picture: googleSession.picture,
|
|
5357
|
+
};
|
|
5358
|
+
|
|
4767
5359
|
if (googleCreator?.sub && googleCreator?.ownerJid) {
|
|
4768
5360
|
await upsertGoogleWebUserRecord({
|
|
4769
5361
|
sub: googleCreator.sub,
|
|
@@ -4779,17 +5371,7 @@ const handleCreatePackRequest = async (req, res) => {
|
|
|
4779
5371
|
});
|
|
4780
5372
|
}
|
|
4781
5373
|
|
|
4782
|
-
const ownerJid = googleCreator
|
|
4783
|
-
|
|
4784
|
-
if (!ownerJid) {
|
|
4785
|
-
sendJson(req, res, 400, {
|
|
4786
|
-
error: STICKER_WEB_GOOGLE_AUTH_REQUIRED
|
|
4787
|
-
? 'Faça login com Google para criar packs nesta página.'
|
|
4788
|
-
: 'Não foi possível resolver owner_jid para criar o pack.',
|
|
4789
|
-
code: STICKER_PACK_ERROR_CODES.INVALID_INPUT,
|
|
4790
|
-
});
|
|
4791
|
-
return;
|
|
4792
|
-
}
|
|
5374
|
+
const ownerJid = googleCreator.ownerJid;
|
|
4793
5375
|
|
|
4794
5376
|
try {
|
|
4795
5377
|
logPackWebFlow('info', 'create_pack_start', {
|
|
@@ -5390,46 +5972,63 @@ const handleCreatorRankingRequest = async (req, res, url) => {
|
|
|
5390
5972
|
const limit = clampInt(url.searchParams.get('limit'), 50, 5, 200);
|
|
5391
5973
|
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
5392
5974
|
const hasNsfwAccess = Boolean(googleSession?.sub && googleSession?.ownerJid);
|
|
5393
|
-
|
|
5394
|
-
|
|
5975
|
+
const cacheKey = buildCacheKey([
|
|
5976
|
+
'creator_ranking',
|
|
5395
5977
|
visibility,
|
|
5396
|
-
|
|
5397
|
-
limit
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
const
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5978
|
+
q,
|
|
5979
|
+
limit,
|
|
5980
|
+
hasNsfwAccess ? 1 : 0,
|
|
5981
|
+
]);
|
|
5982
|
+
const payload = await getCachedSnapshot({
|
|
5983
|
+
cacheMap: CATALOG_CREATOR_RANKING_CACHE,
|
|
5984
|
+
key: cacheKey,
|
|
5985
|
+
ttlSeconds: CATALOG_CREATOR_RANKING_CACHE_SECONDS,
|
|
5986
|
+
staleWhileRefresh: true,
|
|
5987
|
+
staleOnError: true,
|
|
5988
|
+
load: async () => {
|
|
5989
|
+
const { packs } = await listStickerPacksForCatalog({
|
|
5990
|
+
visibility,
|
|
5991
|
+
search: q,
|
|
5992
|
+
limit: 120,
|
|
5993
|
+
offset: 0,
|
|
5994
|
+
});
|
|
5995
|
+
const driftSnapshot = await getMarketplaceDriftSnapshot();
|
|
5996
|
+
const { entries } = await hydrateMarketplaceEntries(packs, { driftSnapshot });
|
|
5997
|
+
const ranking = buildCreatorRanking(
|
|
5998
|
+
STICKER_CATALOG_ONLY_CLASSIFIED ? entries.filter((entry) => isPackClassified(entry.packClassification)) : entries,
|
|
5999
|
+
{ limit },
|
|
6000
|
+
);
|
|
5406
6001
|
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
6002
|
+
return {
|
|
6003
|
+
data: ranking.map((creator) => ({
|
|
6004
|
+
creator_score: Number(
|
|
6005
|
+
(
|
|
6006
|
+
Number(creator.avg_pack_score || 0) * 0.45 +
|
|
6007
|
+
Number(creator.total_likes || 0) * 0.0008 +
|
|
6008
|
+
Number(creator.total_opens || 0) * 0.00015
|
|
6009
|
+
).toFixed(6),
|
|
6010
|
+
),
|
|
6011
|
+
publisher: creator.publisher,
|
|
6012
|
+
verified: Boolean(creator.verified),
|
|
6013
|
+
badges: creator.verified ? ['verified_creator'] : [],
|
|
6014
|
+
stats: {
|
|
6015
|
+
packs_count: Number(creator.packs_count || 0),
|
|
6016
|
+
total_likes: Number(creator.total_likes || 0),
|
|
6017
|
+
total_opens: Number(creator.total_opens || 0),
|
|
6018
|
+
avg_pack_score: Number(creator.avg_pack_score || 0),
|
|
6019
|
+
},
|
|
6020
|
+
top_pack: creator.top_pack ? toSummaryEntry(creator.top_pack, { hideSensitiveCover: !hasNsfwAccess }) : null,
|
|
6021
|
+
})),
|
|
6022
|
+
filters: {
|
|
6023
|
+
visibility,
|
|
6024
|
+
q,
|
|
6025
|
+
limit,
|
|
6026
|
+
},
|
|
6027
|
+
};
|
|
5431
6028
|
},
|
|
5432
6029
|
});
|
|
6030
|
+
|
|
6031
|
+
sendJson(req, res, 200, payload);
|
|
5433
6032
|
};
|
|
5434
6033
|
|
|
5435
6034
|
const handleRecommendationsRequest = async (req, res, url) => {
|
|
@@ -5581,6 +6180,7 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
5581
6180
|
|
|
5582
6181
|
const socketReadyState = Number(activeSocket?.ws?.readyState);
|
|
5583
6182
|
const botJid = resolveBotJid(activeSocket?.user?.id) || null;
|
|
6183
|
+
const botPhone = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '') || null;
|
|
5584
6184
|
const botConnected = Boolean(botJid) && socketReadyState === 1;
|
|
5585
6185
|
const botConnectionStatus = botConnected
|
|
5586
6186
|
? 'online'
|
|
@@ -5646,6 +6246,7 @@ const buildSystemSummarySnapshot = async () => {
|
|
|
5646
6246
|
connected: botConnected,
|
|
5647
6247
|
connection_status: botConnectionStatus,
|
|
5648
6248
|
jid: botJid,
|
|
6249
|
+
phone: botPhone,
|
|
5649
6250
|
ready_state: Number.isFinite(socketReadyState) ? socketReadyState : null,
|
|
5650
6251
|
},
|
|
5651
6252
|
platform,
|
|
@@ -6510,6 +7111,15 @@ const handleSupportInfoRequest = async (req, res) => {
|
|
|
6510
7111
|
sendJson(req, res, 200, { data });
|
|
6511
7112
|
};
|
|
6512
7113
|
|
|
7114
|
+
const handleBotContactInfoRequest = async (req, res) => {
|
|
7115
|
+
const data = buildBotContactInfo();
|
|
7116
|
+
if (!data) {
|
|
7117
|
+
sendJson(req, res, 404, { error: 'Contato do bot indisponivel no momento.' });
|
|
7118
|
+
return;
|
|
7119
|
+
}
|
|
7120
|
+
sendJson(req, res, 200, { data });
|
|
7121
|
+
};
|
|
7122
|
+
|
|
6513
7123
|
const handlePublicDataAssetRequest = async (req, res, pathname) => {
|
|
6514
7124
|
const suffix = pathname.slice(STICKER_DATA_PUBLIC_PATH.length).replace(/^\/+/, '');
|
|
6515
7125
|
if (!suffix) {
|
|
@@ -6560,42 +7170,60 @@ const handlePublicDataAssetRequest = async (req, res, pathname) => {
|
|
|
6560
7170
|
};
|
|
6561
7171
|
|
|
6562
7172
|
const fetchPublicPackPayload = async (normalizedPackKey) => {
|
|
6563
|
-
const
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
7173
|
+
const cacheKey = buildCacheKey(['pack_payload', normalizedPackKey]);
|
|
7174
|
+
return getCachedSnapshot({
|
|
7175
|
+
cacheMap: CATALOG_PACK_PAYLOAD_CACHE,
|
|
7176
|
+
key: cacheKey,
|
|
7177
|
+
ttlSeconds: CATALOG_PACK_PAYLOAD_CACHE_SECONDS,
|
|
7178
|
+
staleWhileRefresh: true,
|
|
7179
|
+
staleOnError: true,
|
|
7180
|
+
load: async () => {
|
|
7181
|
+
const pack = await findStickerPackByPackKey(normalizedPackKey);
|
|
7182
|
+
if (!pack || !isPackPubliclyVisible(pack)) return null;
|
|
7183
|
+
|
|
7184
|
+
const items = await listStickerPackItems(pack.id);
|
|
7185
|
+
const stickerIds = items.map((item) => item.sticker_id);
|
|
7186
|
+
const [classifications, packClassification, engagement] = await Promise.all([
|
|
7187
|
+
listStickerClassificationsByAssetIds(stickerIds),
|
|
7188
|
+
getPackClassificationSummaryByAssetIds(stickerIds),
|
|
7189
|
+
getStickerPackEngagementByPackId(pack.id),
|
|
7190
|
+
]);
|
|
7191
|
+
|
|
7192
|
+
if (STICKER_CATALOG_ONLY_CLASSIFIED && !isPackClassified(packClassification)) {
|
|
7193
|
+
return null;
|
|
7194
|
+
}
|
|
6573
7195
|
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
7196
|
+
const [interactionStatsByPack, driftSnapshot, snapshotByPackId] = await Promise.all([
|
|
7197
|
+
listStickerPackInteractionStatsByPackIds([pack.id]),
|
|
7198
|
+
getMarketplaceDriftSnapshot(),
|
|
7199
|
+
canUseRankingSnapshotRead(`pack_payload:${pack.id}`)
|
|
7200
|
+
.then((enabled) => (enabled ? listStickerPackScoreSnapshotsByPackIds([pack.id]) : new Map()))
|
|
7201
|
+
.catch(() => new Map()),
|
|
7202
|
+
]);
|
|
7203
|
+
const byAssetClassification = new Map(classifications.map((entry) => [entry.asset_id, entry]));
|
|
7204
|
+
const orderedClassifications = stickerIds.map((stickerId) => byAssetClassification.get(stickerId)).filter(Boolean);
|
|
7205
|
+
const snapshot = snapshotByPackId.get(pack.id);
|
|
7206
|
+
const signals = snapshot?.signals
|
|
7207
|
+
? snapshot.signals
|
|
7208
|
+
: computePackSignals({
|
|
7209
|
+
pack: { ...pack, items },
|
|
7210
|
+
engagement,
|
|
7211
|
+
packClassification,
|
|
7212
|
+
itemClassifications: orderedClassifications,
|
|
7213
|
+
interactionStats: interactionStatsByPack.get(pack.id) || null,
|
|
7214
|
+
scoringWeights: driftSnapshot.weights,
|
|
7215
|
+
});
|
|
6577
7216
|
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
interactionStats: interactionStatsByPack.get(pack.id) || null,
|
|
6588
|
-
scoringWeights: driftSnapshot.weights,
|
|
7217
|
+
return {
|
|
7218
|
+
pack,
|
|
7219
|
+
items,
|
|
7220
|
+
byAssetClassification,
|
|
7221
|
+
packClassification,
|
|
7222
|
+
engagement,
|
|
7223
|
+
signals,
|
|
7224
|
+
};
|
|
7225
|
+
},
|
|
6589
7226
|
});
|
|
6590
|
-
|
|
6591
|
-
return {
|
|
6592
|
-
pack,
|
|
6593
|
-
items,
|
|
6594
|
-
byAssetClassification,
|
|
6595
|
-
packClassification,
|
|
6596
|
-
engagement,
|
|
6597
|
-
signals,
|
|
6598
|
-
};
|
|
6599
7227
|
};
|
|
6600
7228
|
|
|
6601
7229
|
const handleDetailsRequest = async (req, res, packKey, url) => {
|
|
@@ -6686,6 +7314,23 @@ const handleAssetRequest = async (req, res, packKey, stickerToken) => {
|
|
|
6686
7314
|
return;
|
|
6687
7315
|
}
|
|
6688
7316
|
}
|
|
7317
|
+
|
|
7318
|
+
const externalAssetUrl = await getStickerAssetExternalUrl(item.asset, {
|
|
7319
|
+
secure: true,
|
|
7320
|
+
expiresInSeconds: Math.max(60, Math.min(3600, Number(process.env.STICKER_OBJECT_STORAGE_SIGNED_URL_TTL_SECONDS) || 300)),
|
|
7321
|
+
}).catch(() => null);
|
|
7322
|
+
if (externalAssetUrl) {
|
|
7323
|
+
res.statusCode = 302;
|
|
7324
|
+
res.setHeader('Location', externalAssetUrl);
|
|
7325
|
+
res.setHeader('Cache-Control', 'private, max-age=45');
|
|
7326
|
+
if (req.method === 'HEAD') {
|
|
7327
|
+
res.end();
|
|
7328
|
+
return;
|
|
7329
|
+
}
|
|
7330
|
+
res.end();
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
|
|
6689
7334
|
if (decorated) {
|
|
6690
7335
|
res.setHeader('X-Sticker-Category', String(decorated?.category || 'unknown'));
|
|
6691
7336
|
res.setHeader('X-Sticker-NSFW', decorated?.is_nsfw ? '1' : '0');
|
|
@@ -6759,7 +7404,7 @@ const handlePackInteractionRequest = async (req, res, packKey, interaction, url)
|
|
|
6759
7404
|
const listAdminActiveGoogleWebSessions = async ({ limit = 200 } = {}) => {
|
|
6760
7405
|
const safeLimit = Math.max(1, Math.min(500, Number(limit || 200)));
|
|
6761
7406
|
const rows = await executeQuery(
|
|
6762
|
-
`SELECT session_token, google_sub, owner_jid, email, name, picture_url, created_at, last_seen_at, expires_at
|
|
7407
|
+
`SELECT session_token, google_sub, owner_jid, owner_phone, email, name, picture_url, created_at, last_seen_at, expires_at
|
|
6763
7408
|
FROM ${TABLES.STICKER_WEB_GOOGLE_SESSION}
|
|
6764
7409
|
WHERE revoked_at IS NULL
|
|
6765
7410
|
AND expires_at > UTC_TIMESTAMP()
|
|
@@ -6770,6 +7415,7 @@ const listAdminActiveGoogleWebSessions = async ({ limit = 200 } = {}) => {
|
|
|
6770
7415
|
session_token: String(row.session_token || '').trim(),
|
|
6771
7416
|
google_sub: normalizeGoogleSubject(row.google_sub),
|
|
6772
7417
|
owner_jid: normalizeJid(row.owner_jid) || null,
|
|
7418
|
+
owner_phone: toWhatsAppPhoneDigits(row.owner_phone || row.owner_jid) || null,
|
|
6773
7419
|
email: normalizeEmail(row.email) || null,
|
|
6774
7420
|
name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
|
|
6775
7421
|
picture: String(row.picture_url || '').trim() || null,
|
|
@@ -6782,7 +7428,7 @@ const listAdminActiveGoogleWebSessions = async ({ limit = 200 } = {}) => {
|
|
|
6782
7428
|
const listAdminKnownGoogleUsers = async ({ limit = 200 } = {}) => {
|
|
6783
7429
|
const safeLimit = Math.max(1, Math.min(500, Number(limit || 200)));
|
|
6784
7430
|
const rows = await executeQuery(
|
|
6785
|
-
`SELECT google_sub, owner_jid, email, name, picture_url, created_at, updated_at, last_login_at, last_seen_at
|
|
7431
|
+
`SELECT google_sub, owner_jid, owner_phone, email, name, picture_url, created_at, updated_at, last_login_at, last_seen_at
|
|
6786
7432
|
FROM ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
6787
7433
|
ORDER BY COALESCE(last_seen_at, last_login_at, updated_at, created_at) DESC
|
|
6788
7434
|
LIMIT ${safeLimit}`,
|
|
@@ -6790,6 +7436,7 @@ const listAdminKnownGoogleUsers = async ({ limit = 200 } = {}) => {
|
|
|
6790
7436
|
return (Array.isArray(rows) ? rows : []).map((row) => ({
|
|
6791
7437
|
google_sub: normalizeGoogleSubject(row.google_sub),
|
|
6792
7438
|
owner_jid: normalizeJid(row.owner_jid) || null,
|
|
7439
|
+
owner_phone: toWhatsAppPhoneDigits(row.owner_phone || row.owner_jid) || null,
|
|
6793
7440
|
email: normalizeEmail(row.email) || null,
|
|
6794
7441
|
name: sanitizeText(row.name || '', 120, { allowEmpty: true }) || null,
|
|
6795
7442
|
picture: String(row.picture_url || '').trim() || null,
|
|
@@ -7291,314 +7938,60 @@ const handleAdminBanRevokeRequest = async (req, res, banId) => {
|
|
|
7291
7938
|
}
|
|
7292
7939
|
};
|
|
7293
7940
|
|
|
7294
|
-
const
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
if (pathname === `${STICKER_API_BASE_PATH}/recommendations`) {
|
|
7347
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7348
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7349
|
-
return true;
|
|
7350
|
-
}
|
|
7351
|
-
await handleRecommendationsRequest(req, res, url);
|
|
7352
|
-
return true;
|
|
7353
|
-
}
|
|
7354
|
-
|
|
7355
|
-
if (pathname === `${STICKER_API_BASE_PATH}/stats`) {
|
|
7356
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7357
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7358
|
-
return true;
|
|
7359
|
-
}
|
|
7360
|
-
await handleMarketplaceStatsRequest(req, res, url);
|
|
7361
|
-
return true;
|
|
7362
|
-
}
|
|
7363
|
-
|
|
7364
|
-
if (pathname === `${STICKER_API_BASE_PATH}/create-config`) {
|
|
7365
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7366
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7367
|
-
return true;
|
|
7368
|
-
}
|
|
7369
|
-
await handleCreatePackConfigRequest(req, res);
|
|
7370
|
-
return true;
|
|
7371
|
-
}
|
|
7372
|
-
|
|
7373
|
-
if (pathname === STICKER_ORPHAN_API_PATH) {
|
|
7374
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7375
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7376
|
-
return true;
|
|
7377
|
-
}
|
|
7378
|
-
await handleOrphanStickerListRequest(req, res, url);
|
|
7379
|
-
return true;
|
|
7380
|
-
}
|
|
7381
|
-
|
|
7382
|
-
const suffix = pathname.slice(STICKER_API_BASE_PATH.length).replace(/^\/+/, '');
|
|
7383
|
-
if (!suffix) return false;
|
|
7384
|
-
|
|
7385
|
-
const segments = suffix.split('/').filter(Boolean).map((segment) => {
|
|
7386
|
-
try {
|
|
7387
|
-
return decodeURIComponent(segment);
|
|
7388
|
-
} catch {
|
|
7389
|
-
return segment;
|
|
7390
|
-
}
|
|
7391
|
-
});
|
|
7392
|
-
|
|
7393
|
-
if (segments.length === 1 && segments[0] === 'data-files') {
|
|
7394
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7395
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7396
|
-
return true;
|
|
7397
|
-
}
|
|
7398
|
-
await handleDataFileListRequest(req, res, url);
|
|
7399
|
-
return true;
|
|
7400
|
-
}
|
|
7401
|
-
|
|
7402
|
-
if (segments.length === 1 && segments[0] === 'system-summary') {
|
|
7403
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7404
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7405
|
-
return true;
|
|
7406
|
-
}
|
|
7407
|
-
await handleSystemSummaryRequest(req, res);
|
|
7408
|
-
return true;
|
|
7409
|
-
}
|
|
7410
|
-
|
|
7411
|
-
if (segments.length === 1 && segments[0] === 'project-summary') {
|
|
7412
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7413
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7414
|
-
return true;
|
|
7415
|
-
}
|
|
7416
|
-
await handleGitHubProjectSummaryRequest(req, res);
|
|
7417
|
-
return true;
|
|
7418
|
-
}
|
|
7419
|
-
|
|
7420
|
-
if (segments.length === 1 && segments[0] === 'global-ranking-summary') {
|
|
7421
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7422
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7423
|
-
return true;
|
|
7424
|
-
}
|
|
7425
|
-
await handleGlobalRankingSummaryRequest(req, res);
|
|
7426
|
-
return true;
|
|
7427
|
-
}
|
|
7428
|
-
|
|
7429
|
-
if (segments.length === 1 && segments[0] === 'readme-summary') {
|
|
7430
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7431
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7432
|
-
return true;
|
|
7433
|
-
}
|
|
7434
|
-
await handleReadmeSummaryRequest(req, res);
|
|
7435
|
-
return true;
|
|
7436
|
-
}
|
|
7437
|
-
|
|
7438
|
-
if (segments.length === 1 && segments[0] === 'readme-markdown') {
|
|
7439
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7440
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7441
|
-
return true;
|
|
7442
|
-
}
|
|
7443
|
-
await handleReadmeMarkdownRequest(req, res);
|
|
7444
|
-
return true;
|
|
7445
|
-
}
|
|
7446
|
-
|
|
7447
|
-
if (segments.length === 1 && segments[0] === 'support') {
|
|
7448
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7449
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7450
|
-
return true;
|
|
7451
|
-
}
|
|
7452
|
-
await handleSupportInfoRequest(req, res);
|
|
7453
|
-
return true;
|
|
7454
|
-
}
|
|
7455
|
-
|
|
7456
|
-
if (segments[0] === 'admin') {
|
|
7457
|
-
if (segments.length === 2 && segments[1] === 'overview') {
|
|
7458
|
-
await handleAdminOverviewRequest(req, res);
|
|
7459
|
-
return true;
|
|
7460
|
-
}
|
|
7461
|
-
if (segments.length === 2 && segments[1] === 'users') {
|
|
7462
|
-
await handleAdminUsersRequest(req, res, url);
|
|
7463
|
-
return true;
|
|
7464
|
-
}
|
|
7465
|
-
if (segments.length === 2 && segments[1] === 'moderators') {
|
|
7466
|
-
await handleAdminModeratorsRequest(req, res);
|
|
7467
|
-
return true;
|
|
7468
|
-
}
|
|
7469
|
-
if (segments.length === 3 && segments[1] === 'moderators') {
|
|
7470
|
-
await handleAdminModeratorDeleteRequest(req, res, segments[2]);
|
|
7471
|
-
return true;
|
|
7472
|
-
}
|
|
7473
|
-
if (segments.length === 2 && segments[1] === 'packs') {
|
|
7474
|
-
await handleAdminPacksRequest(req, res, url);
|
|
7475
|
-
return true;
|
|
7476
|
-
}
|
|
7477
|
-
if (segments.length === 3 && segments[1] === 'packs') {
|
|
7478
|
-
await handleAdminPackDetailsRequest(req, res, segments[2]);
|
|
7479
|
-
return true;
|
|
7480
|
-
}
|
|
7481
|
-
if (segments.length === 4 && segments[1] === 'packs' && segments[3] === 'delete') {
|
|
7482
|
-
await handleAdminPackDeleteRequest(req, res, segments[2]);
|
|
7483
|
-
return true;
|
|
7484
|
-
}
|
|
7485
|
-
if (segments.length === 6 && segments[1] === 'packs' && segments[3] === 'stickers' && segments[5] === 'delete') {
|
|
7486
|
-
await handleAdminPackStickerDeleteRequest(req, res, segments[2], segments[4]);
|
|
7487
|
-
return true;
|
|
7488
|
-
}
|
|
7489
|
-
if (segments.length === 4 && segments[1] === 'stickers' && segments[3] === 'delete') {
|
|
7490
|
-
await handleAdminGlobalStickerDeleteRequest(req, res, segments[2]);
|
|
7491
|
-
return true;
|
|
7492
|
-
}
|
|
7493
|
-
if (segments.length === 2 && segments[1] === 'bans') {
|
|
7494
|
-
await handleAdminBansRequest(req, res);
|
|
7495
|
-
return true;
|
|
7496
|
-
}
|
|
7497
|
-
if (segments.length === 4 && segments[1] === 'bans' && segments[3] === 'revoke') {
|
|
7498
|
-
await handleAdminBanRevokeRequest(req, res, segments[2]);
|
|
7499
|
-
return true;
|
|
7500
|
-
}
|
|
7501
|
-
sendJson(req, res, 404, { error: 'Rota admin nao encontrada.' });
|
|
7502
|
-
return true;
|
|
7503
|
-
}
|
|
7504
|
-
|
|
7505
|
-
if (segments.length === 1) {
|
|
7506
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7507
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7508
|
-
return true;
|
|
7509
|
-
}
|
|
7510
|
-
await handleDetailsRequest(req, res, segments[0], url);
|
|
7511
|
-
return true;
|
|
7512
|
-
}
|
|
7513
|
-
|
|
7514
|
-
if (segments.length === 2 && ['open', 'like', 'dislike'].includes(segments[1])) {
|
|
7515
|
-
if (req.method !== 'POST') {
|
|
7516
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7517
|
-
return true;
|
|
7518
|
-
}
|
|
7519
|
-
await handlePackInteractionRequest(req, res, segments[0], segments[1], url);
|
|
7520
|
-
return true;
|
|
7521
|
-
}
|
|
7522
|
-
|
|
7523
|
-
if (segments.length === 2 && segments[1] === 'manage') {
|
|
7524
|
-
await handleManagedPackRequest(req, res, segments[0]);
|
|
7525
|
-
return true;
|
|
7526
|
-
}
|
|
7527
|
-
|
|
7528
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'clone') {
|
|
7529
|
-
await handleManagedPackCloneRequest(req, res, segments[0]);
|
|
7530
|
-
return true;
|
|
7531
|
-
}
|
|
7532
|
-
|
|
7533
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'cover') {
|
|
7534
|
-
await handleManagedPackCoverRequest(req, res, segments[0]);
|
|
7535
|
-
return true;
|
|
7536
|
-
}
|
|
7537
|
-
|
|
7538
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'reorder') {
|
|
7539
|
-
await handleManagedPackReorderRequest(req, res, segments[0]);
|
|
7540
|
-
return true;
|
|
7541
|
-
}
|
|
7542
|
-
|
|
7543
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'analytics') {
|
|
7544
|
-
await handleManagedPackAnalyticsRequest(req, res, segments[0]);
|
|
7545
|
-
return true;
|
|
7546
|
-
}
|
|
7547
|
-
|
|
7548
|
-
if (segments.length === 3 && segments[1] === 'manage' && segments[2] === 'stickers') {
|
|
7549
|
-
await handleManagedPackStickerCreateRequest(req, res, segments[0]);
|
|
7550
|
-
return true;
|
|
7551
|
-
}
|
|
7552
|
-
|
|
7553
|
-
if (segments.length === 4 && segments[1] === 'manage' && segments[2] === 'stickers') {
|
|
7554
|
-
await handleManagedPackStickerDeleteRequest(req, res, segments[0], segments[3]);
|
|
7555
|
-
return true;
|
|
7556
|
-
}
|
|
7557
|
-
|
|
7558
|
-
if (segments.length === 5 && segments[1] === 'manage' && segments[2] === 'stickers' && segments[4] === 'replace') {
|
|
7559
|
-
await handleManagedPackStickerReplaceRequest(req, res, segments[0], segments[3]);
|
|
7560
|
-
return true;
|
|
7561
|
-
}
|
|
7562
|
-
|
|
7563
|
-
if (segments.length === 2 && segments[1] === 'publish-state') {
|
|
7564
|
-
if (!['GET', 'HEAD', 'POST'].includes(req.method || '')) {
|
|
7565
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7566
|
-
return true;
|
|
7567
|
-
}
|
|
7568
|
-
await handlePackPublishStateRequest(req, res, segments[0], url);
|
|
7569
|
-
return true;
|
|
7570
|
-
}
|
|
7571
|
-
|
|
7572
|
-
if (segments.length === 2 && segments[1] === 'finalize') {
|
|
7573
|
-
if (req.method !== 'POST') {
|
|
7574
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7575
|
-
return true;
|
|
7576
|
-
}
|
|
7577
|
-
await handleFinalizePackRequest(req, res, segments[0]);
|
|
7578
|
-
return true;
|
|
7579
|
-
}
|
|
7580
|
-
|
|
7581
|
-
if (segments.length === 2 && segments[1] === 'stickers-upload') {
|
|
7582
|
-
if (req.method !== 'POST') {
|
|
7583
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7584
|
-
return true;
|
|
7585
|
-
}
|
|
7586
|
-
await handleUploadStickerToPackRequest(req, res, segments[0]);
|
|
7587
|
-
return true;
|
|
7588
|
-
}
|
|
7589
|
-
|
|
7590
|
-
if (segments.length === 3 && segments[1] === 'stickers') {
|
|
7591
|
-
if (!['GET', 'HEAD'].includes(req.method || '')) {
|
|
7592
|
-
sendJson(req, res, 405, { error: 'Metodo nao permitido.' });
|
|
7593
|
-
return true;
|
|
7594
|
-
}
|
|
7595
|
-
await handleAssetRequest(req, res, segments[0], segments[2]);
|
|
7596
|
-
return true;
|
|
7597
|
-
}
|
|
7941
|
+
const catalogApiRouter = createCatalogApiRouter({
|
|
7942
|
+
apiBasePath: STICKER_API_BASE_PATH,
|
|
7943
|
+
orphanApiPath: STICKER_ORPHAN_API_PATH,
|
|
7944
|
+
sendJson,
|
|
7945
|
+
handlers: {
|
|
7946
|
+
handleCreatePackRequest,
|
|
7947
|
+
handleGoogleAuthSessionRequest,
|
|
7948
|
+
handleMyProfileRequest,
|
|
7949
|
+
handleAdminPanelSessionRequest,
|
|
7950
|
+
handleListRequest,
|
|
7951
|
+
handleIntentCollectionsRequest,
|
|
7952
|
+
handleCreatorRankingRequest,
|
|
7953
|
+
handleRecommendationsRequest,
|
|
7954
|
+
handleMarketplaceStatsRequest,
|
|
7955
|
+
handleCreatePackConfigRequest,
|
|
7956
|
+
handleOrphanStickerListRequest,
|
|
7957
|
+
handleDataFileListRequest,
|
|
7958
|
+
handleSystemSummaryRequest,
|
|
7959
|
+
handleGitHubProjectSummaryRequest,
|
|
7960
|
+
handleGlobalRankingSummaryRequest,
|
|
7961
|
+
handleReadmeSummaryRequest,
|
|
7962
|
+
handleReadmeMarkdownRequest,
|
|
7963
|
+
handleSupportInfoRequest,
|
|
7964
|
+
handleBotContactInfoRequest,
|
|
7965
|
+
handleAdminOverviewRequest,
|
|
7966
|
+
handleAdminUsersRequest,
|
|
7967
|
+
handleAdminModeratorsRequest,
|
|
7968
|
+
handleAdminModeratorDeleteRequest,
|
|
7969
|
+
handleAdminPacksRequest,
|
|
7970
|
+
handleAdminPackDetailsRequest,
|
|
7971
|
+
handleAdminPackDeleteRequest,
|
|
7972
|
+
handleAdminPackStickerDeleteRequest,
|
|
7973
|
+
handleAdminGlobalStickerDeleteRequest,
|
|
7974
|
+
handleAdminBansRequest,
|
|
7975
|
+
handleAdminBanRevokeRequest,
|
|
7976
|
+
handleDetailsRequest,
|
|
7977
|
+
handlePackInteractionRequest,
|
|
7978
|
+
handleManagedPackRequest,
|
|
7979
|
+
handleManagedPackCloneRequest,
|
|
7980
|
+
handleManagedPackCoverRequest,
|
|
7981
|
+
handleManagedPackReorderRequest,
|
|
7982
|
+
handleManagedPackAnalyticsRequest,
|
|
7983
|
+
handleManagedPackStickerCreateRequest,
|
|
7984
|
+
handleManagedPackStickerDeleteRequest,
|
|
7985
|
+
handleManagedPackStickerReplaceRequest,
|
|
7986
|
+
handlePackPublishStateRequest,
|
|
7987
|
+
handleFinalizePackRequest,
|
|
7988
|
+
handleUploadStickerToPackRequest,
|
|
7989
|
+
handleAssetRequest,
|
|
7990
|
+
},
|
|
7991
|
+
});
|
|
7598
7992
|
|
|
7599
|
-
|
|
7600
|
-
|
|
7601
|
-
};
|
|
7993
|
+
const handleCatalogApiRequest = async (req, res, pathname, url) =>
|
|
7994
|
+
catalogApiRouter({ req, res, pathname, url });
|
|
7602
7995
|
|
|
7603
7996
|
const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
7604
7997
|
const normalizedPath = pathname.length > 1 ? pathname.replace(/\/+$/, '') : pathname;
|
|
@@ -7624,6 +8017,20 @@ const handleCatalogPageRequest = async (req, res, pathname) => {
|
|
|
7624
8017
|
|
|
7625
8018
|
if (normalizedPath === STICKER_CREATE_WEB_PATH) {
|
|
7626
8019
|
try {
|
|
8020
|
+
const googleSession = await resolveGoogleWebSessionFromRequest(req);
|
|
8021
|
+
if (!googleSession?.ownerJid) {
|
|
8022
|
+
const requestUrl = new URL(req.url || `${STICKER_CREATE_WEB_PATH}/`, SITE_ORIGIN);
|
|
8023
|
+
const nextPath = `${requestUrl.pathname}${requestUrl.search}`;
|
|
8024
|
+
const loginRedirectUrl = new URL(`${STICKER_LOGIN_WEB_PATH}/`, SITE_ORIGIN);
|
|
8025
|
+
loginRedirectUrl.searchParams.set('next', nextPath);
|
|
8026
|
+
|
|
8027
|
+
res.statusCode = 302;
|
|
8028
|
+
res.setHeader('Location', `${loginRedirectUrl.pathname}${loginRedirectUrl.search}`);
|
|
8029
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
8030
|
+
res.end();
|
|
8031
|
+
return;
|
|
8032
|
+
}
|
|
8033
|
+
|
|
7627
8034
|
const html = await renderCreatePackHtml();
|
|
7628
8035
|
sendText(req, res, 200, html, 'text/html; charset=utf-8');
|
|
7629
8036
|
return;
|
|
@@ -7711,6 +8118,7 @@ export const isStickerCatalogEnabled = () => STICKER_CATALOG_ENABLED;
|
|
|
7711
8118
|
export const getStickerCatalogConfig = () => ({
|
|
7712
8119
|
enabled: STICKER_CATALOG_ENABLED,
|
|
7713
8120
|
webPath: STICKER_WEB_PATH,
|
|
8121
|
+
userProfilePath: USER_PROFILE_WEB_PATH,
|
|
7714
8122
|
apiBasePath: STICKER_API_BASE_PATH,
|
|
7715
8123
|
orphanApiPath: STICKER_ORPHAN_API_PATH,
|
|
7716
8124
|
dataPublicPath: STICKER_DATA_PUBLIC_PATH,
|
|
@@ -7734,6 +8142,29 @@ export async function maybeHandleStickerCatalogRequest(req, res, { pathname, url
|
|
|
7734
8142
|
if (!['GET', 'HEAD', 'POST', 'PATCH', 'DELETE'].includes(req.method || '')) return false;
|
|
7735
8143
|
if (maybeRedirectToCanonicalHost(req, res, url)) return true;
|
|
7736
8144
|
|
|
8145
|
+
if (pathname === USER_PROFILE_WEB_PATH || pathname === `${USER_PROFILE_WEB_PATH}/`) {
|
|
8146
|
+
if (!['GET', 'HEAD'].includes(req.method || '')) return false;
|
|
8147
|
+
try {
|
|
8148
|
+
const html = await renderUserDashboardHtml();
|
|
8149
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
8150
|
+
res.setHeader('X-Robots-Tag', 'noindex, nofollow');
|
|
8151
|
+
sendText(req, res, 200, html, 'text/html; charset=utf-8');
|
|
8152
|
+
} catch (error) {
|
|
8153
|
+
if (error?.code === 'ENOENT') {
|
|
8154
|
+
sendJson(req, res, 404, { error: 'Template da pagina de usuario nao encontrado.' });
|
|
8155
|
+
return true;
|
|
8156
|
+
}
|
|
8157
|
+
|
|
8158
|
+
logger.error('Falha ao renderizar pagina de usuario.', {
|
|
8159
|
+
action: 'user_dashboard_page_render_failed',
|
|
8160
|
+
path: pathname,
|
|
8161
|
+
error: error?.message,
|
|
8162
|
+
});
|
|
8163
|
+
sendJson(req, res, 500, { error: 'Falha interna ao renderizar pagina de usuario.' });
|
|
8164
|
+
}
|
|
8165
|
+
return true;
|
|
8166
|
+
}
|
|
8167
|
+
|
|
7737
8168
|
if (pathname === '/sitemap.xml') {
|
|
7738
8169
|
if (!['GET', 'HEAD'].includes(req.method || '')) return false;
|
|
7739
8170
|
try {
|