@omnizap-system/omnizap 2.5.12
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/.clusterfuzzlite/Dockerfile +10 -0
- package/.env.example +907 -0
- package/.github/codeql/codeql-config.yml +10 -0
- package/.github/dependabot.yml +35 -0
- package/.github/workflows/ci.yml +73 -0
- package/.github/workflows/codeql.yml +106 -0
- package/.github/workflows/db-migration-check.yml +98 -0
- package/.github/workflows/dependency-review.yml +22 -0
- package/.github/workflows/deploy.yml +95 -0
- package/.github/workflows/release.yml +106 -0
- package/.github/workflows/security-attest-provenance.yml +51 -0
- package/.github/workflows/security-gitleaks.yml +34 -0
- package/.github/workflows/security-runner-hardening.yml +31 -0
- package/.github/workflows/security-scorecard.yml +44 -0
- package/.github/workflows/security-zap-baseline.yml +44 -0
- package/.github/workflows/security-zap-full-scan.yml +43 -0
- package/.github/workflows/security-zizmor.yml +36 -0
- package/.github/workflows/wiki-sync.yml +44 -0
- package/.gitleaks.toml +15 -0
- package/.prettierrc +34 -0
- package/CODE_OF_CONDUCT.md +114 -0
- package/LICENSE +56 -0
- package/README.md +110 -0
- package/SECURITY.md +110 -0
- package/app/config/index.js +4 -0
- package/app/configParts/adminIdentity.js +92 -0
- package/app/configParts/baileysConfig.js +1818 -0
- package/app/configParts/groupUtils.js +692 -0
- package/app/configParts/loggerConfig.js +394 -0
- package/app/configParts/messagePersistenceService.js +305 -0
- package/app/connection/baileysCompatibility.test.js +40 -0
- package/app/connection/baileysDbAuthState.js +344 -0
- package/app/connection/socketController.js +2243 -0
- package/app/controllers/messageController.js +7 -0
- package/app/controllers/messagePipeline/commandMiddleware.js +146 -0
- package/app/controllers/messagePipeline/conversationMiddleware.js +183 -0
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +522 -0
- package/app/controllers/messagePipeline/postProcessingMiddleware.js +41 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +166 -0
- package/app/controllers/messageProcessingPipeline.js +699 -0
- package/app/modules/adminModule/AGENT.md +4056 -0
- package/app/modules/adminModule/adminAiHelpService.js +56 -0
- package/app/modules/adminModule/adminConfigRuntime.js +177 -0
- package/app/modules/adminModule/commandConfig.json +7122 -0
- package/app/modules/adminModule/groupCommandHandlers.js +1823 -0
- package/app/modules/adminModule/groupCommandHandlers.test.js +350 -0
- package/app/modules/adminModule/groupEventHandlers.js +399 -0
- package/app/modules/aiModule/AGENT.md +547 -0
- package/app/modules/aiModule/aiAiHelpService.js +14 -0
- package/app/modules/aiModule/aiConfigRuntime.js +135 -0
- package/app/modules/aiModule/catCommand.js +967 -0
- package/app/modules/aiModule/commandConfig.json +981 -0
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
- package/app/modules/gameModule/AGENT.md +196 -0
- package/app/modules/gameModule/commandConfig.json +366 -0
- package/app/modules/gameModule/diceCommand.js +42 -0
- package/app/modules/gameModule/gameAiHelpService.js +14 -0
- package/app/modules/gameModule/gameConfigRuntime.js +68 -0
- package/app/modules/menuModule/AGENT.md +205 -0
- package/app/modules/menuModule/commandConfig.json +366 -0
- package/app/modules/menuModule/common.js +316 -0
- package/app/modules/menuModule/menuAiHelpService.js +14 -0
- package/app/modules/menuModule/menuConfigRuntime.js +68 -0
- package/app/modules/menuModule/menus.js +66 -0
- package/app/modules/playModule/AGENT.md +321 -0
- package/app/modules/playModule/commandConfig.json +584 -0
- package/app/modules/playModule/playAiHelpService.js +14 -0
- package/app/modules/playModule/playCommand.js +1417 -0
- package/app/modules/playModule/playConfigRuntime.js +68 -0
- package/app/modules/quoteModule/AGENT.md +199 -0
- package/app/modules/quoteModule/commandConfig.json +366 -0
- package/app/modules/quoteModule/quoteAiHelpService.js +14 -0
- package/app/modules/quoteModule/quoteCommand.js +842 -0
- package/app/modules/quoteModule/quoteConfigRuntime.js +68 -0
- package/app/modules/rpgPokemonModule/AGENT.md +229 -0
- package/app/modules/rpgPokemonModule/commandConfig.json +386 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +795 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.js +2110 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +770 -0
- package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
- package/app/modules/rpgPokemonModule/rpgPokemonAiHelpService.js +14 -0
- package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +174 -0
- package/app/modules/rpgPokemonModule/rpgPokemonConfigRuntime.js +68 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
- package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
- package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1847 -0
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +6839 -0
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
- package/app/modules/statsModule/AGENT.md +320 -0
- package/app/modules/statsModule/commandConfig.json +540 -0
- package/app/modules/statsModule/globalRankingCommand.js +64 -0
- package/app/modules/statsModule/rankingCommand.js +41 -0
- package/app/modules/statsModule/rankingCommon.js +1305 -0
- package/app/modules/statsModule/statsAiHelpService.js +14 -0
- package/app/modules/statsModule/statsConfigRuntime.js +68 -0
- package/app/modules/stickerModule/AGENT.md +692 -0
- package/app/modules/stickerModule/addStickerMetadata.js +239 -0
- package/app/modules/stickerModule/commandConfig.json +1216 -0
- package/app/modules/stickerModule/convertToWebp.js +367 -0
- package/app/modules/stickerModule/stickerAiHelpService.js +14 -0
- package/app/modules/stickerModule/stickerCommand.js +446 -0
- package/app/modules/stickerModule/stickerConfigRuntime.js +68 -0
- package/app/modules/stickerModule/stickerConvertCommand.js +159 -0
- package/app/modules/stickerModule/stickerTextCommand.js +653 -0
- package/app/modules/stickerPackModule/AGENT.md +215 -0
- package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
- package/app/modules/stickerPackModule/autoPackCollectorService.js +357 -0
- package/app/modules/stickerPackModule/commandConfig.json +387 -0
- package/app/modules/stickerPackModule/domainEventOutboxRepository.js +227 -0
- package/app/modules/stickerPackModule/domainEvents.js +52 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +429 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +75 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +544 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +175 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +3702 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +559 -0
- package/app/modules/stickerPackModule/stickerClassificationService.js +557 -0
- package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +249 -0
- package/app/modules/stickerPackModule/stickerDomainEventBus.js +65 -0
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +208 -0
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +99 -0
- package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
- package/app/modules/stickerPackModule/stickerPackAiHelpService.js +14 -0
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1148 -0
- package/app/modules/stickerPackModule/stickerPackConfigRuntime.js +68 -0
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +152 -0
- package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +101 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +432 -0
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +313 -0
- package/app/modules/stickerPackModule/stickerPackMessageService.js +268 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +450 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +179 -0
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +271 -0
- package/app/modules/stickerPackModule/stickerPackService.js +733 -0
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +32 -0
- package/app/modules/stickerPackModule/stickerPackUtils.js +107 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +559 -0
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +242 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +242 -0
- package/app/modules/systemMetricsModule/AGENT.md +193 -0
- package/app/modules/systemMetricsModule/commandConfig.json +344 -0
- package/app/modules/systemMetricsModule/pingCommand.js +399 -0
- package/app/modules/systemMetricsModule/systemMetricsAiHelpService.js +14 -0
- package/app/modules/systemMetricsModule/systemMetricsConfigRuntime.js +68 -0
- package/app/modules/tiktokModule/AGENT.md +196 -0
- package/app/modules/tiktokModule/commandConfig.json +366 -0
- package/app/modules/tiktokModule/tiktokAiHelpService.js +14 -0
- package/app/modules/tiktokModule/tiktokCommand.js +716 -0
- package/app/modules/tiktokModule/tiktokConfigRuntime.js +68 -0
- package/app/modules/userModule/AGENT.md +200 -0
- package/app/modules/userModule/commandConfig.json +386 -0
- package/app/modules/userModule/userAiHelpService.js +14 -0
- package/app/modules/userModule/userCommand.js +1155 -0
- package/app/modules/userModule/userConfigRuntime.js +68 -0
- package/app/modules/waifuPicsModule/AGENT.md +431 -0
- package/app/modules/waifuPicsModule/commandConfig.json +780 -0
- package/app/modules/waifuPicsModule/waifuPicsAiHelpService.js +14 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +586 -0
- package/app/modules/waifuPicsModule/waifuPicsConfigRuntime.js +68 -0
- package/app/observability/metrics.js +766 -0
- package/app/services/ai/aiHelpResponseCacheRepository.js +280 -0
- package/app/services/ai/aiLearningRepository.js +400 -0
- package/app/services/ai/commandConfigEnrichmentRepository.js +769 -0
- package/app/services/ai/commandConfigEnrichmentService.js +452 -0
- package/app/services/ai/commandConfigValidationService.js +443 -0
- package/app/services/ai/commandToolBuilderService.js +192 -0
- package/app/services/ai/conversationRouterService.js +516 -0
- package/app/services/ai/geminiService.js +115 -0
- package/app/services/ai/geminiService.test.js +87 -0
- package/app/services/ai/globalModuleAiHelpService.js +1412 -0
- package/app/services/ai/globalToolCallingService.js +203 -0
- package/app/services/ai/messageCommandExecutionService.js +391 -0
- package/app/services/ai/moduleAiHelpCoreService.js +1099 -0
- package/app/services/ai/moduleAiHelpWrapperFactory.js +65 -0
- package/app/services/ai/moduleCommandConfigRuntimeService.js +113 -0
- package/app/services/ai/moduleToolExecutorService.js +464 -0
- package/app/services/ai/moduleToolRegistryService.js +178 -0
- package/app/services/ai/toolCandidateSelectorService.js +781 -0
- package/app/services/auth/googleWebLinkService.js +80 -0
- package/app/services/auth/whatsappLoginLinkService.js +230 -0
- package/app/services/external/pokeApiService.js +398 -0
- package/app/services/group/groupMetadataService.js +311 -0
- package/app/services/infra/dbWriteQueue.js +874 -0
- package/app/services/infra/featureFlagService.js +131 -0
- package/app/services/infra/queueUtils.js +55 -0
- package/app/services/messaging/captchaService.js +491 -0
- package/app/services/messaging/messagePersistenceService.js +1 -0
- package/app/services/messaging/newsBroadcastService.js +347 -0
- package/app/services/sticker/stickerFocusService.js +347 -0
- package/app/services/sticker/stickerFocusService.test.js +43 -0
- package/app/store/aiPromptStore.js +38 -0
- package/app/store/conversationSessionStore.js +131 -0
- package/app/store/groupConfigStore.js +58 -0
- package/app/store/premiumUserStore.js +54 -0
- package/app/utils/antiLink/antiLinkModule.js +700 -0
- package/app/utils/http/getImageBufferModule.js +18 -0
- package/app/utils/json/jsonSanitizer.js +113 -0
- package/app/utils/json/jsonSanitizer.test.js +40 -0
- package/app/utils/systemMetrics/systemMetricsModule.js +88 -0
- package/app/workers/aiLearningWorker.js +605 -0
- package/app/workers/commandConfigEnrichmentWorker.js +242 -0
- package/database/index.js +2075 -0
- package/database/init.js +151 -0
- package/database/migrations/.gitkeep +0 -0
- package/database/migrations/20260307_d0_hardening_down.sql +64 -0
- package/database/migrations/20260307_d0_hardening_up.sql +79 -0
- package/database/migrations/20260307_d1_terms_acceptance_down.sql +11 -0
- package/database/migrations/20260307_d1_terms_acceptance_up.sql +37 -0
- package/database/migrations/20260307_d2_auth_hardening_down.sql +75 -0
- package/database/migrations/20260307_d2_auth_hardening_up.sql +100 -0
- package/database/migrations/20260314_d7_canonical_sender_down.sql +53 -0
- package/database/migrations/20260314_d7_canonical_sender_up.sql +114 -0
- package/database/migrations/20260406_d30_security_analytics_down.sql +95 -0
- package/database/migrations/20260406_d30_security_analytics_up.sql +292 -0
- package/database/migrations/20260407_d31_web_google_session_token_hardening_down.sql +2 -0
- package/database/migrations/20260407_d31_web_google_session_token_hardening_up.sql +17 -0
- package/database/migrations/20260408_d32_ai_help_response_cache_down.sql +1 -0
- package/database/migrations/20260408_d32_ai_help_response_cache_up.sql +22 -0
- package/database/migrations/20260409_d33_ai_learning_tables_down.sql +4 -0
- package/database/migrations/20260409_d33_ai_learning_tables_up.sql +52 -0
- package/database/migrations/20260410_d34_command_config_enrichment_down.sql +3 -0
- package/database/migrations/20260410_d34_command_config_enrichment_up.sql +48 -0
- package/database/schema.sql +1186 -0
- package/docker-compose.yml +104 -0
- package/docs/audits/stickerCatalogController-out-of-scope.md +103 -0
- package/docs/audits/stickerCatalogController-symbols.md +58 -0
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +35 -0
- package/docs/compliance/dpa-b2b-standard-2026-03-07.md +80 -0
- package/docs/compliance/monthly-compliance-checklist-2026-03-07.md +88 -0
- package/docs/compliance/notice-and-takedown-policy-2026-03-07.md +34 -0
- package/docs/compliance/privacy-policy-2026-03-07.md +75 -0
- package/docs/compliance/subprocessors-inventory-2026-03-07.md +16 -0
- package/docs/database/production-db-evolution-runbook-2026q1.md +365 -0
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +86 -0
- package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +77 -0
- package/docs/security/network-hardening-runbook-2026-03-07.md +137 -0
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +238 -0
- package/docs/seo/satellite-page-template.md +116 -0
- package/docs/seo/satellite-pages-phase1.json +364 -0
- package/docs/wiki/Home.md +120 -0
- package/docs/wiki/pair-extraordinaire-2026-03-08.md +3 -0
- package/docs/wiki/recent-changes-2026-03-08.md +47 -0
- package/ecosystem.prod.config.cjs +135 -0
- package/eslint.config.js +89 -0
- package/index.js +488 -0
- package/ml/clip_classifier/Dockerfile +18 -0
- package/ml/clip_classifier/README.md +118 -0
- package/ml/clip_classifier/adaptive_scoring.py +40 -0
- package/ml/clip_classifier/classifier.py +654 -0
- package/ml/clip_classifier/embedding_store.py +481 -0
- package/ml/clip_classifier/env_loader.py +15 -0
- package/ml/clip_classifier/llm_label_expander.py +144 -0
- package/ml/clip_classifier/main.py +213 -0
- package/ml/clip_classifier/requirements.txt +10 -0
- package/ml/clip_classifier/similarity_engine.py +74 -0
- package/new-logo.png +0 -0
- package/observability/alert-rules.yml +60 -0
- package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
- package/observability/grafana/dashboards/omnizap-overview.json +170 -0
- package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
- package/observability/loki-config.yml +38 -0
- package/observability/mysql-setup.sql +46 -0
- package/observability/prometheus.yml +35 -0
- package/observability/promtail-config.yml +84 -0
- package/observability/sticker-catalog-slo.md +83 -0
- package/observability/sticker-scale-hardening-rollout.md +128 -0
- package/package.json +144 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/css/commands-react.input.css +71 -0
- package/public/assets/css/create-pack-react.input.css +31 -0
- package/public/assets/css/home-react.input.css +106 -0
- package/public/assets/css/login-react.input.css +58 -0
- package/public/assets/css/stickers-react.input.css +18 -0
- package/public/assets/css/terms-react.input.css +115 -0
- package/public/assets/css/user-react.input.css +57 -0
- package/public/assets/images/brand-icon-192.png +0 -0
- package/public/assets/images/brand-logo-128.webp +0 -0
- package/public/assets/images/hero-banner-1280.jpg +0 -0
- package/public/comandos/commands-catalog.json +4517 -0
- package/public/css/api-docs.css +161 -0
- package/public/css/stickers-admin.css +1288 -0
- package/public/css/styles.css +679 -0
- package/public/css/systemadm/admin.css +474 -0
- package/public/css/systemadm/base.css +73 -0
- package/public/css/systemadm/components.css +662 -0
- package/public/css/systemadm/layout.css +229 -0
- package/public/css/systemadm/tokens.css +56 -0
- 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 +235 -0
- package/public/js/apps/commandsReactApp.js +528 -0
- package/public/js/apps/createPackApp.js +1646 -0
- package/public/js/apps/homeReactApp.js +942 -0
- package/public/js/apps/loginReactApp.js +496 -0
- package/public/js/apps/stickersAdminApp.js +1753 -0
- package/public/js/apps/stickersApp.js +3797 -0
- package/public/js/apps/termsReactApp.js +528 -0
- package/public/js/apps/userApp.js +2540 -0
- package/public/js/apps/userProfile/actions.js +66 -0
- package/public/js/apps/userReactApp.js +547 -0
- package/public/js/catalog.js +950 -0
- package/public/pages/api-docs.html +40 -0
- package/public/pages/aup.html +158 -0
- package/public/pages/comandos.html +41 -0
- package/public/pages/dpa.html +227 -0
- package/public/pages/home.html +45 -0
- package/public/pages/licenca.html +182 -0
- package/public/pages/login.html +40 -0
- package/public/pages/notice-and-takedown.html +234 -0
- package/public/pages/politica-de-privacidade.html +251 -0
- package/public/pages/seo-bot-whatsapp-para-grupo.html +350 -0
- package/public/pages/seo-bot-whatsapp-sem-programar.html +350 -0
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +350 -0
- package/public/pages/seo-como-criar-comandos-whatsapp.html +350 -0
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +350 -0
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +350 -0
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +350 -0
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +350 -0
- package/public/pages/stickers-admin.html +31 -0
- package/public/pages/stickers-create.html +41 -0
- package/public/pages/stickers.html +45 -0
- package/public/pages/suboperadores.html +237 -0
- package/public/pages/termos-de-uso-texto-integral.html +241 -0
- package/public/pages/termos-de-uso.html +41 -0
- package/public/pages/user-password-reset.html +32 -0
- package/public/pages/user-systemadm.html +508 -0
- package/public/pages/user.html +39 -0
- package/public/robots.txt +9 -0
- package/public/site.webmanifest +24 -0
- package/public/sitemap.xml +98 -0
- package/schemas/command-config.schema.json +582 -0
- package/scripts/baileys-compat-smoke.mjs +12 -0
- package/scripts/cache-bust.mjs +142 -0
- package/scripts/deploy.sh +916 -0
- package/scripts/email-broadcast-terms-update.mjs +170 -0
- package/scripts/enrich-command-discovery-fields.mjs +286 -0
- package/scripts/generate-command-config-schema.mjs +273 -0
- package/scripts/generate-commands-catalog.mjs +308 -0
- package/scripts/generate-module-agents.mjs +631 -0
- package/scripts/generate-seo-satellite-pages.mjs +400 -0
- package/scripts/github-deploy-notify.mjs +174 -0
- package/scripts/github-release-notify.mjs +219 -0
- package/scripts/release.sh +599 -0
- package/scripts/run-codeql-local.sh +116 -0
- package/scripts/run-prettier-all.mjs +25 -0
- package/scripts/security-smoketest.mjs +581 -0
- package/scripts/sticker-catalog-loadtest.mjs +210 -0
- package/scripts/sticker-worker-task.mjs +119 -0
- package/scripts/sync-readme-snapshot.mjs +133 -0
- package/scripts/validate-command-config-schema.mjs +130 -0
- package/scripts/validate-command-configs.mjs +15 -0
- package/scripts/wiki-sync.sh +191 -0
- package/server/auth/googleWebAuth/googleWebAuthRuntime.js +62 -0
- package/server/auth/googleWebAuth/googleWebAuthService.js +807 -0
- package/server/auth/jwt/webJwtService.js +147 -0
- package/server/auth/stickerCatalogAuthContext.js +165 -0
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +189 -0
- package/server/auth/userPassword/index.js +14 -0
- package/server/auth/userPassword/userPasswordAuthService.js +422 -0
- package/server/auth/userPassword/userPasswordCrypto.js +199 -0
- package/server/auth/userPassword/userPasswordCrypto.test.js +76 -0
- package/server/auth/userPassword/userPasswordRecoveryService.js +728 -0
- package/server/auth/validation/authSchemas.js +236 -0
- package/server/auth/webAccount/webAccountHandlers.js +1434 -0
- package/server/controllers/admin/adminBanService.js +138 -0
- package/server/controllers/admin/adminPanelHandlers.js +2083 -0
- package/server/controllers/admin/stickerCatalogAdminContext.js +17 -0
- package/server/controllers/admin/systemAdminController.js +201 -0
- package/server/controllers/email/emailAutomationController.js +239 -0
- package/server/controllers/metricsController.js +21 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +514 -0
- package/server/controllers/sticker/nonCatalogHandlers.js +303 -0
- package/server/controllers/sticker/stickerCatalogController.js +4700 -0
- package/server/controllers/system/contactController.js +115 -0
- package/server/controllers/system/githubController.js +137 -0
- package/server/controllers/system/stickerCatalogSystemContext.js +758 -0
- package/server/controllers/system/storageController.js +154 -0
- package/server/controllers/system/systemController.js +135 -0
- package/server/controllers/system/systemMetricsController.js +156 -0
- package/server/controllers/system/visitController.js +90 -0
- package/server/controllers/userController.js +145 -0
- package/server/email/emailAutomationRuntime.js +225 -0
- package/server/email/emailAutomationService.js +125 -0
- package/server/email/emailOutboxRepository.js +282 -0
- package/server/email/emailTemplateService.js +480 -0
- package/server/email/emailTransportService.js +156 -0
- package/server/http/clientIp.js +95 -0
- package/server/http/httpRequestUtils.js +262 -0
- package/server/http/httpRequestUtils.test.js +80 -0
- package/server/http/httpServer.js +180 -0
- package/server/http/requestContext.js +20 -0
- package/server/http/siteRoutingUtils.js +87 -0
- package/server/index.js +1 -0
- package/server/middleware/cachePolicy.js +26 -0
- package/server/middleware/cachePolicyHelpers.js +1 -0
- package/server/middleware/endpointRateLimit.js +181 -0
- package/server/middleware/rateLimit.js +70 -0
- package/server/middleware/requireAdminAuth.js +48 -0
- package/server/middleware/securityHeaders.js +97 -0
- package/server/routes/admin/systemAdminRouter.js +64 -0
- package/server/routes/email/emailAutomationRouter.js +46 -0
- package/server/routes/health/healthRouter.js +41 -0
- package/server/routes/indexRouter.js +234 -0
- package/server/routes/metrics/metricsRouter.js +58 -0
- package/server/routes/static/staticPageRouter.js +134 -0
- package/server/routes/sticker/catalogHandlers/catalogAdminHttp.js +105 -0
- package/server/routes/sticker/catalogHandlers/catalogAuthHttp.js +77 -0
- package/server/routes/sticker/catalogHandlers/catalogPublicHttp.js +120 -0
- package/server/routes/sticker/catalogHandlers/catalogUploadHttp.js +83 -0
- package/server/routes/sticker/catalogRouter.js +77 -0
- package/server/routes/sticker/stickerApiRouter.js +84 -0
- package/server/routes/sticker/stickerDataRouter.js +145 -0
- package/server/routes/sticker/stickerSiteRouter.js +43 -0
- package/server/routes/user/userApiPaths.js +66 -0
- package/server/routes/user/userRouter.js +65 -0
- package/server/utils/safePath.js +26 -0
- package/utils/logger/loggerModule.js +35 -0
- package/vite.config.mjs +38 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
import logger from '#logger';
|
|
2
|
+
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
3
|
+
import { queueLidUpdate, flushLidQueue, resolveUserIdCached } from '../../config/index.js';
|
|
4
|
+
import { buildPlaceholders, createFlushRunner } from './queueUtils.js';
|
|
5
|
+
import { recordError, setQueueDepth } from '../../observability/metrics.js';
|
|
6
|
+
import { sanitizeUnicodeString, toSafeJsonColumnValue } from '../../utils/json/jsonSanitizer.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converte um valor para número com fallback.
|
|
10
|
+
*
|
|
11
|
+
* @param {*} value - Valor de entrada (string, number, etc.).
|
|
12
|
+
* @param {number} fallback - Valor padrão caso a conversão falhe.
|
|
13
|
+
* @returns {number} Número finito convertido, ou o fallback.
|
|
14
|
+
*/
|
|
15
|
+
const parseNumber = (value, fallback) => {
|
|
16
|
+
const parsed = Number(value);
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Intervalo (ms) do flush periódico das filas de escrita no banco.
|
|
22
|
+
* É limitado entre 1000ms e 3000ms por segurança.
|
|
23
|
+
*
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
const FLUSH_INTERVAL_MS = Math.min(3000, Math.max(1000, parseNumber(process.env.DB_WRITE_FLUSH_MS, 1500)));
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tamanho máximo do batch de mensagens por INSERT.
|
|
30
|
+
*
|
|
31
|
+
* @type {number}
|
|
32
|
+
*/
|
|
33
|
+
const MESSAGE_BATCH_SIZE = Math.max(1, Math.floor(parseNumber(process.env.DB_MESSAGE_BATCH_SIZE, 200)));
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tamanho máximo do batch de chats por INSERT/UPSERT.
|
|
37
|
+
*
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
40
|
+
const CHAT_BATCH_SIZE = Math.max(1, Math.floor(parseNumber(process.env.DB_CHAT_BATCH_SIZE, 200)));
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tempo de “cooldown” (ms) por chat antes de permitir novo write no banco.
|
|
44
|
+
* Ajuda a reduzir writes repetidos do mesmo chat em curto período.
|
|
45
|
+
*
|
|
46
|
+
* @type {number}
|
|
47
|
+
*/
|
|
48
|
+
const CHAT_COOLDOWN_MS = Math.max(1000, Math.floor(parseNumber(process.env.DB_CHAT_COOLDOWN_MS, 45000)));
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Capacidade máxima da fila de mensagens.
|
|
52
|
+
* Quando atinge esse limite, forçamos flush para tentar esvaziar.
|
|
53
|
+
*
|
|
54
|
+
* @type {number}
|
|
55
|
+
*/
|
|
56
|
+
const MESSAGE_QUEUE_MAX = Math.max(MESSAGE_BATCH_SIZE * 5, Math.floor(parseNumber(process.env.DB_MESSAGE_QUEUE_MAX, 5000)));
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Tamanho máximo do batch de eventos do Baileys por INSERT.
|
|
60
|
+
* @type {number}
|
|
61
|
+
*/
|
|
62
|
+
const BAILEYS_EVENT_BATCH_SIZE = Math.max(1, Math.floor(parseNumber(process.env.BAILEYS_EVENT_BATCH_SIZE, 100)));
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Capacidade máxima da fila de eventos do Baileys.
|
|
66
|
+
* @type {number}
|
|
67
|
+
*/
|
|
68
|
+
const BAILEYS_EVENT_QUEUE_MAX = Math.max(BAILEYS_EVENT_BATCH_SIZE * 5, Math.floor(parseNumber(process.env.BAILEYS_EVENT_QUEUE_MAX, 4000)));
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Retenção do journal de eventos em dias (0 desativa prune).
|
|
72
|
+
* @type {number}
|
|
73
|
+
*/
|
|
74
|
+
const BAILEYS_EVENT_JOURNAL_RETENTION_DAYS = Math.max(0, Math.floor(parseNumber(process.env.BAILEYS_EVENT_JOURNAL_RETENTION_DAYS, 14)));
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Intervalo entre execuções de prune do journal de eventos.
|
|
78
|
+
* @type {number}
|
|
79
|
+
*/
|
|
80
|
+
const BAILEYS_EVENT_JOURNAL_PRUNE_INTERVAL_MS = Math.max(60_000, Math.floor(parseNumber(process.env.BAILEYS_EVENT_JOURNAL_PRUNE_INTERVAL_MS, 6 * 60 * 60 * 1000)));
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Limite de deleções por rodada de prune (protege lock longo).
|
|
84
|
+
* @type {number}
|
|
85
|
+
*/
|
|
86
|
+
const BAILEYS_EVENT_JOURNAL_PRUNE_DELETE_LIMIT = Math.max(500, Math.floor(parseNumber(process.env.BAILEYS_EVENT_JOURNAL_PRUNE_DELETE_LIMIT, 10_000)));
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Regex de erro para payload JSON inválido no MySQL.
|
|
90
|
+
* @type {RegExp}
|
|
91
|
+
*/
|
|
92
|
+
const INVALID_JSON_TEXT_REGEX = /Invalid JSON text/i;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Regex específica para surrogate inválido.
|
|
96
|
+
* @type {RegExp}
|
|
97
|
+
*/
|
|
98
|
+
const INVALID_SURROGATE_REGEX = /surrogate pair/i;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Fila em memória com mensagens pendentes de persistência.
|
|
102
|
+
* @type {Array<Object>}
|
|
103
|
+
*/
|
|
104
|
+
const messageQueue = [];
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Conjunto de IDs de mensagens que já estão enfileiradas, para evitar duplicação.
|
|
108
|
+
* @type {Set<string>}
|
|
109
|
+
*/
|
|
110
|
+
const messagePendingIds = new Set();
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fila (por chat.id) com atualizações pendentes de chats.
|
|
114
|
+
* @type {Map<string, {id:string, name:(string|null), raw:(Object|null), rawHash:string, queuedAt:number, nextAllowedAt:number}>}
|
|
115
|
+
*/
|
|
116
|
+
const chatQueue = new Map();
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Cache por chat.id para comparar estado persistido vs pendente (hash/name/último write).
|
|
120
|
+
* @type {Map<string, {
|
|
121
|
+
* storedRaw: (Object|null),
|
|
122
|
+
* storedHash: (string|null),
|
|
123
|
+
* storedName: (string|null),
|
|
124
|
+
* lastWriteAt: number,
|
|
125
|
+
* pendingRaw: (Object|null),
|
|
126
|
+
* pendingHash: (string|null),
|
|
127
|
+
* pendingName: (string|null)
|
|
128
|
+
* }>}
|
|
129
|
+
*/
|
|
130
|
+
const chatCache = new Map();
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 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
|
+
*/
|
|
136
|
+
const baileysEventQueue = [];
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Indica se já há um flush agendado via setImmediate.
|
|
140
|
+
* @type {boolean}
|
|
141
|
+
*/
|
|
142
|
+
let flushScheduled = false;
|
|
143
|
+
let baileysEventTableMissingLogged = false;
|
|
144
|
+
let messageActivityDailyTableMissingLogged = false;
|
|
145
|
+
let nextBaileysEventPruneAt = 0;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Atualiza as métricas de profundidade das filas (monitoramento).
|
|
149
|
+
*
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
const updateQueueMetrics = () => {
|
|
153
|
+
setQueueDepth('messages', messageQueue.length);
|
|
154
|
+
setQueueDepth('chats', chatQueue.size);
|
|
155
|
+
setQueueDepth('baileys_events', baileysEventQueue.length);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Agenda a execução de flush das filas no próximo ciclo do event-loop.
|
|
160
|
+
* Evita agendar múltiplas execuções repetidas em sequência.
|
|
161
|
+
*
|
|
162
|
+
* @returns {void}
|
|
163
|
+
*/
|
|
164
|
+
const scheduleFlush = () => {
|
|
165
|
+
if (flushScheduled) return;
|
|
166
|
+
flushScheduled = true;
|
|
167
|
+
setImmediate(() => {
|
|
168
|
+
flushScheduled = false;
|
|
169
|
+
flushQueues().catch((error) => {
|
|
170
|
+
logger.error('Falha ao executar flush das filas.', { error: error.message });
|
|
171
|
+
recordError('db_write_queue');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Calcula um hash FNV-1a (32-bit) de uma string.
|
|
178
|
+
* Útil para detectar mudanças em objetos serializados.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} input - Texto de entrada.
|
|
181
|
+
* @returns {string} Hash em hexadecimal (8 chars).
|
|
182
|
+
*/
|
|
183
|
+
const fnv1aHash = (input) => {
|
|
184
|
+
let hash = 0x811c9dc5;
|
|
185
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
186
|
+
hash ^= input.charCodeAt(i);
|
|
187
|
+
hash = (hash + (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
|
|
188
|
+
}
|
|
189
|
+
return hash.toString(16).padStart(8, '0');
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Serializa um valor para string de forma estável (ordena chaves),
|
|
194
|
+
* com proteção contra referências circulares e profundidade máxima.
|
|
195
|
+
*
|
|
196
|
+
* @param {*} value - Valor a serializar.
|
|
197
|
+
* @param {number} [depth=0] - Profundidade atual (uso interno).
|
|
198
|
+
* @param {WeakSet<object>} [seen=new WeakSet()] - Rastreamento de objetos vistos (circular).
|
|
199
|
+
* @returns {string} String estável do valor.
|
|
200
|
+
*/
|
|
201
|
+
const stableStringify = (value, depth = 0, seen = new WeakSet()) => {
|
|
202
|
+
if (value === null || value === undefined) return 'null';
|
|
203
|
+
if (typeof value === 'string') return JSON.stringify(value);
|
|
204
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
205
|
+
if (typeof value === 'bigint') return `"${value.toString()}"`;
|
|
206
|
+
if (value instanceof Date) return `"${value.toISOString()}"`;
|
|
207
|
+
if (Buffer.isBuffer(value)) return `"Buffer:${value.length}"`;
|
|
208
|
+
if (typeof value !== 'object') return `"${String(value)}"`;
|
|
209
|
+
if (seen.has(value)) return '"[Circular]"';
|
|
210
|
+
if (depth > 6) return '"[MaxDepth]"';
|
|
211
|
+
|
|
212
|
+
seen.add(value);
|
|
213
|
+
if (Array.isArray(value)) {
|
|
214
|
+
const items = value.map((item) => stableStringify(item, depth + 1, seen));
|
|
215
|
+
return `[${items.join(',')}]`;
|
|
216
|
+
}
|
|
217
|
+
const keys = Object.keys(value).sort();
|
|
218
|
+
const items = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key], depth + 1, seen)}`);
|
|
219
|
+
return `{${items.join(',')}}`;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Gera um hash estável de qualquer valor/objeto.
|
|
224
|
+
* Caso a serialização falhe, usa fallback para String(value).
|
|
225
|
+
*
|
|
226
|
+
* @param {*} value - Valor a hashear.
|
|
227
|
+
* @returns {string} Hash em hexadecimal (8 chars).
|
|
228
|
+
*/
|
|
229
|
+
const hashObject = (value) => {
|
|
230
|
+
try {
|
|
231
|
+
return fnv1aHash(stableStringify(value));
|
|
232
|
+
} catch {
|
|
233
|
+
return fnv1aHash(String(value));
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Indica se o erro foi causado por texto JSON inválido (payload determinístico).
|
|
239
|
+
*
|
|
240
|
+
* @param {Error} error
|
|
241
|
+
* @returns {boolean}
|
|
242
|
+
*/
|
|
243
|
+
const isInvalidJsonPayloadError = (error) => {
|
|
244
|
+
const message = error?.message || '';
|
|
245
|
+
return INVALID_JSON_TEXT_REGEX.test(message) || INVALID_SURROGATE_REGEX.test(message);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Normaliza payload de mensagem antes de persistir.
|
|
250
|
+
* - content: remove surrogate inválido
|
|
251
|
+
* - raw_message: serializa JSON seguro para coluna JSON
|
|
252
|
+
*
|
|
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)}}
|
|
255
|
+
*/
|
|
256
|
+
const normalizeUserIdForColumn = (value, maxLength = 255) => {
|
|
257
|
+
if (value === null || value === undefined) return null;
|
|
258
|
+
const normalized = sanitizeUnicodeString(String(value).trim()).slice(0, maxLength);
|
|
259
|
+
return normalized || null;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const resolveCanonicalSenderIdForMessage = (messageData) => {
|
|
263
|
+
const explicitCanonical = normalizeUserIdForColumn(messageData?.canonical_sender_id, 255);
|
|
264
|
+
if (explicitCanonical) return explicitCanonical;
|
|
265
|
+
|
|
266
|
+
const senderId = normalizeUserIdForColumn(messageData?.sender_id, 255);
|
|
267
|
+
if (!senderId) return null;
|
|
268
|
+
|
|
269
|
+
const cachedCanonical = resolveUserIdCached({
|
|
270
|
+
lid: senderId,
|
|
271
|
+
jid: senderId,
|
|
272
|
+
participantAlt: null,
|
|
273
|
+
});
|
|
274
|
+
return normalizeUserIdForColumn(cachedCanonical || senderId, 255);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const normalizeMessageForQueue = (messageData) => {
|
|
278
|
+
const senderId = normalizeUserIdForColumn(messageData?.sender_id, 255);
|
|
279
|
+
return {
|
|
280
|
+
...messageData,
|
|
281
|
+
sender_id: senderId,
|
|
282
|
+
canonical_sender_id: resolveCanonicalSenderIdForMessage({
|
|
283
|
+
...messageData,
|
|
284
|
+
sender_id: senderId,
|
|
285
|
+
}),
|
|
286
|
+
content: typeof messageData?.content === 'string' ? sanitizeUnicodeString(messageData.content) : messageData?.content,
|
|
287
|
+
raw_message: toSafeJsonColumnValue(messageData?.raw_message),
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const normalizeTextForColumn = (value, maxLength = 255) => {
|
|
292
|
+
if (value === null || value === undefined) return null;
|
|
293
|
+
const normalized = sanitizeUnicodeString(String(value).trim()).slice(0, maxLength);
|
|
294
|
+
return normalized || null;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const normalizeTimestampForColumn = (value) => {
|
|
298
|
+
if (value instanceof Date && Number.isFinite(value.getTime())) return value;
|
|
299
|
+
const parsed = new Date(value);
|
|
300
|
+
if (Number.isFinite(parsed.getTime())) return parsed;
|
|
301
|
+
return new Date();
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const normalizeBaileysEventForQueue = (eventData) => ({
|
|
305
|
+
event_name: normalizeTextForColumn(eventData?.event_name, 64),
|
|
306
|
+
socket_generation: Number.isFinite(Number(eventData?.socket_generation)) ? Math.max(0, Math.floor(Number(eventData.socket_generation))) : null,
|
|
307
|
+
chat_id: normalizeTextForColumn(eventData?.chat_id, 255),
|
|
308
|
+
message_id: normalizeTextForColumn(eventData?.message_id, 255),
|
|
309
|
+
participant_id: normalizeTextForColumn(eventData?.participant_id, 255),
|
|
310
|
+
payload_summary: toSafeJsonColumnValue(eventData?.payload_summary),
|
|
311
|
+
event_timestamp: normalizeTimestampForColumn(eventData?.event_timestamp),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const insertBaileysEventBatch = async (batch) => {
|
|
315
|
+
const placeholders = buildPlaceholders(batch.length, 7);
|
|
316
|
+
const params = [];
|
|
317
|
+
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);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const sql = `INSERT INTO ${TABLES.BAILEYS_EVENT_JOURNAL}
|
|
322
|
+
(event_name, socket_generation, chat_id, message_id, participant_id, payload_summary, event_timestamp)
|
|
323
|
+
VALUES ${placeholders}`;
|
|
324
|
+
|
|
325
|
+
await executeQuery(sql, params);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Executa INSERT IGNORE de um batch de mensagens.
|
|
330
|
+
*
|
|
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
|
|
332
|
+
* @returns {Promise<void>}
|
|
333
|
+
*/
|
|
334
|
+
const insertMessageBatch = async (batch) => {
|
|
335
|
+
const placeholders = buildPlaceholders(batch.length, 7);
|
|
336
|
+
const params = [];
|
|
337
|
+
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);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const sql = `INSERT IGNORE INTO ${TABLES.MESSAGES}
|
|
342
|
+
(message_id, chat_id, sender_id, canonical_sender_id, content, raw_message, timestamp)
|
|
343
|
+
VALUES ${placeholders}`;
|
|
344
|
+
|
|
345
|
+
await executeQuery(sql, params);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const toUtcDateKey = (value) => {
|
|
349
|
+
const parsed = value instanceof Date ? value : new Date(value);
|
|
350
|
+
if (!Number.isFinite(parsed.getTime())) return null;
|
|
351
|
+
return parsed.toISOString().slice(0, 10);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const buildMessageActivityDailyKeys = (batch) => {
|
|
355
|
+
const keyMap = new Map();
|
|
356
|
+
for (const message of batch) {
|
|
357
|
+
const dayRefDate = toUtcDateKey(message?.timestamp);
|
|
358
|
+
const chatId = normalizeUserIdForColumn(message?.chat_id, 255);
|
|
359
|
+
const canonicalSenderId = normalizeUserIdForColumn(message?.canonical_sender_id || message?.sender_id, 255);
|
|
360
|
+
if (!dayRefDate || !chatId || !canonicalSenderId) continue;
|
|
361
|
+
|
|
362
|
+
const key = `${dayRefDate}\u001f${chatId}\u001f${canonicalSenderId}`;
|
|
363
|
+
if (!keyMap.has(key)) {
|
|
364
|
+
keyMap.set(key, { dayRefDate, chatId, canonicalSenderId });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return Array.from(keyMap.values());
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const isMessageActivityDailyMissing = (error) => {
|
|
371
|
+
const code = String(error?.code || '')
|
|
372
|
+
.trim()
|
|
373
|
+
.toUpperCase();
|
|
374
|
+
if (code === 'ER_NO_SUCH_TABLE') return true;
|
|
375
|
+
const errno = Number(error?.errno || 0);
|
|
376
|
+
if (errno === 1146) return true;
|
|
377
|
+
const message = String(error?.message || '').toLowerCase();
|
|
378
|
+
return message.includes('message_activity_daily') && message.includes("doesn't exist");
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const refreshMessageActivityDailyForBatch = async (batch) => {
|
|
382
|
+
const keys = buildMessageActivityDailyKeys(batch);
|
|
383
|
+
if (!keys.length) return;
|
|
384
|
+
|
|
385
|
+
const keyRowsSql = keys.map((_, index) => (index === 0 ? 'SELECT ? AS day_ref_date, ? AS chat_id, ? AS canonical_sender_id' : 'UNION ALL SELECT ?, ?, ?')).join('\n');
|
|
386
|
+
const params = [];
|
|
387
|
+
for (const keyRow of keys) {
|
|
388
|
+
params.push(keyRow.dayRefDate, keyRow.chatId, keyRow.canonicalSenderId);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const sql = `INSERT INTO message_activity_daily (
|
|
392
|
+
day_ref_date,
|
|
393
|
+
chat_id,
|
|
394
|
+
canonical_sender_id,
|
|
395
|
+
total_messages,
|
|
396
|
+
first_message_at,
|
|
397
|
+
last_message_at
|
|
398
|
+
)
|
|
399
|
+
SELECT
|
|
400
|
+
k.day_ref_date,
|
|
401
|
+
k.chat_id,
|
|
402
|
+
k.canonical_sender_id,
|
|
403
|
+
COUNT(*) AS total_messages,
|
|
404
|
+
MIN(m.timestamp) AS first_message_at,
|
|
405
|
+
MAX(m.timestamp) AS last_message_at
|
|
406
|
+
FROM (
|
|
407
|
+
${keyRowsSql}
|
|
408
|
+
) k
|
|
409
|
+
JOIN ${TABLES.MESSAGES} m
|
|
410
|
+
ON DATE(m.timestamp) = k.day_ref_date
|
|
411
|
+
AND m.chat_id = k.chat_id
|
|
412
|
+
AND COALESCE(m.canonical_sender_id, m.sender_id) = k.canonical_sender_id
|
|
413
|
+
GROUP BY k.day_ref_date, k.chat_id, k.canonical_sender_id
|
|
414
|
+
ON DUPLICATE KEY UPDATE
|
|
415
|
+
total_messages = VALUES(total_messages),
|
|
416
|
+
first_message_at = VALUES(first_message_at),
|
|
417
|
+
last_message_at = VALUES(last_message_at),
|
|
418
|
+
updated_at = CURRENT_TIMESTAMP`;
|
|
419
|
+
|
|
420
|
+
await executeQuery(sql, params);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Remove IDs de mensagens do set de pendentes.
|
|
425
|
+
*
|
|
426
|
+
* @param {Array<{message_id:string}>} batch
|
|
427
|
+
* @returns {void}
|
|
428
|
+
*/
|
|
429
|
+
const clearPendingMessageIds = (batch) => {
|
|
430
|
+
for (const message of batch) {
|
|
431
|
+
messagePendingIds.delete(message.message_id);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Em caso de erro JSON no batch, tenta persistir item a item.
|
|
437
|
+
* - Mensagem inválida é descartada para não travar a fila inteira.
|
|
438
|
+
* - Em erro transitório, re-enfileira o restante e interrompe.
|
|
439
|
+
*
|
|
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
|
|
441
|
+
* @returns {Promise<void>}
|
|
442
|
+
*/
|
|
443
|
+
const salvageJsonErrorBatch = async (batch) => {
|
|
444
|
+
for (let index = 0; index < batch.length; index += 1) {
|
|
445
|
+
const message = batch[index];
|
|
446
|
+
try {
|
|
447
|
+
await insertMessageBatch([message]);
|
|
448
|
+
clearPendingMessageIds([message]);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (isInvalidJsonPayloadError(error)) {
|
|
451
|
+
clearPendingMessageIds([message]);
|
|
452
|
+
logger.warn('Mensagem descartada por payload JSON inválido.', {
|
|
453
|
+
messageId: message?.message_id,
|
|
454
|
+
chatId: message?.chat_id,
|
|
455
|
+
error: error.message,
|
|
456
|
+
});
|
|
457
|
+
recordError('db_write_queue');
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
messageQueue.unshift(...batch.slice(index));
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const flushMessageQueueCore = async () => {
|
|
468
|
+
while (messageQueue.length > 0) {
|
|
469
|
+
const batch = messageQueue.splice(0, MESSAGE_BATCH_SIZE);
|
|
470
|
+
try {
|
|
471
|
+
await insertMessageBatch(batch);
|
|
472
|
+
try {
|
|
473
|
+
await refreshMessageActivityDailyForBatch(batch);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (isMessageActivityDailyMissing(error)) {
|
|
476
|
+
if (!messageActivityDailyTableMissingLogged) {
|
|
477
|
+
messageActivityDailyTableMissingLogged = true;
|
|
478
|
+
logger.warn('Tabela message_activity_daily ausente; rollup incremental desativado.', {
|
|
479
|
+
action: 'message_activity_daily_missing',
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
logger.warn('Falha ao atualizar rollup message_activity_daily apos flush de mensagens.', {
|
|
484
|
+
action: 'message_activity_daily_upsert_failed',
|
|
485
|
+
error: error?.message,
|
|
486
|
+
});
|
|
487
|
+
recordError('db_write_queue');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
clearPendingMessageIds(batch);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
logger.error('Falha ao inserir batch de mensagens.', { error: error.message });
|
|
493
|
+
recordError('db_write_queue');
|
|
494
|
+
|
|
495
|
+
if (isInvalidJsonPayloadError(error)) {
|
|
496
|
+
try {
|
|
497
|
+
await salvageJsonErrorBatch(batch);
|
|
498
|
+
continue;
|
|
499
|
+
} catch (salvageError) {
|
|
500
|
+
logger.error('Falha ao recuperar batch de mensagens após erro de JSON inválido.', {
|
|
501
|
+
error: salvageError.message,
|
|
502
|
+
});
|
|
503
|
+
recordError('db_write_queue');
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
messageQueue.unshift(...batch);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const flushChatQueueCore = async () => {
|
|
515
|
+
while (chatQueue.size > 0) {
|
|
516
|
+
const now = Date.now();
|
|
517
|
+
const ready = [];
|
|
518
|
+
for (const entry of chatQueue.values()) {
|
|
519
|
+
if (now < entry.nextAllowedAt) continue;
|
|
520
|
+
ready.push(entry);
|
|
521
|
+
if (ready.length >= CHAT_BATCH_SIZE) break;
|
|
522
|
+
}
|
|
523
|
+
if (!ready.length) break;
|
|
524
|
+
|
|
525
|
+
const placeholders = buildPlaceholders(ready.length, 3);
|
|
526
|
+
const params = [];
|
|
527
|
+
for (const entry of ready) {
|
|
528
|
+
if (typeof entry.name === 'string') {
|
|
529
|
+
entry.name = sanitizeUnicodeString(entry.name);
|
|
530
|
+
}
|
|
531
|
+
params.push(entry.id, entry.name, toSafeJsonColumnValue(entry.raw));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const sql = `INSERT INTO ${TABLES.CHATS} (id, name, raw_chat)
|
|
535
|
+
VALUES ${placeholders}
|
|
536
|
+
ON DUPLICATE KEY UPDATE
|
|
537
|
+
name = COALESCE(VALUES(name), name),
|
|
538
|
+
raw_chat = COALESCE(VALUES(raw_chat), raw_chat)`;
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
await executeQuery(sql, params);
|
|
542
|
+
const writeAt = Date.now();
|
|
543
|
+
for (const entry of ready) {
|
|
544
|
+
const current = chatQueue.get(entry.id);
|
|
545
|
+
const cache = chatCache.get(entry.id) || {};
|
|
546
|
+
|
|
547
|
+
if (entry.raw) {
|
|
548
|
+
cache.storedRaw = entry.raw;
|
|
549
|
+
cache.storedHash = entry.rawHash;
|
|
550
|
+
}
|
|
551
|
+
if (entry.name !== null && entry.name !== undefined) {
|
|
552
|
+
cache.storedName = entry.name;
|
|
553
|
+
}
|
|
554
|
+
cache.lastWriteAt = writeAt;
|
|
555
|
+
|
|
556
|
+
if (!current || current.queuedAt === entry.queuedAt) {
|
|
557
|
+
chatQueue.delete(entry.id);
|
|
558
|
+
cache.pendingRaw = null;
|
|
559
|
+
cache.pendingHash = null;
|
|
560
|
+
cache.pendingName = null;
|
|
561
|
+
} else {
|
|
562
|
+
current.nextAllowedAt = writeAt + CHAT_COOLDOWN_MS;
|
|
563
|
+
chatQueue.set(entry.id, current);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
chatCache.set(entry.id, cache);
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
logger.error('Falha ao inserir batch de chats.', { error: error.message });
|
|
570
|
+
recordError('db_write_queue');
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const pruneBaileysEventJournal = async () => {
|
|
577
|
+
if (BAILEYS_EVENT_JOURNAL_RETENTION_DAYS <= 0) return;
|
|
578
|
+
|
|
579
|
+
const now = Date.now();
|
|
580
|
+
if (now < nextBaileysEventPruneAt) return;
|
|
581
|
+
nextBaileysEventPruneAt = now + BAILEYS_EVENT_JOURNAL_PRUNE_INTERVAL_MS;
|
|
582
|
+
|
|
583
|
+
let totalDeleted = 0;
|
|
584
|
+
const maxRounds = 5;
|
|
585
|
+
for (let round = 0; round < maxRounds; round += 1) {
|
|
586
|
+
const result = await executeQuery(
|
|
587
|
+
`DELETE FROM ${TABLES.BAILEYS_EVENT_JOURNAL}
|
|
588
|
+
WHERE created_at < (UTC_TIMESTAMP() - INTERVAL ? DAY)
|
|
589
|
+
LIMIT ?`,
|
|
590
|
+
[BAILEYS_EVENT_JOURNAL_RETENTION_DAYS, BAILEYS_EVENT_JOURNAL_PRUNE_DELETE_LIMIT],
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
const affectedRows = Number(result?.affectedRows || 0);
|
|
594
|
+
if (affectedRows <= 0) break;
|
|
595
|
+
totalDeleted += affectedRows;
|
|
596
|
+
|
|
597
|
+
if (affectedRows < BAILEYS_EVENT_JOURNAL_PRUNE_DELETE_LIMIT) {
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (totalDeleted > 0) {
|
|
603
|
+
logger.info('Prune do journal de eventos Baileys concluído.', {
|
|
604
|
+
deletedRows: totalDeleted,
|
|
605
|
+
retentionDays: BAILEYS_EVENT_JOURNAL_RETENTION_DAYS,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const flushBaileysEventQueueCore = async () => {
|
|
611
|
+
while (baileysEventQueue.length > 0) {
|
|
612
|
+
const batch = baileysEventQueue.splice(0, BAILEYS_EVENT_BATCH_SIZE);
|
|
613
|
+
try {
|
|
614
|
+
await insertBaileysEventBatch(batch);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
if (error?.code === 'ER_NO_SUCH_TABLE') {
|
|
617
|
+
if (!baileysEventTableMissingLogged) {
|
|
618
|
+
baileysEventTableMissingLogged = true;
|
|
619
|
+
logger.warn('Tabela baileys_event_journal não encontrada. Execute db:init para habilitar o journal.', {
|
|
620
|
+
action: 'baileys_event_journal_table_missing',
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
logger.error('Falha ao inserir batch de eventos do Baileys.', { error: error.message });
|
|
627
|
+
recordError('db_write_queue');
|
|
628
|
+
baileysEventQueue.unshift(...batch);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
try {
|
|
634
|
+
await pruneBaileysEventJournal();
|
|
635
|
+
} catch (error) {
|
|
636
|
+
if (error?.code === 'ER_NO_SUCH_TABLE') return;
|
|
637
|
+
logger.error('Falha ao executar prune do journal de eventos Baileys.', {
|
|
638
|
+
error: error.message,
|
|
639
|
+
});
|
|
640
|
+
recordError('db_write_queue');
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
const messageFlushRunner = createFlushRunner({
|
|
645
|
+
onFlush: flushMessageQueueCore,
|
|
646
|
+
onError: (error) => {
|
|
647
|
+
logger.error('Falha ao executar flush da fila de mensagens.', { error: error.message });
|
|
648
|
+
recordError('db_write_queue');
|
|
649
|
+
},
|
|
650
|
+
onFinally: () => {
|
|
651
|
+
updateQueueMetrics();
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const chatFlushRunner = createFlushRunner({
|
|
656
|
+
onFlush: flushChatQueueCore,
|
|
657
|
+
onError: (error) => {
|
|
658
|
+
logger.error('Falha ao executar flush da fila de chats.', { error: error.message });
|
|
659
|
+
recordError('db_write_queue');
|
|
660
|
+
},
|
|
661
|
+
onFinally: () => {
|
|
662
|
+
updateQueueMetrics();
|
|
663
|
+
},
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
const baileysEventFlushRunner = createFlushRunner({
|
|
667
|
+
onFlush: flushBaileysEventQueueCore,
|
|
668
|
+
onError: (error) => {
|
|
669
|
+
logger.error('Falha ao executar flush da fila de eventos do Baileys.', {
|
|
670
|
+
error: error.message,
|
|
671
|
+
});
|
|
672
|
+
recordError('db_write_queue');
|
|
673
|
+
},
|
|
674
|
+
onFinally: () => {
|
|
675
|
+
updateQueueMetrics();
|
|
676
|
+
},
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Enfileira uma mensagem para INSERT no banco (INSERT IGNORE).
|
|
681
|
+
* - Evita duplicar message_id usando um Set.
|
|
682
|
+
* - Força flush se a fila estiver muito grande.
|
|
683
|
+
* - Agenda flush quando atinge o tamanho de batch.
|
|
684
|
+
*
|
|
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
|
|
686
|
+
* Objeto com os campos necessários para persistência.
|
|
687
|
+
* @returns {boolean} true se foi enfileirada; false se inválida/duplicada.
|
|
688
|
+
*/
|
|
689
|
+
export function queueMessageInsert(messageData) {
|
|
690
|
+
if (!messageData?.message_id) return false;
|
|
691
|
+
if (messagePendingIds.has(messageData.message_id)) return false;
|
|
692
|
+
|
|
693
|
+
const normalizedMessage = normalizeMessageForQueue(messageData);
|
|
694
|
+
|
|
695
|
+
if (messageQueue.length >= MESSAGE_QUEUE_MAX) {
|
|
696
|
+
logger.warn('Fila de mensagens cheia, forçando flush.', { size: messageQueue.length });
|
|
697
|
+
scheduleFlush();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
messagePendingIds.add(normalizedMessage.message_id);
|
|
701
|
+
messageQueue.push(normalizedMessage);
|
|
702
|
+
updateQueueMetrics();
|
|
703
|
+
|
|
704
|
+
if (messageQueue.length >= MESSAGE_BATCH_SIZE) {
|
|
705
|
+
scheduleFlush();
|
|
706
|
+
}
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Enfileira uma atualização de chat para UPSERT no banco.
|
|
712
|
+
* Suporta atualização parcial (merge com cache) e opção de “forçar nome”.
|
|
713
|
+
*
|
|
714
|
+
* Regras:
|
|
715
|
+
* - Detecta mudança de "raw_chat" via hash estável.
|
|
716
|
+
* - Detecta mudança de "name" apenas quando fornecido.
|
|
717
|
+
* - Aplica cooldown por chat para reduzir writes repetidos.
|
|
718
|
+
*
|
|
719
|
+
* @param {{id:string, name?:string, [key:string]:any}} chat - Objeto do chat (mínimo: {id}).
|
|
720
|
+
* @param {{partial?:boolean, forceName?:boolean}} [options={}]
|
|
721
|
+
* partial: se true, faz merge do chat com o estado base do cache.
|
|
722
|
+
* forceName: se true, sempre tenta gravar name (fallback para id).
|
|
723
|
+
* @returns {boolean} true se algo mudou e foi enfileirado; false se nada mudou ou inválido.
|
|
724
|
+
*/
|
|
725
|
+
export function queueChatUpdate(chat, options = {}) {
|
|
726
|
+
if (!chat || !chat.id) return false;
|
|
727
|
+
|
|
728
|
+
const now = Date.now();
|
|
729
|
+
const isPartial = Boolean(options.partial);
|
|
730
|
+
const forceName = Boolean(options.forceName);
|
|
731
|
+
const cache = chatCache.get(chat.id) || {
|
|
732
|
+
storedRaw: null,
|
|
733
|
+
storedHash: null,
|
|
734
|
+
storedName: null,
|
|
735
|
+
lastWriteAt: 0,
|
|
736
|
+
pendingRaw: null,
|
|
737
|
+
pendingHash: null,
|
|
738
|
+
pendingName: null,
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const baseRaw = cache.pendingRaw || cache.storedRaw;
|
|
742
|
+
const rawChat = isPartial ? (baseRaw ? { ...baseRaw, ...chat } : null) : chat;
|
|
743
|
+
const rawHash = rawChat ? hashObject(rawChat) : cache.pendingHash || cache.storedHash;
|
|
744
|
+
|
|
745
|
+
const providedName = forceName ? chat.name || chat.id : chat.name;
|
|
746
|
+
const nameProvided = providedName !== undefined && providedName !== null;
|
|
747
|
+
const name = nameProvided ? providedName : cache.pendingName || cache.storedName || null;
|
|
748
|
+
const compareHash = cache.pendingHash || cache.storedHash;
|
|
749
|
+
const compareName = cache.pendingName || cache.storedName;
|
|
750
|
+
|
|
751
|
+
const rawChanged = Boolean(rawChat && rawHash && rawHash !== compareHash);
|
|
752
|
+
const nameChanged = nameProvided && name !== compareName;
|
|
753
|
+
|
|
754
|
+
if (!rawChanged && !nameChanged) {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const nextAllowedAt = cache.lastWriteAt ? cache.lastWriteAt + CHAT_COOLDOWN_MS : now;
|
|
759
|
+
const entry = {
|
|
760
|
+
id: chat.id,
|
|
761
|
+
name: nameProvided ? name : null,
|
|
762
|
+
raw: rawChanged ? rawChat : null,
|
|
763
|
+
rawHash: rawHash || compareHash,
|
|
764
|
+
queuedAt: now,
|
|
765
|
+
nextAllowedAt,
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
chatQueue.set(chat.id, entry);
|
|
769
|
+
chatCache.set(chat.id, {
|
|
770
|
+
...cache,
|
|
771
|
+
pendingRaw: rawChat || cache.pendingRaw,
|
|
772
|
+
pendingHash: rawHash || cache.pendingHash,
|
|
773
|
+
pendingName: nameProvided ? name : cache.pendingName,
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
updateQueueMetrics();
|
|
777
|
+
scheduleFlush();
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Enfileira um evento resumido do Baileys para o journal de auditoria.
|
|
783
|
+
*
|
|
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
|
|
785
|
+
* @returns {boolean}
|
|
786
|
+
*/
|
|
787
|
+
export function queueBaileysEventInsert(eventData) {
|
|
788
|
+
const normalizedEvent = normalizeBaileysEventForQueue(eventData);
|
|
789
|
+
if (!normalizedEvent.event_name) return false;
|
|
790
|
+
|
|
791
|
+
if (baileysEventQueue.length >= BAILEYS_EVENT_QUEUE_MAX) {
|
|
792
|
+
logger.warn('Fila de eventos Baileys cheia, descartando item mais antigo.', {
|
|
793
|
+
size: baileysEventQueue.length,
|
|
794
|
+
max: BAILEYS_EVENT_QUEUE_MAX,
|
|
795
|
+
});
|
|
796
|
+
baileysEventQueue.shift();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
baileysEventQueue.push(normalizedEvent);
|
|
800
|
+
updateQueueMetrics();
|
|
801
|
+
|
|
802
|
+
if (baileysEventQueue.length >= BAILEYS_EVENT_BATCH_SIZE) {
|
|
803
|
+
scheduleFlush();
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Faz flush da fila de mensagens:
|
|
811
|
+
* - Processa em batches (MESSAGE_BATCH_SIZE).
|
|
812
|
+
* - Usa INSERT IGNORE para evitar duplicidade.
|
|
813
|
+
* - Em erro, re-enfileira o batch no início e interrompe para tentar depois.
|
|
814
|
+
*
|
|
815
|
+
* @returns {Promise<void>}
|
|
816
|
+
*/
|
|
817
|
+
async function flushMessageQueue() {
|
|
818
|
+
await messageFlushRunner.run();
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Faz flush da fila de chats:
|
|
823
|
+
* - Respeita cooldown (nextAllowedAt) por chat.
|
|
824
|
+
* - Processa em batches (CHAT_BATCH_SIZE).
|
|
825
|
+
* - Usa UPSERT para atualizar name/raw_chat quando fornecidos.
|
|
826
|
+
*
|
|
827
|
+
* @returns {Promise<void>}
|
|
828
|
+
*/
|
|
829
|
+
async function flushChatQueue() {
|
|
830
|
+
await chatFlushRunner.run();
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Faz flush da fila de eventos do Baileys.
|
|
835
|
+
*
|
|
836
|
+
* @returns {Promise<void>}
|
|
837
|
+
*/
|
|
838
|
+
async function flushBaileysEventQueue() {
|
|
839
|
+
await baileysEventFlushRunner.run();
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Executa flush de todas as filas (mensagens, chats, eventos do Baileys e LID).
|
|
844
|
+
* Usa Promise.allSettled para não “matar” as outras filas caso uma falhe.
|
|
845
|
+
*
|
|
846
|
+
* @returns {Promise<void>}
|
|
847
|
+
*/
|
|
848
|
+
export async function flushQueues() {
|
|
849
|
+
await Promise.allSettled([flushMessageQueue(), flushChatQueue(), flushBaileysEventQueue(), flushLidQueue()]);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
updateQueueMetrics();
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Timer periódico para garantir flush mesmo sem eventos/tráfego.
|
|
856
|
+
* O timer é "unref" quando disponível, para não segurar o processo aberto.
|
|
857
|
+
*/
|
|
858
|
+
if (FLUSH_INTERVAL_MS > 0) {
|
|
859
|
+
const timer = setInterval(() => {
|
|
860
|
+
flushQueues().catch((error) => {
|
|
861
|
+
logger.error('Erro ao executar flush periódico das filas.', { error: error.message });
|
|
862
|
+
recordError('db_write_queue');
|
|
863
|
+
});
|
|
864
|
+
}, FLUSH_INTERVAL_MS);
|
|
865
|
+
if (typeof timer.unref === 'function') {
|
|
866
|
+
timer.unref();
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Re-export do enfileiramento de update de LID.
|
|
872
|
+
* @type {(update:any)=>any}
|
|
873
|
+
*/
|
|
874
|
+
export { queueLidUpdate };
|