@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
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { baileysConnectionLogger as logger } from './loggerConfig.js';
|
|
2
3
|
import { queueMessageInsert } from '../services/infra/dbWriteQueue.js';
|
|
3
4
|
import { parseEnvBool, parseEnvInt, normalizeJid, isGroupJid, isStatusJid, isBroadcastJid, isNewsletterJid, normalizeWAPresence } from './baileysConfig.js';
|
|
5
|
+
import { getOwner as getGroupOwner, tryAcquire as tryAcquireGroupOwner } from '../services/multiSession/groupOwnershipService.js';
|
|
4
6
|
|
|
5
7
|
const BAILEYS_SEND_RETRY_ATTEMPTS = parseEnvInt(process.env.BAILEYS_SEND_RETRY_ATTEMPTS, 2, 1, 5);
|
|
6
8
|
const BAILEYS_SEND_RETRY_BASE_DELAY_MS = parseEnvInt(process.env.BAILEYS_SEND_RETRY_BASE_DELAY_MS, 600, 100, 10_000);
|
|
@@ -10,11 +12,102 @@ const BAILEYS_REPLY_PRESENCE_SUBSCRIBE = parseEnvBool(process.env.BAILEYS_REPLY_
|
|
|
10
12
|
const BAILEYS_REPLY_PRESENCE_DELAY_MS = parseEnvInt(process.env.BAILEYS_REPLY_PRESENCE_DELAY_MS, 280, 0, 3_000);
|
|
11
13
|
const BAILEYS_REPLY_PRESENCE_BEFORE = normalizeWAPresence(process.env.BAILEYS_REPLY_PRESENCE_BEFORE, 'composing');
|
|
12
14
|
const BAILEYS_REPLY_PRESENCE_AFTER = normalizeWAPresence(process.env.BAILEYS_REPLY_PRESENCE_AFTER, 'paused');
|
|
15
|
+
const GROUP_WRITE_PERMISSION_CACHE_TTL_MS = parseEnvInt(process.env.GROUP_OWNER_WRITE_CACHE_TTL_MS, 8_000, 1_000, 60_000);
|
|
13
16
|
|
|
14
17
|
const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]';
|
|
15
18
|
|
|
16
19
|
const ANY_MESSAGE_CONTENT_PRIMARY_KEYS = new Set(['text', 'image', 'video', 'audio', 'sticker', 'stickerPack', 'stickerPackMessage', 'document', 'event', 'poll', 'contacts', 'location', 'react', 'buttonReply', 'groupInvite', 'listReply', 'pin', 'product', 'sharePhoneNumber', 'requestPhoneNumber', 'forward', 'delete', 'disappearingMessagesInChat', 'limitSharing']);
|
|
17
20
|
const PRESENCE_NON_REPLY_CONTENT_KEYS = new Set(['react', 'delete', 'pin', 'disappearingMessagesInChat']);
|
|
21
|
+
const groupWritePermissionCache = new Map();
|
|
22
|
+
|
|
23
|
+
const normalizeSessionId = (value) => {
|
|
24
|
+
const normalized = String(value || '').trim();
|
|
25
|
+
return normalized || null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getCachedGroupWritePermission = (groupJid, sessionId) => {
|
|
29
|
+
const key = `${sessionId}:${groupJid}`;
|
|
30
|
+
const cached = groupWritePermissionCache.get(key);
|
|
31
|
+
if (!cached) return null;
|
|
32
|
+
if (cached.expiresAtMs <= __timeNowMs()) {
|
|
33
|
+
groupWritePermissionCache.delete(key);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return cached;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const setCachedGroupWritePermission = (groupJid, sessionId, allowed, ownerSessionId = null) => {
|
|
40
|
+
const key = `${sessionId}:${groupJid}`;
|
|
41
|
+
groupWritePermissionCache.set(key, {
|
|
42
|
+
allowed: Boolean(allowed),
|
|
43
|
+
ownerSessionId: normalizeSessionId(ownerSessionId),
|
|
44
|
+
expiresAtMs: __timeNowMs() + GROUP_WRITE_PERMISSION_CACHE_TTL_MS,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const resolveGroupWritePermission = async (groupJid, sessionId) => {
|
|
49
|
+
if (!isGroupJid(groupJid) || !sessionId) {
|
|
50
|
+
return {
|
|
51
|
+
allowed: true,
|
|
52
|
+
ownerSessionId: null,
|
|
53
|
+
reason: 'not_group_or_missing_session',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const cached = getCachedGroupWritePermission(groupJid, sessionId);
|
|
58
|
+
if (cached) {
|
|
59
|
+
return {
|
|
60
|
+
allowed: cached.allowed,
|
|
61
|
+
ownerSessionId: cached.ownerSessionId,
|
|
62
|
+
reason: 'cache_hit',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const ownerState = await getGroupOwner(groupJid);
|
|
68
|
+
let ownerSessionId = normalizeSessionId(ownerState?.ownerSessionId);
|
|
69
|
+
let allowed = false;
|
|
70
|
+
let reason = 'owned_by_other';
|
|
71
|
+
|
|
72
|
+
if (!ownerSessionId) {
|
|
73
|
+
const claimOutcome = await tryAcquireGroupOwner({
|
|
74
|
+
groupJid,
|
|
75
|
+
sessionId,
|
|
76
|
+
reason: 'send_store_claim',
|
|
77
|
+
changedBy: sessionId,
|
|
78
|
+
metadata: {
|
|
79
|
+
source: 'message_persistence_service',
|
|
80
|
+
gate: 'group_write',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
ownerSessionId = normalizeSessionId(claimOutcome?.owner?.ownerSessionId);
|
|
84
|
+
allowed = Boolean(claimOutcome?.acquired && ownerSessionId === sessionId);
|
|
85
|
+
reason = claimOutcome?.reason || 'claim_attempt';
|
|
86
|
+
} else {
|
|
87
|
+
allowed = ownerSessionId === sessionId;
|
|
88
|
+
reason = allowed ? 'owner_match' : 'owned_by_other';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setCachedGroupWritePermission(groupJid, sessionId, allowed, ownerSessionId);
|
|
92
|
+
return {
|
|
93
|
+
allowed,
|
|
94
|
+
ownerSessionId,
|
|
95
|
+
reason,
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
logger.warn('Falha ao validar ownership para persistência de saída em grupo.', {
|
|
99
|
+
action: 'group_write_permission_resolution_failed',
|
|
100
|
+
groupJid,
|
|
101
|
+
sessionId,
|
|
102
|
+
error: error?.message,
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
allowed: false,
|
|
106
|
+
ownerSessionId: null,
|
|
107
|
+
reason: 'resolution_failed',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
};
|
|
18
111
|
|
|
19
112
|
/**
|
|
20
113
|
* Verifica se o payload se parece com AnyMessageContent do Baileys.
|
|
@@ -61,6 +154,8 @@ const normalizeSendOptions = (options) => {
|
|
|
61
154
|
* @param {unknown} options
|
|
62
155
|
* @returns {{
|
|
63
156
|
* sendOptions: import('@whiskeysockets/baileys').MiscMessageGenerationOptions|undefined,
|
|
157
|
+
* sessionId: string | null,
|
|
158
|
+
* allowGroupWrite: boolean | undefined,
|
|
64
159
|
* skipPresenceUpdate: boolean,
|
|
65
160
|
* presenceBefore: import('@whiskeysockets/baileys').WAPresence,
|
|
66
161
|
* presenceAfter: import('@whiskeysockets/baileys').WAPresence,
|
|
@@ -72,6 +167,8 @@ const resolveRuntimeSendOptions = (options) => {
|
|
|
72
167
|
if (!isPlainObject(options)) {
|
|
73
168
|
return {
|
|
74
169
|
sendOptions: undefined,
|
|
170
|
+
sessionId: null,
|
|
171
|
+
allowGroupWrite: undefined,
|
|
75
172
|
skipPresenceUpdate: false,
|
|
76
173
|
presenceBefore: BAILEYS_REPLY_PRESENCE_BEFORE,
|
|
77
174
|
presenceAfter: BAILEYS_REPLY_PRESENCE_AFTER,
|
|
@@ -80,10 +177,12 @@ const resolveRuntimeSendOptions = (options) => {
|
|
|
80
177
|
};
|
|
81
178
|
}
|
|
82
179
|
|
|
83
|
-
const { skipPresenceUpdate, presenceBefore, presenceAfter, presenceDelayMs, presenceSubscribe, ...sendOptions } = options;
|
|
180
|
+
const { skipPresenceUpdate, presenceBefore, presenceAfter, presenceDelayMs, presenceSubscribe, sessionId, allowGroupWrite, ...sendOptions } = options;
|
|
84
181
|
const normalizedDelay = parseEnvInt(presenceDelayMs, BAILEYS_REPLY_PRESENCE_DELAY_MS, 0, 3_000);
|
|
85
182
|
return {
|
|
86
183
|
sendOptions: Object.keys(sendOptions).length > 0 ? sendOptions : undefined,
|
|
184
|
+
sessionId: normalizeSessionId(sessionId),
|
|
185
|
+
allowGroupWrite: typeof allowGroupWrite === 'boolean' ? allowGroupWrite : undefined,
|
|
87
186
|
skipPresenceUpdate: Boolean(skipPresenceUpdate),
|
|
88
187
|
presenceBefore: normalizeWAPresence(presenceBefore, BAILEYS_REPLY_PRESENCE_BEFORE),
|
|
89
188
|
presenceAfter: normalizeWAPresence(presenceAfter, BAILEYS_REPLY_PRESENCE_AFTER),
|
|
@@ -155,16 +254,18 @@ const resolveMessageTimestampMs = (msg) => {
|
|
|
155
254
|
return tsNumber * 1000;
|
|
156
255
|
}
|
|
157
256
|
}
|
|
158
|
-
return
|
|
257
|
+
return __timeNowMs();
|
|
159
258
|
};
|
|
160
259
|
|
|
161
260
|
/**
|
|
162
261
|
* Normaliza uma mensagem do Baileys para o formato persistido no banco.
|
|
163
262
|
* @param {import('@whiskeysockets/baileys').WAMessage} msg - Mensagem recebida/enviada.
|
|
164
263
|
* @param {string} [senderId] - ID do remetente (opcional).
|
|
264
|
+
* @param {string|null} [sessionId] - Sessão lógica para persistência.
|
|
165
265
|
* @returns {Object} Objeto com dados prontos para persistencia.
|
|
166
266
|
*/
|
|
167
|
-
export const buildMessageData = (msg, senderId) => ({
|
|
267
|
+
export const buildMessageData = (msg, senderId, sessionId = null) => ({
|
|
268
|
+
session_id: normalizeSessionId(sessionId),
|
|
168
269
|
message_id: msg?.key?.id,
|
|
169
270
|
chat_id: msg?.key?.remoteJid,
|
|
170
271
|
sender_id: senderId || msg?.key?.participant || msg?.key?.remoteJid,
|
|
@@ -226,6 +327,40 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
226
327
|
|
|
227
328
|
const normalizedJid = normalizeJid(jid) || String(jid).trim();
|
|
228
329
|
const runtimeOptions = resolveRuntimeSendOptions(options);
|
|
330
|
+
const runtimeSessionId = runtimeOptions.sessionId || normalizeSessionId(sock?.__omnizapSessionId);
|
|
331
|
+
let resolvedGroupWritePermission = null;
|
|
332
|
+
|
|
333
|
+
if (isGroupJid(normalizedJid)) {
|
|
334
|
+
if (runtimeOptions.allowGroupWrite === false) {
|
|
335
|
+
logger.debug('Envio para grupo ignorado por bloqueio explícito de escrita.', {
|
|
336
|
+
action: 'send_group_blocked_explicit',
|
|
337
|
+
groupJid: normalizedJid,
|
|
338
|
+
sessionId: runtimeSessionId,
|
|
339
|
+
});
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (runtimeOptions.allowGroupWrite === true) {
|
|
344
|
+
resolvedGroupWritePermission = {
|
|
345
|
+
allowed: true,
|
|
346
|
+
ownerSessionId: runtimeSessionId,
|
|
347
|
+
reason: 'explicit_allow',
|
|
348
|
+
};
|
|
349
|
+
} else {
|
|
350
|
+
resolvedGroupWritePermission = await resolveGroupWritePermission(normalizedJid, runtimeSessionId);
|
|
351
|
+
if (!resolvedGroupWritePermission.allowed) {
|
|
352
|
+
logger.info('Envio para grupo bloqueado por sessão não-owner.', {
|
|
353
|
+
action: 'send_group_blocked_non_owner',
|
|
354
|
+
groupJid: normalizedJid,
|
|
355
|
+
sessionId: runtimeSessionId,
|
|
356
|
+
ownerSessionId: resolvedGroupWritePermission.ownerSessionId,
|
|
357
|
+
reason: resolvedGroupWritePermission.reason,
|
|
358
|
+
});
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
229
364
|
const normalizedOptions = normalizeSendOptions(runtimeOptions.sendOptions);
|
|
230
365
|
const shouldSendPresence = shouldSendReplyPresence(normalizedJid, content, runtimeOptions);
|
|
231
366
|
|
|
@@ -291,7 +426,13 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
291
426
|
const senderId = sock?.user?.id || sent?.key?.participant;
|
|
292
427
|
if (sent?.key?.id) {
|
|
293
428
|
try {
|
|
294
|
-
|
|
429
|
+
const messageData = buildMessageData(sent, senderId, runtimeSessionId);
|
|
430
|
+
const targetGroupJid = normalizeJid(messageData.chat_id || normalizedJid);
|
|
431
|
+
if (isGroupJid(targetGroupJid)) {
|
|
432
|
+
const allowGroupWrite = runtimeOptions.allowGroupWrite === true || resolvedGroupWritePermission?.allowed === true;
|
|
433
|
+
messageData.allow_group_write = allowGroupWrite;
|
|
434
|
+
}
|
|
435
|
+
queueMessageInsert(messageData);
|
|
295
436
|
} catch (error) {
|
|
296
437
|
logger.warn('Falha ao enfileirar mensagem enviada para persistencia.', {
|
|
297
438
|
error: error.message,
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const DEFAULT_SESSION_ID = 'default';
|
|
2
|
+
const SESSION_ID_MAX_LENGTH = 64;
|
|
3
|
+
const SESSION_ID_PATTERN = /^[a-zA-Z0-9:_-]+$/;
|
|
4
|
+
const OWNER_ENFORCEMENT_MODES = new Set(['off', 'shadow', 'enforce']);
|
|
5
|
+
|
|
6
|
+
const parseEnvBool = (value, fallback) => {
|
|
7
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
8
|
+
const normalized = String(value).trim().toLowerCase();
|
|
9
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
10
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
11
|
+
return fallback;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const parseEnvInt = (value, fallback, min, max) => {
|
|
15
|
+
const parsed = Number(value);
|
|
16
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
17
|
+
return Math.max(min, Math.min(max, Math.floor(parsed)));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const parseFlexibleEntries = (value) =>
|
|
21
|
+
String(value || '')
|
|
22
|
+
.split(/[,\n;]+/g)
|
|
23
|
+
.map((entry) => String(entry || '').trim())
|
|
24
|
+
.filter(Boolean);
|
|
25
|
+
|
|
26
|
+
const normalizeSessionId = (value) => String(value || '').trim();
|
|
27
|
+
|
|
28
|
+
const isValidSessionId = (value) => {
|
|
29
|
+
const normalized = normalizeSessionId(value);
|
|
30
|
+
if (!normalized) return false;
|
|
31
|
+
if (normalized.length > SESSION_ID_MAX_LENGTH) return false;
|
|
32
|
+
return SESSION_ID_PATTERN.test(normalized);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const parseSessionIds = ({ sessionIdsRaw = '', legacySessionIdRaw = '' } = {}) => {
|
|
36
|
+
const warnings = [];
|
|
37
|
+
const validSessionIds = [];
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
|
|
40
|
+
const legacySessionId = normalizeSessionId(legacySessionIdRaw) || DEFAULT_SESSION_ID;
|
|
41
|
+
const requestedSessionIds = parseFlexibleEntries(sessionIdsRaw);
|
|
42
|
+
const sourceSessionIds = requestedSessionIds.length > 0 ? requestedSessionIds : [legacySessionId];
|
|
43
|
+
|
|
44
|
+
for (const candidate of sourceSessionIds) {
|
|
45
|
+
const sessionId = normalizeSessionId(candidate);
|
|
46
|
+
if (!isValidSessionId(sessionId)) {
|
|
47
|
+
warnings.push(`session_id invalido ignorado: "${candidate}"`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (seen.has(sessionId)) continue;
|
|
51
|
+
seen.add(sessionId);
|
|
52
|
+
validSessionIds.push(sessionId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (validSessionIds.length === 0) {
|
|
56
|
+
validSessionIds.push(DEFAULT_SESSION_ID);
|
|
57
|
+
warnings.push(`nenhum session_id valido encontrado; usando fallback "${DEFAULT_SESSION_ID}"`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
sessionIds: validSessionIds,
|
|
62
|
+
warnings,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const parseSessionWeights = (rawValue, sessionIds, warnings) => {
|
|
67
|
+
const allowedSessions = new Set(sessionIds);
|
|
68
|
+
const weights = {};
|
|
69
|
+
for (const sessionId of sessionIds) {
|
|
70
|
+
weights[sessionId] = 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const entries = parseFlexibleEntries(rawValue);
|
|
74
|
+
if (entries.length === 0) return weights;
|
|
75
|
+
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const separator = entry.includes('=') ? '=' : entry.includes(':') ? ':' : '';
|
|
78
|
+
if (!separator) {
|
|
79
|
+
warnings.push(`peso de sessao invalido (faltando separador "=" ou ":"): "${entry}"`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const [rawSessionId, rawWeight] = entry.split(separator, 2);
|
|
84
|
+
const sessionId = normalizeSessionId(rawSessionId);
|
|
85
|
+
if (!isValidSessionId(sessionId)) {
|
|
86
|
+
warnings.push(`peso ignorado para session_id invalido: "${rawSessionId}"`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!allowedSessions.has(sessionId)) {
|
|
90
|
+
warnings.push(`peso ignorado para session_id nao listado em BAILEYS_SESSION_IDS: "${sessionId}"`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const weight = parseEnvInt(rawWeight, Number.NaN, 1, 1000);
|
|
95
|
+
if (!Number.isFinite(weight)) {
|
|
96
|
+
warnings.push(`peso invalido para "${sessionId}": "${rawWeight}"`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
weights[sessionId] = weight;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return weights;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const resolveMultiSessionRuntimeConfig = (env = process.env) => {
|
|
106
|
+
const warnings = [];
|
|
107
|
+
const legacySessionId = normalizeSessionId(env.BAILEYS_AUTH_SESSION_ID) || DEFAULT_SESSION_ID;
|
|
108
|
+
const { sessionIds, warnings: parseWarnings } = parseSessionIds({
|
|
109
|
+
sessionIdsRaw: env.BAILEYS_SESSION_IDS,
|
|
110
|
+
legacySessionIdRaw: legacySessionId,
|
|
111
|
+
});
|
|
112
|
+
warnings.push(...parseWarnings);
|
|
113
|
+
|
|
114
|
+
const requestedPrimary = normalizeSessionId(env.BAILEYS_PRIMARY_SESSION_ID);
|
|
115
|
+
let primarySessionId = requestedPrimary || sessionIds[0];
|
|
116
|
+
|
|
117
|
+
if (requestedPrimary && !isValidSessionId(requestedPrimary)) {
|
|
118
|
+
warnings.push(`BAILEYS_PRIMARY_SESSION_ID invalido: "${requestedPrimary}"`);
|
|
119
|
+
primarySessionId = sessionIds[0];
|
|
120
|
+
} else if (requestedPrimary && !sessionIds.includes(requestedPrimary)) {
|
|
121
|
+
warnings.push(`BAILEYS_PRIMARY_SESSION_ID fora da lista de sessoes: "${requestedPrimary}"`);
|
|
122
|
+
primarySessionId = sessionIds[0];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ownerEnforcementModeRaw = String(env.GROUP_OWNER_ENFORCEMENT_MODE || 'off')
|
|
126
|
+
.trim()
|
|
127
|
+
.toLowerCase();
|
|
128
|
+
const ownerEnforcementMode = OWNER_ENFORCEMENT_MODES.has(ownerEnforcementModeRaw) ? ownerEnforcementModeRaw : 'off';
|
|
129
|
+
if (!OWNER_ENFORCEMENT_MODES.has(ownerEnforcementModeRaw)) {
|
|
130
|
+
warnings.push(`GROUP_OWNER_ENFORCEMENT_MODE invalido: "${ownerEnforcementModeRaw}"`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const ownerLeaseMs = parseEnvInt(env.GROUP_OWNER_LEASE_MS, 120_000, 5_000, 15 * 60 * 1000);
|
|
134
|
+
let ownerHeartbeatMs = parseEnvInt(env.GROUP_OWNER_HEARTBEAT_MS, 30_000, 1_000, 5 * 60 * 1000);
|
|
135
|
+
if (ownerHeartbeatMs >= ownerLeaseMs) {
|
|
136
|
+
ownerHeartbeatMs = Math.max(1_000, Math.floor(ownerLeaseMs / 2));
|
|
137
|
+
warnings.push(`GROUP_OWNER_HEARTBEAT_MS ajustado automaticamente para ${ownerHeartbeatMs}ms (precisa ser menor que lease)`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const balancerEnabled = parseEnvBool(env.GROUP_BALANCER_ENABLED, false);
|
|
141
|
+
const sessionWeights = parseSessionWeights(env.BAILEYS_SESSION_WEIGHTS, sessionIds, warnings);
|
|
142
|
+
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
sessionIds: Object.freeze([...sessionIds]),
|
|
145
|
+
primarySessionId,
|
|
146
|
+
sessionWeights: Object.freeze({ ...sessionWeights }),
|
|
147
|
+
ownerEnforcementMode,
|
|
148
|
+
ownerLeaseMs,
|
|
149
|
+
ownerHeartbeatMs,
|
|
150
|
+
balancerEnabled,
|
|
151
|
+
warnings: Object.freeze([...warnings]),
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const multiSessionRuntimeConfig = resolveMultiSessionRuntimeConfig();
|
|
156
|
+
|
|
157
|
+
export const getMultiSessionRuntimeConfig = () => multiSessionRuntimeConfig;
|
|
@@ -6,7 +6,7 @@ import test from 'node:test';
|
|
|
6
6
|
|
|
7
7
|
import { initAuthCreds, proto } from '@whiskeysockets/baileys';
|
|
8
8
|
|
|
9
|
-
const PINNED_BAILEYS_REF = 'github:jlucaso1/Baileys#
|
|
9
|
+
const PINNED_BAILEYS_REF = 'github:jlucaso1/Baileys#feat-add-stickerpack-support';
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
const baileysPackageJsonPath = require.resolve('@whiskeysockets/baileys/package.json');
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export const normalizeAssignmentVersion = (value) => {
|
|
2
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
3
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
4
|
+
return parsed;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const createGroupOwnerWriteStateResolver = ({
|
|
8
|
+
buildCacheKeyImpl,
|
|
9
|
+
getOwnerImpl,
|
|
10
|
+
tryAcquireImpl,
|
|
11
|
+
cacheImpl,
|
|
12
|
+
isGroupJidImpl,
|
|
13
|
+
normalizeSessionIdImpl,
|
|
14
|
+
loggerImpl,
|
|
15
|
+
defaultAllowClaim = true,
|
|
16
|
+
} = {}) =>
|
|
17
|
+
async (
|
|
18
|
+
groupJid,
|
|
19
|
+
sessionId,
|
|
20
|
+
{
|
|
21
|
+
allowClaim = defaultAllowClaim,
|
|
22
|
+
bypassCache = false,
|
|
23
|
+
source = 'unknown',
|
|
24
|
+
expectedAssignmentVersion = null,
|
|
25
|
+
enforceFence = true,
|
|
26
|
+
} = {},
|
|
27
|
+
) => {
|
|
28
|
+
const safeGroupJid = String(groupJid || '').trim();
|
|
29
|
+
const safeSessionId = normalizeSessionIdImpl(sessionId);
|
|
30
|
+
if (!safeGroupJid || !isGroupJidImpl(safeGroupJid)) {
|
|
31
|
+
return {
|
|
32
|
+
allowed: true,
|
|
33
|
+
ownerSessionId: null,
|
|
34
|
+
assignmentVersion: null,
|
|
35
|
+
reason: 'not_group',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const safeExpectedAssignmentVersion = normalizeAssignmentVersion(expectedAssignmentVersion);
|
|
40
|
+
const cacheKey = buildCacheKeyImpl(safeGroupJid, safeSessionId);
|
|
41
|
+
const mustBypassCache = bypassCache || Boolean(enforceFence && safeExpectedAssignmentVersion);
|
|
42
|
+
|
|
43
|
+
if (!mustBypassCache && cacheKey) {
|
|
44
|
+
const cached = cacheImpl.get(cacheKey);
|
|
45
|
+
if (cached && typeof cached === 'object') {
|
|
46
|
+
return cached;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const ownerState = await getOwnerImpl(safeGroupJid, { bypassCache: mustBypassCache });
|
|
52
|
+
let ownerSessionId = String(ownerState?.ownerSessionId || '').trim() || null;
|
|
53
|
+
let assignmentVersion = normalizeAssignmentVersion(ownerState?.assignmentVersion);
|
|
54
|
+
let allowed = false;
|
|
55
|
+
let reason = 'owned_by_other';
|
|
56
|
+
|
|
57
|
+
if (!ownerSessionId && allowClaim) {
|
|
58
|
+
const claimOutcome = await tryAcquireImpl({
|
|
59
|
+
groupJid: safeGroupJid,
|
|
60
|
+
sessionId: safeSessionId,
|
|
61
|
+
reason: 'writer_gate_claim',
|
|
62
|
+
changedBy: safeSessionId,
|
|
63
|
+
metadata: {
|
|
64
|
+
source,
|
|
65
|
+
gate: 'group_write',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
ownerSessionId = String(claimOutcome?.owner?.ownerSessionId || '').trim() || null;
|
|
70
|
+
assignmentVersion = normalizeAssignmentVersion(claimOutcome?.assignmentVersion ?? claimOutcome?.owner?.assignmentVersion);
|
|
71
|
+
allowed = Boolean(claimOutcome?.acquired && ownerSessionId === safeSessionId);
|
|
72
|
+
reason = claimOutcome?.reason || 'claim_attempt';
|
|
73
|
+
} else {
|
|
74
|
+
allowed = Boolean(ownerSessionId && ownerSessionId === safeSessionId);
|
|
75
|
+
reason = !ownerSessionId ? 'owner_missing' : allowed ? 'owner_match' : 'owned_by_other';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (allowed && enforceFence && safeExpectedAssignmentVersion && assignmentVersion && assignmentVersion !== safeExpectedAssignmentVersion) {
|
|
79
|
+
allowed = false;
|
|
80
|
+
reason = 'fence_token_mismatch';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const resolved = {
|
|
84
|
+
allowed,
|
|
85
|
+
ownerSessionId,
|
|
86
|
+
assignmentVersion,
|
|
87
|
+
reason,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (cacheKey) {
|
|
91
|
+
cacheImpl.set(cacheKey, resolved);
|
|
92
|
+
}
|
|
93
|
+
return resolved;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
loggerImpl.warn('Falha ao resolver ownership para escrita de grupo.', {
|
|
96
|
+
action: 'group_owner_write_state_failed',
|
|
97
|
+
source,
|
|
98
|
+
sessionId: safeSessionId,
|
|
99
|
+
groupId: safeGroupJid,
|
|
100
|
+
error: error?.message,
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
allowed: false,
|
|
104
|
+
ownerSessionId: null,
|
|
105
|
+
assignmentVersion: null,
|
|
106
|
+
reason: 'owner_resolution_failed',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
};
|