@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
|
@@ -1,21 +1,196 @@
|
|
|
1
1
|
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
2
|
import { baileysConnectionLogger as logger } from './loggerConfig.js';
|
|
3
3
|
import { queueMessageInsert } from '../services/infra/dbWriteQueue.js';
|
|
4
|
-
import { parseEnvBool, parseEnvInt, normalizeJid, isGroupJid, isStatusJid, isBroadcastJid, isNewsletterJid, normalizeWAPresence } from './baileysConfig.js';
|
|
4
|
+
import { parseEnvBool, parseEnvInt, normalizeJid, isGroupJid, isStatusJid, isBroadcastJid, isNewsletterJid, normalizeWAPresence, isLidJid, isWhatsAppJid, normalizePnToJid, resolveUserId } from './baileysConfig.js';
|
|
5
|
+
import { getOwner as getGroupOwner, tryAcquire as tryAcquireGroupOwner } from '../services/multiSession/groupOwnershipService.js';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Número máximo de tentativas de envio.
|
|
9
|
+
* @type {number}
|
|
10
|
+
*/
|
|
6
11
|
const BAILEYS_SEND_RETRY_ATTEMPTS = parseEnvInt(process.env.BAILEYS_SEND_RETRY_ATTEMPTS, 2, 1, 5);
|
|
12
|
+
/**
|
|
13
|
+
* Atraso base (ms) para backoff exponencial entre retries.
|
|
14
|
+
* @type {number}
|
|
15
|
+
*/
|
|
7
16
|
const BAILEYS_SEND_RETRY_BASE_DELAY_MS = parseEnvInt(process.env.BAILEYS_SEND_RETRY_BASE_DELAY_MS, 600, 100, 10_000);
|
|
17
|
+
/**
|
|
18
|
+
* Timeout de upload de mídia repassado ao Baileys.
|
|
19
|
+
* @type {number}
|
|
20
|
+
*/
|
|
8
21
|
const BAILEYS_SEND_MEDIA_UPLOAD_TIMEOUT_MS = parseEnvInt(process.env.BAILEYS_SEND_MEDIA_UPLOAD_TIMEOUT_MS, 0, 0, 120_000);
|
|
22
|
+
/**
|
|
23
|
+
* Habilita presença automática durante replies.
|
|
24
|
+
* @type {boolean}
|
|
25
|
+
*/
|
|
9
26
|
const BAILEYS_REPLY_PRESENCE_ENABLED = parseEnvBool(process.env.BAILEYS_REPLY_PRESENCE_ENABLED, true);
|
|
27
|
+
/**
|
|
28
|
+
* Define se deve assinar presença antes de enviar update.
|
|
29
|
+
* @type {boolean}
|
|
30
|
+
*/
|
|
10
31
|
const BAILEYS_REPLY_PRESENCE_SUBSCRIBE = parseEnvBool(process.env.BAILEYS_REPLY_PRESENCE_SUBSCRIBE, true);
|
|
32
|
+
/**
|
|
33
|
+
* Delay entre presença "before" e envio (ms).
|
|
34
|
+
* @type {number}
|
|
35
|
+
*/
|
|
11
36
|
const BAILEYS_REPLY_PRESENCE_DELAY_MS = parseEnvInt(process.env.BAILEYS_REPLY_PRESENCE_DELAY_MS, 280, 0, 3_000);
|
|
37
|
+
/**
|
|
38
|
+
* Presença enviada antes do envio.
|
|
39
|
+
* @type {import('@whiskeysockets/baileys').WAPresence}
|
|
40
|
+
*/
|
|
12
41
|
const BAILEYS_REPLY_PRESENCE_BEFORE = normalizeWAPresence(process.env.BAILEYS_REPLY_PRESENCE_BEFORE, 'composing');
|
|
42
|
+
/**
|
|
43
|
+
* Presença enviada após o envio.
|
|
44
|
+
* @type {import('@whiskeysockets/baileys').WAPresence}
|
|
45
|
+
*/
|
|
13
46
|
const BAILEYS_REPLY_PRESENCE_AFTER = normalizeWAPresence(process.env.BAILEYS_REPLY_PRESENCE_AFTER, 'paused');
|
|
47
|
+
/**
|
|
48
|
+
* Prefere enviar para PN quando o destino original for LID.
|
|
49
|
+
* @type {boolean}
|
|
50
|
+
*/
|
|
51
|
+
const BAILEYS_SEND_PREFER_PN_FOR_LID = parseEnvBool(process.env.BAILEYS_SEND_PREFER_PN_FOR_LID, true);
|
|
52
|
+
/**
|
|
53
|
+
* TTL do cache de permissão de escrita em grupo (ms).
|
|
54
|
+
* @type {number}
|
|
55
|
+
*/
|
|
56
|
+
const GROUP_WRITE_PERMISSION_CACHE_TTL_MS = parseEnvInt(process.env.GROUP_OWNER_WRITE_CACHE_TTL_MS, 8_000, 1_000, 60_000);
|
|
14
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Verifica se o valor é um objeto plano.
|
|
60
|
+
* @param {unknown} value
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
15
63
|
const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]';
|
|
16
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Chaves primárias conhecidas de `AnyMessageContent`.
|
|
67
|
+
* @type {Set<string>}
|
|
68
|
+
*/
|
|
17
69
|
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']);
|
|
70
|
+
/**
|
|
71
|
+
* Conteúdos que não disparam presença de resposta.
|
|
72
|
+
* @type {Set<string>}
|
|
73
|
+
*/
|
|
18
74
|
const PRESENCE_NON_REPLY_CONTENT_KEYS = new Set(['react', 'delete', 'pin', 'disappearingMessagesInChat']);
|
|
75
|
+
/**
|
|
76
|
+
* Cache local de permissão de escrita por `sessionId+groupJid`.
|
|
77
|
+
* @type {Map<string, {allowed: boolean, ownerSessionId: string | null, expiresAtMs: number}>}
|
|
78
|
+
*/
|
|
79
|
+
const groupWritePermissionCache = new Map();
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Normaliza um ID de sessão.
|
|
83
|
+
* @param {unknown} value
|
|
84
|
+
* @returns {string|null}
|
|
85
|
+
*/
|
|
86
|
+
const normalizeSessionId = (value) => {
|
|
87
|
+
const normalized = String(value || '').trim();
|
|
88
|
+
return normalized || null;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Recupera permissão de escrita de grupo no cache.
|
|
93
|
+
* @param {string} groupJid
|
|
94
|
+
* @param {string} sessionId
|
|
95
|
+
* @returns {{allowed: boolean, ownerSessionId: string | null, expiresAtMs: number} | null}
|
|
96
|
+
*/
|
|
97
|
+
const getCachedGroupWritePermission = (groupJid, sessionId) => {
|
|
98
|
+
const key = `${sessionId}:${groupJid}`;
|
|
99
|
+
const cached = groupWritePermissionCache.get(key);
|
|
100
|
+
if (!cached) return null;
|
|
101
|
+
if (cached.expiresAtMs <= __timeNowMs()) {
|
|
102
|
+
groupWritePermissionCache.delete(key);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return cached;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Salva permissão de escrita de grupo no cache.
|
|
110
|
+
* @param {string} groupJid
|
|
111
|
+
* @param {string} sessionId
|
|
112
|
+
* @param {boolean} allowed
|
|
113
|
+
* @param {string|null} [ownerSessionId=null]
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
116
|
+
const setCachedGroupWritePermission = (groupJid, sessionId, allowed, ownerSessionId = null) => {
|
|
117
|
+
const key = `${sessionId}:${groupJid}`;
|
|
118
|
+
groupWritePermissionCache.set(key, {
|
|
119
|
+
allowed: Boolean(allowed),
|
|
120
|
+
ownerSessionId: normalizeSessionId(ownerSessionId),
|
|
121
|
+
expiresAtMs: __timeNowMs() + GROUP_WRITE_PERMISSION_CACHE_TTL_MS,
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve se a sessão atual pode escrever em um grupo.
|
|
127
|
+
* @param {string} groupJid
|
|
128
|
+
* @param {string|null} sessionId
|
|
129
|
+
* @returns {Promise<{allowed: boolean, ownerSessionId: string | null, reason: string}>}
|
|
130
|
+
*/
|
|
131
|
+
const resolveGroupWritePermission = async (groupJid, sessionId) => {
|
|
132
|
+
if (!isGroupJid(groupJid) || !sessionId) {
|
|
133
|
+
return {
|
|
134
|
+
allowed: true,
|
|
135
|
+
ownerSessionId: null,
|
|
136
|
+
reason: 'not_group_or_missing_session',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const cached = getCachedGroupWritePermission(groupJid, sessionId);
|
|
141
|
+
if (cached) {
|
|
142
|
+
return {
|
|
143
|
+
allowed: cached.allowed,
|
|
144
|
+
ownerSessionId: cached.ownerSessionId,
|
|
145
|
+
reason: 'cache_hit',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const ownerState = await getGroupOwner(groupJid);
|
|
151
|
+
let ownerSessionId = normalizeSessionId(ownerState?.ownerSessionId);
|
|
152
|
+
let allowed = false;
|
|
153
|
+
let reason = 'owned_by_other';
|
|
154
|
+
|
|
155
|
+
if (!ownerSessionId) {
|
|
156
|
+
const claimOutcome = await tryAcquireGroupOwner({
|
|
157
|
+
groupJid,
|
|
158
|
+
sessionId,
|
|
159
|
+
reason: 'send_store_claim',
|
|
160
|
+
changedBy: sessionId,
|
|
161
|
+
metadata: {
|
|
162
|
+
source: 'message_persistence_service',
|
|
163
|
+
gate: 'group_write',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
ownerSessionId = normalizeSessionId(claimOutcome?.owner?.ownerSessionId);
|
|
167
|
+
allowed = Boolean(claimOutcome?.acquired && ownerSessionId === sessionId);
|
|
168
|
+
reason = claimOutcome?.reason || 'claim_attempt';
|
|
169
|
+
} else {
|
|
170
|
+
allowed = ownerSessionId === sessionId;
|
|
171
|
+
reason = allowed ? 'owner_match' : 'owned_by_other';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
setCachedGroupWritePermission(groupJid, sessionId, allowed, ownerSessionId);
|
|
175
|
+
return {
|
|
176
|
+
allowed,
|
|
177
|
+
ownerSessionId,
|
|
178
|
+
reason,
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
logger.warn('Falha ao validar ownership para persistência de saída em grupo.', {
|
|
182
|
+
action: 'group_write_permission_resolution_failed',
|
|
183
|
+
groupJid,
|
|
184
|
+
sessionId,
|
|
185
|
+
error: error?.message,
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
allowed: false,
|
|
189
|
+
ownerSessionId: null,
|
|
190
|
+
reason: 'resolution_failed',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
};
|
|
19
194
|
|
|
20
195
|
/**
|
|
21
196
|
* Verifica se o payload se parece com AnyMessageContent do Baileys.
|
|
@@ -62,6 +237,8 @@ const normalizeSendOptions = (options) => {
|
|
|
62
237
|
* @param {unknown} options
|
|
63
238
|
* @returns {{
|
|
64
239
|
* sendOptions: import('@whiskeysockets/baileys').MiscMessageGenerationOptions|undefined,
|
|
240
|
+
* sessionId: string | null,
|
|
241
|
+
* allowGroupWrite: boolean | undefined,
|
|
65
242
|
* skipPresenceUpdate: boolean,
|
|
66
243
|
* presenceBefore: import('@whiskeysockets/baileys').WAPresence,
|
|
67
244
|
* presenceAfter: import('@whiskeysockets/baileys').WAPresence,
|
|
@@ -73,6 +250,8 @@ const resolveRuntimeSendOptions = (options) => {
|
|
|
73
250
|
if (!isPlainObject(options)) {
|
|
74
251
|
return {
|
|
75
252
|
sendOptions: undefined,
|
|
253
|
+
sessionId: null,
|
|
254
|
+
allowGroupWrite: undefined,
|
|
76
255
|
skipPresenceUpdate: false,
|
|
77
256
|
presenceBefore: BAILEYS_REPLY_PRESENCE_BEFORE,
|
|
78
257
|
presenceAfter: BAILEYS_REPLY_PRESENCE_AFTER,
|
|
@@ -81,10 +260,12 @@ const resolveRuntimeSendOptions = (options) => {
|
|
|
81
260
|
};
|
|
82
261
|
}
|
|
83
262
|
|
|
84
|
-
const { skipPresenceUpdate, presenceBefore, presenceAfter, presenceDelayMs, presenceSubscribe, ...sendOptions } = options;
|
|
263
|
+
const { skipPresenceUpdate, presenceBefore, presenceAfter, presenceDelayMs, presenceSubscribe, sessionId, allowGroupWrite, ...sendOptions } = options;
|
|
85
264
|
const normalizedDelay = parseEnvInt(presenceDelayMs, BAILEYS_REPLY_PRESENCE_DELAY_MS, 0, 3_000);
|
|
86
265
|
return {
|
|
87
266
|
sendOptions: Object.keys(sendOptions).length > 0 ? sendOptions : undefined,
|
|
267
|
+
sessionId: normalizeSessionId(sessionId),
|
|
268
|
+
allowGroupWrite: typeof allowGroupWrite === 'boolean' ? allowGroupWrite : undefined,
|
|
88
269
|
skipPresenceUpdate: Boolean(skipPresenceUpdate),
|
|
89
270
|
presenceBefore: normalizeWAPresence(presenceBefore, BAILEYS_REPLY_PRESENCE_BEFORE),
|
|
90
271
|
presenceAfter: normalizeWAPresence(presenceAfter, BAILEYS_REPLY_PRESENCE_AFTER),
|
|
@@ -127,6 +308,60 @@ const shouldSendReplyPresence = (jid, content, runtimeOptions) => {
|
|
|
127
308
|
return true;
|
|
128
309
|
};
|
|
129
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Verifica se o JID é de usuário direto (não grupo/broadcast/status/newsletter).
|
|
313
|
+
* @param {string} jid
|
|
314
|
+
* @returns {boolean}
|
|
315
|
+
*/
|
|
316
|
+
const isDirectUserJid = (jid) => {
|
|
317
|
+
if (!jid) return false;
|
|
318
|
+
if (isGroupJid(jid)) return false;
|
|
319
|
+
if (isStatusJid(jid)) return false;
|
|
320
|
+
if (isBroadcastJid(jid)) return false;
|
|
321
|
+
if (isNewsletterJid(jid)) return false;
|
|
322
|
+
return true;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Resolve JID preferencial de envio, convertendo LID para PN quando possível.
|
|
327
|
+
* @param {string} normalizedJid
|
|
328
|
+
* @returns {Promise<string>}
|
|
329
|
+
*/
|
|
330
|
+
const resolvePreferredSendJid = async (normalizedJid) => {
|
|
331
|
+
if (!normalizedJid) return normalizedJid;
|
|
332
|
+
if (!BAILEYS_SEND_PREFER_PN_FOR_LID) return normalizedJid;
|
|
333
|
+
if (!isDirectUserJid(normalizedJid)) return normalizedJid;
|
|
334
|
+
if (!isLidJid(normalizedJid)) return normalizedJid;
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const resolvedIdentity = await resolveUserId({
|
|
338
|
+
lid: normalizedJid,
|
|
339
|
+
jid: normalizedJid,
|
|
340
|
+
});
|
|
341
|
+
const normalizedResolved = normalizeJid(String(resolvedIdentity || '').trim());
|
|
342
|
+
const candidatePnJid = normalizePnToJid(normalizedResolved || String(resolvedIdentity || '').trim());
|
|
343
|
+
if (candidatePnJid && isWhatsAppJid(candidatePnJid)) {
|
|
344
|
+
return candidatePnJid;
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
logger.debug('Falha ao resolver PN para envio com destino LID. Mantendo destino original.', {
|
|
348
|
+
action: 'resolve_preferred_send_jid_failed',
|
|
349
|
+
jid: normalizedJid,
|
|
350
|
+
error: error?.message,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return normalizedJid;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Envia presença no Baileys sem interromper o fluxo principal em caso de erro.
|
|
359
|
+
* @param {import('@whiskeysockets/baileys').WASocket} sock
|
|
360
|
+
* @param {import('@whiskeysockets/baileys').WAPresence} type
|
|
361
|
+
* @param {string} jid
|
|
362
|
+
* @param {boolean} [subscribeFirst=false]
|
|
363
|
+
* @returns {Promise<void>}
|
|
364
|
+
*/
|
|
130
365
|
const sendPresenceSilently = async (sock, type, jid, subscribeFirst = false) => {
|
|
131
366
|
if (!sock || typeof sock.sendPresenceUpdate !== 'function') return;
|
|
132
367
|
try {
|
|
@@ -163,9 +398,11 @@ const resolveMessageTimestampMs = (msg) => {
|
|
|
163
398
|
* Normaliza uma mensagem do Baileys para o formato persistido no banco.
|
|
164
399
|
* @param {import('@whiskeysockets/baileys').WAMessage} msg - Mensagem recebida/enviada.
|
|
165
400
|
* @param {string} [senderId] - ID do remetente (opcional).
|
|
401
|
+
* @param {string|null} [sessionId] - Sessão lógica para persistência.
|
|
166
402
|
* @returns {Object} Objeto com dados prontos para persistencia.
|
|
167
403
|
*/
|
|
168
|
-
export const buildMessageData = (msg, senderId) => ({
|
|
404
|
+
export const buildMessageData = (msg, senderId, sessionId = null) => ({
|
|
405
|
+
session_id: normalizeSessionId(sessionId),
|
|
169
406
|
message_id: msg?.key?.id,
|
|
170
407
|
chat_id: msg?.key?.remoteJid,
|
|
171
408
|
sender_id: senderId || msg?.key?.participant || msg?.key?.remoteJid,
|
|
@@ -174,8 +411,18 @@ export const buildMessageData = (msg, senderId) => ({
|
|
|
174
411
|
timestamp: new Date(resolveMessageTimestampMs(msg)),
|
|
175
412
|
});
|
|
176
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Atrasa execução por `ms`.
|
|
416
|
+
* @param {number} ms
|
|
417
|
+
* @returns {Promise<void>}
|
|
418
|
+
*/
|
|
177
419
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
178
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Detecta se um erro de envio é potencialmente transitório.
|
|
423
|
+
* @param {any} error
|
|
424
|
+
* @returns {boolean}
|
|
425
|
+
*/
|
|
179
426
|
const isTransientSendError = (error) => {
|
|
180
427
|
const statusCode = Number(error?.output?.statusCode || error?.statusCode || 0);
|
|
181
428
|
if ([408, 409, 425, 429, 500, 502, 503, 504].includes(statusCode)) return true;
|
|
@@ -192,6 +439,11 @@ const isTransientSendError = (error) => {
|
|
|
192
439
|
return transientFragments.some((fragment) => rawMessage.includes(fragment));
|
|
193
440
|
};
|
|
194
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Indica se o erro sugere refresh explícito de media connection.
|
|
444
|
+
* @param {any} error
|
|
445
|
+
* @returns {boolean}
|
|
446
|
+
*/
|
|
195
447
|
const shouldRefreshMediaConnection = (error) => {
|
|
196
448
|
const rawMessage = `${error?.message || ''} ${error?.data?.message || ''}`.toLowerCase();
|
|
197
449
|
return rawMessage.includes('media') || rawMessage.includes('directpath') || rawMessage.includes('upload');
|
|
@@ -225,8 +477,52 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
225
477
|
throw new TypeError(`Payload de mensagem inválido. Chaves recebidas: ${payloadKeys.join(', ') || 'nenhuma'}`);
|
|
226
478
|
}
|
|
227
479
|
|
|
228
|
-
const
|
|
480
|
+
const normalizedInputJid = normalizeJid(jid) || String(jid).trim();
|
|
229
481
|
const runtimeOptions = resolveRuntimeSendOptions(options);
|
|
482
|
+
const runtimeSessionId = runtimeOptions.sessionId || normalizeSessionId(sock?.__omnizapSessionId);
|
|
483
|
+
let resolvedGroupWritePermission = null;
|
|
484
|
+
|
|
485
|
+
if (isGroupJid(normalizedInputJid)) {
|
|
486
|
+
if (runtimeOptions.allowGroupWrite === false) {
|
|
487
|
+
logger.debug('Envio para grupo ignorado por bloqueio explícito de escrita.', {
|
|
488
|
+
action: 'send_group_blocked_explicit',
|
|
489
|
+
groupJid: normalizedInputJid,
|
|
490
|
+
sessionId: runtimeSessionId,
|
|
491
|
+
});
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (runtimeOptions.allowGroupWrite === true) {
|
|
496
|
+
resolvedGroupWritePermission = {
|
|
497
|
+
allowed: true,
|
|
498
|
+
ownerSessionId: runtimeSessionId,
|
|
499
|
+
reason: 'explicit_allow',
|
|
500
|
+
};
|
|
501
|
+
} else {
|
|
502
|
+
resolvedGroupWritePermission = await resolveGroupWritePermission(normalizedInputJid, runtimeSessionId);
|
|
503
|
+
if (!resolvedGroupWritePermission.allowed) {
|
|
504
|
+
logger.info('Envio para grupo bloqueado por sessão não-owner.', {
|
|
505
|
+
action: 'send_group_blocked_non_owner',
|
|
506
|
+
groupJid: normalizedInputJid,
|
|
507
|
+
sessionId: runtimeSessionId,
|
|
508
|
+
ownerSessionId: resolvedGroupWritePermission.ownerSessionId,
|
|
509
|
+
reason: resolvedGroupWritePermission.reason,
|
|
510
|
+
});
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const normalizedJid = await resolvePreferredSendJid(normalizedInputJid);
|
|
517
|
+
if (normalizedJid !== normalizedInputJid) {
|
|
518
|
+
logger.debug('Destino LID convertido para PN antes do envio.', {
|
|
519
|
+
action: 'send_target_lid_to_pn',
|
|
520
|
+
from: normalizedInputJid,
|
|
521
|
+
to: normalizedJid,
|
|
522
|
+
sessionId: runtimeSessionId,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
230
526
|
const normalizedOptions = normalizeSendOptions(runtimeOptions.sendOptions);
|
|
231
527
|
const shouldSendPresence = shouldSendReplyPresence(normalizedJid, content, runtimeOptions);
|
|
232
528
|
|
|
@@ -292,7 +588,13 @@ export async function sendAndStore(sock, jid, content, options) {
|
|
|
292
588
|
const senderId = sock?.user?.id || sent?.key?.participant;
|
|
293
589
|
if (sent?.key?.id) {
|
|
294
590
|
try {
|
|
295
|
-
|
|
591
|
+
const messageData = buildMessageData(sent, senderId, runtimeSessionId);
|
|
592
|
+
const targetGroupJid = normalizeJid(messageData.chat_id || normalizedInputJid);
|
|
593
|
+
if (isGroupJid(targetGroupJid)) {
|
|
594
|
+
const allowGroupWrite = runtimeOptions.allowGroupWrite === true || resolvedGroupWritePermission?.allowed === true;
|
|
595
|
+
messageData.allow_group_write = allowGroupWrite;
|
|
596
|
+
}
|
|
597
|
+
queueMessageInsert(messageData);
|
|
296
598
|
} catch (error) {
|
|
297
599
|
logger.warn('Falha ao enfileirar mensagem enviada para persistencia.', {
|
|
298
600
|
error: error.message,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessão padrão usada como fallback.
|
|
3
|
+
* @type {string}
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_SESSION_ID = 'default';
|
|
6
|
+
/**
|
|
7
|
+
* Tamanho máximo permitido para IDs de sessão.
|
|
8
|
+
* @type {number}
|
|
9
|
+
*/
|
|
10
|
+
const SESSION_ID_MAX_LENGTH = 64;
|
|
11
|
+
/**
|
|
12
|
+
* Regex de validação para IDs de sessão.
|
|
13
|
+
* @type {RegExp}
|
|
14
|
+
*/
|
|
15
|
+
const SESSION_ID_PATTERN = /^[a-zA-Z0-9:_-]+$/;
|
|
16
|
+
/**
|
|
17
|
+
* Modos de enforcement válidos para ownership de grupo.
|
|
18
|
+
* @type {Set<string>}
|
|
19
|
+
*/
|
|
20
|
+
const OWNER_ENFORCEMENT_MODES = new Set(['off', 'shadow', 'enforce']);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Converte um valor de ambiente para boolean com fallback.
|
|
24
|
+
* @param {unknown} value
|
|
25
|
+
* @param {boolean} fallback
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
const parseEnvBool = (value, fallback) => {
|
|
29
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
30
|
+
const normalized = String(value).trim().toLowerCase();
|
|
31
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
32
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
33
|
+
return fallback;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Converte um valor em inteiro com limites.
|
|
38
|
+
* @param {unknown} value
|
|
39
|
+
* @param {number} fallback
|
|
40
|
+
* @param {number} min
|
|
41
|
+
* @param {number} max
|
|
42
|
+
* @returns {number}
|
|
43
|
+
*/
|
|
44
|
+
const parseEnvInt = (value, fallback, min, max) => {
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
47
|
+
return Math.max(min, Math.min(max, Math.floor(parsed)));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Faz parse de entradas separadas por vírgula, quebra de linha ou `;`.
|
|
52
|
+
* @param {unknown} value
|
|
53
|
+
* @returns {string[]}
|
|
54
|
+
*/
|
|
55
|
+
const parseFlexibleEntries = (value) =>
|
|
56
|
+
String(value || '')
|
|
57
|
+
.split(/[,\n;]+/g)
|
|
58
|
+
.map((entry) => String(entry || '').trim())
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Normaliza um ID de sessão.
|
|
63
|
+
* @param {unknown} value
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
const normalizeSessionId = (value) => String(value || '').trim();
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Valida formato e tamanho de ID de sessão.
|
|
70
|
+
* @param {unknown} value
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
const isValidSessionId = (value) => {
|
|
74
|
+
const normalized = normalizeSessionId(value);
|
|
75
|
+
if (!normalized) return false;
|
|
76
|
+
if (normalized.length > SESSION_ID_MAX_LENGTH) return false;
|
|
77
|
+
return SESSION_ID_PATTERN.test(normalized);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {{sessionIds: string[], warnings: string[]}} ParsedSessionIds
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Interpreta lista de sessões do ambiente e aplica fallback legado.
|
|
85
|
+
* @param {{sessionIdsRaw?: string, legacySessionIdRaw?: string}} [params]
|
|
86
|
+
* @returns {ParsedSessionIds}
|
|
87
|
+
*/
|
|
88
|
+
const parseSessionIds = ({ sessionIdsRaw = '', legacySessionIdRaw = '' } = {}) => {
|
|
89
|
+
const warnings = [];
|
|
90
|
+
const validSessionIds = [];
|
|
91
|
+
const seen = new Set();
|
|
92
|
+
|
|
93
|
+
const legacySessionId = normalizeSessionId(legacySessionIdRaw) || DEFAULT_SESSION_ID;
|
|
94
|
+
const requestedSessionIds = parseFlexibleEntries(sessionIdsRaw);
|
|
95
|
+
const sourceSessionIds = requestedSessionIds.length > 0 ? requestedSessionIds : [legacySessionId];
|
|
96
|
+
|
|
97
|
+
for (const candidate of sourceSessionIds) {
|
|
98
|
+
const sessionId = normalizeSessionId(candidate);
|
|
99
|
+
if (!isValidSessionId(sessionId)) {
|
|
100
|
+
warnings.push(`session_id invalido ignorado: "${candidate}"`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (seen.has(sessionId)) continue;
|
|
104
|
+
seen.add(sessionId);
|
|
105
|
+
validSessionIds.push(sessionId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (validSessionIds.length === 0) {
|
|
109
|
+
validSessionIds.push(DEFAULT_SESSION_ID);
|
|
110
|
+
warnings.push(`nenhum session_id valido encontrado; usando fallback "${DEFAULT_SESSION_ID}"`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
sessionIds: validSessionIds,
|
|
115
|
+
warnings,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resolve pesos por sessão com validação e defaults.
|
|
121
|
+
* @param {unknown} rawValue
|
|
122
|
+
* @param {string[]} sessionIds
|
|
123
|
+
* @param {string[]} warnings
|
|
124
|
+
* @returns {Record<string, number>}
|
|
125
|
+
*/
|
|
126
|
+
const parseSessionWeights = (rawValue, sessionIds, warnings) => {
|
|
127
|
+
const allowedSessions = new Set(sessionIds);
|
|
128
|
+
const weights = {};
|
|
129
|
+
for (const sessionId of sessionIds) {
|
|
130
|
+
weights[sessionId] = 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const entries = parseFlexibleEntries(rawValue);
|
|
134
|
+
if (entries.length === 0) return weights;
|
|
135
|
+
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const separator = entry.includes('=') ? '=' : entry.includes(':') ? ':' : '';
|
|
138
|
+
if (!separator) {
|
|
139
|
+
warnings.push(`peso de sessao invalido (faltando separador "=" ou ":"): "${entry}"`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const [rawSessionId, rawWeight] = entry.split(separator, 2);
|
|
144
|
+
const sessionId = normalizeSessionId(rawSessionId);
|
|
145
|
+
if (!isValidSessionId(sessionId)) {
|
|
146
|
+
warnings.push(`peso ignorado para session_id invalido: "${rawSessionId}"`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!allowedSessions.has(sessionId)) {
|
|
150
|
+
warnings.push(`peso ignorado para session_id nao listado em BAILEYS_SESSION_IDS: "${sessionId}"`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const weight = parseEnvInt(rawWeight, Number.NaN, 1, 1000);
|
|
155
|
+
if (!Number.isFinite(weight)) {
|
|
156
|
+
warnings.push(`peso invalido para "${sessionId}": "${rawWeight}"`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
weights[sessionId] = weight;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return weights;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @typedef {{
|
|
167
|
+
* sessionIds: readonly string[],
|
|
168
|
+
* primarySessionId: string,
|
|
169
|
+
* sessionWeights: Readonly<Record<string, number>>,
|
|
170
|
+
* ownerEnforcementMode: 'off'|'shadow'|'enforce',
|
|
171
|
+
* ownerLeaseMs: number,
|
|
172
|
+
* ownerHeartbeatMs: number,
|
|
173
|
+
* balancerEnabled: boolean,
|
|
174
|
+
* warnings: readonly string[]
|
|
175
|
+
* }} MultiSessionRuntimeConfig
|
|
176
|
+
*/
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a configuração de runtime para múltiplas sessões.
|
|
179
|
+
* @param {Record<string, any>} [env=process.env]
|
|
180
|
+
* @returns {MultiSessionRuntimeConfig}
|
|
181
|
+
*/
|
|
182
|
+
export const resolveMultiSessionRuntimeConfig = (env = process.env) => {
|
|
183
|
+
const warnings = [];
|
|
184
|
+
const legacySessionId = normalizeSessionId(env.BAILEYS_AUTH_SESSION_ID) || DEFAULT_SESSION_ID;
|
|
185
|
+
const { sessionIds, warnings: parseWarnings } = parseSessionIds({
|
|
186
|
+
sessionIdsRaw: env.BAILEYS_SESSION_IDS,
|
|
187
|
+
legacySessionIdRaw: legacySessionId,
|
|
188
|
+
});
|
|
189
|
+
warnings.push(...parseWarnings);
|
|
190
|
+
|
|
191
|
+
const requestedPrimary = normalizeSessionId(env.BAILEYS_PRIMARY_SESSION_ID);
|
|
192
|
+
let primarySessionId = requestedPrimary || sessionIds[0];
|
|
193
|
+
|
|
194
|
+
if (requestedPrimary && !isValidSessionId(requestedPrimary)) {
|
|
195
|
+
warnings.push(`BAILEYS_PRIMARY_SESSION_ID invalido: "${requestedPrimary}"`);
|
|
196
|
+
primarySessionId = sessionIds[0];
|
|
197
|
+
} else if (requestedPrimary && !sessionIds.includes(requestedPrimary)) {
|
|
198
|
+
warnings.push(`BAILEYS_PRIMARY_SESSION_ID fora da lista de sessoes: "${requestedPrimary}"`);
|
|
199
|
+
primarySessionId = sessionIds[0];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const ownerEnforcementModeRaw = String(env.GROUP_OWNER_ENFORCEMENT_MODE || 'off')
|
|
203
|
+
.trim()
|
|
204
|
+
.toLowerCase();
|
|
205
|
+
const ownerEnforcementMode = OWNER_ENFORCEMENT_MODES.has(ownerEnforcementModeRaw) ? ownerEnforcementModeRaw : 'off';
|
|
206
|
+
if (!OWNER_ENFORCEMENT_MODES.has(ownerEnforcementModeRaw)) {
|
|
207
|
+
warnings.push(`GROUP_OWNER_ENFORCEMENT_MODE invalido: "${ownerEnforcementModeRaw}"`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const ownerLeaseMs = parseEnvInt(env.GROUP_OWNER_LEASE_MS, 120_000, 5_000, 15 * 60 * 1000);
|
|
211
|
+
let ownerHeartbeatMs = parseEnvInt(env.GROUP_OWNER_HEARTBEAT_MS, 30_000, 1_000, 5 * 60 * 1000);
|
|
212
|
+
if (ownerHeartbeatMs >= ownerLeaseMs) {
|
|
213
|
+
ownerHeartbeatMs = Math.max(1_000, Math.floor(ownerLeaseMs / 2));
|
|
214
|
+
warnings.push(`GROUP_OWNER_HEARTBEAT_MS ajustado automaticamente para ${ownerHeartbeatMs}ms (precisa ser menor que lease)`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const balancerEnabled = parseEnvBool(env.GROUP_BALANCER_ENABLED, false);
|
|
218
|
+
const sessionWeights = parseSessionWeights(env.BAILEYS_SESSION_WEIGHTS, sessionIds, warnings);
|
|
219
|
+
|
|
220
|
+
return Object.freeze({
|
|
221
|
+
sessionIds: Object.freeze([...sessionIds]),
|
|
222
|
+
primarySessionId,
|
|
223
|
+
sessionWeights: Object.freeze({ ...sessionWeights }),
|
|
224
|
+
ownerEnforcementMode,
|
|
225
|
+
ownerLeaseMs,
|
|
226
|
+
ownerHeartbeatMs,
|
|
227
|
+
balancerEnabled,
|
|
228
|
+
warnings: Object.freeze([...warnings]),
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Configuração resolvida uma única vez por processo.
|
|
234
|
+
* @type {MultiSessionRuntimeConfig}
|
|
235
|
+
*/
|
|
236
|
+
export const multiSessionRuntimeConfig = resolveMultiSessionRuntimeConfig();
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Retorna a configuração de runtime multi-sessão.
|
|
240
|
+
* @returns {MultiSessionRuntimeConfig}
|
|
241
|
+
*/
|
|
242
|
+
export const getMultiSessionRuntimeConfig = () => multiSessionRuntimeConfig;
|
|
@@ -6,12 +6,21 @@ import test from 'node:test';
|
|
|
6
6
|
|
|
7
7
|
import { initAuthCreds, proto } from '@whiskeysockets/baileys';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Referência do fork/branch do Baileys validada pelos testes.
|
|
11
|
+
* @type {string}
|
|
12
|
+
*/
|
|
13
|
+
const PINNED_BAILEYS_REF = 'github:jlucaso1/Baileys#feat-add-stickerpack-support';
|
|
10
14
|
|
|
11
15
|
const require = createRequire(import.meta.url);
|
|
12
16
|
const baileysPackageJsonPath = require.resolve('@whiskeysockets/baileys/package.json');
|
|
13
17
|
const baileysPackageDir = path.dirname(baileysPackageJsonPath);
|
|
14
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Lê um arquivo de tipos dentro do pacote instalado do Baileys.
|
|
21
|
+
* @param {string} relativePath
|
|
22
|
+
* @returns {Promise<string>}
|
|
23
|
+
*/
|
|
15
24
|
const readBaileysTypeFile = async (relativePath) => readFile(path.join(baileysPackageDir, relativePath), 'utf8');
|
|
16
25
|
|
|
17
26
|
test('Auth.d.ts expõe AuthenticationState compatível com SocketConfig.auth', async () => {
|