@omnizap-system/omnizap 2.6.1 → 2.6.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/.env.example +78 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +6 -0
- package/app/configParts/adminIdentity.js +36 -7
- package/app/configParts/baileysConfig.js +343 -56
- package/app/configParts/groupUtils.js +226 -0
- package/app/configParts/loggerConfig.js +185 -0
- package/app/configParts/messagePersistenceService.js +307 -5
- package/app/configParts/sessionConfig.js +242 -0
- package/app/connection/baileysCompatibility.test.js +10 -1
- package/app/connection/baileysDbAuthState.js +205 -9
- package/app/connection/baileysLibsignalPatch.js +210 -0
- package/app/connection/groupOwnerWriteStateResolver.js +141 -0
- package/app/connection/socketController.js +694 -123
- package/app/connection/socketController.multiSession.test.js +128 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +96 -4
- package/app/controllers/messageProcessingPipeline.js +90 -9
- package/app/controllers/messageProcessingPipeline.test.js +202 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +452 -0
- package/app/services/multiSession/groupOwnershipRepository.js +346 -0
- package/app/services/multiSession/groupOwnershipService.js +809 -0
- package/app/services/multiSession/groupOwnershipService.test.js +317 -0
- package/app/services/multiSession/sessionRegistryService.js +239 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +391 -25
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +14 -6
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/css/payments-react.css +478 -0
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/homeReactApp.js +2 -2
- package/public/js/apps/paymentsCancelReactApp.js +45 -0
- package/public/js/apps/paymentsReactApp.js +399 -0
- package/public/js/apps/paymentsSuccessReactApp.js +148 -0
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/pagamentos-cancelado.html +21 -0
- package/public/pages/pagamentos-sucesso.html +21 -0
- package/public/pages/pagamentos.html +30 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +13 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +564 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +254 -0
- package/server/controllers/payments/paymentsController.js +731 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +228 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailAutomationRuntime.js +36 -1
- package/server/email/emailAutomationService.js +42 -1
- package/server/email/emailTemplateService.js +140 -33
- package/server/http/httpRequestUtils.js +18 -14
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +35 -3
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +50 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/payments/paymentsRouter.js +47 -0
- package/server/routes/static/staticPageRouter.js +30 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +5 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -4,7 +4,7 @@ import 'dotenv/config';
|
|
|
4
4
|
import { isAdminCommand } from '../modules/adminModule/groupCommandHandlers.js';
|
|
5
5
|
import { explicarComandoGlobal, registerGlobalHelpCommandExecution } from '../services/ai/globalModuleAiHelpService.js';
|
|
6
6
|
import { extractSupportedStickerMediaDetails, processSticker } from '../modules/stickerModule/stickerCommand.js';
|
|
7
|
-
import { detectAllMediaTypes, extractMessageContent, getExpiration, getJidServer, isGroupJid, isStatusJid, isSameJidUser, normalizeJid, normalizeWAPresence, resolveBotJid, extractSenderInfoFromMessage, resolveUserId, resolveAddressingModeFromMessageKey, resolveCanonicalWhatsAppJid, parseEnvBool, parseEnvInt } from '../config/index.js';
|
|
7
|
+
import { detectAllMediaTypes, extractMessageContent, getExpiration, getJidServer, isGroupJid, isStatusJid, isSameJidUser, normalizeJid, normalizeWAPresence, resolveBotJid, extractSenderInfoFromMessage, resolveUserId, resolveAddressingModeFromMessageKey, resolveCanonicalWhatsAppJid, parseEnvBool, parseEnvInt, getMultiSessionRuntimeConfig } from '../config/index.js';
|
|
8
8
|
import { isUserAdmin } from '../config/index.js';
|
|
9
9
|
import { isAdminSenderAsync } from '../config/index.js';
|
|
10
10
|
import { executeQuery, TABLES } from '../../database/index.js';
|
|
@@ -20,6 +20,7 @@ import { createMessageAnalysisEvent } from '../modules/analyticsModule/messageAn
|
|
|
20
20
|
import { routeConversationMessage } from '../services/ai/conversationRouterService.js';
|
|
21
21
|
import { executeMessageCommandRoute, isKnownNonAdminCommand } from '../services/ai/messageCommandExecutionService.js';
|
|
22
22
|
import { canSendMessageInStickerFocus, registerMessageUsageInStickerFocus, resolveStickerFocusMessageClassification, resolveStickerFocusState, shouldSendStickerFocusWarning } from '../services/sticker/stickerFocusService.js';
|
|
23
|
+
import { getOwner as getGroupOwner } from '../services/multiSession/groupOwnershipService.js';
|
|
23
24
|
import { createPreProcessingMiddlewares } from './messagePipeline/preProcessingMiddlewares.js';
|
|
24
25
|
import { createConversationMiddleware } from './messagePipeline/conversationMiddleware.js';
|
|
25
26
|
import { createCommandMiddleware } from './messagePipeline/commandMiddleware.js';
|
|
@@ -41,13 +42,28 @@ const MESSAGE_REPLY_PRESENCE_BEFORE = normalizeWAPresence(process.env.MESSAGE_RE
|
|
|
41
42
|
const MESSAGE_REPLY_PRESENCE_AFTER = normalizeWAPresence(process.env.MESSAGE_REPLY_PRESENCE_AFTER, 'paused');
|
|
42
43
|
const MESSAGE_REPLY_PRESENCE_DELAY_MS = parseEnvInt(process.env.MESSAGE_REPLY_PRESENCE_DELAY_MS, 280, 0, 3_000);
|
|
43
44
|
const MESSAGE_REPLY_PRESENCE_SUBSCRIBE = parseEnvBool(process.env.MESSAGE_REPLY_PRESENCE_SUBSCRIBE, true);
|
|
45
|
+
const CONVERSATIONAL_AUTO_REPLY_ENABLED = parseEnvBool(process.env.CONVERSATIONAL_AUTO_REPLY_ENABLED, false);
|
|
44
46
|
const WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN = parseEnvBool(process.env.WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN, true);
|
|
47
|
+
const WHATSAPP_ALLOW_SELF_COMMANDS_ON_APPEND = parseEnvBool(process.env.WHATSAPP_ALLOW_SELF_COMMANDS_ON_APPEND, true);
|
|
45
48
|
const SITE_ORIGIN =
|
|
46
49
|
String(process.env.SITE_ORIGIN || process.env.WHATSAPP_LOGIN_BASE_URL || 'https://omnizap.shop')
|
|
47
50
|
.trim()
|
|
48
51
|
.replace(/\/+$/, '') || 'https://omnizap.shop';
|
|
49
52
|
const SITE_LOGIN_URL = `${SITE_ORIGIN}/login/`;
|
|
50
53
|
const SITE_GROUP_LOGIN_URL = `${SITE_ORIGIN}/login`;
|
|
54
|
+
const MULTI_SESSION_RUNTIME_CONFIG = getMultiSessionRuntimeConfig();
|
|
55
|
+
const PRIMARY_SESSION_ID = String(MULTI_SESSION_RUNTIME_CONFIG?.primarySessionId || 'default').trim() || 'default';
|
|
56
|
+
const VALID_SESSION_IDS = new Set(Array.isArray(MULTI_SESSION_RUNTIME_CONFIG?.sessionIds) ? MULTI_SESSION_RUNTIME_CONFIG.sessionIds : [PRIMARY_SESSION_ID]);
|
|
57
|
+
const GROUP_OWNER_ENFORCEMENT_MODE = String(MULTI_SESSION_RUNTIME_CONFIG?.ownerEnforcementMode || 'off')
|
|
58
|
+
.trim()
|
|
59
|
+
.toLowerCase();
|
|
60
|
+
|
|
61
|
+
const normalizeSessionId = (sessionId) => {
|
|
62
|
+
const normalized = String(sessionId || '').trim();
|
|
63
|
+
if (!normalized) return PRIMARY_SESSION_ID;
|
|
64
|
+
if (VALID_SESSION_IDS.has(normalized)) return normalized;
|
|
65
|
+
return PRIMARY_SESSION_ID;
|
|
66
|
+
};
|
|
51
67
|
|
|
52
68
|
let messageAnalyticsTableMissingLogged = false;
|
|
53
69
|
const recentCommandExecutions = new Map();
|
|
@@ -170,7 +186,7 @@ const maybeHandleStartLoginMessage = async ({ sock, messageInfo, extractedText,
|
|
|
170
186
|
|
|
171
187
|
if (isGroupMessage) {
|
|
172
188
|
await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
|
|
173
|
-
text: '
|
|
189
|
+
text: '🔐 *Segurança dos seus dados*\n\n' + 'Para proteger sua conta, o acesso é feito apenas no *privado*.\n' + 'Envie *iniciar* no chat privado do bot para receber seu link de login seguro.\n\n' + '⚠️ Por segurança, não enviamos links de acesso em grupos.',
|
|
174
190
|
});
|
|
175
191
|
return true;
|
|
176
192
|
}
|
|
@@ -199,7 +215,7 @@ const maybeHandleStartLoginMessage = async ({ sock, messageInfo, extractedText,
|
|
|
199
215
|
const safeName = String(senderName || '').trim();
|
|
200
216
|
const greeting = safeName ? `Oi, *${safeName}*!` : 'Oi!';
|
|
201
217
|
await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
|
|
202
|
-
text: `${greeting}\n\n` + '
|
|
218
|
+
text: `${greeting}\n\n` + '🚀 *Bem-vindo ao OmniZap!*\n\n' + 'Para continuar, faça login com sua conta *Google* no link abaixo:\n\n' + `${loginUrl}\n\n` + '🔐 Seu número do WhatsApp será vinculado automaticamente à conta após o login.\n' + '⚠️ Use apenas este link e não compartilhe com outras pessoas.',
|
|
203
219
|
});
|
|
204
220
|
|
|
205
221
|
return true;
|
|
@@ -374,7 +390,7 @@ const ensureUserHasGoogleWebLoginForCommand = async ({ sock, messageInfo, sender
|
|
|
374
390
|
}
|
|
375
391
|
|
|
376
392
|
const loginUrl = isGroupMessage ? SITE_GROUP_LOGIN_URL : buildSiteLoginUrlForUser(resolvedCanonicalUserId || senderJid);
|
|
377
|
-
const loginMessage = isGroupMessage ?
|
|
393
|
+
const loginMessage = isGroupMessage ? '🔐 *Login necessário*\n\n' + 'Para usar os comandos do bot, você precisa estar logado com sua conta *Google*.\n\n' + 'Acesse o link abaixo para entrar:\n' + `${loginUrl}\n\n` + '⚠️ Por segurança, recomendamos abrir o link no privado.' : '🔐 *Login necessário*\n\n' + 'Para usar os comandos do bot, faça login com sua conta *Google* no link abaixo:\n\n' + `${loginUrl}\n\n` + '✅ Após entrar, volte aqui e envie o comando novamente\n' + `(ex.: *${commandPrefix}menu*).`;
|
|
378
394
|
|
|
379
395
|
await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
|
|
380
396
|
text: loginMessage,
|
|
@@ -459,6 +475,43 @@ const resolveSenderOwnerForContext = async (ctx) => {
|
|
|
459
475
|
return ctx.memo.senderOwnerPromise;
|
|
460
476
|
};
|
|
461
477
|
|
|
478
|
+
const resolveGroupOwnerForContext = async (ctx, { bypassCache = false } = {}) => {
|
|
479
|
+
if (!ctx.isGroupMessage) return null;
|
|
480
|
+
|
|
481
|
+
if (!bypassCache && ctx.memo.groupOwnerPromise) {
|
|
482
|
+
return ctx.memo.groupOwnerPromise;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const fetchOwnerPromise = (async () => {
|
|
486
|
+
try {
|
|
487
|
+
const ownerState = await getGroupOwner(ctx.remoteJid, { bypassCache });
|
|
488
|
+
const ownerSessionId = String(ownerState?.ownerSessionId || '').trim() || null;
|
|
489
|
+
ctx.ownerSessionId = ownerSessionId;
|
|
490
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
491
|
+
owner_session_id: ownerSessionId,
|
|
492
|
+
});
|
|
493
|
+
return ownerState;
|
|
494
|
+
} catch (error) {
|
|
495
|
+
logger.warn('Falha ao resolver owner de grupo para roteamento de sessão.', {
|
|
496
|
+
action: 'group_owner_resolution_failed',
|
|
497
|
+
sessionId: ctx.sessionId,
|
|
498
|
+
groupId: ctx.remoteJid,
|
|
499
|
+
error: error?.message,
|
|
500
|
+
});
|
|
501
|
+
ctx.ownerSessionId = null;
|
|
502
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
503
|
+
owner_resolution_error: String(error?.code || error?.name || 'lookup_failed')
|
|
504
|
+
.trim()
|
|
505
|
+
.toLowerCase(),
|
|
506
|
+
});
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
})();
|
|
510
|
+
|
|
511
|
+
ctx.memo.groupOwnerPromise = fetchOwnerPromise;
|
|
512
|
+
return fetchOwnerPromise;
|
|
513
|
+
};
|
|
514
|
+
|
|
462
515
|
const resolveHasGoogleLoginForContext = async (ctx) => {
|
|
463
516
|
if (ctx.isMessageFromBot || !WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN) {
|
|
464
517
|
return undefined;
|
|
@@ -476,10 +529,11 @@ const resolveHasGoogleLoginForContext = async (ctx) => {
|
|
|
476
529
|
return ctx.memo.hasGoogleLoginPromise;
|
|
477
530
|
};
|
|
478
531
|
|
|
479
|
-
const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyUpsert, sock }) => {
|
|
532
|
+
const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyUpsert, sock, sessionId }) => {
|
|
480
533
|
const key = messageInfo?.key || {};
|
|
481
534
|
const remoteJid = key?.remoteJid;
|
|
482
535
|
if (!remoteJid) return null;
|
|
536
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
483
537
|
|
|
484
538
|
const isGroupMessage = isGroupJid(remoteJid);
|
|
485
539
|
const extractedText = extractMessageContent(messageInfo);
|
|
@@ -505,6 +559,7 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
505
559
|
.slice(0, 10);
|
|
506
560
|
|
|
507
561
|
const analysisPayload = {
|
|
562
|
+
sessionId: normalizedSessionId,
|
|
508
563
|
messageId: key?.id || null,
|
|
509
564
|
chatId: remoteJid || null,
|
|
510
565
|
senderId: senderJid || null,
|
|
@@ -530,6 +585,8 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
530
585
|
upsert_type: upsertType,
|
|
531
586
|
is_notify_upsert: isNotifyUpsert,
|
|
532
587
|
is_history_append: upsertType === 'append',
|
|
588
|
+
session_id: normalizedSessionId,
|
|
589
|
+
owner_session_id: null,
|
|
533
590
|
addressing_mode: addressingMode || null,
|
|
534
591
|
participant_alt: key?.participantAlt || null,
|
|
535
592
|
remote_jid_alt: key?.remoteJidAlt || null,
|
|
@@ -550,6 +607,8 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
550
607
|
botJidCandidates,
|
|
551
608
|
botJid,
|
|
552
609
|
isMessageFromBot,
|
|
610
|
+
sessionId: normalizedSessionId,
|
|
611
|
+
ownerSessionId: null,
|
|
553
612
|
commandPrefix: DEFAULT_COMMAND_PREFIX,
|
|
554
613
|
groupConfig: null,
|
|
555
614
|
groupConfigLoaded: false,
|
|
@@ -564,7 +623,7 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
564
623
|
};
|
|
565
624
|
};
|
|
566
625
|
|
|
567
|
-
const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware } = createPreProcessingMiddlewares({
|
|
626
|
+
const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, enforceGroupOwnerMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware } = createPreProcessingMiddlewares({
|
|
568
627
|
executeQuery,
|
|
569
628
|
TABLES,
|
|
570
629
|
isStatusJid,
|
|
@@ -577,6 +636,10 @@ const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, app
|
|
|
577
636
|
ensureGroupConfigForContext,
|
|
578
637
|
resolveStickerFocusState,
|
|
579
638
|
resolveStickerFocusMessageClassification,
|
|
639
|
+
resolveGroupOwnerForContext,
|
|
640
|
+
ownerEnforcementMode: GROUP_OWNER_ENFORCEMENT_MODE,
|
|
641
|
+
primarySessionId: PRIMARY_SESSION_ID,
|
|
642
|
+
allowSelfCommandsOnAppend: WHATSAPP_ALLOW_SELF_COMMANDS_ON_APPEND,
|
|
580
643
|
resolveSenderAdminForContext,
|
|
581
644
|
isUserAdmin,
|
|
582
645
|
canSendMessageInStickerFocus,
|
|
@@ -605,6 +668,7 @@ const routeConversationMiddleware = createConversationMiddleware({
|
|
|
605
668
|
sendReply,
|
|
606
669
|
routeConversationMessage,
|
|
607
670
|
stopMessagePipeline,
|
|
671
|
+
conversationAutoReplyEnabled: CONVERSATIONAL_AUTO_REPLY_ENABLED,
|
|
608
672
|
});
|
|
609
673
|
|
|
610
674
|
const executeCommandMiddleware = createCommandMiddleware({
|
|
@@ -642,7 +706,7 @@ const runPostProcessingMiddleware = createPostProcessingMiddleware({
|
|
|
642
706
|
normalizeAnalysisErrorCode,
|
|
643
707
|
});
|
|
644
708
|
|
|
645
|
-
const MESSAGE_PIPELINE_MIDDLEWARES = [touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware, routeConversationMiddleware, executeCommandMiddleware, runPostProcessingMiddleware];
|
|
709
|
+
const MESSAGE_PIPELINE_MIDDLEWARES = [touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, enforceGroupOwnerMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware, routeConversationMiddleware, executeCommandMiddleware, runPostProcessingMiddleware];
|
|
646
710
|
|
|
647
711
|
const runMessagePipeline = async (ctx) => {
|
|
648
712
|
for (const middleware of MESSAGE_PIPELINE_MIDDLEWARES) {
|
|
@@ -657,7 +721,8 @@ const runMessagePipeline = async (ctx) => {
|
|
|
657
721
|
*
|
|
658
722
|
* @param {Object} update - Objeto contendo a atualização do WhatsApp.
|
|
659
723
|
*/
|
|
660
|
-
export const handleMessagesThroughPipeline = async (update, sock) => {
|
|
724
|
+
export const handleMessagesThroughPipeline = async (update, sock, options = {}) => {
|
|
725
|
+
const sessionId = normalizeSessionId(options?.sessionId);
|
|
661
726
|
if (update.messages && Array.isArray(update.messages)) {
|
|
662
727
|
try {
|
|
663
728
|
const upsertType = update?.type || null;
|
|
@@ -669,6 +734,7 @@ export const handleMessagesThroughPipeline = async (update, sock) => {
|
|
|
669
734
|
upsertType,
|
|
670
735
|
isNotifyUpsert,
|
|
671
736
|
sock,
|
|
737
|
+
sessionId,
|
|
672
738
|
});
|
|
673
739
|
if (!context) continue;
|
|
674
740
|
|
|
@@ -678,19 +744,34 @@ export const handleMessagesThroughPipeline = async (update, sock) => {
|
|
|
678
744
|
context.analysisPayload.processingResult = 'error';
|
|
679
745
|
context.analysisPayload.errorCode = normalizeAnalysisErrorCode(messageError);
|
|
680
746
|
logger.error('Erro ao processar mensagem individual:', {
|
|
747
|
+
sessionId,
|
|
748
|
+
ownerSessionId: context.ownerSessionId || null,
|
|
681
749
|
error: messageError?.message,
|
|
682
750
|
messageId: context.key?.id || null,
|
|
683
751
|
remoteJid: context.remoteJid,
|
|
684
752
|
});
|
|
685
753
|
} finally {
|
|
754
|
+
logger.debug('Mensagem processada pelo pipeline.', {
|
|
755
|
+
action: 'message_pipeline_processed',
|
|
756
|
+
sessionId: context.sessionId,
|
|
757
|
+
ownerSessionId: context.ownerSessionId || null,
|
|
758
|
+
messageId: context.key?.id || null,
|
|
759
|
+
remoteJid: context.remoteJid,
|
|
760
|
+
result: context.analysisPayload.processingResult,
|
|
761
|
+
isCommand: context.analysisPayload.isCommand,
|
|
762
|
+
});
|
|
686
763
|
persistMessageAnalysisEvent(context.analysisPayload);
|
|
687
764
|
}
|
|
688
765
|
}
|
|
689
766
|
} catch (error) {
|
|
690
|
-
logger.error('Erro ao processar mensagens:',
|
|
767
|
+
logger.error('Erro ao processar mensagens:', {
|
|
768
|
+
sessionId,
|
|
769
|
+
error: error?.message,
|
|
770
|
+
});
|
|
691
771
|
}
|
|
692
772
|
} else {
|
|
693
773
|
logger.info('🔄 Processando evento recebido:', {
|
|
774
|
+
sessionId,
|
|
694
775
|
eventType: update?.type || 'unknown',
|
|
695
776
|
eventData: update,
|
|
696
777
|
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { createPreProcessingMiddlewares } from './messagePipeline/preProcessingMiddlewares.js';
|
|
5
|
+
import { createCommandMiddleware } from './messagePipeline/commandMiddleware.js';
|
|
6
|
+
|
|
7
|
+
const createContext = (overrides = {}) => ({
|
|
8
|
+
sock: {},
|
|
9
|
+
messageInfo: { key: { id: 'msg-1' }, message: { conversation: '/menu' } },
|
|
10
|
+
key: { id: 'msg-1' },
|
|
11
|
+
remoteJid: '120363111111111111@g.us',
|
|
12
|
+
isGroupMessage: true,
|
|
13
|
+
extractedText: '/menu',
|
|
14
|
+
senderJid: '5511999999999@s.whatsapp.net',
|
|
15
|
+
senderIdentity: '5511999999999@s.whatsapp.net',
|
|
16
|
+
senderName: 'Tester',
|
|
17
|
+
expirationMessage: 0,
|
|
18
|
+
botJid: '5511888888888@s.whatsapp.net',
|
|
19
|
+
isMessageFromBot: false,
|
|
20
|
+
commandPrefix: '/',
|
|
21
|
+
mediaEntries: [],
|
|
22
|
+
upsertType: 'notify',
|
|
23
|
+
isNotifyUpsert: true,
|
|
24
|
+
sessionId: 'session-a',
|
|
25
|
+
ownerSessionId: null,
|
|
26
|
+
isCommandMessage: false,
|
|
27
|
+
hasCommandPrefix: false,
|
|
28
|
+
pipelineStopped: false,
|
|
29
|
+
analysisPayload: {
|
|
30
|
+
processingResult: 'processed',
|
|
31
|
+
errorCode: null,
|
|
32
|
+
metadata: {},
|
|
33
|
+
isCommand: false,
|
|
34
|
+
commandPrefix: '/',
|
|
35
|
+
commandName: null,
|
|
36
|
+
commandArgsCount: 0,
|
|
37
|
+
commandKnown: null,
|
|
38
|
+
},
|
|
39
|
+
...overrides,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const createStopMessagePipeline =
|
|
43
|
+
() =>
|
|
44
|
+
(ctx, processingResult = '', metadataPatch = null) => {
|
|
45
|
+
if (processingResult) {
|
|
46
|
+
ctx.analysisPayload.processingResult = processingResult;
|
|
47
|
+
}
|
|
48
|
+
if (metadataPatch) {
|
|
49
|
+
ctx.analysisPayload.metadata = {
|
|
50
|
+
...(ctx.analysisPayload.metadata || {}),
|
|
51
|
+
...(metadataPatch || {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
ctx.pipelineStopped = true;
|
|
55
|
+
return { stop: true };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const mergeAnalysisMetadata = (analysisPayload, patch) => {
|
|
59
|
+
analysisPayload.metadata = {
|
|
60
|
+
...(analysisPayload.metadata || {}),
|
|
61
|
+
...(patch || {}),
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const createSharedCommandDedupe = () => {
|
|
66
|
+
const cache = new Map();
|
|
67
|
+
return {
|
|
68
|
+
isDuplicateCommandExecution: (chatId, messageId) => cache.has(`${chatId}:${messageId}`),
|
|
69
|
+
markCommandExecution: (chatId, messageId) => {
|
|
70
|
+
cache.set(`${chatId}:${messageId}`, true);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const createCommandMiddlewareForTest = ({ executeRouteSpy, dedupe }) =>
|
|
76
|
+
createCommandMiddleware({
|
|
77
|
+
isAdminCommand: () => false,
|
|
78
|
+
isKnownNonAdminCommand: () => true,
|
|
79
|
+
isDuplicateCommandExecution: dedupe.isDuplicateCommandExecution,
|
|
80
|
+
markCommandExecution: dedupe.markCommandExecution,
|
|
81
|
+
MESSAGE_COMMAND_DEDUPE_TTL_MS: 120_000,
|
|
82
|
+
stopMessagePipeline: createStopMessagePipeline(),
|
|
83
|
+
WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN: false,
|
|
84
|
+
resolveCanonicalSenderJidForContext: async () => '5511999999999@s.whatsapp.net',
|
|
85
|
+
ensureUserHasGoogleWebLoginForCommand: async () => ({ allowed: true }),
|
|
86
|
+
SITE_LOGIN_URL: 'https://omnizap.shop/login',
|
|
87
|
+
COMMAND_REACT_EMOJI: '',
|
|
88
|
+
sendAndStore: async () => {},
|
|
89
|
+
executeMessageCommandRoute: async (payload) => {
|
|
90
|
+
executeRouteSpy.push(payload);
|
|
91
|
+
return {
|
|
92
|
+
commandRoute: payload.command || 'menu',
|
|
93
|
+
commandResult: { ok: true },
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
runCommand: async (_label, handler) => {
|
|
97
|
+
try {
|
|
98
|
+
await handler();
|
|
99
|
+
return { ok: true };
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return { ok: false, error };
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
sendReply: async () => {},
|
|
105
|
+
registerGlobalHelpCommandExecution: async () => {},
|
|
106
|
+
logger: { warn: () => {} },
|
|
107
|
+
normalizeAnalysisErrorCode: () => 'processing_error',
|
|
108
|
+
resolveSenderAdminForContext: async () => false,
|
|
109
|
+
isUserAdmin: async () => false,
|
|
110
|
+
buildCommandErrorHelpText: async () => '',
|
|
111
|
+
mergeAnalysisMetadata,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('messageProcessingPipeline: com owner enforcement ativo, apenas owner executa comando no grupo', async () => {
|
|
115
|
+
const pre = createPreProcessingMiddlewares({
|
|
116
|
+
executeQuery: async () => [],
|
|
117
|
+
TABLES: { RPG_PLAYER: 'rpg_player' },
|
|
118
|
+
isStatusJid: () => false,
|
|
119
|
+
stopMessagePipeline: createStopMessagePipeline(),
|
|
120
|
+
handleAntiLink: async () => false,
|
|
121
|
+
ensureCommandPrefixForContext: async () => '/',
|
|
122
|
+
resolveCaptchaByMessage: async () => {},
|
|
123
|
+
maybeHandleStartLoginMessage: async () => false,
|
|
124
|
+
mergeAnalysisMetadata,
|
|
125
|
+
ensureGroupConfigForContext: async () => ({}),
|
|
126
|
+
resolveStickerFocusState: () => ({ enabled: false }),
|
|
127
|
+
resolveStickerFocusMessageClassification: () => ({ isThrottleCandidate: false }),
|
|
128
|
+
resolveGroupOwnerForContext: async (ctx) => {
|
|
129
|
+
ctx.ownerSessionId = 'session-a';
|
|
130
|
+
return { ownerSessionId: 'session-a', assignmentVersion: 7 };
|
|
131
|
+
},
|
|
132
|
+
ownerEnforcementMode: 'enforce',
|
|
133
|
+
primarySessionId: 'session-a',
|
|
134
|
+
resolveSenderAdminForContext: async () => false,
|
|
135
|
+
isUserAdmin: async () => false,
|
|
136
|
+
canSendMessageInStickerFocus: () => ({ allowed: true, remainingMs: 0 }),
|
|
137
|
+
registerMessageUsageInStickerFocus: () => {},
|
|
138
|
+
shouldSendStickerFocusWarning: () => false,
|
|
139
|
+
sendReply: async () => {},
|
|
140
|
+
formatStickerFocusRuleLabel: () => '',
|
|
141
|
+
formatRemainingMinutesLabel: () => 1,
|
|
142
|
+
logger: { warn: () => {}, info: () => {} },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const executeRouteSpy = [];
|
|
146
|
+
const dedupe = createSharedCommandDedupe();
|
|
147
|
+
const commandMiddleware = createCommandMiddlewareForTest({ executeRouteSpy, dedupe });
|
|
148
|
+
|
|
149
|
+
const ownerCtx = createContext({
|
|
150
|
+
sessionId: 'session-a',
|
|
151
|
+
key: { id: 'msg-shared' },
|
|
152
|
+
messageInfo: { key: { id: 'msg-shared' }, message: { conversation: '/menu' } },
|
|
153
|
+
});
|
|
154
|
+
const nonOwnerCtx = createContext({
|
|
155
|
+
sessionId: 'session-b',
|
|
156
|
+
key: { id: 'msg-shared' },
|
|
157
|
+
messageInfo: { key: { id: 'msg-shared' }, message: { conversation: '/menu' } },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await pre.enforceGroupOwnerMiddleware(ownerCtx);
|
|
161
|
+
await pre.detectCommandIntentMiddleware(ownerCtx);
|
|
162
|
+
await commandMiddleware(ownerCtx);
|
|
163
|
+
|
|
164
|
+
const blockedResult = await pre.enforceGroupOwnerMiddleware(nonOwnerCtx);
|
|
165
|
+
assert.deepEqual(blockedResult, { stop: true });
|
|
166
|
+
await pre.detectCommandIntentMiddleware(nonOwnerCtx);
|
|
167
|
+
if (!nonOwnerCtx.pipelineStopped) {
|
|
168
|
+
await commandMiddleware(nonOwnerCtx);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
assert.equal(executeRouteSpy.length, 1);
|
|
172
|
+
assert.equal(ownerCtx.analysisPayload.processingResult, 'command_executed');
|
|
173
|
+
assert.equal(nonOwnerCtx.analysisPayload.processingResult, 'blocked_group_owner_enforcement');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('messageProcessingPipeline: dedupe de comando por chat+message impede execução dupla em duas sessões', async () => {
|
|
177
|
+
const executeRouteSpy = [];
|
|
178
|
+
const dedupe = createSharedCommandDedupe();
|
|
179
|
+
const commandMiddleware = createCommandMiddlewareForTest({ executeRouteSpy, dedupe });
|
|
180
|
+
|
|
181
|
+
const firstCtx = createContext({
|
|
182
|
+
sessionId: 'session-a',
|
|
183
|
+
isCommandMessage: true,
|
|
184
|
+
hasCommandPrefix: true,
|
|
185
|
+
key: { id: 'dup-msg-1' },
|
|
186
|
+
messageInfo: { key: { id: 'dup-msg-1' }, message: { conversation: '/menu' } },
|
|
187
|
+
});
|
|
188
|
+
const secondCtx = createContext({
|
|
189
|
+
sessionId: 'session-b',
|
|
190
|
+
isCommandMessage: true,
|
|
191
|
+
hasCommandPrefix: true,
|
|
192
|
+
key: { id: 'dup-msg-1' },
|
|
193
|
+
messageInfo: { key: { id: 'dup-msg-1' }, message: { conversation: '/menu' } },
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await commandMiddleware(firstCtx);
|
|
197
|
+
await commandMiddleware(secondCtx);
|
|
198
|
+
|
|
199
|
+
assert.equal(executeRouteSpy.length, 1);
|
|
200
|
+
assert.equal(firstCtx.analysisPayload.processingResult, 'command_executed');
|
|
201
|
+
assert.equal(secondCtx.analysisPayload.processingResult, 'duplicate_command_ignored');
|
|
202
|
+
});
|
|
@@ -7,7 +7,7 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
7
7
|
- arquivo_base: `app/modules/adminModule/commandConfig.json`
|
|
8
8
|
- schema_version: `2.0.0`
|
|
9
9
|
- module_enabled: `true`
|
|
10
|
-
- generated_at: `2026-03-
|
|
10
|
+
- generated_at: `2026-03-17T04:04:14.195Z`
|
|
11
11
|
|
|
12
12
|
## Escopo do Modulo
|
|
13
13
|
|