@kaikybrofc/omnizap-system 2.3.1 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -483
- package/app/controllers/messageController.js +473 -255
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
- package/app/modules/stickerModule/stickerCommand.js +7 -2
- package/app/modules/stickerModule/stickerTextCommand.js +7 -2
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +1 -3
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +224 -53
- package/app/observability/metrics.js +6 -3
- package/app/services/googleWebLinkService.js +77 -0
- package/app/services/lidMapService.js +83 -4
- package/database/index.js +2 -0
- package/database/migrations/20260301_0028_message_analysis_event.sql +32 -0
- package/database/migrations/20260301_0029_admin_action_audit.sql +16 -0
- package/package.json +1 -1
- package/public/index.html +12 -8
- package/public/js/apps/createPackApp.js +4 -4
- package/public/js/apps/homeApp.js +78 -34
- package/public/js/apps/loginApp.js +245 -35
- package/public/js/apps/stickersAdminApp.js +4 -10
- package/public/js/apps/stickersApp.js +1 -1
- package/public/js/apps/userApp.js +956 -55
- package/public/js/apps/userProfileApp.js +244 -0
- package/public/login/index.html +437 -101
- package/public/termos-de-uso/index.html +1 -1
- package/public/user/index.html +2 -181
- package/public/user/systemadm/index.html +774 -0
- package/server/controllers/stickerCatalog/nonCatalogHandlers.js +183 -0
- package/server/controllers/stickerCatalogController.js +1289 -368
- package/server/controllers/systemAdminController.js +141 -0
- package/server/controllers/userController.js +87 -0
- package/server/http/httpServer.js +72 -32
- package/server/middleware/cachePolicy.js +24 -0
- package/server/middleware/cachePolicyHelpers.js +1 -0
- package/server/middleware/rateLimit.js +89 -0
- package/server/middleware/requestLogger.js +16 -0
- package/server/middleware/requireAdminAuth.js +42 -0
- package/server/middleware/securityHeaders.js +6 -0
- package/server/routes/admin/systemAdminRouter.js +56 -0
- package/server/routes/health/healthRouter.js +41 -0
- package/server/routes/indexRouter.js +197 -0
- package/server/routes/metrics/metricsRouter.js +13 -0
- package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +44 -0
- package/server/routes/stickerCatalog/stickerApiRouter.js +84 -0
- package/server/routes/stickerCatalog/stickerDataRouter.js +140 -0
- package/server/routes/stickerCatalog/stickerSiteRouter.js +43 -0
- package/server/routes/user/userRouter.js +56 -0
- package/server/utils/safePath.js +26 -0
- package/server/routes/metricsRoute.js +0 -7
- package/server/routes/stickerCatalogRoute.js +0 -20
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
|
+
|
|
3
|
+
const sanitizeText = (value, maxLength = 255) => {
|
|
4
|
+
const normalized = String(value || '')
|
|
5
|
+
.trim()
|
|
6
|
+
.replace(/\s+/g, ' ')
|
|
7
|
+
.slice(0, maxLength);
|
|
8
|
+
return normalized || null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const sanitizeCommandName = (value) => {
|
|
12
|
+
const normalized = String(value || '')
|
|
13
|
+
.trim()
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9_-]/g, '')
|
|
16
|
+
.slice(0, 64);
|
|
17
|
+
return normalized || null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const sanitizeBool = (value) => (value ? 1 : 0);
|
|
21
|
+
|
|
22
|
+
const clampInt = (value, fallback, min, max) => {
|
|
23
|
+
const numeric = Number(value);
|
|
24
|
+
if (!Number.isFinite(numeric)) return fallback;
|
|
25
|
+
return Math.max(min, Math.min(max, Math.floor(numeric)));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const sanitizeMetadata = (value) => {
|
|
29
|
+
if (!value || typeof value !== 'object') return null;
|
|
30
|
+
try {
|
|
31
|
+
const asJson = JSON.stringify(value);
|
|
32
|
+
if (!asJson || asJson === '{}') return null;
|
|
33
|
+
return asJson;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export async function createMessageAnalysisEvent(payload = {}, connection = null) {
|
|
40
|
+
const messageId = sanitizeText(payload.messageId, 255);
|
|
41
|
+
const chatId = sanitizeText(payload.chatId, 255);
|
|
42
|
+
const senderId = sanitizeText(payload.senderId, 255);
|
|
43
|
+
const senderName = sanitizeText(payload.senderName, 120);
|
|
44
|
+
const upsertType = sanitizeText(payload.upsertType, 32);
|
|
45
|
+
const source = sanitizeText(payload.source, 32) || 'whatsapp';
|
|
46
|
+
const commandPrefix = sanitizeText(payload.commandPrefix, 8);
|
|
47
|
+
const commandName = sanitizeCommandName(payload.commandName);
|
|
48
|
+
const messageKind = sanitizeText(payload.messageKind, 48) || 'other';
|
|
49
|
+
const processingResult = sanitizeText(payload.processingResult, 64) || 'processed';
|
|
50
|
+
const errorCode = sanitizeText(payload.errorCode, 96);
|
|
51
|
+
const metadata = sanitizeMetadata(payload.metadata);
|
|
52
|
+
|
|
53
|
+
await executeQuery(
|
|
54
|
+
`INSERT INTO ${TABLES.MESSAGE_ANALYSIS_EVENT}
|
|
55
|
+
(
|
|
56
|
+
message_id,
|
|
57
|
+
chat_id,
|
|
58
|
+
sender_id,
|
|
59
|
+
sender_name,
|
|
60
|
+
upsert_type,
|
|
61
|
+
source,
|
|
62
|
+
is_group,
|
|
63
|
+
is_from_bot,
|
|
64
|
+
is_command,
|
|
65
|
+
command_name,
|
|
66
|
+
command_args_count,
|
|
67
|
+
command_known,
|
|
68
|
+
command_prefix,
|
|
69
|
+
message_kind,
|
|
70
|
+
has_media,
|
|
71
|
+
media_count,
|
|
72
|
+
text_length,
|
|
73
|
+
processing_result,
|
|
74
|
+
error_code,
|
|
75
|
+
metadata
|
|
76
|
+
)
|
|
77
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
78
|
+
[messageId, chatId, senderId, senderName, upsertType, source, sanitizeBool(payload.isGroup), sanitizeBool(payload.isFromBot), sanitizeBool(payload.isCommand), commandName, clampInt(payload.commandArgsCount, 0, 0, 64), payload.commandKnown === null || payload.commandKnown === undefined ? null : sanitizeBool(payload.commandKnown), commandPrefix, messageKind, sanitizeBool(payload.hasMedia), clampInt(payload.mediaCount, 0, 0, 25), clampInt(payload.textLength, 0, 0, 16_000), processingResult, errorCode, metadata],
|
|
79
|
+
connection,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
@@ -223,9 +223,14 @@ function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX)
|
|
|
223
223
|
return duplicateLines.join('\n');
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
const savedLines = [
|
|
226
|
+
const savedLines = [
|
|
227
|
+
`✅ Figurinha adicionada ao pack *${packName}*${countLabel}.`,
|
|
228
|
+
'',
|
|
229
|
+
`📋 Gerencie seus packs com *${commandPrefix}pack list*.`,
|
|
230
|
+
`🚀 Envie agora com *${commandPrefix}pack send ${packCommandTarget}*.`,
|
|
231
|
+
];
|
|
227
232
|
if (packWebUrl) {
|
|
228
|
-
savedLines.push(`🌐
|
|
233
|
+
savedLines.push(`🌐 Veja no site: ${packWebUrl}`);
|
|
229
234
|
} else {
|
|
230
235
|
savedLines.push(`🔒 Pack privado/não publicado. Gerencie em: ${profileUrl}`);
|
|
231
236
|
}
|
|
@@ -150,9 +150,14 @@ function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX)
|
|
|
150
150
|
return duplicateLines.join('\n');
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
const savedLines = [
|
|
153
|
+
const savedLines = [
|
|
154
|
+
`✅ Figurinha adicionada ao pack *${packName}*${countLabel}.`,
|
|
155
|
+
'',
|
|
156
|
+
`📋 Gerencie seus packs com *${commandPrefix}pack list*.`,
|
|
157
|
+
`🚀 Envie agora com *${commandPrefix}pack send ${packCommandTarget}*.`,
|
|
158
|
+
];
|
|
154
159
|
if (packWebUrl) {
|
|
155
|
-
savedLines.push(`🌐
|
|
160
|
+
savedLines.push(`🌐 Veja no site: ${packWebUrl}`);
|
|
156
161
|
} else {
|
|
157
162
|
savedLines.push(`🔒 Pack privado/não publicado. Gerencie em: ${profileUrl}`);
|
|
158
163
|
}
|
|
@@ -108,9 +108,7 @@ const handleDomainEvent = async (event) => {
|
|
|
108
108
|
if (packId) {
|
|
109
109
|
enqueuePackScoreSnapshotRefresh([packId]);
|
|
110
110
|
}
|
|
111
|
-
const rebuildIdempotency = packId
|
|
112
|
-
? `evt:${eventType}:${packId}:${coalesceBucket}:rebuild_cycle`
|
|
113
|
-
: `evt:${eventType}:${coalesceBucket}:rebuild_cycle`;
|
|
111
|
+
const rebuildIdempotency = packId ? `evt:${eventType}:${packId}:${coalesceBucket}:rebuild_cycle` : `evt:${eventType}:${coalesceBucket}:rebuild_cycle`;
|
|
114
112
|
await enqueueTaskSafely({
|
|
115
113
|
taskType: 'rebuild_cycle',
|
|
116
114
|
payload: { reason: 'domain_event', event_type: eventType, aggregate_id: aggregateId, pack_id: packId || null, coalesced: true },
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import logger from '../../utils/logger/loggerModule.js';
|
|
2
2
|
import { sendAndStore } from '../../services/messagePersistenceService.js';
|
|
3
|
-
import { isUserJid } from '../../config/baileysConfig.js';
|
|
3
|
+
import { getJidServer, isUserJid, normalizeJid } from '../../config/baileysConfig.js';
|
|
4
4
|
import stickerPackService from './stickerPackServiceRuntime.js';
|
|
5
5
|
import { STICKER_PACK_ERROR_CODES, StickerPackError } from './stickerPackErrors.js';
|
|
6
6
|
import { captureIncomingStickerAsset, resolveStickerAssetForCommand } from './stickerStorageService.js';
|
|
7
7
|
import { buildStickerPackMessage, sendStickerPackWithFallback } from './stickerPackMessageService.js';
|
|
8
8
|
import { sanitizeText } from './stickerPackUtils.js';
|
|
9
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
10
|
+
import { extractSenderInfoFromMessage, extractUserIdInfo, resolveUserId } from '../../services/lidMapService.js';
|
|
11
|
+
import { toWhatsAppPhoneDigits } from '../../services/whatsappLoginLinkService.js';
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Handlers de comando textual para gerenciamento de packs de figurinha.
|
|
@@ -14,6 +17,7 @@ const RATE_WINDOW_MS = Math.max(10_000, Number(process.env.STICKER_PACK_RATE_WIN
|
|
|
14
17
|
const RATE_MAX_ACTIONS = Math.max(1, Number(process.env.STICKER_PACK_RATE_MAX_ACTIONS) || 20);
|
|
15
18
|
const MAX_PACK_ITEMS = Math.max(1, Number(process.env.STICKER_PACK_MAX_ITEMS) || 30);
|
|
16
19
|
const MAX_PACK_NAME_LENGTH = 120;
|
|
20
|
+
const LID_SERVERS = new Set(['lid', 'hosted.lid']);
|
|
17
21
|
|
|
18
22
|
const rateMap = new Map();
|
|
19
23
|
|
|
@@ -159,20 +163,23 @@ const formatVisibilityLabel = (visibility) => {
|
|
|
159
163
|
};
|
|
160
164
|
|
|
161
165
|
/**
|
|
162
|
-
* Detecta packs automáticos para ocultar em listagens
|
|
166
|
+
* Detecta packs automáticos de curadoria temática para ocultar em listagens padrão.
|
|
167
|
+
* Mantém visível o auto-pack coletor do usuário (ex.: "minhasfigurinhas1").
|
|
163
168
|
*
|
|
164
169
|
* @param {object|null|undefined} pack Pack retornado pelo serviço.
|
|
165
|
-
* @returns {boolean} Verdadeiro quando
|
|
170
|
+
* @returns {boolean} Verdadeiro quando for auto-pack temático/curadoria.
|
|
166
171
|
*/
|
|
167
|
-
const
|
|
172
|
+
const isThemeCurationPack = (pack) => {
|
|
168
173
|
if (!pack || typeof pack !== 'object') return false;
|
|
169
|
-
if (pack.is_auto_pack === true || Number(pack.is_auto_pack || 0) === 1) return true;
|
|
170
174
|
|
|
171
175
|
const name = String(pack.name || '').trim();
|
|
172
176
|
if (/^\[auto\]/i.test(name)) return true;
|
|
173
177
|
|
|
174
178
|
const description = String(pack.description || '').toLowerCase();
|
|
175
|
-
|
|
179
|
+
if (description.includes('[auto-theme:') || description.includes('[auto-tag:')) return true;
|
|
180
|
+
|
|
181
|
+
const themeKey = String(pack.pack_theme_key || '').trim();
|
|
182
|
+
return Boolean(themeKey);
|
|
176
183
|
};
|
|
177
184
|
|
|
178
185
|
/**
|
|
@@ -448,6 +455,142 @@ const readSingleArgument = (input) => {
|
|
|
448
455
|
return value ? value : null;
|
|
449
456
|
};
|
|
450
457
|
|
|
458
|
+
const buildOwnerLookupJids = (value) => {
|
|
459
|
+
const normalized = normalizeJid(value) || '';
|
|
460
|
+
if (!normalized || !normalized.includes('@')) return [];
|
|
461
|
+
const lookup = new Set([normalized]);
|
|
462
|
+
const digits = toWhatsAppPhoneDigits(normalized);
|
|
463
|
+
if (!digits) return Array.from(lookup);
|
|
464
|
+
lookup.add(normalizeJid(`${digits}@s.whatsapp.net`) || '');
|
|
465
|
+
lookup.add(normalizeJid(`${digits}@c.us`) || '');
|
|
466
|
+
lookup.add(normalizeJid(`${digits}@hosted`) || '');
|
|
467
|
+
return Array.from(lookup).filter(Boolean);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const appendOwnerCandidate = (candidateSet, lookupSet, value) => {
|
|
471
|
+
const normalized = normalizeJid(value) || '';
|
|
472
|
+
if (!normalized || !normalized.includes('@')) return;
|
|
473
|
+
candidateSet.add(normalized);
|
|
474
|
+
for (const lookupJid of buildOwnerLookupJids(normalized)) {
|
|
475
|
+
lookupSet.add(lookupJid);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const dedupePacksById = (packs = []) => {
|
|
480
|
+
const dedup = new Map();
|
|
481
|
+
for (const pack of Array.isArray(packs) ? packs : []) {
|
|
482
|
+
if (!pack?.id) continue;
|
|
483
|
+
const existing = dedup.get(pack.id);
|
|
484
|
+
if (!existing) {
|
|
485
|
+
dedup.set(pack.id, pack);
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const currentUpdatedAt = Date.parse(String(pack.updated_at || pack.created_at || ''));
|
|
489
|
+
const existingUpdatedAt = Date.parse(String(existing.updated_at || existing.created_at || ''));
|
|
490
|
+
if (Number.isFinite(currentUpdatedAt) && (!Number.isFinite(existingUpdatedAt) || currentUpdatedAt > existingUpdatedAt)) {
|
|
491
|
+
dedup.set(pack.id, pack);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return Array.from(dedup.values()).sort((a, b) => {
|
|
496
|
+
const aUpdatedAt = Date.parse(String(a?.updated_at || a?.created_at || ''));
|
|
497
|
+
const bUpdatedAt = Date.parse(String(b?.updated_at || b?.created_at || ''));
|
|
498
|
+
if (!Number.isFinite(aUpdatedAt) && !Number.isFinite(bUpdatedAt)) return 0;
|
|
499
|
+
if (!Number.isFinite(aUpdatedAt)) return 1;
|
|
500
|
+
if (!Number.isFinite(bUpdatedAt)) return -1;
|
|
501
|
+
return bUpdatedAt - aUpdatedAt;
|
|
502
|
+
});
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const resolveOwnerCandidatesForPackCommand = async ({ senderJid, messageInfo }) => {
|
|
506
|
+
const candidates = new Set();
|
|
507
|
+
const lookupByJid = new Set();
|
|
508
|
+
|
|
509
|
+
const senderInfo = extractSenderInfoFromMessage(messageInfo);
|
|
510
|
+
appendOwnerCandidate(candidates, lookupByJid, senderJid);
|
|
511
|
+
appendOwnerCandidate(candidates, lookupByJid, senderInfo?.jid);
|
|
512
|
+
appendOwnerCandidate(candidates, lookupByJid, senderInfo?.participantAlt);
|
|
513
|
+
appendOwnerCandidate(candidates, lookupByJid, senderInfo?.lid);
|
|
514
|
+
|
|
515
|
+
const directResolved = await resolveUserId(extractUserIdInfo(senderJid)).catch(() => null);
|
|
516
|
+
if (directResolved) {
|
|
517
|
+
appendOwnerCandidate(candidates, lookupByJid, directResolved);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const senderResolved = await resolveUserId({
|
|
521
|
+
lid: senderInfo?.lid,
|
|
522
|
+
jid: senderInfo?.jid || senderJid || null,
|
|
523
|
+
participantAlt: senderInfo?.participantAlt || null,
|
|
524
|
+
}).catch(() => null);
|
|
525
|
+
if (senderResolved) {
|
|
526
|
+
appendOwnerCandidate(candidates, lookupByJid, senderResolved);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const lookupValues = Array.from(lookupByJid).filter(Boolean);
|
|
530
|
+
for (let offset = 0; offset < lookupValues.length; offset += 200) {
|
|
531
|
+
const chunk = lookupValues.slice(offset, offset + 200);
|
|
532
|
+
if (!chunk.length) continue;
|
|
533
|
+
const placeholders = chunk.map(() => '?').join(', ');
|
|
534
|
+
const lookupParams = [...chunk, ...chunk];
|
|
535
|
+
const rows = await executeQuery(
|
|
536
|
+
`SELECT lid, jid
|
|
537
|
+
FROM ${TABLES.LID_MAP}
|
|
538
|
+
WHERE jid IN (${placeholders})
|
|
539
|
+
OR lid IN (${placeholders})
|
|
540
|
+
ORDER BY last_seen DESC
|
|
541
|
+
LIMIT 500`,
|
|
542
|
+
lookupParams,
|
|
543
|
+
).catch(() => []);
|
|
544
|
+
|
|
545
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
546
|
+
appendOwnerCandidate(candidates, lookupByJid, row?.jid || '');
|
|
547
|
+
appendOwnerCandidate(candidates, lookupByJid, row?.lid || '');
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const lidCandidates = Array.from(candidates).filter((candidate) => LID_SERVERS.has(getJidServer(candidate)));
|
|
552
|
+
for (const lidValue of lidCandidates) {
|
|
553
|
+
const resolved = await resolveUserId(extractUserIdInfo(lidValue)).catch(() => null);
|
|
554
|
+
if (resolved) {
|
|
555
|
+
appendOwnerCandidate(candidates, lookupByJid, resolved);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return Array.from(candidates);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const pickPrimaryOwnerCandidate = (ownerCandidates, senderJid) => {
|
|
563
|
+
const preferred = (Array.isArray(ownerCandidates) ? ownerCandidates : []).find((candidate) => {
|
|
564
|
+
const server = getJidServer(candidate);
|
|
565
|
+
if (!server || LID_SERVERS.has(server)) return false;
|
|
566
|
+
return server !== 'google.oauth';
|
|
567
|
+
});
|
|
568
|
+
if (preferred) return preferred;
|
|
569
|
+
|
|
570
|
+
const normalizedSender = normalizeJid(senderJid) || '';
|
|
571
|
+
if (normalizedSender) return normalizedSender;
|
|
572
|
+
return Array.isArray(ownerCandidates) && ownerCandidates.length ? ownerCandidates[0] : senderJid;
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const runWithOwnerFallback = async (ownerCandidates, action) => {
|
|
576
|
+
const owners = Array.isArray(ownerCandidates) && ownerCandidates.length ? ownerCandidates : [];
|
|
577
|
+
let notFoundError = null;
|
|
578
|
+
for (const candidateOwner of owners) {
|
|
579
|
+
try {
|
|
580
|
+
return await action(candidateOwner);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
if (error instanceof StickerPackError && error.code === STICKER_PACK_ERROR_CODES.PACK_NOT_FOUND) {
|
|
583
|
+
notFoundError = notFoundError || error;
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
throw error;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (notFoundError) throw notFoundError;
|
|
591
|
+
throw new StickerPackError(STICKER_PACK_ERROR_CODES.PACK_NOT_FOUND, 'Pack não encontrado para este usuário.');
|
|
592
|
+
};
|
|
593
|
+
|
|
451
594
|
/**
|
|
452
595
|
* Normaliza e valida nome de pack (permite espaços e emojis).
|
|
453
596
|
*
|
|
@@ -530,7 +673,9 @@ const resolveStickerFromCommandContext = async ({ messageInfo, ownerJid, include
|
|
|
530
673
|
* @returns {Promise<void>}
|
|
531
674
|
*/
|
|
532
675
|
export async function handlePackCommand({ sock, remoteJid, messageInfo, expirationMessage, senderJid, senderName, text, commandPrefix }) {
|
|
533
|
-
const
|
|
676
|
+
const ownerCandidatesRaw = await resolveOwnerCandidatesForPackCommand({ senderJid, messageInfo }).catch(() => []);
|
|
677
|
+
const ownerJid = pickPrimaryOwnerCandidate(ownerCandidatesRaw, senderJid);
|
|
678
|
+
const ownerCandidates = Array.from(new Set([ownerJid, ...ownerCandidatesRaw].filter(Boolean)));
|
|
534
679
|
const rate = checkRateLimit(ownerJid);
|
|
535
680
|
|
|
536
681
|
if (rate.limited) {
|
|
@@ -590,8 +735,9 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
590
735
|
}
|
|
591
736
|
|
|
592
737
|
case 'list': {
|
|
593
|
-
const
|
|
594
|
-
const
|
|
738
|
+
const packLists = await Promise.all(ownerCandidates.map((candidateOwner) => stickerPackService.listPacks({ ownerJid: candidateOwner, limit: 100 })));
|
|
739
|
+
const packs = dedupePacksById(packLists.flatMap((items) => (Array.isArray(items) ? items : [])));
|
|
740
|
+
const manualPacks = packs.filter((pack) => !isThemeCurationPack(pack));
|
|
595
741
|
|
|
596
742
|
await sendReply({
|
|
597
743
|
sock,
|
|
@@ -605,7 +751,7 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
605
751
|
|
|
606
752
|
case 'info': {
|
|
607
753
|
const identifier = readSingleArgument(rest);
|
|
608
|
-
const pack = await stickerPackService.getPackInfo({ ownerJid, identifier });
|
|
754
|
+
const pack = await runWithOwnerFallback(ownerCandidates, (candidateOwner) => stickerPackService.getPackInfo({ ownerJid: candidateOwner, identifier }));
|
|
609
755
|
|
|
610
756
|
await sendReply({
|
|
611
757
|
sock,
|
|
@@ -620,7 +766,7 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
620
766
|
case 'rename': {
|
|
621
767
|
const { identifier, value } = parseIdentifierAndValue(rest);
|
|
622
768
|
const normalizedName = normalizePackName(value, { label: 'Novo nome do pack' });
|
|
623
|
-
const updated = await stickerPackService.renamePack({ ownerJid, identifier, name: normalizedName });
|
|
769
|
+
const updated = await runWithOwnerFallback(ownerCandidates, (candidateOwner) => stickerPackService.renamePack({ ownerJid: candidateOwner, identifier, name: normalizedName }));
|
|
624
770
|
|
|
625
771
|
await sendReply({
|
|
626
772
|
sock,
|
|
@@ -639,7 +785,7 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
639
785
|
|
|
640
786
|
case 'setpub': {
|
|
641
787
|
const { identifier, value } = parseIdentifierAndValue(rest);
|
|
642
|
-
const updated = await stickerPackService.setPackPublisher({ ownerJid, identifier, publisher: value });
|
|
788
|
+
const updated = await runWithOwnerFallback(ownerCandidates, (candidateOwner) => stickerPackService.setPackPublisher({ ownerJid: candidateOwner, identifier, publisher: value }));
|
|
643
789
|
|
|
644
790
|
await sendReply({
|
|
645
791
|
sock,
|
|
@@ -659,7 +805,7 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
659
805
|
case 'setdesc': {
|
|
660
806
|
const { identifier, value } = parseIdentifierAndValue(rest);
|
|
661
807
|
const description = value === '-' || value.toLowerCase() === 'clear' ? '' : value;
|
|
662
|
-
const updated = await stickerPackService.setPackDescription({ ownerJid, identifier, description });
|
|
808
|
+
const updated = await runWithOwnerFallback(ownerCandidates, (candidateOwner) => stickerPackService.setPackDescription({ ownerJid: candidateOwner, identifier, description }));
|
|
663
809
|
|
|
664
810
|
await sendReply({
|
|
665
811
|
sock,
|
|
@@ -684,11 +830,13 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
684
830
|
throw new StickerPackError(STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND, 'Não encontrei uma figurinha para definir como capa.');
|
|
685
831
|
}
|
|
686
832
|
|
|
687
|
-
const updated = await
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
833
|
+
const updated = await runWithOwnerFallback(ownerCandidates, (candidateOwner) =>
|
|
834
|
+
stickerPackService.setPackCover({
|
|
835
|
+
ownerJid: candidateOwner,
|
|
836
|
+
identifier,
|
|
837
|
+
stickerId: asset.id,
|
|
838
|
+
}),
|
|
839
|
+
);
|
|
692
840
|
|
|
693
841
|
await sendReply({
|
|
694
842
|
sock,
|
|
@@ -715,13 +863,15 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
715
863
|
throw new StickerPackError(STICKER_PACK_ERROR_CODES.STICKER_NOT_FOUND, 'Não encontrei uma figurinha para adicionar.');
|
|
716
864
|
}
|
|
717
865
|
|
|
718
|
-
const updated = await
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
866
|
+
const updated = await runWithOwnerFallback(ownerCandidates, (candidateOwner) =>
|
|
867
|
+
stickerPackService.addStickerToPack({
|
|
868
|
+
ownerJid: candidateOwner,
|
|
869
|
+
identifier,
|
|
870
|
+
asset,
|
|
871
|
+
emojis: options.emojis,
|
|
872
|
+
accessibilityLabel: options.label || options.accessibility || null,
|
|
873
|
+
}),
|
|
874
|
+
);
|
|
725
875
|
|
|
726
876
|
await sendReply({
|
|
727
877
|
sock,
|
|
@@ -742,11 +892,13 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
742
892
|
const { token: identifier, rest: selectorRest } = readToken(rest);
|
|
743
893
|
const { token: selector } = readToken(selectorRest);
|
|
744
894
|
|
|
745
|
-
const result = await
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
895
|
+
const result = await runWithOwnerFallback(ownerCandidates, (candidateOwner) =>
|
|
896
|
+
stickerPackService.removeStickerFromPack({
|
|
897
|
+
ownerJid: candidateOwner,
|
|
898
|
+
identifier,
|
|
899
|
+
selector,
|
|
900
|
+
}),
|
|
901
|
+
);
|
|
750
902
|
|
|
751
903
|
await sendReply({
|
|
752
904
|
sock,
|
|
@@ -765,16 +917,18 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
765
917
|
|
|
766
918
|
case 'reorder': {
|
|
767
919
|
const { token: identifier, rest: rawOrder } = readToken(rest);
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
920
|
+
const updated = await runWithOwnerFallback(ownerCandidates, async (candidateOwner) => {
|
|
921
|
+
const orderStickerIds = await parseReorderInput({
|
|
922
|
+
ownerJid: candidateOwner,
|
|
923
|
+
identifier,
|
|
924
|
+
rawOrder,
|
|
925
|
+
});
|
|
773
926
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
927
|
+
return stickerPackService.reorderPackItems({
|
|
928
|
+
ownerJid: candidateOwner,
|
|
929
|
+
identifier,
|
|
930
|
+
orderStickerIds,
|
|
931
|
+
});
|
|
778
932
|
});
|
|
779
933
|
|
|
780
934
|
await sendReply({
|
|
@@ -796,11 +950,13 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
796
950
|
const { token: identifier, rest: cloneNameRaw } = readToken(rest);
|
|
797
951
|
const cloneName = normalizePackName(cloneNameRaw, { label: 'Novo nome do clone' });
|
|
798
952
|
|
|
799
|
-
const cloned = await
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
953
|
+
const cloned = await runWithOwnerFallback(ownerCandidates, (candidateOwner) =>
|
|
954
|
+
stickerPackService.clonePack({
|
|
955
|
+
ownerJid: candidateOwner,
|
|
956
|
+
identifier,
|
|
957
|
+
newName: cloneName,
|
|
958
|
+
}),
|
|
959
|
+
);
|
|
804
960
|
|
|
805
961
|
await sendReply({
|
|
806
962
|
sock,
|
|
@@ -819,7 +975,7 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
819
975
|
|
|
820
976
|
case 'delete': {
|
|
821
977
|
const identifier = readSingleArgument(rest);
|
|
822
|
-
const deleted = await stickerPackService.deletePack({ ownerJid, identifier });
|
|
978
|
+
const deleted = await runWithOwnerFallback(ownerCandidates, (candidateOwner) => stickerPackService.deletePack({ ownerJid: candidateOwner, identifier }));
|
|
823
979
|
|
|
824
980
|
await sendReply({
|
|
825
981
|
sock,
|
|
@@ -840,11 +996,13 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
840
996
|
const { token: identifier, rest: visibilityRaw } = readToken(rest);
|
|
841
997
|
const visibility = unquote(visibilityRaw);
|
|
842
998
|
|
|
843
|
-
const updated = await
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
999
|
+
const updated = await runWithOwnerFallback(ownerCandidates, (candidateOwner) =>
|
|
1000
|
+
stickerPackService.setPackVisibility({
|
|
1001
|
+
ownerJid: candidateOwner,
|
|
1002
|
+
identifier,
|
|
1003
|
+
visibility,
|
|
1004
|
+
}),
|
|
1005
|
+
);
|
|
848
1006
|
|
|
849
1007
|
await sendReply({
|
|
850
1008
|
sock,
|
|
@@ -863,7 +1021,7 @@ export async function handlePackCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
863
1021
|
|
|
864
1022
|
case 'send': {
|
|
865
1023
|
const identifier = readSingleArgument(rest);
|
|
866
|
-
const packDetails = await stickerPackService.getPackInfoForSend({ ownerJid, identifier });
|
|
1024
|
+
const packDetails = await runWithOwnerFallback(ownerCandidates, (candidateOwner) => stickerPackService.getPackInfoForSend({ ownerJid: candidateOwner, identifier }));
|
|
867
1025
|
const packBuild = await buildStickerPackMessage(packDetails);
|
|
868
1026
|
const sendResult = await sendStickerPackWithFallback({
|
|
869
1027
|
sock,
|
|
@@ -942,15 +1100,28 @@ export async function maybeCaptureIncomingSticker({ messageInfo, senderJid, isMe
|
|
|
942
1100
|
if (isMessageFromBot) return null;
|
|
943
1101
|
if (!isUserJid(senderJid)) return null;
|
|
944
1102
|
|
|
1103
|
+
const senderInfo = extractSenderInfoFromMessage(messageInfo);
|
|
1104
|
+
let ownerJid = normalizeJid(senderJid) || senderJid;
|
|
1105
|
+
try {
|
|
1106
|
+
const resolvedOwner = await resolveUserId({
|
|
1107
|
+
lid: senderInfo?.lid,
|
|
1108
|
+
jid: senderInfo?.jid || senderJid || null,
|
|
1109
|
+
participantAlt: senderInfo?.participantAlt || null,
|
|
1110
|
+
});
|
|
1111
|
+
ownerJid = normalizeJid(resolvedOwner || ownerJid) || ownerJid;
|
|
1112
|
+
} catch {
|
|
1113
|
+
ownerJid = normalizeJid(senderJid) || senderJid;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
945
1116
|
try {
|
|
946
1117
|
return await captureIncomingStickerAsset({
|
|
947
1118
|
messageInfo,
|
|
948
|
-
ownerJid
|
|
1119
|
+
ownerJid,
|
|
949
1120
|
});
|
|
950
1121
|
} catch (error) {
|
|
951
1122
|
logger.warn('Falha ao capturar figurinha recebida para storage.', {
|
|
952
1123
|
action: 'pack_capture_warning',
|
|
953
|
-
owner_jid:
|
|
1124
|
+
owner_jid: ownerJid,
|
|
954
1125
|
error: error.message,
|
|
955
1126
|
});
|
|
956
1127
|
return null;
|
|
@@ -57,15 +57,17 @@ const toStatusClass = (statusCode) => {
|
|
|
57
57
|
return `${head}xx`;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
export const resolveRouteGroup = ({ pathname, metricsPath, catalogConfig = null } = {}) => {
|
|
60
|
+
export const resolveRouteGroup = ({ pathname, metricsPath, catalogConfig = null, userConfig = null, systemAdminConfig = null } = {}) => {
|
|
61
61
|
if (pathname?.startsWith(metricsPath)) return 'metrics';
|
|
62
|
+
if (pathname === '/healthz' || pathname === '/readyz') return 'health';
|
|
62
63
|
if (pathname === '/sitemap.xml') return 'sitemap';
|
|
63
64
|
if (pathname === '/api/marketplace/stats') return 'marketplace_stats';
|
|
64
65
|
|
|
65
|
-
const apiBasePath = catalogConfig?.apiBasePath || '';
|
|
66
|
+
const apiBasePath = catalogConfig?.apiBasePath || userConfig?.apiBasePath || '';
|
|
66
67
|
const webPath = catalogConfig?.webPath || '';
|
|
67
68
|
const dataPublicPath = catalogConfig?.dataPublicPath || '';
|
|
68
|
-
const userProfilePath =
|
|
69
|
+
const userProfilePath = userConfig?.webPath || '';
|
|
70
|
+
const systemAdminPath = systemAdminConfig?.webPath || '';
|
|
69
71
|
|
|
70
72
|
if (apiBasePath && (pathname === apiBasePath || pathname?.startsWith(`${apiBasePath}/`))) {
|
|
71
73
|
if (pathname === `${apiBasePath}/auth/google/session` || pathname === `${apiBasePath}/me` || pathname === `${apiBasePath}/admin/session`) {
|
|
@@ -78,6 +80,7 @@ export const resolveRouteGroup = ({ pathname, metricsPath, catalogConfig = null
|
|
|
78
80
|
return 'catalog_api_public';
|
|
79
81
|
}
|
|
80
82
|
if (dataPublicPath && (pathname === dataPublicPath || pathname?.startsWith(`${dataPublicPath}/`))) return 'catalog_data_asset';
|
|
83
|
+
if (systemAdminPath && (pathname === systemAdminPath || pathname === `${systemAdminPath}/`)) return 'system_admin_web';
|
|
81
84
|
if (userProfilePath && (pathname === userProfilePath || pathname === `${userProfilePath}/`)) return 'catalog_user_profile';
|
|
82
85
|
if (webPath && (pathname === webPath || pathname?.startsWith(`${webPath}/`))) return 'catalog_web';
|
|
83
86
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { executeQuery, TABLES } from '../../database/index.js';
|
|
2
|
+
import { normalizeJid } from '../config/baileysConfig.js';
|
|
3
|
+
import { toWhatsAppPhoneDigits } from './whatsappLoginLinkService.js';
|
|
4
|
+
|
|
5
|
+
const parseEnvInt = (value, fallback, min, max) => {
|
|
6
|
+
const numeric = Number(value);
|
|
7
|
+
if (!Number.isFinite(numeric)) return fallback;
|
|
8
|
+
return Math.max(min, Math.min(max, Math.floor(numeric)));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const GOOGLE_LINK_CHECK_CACHE_TTL_MS = parseEnvInt(process.env.WHATSAPP_GOOGLE_LINK_CHECK_CACHE_TTL_MS, 60_000, 1_000, 10 * 60_000);
|
|
12
|
+
const googleLinkCheckCache = new Map();
|
|
13
|
+
let googleLinkTableMissingLogged = false;
|
|
14
|
+
|
|
15
|
+
const normalizeCacheKey = ({ ownerJid = '', ownerPhone = '' }) => {
|
|
16
|
+
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
17
|
+
const normalizedOwnerPhone = toWhatsAppPhoneDigits(ownerPhone || ownerJid) || '';
|
|
18
|
+
return `${normalizedOwnerJid}|${normalizedOwnerPhone}`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getCachedGoogleLinkStatus = (cacheKey) => {
|
|
22
|
+
const cached = googleLinkCheckCache.get(cacheKey);
|
|
23
|
+
if (!cached) return null;
|
|
24
|
+
if (Number(cached.expiresAt || 0) <= Date.now()) {
|
|
25
|
+
googleLinkCheckCache.delete(cacheKey);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return Boolean(cached.linked);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const setCachedGoogleLinkStatus = (cacheKey, linked) => {
|
|
32
|
+
googleLinkCheckCache.set(cacheKey, {
|
|
33
|
+
linked: Boolean(linked),
|
|
34
|
+
expiresAt: Date.now() + GOOGLE_LINK_CHECK_CACHE_TTL_MS,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const isWhatsAppUserLinkedToGoogleWebAccount = async ({ ownerJid = '', ownerPhone = '' } = {}) => {
|
|
39
|
+
const normalizedOwnerJid = normalizeJid(ownerJid) || '';
|
|
40
|
+
const normalizedOwnerPhone = toWhatsAppPhoneDigits(ownerPhone || ownerJid) || '';
|
|
41
|
+
if (!normalizedOwnerJid && !normalizedOwnerPhone) return false;
|
|
42
|
+
|
|
43
|
+
const cacheKey = normalizeCacheKey({ ownerJid: normalizedOwnerJid, ownerPhone: normalizedOwnerPhone });
|
|
44
|
+
const cached = getCachedGoogleLinkStatus(cacheKey);
|
|
45
|
+
if (cached !== null) return cached;
|
|
46
|
+
|
|
47
|
+
const whereClauses = [];
|
|
48
|
+
const params = [];
|
|
49
|
+
if (normalizedOwnerJid) {
|
|
50
|
+
whereClauses.push('owner_jid = ?');
|
|
51
|
+
params.push(normalizedOwnerJid);
|
|
52
|
+
}
|
|
53
|
+
if (normalizedOwnerPhone) {
|
|
54
|
+
whereClauses.push('owner_phone = ?');
|
|
55
|
+
params.push(normalizedOwnerPhone);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const rows = await executeQuery(
|
|
59
|
+
`SELECT google_sub
|
|
60
|
+
FROM ${TABLES.STICKER_WEB_GOOGLE_USER}
|
|
61
|
+
WHERE ${whereClauses.join(' OR ')}
|
|
62
|
+
LIMIT 1`,
|
|
63
|
+
params,
|
|
64
|
+
).catch((error) => {
|
|
65
|
+
if (error?.code === 'ER_NO_SUCH_TABLE') {
|
|
66
|
+
if (!googleLinkTableMissingLogged) {
|
|
67
|
+
googleLinkTableMissingLogged = true;
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const linked = Array.isArray(rows) && rows.length > 0;
|
|
75
|
+
setCachedGoogleLinkStatus(cacheKey, linked);
|
|
76
|
+
return linked;
|
|
77
|
+
};
|