@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { createGroupOwnerWriteStateResolver } from './groupOwnerWriteStateResolver.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cria um cache em memória simples para testes.
|
|
8
|
+
* @returns {{get: (key: string) => any, set: (key: string, value: any) => boolean, del: (key: string) => boolean, keys: () => string[]}}
|
|
9
|
+
*/
|
|
10
|
+
const createCache = () => {
|
|
11
|
+
const map = new Map();
|
|
12
|
+
return {
|
|
13
|
+
get: (key) => map.get(key),
|
|
14
|
+
set: (key, value) => {
|
|
15
|
+
map.set(key, value);
|
|
16
|
+
return true;
|
|
17
|
+
},
|
|
18
|
+
del: (key) => map.delete(key),
|
|
19
|
+
keys: () => Array.from(map.keys()),
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Monta a chave de cache por sessão e grupo.
|
|
25
|
+
* @param {string} groupJid
|
|
26
|
+
* @param {string} sessionId
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
const buildCacheKey = (groupJid, sessionId) => `${sessionId}:${groupJid}`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Normaliza o ID da sessão para os cenários de teste.
|
|
33
|
+
* @param {string | null | undefined} value
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
const normalizeSessionId = (value) => String(value || '').trim() || 'default';
|
|
37
|
+
/**
|
|
38
|
+
* Verifica se o JID pertence a grupo.
|
|
39
|
+
* @param {string | null | undefined} jid
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
const isGroupJid = (jid) => String(jid || '').endsWith('@g.us');
|
|
43
|
+
|
|
44
|
+
test('socketController multi-session: fencing token por assignment_version invalida writer stale', async () => {
|
|
45
|
+
let ownerState = {
|
|
46
|
+
ownerSessionId: 'session-a',
|
|
47
|
+
assignmentVersion: 1,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const resolver = createGroupOwnerWriteStateResolver({
|
|
51
|
+
buildCacheKeyImpl: buildCacheKey,
|
|
52
|
+
getOwnerImpl: async () => ownerState,
|
|
53
|
+
tryAcquireImpl: async () => ({ acquired: false, reason: 'claim_disabled' }),
|
|
54
|
+
cacheImpl: createCache(),
|
|
55
|
+
isGroupJidImpl: isGroupJid,
|
|
56
|
+
normalizeSessionIdImpl: normalizeSessionId,
|
|
57
|
+
loggerImpl: { warn: () => {} },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const first = await resolver('120363555555555555@g.us', 'session-a', {
|
|
61
|
+
allowClaim: false,
|
|
62
|
+
source: 'test_first',
|
|
63
|
+
});
|
|
64
|
+
assert.equal(first.allowed, true);
|
|
65
|
+
assert.equal(first.assignmentVersion, 1);
|
|
66
|
+
|
|
67
|
+
ownerState = {
|
|
68
|
+
ownerSessionId: 'session-a',
|
|
69
|
+
assignmentVersion: 2,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const stale = await resolver('120363555555555555@g.us', 'session-a', {
|
|
73
|
+
allowClaim: false,
|
|
74
|
+
source: 'test_stale',
|
|
75
|
+
expectedAssignmentVersion: 1,
|
|
76
|
+
enforceFence: true,
|
|
77
|
+
});
|
|
78
|
+
assert.equal(stale.allowed, false);
|
|
79
|
+
assert.equal(stale.reason, 'fence_token_mismatch');
|
|
80
|
+
assert.equal(stale.assignmentVersion, 2);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('socketController multi-session: sessão antiga perde escrita após failover de owner', async () => {
|
|
84
|
+
let ownerState = {
|
|
85
|
+
ownerSessionId: 'session-a',
|
|
86
|
+
assignmentVersion: 7,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const resolver = createGroupOwnerWriteStateResolver({
|
|
90
|
+
buildCacheKeyImpl: buildCacheKey,
|
|
91
|
+
getOwnerImpl: async () => ownerState,
|
|
92
|
+
tryAcquireImpl: async () => ({ acquired: false, reason: 'claim_disabled' }),
|
|
93
|
+
cacheImpl: createCache(),
|
|
94
|
+
isGroupJidImpl: isGroupJid,
|
|
95
|
+
normalizeSessionIdImpl: normalizeSessionId,
|
|
96
|
+
loggerImpl: { warn: () => {} },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const beforeFailover = await resolver('120363666666666666@g.us', 'session-a', {
|
|
100
|
+
allowClaim: false,
|
|
101
|
+
source: 'before_failover',
|
|
102
|
+
});
|
|
103
|
+
assert.equal(beforeFailover.allowed, true);
|
|
104
|
+
assert.equal(beforeFailover.assignmentVersion, 7);
|
|
105
|
+
|
|
106
|
+
ownerState = {
|
|
107
|
+
ownerSessionId: 'session-b',
|
|
108
|
+
assignmentVersion: 8,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const staleOwner = await resolver('120363666666666666@g.us', 'session-a', {
|
|
112
|
+
allowClaim: false,
|
|
113
|
+
source: 'after_failover_old_owner',
|
|
114
|
+
expectedAssignmentVersion: 7,
|
|
115
|
+
enforceFence: true,
|
|
116
|
+
});
|
|
117
|
+
assert.equal(staleOwner.allowed, false);
|
|
118
|
+
assert.equal(staleOwner.reason, 'owned_by_other');
|
|
119
|
+
|
|
120
|
+
const newOwner = await resolver('120363666666666666@g.us', 'session-b', {
|
|
121
|
+
allowClaim: false,
|
|
122
|
+
source: 'after_failover_new_owner',
|
|
123
|
+
expectedAssignmentVersion: 8,
|
|
124
|
+
enforceFence: true,
|
|
125
|
+
});
|
|
126
|
+
assert.equal(newOwner.allowed, true);
|
|
127
|
+
assert.equal(newOwner.assignmentVersion, 8);
|
|
128
|
+
});
|
|
@@ -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', allowSelfCommandsOnAppend = true, 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
|
|
|
@@ -79,12 +156,26 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
|
|
|
79
156
|
|
|
80
157
|
const detectCommandIntentMiddleware = async (ctx) => {
|
|
81
158
|
ctx.hasCommandPrefix = ctx.extractedText.startsWith(ctx.commandPrefix);
|
|
82
|
-
|
|
159
|
+
const isSelfAppendCommand =
|
|
160
|
+
allowSelfCommandsOnAppend &&
|
|
161
|
+
ctx.hasCommandPrefix &&
|
|
162
|
+
!ctx.isNotifyUpsert &&
|
|
163
|
+
String(ctx.upsertType || '')
|
|
164
|
+
.trim()
|
|
165
|
+
.toLowerCase() === 'append' &&
|
|
166
|
+
ctx.isMessageFromBot;
|
|
167
|
+
ctx.isCommandMessage = ctx.hasCommandPrefix && (ctx.isNotifyUpsert || isSelfAppendCommand);
|
|
83
168
|
|
|
84
169
|
ctx.analysisPayload.isCommand = ctx.isCommandMessage;
|
|
85
170
|
ctx.analysisPayload.commandPrefix = ctx.commandPrefix;
|
|
86
171
|
|
|
87
|
-
if (
|
|
172
|
+
if (isSelfAppendCommand) {
|
|
173
|
+
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
174
|
+
command_detected_via: 'append_from_me',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (ctx.hasCommandPrefix && !ctx.isCommandMessage) {
|
|
88
179
|
mergeAnalysisMetadata(ctx.analysisPayload, {
|
|
89
180
|
command_suppressed_reason: 'non_notify_upsert',
|
|
90
181
|
});
|
|
@@ -129,7 +220,7 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
|
|
|
129
220
|
if (shouldSendStickerFocusWarning({ groupId: ctx.remoteJid, senderJid: ctx.senderJid })) {
|
|
130
221
|
try {
|
|
131
222
|
await sendReply(ctx.sock, ctx.remoteJid, ctx.messageInfo, ctx.expirationMessage, {
|
|
132
|
-
text: '🖼️ Este chat está
|
|
223
|
+
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
224
|
});
|
|
134
225
|
} catch (error) {
|
|
135
226
|
logger.warn('Falha ao enviar aviso de sticker focus.', {
|
|
@@ -157,6 +248,7 @@ export const createPreProcessingMiddlewares = ({ executeQuery, TABLES, isStatusJ
|
|
|
157
248
|
return {
|
|
158
249
|
touchSenderLastSeenMiddleware,
|
|
159
250
|
ignoreUnprocessableMessageMiddleware,
|
|
251
|
+
enforceGroupOwnerMiddleware,
|
|
160
252
|
applyGroupPolicyMiddleware,
|
|
161
253
|
resolveCaptchaMiddleware,
|
|
162
254
|
handleStartLoginTriggerMiddleware,
|