@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,8 +1,9 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import OpenAI from 'openai';
|
|
4
5
|
import { getAiHelpCachedResponse, upsertAiHelpCachedResponse } from './aiHelpResponseCacheRepository.js';
|
|
5
|
-
import { createGeminiTextService, DEFAULT_GEMINI_MODEL } from './geminiService.js';
|
|
6
|
+
import { createGeminiTextService, DEFAULT_GEMINI_MODEL, isGeminiAuthReady } from './geminiService.js';
|
|
6
7
|
|
|
7
8
|
const DEFAULT_FAQ_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
8
9
|
const DEFAULT_MAX_RESPONSE_CHARS = 3400;
|
|
@@ -168,6 +169,16 @@ const normalizeLlmProvider = (value, fallback = 'gemini') => {
|
|
|
168
169
|
return fallback;
|
|
169
170
|
};
|
|
170
171
|
|
|
172
|
+
const normalizeGeminiAuthMode = (value, fallback = 'cli') => {
|
|
173
|
+
const normalized = String(value || '')
|
|
174
|
+
.trim()
|
|
175
|
+
.toLowerCase();
|
|
176
|
+
if (normalized === 'api_key') return 'api_key';
|
|
177
|
+
if (normalized === 'cli') return 'cli';
|
|
178
|
+
if (normalized === 'auto') return 'auto';
|
|
179
|
+
return fallback;
|
|
180
|
+
};
|
|
181
|
+
|
|
171
182
|
const looksLikeGeminiModel = (value) =>
|
|
172
183
|
String(value || '')
|
|
173
184
|
.trim()
|
|
@@ -240,7 +251,13 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
240
251
|
const cachePathValue = String(faq.cache_file || '').trim();
|
|
241
252
|
const cachePath = cachePathValue ? path.resolve(process.cwd(), cachePathValue) : path.join(process.cwd(), 'data', 'cache', `${moduleKey}-ai-faq-cache.json`);
|
|
242
253
|
|
|
243
|
-
const
|
|
254
|
+
const geminiAuthMode = normalizeGeminiAuthMode(envValue('GEMINI_AUTH_MODE') || llm.gemini_auth_mode || process.env.GEMINI_AUTH_MODE, 'cli');
|
|
255
|
+
const hasGeminiAuthHint = isGeminiAuthReady({
|
|
256
|
+
authMode: geminiAuthMode,
|
|
257
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
258
|
+
cliCommand: process.env.GEMINI_CLI_COMMAND || 'gemini',
|
|
259
|
+
});
|
|
260
|
+
const provider = normalizeLlmProvider(envValue('PROVIDER') || llm.provider || process.env.AI_HELP_LLM_PROVIDER, hasGeminiAuthHint ? 'gemini' : 'openai');
|
|
244
261
|
const defaultModelByProvider = provider === 'gemini' ? process.env.GEMINI_MODEL || DEFAULT_GEMINI_MODEL : process.env.OPENAI_MODEL || DEFAULT_OPENAI_MODEL;
|
|
245
262
|
const rawModel = String(envValue('MODEL') || llm.model || defaultModelByProvider).trim() || defaultModelByProvider;
|
|
246
263
|
const modelFromEnv = String(envValue('MODEL') || '').trim();
|
|
@@ -270,6 +287,7 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
270
287
|
.trim()
|
|
271
288
|
.toLowerCase() !== 'false',
|
|
272
289
|
provider,
|
|
290
|
+
geminiAuthMode,
|
|
273
291
|
model: resolvedModel,
|
|
274
292
|
maxResponseChars: Math.max(400, toPositiveInt(envValue('MAX_RESPONSE_CHARS') || llm.max_response_chars, DEFAULT_MAX_RESPONSE_CHARS, 400)),
|
|
275
293
|
maxAgentContextChars: Math.max(2_000, toPositiveInt(envValue('MAX_AGENT_CONTEXT_CHARS') || llm.max_agent_context_chars, DEFAULT_MAX_AGENT_CONTEXT_CHARS, 2_000)),
|
|
@@ -285,6 +303,7 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
285
303
|
let faqGenerationPromise = null;
|
|
286
304
|
let cachedOpenAIClient = null;
|
|
287
305
|
let cachedGeminiService = null;
|
|
306
|
+
let cachedGeminiServiceKey = '';
|
|
288
307
|
|
|
289
308
|
const createEmptyCache = () => ({
|
|
290
309
|
version: FAQ_CACHE_VERSION,
|
|
@@ -420,13 +439,14 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
420
439
|
|
|
421
440
|
const readAgentExcerpt = async () => {
|
|
422
441
|
const { llm } = getAiHelpConfig();
|
|
442
|
+
const explicitExcerpt = clampText(process.env[`${envPrefix}_AGENT_EXCERPT`], llm.maxAgentContextChars);
|
|
443
|
+
if (explicitExcerpt) return explicitExcerpt;
|
|
423
444
|
if (!agentMdPath) return '';
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
445
|
+
|
|
446
|
+
const normalizedAgentPath = String(agentMdPath).trim();
|
|
447
|
+
if (!normalizedAgentPath) return '';
|
|
448
|
+
const safeAgentName = path.basename(normalizedAgentPath);
|
|
449
|
+
return clampText(`Considere as orientacoes locais do arquivo ${safeAgentName}.`, llm.maxAgentContextChars);
|
|
430
450
|
};
|
|
431
451
|
|
|
432
452
|
const summarizeConfigForPrompt = () => {
|
|
@@ -439,7 +459,14 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
439
459
|
.join('\n');
|
|
440
460
|
};
|
|
441
461
|
|
|
442
|
-
const isGeminiReady = () =>
|
|
462
|
+
const isGeminiReady = () => {
|
|
463
|
+
const config = getAiHelpConfig();
|
|
464
|
+
return isGeminiAuthReady({
|
|
465
|
+
authMode: config.llm.geminiAuthMode,
|
|
466
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
467
|
+
cliCommand: process.env.GEMINI_CLI_COMMAND || 'gemini',
|
|
468
|
+
});
|
|
469
|
+
};
|
|
443
470
|
const isOpenAIReady = () => Boolean(String(process.env.OPENAI_API_KEY || '').trim());
|
|
444
471
|
const isProviderReady = (provider) => (provider === 'gemini' ? isGeminiReady() : isOpenAIReady());
|
|
445
472
|
|
|
@@ -451,12 +478,16 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
451
478
|
const getGeminiService = () => {
|
|
452
479
|
if (!isGeminiReady()) return null;
|
|
453
480
|
const config = getAiHelpConfig();
|
|
454
|
-
|
|
481
|
+
const currentServiceKey = `${config.llm.model}|${config.llm.timeoutMs}|${config.llm.geminiAuthMode}|${process.env.GEMINI_CLI_COMMAND || 'gemini'}|${Boolean(String(process.env.GEMINI_API_KEY || '').trim())}`;
|
|
482
|
+
if (!cachedGeminiService || cachedGeminiServiceKey !== currentServiceKey) {
|
|
455
483
|
cachedGeminiService = createGeminiTextService({
|
|
456
484
|
apiKey: process.env.GEMINI_API_KEY,
|
|
457
485
|
defaultModel: config.llm.model || process.env.GEMINI_MODEL || DEFAULT_GEMINI_MODEL,
|
|
458
486
|
timeoutMs: config.llm.timeoutMs,
|
|
487
|
+
authMode: config.llm.geminiAuthMode,
|
|
488
|
+
cliCommand: process.env.GEMINI_CLI_COMMAND || 'gemini',
|
|
459
489
|
});
|
|
490
|
+
cachedGeminiServiceKey = currentServiceKey;
|
|
460
491
|
}
|
|
461
492
|
return cachedGeminiService;
|
|
462
493
|
};
|
|
@@ -578,7 +609,7 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
578
609
|
...cache.metrics,
|
|
579
610
|
[metricKey]: Number(cache.metrics?.[metricKey] || 0) + 1,
|
|
580
611
|
};
|
|
581
|
-
cache.updatedAt =
|
|
612
|
+
cache.updatedAt = __timeNowIso();
|
|
582
613
|
await writeCache(cache);
|
|
583
614
|
};
|
|
584
615
|
|
|
@@ -590,7 +621,7 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
590
621
|
const normalizedSource = normalizeCacheSource(source);
|
|
591
622
|
const normalizedAnswer = clampText(answer, config.llm.maxResponseChars);
|
|
592
623
|
if (!normalizedAnswer) return;
|
|
593
|
-
const now =
|
|
624
|
+
const now = __timeNowIso();
|
|
594
625
|
|
|
595
626
|
const cache = await readCache();
|
|
596
627
|
cache.questionCache[key] = {
|
|
@@ -738,10 +769,10 @@ export const createModuleAiHelpService = ({ moduleKey, moduleLabel = 'modulo', e
|
|
|
738
769
|
}
|
|
739
770
|
|
|
740
771
|
const cache = await readCache();
|
|
741
|
-
const now =
|
|
772
|
+
const now = __timeNowIso();
|
|
742
773
|
|
|
743
774
|
if (!force && cache.generatedAt) {
|
|
744
|
-
const ageMs =
|
|
775
|
+
const ageMs = __timeNowMs() - new Date(cache.generatedAt).getTime();
|
|
745
776
|
if (Number.isFinite(ageMs) && ageMs >= 0 && ageMs < config.faq.intervalMs) {
|
|
746
777
|
const commandCount = Object.keys(cache.faqByCommand || {}).length;
|
|
747
778
|
const faqCount = Object.values(cache.faqByCommand || {}).reduce((acc, list) => acc + (Array.isArray(list) ? list.length : 0), 0);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import logger from '#logger';
|
|
2
3
|
import { getToolRecord } from './moduleToolRegistryService.js';
|
|
3
4
|
import { mapToolArgsToCommandText } from './commandToolBuilderService.js';
|
|
@@ -388,7 +389,7 @@ export const executeTool = async (toolName, toolArgs, context = {}) => {
|
|
|
388
389
|
}
|
|
389
390
|
|
|
390
391
|
const mapped = mapToolArgsToCommandText(record.argumentSpecs, argsValidation.normalizedArgs);
|
|
391
|
-
const startedAt =
|
|
392
|
+
const startedAt = __timeNowMs();
|
|
392
393
|
|
|
393
394
|
let executionResult = null;
|
|
394
395
|
try {
|
|
@@ -409,7 +410,7 @@ export const executeTool = async (toolName, toolArgs, context = {}) => {
|
|
|
409
410
|
};
|
|
410
411
|
}
|
|
411
412
|
|
|
412
|
-
const executionTimeMs =
|
|
413
|
+
const executionTimeMs = __timeNowMs() - startedAt;
|
|
413
414
|
logger.info('Execucao de tool AI concluida.', {
|
|
414
415
|
action: 'ai_tool_execution',
|
|
415
416
|
tool_used: record.toolName,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import logger from '#logger';
|
|
@@ -126,7 +127,7 @@ const buildRegistrySnapshot = () => {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
cachedRegistry = {
|
|
129
|
-
builtAt:
|
|
130
|
+
builtAt: __timeNowIso(),
|
|
130
131
|
signature,
|
|
131
132
|
records: records.sort((a, b) => a.toolName.localeCompare(b.toolName)),
|
|
132
133
|
toolNameToRecord,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import natural from 'natural';
|
|
2
3
|
import winkBm25TextSearch from 'wink-bm25-text-search';
|
|
3
4
|
import logger from '#logger';
|
|
@@ -193,7 +194,7 @@ const ensureCacheSize = () => {
|
|
|
193
194
|
}
|
|
194
195
|
};
|
|
195
196
|
|
|
196
|
-
const pruneCache = (nowMs =
|
|
197
|
+
const pruneCache = (nowMs = __timeNowMs()) => {
|
|
197
198
|
for (const [cacheKey, cacheEntry] of queryCache.entries()) {
|
|
198
199
|
if (!cacheEntry || cacheEntry.expiresAt <= nowMs) {
|
|
199
200
|
queryCache.delete(cacheKey);
|
|
@@ -290,7 +291,7 @@ const buildOrGetIndexSnapshot = () => {
|
|
|
290
291
|
|
|
291
292
|
cachedIndexSnapshot = {
|
|
292
293
|
signature,
|
|
293
|
-
builtAt:
|
|
294
|
+
builtAt: __timeNowIso(),
|
|
294
295
|
bm25Ready,
|
|
295
296
|
bm25Engine,
|
|
296
297
|
entries,
|
|
@@ -445,7 +446,7 @@ const buildCommandConfigOverlayMaps = (states = []) => {
|
|
|
445
446
|
};
|
|
446
447
|
|
|
447
448
|
const refreshLearnedKnowledgeCache = async ({ force = false } = {}) => {
|
|
448
|
-
const nowMs =
|
|
449
|
+
const nowMs = __timeNowMs();
|
|
449
450
|
if (!force && learnedKnowledgeCache.loaded && learnedKnowledgeCache.nextRefreshAt > nowMs && learnedKnowledgeCache.version) {
|
|
450
451
|
return learnedKnowledgeCache;
|
|
451
452
|
}
|
|
@@ -498,7 +499,7 @@ const refreshLearnedKnowledgeCache = async ({ force = false } = {}) => {
|
|
|
498
499
|
};
|
|
499
500
|
|
|
500
501
|
const refreshCommandConfigEnrichmentCache = async ({ force = false } = {}) => {
|
|
501
|
-
const nowMs =
|
|
502
|
+
const nowMs = __timeNowMs();
|
|
502
503
|
if (!force && commandConfigEnrichmentCache.loaded && commandConfigEnrichmentCache.nextRefreshAt > nowMs && commandConfigEnrichmentCache.version) {
|
|
503
504
|
return commandConfigEnrichmentCache;
|
|
504
505
|
}
|
|
@@ -593,7 +594,7 @@ export const selectCandidateTools = async (userMessage, limit = TOOL_SELECTION_M
|
|
|
593
594
|
}
|
|
594
595
|
|
|
595
596
|
const normalizedMessage = normalizeText(userMessage);
|
|
596
|
-
const nowMs =
|
|
597
|
+
const nowMs = __timeNowMs();
|
|
597
598
|
pruneCache(nowMs);
|
|
598
599
|
const cacheKey = buildCacheKey({
|
|
599
600
|
message: normalizedMessage,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
3
|
import { normalizeJid } from '../../config/index.js';
|
|
3
4
|
import { toWhatsAppPhoneDigits } from './whatsappLoginLinkService.js';
|
|
@@ -21,7 +22,7 @@ const normalizeCacheKey = ({ ownerJid = '', ownerPhone = '' }) => {
|
|
|
21
22
|
const getCachedGoogleLinkStatus = (cacheKey) => {
|
|
22
23
|
const cached = googleLinkCheckCache.get(cacheKey);
|
|
23
24
|
if (!cached) return null;
|
|
24
|
-
if (Number(cached.expiresAt || 0) <=
|
|
25
|
+
if (Number(cached.expiresAt || 0) <= __timeNowMs()) {
|
|
25
26
|
googleLinkCheckCache.delete(cacheKey);
|
|
26
27
|
return null;
|
|
27
28
|
}
|
|
@@ -31,7 +32,7 @@ const getCachedGoogleLinkStatus = (cacheKey) => {
|
|
|
31
32
|
const setCachedGoogleLinkStatus = (cacheKey, linked) => {
|
|
32
33
|
googleLinkCheckCache.set(cacheKey, {
|
|
33
34
|
linked: Boolean(linked),
|
|
34
|
-
expiresAt:
|
|
35
|
+
expiresAt: __timeNowMs() + GOOGLE_LINK_CHECK_CACHE_TTL_MS,
|
|
35
36
|
});
|
|
36
37
|
};
|
|
37
38
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
3
|
import { URL } from 'node:url';
|
|
3
4
|
import { getJidServer, getJidUser, normalizeJid } from '../../config/index.js';
|
|
@@ -103,7 +104,7 @@ export const toWhatsAppOwnerJid = (value) => {
|
|
|
103
104
|
return normalizeJid(`${digits}@s.whatsapp.net`) || '';
|
|
104
105
|
};
|
|
105
106
|
|
|
106
|
-
export const buildWhatsAppLoginHint = (value, { nowMs =
|
|
107
|
+
export const buildWhatsAppLoginHint = (value, { nowMs = __timeNowMs() } = {}) => {
|
|
107
108
|
const phoneDigits = toWhatsAppPhoneDigits(value);
|
|
108
109
|
if (!phoneDigits) return null;
|
|
109
110
|
|
|
@@ -141,7 +142,7 @@ export const extractWhatsAppLoginHint = (payload = {}) => {
|
|
|
141
142
|
};
|
|
142
143
|
};
|
|
143
144
|
|
|
144
|
-
export const resolveWhatsAppOwnerJidFromLoginPayload = (payload, { nowMs =
|
|
145
|
+
export const resolveWhatsAppOwnerJidFromLoginPayload = (payload, { nowMs = __timeNowMs() } = {}) => {
|
|
145
146
|
const hint = extractWhatsAppLoginHint(payload);
|
|
146
147
|
const hasPayload = Boolean(hint.wa || hint.wa_ts || hint.wa_sig);
|
|
147
148
|
if (!hasPayload) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import axios from 'axios';
|
|
2
3
|
import logger from '#logger';
|
|
3
4
|
import { recordPokeApiCacheHit } from '../../observability/metrics.js';
|
|
@@ -9,7 +10,7 @@ const REQUEST_TIMEOUT_MS = Math.max(3_000, Number(process.env.POKEAPI_TIMEOUT_MS
|
|
|
9
10
|
const REQUEST_RETRY_ATTEMPTS = Math.max(0, Number(process.env.POKEAPI_RETRY_ATTEMPTS) || 2);
|
|
10
11
|
const REQUEST_RETRY_BASE_DELAY_MS = Math.max(120, Number(process.env.POKEAPI_RETRY_BASE_DELAY_MS) || 350);
|
|
11
12
|
const DNS_FAMILY = [0, 4, 6].includes(Number(process.env.POKEAPI_DNS_FAMILY)) ? Number(process.env.POKEAPI_DNS_FAMILY) : 4;
|
|
12
|
-
const REQUEST_USER_AGENT = String(process.env.POKEAPI_USER_AGENT || 'omnizap
|
|
13
|
+
const REQUEST_USER_AGENT = String(process.env.POKEAPI_USER_AGENT || 'omnizap/2.1 (+https://github.com/Omnizap-System/omnizap)').trim();
|
|
13
14
|
const DEFAULT_LORE_LANGUAGES = String(process.env.POKEAPI_LORE_LANGS || 'pt-br,pt,en')
|
|
14
15
|
.split(',')
|
|
15
16
|
.map((entry) =>
|
|
@@ -177,7 +178,7 @@ const cleanupExpiredEntry = (key, now) => {
|
|
|
177
178
|
};
|
|
178
179
|
|
|
179
180
|
const requestResource = async ({ path, cacheKey }) => {
|
|
180
|
-
const now =
|
|
181
|
+
const now = __timeNowMs();
|
|
181
182
|
const staleEntry = sharedCache.get(cacheKey);
|
|
182
183
|
const staleData = staleEntry?.data || null;
|
|
183
184
|
const cached = cleanupExpiredEntry(cacheKey, now);
|
|
@@ -207,7 +208,7 @@ const requestResource = async ({ path, cacheKey }) => {
|
|
|
207
208
|
const data = response?.data;
|
|
208
209
|
sharedCache.set(cacheKey, {
|
|
209
210
|
data,
|
|
210
|
-
expiresAt:
|
|
211
|
+
expiresAt: __timeNowMs() + CACHE_TTL_MS,
|
|
211
212
|
});
|
|
212
213
|
return data;
|
|
213
214
|
} catch (error) {
|
|
@@ -2,7 +2,7 @@ import logger from '#logger';
|
|
|
2
2
|
import { findById, upsert } from '../../../database/index.js';
|
|
3
3
|
import { extractUserIdInfo, resolveUserIdCached, isLidUserId, isWhatsAppUserId } from '../../config/index.js';
|
|
4
4
|
|
|
5
|
-
const GROUP_METADATA_FIELDS = ['id', 'subject', 'description', 'owner_jid', 'creation', 'participants'];
|
|
5
|
+
const GROUP_METADATA_FIELDS = ['id', 'subject', 'description', 'owner_jid', 'creation', 'participants', 'linked_parent_jid', 'is_community', 'is_community_announce', 'member_add_mode', 'join_approval_mode', 'addressing_mode'];
|
|
6
6
|
|
|
7
7
|
const PARTICIPANT_ACTIONS = new Set(['add', 'remove', 'promote', 'demote', 'modify']);
|
|
8
8
|
|
|
@@ -285,6 +285,17 @@ export const buildGroupMetadataFromUpdate = (event, existing) => {
|
|
|
285
285
|
const currentParticipants = parseParticipantsFromDb(existing?.participants);
|
|
286
286
|
const participants = event.participants || currentParticipants;
|
|
287
287
|
const normalizedParticipants = normalizeParticipantsList(participants);
|
|
288
|
+
const resolveExistingValue = (snakeKey, camelKey) => existing?.[snakeKey] ?? existing?.[camelKey];
|
|
289
|
+
const resolveBoolean = (eventValue, snakeKey, camelKey) => {
|
|
290
|
+
if (eventValue !== undefined) return Boolean(eventValue);
|
|
291
|
+
const existingValue = resolveExistingValue(snakeKey, camelKey);
|
|
292
|
+
return existingValue === undefined || existingValue === null ? null : Boolean(existingValue);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const linkedParentRaw = event.linkedParent ?? resolveExistingValue('linked_parent_jid', 'linkedParent');
|
|
296
|
+
const linkedParent = typeof linkedParentRaw === 'string' ? linkedParentRaw.trim() || null : linkedParentRaw || null;
|
|
297
|
+
const addressingModeRaw = event.addressingMode ?? resolveExistingValue('addressing_mode', 'addressingMode');
|
|
298
|
+
const addressingMode = typeof addressingModeRaw === 'string' ? addressingModeRaw.trim() || null : addressingModeRaw || null;
|
|
288
299
|
|
|
289
300
|
return {
|
|
290
301
|
id: event.id,
|
|
@@ -293,6 +304,12 @@ export const buildGroupMetadataFromUpdate = (event, existing) => {
|
|
|
293
304
|
owner_jid: event.owner ?? existing?.owner_jid,
|
|
294
305
|
creation: event.creation ?? existing?.creation,
|
|
295
306
|
participants: normalizedParticipants,
|
|
307
|
+
linked_parent_jid: linkedParent,
|
|
308
|
+
is_community: resolveBoolean(event.isCommunity, 'is_community', 'isCommunity'),
|
|
309
|
+
is_community_announce: resolveBoolean(event.isCommunityAnnounce, 'is_community_announce', 'isCommunityAnnounce'),
|
|
310
|
+
member_add_mode: resolveBoolean(event.memberAddMode, 'member_add_mode', 'memberAddMode'),
|
|
311
|
+
join_approval_mode: resolveBoolean(event.joinApprovalMode, 'join_approval_mode', 'joinApprovalMode'),
|
|
312
|
+
addressing_mode: addressingMode,
|
|
296
313
|
};
|
|
297
314
|
};
|
|
298
315
|
|
|
@@ -308,4 +325,10 @@ export const buildGroupMetadataFromGroup = (group) => ({
|
|
|
308
325
|
owner_jid: group.owner,
|
|
309
326
|
creation: group.creation,
|
|
310
327
|
participants: normalizeParticipantsList(group.participants || []),
|
|
328
|
+
linked_parent_jid: typeof group.linkedParent === 'string' ? group.linkedParent.trim() || null : group.linkedParent || null,
|
|
329
|
+
is_community: group.isCommunity === undefined ? null : Boolean(group.isCommunity),
|
|
330
|
+
is_community_announce: group.isCommunityAnnounce === undefined ? null : Boolean(group.isCommunityAnnounce),
|
|
331
|
+
member_add_mode: group.memberAddMode === undefined ? null : Boolean(group.memberAddMode),
|
|
332
|
+
join_approval_mode: group.joinApprovalMode === undefined ? null : Boolean(group.joinApprovalMode),
|
|
333
|
+
addressing_mode: typeof group.addressingMode === 'string' ? group.addressingMode.trim() || null : group.addressingMode || null,
|
|
311
334
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import logger from '#logger';
|
|
2
3
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
3
4
|
import { queueLidUpdate, flushLidQueue, resolveUserIdCached } from '../../config/index.js';
|
|
@@ -104,7 +105,7 @@ const INVALID_SURROGATE_REGEX = /surrogate pair/i;
|
|
|
104
105
|
const messageQueue = [];
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
|
-
* Conjunto de IDs de mensagens
|
|
108
|
+
* Conjunto de IDs de mensagens já enfileiradas no formato `${session_id}:${message_id}`.
|
|
108
109
|
* @type {Set<string>}
|
|
109
110
|
*/
|
|
110
111
|
const messagePendingIds = new Set();
|
|
@@ -131,7 +132,7 @@ const chatCache = new Map();
|
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
134
|
* Fila em memória com eventos do Baileys pendentes de persistência.
|
|
134
|
-
* @type {Array<{event_name:string, socket_generation:(number|null), chat_id:(string|null), message_id:(string|null), participant_id:(string|null), payload_summary:(string|null), event_timestamp:Date}>}
|
|
135
|
+
* @type {Array<{session_id:string, event_name:string, socket_generation:(number|null), chat_id:(string|null), message_id:(string|null), participant_id:(string|null), payload_summary:(string|null), event_timestamp:Date}>}
|
|
135
136
|
*/
|
|
136
137
|
const baileysEventQueue = [];
|
|
137
138
|
|
|
@@ -250,8 +251,8 @@ const isInvalidJsonPayloadError = (error) => {
|
|
|
250
251
|
* - content: remove surrogate inválido
|
|
251
252
|
* - raw_message: serializa JSON seguro para coluna JSON
|
|
252
253
|
*
|
|
253
|
-
* @param {{message_id:string, chat_id:string, sender_id:string, canonical_sender_id?:(string|null), content:(string|null), raw_message:(Object|string|null), timestamp:(number|string|Date)}} messageData
|
|
254
|
-
* @returns {{message_id:string, chat_id:string, sender_id:(string|null), canonical_sender_id:(string|null), content:(string|null), raw_message:(string|null), timestamp:(number|string|Date)}}
|
|
254
|
+
* @param {{session_id?:(string|null), message_id:string, chat_id:string, sender_id:string, canonical_sender_id?:(string|null), content:(string|null), raw_message:(Object|string|null), timestamp:(number|string|Date), allow_group_write?:(boolean)}} messageData
|
|
255
|
+
* @returns {{session_id:string, message_id:string, chat_id:(string|null), sender_id:(string|null), canonical_sender_id:(string|null), content:(string|null), raw_message:(string|null), timestamp:(number|string|Date)}}
|
|
255
256
|
*/
|
|
256
257
|
const normalizeUserIdForColumn = (value, maxLength = 255) => {
|
|
257
258
|
if (value === null || value === undefined) return null;
|
|
@@ -276,8 +277,13 @@ const resolveCanonicalSenderIdForMessage = (messageData) => {
|
|
|
276
277
|
|
|
277
278
|
const normalizeMessageForQueue = (messageData) => {
|
|
278
279
|
const senderId = normalizeUserIdForColumn(messageData?.sender_id, 255);
|
|
280
|
+
const messageId = normalizeTextForColumn(messageData?.message_id, 255);
|
|
281
|
+
const chatId = normalizeTextForColumn(messageData?.chat_id, 255);
|
|
279
282
|
return {
|
|
280
283
|
...messageData,
|
|
284
|
+
session_id: normalizeSessionIdForColumn(messageData?.session_id || messageData?.sessionId),
|
|
285
|
+
message_id: messageId,
|
|
286
|
+
chat_id: chatId,
|
|
281
287
|
sender_id: senderId,
|
|
282
288
|
canonical_sender_id: resolveCanonicalSenderIdForMessage({
|
|
283
289
|
...messageData,
|
|
@@ -298,10 +304,28 @@ const normalizeTimestampForColumn = (value) => {
|
|
|
298
304
|
if (value instanceof Date && Number.isFinite(value.getTime())) return value;
|
|
299
305
|
const parsed = new Date(value);
|
|
300
306
|
if (Number.isFinite(parsed.getTime())) return parsed;
|
|
301
|
-
return
|
|
307
|
+
return __timeNow();
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const normalizeSessionIdForColumn = (value) => {
|
|
311
|
+
const normalized = normalizeTextForColumn(value, 64);
|
|
312
|
+
return normalized || 'default';
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const isGroupChatId = (chatId) => {
|
|
316
|
+
const normalized = String(chatId || '').trim();
|
|
317
|
+
return normalized.endsWith('@g.us');
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const buildMessagePendingKey = (sessionId, messageId) => {
|
|
321
|
+
const safeSessionId = normalizeSessionIdForColumn(sessionId);
|
|
322
|
+
const safeMessageId = normalizeTextForColumn(messageId, 255);
|
|
323
|
+
if (!safeMessageId) return null;
|
|
324
|
+
return `${safeSessionId}:${safeMessageId}`;
|
|
302
325
|
};
|
|
303
326
|
|
|
304
327
|
const normalizeBaileysEventForQueue = (eventData) => ({
|
|
328
|
+
session_id: normalizeSessionIdForColumn(eventData?.session_id || eventData?.sessionId),
|
|
305
329
|
event_name: normalizeTextForColumn(eventData?.event_name, 64),
|
|
306
330
|
socket_generation: Number.isFinite(Number(eventData?.socket_generation)) ? Math.max(0, Math.floor(Number(eventData.socket_generation))) : null,
|
|
307
331
|
chat_id: normalizeTextForColumn(eventData?.chat_id, 255),
|
|
@@ -312,14 +336,14 @@ const normalizeBaileysEventForQueue = (eventData) => ({
|
|
|
312
336
|
});
|
|
313
337
|
|
|
314
338
|
const insertBaileysEventBatch = async (batch) => {
|
|
315
|
-
const placeholders = buildPlaceholders(batch.length,
|
|
339
|
+
const placeholders = buildPlaceholders(batch.length, 8);
|
|
316
340
|
const params = [];
|
|
317
341
|
for (const entry of batch) {
|
|
318
|
-
params.push(entry.event_name, entry.socket_generation, entry.chat_id, entry.message_id, entry.participant_id, entry.payload_summary, entry.event_timestamp);
|
|
342
|
+
params.push(entry.session_id, entry.event_name, entry.socket_generation, entry.chat_id, entry.message_id, entry.participant_id, entry.payload_summary, entry.event_timestamp);
|
|
319
343
|
}
|
|
320
344
|
|
|
321
345
|
const sql = `INSERT INTO ${TABLES.BAILEYS_EVENT_JOURNAL}
|
|
322
|
-
(event_name, socket_generation, chat_id, message_id, participant_id, payload_summary, event_timestamp)
|
|
346
|
+
(session_id, event_name, socket_generation, chat_id, message_id, participant_id, payload_summary, event_timestamp)
|
|
323
347
|
VALUES ${placeholders}`;
|
|
324
348
|
|
|
325
349
|
await executeQuery(sql, params);
|
|
@@ -328,18 +352,18 @@ const insertBaileysEventBatch = async (batch) => {
|
|
|
328
352
|
/**
|
|
329
353
|
* Executa INSERT IGNORE de um batch de mensagens.
|
|
330
354
|
*
|
|
331
|
-
* @param {Array<{message_id:string, chat_id:string, sender_id:(string|null), canonical_sender_id:(string|null), content:(string|null), raw_message:(string|null), timestamp:(number|string|Date)}>} batch
|
|
355
|
+
* @param {Array<{session_id:string, message_id:string, chat_id:string, sender_id:(string|null), canonical_sender_id:(string|null), content:(string|null), raw_message:(string|null), timestamp:(number|string|Date)}>} batch
|
|
332
356
|
* @returns {Promise<void>}
|
|
333
357
|
*/
|
|
334
358
|
const insertMessageBatch = async (batch) => {
|
|
335
|
-
const placeholders = buildPlaceholders(batch.length,
|
|
359
|
+
const placeholders = buildPlaceholders(batch.length, 8);
|
|
336
360
|
const params = [];
|
|
337
361
|
for (const message of batch) {
|
|
338
|
-
params.push(message.message_id, message.chat_id, message.sender_id, message.canonical_sender_id, message.content, message.raw_message, message.timestamp);
|
|
362
|
+
params.push(message.session_id, message.message_id, message.chat_id, message.sender_id, message.canonical_sender_id, message.content, message.raw_message, message.timestamp);
|
|
339
363
|
}
|
|
340
364
|
|
|
341
365
|
const sql = `INSERT IGNORE INTO ${TABLES.MESSAGES}
|
|
342
|
-
(message_id, chat_id, sender_id, canonical_sender_id, content, raw_message, timestamp)
|
|
366
|
+
(session_id, message_id, chat_id, sender_id, canonical_sender_id, content, raw_message, timestamp)
|
|
343
367
|
VALUES ${placeholders}`;
|
|
344
368
|
|
|
345
369
|
await executeQuery(sql, params);
|
|
@@ -423,12 +447,13 @@ const refreshMessageActivityDailyForBatch = async (batch) => {
|
|
|
423
447
|
/**
|
|
424
448
|
* Remove IDs de mensagens do set de pendentes.
|
|
425
449
|
*
|
|
426
|
-
* @param {Array<{message_id:string}>} batch
|
|
450
|
+
* @param {Array<{session_id:string, message_id:string}>} batch
|
|
427
451
|
* @returns {void}
|
|
428
452
|
*/
|
|
429
453
|
const clearPendingMessageIds = (batch) => {
|
|
430
454
|
for (const message of batch) {
|
|
431
|
-
|
|
455
|
+
const key = buildMessagePendingKey(message?.session_id, message?.message_id);
|
|
456
|
+
if (key) messagePendingIds.delete(key);
|
|
432
457
|
}
|
|
433
458
|
};
|
|
434
459
|
|
|
@@ -437,7 +462,7 @@ const clearPendingMessageIds = (batch) => {
|
|
|
437
462
|
* - Mensagem inválida é descartada para não travar a fila inteira.
|
|
438
463
|
* - Em erro transitório, re-enfileira o restante e interrompe.
|
|
439
464
|
*
|
|
440
|
-
* @param {Array<{message_id:string, chat_id:string, sender_id:(string|null), canonical_sender_id:(string|null), content:(string|null), raw_message:(string|null), timestamp:(number|string|Date)}>} batch
|
|
465
|
+
* @param {Array<{session_id:string, message_id:string, chat_id:string, sender_id:(string|null), canonical_sender_id:(string|null), content:(string|null), raw_message:(string|null), timestamp:(number|string|Date)}>} batch
|
|
441
466
|
* @returns {Promise<void>}
|
|
442
467
|
*/
|
|
443
468
|
const salvageJsonErrorBatch = async (batch) => {
|
|
@@ -513,7 +538,7 @@ const flushMessageQueueCore = async () => {
|
|
|
513
538
|
|
|
514
539
|
const flushChatQueueCore = async () => {
|
|
515
540
|
while (chatQueue.size > 0) {
|
|
516
|
-
const now =
|
|
541
|
+
const now = __timeNowMs();
|
|
517
542
|
const ready = [];
|
|
518
543
|
for (const entry of chatQueue.values()) {
|
|
519
544
|
if (now < entry.nextAllowedAt) continue;
|
|
@@ -539,7 +564,7 @@ const flushChatQueueCore = async () => {
|
|
|
539
564
|
|
|
540
565
|
try {
|
|
541
566
|
await executeQuery(sql, params);
|
|
542
|
-
const writeAt =
|
|
567
|
+
const writeAt = __timeNowMs();
|
|
543
568
|
for (const entry of ready) {
|
|
544
569
|
const current = chatQueue.get(entry.id);
|
|
545
570
|
const cache = chatCache.get(entry.id) || {};
|
|
@@ -576,7 +601,7 @@ const flushChatQueueCore = async () => {
|
|
|
576
601
|
const pruneBaileysEventJournal = async () => {
|
|
577
602
|
if (BAILEYS_EVENT_JOURNAL_RETENTION_DAYS <= 0) return;
|
|
578
603
|
|
|
579
|
-
const now =
|
|
604
|
+
const now = __timeNowMs();
|
|
580
605
|
if (now < nextBaileysEventPruneAt) return;
|
|
581
606
|
nextBaileysEventPruneAt = now + BAILEYS_EVENT_JOURNAL_PRUNE_INTERVAL_MS;
|
|
582
607
|
|
|
@@ -678,26 +703,32 @@ const baileysEventFlushRunner = createFlushRunner({
|
|
|
678
703
|
|
|
679
704
|
/**
|
|
680
705
|
* Enfileira uma mensagem para INSERT no banco (INSERT IGNORE).
|
|
681
|
-
* - Evita duplicar message_id usando um Set.
|
|
706
|
+
* - Evita duplicar por `${session_id}:${message_id}` usando um Set.
|
|
682
707
|
* - Força flush se a fila estiver muito grande.
|
|
683
708
|
* - Agenda flush quando atinge o tamanho de batch.
|
|
684
709
|
*
|
|
685
|
-
* @param {{message_id:string, chat_id:string, sender_id:string, canonical_sender_id?:(string|null), content:(string|null), raw_message:(Object|string|null), timestamp:(number|string)}} messageData
|
|
710
|
+
* @param {{session_id?:(string|null), message_id:string, chat_id:string, sender_id:string, canonical_sender_id?:(string|null), content:(string|null), raw_message:(Object|string|null), timestamp:(number|string), allow_group_write?:(boolean)}} messageData
|
|
686
711
|
* Objeto com os campos necessários para persistência.
|
|
687
712
|
* @returns {boolean} true se foi enfileirada; false se inválida/duplicada.
|
|
688
713
|
*/
|
|
689
714
|
export function queueMessageInsert(messageData) {
|
|
690
|
-
if (!messageData?.message_id) return false;
|
|
691
|
-
if (messagePendingIds.has(messageData.message_id)) return false;
|
|
692
|
-
|
|
693
715
|
const normalizedMessage = normalizeMessageForQueue(messageData);
|
|
716
|
+
if (!normalizedMessage?.message_id) return false;
|
|
717
|
+
|
|
718
|
+
if (isGroupChatId(normalizedMessage.chat_id) && normalizedMessage.allow_group_write === false) {
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const pendingKey = buildMessagePendingKey(normalizedMessage.session_id, normalizedMessage.message_id);
|
|
723
|
+
if (!pendingKey) return false;
|
|
724
|
+
if (messagePendingIds.has(pendingKey)) return false;
|
|
694
725
|
|
|
695
726
|
if (messageQueue.length >= MESSAGE_QUEUE_MAX) {
|
|
696
727
|
logger.warn('Fila de mensagens cheia, forçando flush.', { size: messageQueue.length });
|
|
697
728
|
scheduleFlush();
|
|
698
729
|
}
|
|
699
730
|
|
|
700
|
-
messagePendingIds.add(
|
|
731
|
+
messagePendingIds.add(pendingKey);
|
|
701
732
|
messageQueue.push(normalizedMessage);
|
|
702
733
|
updateQueueMetrics();
|
|
703
734
|
|
|
@@ -725,7 +756,7 @@ export function queueMessageInsert(messageData) {
|
|
|
725
756
|
export function queueChatUpdate(chat, options = {}) {
|
|
726
757
|
if (!chat || !chat.id) return false;
|
|
727
758
|
|
|
728
|
-
const now =
|
|
759
|
+
const now = __timeNowMs();
|
|
729
760
|
const isPartial = Boolean(options.partial);
|
|
730
761
|
const forceName = Boolean(options.forceName);
|
|
731
762
|
const cache = chatCache.get(chat.id) || {
|
|
@@ -781,7 +812,7 @@ export function queueChatUpdate(chat, options = {}) {
|
|
|
781
812
|
/**
|
|
782
813
|
* Enfileira um evento resumido do Baileys para o journal de auditoria.
|
|
783
814
|
*
|
|
784
|
-
* @param {{event_name:string, socket_generation?:(number|null), chat_id?:(string|null), message_id?:(string|null), participant_id?:(string|null), payload_summary?:any, event_timestamp?:(string|number|Date)}} eventData
|
|
815
|
+
* @param {{session_id?:(string|null), event_name:string, socket_generation?:(number|null), chat_id?:(string|null), message_id?:(string|null), participant_id?:(string|null), payload_summary?:any, event_timestamp?:(string|number|Date)}} eventData
|
|
785
816
|
* @returns {boolean}
|
|
786
817
|
*/
|
|
787
818
|
export function queueBaileysEventInsert(eventData) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
|
|
3
4
|
import logger from '#logger';
|
|
@@ -65,7 +66,7 @@ const loadFlagsFromDatabase = async () => {
|
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
export const refreshFeatureFlags = async ({ force = false } = {}) => {
|
|
68
|
-
const now =
|
|
69
|
+
const now = __timeNowMs();
|
|
69
70
|
const isFresh = now - cacheState.loadedAt < FEATURE_FLAG_CACHE_TTL_MS;
|
|
70
71
|
if (!force && isFresh) return cacheState.byName;
|
|
71
72
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import logger from '#logger';
|
|
2
3
|
import { getJidUser } from '../../config/index.js';
|
|
3
4
|
import { isUserAdmin, updateGroupParticipants } from '../../config/index.js';
|
|
@@ -200,7 +201,7 @@ const handleCaptchaTimeout = async (groupId, userId) => {
|
|
|
200
201
|
const entry = groupMap?.get(userId);
|
|
201
202
|
if (!entry) return;
|
|
202
203
|
|
|
203
|
-
if (
|
|
204
|
+
if (__timeNowMs() < entry.expiresAt) {
|
|
204
205
|
return;
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -377,7 +378,7 @@ export const registerCaptchaChallenge = ({ groupId, participantJid, messageKey,
|
|
|
377
378
|
cleanupMessageStateForEntry(existingMatch.entry);
|
|
378
379
|
}
|
|
379
380
|
|
|
380
|
-
const expiresAt =
|
|
381
|
+
const expiresAt = __timeNowMs() + CAPTCHA_TIMEOUT_MS;
|
|
381
382
|
const messageId = messageKey?.id || null;
|
|
382
383
|
const normalizedText = normalizeMessageText(messageText);
|
|
383
384
|
const messageStateKey = messageId && normalizedText ? buildMessageStateKey(groupId, messageId) : null;
|