@omnizap-system/omnizap 2.6.0 → 2.6.2
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 +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +660 -158
- package/app/connection/socketController.multiSession.test.js +108 -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 +80 -2
- package/app/controllers/messageProcessingPipeline.js +93 -13
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- 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 +135 -27
- 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 +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- 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/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- 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/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-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/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- 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 +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/js/apps/apiDocsApp.js +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { createGroupOwnerWriteStateResolver } from './groupOwnerWriteStateResolver.js';
|
|
5
|
+
|
|
6
|
+
const createCache = () => {
|
|
7
|
+
const map = new Map();
|
|
8
|
+
return {
|
|
9
|
+
get: (key) => map.get(key),
|
|
10
|
+
set: (key, value) => {
|
|
11
|
+
map.set(key, value);
|
|
12
|
+
return true;
|
|
13
|
+
},
|
|
14
|
+
del: (key) => map.delete(key),
|
|
15
|
+
keys: () => Array.from(map.keys()),
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const buildCacheKey = (groupJid, sessionId) => `${sessionId}:${groupJid}`;
|
|
20
|
+
|
|
21
|
+
const normalizeSessionId = (value) => String(value || '').trim() || 'default';
|
|
22
|
+
const isGroupJid = (jid) => String(jid || '').endsWith('@g.us');
|
|
23
|
+
|
|
24
|
+
test('socketController multi-session: fencing token por assignment_version invalida writer stale', async () => {
|
|
25
|
+
let ownerState = {
|
|
26
|
+
ownerSessionId: 'session-a',
|
|
27
|
+
assignmentVersion: 1,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const resolver = createGroupOwnerWriteStateResolver({
|
|
31
|
+
buildCacheKeyImpl: buildCacheKey,
|
|
32
|
+
getOwnerImpl: async () => ownerState,
|
|
33
|
+
tryAcquireImpl: async () => ({ acquired: false, reason: 'claim_disabled' }),
|
|
34
|
+
cacheImpl: createCache(),
|
|
35
|
+
isGroupJidImpl: isGroupJid,
|
|
36
|
+
normalizeSessionIdImpl: normalizeSessionId,
|
|
37
|
+
loggerImpl: { warn: () => {} },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const first = await resolver('120363555555555555@g.us', 'session-a', {
|
|
41
|
+
allowClaim: false,
|
|
42
|
+
source: 'test_first',
|
|
43
|
+
});
|
|
44
|
+
assert.equal(first.allowed, true);
|
|
45
|
+
assert.equal(first.assignmentVersion, 1);
|
|
46
|
+
|
|
47
|
+
ownerState = {
|
|
48
|
+
ownerSessionId: 'session-a',
|
|
49
|
+
assignmentVersion: 2,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const stale = await resolver('120363555555555555@g.us', 'session-a', {
|
|
53
|
+
allowClaim: false,
|
|
54
|
+
source: 'test_stale',
|
|
55
|
+
expectedAssignmentVersion: 1,
|
|
56
|
+
enforceFence: true,
|
|
57
|
+
});
|
|
58
|
+
assert.equal(stale.allowed, false);
|
|
59
|
+
assert.equal(stale.reason, 'fence_token_mismatch');
|
|
60
|
+
assert.equal(stale.assignmentVersion, 2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('socketController multi-session: sessão antiga perde escrita após failover de owner', async () => {
|
|
64
|
+
let ownerState = {
|
|
65
|
+
ownerSessionId: 'session-a',
|
|
66
|
+
assignmentVersion: 7,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const resolver = createGroupOwnerWriteStateResolver({
|
|
70
|
+
buildCacheKeyImpl: buildCacheKey,
|
|
71
|
+
getOwnerImpl: async () => ownerState,
|
|
72
|
+
tryAcquireImpl: async () => ({ acquired: false, reason: 'claim_disabled' }),
|
|
73
|
+
cacheImpl: createCache(),
|
|
74
|
+
isGroupJidImpl: isGroupJid,
|
|
75
|
+
normalizeSessionIdImpl: normalizeSessionId,
|
|
76
|
+
loggerImpl: { warn: () => {} },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const beforeFailover = await resolver('120363666666666666@g.us', 'session-a', {
|
|
80
|
+
allowClaim: false,
|
|
81
|
+
source: 'before_failover',
|
|
82
|
+
});
|
|
83
|
+
assert.equal(beforeFailover.allowed, true);
|
|
84
|
+
assert.equal(beforeFailover.assignmentVersion, 7);
|
|
85
|
+
|
|
86
|
+
ownerState = {
|
|
87
|
+
ownerSessionId: 'session-b',
|
|
88
|
+
assignmentVersion: 8,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const staleOwner = await resolver('120363666666666666@g.us', 'session-a', {
|
|
92
|
+
allowClaim: false,
|
|
93
|
+
source: 'after_failover_old_owner',
|
|
94
|
+
expectedAssignmentVersion: 7,
|
|
95
|
+
enforceFence: true,
|
|
96
|
+
});
|
|
97
|
+
assert.equal(staleOwner.allowed, false);
|
|
98
|
+
assert.equal(staleOwner.reason, 'owned_by_other');
|
|
99
|
+
|
|
100
|
+
const newOwner = await resolver('120363666666666666@g.us', 'session-b', {
|
|
101
|
+
allowClaim: false,
|
|
102
|
+
source: 'after_failover_new_owner',
|
|
103
|
+
expectedAssignmentVersion: 8,
|
|
104
|
+
enforceFence: true,
|
|
105
|
+
});
|
|
106
|
+
assert.equal(newOwner.allowed, true);
|
|
107
|
+
assert.equal(newOwner.assignmentVersion, 8);
|
|
108
|
+
});
|
|
@@ -4,4 +4,4 @@ import { handleMessagesThroughPipeline } from './messageProcessingPipeline.js';
|
|
|
4
4
|
* Facade do controller de mensagens.
|
|
5
5
|
* Mantem assinatura/compatibilidade enquanto delega ao pipeline modular.
|
|
6
6
|
*/
|
|
7
|
-
export const handleMessages = async (update, sock) => handleMessagesThroughPipeline(update, sock);
|
|
7
|
+
export const handleMessages = async (update, sock, options = {}) => handleMessagesThroughPipeline(update, sock, options);
|
|
@@ -42,17 +42,19 @@ export const createCommandMiddleware = ({ isAdminCommand, isKnownNonAdminCommand
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
if (COMMAND_REACT_EMOJI) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
if (COMMAND_REACT_EMOJI?.trim() && ctx?.key) {
|
|
46
|
+
sendAndStore(ctx.sock, ctx.remoteJid, {
|
|
47
|
+
react: {
|
|
48
|
+
text: COMMAND_REACT_EMOJI,
|
|
49
|
+
key: ctx.key,
|
|
50
|
+
},
|
|
51
|
+
}).catch((error) => {
|
|
52
|
+
logger.warn('Falha ao enviar reação de comando', {
|
|
53
|
+
error: error?.message,
|
|
54
|
+
jid: ctx.remoteJid,
|
|
55
|
+
messageId: ctx.key?.id,
|
|
52
56
|
});
|
|
53
|
-
}
|
|
54
|
-
logger.warn('Falha ao enviar reação de comando:', error?.message);
|
|
55
|
-
}
|
|
57
|
+
});
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
const execution = await executeMessageCommandRoute({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const createConversationMiddleware = ({ logger, resolveSenderAdminForContext, resolveSenderOwnerForContext, resolveHasGoogleLoginForContext, isUserAdmin, isAdminSenderAsync, resolveCanonicalSenderJidForContext, isWhatsAppUserLinkedToGoogleWebAccount, WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN, ensureUserHasGoogleWebLoginForCommand, executeMessageCommandRoute, isAdminCommand, runCommand, sendReply, routeConversationMessage, stopMessagePipeline }) => {
|
|
1
|
+
export const createConversationMiddleware = ({ logger, resolveSenderAdminForContext, resolveSenderOwnerForContext, resolveHasGoogleLoginForContext, isUserAdmin, isAdminSenderAsync, resolveCanonicalSenderJidForContext, isWhatsAppUserLinkedToGoogleWebAccount, WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN, ensureUserHasGoogleWebLoginForCommand, executeMessageCommandRoute, isAdminCommand, runCommand, sendReply, routeConversationMessage, stopMessagePipeline, conversationAutoReplyEnabled = true }) => {
|
|
2
2
|
const resolveToolSecurityContextForConversation = async (ctx) => {
|
|
3
3
|
if (ctx.memo.toolSecurityContext) return ctx.memo.toolSecurityContext;
|
|
4
4
|
|
|
@@ -133,6 +133,7 @@ export const createConversationMiddleware = ({ logger, resolveSenderAdminForCont
|
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
return async (ctx) => {
|
|
136
|
+
if (!conversationAutoReplyEnabled) return null;
|
|
136
137
|
if (ctx.isCommandMessage || ctx.isMessageFromBot || !ctx.isNotifyUpsert) return null;
|
|
137
138
|
|
|
138
139
|
try {
|
|
@@ -26,6 +26,8 @@ const createBaseContext = (overrides = {}) => ({
|
|
|
26
26
|
mediaEntries: [],
|
|
27
27
|
upsertType: 'notify',
|
|
28
28
|
isNotifyUpsert: true,
|
|
29
|
+
sessionId: 'session-default',
|
|
30
|
+
ownerSessionId: null,
|
|
29
31
|
isCommandMessage: false,
|
|
30
32
|
hasCommandPrefix: false,
|
|
31
33
|
analysisPayload: {
|
|
@@ -166,6 +168,108 @@ test('pre-processing trata trigger de iniciar login', async () => {
|
|
|
166
168
|
assert.equal(stopSpy.calls[0].metadataPatch.flow, 'whatsapp_google_login');
|
|
167
169
|
});
|
|
168
170
|
|
|
171
|
+
test('pre-processing owner gate bloqueia sessao nao-owner em modo enforce', async () => {
|
|
172
|
+
const stopSpy = createStopSpy();
|
|
173
|
+
|
|
174
|
+
const middlewares = createPreProcessingMiddlewares({
|
|
175
|
+
executeQuery: async () => [],
|
|
176
|
+
TABLES: { RPG_PLAYER: 'rpg_player' },
|
|
177
|
+
isStatusJid: () => false,
|
|
178
|
+
stopMessagePipeline: stopSpy.stopMessagePipeline,
|
|
179
|
+
handleAntiLink: async () => false,
|
|
180
|
+
ensureCommandPrefixForContext: async () => '/',
|
|
181
|
+
resolveCaptchaByMessage: async () => {},
|
|
182
|
+
maybeHandleStartLoginMessage: async () => false,
|
|
183
|
+
mergeAnalysisMetadata: (analysisPayload, patch) => {
|
|
184
|
+
analysisPayload.metadata = {
|
|
185
|
+
...(analysisPayload.metadata || {}),
|
|
186
|
+
...(patch || {}),
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
ensureGroupConfigForContext: async () => ({}),
|
|
190
|
+
resolveStickerFocusState: () => ({ enabled: false }),
|
|
191
|
+
resolveStickerFocusMessageClassification: () => ({ isThrottleCandidate: false }),
|
|
192
|
+
resolveGroupOwnerForContext: async (ctx) => {
|
|
193
|
+
ctx.ownerSessionId = 'session-owner';
|
|
194
|
+
return { ownerSessionId: 'session-owner' };
|
|
195
|
+
},
|
|
196
|
+
ownerEnforcementMode: 'enforce',
|
|
197
|
+
primarySessionId: 'session-primary',
|
|
198
|
+
isUserAdmin: async () => false,
|
|
199
|
+
canSendMessageInStickerFocus: () => ({ allowed: true, remainingMs: 0 }),
|
|
200
|
+
registerMessageUsageInStickerFocus: () => {},
|
|
201
|
+
shouldSendStickerFocusWarning: () => false,
|
|
202
|
+
sendReply: async () => {},
|
|
203
|
+
formatStickerFocusRuleLabel: () => '',
|
|
204
|
+
formatRemainingMinutesLabel: () => 1,
|
|
205
|
+
logger: { warn: () => {}, info: () => {} },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const ctx = createBaseContext({
|
|
209
|
+
sessionId: 'session-worker',
|
|
210
|
+
isGroupMessage: true,
|
|
211
|
+
remoteJid: '120363111111111111@g.us',
|
|
212
|
+
});
|
|
213
|
+
const result = await middlewares.enforceGroupOwnerMiddleware(ctx);
|
|
214
|
+
|
|
215
|
+
assert.deepEqual(result, { stop: true });
|
|
216
|
+
assert.equal(stopSpy.calls.length, 1);
|
|
217
|
+
assert.equal(stopSpy.calls[0].processingResult, 'blocked_group_owner_enforcement');
|
|
218
|
+
assert.equal(ctx.analysisPayload.metadata.owner_enforcement_result, 'blocked_non_owner');
|
|
219
|
+
assert.equal(ctx.analysisPayload.metadata.owner_session_id, 'session-owner');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('pre-processing owner gate apenas loga em modo shadow para sessao nao-owner', async () => {
|
|
223
|
+
const stopSpy = createStopSpy();
|
|
224
|
+
const infoLogs = [];
|
|
225
|
+
|
|
226
|
+
const middlewares = createPreProcessingMiddlewares({
|
|
227
|
+
executeQuery: async () => [],
|
|
228
|
+
TABLES: { RPG_PLAYER: 'rpg_player' },
|
|
229
|
+
isStatusJid: () => false,
|
|
230
|
+
stopMessagePipeline: stopSpy.stopMessagePipeline,
|
|
231
|
+
handleAntiLink: async () => false,
|
|
232
|
+
ensureCommandPrefixForContext: async () => '/',
|
|
233
|
+
resolveCaptchaByMessage: async () => {},
|
|
234
|
+
maybeHandleStartLoginMessage: async () => false,
|
|
235
|
+
mergeAnalysisMetadata: (analysisPayload, patch) => {
|
|
236
|
+
analysisPayload.metadata = {
|
|
237
|
+
...(analysisPayload.metadata || {}),
|
|
238
|
+
...(patch || {}),
|
|
239
|
+
};
|
|
240
|
+
},
|
|
241
|
+
ensureGroupConfigForContext: async () => ({}),
|
|
242
|
+
resolveStickerFocusState: () => ({ enabled: false }),
|
|
243
|
+
resolveStickerFocusMessageClassification: () => ({ isThrottleCandidate: false }),
|
|
244
|
+
resolveGroupOwnerForContext: async (ctx) => {
|
|
245
|
+
ctx.ownerSessionId = 'session-owner';
|
|
246
|
+
return { ownerSessionId: 'session-owner' };
|
|
247
|
+
},
|
|
248
|
+
ownerEnforcementMode: 'shadow',
|
|
249
|
+
primarySessionId: 'session-primary',
|
|
250
|
+
isUserAdmin: async () => false,
|
|
251
|
+
canSendMessageInStickerFocus: () => ({ allowed: true, remainingMs: 0 }),
|
|
252
|
+
registerMessageUsageInStickerFocus: () => {},
|
|
253
|
+
shouldSendStickerFocusWarning: () => false,
|
|
254
|
+
sendReply: async () => {},
|
|
255
|
+
formatStickerFocusRuleLabel: () => '',
|
|
256
|
+
formatRemainingMinutesLabel: () => 1,
|
|
257
|
+
logger: { warn: () => {}, info: (msg, payload) => infoLogs.push({ msg, payload }) },
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const ctx = createBaseContext({
|
|
261
|
+
sessionId: 'session-worker',
|
|
262
|
+
isGroupMessage: true,
|
|
263
|
+
});
|
|
264
|
+
const result = await middlewares.enforceGroupOwnerMiddleware(ctx);
|
|
265
|
+
|
|
266
|
+
assert.equal(result, null);
|
|
267
|
+
assert.equal(stopSpy.calls.length, 0);
|
|
268
|
+
assert.equal(ctx.pipelineStopped, false);
|
|
269
|
+
assert.equal(ctx.analysisPayload.metadata.owner_enforcement_result, 'shadow_non_owner');
|
|
270
|
+
assert.equal(infoLogs.length, 1);
|
|
271
|
+
});
|
|
272
|
+
|
|
169
273
|
test('conversation middleware responde e interrompe pipeline', async () => {
|
|
170
274
|
const stopSpy = createStopSpy();
|
|
171
275
|
const replies = [];
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJid, stopMessagePipeline, handleAntiLink, ensureCommandPrefixForContext, resolveCaptchaByMessage, maybeHandleStartLoginMessage, mergeAnalysisMetadata, ensureGroupConfigForContext, resolveStickerFocusState, resolveStickerFocusMessageClassification, resolveSenderAdminForContext, isUserAdmin, canSendMessageInStickerFocus, registerMessageUsageInStickerFocus, shouldSendStickerFocusWarning, sendReply, formatStickerFocusRuleLabel, formatRemainingMinutesLabel, logger }) => {
|
|
1
|
+
export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJid, stopMessagePipeline, handleAntiLink, ensureCommandPrefixForContext, resolveCaptchaByMessage, maybeHandleStartLoginMessage, mergeAnalysisMetadata, ensureGroupConfigForContext, resolveStickerFocusState, resolveStickerFocusMessageClassification, resolveGroupOwnerForContext, ownerEnforcementMode = 'off', primarySessionId = 'default', resolveSenderAdminForContext, isUserAdmin, canSendMessageInStickerFocus, registerMessageUsageInStickerFocus, shouldSendStickerFocusWarning, sendReply, formatStickerFocusRuleLabel, formatRemainingMinutesLabel, logger }) => {
|
|
2
|
+
const normalizedOwnerEnforcementMode = String(ownerEnforcementMode || 'off')
|
|
3
|
+
.trim()
|
|
4
|
+
.toLowerCase();
|
|
5
|
+
const effectiveOwnerEnforcementMode = normalizedOwnerEnforcementMode === 'enforce' || normalizedOwnerEnforcementMode === 'shadow' ? normalizedOwnerEnforcementMode : 'off';
|
|
6
|
+
|
|
2
7
|
const touchSenderLastSeenMiddleware = async (ctx) => {
|
|
3
8
|
if (!ctx.senderJid || isStatusJid(ctx.remoteJid)) return;
|
|
4
9
|
|
|
@@ -21,6 +26,78 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
|
|
|
21
26
|
});
|
|
22
27
|
};
|
|
23
28
|
|
|
29
|
+
const enforceGroupOwnerMiddleware = async (ctx) => {
|
|
30
|
+
if (!ctx.isGroupMessage) return null;
|
|
31
|
+
|
|
32
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
33
|
+
owner_enforcement_mode: effectiveOwnerEnforcementMode,
|
|
34
|
+
processing_session_id: ctx.sessionId,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (effectiveOwnerEnforcementMode === 'off') return null;
|
|
38
|
+
|
|
39
|
+
if (typeof resolveGroupOwnerForContext !== 'function') {
|
|
40
|
+
logger.warn('Middleware de owner enforcement sem resolver de owner configurado.', {
|
|
41
|
+
action: 'group_owner_enforcement_missing_resolver',
|
|
42
|
+
sessionId: ctx.sessionId,
|
|
43
|
+
groupId: ctx.remoteJid,
|
|
44
|
+
});
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ownerState = await resolveGroupOwnerForContext(ctx);
|
|
49
|
+
const ownerSessionId = String(ctx.ownerSessionId || ownerState?.ownerSessionId || '').trim() || null;
|
|
50
|
+
ctx.ownerSessionId = ownerSessionId;
|
|
51
|
+
|
|
52
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
53
|
+
owner_session_id: ownerSessionId,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!ownerSessionId) {
|
|
57
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
58
|
+
owner_enforcement_result: 'owner_not_found',
|
|
59
|
+
});
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const currentSessionId = String(ctx.sessionId || '').trim() || primarySessionId;
|
|
64
|
+
const isOwnerSession = ownerSessionId === currentSessionId;
|
|
65
|
+
if (isOwnerSession) {
|
|
66
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
67
|
+
owner_enforcement_result: 'owner_match',
|
|
68
|
+
});
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (effectiveOwnerEnforcementMode === 'shadow') {
|
|
73
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
74
|
+
owner_enforcement_result: 'shadow_non_owner',
|
|
75
|
+
});
|
|
76
|
+
logger.info('Owner enforcement (shadow): sessao nao-owner detectada no grupo.', {
|
|
77
|
+
action: 'group_owner_enforcement_shadow_non_owner',
|
|
78
|
+
groupId: ctx.remoteJid,
|
|
79
|
+
sessionId: currentSessionId,
|
|
80
|
+
ownerSessionId,
|
|
81
|
+
messageId: ctx.key?.id || null,
|
|
82
|
+
});
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
87
|
+
owner_enforcement_result: 'blocked_non_owner',
|
|
88
|
+
blocked_by: 'group_owner_enforcement',
|
|
89
|
+
});
|
|
90
|
+
logger.info('Owner enforcement: mensagem bloqueada em sessao nao-owner.', {
|
|
91
|
+
action: 'group_owner_enforcement_blocked_non_owner',
|
|
92
|
+
groupId: ctx.remoteJid,
|
|
93
|
+
sessionId: currentSessionId,
|
|
94
|
+
ownerSessionId,
|
|
95
|
+
messageId: ctx.key?.id || null,
|
|
96
|
+
isCommand: ctx.isCommandMessage,
|
|
97
|
+
});
|
|
98
|
+
return stopMessagePipeline(ctx, 'blocked_group_owner_enforcement');
|
|
99
|
+
};
|
|
100
|
+
|
|
24
101
|
const applyGroupPolicyMiddleware = async (ctx) => {
|
|
25
102
|
if (!ctx.isGroupMessage) return null;
|
|
26
103
|
|
|
@@ -129,7 +206,7 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
|
|
|
129
206
|
if (shouldSendStickerFocusWarning({ groupId: ctx.remoteJid, senderJid: ctx.senderJid })) {
|
|
130
207
|
try {
|
|
131
208
|
await sendReply(ctx.sock, ctx.remoteJid, ctx.messageInfo, ctx.expirationMessage, {
|
|
132
|
-
text: '🖼️ Este chat está
|
|
209
|
+
text: '🖼️ *Modo Sticker ativo!*\n\n' + 'Este chat está focado em *stickers automáticos*.\n' + '👉 Envie apenas *imagens* ou *vídeos* para gerar stickers,\n' + '👉 Ou compartilhe *stickers* normalmente.\n\n' + '⏳ *Texto e áudio estão temporariamente limitados*.\n' + `Janela atual: *${formatStickerFocusRuleLabel(stickerFocusState)}*\n` + `Tente novamente em ~${formatRemainingMinutesLabel(messageGate.remainingMs)}.\n\n` + `💡 Um admin pode liberar com: *${ctx.commandPrefix}chatwindow on*`,
|
|
133
210
|
});
|
|
134
211
|
} catch (error) {
|
|
135
212
|
logger.warn('Falha ao enviar aviso de sticker focus.', {
|
|
@@ -157,6 +234,7 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
|
|
|
157
234
|
return {
|
|
158
235
|
touchSenderLastSeenMiddleware,
|
|
159
236
|
ignoreUnprocessableMessageMiddleware,
|
|
237
|
+
enforceGroupOwnerMiddleware,
|
|
160
238
|
applyGroupPolicyMiddleware,
|
|
161
239
|
resolveCaptchaMiddleware,
|
|
162
240
|
handleStartLoginTriggerMiddleware,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import 'dotenv/config';
|
|
2
3
|
|
|
3
4
|
import { isAdminCommand } from '../modules/adminModule/groupCommandHandlers.js';
|
|
4
5
|
import { explicarComandoGlobal, registerGlobalHelpCommandExecution } from '../services/ai/globalModuleAiHelpService.js';
|
|
5
6
|
import { extractSupportedStickerMediaDetails, processSticker } from '../modules/stickerModule/stickerCommand.js';
|
|
6
|
-
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';
|
|
7
8
|
import { isUserAdmin } from '../config/index.js';
|
|
8
9
|
import { isAdminSenderAsync } from '../config/index.js';
|
|
9
10
|
import { executeQuery, TABLES } from '../../database/index.js';
|
|
@@ -19,6 +20,7 @@ import { createMessageAnalysisEvent } from '../modules/analyticsModule/messageAn
|
|
|
19
20
|
import { routeConversationMessage } from '../services/ai/conversationRouterService.js';
|
|
20
21
|
import { executeMessageCommandRoute, isKnownNonAdminCommand } from '../services/ai/messageCommandExecutionService.js';
|
|
21
22
|
import { canSendMessageInStickerFocus, registerMessageUsageInStickerFocus, resolveStickerFocusMessageClassification, resolveStickerFocusState, shouldSendStickerFocusWarning } from '../services/sticker/stickerFocusService.js';
|
|
23
|
+
import { getOwner as getGroupOwner } from '../services/multiSession/groupOwnershipService.js';
|
|
22
24
|
import { createPreProcessingMiddlewares } from './messagePipeline/preProcessingMiddlewares.js';
|
|
23
25
|
import { createConversationMiddleware } from './messagePipeline/conversationMiddleware.js';
|
|
24
26
|
import { createCommandMiddleware } from './messagePipeline/commandMiddleware.js';
|
|
@@ -40,6 +42,7 @@ const MESSAGE_REPLY_PRESENCE_BEFORE = normalizeWAPresence(process.env.MESSAGE_RE
|
|
|
40
42
|
const MESSAGE_REPLY_PRESENCE_AFTER = normalizeWAPresence(process.env.MESSAGE_REPLY_PRESENCE_AFTER, 'paused');
|
|
41
43
|
const MESSAGE_REPLY_PRESENCE_DELAY_MS = parseEnvInt(process.env.MESSAGE_REPLY_PRESENCE_DELAY_MS, 280, 0, 3_000);
|
|
42
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);
|
|
43
46
|
const WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN = parseEnvBool(process.env.WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN, true);
|
|
44
47
|
const SITE_ORIGIN =
|
|
45
48
|
String(process.env.SITE_ORIGIN || process.env.WHATSAPP_LOGIN_BASE_URL || 'https://omnizap.shop')
|
|
@@ -47,11 +50,24 @@ const SITE_ORIGIN =
|
|
|
47
50
|
.replace(/\/+$/, '') || 'https://omnizap.shop';
|
|
48
51
|
const SITE_LOGIN_URL = `${SITE_ORIGIN}/login/`;
|
|
49
52
|
const SITE_GROUP_LOGIN_URL = `${SITE_ORIGIN}/login`;
|
|
53
|
+
const MULTI_SESSION_RUNTIME_CONFIG = getMultiSessionRuntimeConfig();
|
|
54
|
+
const PRIMARY_SESSION_ID = String(MULTI_SESSION_RUNTIME_CONFIG?.primarySessionId || 'default').trim() || 'default';
|
|
55
|
+
const VALID_SESSION_IDS = new Set(Array.isArray(MULTI_SESSION_RUNTIME_CONFIG?.sessionIds) ? MULTI_SESSION_RUNTIME_CONFIG.sessionIds : [PRIMARY_SESSION_ID]);
|
|
56
|
+
const GROUP_OWNER_ENFORCEMENT_MODE = String(MULTI_SESSION_RUNTIME_CONFIG?.ownerEnforcementMode || 'off')
|
|
57
|
+
.trim()
|
|
58
|
+
.toLowerCase();
|
|
59
|
+
|
|
60
|
+
const normalizeSessionId = (sessionId) => {
|
|
61
|
+
const normalized = String(sessionId || '').trim();
|
|
62
|
+
if (!normalized) return PRIMARY_SESSION_ID;
|
|
63
|
+
if (VALID_SESSION_IDS.has(normalized)) return normalized;
|
|
64
|
+
return PRIMARY_SESSION_ID;
|
|
65
|
+
};
|
|
50
66
|
|
|
51
67
|
let messageAnalyticsTableMissingLogged = false;
|
|
52
68
|
const recentCommandExecutions = new Map();
|
|
53
69
|
|
|
54
|
-
const pruneRecentCommandExecutions = (nowMs =
|
|
70
|
+
const pruneRecentCommandExecutions = (nowMs = __timeNowMs()) => {
|
|
55
71
|
for (const [cacheKey, expiresAt] of recentCommandExecutions.entries()) {
|
|
56
72
|
if (expiresAt <= nowMs) {
|
|
57
73
|
recentCommandExecutions.delete(cacheKey);
|
|
@@ -70,7 +86,7 @@ const isDuplicateCommandExecution = (chatId, messageId) => {
|
|
|
70
86
|
const cacheKey = buildCommandExecutionCacheKey(chatId, messageId);
|
|
71
87
|
if (!cacheKey) return false;
|
|
72
88
|
|
|
73
|
-
const nowMs =
|
|
89
|
+
const nowMs = __timeNowMs();
|
|
74
90
|
const expiresAt = recentCommandExecutions.get(cacheKey) || 0;
|
|
75
91
|
if (expiresAt <= nowMs) {
|
|
76
92
|
recentCommandExecutions.delete(cacheKey);
|
|
@@ -83,8 +99,8 @@ const isDuplicateCommandExecution = (chatId, messageId) => {
|
|
|
83
99
|
const markCommandExecution = (chatId, messageId) => {
|
|
84
100
|
const cacheKey = buildCommandExecutionCacheKey(chatId, messageId);
|
|
85
101
|
if (!cacheKey) return;
|
|
86
|
-
pruneRecentCommandExecutions(
|
|
87
|
-
recentCommandExecutions.set(cacheKey,
|
|
102
|
+
pruneRecentCommandExecutions(__timeNowMs());
|
|
103
|
+
recentCommandExecutions.set(cacheKey, __timeNowMs() + MESSAGE_COMMAND_DEDUPE_TTL_MS);
|
|
88
104
|
};
|
|
89
105
|
|
|
90
106
|
const normalizeTriggerText = (value) =>
|
|
@@ -169,7 +185,7 @@ const maybeHandleStartLoginMessage = async ({ sock, messageInfo, extractedText,
|
|
|
169
185
|
|
|
170
186
|
if (isGroupMessage) {
|
|
171
187
|
await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
|
|
172
|
-
text: '
|
|
188
|
+
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.',
|
|
173
189
|
});
|
|
174
190
|
return true;
|
|
175
191
|
}
|
|
@@ -198,7 +214,7 @@ const maybeHandleStartLoginMessage = async ({ sock, messageInfo, extractedText,
|
|
|
198
214
|
const safeName = String(senderName || '').trim();
|
|
199
215
|
const greeting = safeName ? `Oi, *${safeName}*!` : 'Oi!';
|
|
200
216
|
await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
|
|
201
|
-
text: `${greeting}\n\n` + '
|
|
217
|
+
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.',
|
|
202
218
|
});
|
|
203
219
|
|
|
204
220
|
return true;
|
|
@@ -373,7 +389,7 @@ const ensureUserHasGoogleWebLoginForCommand = async ({ sock, messageInfo, sender
|
|
|
373
389
|
}
|
|
374
390
|
|
|
375
391
|
const loginUrl = isGroupMessage ? SITE_GROUP_LOGIN_URL : buildSiteLoginUrlForUser(resolvedCanonicalUserId || senderJid);
|
|
376
|
-
const loginMessage = isGroupMessage ?
|
|
392
|
+
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*).`;
|
|
377
393
|
|
|
378
394
|
await sendReply(sock, remoteJid, messageInfo, expirationMessage, {
|
|
379
395
|
text: loginMessage,
|
|
@@ -458,6 +474,43 @@ const resolveSenderOwnerForContext = async (ctx) => {
|
|
|
458
474
|
return ctx.memo.senderOwnerPromise;
|
|
459
475
|
};
|
|
460
476
|
|
|
477
|
+
const resolveGroupOwnerForContext = async (ctx, { bypassCache = false } = {}) => {
|
|
478
|
+
if (!ctx.isGroupMessage) return null;
|
|
479
|
+
|
|
480
|
+
if (!bypassCache && ctx.memo.groupOwnerPromise) {
|
|
481
|
+
return ctx.memo.groupOwnerPromise;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const fetchOwnerPromise = (async () => {
|
|
485
|
+
try {
|
|
486
|
+
const ownerState = await getGroupOwner(ctx.remoteJid, { bypassCache });
|
|
487
|
+
const ownerSessionId = String(ownerState?.ownerSessionId || '').trim() || null;
|
|
488
|
+
ctx.ownerSessionId = ownerSessionId;
|
|
489
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
490
|
+
owner_session_id: ownerSessionId,
|
|
491
|
+
});
|
|
492
|
+
return ownerState;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
logger.warn('Falha ao resolver owner de grupo para roteamento de sessão.', {
|
|
495
|
+
action: 'group_owner_resolution_failed',
|
|
496
|
+
sessionId: ctx.sessionId,
|
|
497
|
+
groupId: ctx.remoteJid,
|
|
498
|
+
error: error?.message,
|
|
499
|
+
});
|
|
500
|
+
ctx.ownerSessionId = null;
|
|
501
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
502
|
+
owner_resolution_error: String(error?.code || error?.name || 'lookup_failed')
|
|
503
|
+
.trim()
|
|
504
|
+
.toLowerCase(),
|
|
505
|
+
});
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
})();
|
|
509
|
+
|
|
510
|
+
ctx.memo.groupOwnerPromise = fetchOwnerPromise;
|
|
511
|
+
return fetchOwnerPromise;
|
|
512
|
+
};
|
|
513
|
+
|
|
461
514
|
const resolveHasGoogleLoginForContext = async (ctx) => {
|
|
462
515
|
if (ctx.isMessageFromBot || !WHATSAPP_COMMAND_REQUIRES_GOOGLE_LOGIN) {
|
|
463
516
|
return undefined;
|
|
@@ -475,10 +528,11 @@ const resolveHasGoogleLoginForContext = async (ctx) => {
|
|
|
475
528
|
return ctx.memo.hasGoogleLoginPromise;
|
|
476
529
|
};
|
|
477
530
|
|
|
478
|
-
const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyUpsert, sock }) => {
|
|
531
|
+
const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyUpsert, sock, sessionId }) => {
|
|
479
532
|
const key = messageInfo?.key || {};
|
|
480
533
|
const remoteJid = key?.remoteJid;
|
|
481
534
|
if (!remoteJid) return null;
|
|
535
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
482
536
|
|
|
483
537
|
const isGroupMessage = isGroupJid(remoteJid);
|
|
484
538
|
const extractedText = extractMessageContent(messageInfo);
|
|
@@ -504,6 +558,7 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
504
558
|
.slice(0, 10);
|
|
505
559
|
|
|
506
560
|
const analysisPayload = {
|
|
561
|
+
sessionId: normalizedSessionId,
|
|
507
562
|
messageId: key?.id || null,
|
|
508
563
|
chatId: remoteJid || null,
|
|
509
564
|
senderId: senderJid || null,
|
|
@@ -529,6 +584,8 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
529
584
|
upsert_type: upsertType,
|
|
530
585
|
is_notify_upsert: isNotifyUpsert,
|
|
531
586
|
is_history_append: upsertType === 'append',
|
|
587
|
+
session_id: normalizedSessionId,
|
|
588
|
+
owner_session_id: null,
|
|
532
589
|
addressing_mode: addressingMode || null,
|
|
533
590
|
participant_alt: key?.participantAlt || null,
|
|
534
591
|
remote_jid_alt: key?.remoteJidAlt || null,
|
|
@@ -549,6 +606,8 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
549
606
|
botJidCandidates,
|
|
550
607
|
botJid,
|
|
551
608
|
isMessageFromBot,
|
|
609
|
+
sessionId: normalizedSessionId,
|
|
610
|
+
ownerSessionId: null,
|
|
552
611
|
commandPrefix: DEFAULT_COMMAND_PREFIX,
|
|
553
612
|
groupConfig: null,
|
|
554
613
|
groupConfigLoaded: false,
|
|
@@ -563,7 +622,7 @@ const createMessagePipelineContext = async ({ messageInfo, upsertType, isNotifyU
|
|
|
563
622
|
};
|
|
564
623
|
};
|
|
565
624
|
|
|
566
|
-
const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware } = createPreProcessingMiddlewares({
|
|
625
|
+
const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, enforceGroupOwnerMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware } = createPreProcessingMiddlewares({
|
|
567
626
|
executeQuery,
|
|
568
627
|
TABLES,
|
|
569
628
|
isStatusJid,
|
|
@@ -576,6 +635,9 @@ const { touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, app
|
|
|
576
635
|
ensureGroupConfigForContext,
|
|
577
636
|
resolveStickerFocusState,
|
|
578
637
|
resolveStickerFocusMessageClassification,
|
|
638
|
+
resolveGroupOwnerForContext,
|
|
639
|
+
ownerEnforcementMode: GROUP_OWNER_ENFORCEMENT_MODE,
|
|
640
|
+
primarySessionId: PRIMARY_SESSION_ID,
|
|
579
641
|
resolveSenderAdminForContext,
|
|
580
642
|
isUserAdmin,
|
|
581
643
|
canSendMessageInStickerFocus,
|
|
@@ -604,6 +666,7 @@ const routeConversationMiddleware = createConversationMiddleware({
|
|
|
604
666
|
sendReply,
|
|
605
667
|
routeConversationMessage,
|
|
606
668
|
stopMessagePipeline,
|
|
669
|
+
conversationAutoReplyEnabled: CONVERSATIONAL_AUTO_REPLY_ENABLED,
|
|
607
670
|
});
|
|
608
671
|
|
|
609
672
|
const executeCommandMiddleware = createCommandMiddleware({
|
|
@@ -641,7 +704,7 @@ const runPostProcessingMiddleware = createPostProcessingMiddleware({
|
|
|
641
704
|
normalizeAnalysisErrorCode,
|
|
642
705
|
});
|
|
643
706
|
|
|
644
|
-
const MESSAGE_PIPELINE_MIDDLEWARES = [touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware, routeConversationMiddleware, executeCommandMiddleware, runPostProcessingMiddleware];
|
|
707
|
+
const MESSAGE_PIPELINE_MIDDLEWARES = [touchSenderLastSeenMiddleware, ignoreUnprocessableMessageMiddleware, enforceGroupOwnerMiddleware, applyGroupPolicyMiddleware, resolveCaptchaMiddleware, handleStartLoginTriggerMiddleware, detectCommandIntentMiddleware, applyStickerFocusMiddleware, routeConversationMiddleware, executeCommandMiddleware, runPostProcessingMiddleware];
|
|
645
708
|
|
|
646
709
|
const runMessagePipeline = async (ctx) => {
|
|
647
710
|
for (const middleware of MESSAGE_PIPELINE_MIDDLEWARES) {
|
|
@@ -656,7 +719,8 @@ const runMessagePipeline = async (ctx) => {
|
|
|
656
719
|
*
|
|
657
720
|
* @param {Object} update - Objeto contendo a atualização do WhatsApp.
|
|
658
721
|
*/
|
|
659
|
-
export const handleMessagesThroughPipeline = async (update, sock) => {
|
|
722
|
+
export const handleMessagesThroughPipeline = async (update, sock, options = {}) => {
|
|
723
|
+
const sessionId = normalizeSessionId(options?.sessionId);
|
|
660
724
|
if (update.messages && Array.isArray(update.messages)) {
|
|
661
725
|
try {
|
|
662
726
|
const upsertType = update?.type || null;
|
|
@@ -668,6 +732,7 @@ export const handleMessagesThroughPipeline = async (update, sock) => {
|
|
|
668
732
|
upsertType,
|
|
669
733
|
isNotifyUpsert,
|
|
670
734
|
sock,
|
|
735
|
+
sessionId,
|
|
671
736
|
});
|
|
672
737
|
if (!context) continue;
|
|
673
738
|
|
|
@@ -677,19 +742,34 @@ export const handleMessagesThroughPipeline = async (update, sock) => {
|
|
|
677
742
|
context.analysisPayload.processingResult = 'error';
|
|
678
743
|
context.analysisPayload.errorCode = normalizeAnalysisErrorCode(messageError);
|
|
679
744
|
logger.error('Erro ao processar mensagem individual:', {
|
|
745
|
+
sessionId,
|
|
746
|
+
ownerSessionId: context.ownerSessionId || null,
|
|
680
747
|
error: messageError?.message,
|
|
681
748
|
messageId: context.key?.id || null,
|
|
682
749
|
remoteJid: context.remoteJid,
|
|
683
750
|
});
|
|
684
751
|
} finally {
|
|
752
|
+
logger.debug('Mensagem processada pelo pipeline.', {
|
|
753
|
+
action: 'message_pipeline_processed',
|
|
754
|
+
sessionId: context.sessionId,
|
|
755
|
+
ownerSessionId: context.ownerSessionId || null,
|
|
756
|
+
messageId: context.key?.id || null,
|
|
757
|
+
remoteJid: context.remoteJid,
|
|
758
|
+
result: context.analysisPayload.processingResult,
|
|
759
|
+
isCommand: context.analysisPayload.isCommand,
|
|
760
|
+
});
|
|
685
761
|
persistMessageAnalysisEvent(context.analysisPayload);
|
|
686
762
|
}
|
|
687
763
|
}
|
|
688
764
|
} catch (error) {
|
|
689
|
-
logger.error('Erro ao processar mensagens:',
|
|
765
|
+
logger.error('Erro ao processar mensagens:', {
|
|
766
|
+
sessionId,
|
|
767
|
+
error: error?.message,
|
|
768
|
+
});
|
|
690
769
|
}
|
|
691
770
|
} else {
|
|
692
771
|
logger.info('🔄 Processando evento recebido:', {
|
|
772
|
+
sessionId,
|
|
693
773
|
eventType: update?.type || 'unknown',
|
|
694
774
|
eventData: update,
|
|
695
775
|
});
|