@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,758 @@
|
|
|
1
|
+
import { withTimeout } from '../../http/httpRequestUtils.js';
|
|
2
|
+
|
|
3
|
+
export const createStickerCatalogSystemContext = ({ executeQuery, tables, logger, getSystemMetrics, getActiveSocket, resolveSocketReadyState, resolveActiveSocketBotJid, resolveCatalogBotPhone, fetchPrometheusSummary, metricsEndpoint, systemSummaryCache, systemSummaryCacheSeconds, readmeSummaryCache, readmeSummaryCacheSeconds, readmeMessageTypeSampleLimit, readmeCommandPrefix, buildMenuCaption, buildStickerMenu, buildMediaMenu, buildQuoteMenu, buildAnimeMenu, buildAiMenu, buildStatsMenu, buildAdminMenu, profilePictureUrlFromActiveSocket, normalizeJid, getJidUser, globalRankCache, globalRankRefreshSeconds, marketplaceGlobalStatsCache, marketplaceGlobalStatsCacheSeconds }) => {
|
|
4
|
+
let globalRankRefreshTimer = null;
|
|
5
|
+
|
|
6
|
+
const buildSystemSummarySnapshot = async () => {
|
|
7
|
+
const system = getSystemMetrics();
|
|
8
|
+
const activeSocket = getActiveSocket();
|
|
9
|
+
let prometheus = null;
|
|
10
|
+
let prometheusError = null;
|
|
11
|
+
let platformError = null;
|
|
12
|
+
let usageError = null;
|
|
13
|
+
|
|
14
|
+
const socketReadyState = resolveSocketReadyState(activeSocket);
|
|
15
|
+
const botJid = resolveActiveSocketBotJid(activeSocket) || null;
|
|
16
|
+
const botPhone = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '') || null;
|
|
17
|
+
const botConnected = socketReadyState === 1 || (socketReadyState === null && Boolean(botJid));
|
|
18
|
+
const botConnectionStatus = botConnected ? 'online' : socketReadyState === 0 ? 'connecting' : 'offline';
|
|
19
|
+
|
|
20
|
+
let platform = {
|
|
21
|
+
total_users: null,
|
|
22
|
+
total_groups: null,
|
|
23
|
+
total_chats: null,
|
|
24
|
+
};
|
|
25
|
+
let usage = {
|
|
26
|
+
total_messages: null,
|
|
27
|
+
total_commands: null,
|
|
28
|
+
total_commands_source: tables.MESSAGE_ANALYSIS_EVENT,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
prometheus = await fetchPrometheusSummary();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
prometheusError = error?.message || 'Falha ao consultar /metrics';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const [chatTotalsRows, groupsMetadataTotalsRows, lidMapTotalsRows] = await Promise.all([
|
|
39
|
+
executeQuery(
|
|
40
|
+
`SELECT
|
|
41
|
+
COUNT(*) AS total_chats,
|
|
42
|
+
SUM(CASE WHEN id LIKE '%@g.us' THEN 1 ELSE 0 END) AS total_groups
|
|
43
|
+
FROM ${tables.CHATS}`,
|
|
44
|
+
),
|
|
45
|
+
executeQuery(`SELECT COUNT(*) AS total_groups FROM ${tables.GROUPS_METADATA}`),
|
|
46
|
+
executeQuery(`SELECT COUNT(*) AS total_users FROM ${tables.LID_MAP}`),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const chatsTotals = chatTotalsRows?.[0] || {};
|
|
50
|
+
const groupsMetadataTotals = groupsMetadataTotalsRows?.[0] || {};
|
|
51
|
+
const lidMapTotals = lidMapTotalsRows?.[0] || {};
|
|
52
|
+
const totalGroupsFromChats = Number(chatsTotals?.total_groups || 0);
|
|
53
|
+
const totalGroupsFromMetadata = Number(groupsMetadataTotals?.total_groups || 0);
|
|
54
|
+
const totalUsersFromLidMap = Number(lidMapTotals?.total_users || 0);
|
|
55
|
+
|
|
56
|
+
platform = {
|
|
57
|
+
total_users: totalUsersFromLidMap,
|
|
58
|
+
total_users_source: 'lid_map',
|
|
59
|
+
total_groups: Math.max(totalGroupsFromChats, totalGroupsFromMetadata),
|
|
60
|
+
total_chats: Number(chatsTotals?.total_chats || 0),
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
platformError = error?.message || 'Falha ao consultar totais de usuários/grupos';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const [messageTotalsRows, commandTotalsRows] = await Promise.all([
|
|
68
|
+
executeQuery(`SELECT COUNT(*) AS total_messages FROM ${tables.MESSAGES}`),
|
|
69
|
+
executeQuery(
|
|
70
|
+
`SELECT COUNT(*) AS total_commands
|
|
71
|
+
FROM ${tables.MESSAGE_ANALYSIS_EVENT}
|
|
72
|
+
WHERE is_command = 1
|
|
73
|
+
AND COALESCE(is_from_bot, 0) = 0`,
|
|
74
|
+
),
|
|
75
|
+
]);
|
|
76
|
+
const messageTotals = messageTotalsRows?.[0] || {};
|
|
77
|
+
const commandTotals = commandTotalsRows?.[0] || {};
|
|
78
|
+
usage = {
|
|
79
|
+
...usage,
|
|
80
|
+
total_messages: Number(messageTotals?.total_messages || 0),
|
|
81
|
+
total_commands: Number(commandTotals?.total_commands || 0),
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
usageError = error?.message || 'Falha ao consultar totais de mensagens/comandos';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hostCpuPercent = Number(system.usoCpuPercentual);
|
|
88
|
+
const hostMemoryPercent = Number(system.usoMemoriaPercentual);
|
|
89
|
+
const statusReasons = [];
|
|
90
|
+
if (!botConnected) statusReasons.push('bot_disconnected');
|
|
91
|
+
if (!prometheus) statusReasons.push('metrics_unavailable');
|
|
92
|
+
if (Number.isFinite(hostCpuPercent) && hostCpuPercent >= 90) statusReasons.push('host_cpu_high');
|
|
93
|
+
if (Number.isFinite(hostMemoryPercent) && hostMemoryPercent >= 90) statusReasons.push('host_memory_high');
|
|
94
|
+
const systemStatus = statusReasons.length ? 'degraded' : 'online';
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
data: {
|
|
98
|
+
system_status: systemStatus,
|
|
99
|
+
status_reasons: statusReasons,
|
|
100
|
+
bot: {
|
|
101
|
+
connected: botConnected,
|
|
102
|
+
connection_status: botConnectionStatus,
|
|
103
|
+
jid: botJid,
|
|
104
|
+
phone: botPhone,
|
|
105
|
+
ready_state: socketReadyState,
|
|
106
|
+
},
|
|
107
|
+
platform,
|
|
108
|
+
usage,
|
|
109
|
+
host: {
|
|
110
|
+
cpu_percent: system.usoCpuPercentual,
|
|
111
|
+
memory_percent: system.usoMemoriaPercentual,
|
|
112
|
+
memory_used: system.memoriaUsada,
|
|
113
|
+
memory_total: system.memoriaTotal,
|
|
114
|
+
uptime: system.uptimeSistema,
|
|
115
|
+
},
|
|
116
|
+
process: {
|
|
117
|
+
uptime: prometheus?.process_uptime || system.uptime,
|
|
118
|
+
node_version: system.versaoNode,
|
|
119
|
+
},
|
|
120
|
+
observability: {
|
|
121
|
+
lag_p99_ms: prometheus?.lag_p99_ms ?? null,
|
|
122
|
+
db_total: prometheus?.db_total ?? null,
|
|
123
|
+
db_slow: prometheus?.db_slow ?? null,
|
|
124
|
+
http_5xx_total: prometheus?.http_5xx_total ?? null,
|
|
125
|
+
http_latency_p95_ms: prometheus?.http_latency_p95_ms ?? null,
|
|
126
|
+
queue_peak: prometheus?.queue_peak ?? null,
|
|
127
|
+
},
|
|
128
|
+
updated_at: new Date().toISOString(),
|
|
129
|
+
},
|
|
130
|
+
meta: {
|
|
131
|
+
metrics_endpoint: metricsEndpoint,
|
|
132
|
+
metrics_ok: Boolean(prometheus),
|
|
133
|
+
metrics_error: prometheusError,
|
|
134
|
+
platform_error: platformError,
|
|
135
|
+
usage_error: usageError,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const getSystemSummaryCached = async () => {
|
|
141
|
+
const now = Date.now();
|
|
142
|
+
const hasValue = Boolean(systemSummaryCache.value);
|
|
143
|
+
|
|
144
|
+
if (hasValue && now < systemSummaryCache.expiresAt) {
|
|
145
|
+
return systemSummaryCache.value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!systemSummaryCache.pending) {
|
|
149
|
+
systemSummaryCache.pending = withTimeout(buildSystemSummarySnapshot(), 5000)
|
|
150
|
+
.then((payload) => {
|
|
151
|
+
systemSummaryCache.value = payload;
|
|
152
|
+
systemSummaryCache.expiresAt = Date.now() + systemSummaryCacheSeconds * 1000;
|
|
153
|
+
return payload;
|
|
154
|
+
})
|
|
155
|
+
.finally(() => {
|
|
156
|
+
systemSummaryCache.pending = null;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (hasValue) return systemSummaryCache.value;
|
|
161
|
+
return systemSummaryCache.pending;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const parseMessageTypeFromRaw = (rawMessage) => {
|
|
165
|
+
try {
|
|
166
|
+
const message = JSON.parse(rawMessage || '{}')?.message || {};
|
|
167
|
+
if (message.conversation || message.extendedTextMessage) return 'texto';
|
|
168
|
+
if (message.imageMessage) return 'imagem';
|
|
169
|
+
if (message.videoMessage) return 'video';
|
|
170
|
+
if (message.audioMessage) return 'audio';
|
|
171
|
+
if (message.stickerMessage) return 'figurinha';
|
|
172
|
+
if (message.documentMessage) return 'documento';
|
|
173
|
+
if (message.locationMessage) return 'localizacao';
|
|
174
|
+
if (message.reactionMessage) return 'reacao';
|
|
175
|
+
return 'outros';
|
|
176
|
+
} catch {
|
|
177
|
+
return 'outros';
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const formatPtBrInteger = (value) => Number(value || 0).toLocaleString('pt-BR');
|
|
182
|
+
|
|
183
|
+
const extractCommandsFromMenuLine = (line, commandPrefix) => {
|
|
184
|
+
const normalizedLine = String(line || '').trim();
|
|
185
|
+
if (!normalizedLine.startsWith('→')) return [];
|
|
186
|
+
|
|
187
|
+
const commandParts = normalizedLine
|
|
188
|
+
.replace(/^→\s*/, '')
|
|
189
|
+
.split('|')
|
|
190
|
+
.map((part) => part.trim())
|
|
191
|
+
.filter((part) => part.startsWith(commandPrefix));
|
|
192
|
+
|
|
193
|
+
return commandParts
|
|
194
|
+
.map((part) => {
|
|
195
|
+
const normalized = part.replace(/\s{2,}.*/, '').trim();
|
|
196
|
+
const withoutPrefix = normalized.slice(commandPrefix.length).trim();
|
|
197
|
+
if (!withoutPrefix) return '';
|
|
198
|
+
|
|
199
|
+
const tokens = withoutPrefix.split(/\s+/).filter(Boolean);
|
|
200
|
+
const selectedTokens = [];
|
|
201
|
+
for (const token of tokens) {
|
|
202
|
+
if (!/^[a-z0-9_-]+$/i.test(token)) break;
|
|
203
|
+
selectedTokens.push(token);
|
|
204
|
+
}
|
|
205
|
+
if (!selectedTokens.length) return '';
|
|
206
|
+
return `${commandPrefix}${selectedTokens.join(' ')}`;
|
|
207
|
+
})
|
|
208
|
+
.filter(Boolean);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const collectAvailableMenuCommands = (commandPrefix = readmeCommandPrefix) => {
|
|
212
|
+
const sections = [buildMenuCaption('OmniZap', commandPrefix), buildStickerMenu(commandPrefix), buildMediaMenu(commandPrefix), buildQuoteMenu(commandPrefix), buildAnimeMenu(commandPrefix), buildAiMenu(commandPrefix), buildStatsMenu(commandPrefix), buildAdminMenu(commandPrefix)];
|
|
213
|
+
|
|
214
|
+
const commands = new Set();
|
|
215
|
+
for (const section of sections) {
|
|
216
|
+
for (const line of String(section || '').split('\n')) {
|
|
217
|
+
const extracted = extractCommandsFromMenuLine(line, commandPrefix);
|
|
218
|
+
for (const command of extracted) {
|
|
219
|
+
commands.add(command);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return Array.from(commands).sort((left, right) => left.localeCompare(right, 'pt-BR'));
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const renderReadmeSnapshotMarkdown = ({ generatedAt, totals, topMessageTypes, commands }) => {
|
|
228
|
+
const typeRows = topMessageTypes.length ? topMessageTypes.map((entry) => `| \`${entry.type}\` | ${formatPtBrInteger(entry.total)} |`) : ['| `outros` | 0 |'];
|
|
229
|
+
|
|
230
|
+
const commandInline = commands.length ? commands.map((command) => `\`${command}\``).join(' · ') : 'Nenhum comando identificado no menu atual.';
|
|
231
|
+
|
|
232
|
+
return ['### Snapshot do Sistema', '', `> Atualizado em \`${generatedAt}\` | cache \`${readmeSummaryCacheSeconds}s\``, '', '| Métrica | Valor |', '| --- | ---: |', `| Usuários (lid_map) | ${formatPtBrInteger(totals.total_users)} |`, `| Grupos | ${formatPtBrInteger(totals.total_groups)} |`, `| Packs | ${formatPtBrInteger(totals.total_packs)} |`, `| Stickers | ${formatPtBrInteger(totals.total_stickers)} |`, `| Mensagens registradas | ${formatPtBrInteger(totals.total_messages)} |`, '', `#### Tipos de mensagem mais usados (amostra: ${formatPtBrInteger(totals.message_types_sample_size)})`, '| Tipo | Total |', '| --- | ---: |', ...typeRows, '', `<details><summary>Comandos disponíveis (${formatPtBrInteger(commands.length)})</summary>`, '', commandInline, '', '</details>', ''].join('\n');
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const buildReadmeSummarySnapshot = async () => {
|
|
236
|
+
const [lidMapTotalsRows, chatsTotalsRows, groupsMetadataTotalsRows, packTotalsRows, stickerTotalsRows, messageTotalsRows, messageTypeRows] = await Promise.all([
|
|
237
|
+
executeQuery(`SELECT COUNT(*) AS total_users FROM ${tables.LID_MAP}`),
|
|
238
|
+
executeQuery(
|
|
239
|
+
`SELECT
|
|
240
|
+
COUNT(*) AS total_chats,
|
|
241
|
+
SUM(CASE WHEN id LIKE '%@g.us' THEN 1 ELSE 0 END) AS total_groups
|
|
242
|
+
FROM ${tables.CHATS}`,
|
|
243
|
+
),
|
|
244
|
+
executeQuery(`SELECT COUNT(*) AS total_groups FROM ${tables.GROUPS_METADATA}`),
|
|
245
|
+
executeQuery(`SELECT COUNT(*) AS total_packs FROM ${tables.STICKER_PACK} WHERE deleted_at IS NULL`),
|
|
246
|
+
executeQuery(`SELECT COUNT(*) AS total_stickers FROM ${tables.STICKER_ASSET}`),
|
|
247
|
+
executeQuery(`SELECT COUNT(*) AS total_messages FROM ${tables.MESSAGES}`),
|
|
248
|
+
executeQuery(
|
|
249
|
+
`SELECT raw_message
|
|
250
|
+
FROM ${tables.MESSAGES}
|
|
251
|
+
WHERE raw_message IS NOT NULL
|
|
252
|
+
ORDER BY id DESC
|
|
253
|
+
LIMIT ${readmeMessageTypeSampleLimit}`,
|
|
254
|
+
),
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
const lidMapTotals = lidMapTotalsRows?.[0] || {};
|
|
258
|
+
const chatsTotals = chatsTotalsRows?.[0] || {};
|
|
259
|
+
const groupsMetadataTotals = groupsMetadataTotalsRows?.[0] || {};
|
|
260
|
+
const packTotals = packTotalsRows?.[0] || {};
|
|
261
|
+
const stickerTotals = stickerTotalsRows?.[0] || {};
|
|
262
|
+
const messageTotals = messageTotalsRows?.[0] || {};
|
|
263
|
+
|
|
264
|
+
const totalGroupsFromChats = Number(chatsTotals?.total_groups || 0);
|
|
265
|
+
const totalGroupsFromMetadata = Number(groupsMetadataTotals?.total_groups || 0);
|
|
266
|
+
const totalGroups = Math.max(totalGroupsFromChats, totalGroupsFromMetadata);
|
|
267
|
+
const totalMessages = Number(messageTotals?.total_messages || 0);
|
|
268
|
+
|
|
269
|
+
const typeCounts = new Map();
|
|
270
|
+
const sampledMessages = Array.isArray(messageTypeRows) ? messageTypeRows.length : 0;
|
|
271
|
+
for (const row of messageTypeRows || []) {
|
|
272
|
+
const type = parseMessageTypeFromRaw(row?.raw_message);
|
|
273
|
+
typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
|
|
274
|
+
}
|
|
275
|
+
const topMessageTypes = Array.from(typeCounts.entries())
|
|
276
|
+
.map(([type, total]) => ({ type, total: Number(total || 0) }))
|
|
277
|
+
.sort((left, right) => Number(right.total || 0) - Number(left.total || 0))
|
|
278
|
+
.slice(0, 8);
|
|
279
|
+
|
|
280
|
+
const commands = collectAvailableMenuCommands(readmeCommandPrefix);
|
|
281
|
+
const generatedAt = new Date().toISOString();
|
|
282
|
+
|
|
283
|
+
const totals = {
|
|
284
|
+
total_users: Number(lidMapTotals?.total_users || 0),
|
|
285
|
+
total_groups: totalGroups,
|
|
286
|
+
total_groups_from_chats: totalGroupsFromChats,
|
|
287
|
+
total_groups_from_metadata: totalGroupsFromMetadata,
|
|
288
|
+
total_chats: Number(chatsTotals?.total_chats || 0),
|
|
289
|
+
total_packs: Number(packTotals?.total_packs || 0),
|
|
290
|
+
total_stickers: Number(stickerTotals?.total_stickers || 0),
|
|
291
|
+
total_messages: totalMessages,
|
|
292
|
+
message_types_sample_size: sampledMessages,
|
|
293
|
+
message_types_total_coverage_percent: totalMessages > 0 ? Number(((sampledMessages / totalMessages) * 100).toFixed(2)) : 0,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const markdown = renderReadmeSnapshotMarkdown({
|
|
297
|
+
generatedAt,
|
|
298
|
+
totals,
|
|
299
|
+
topMessageTypes,
|
|
300
|
+
commands,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
data: {
|
|
305
|
+
generated_at: generatedAt,
|
|
306
|
+
cache_seconds: readmeSummaryCacheSeconds,
|
|
307
|
+
command_prefix: readmeCommandPrefix,
|
|
308
|
+
totals,
|
|
309
|
+
top_message_types: topMessageTypes,
|
|
310
|
+
commands,
|
|
311
|
+
markdown,
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const getReadmeSummaryCached = async () => {
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
const hasValue = Boolean(readmeSummaryCache.value);
|
|
319
|
+
|
|
320
|
+
if (hasValue && now < readmeSummaryCache.expiresAt) {
|
|
321
|
+
return readmeSummaryCache.value;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!readmeSummaryCache.pending) {
|
|
325
|
+
readmeSummaryCache.pending = withTimeout(buildReadmeSummarySnapshot(), 7000)
|
|
326
|
+
.then((payload) => {
|
|
327
|
+
readmeSummaryCache.value = payload;
|
|
328
|
+
readmeSummaryCache.expiresAt = Date.now() + readmeSummaryCacheSeconds * 1000;
|
|
329
|
+
return payload;
|
|
330
|
+
})
|
|
331
|
+
.finally(() => {
|
|
332
|
+
readmeSummaryCache.pending = null;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (hasValue) return readmeSummaryCache.value;
|
|
337
|
+
return readmeSummaryCache.pending;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const resolveBotUserCandidates = (activeSocket) => {
|
|
341
|
+
const candidates = new Set();
|
|
342
|
+
const botJidFromSocket = resolveActiveSocketBotJid(activeSocket);
|
|
343
|
+
const botUserFromSocket = getJidUser(botJidFromSocket || '');
|
|
344
|
+
if (botUserFromSocket) candidates.add(String(botUserFromSocket).trim());
|
|
345
|
+
const botPhoneFromCatalog = String(resolveCatalogBotPhone() || '').replace(/\D+/g, '');
|
|
346
|
+
if (botPhoneFromCatalog) candidates.add(botPhoneFromCatalog);
|
|
347
|
+
|
|
348
|
+
const envCandidates = [process.env.WHATSAPP_BOT_NUMBER, process.env.BOT_NUMBER, process.env.PHONE_NUMBER, process.env.BOT_PHONE_NUMBER];
|
|
349
|
+
|
|
350
|
+
for (const candidate of envCandidates) {
|
|
351
|
+
const digits = String(candidate || '').replace(/\D+/g, '');
|
|
352
|
+
if (digits) candidates.add(digits);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return Array.from(candidates).filter((value) => value.length >= 8);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const isSenderFromAnyBotUser = (senderId, botUsers) => {
|
|
359
|
+
const normalizedSender = String(senderId || '').trim();
|
|
360
|
+
if (!normalizedSender) return false;
|
|
361
|
+
return botUsers.some((botUser) => {
|
|
362
|
+
const safe = String(botUser || '').trim();
|
|
363
|
+
if (!safe) return false;
|
|
364
|
+
return normalizedSender === `${safe}@s.whatsapp.net` || normalizedSender.startsWith(`${safe}:`) || normalizedSender.startsWith(`${safe}@`);
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const sanitizeRankingPayloadByBot = (payload, botUsers) => {
|
|
369
|
+
const sourceRows = Array.isArray(payload?.rows) ? payload.rows : [];
|
|
370
|
+
const filteredRows = sourceRows.filter((row) => !isSenderFromAnyBotUser(row?.sender_id, botUsers));
|
|
371
|
+
const normalizedRows = filteredRows.slice(0, Number(payload?.limit || 5)).map((row, index) => ({
|
|
372
|
+
...row,
|
|
373
|
+
position: index + 1,
|
|
374
|
+
}));
|
|
375
|
+
const totalMessages = Number(payload?.total_messages || 0);
|
|
376
|
+
const topTotal = normalizedRows.reduce((acc, row) => acc + Number(row?.total_messages || 0), 0);
|
|
377
|
+
const topShare = totalMessages > 0 ? Number(((topTotal / totalMessages) * 100).toFixed(2)) : 0;
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
...payload,
|
|
381
|
+
rows: normalizedRows,
|
|
382
|
+
top_share_percent: topShare,
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const extractPushNameFromRaw = (rawMessage) => {
|
|
387
|
+
try {
|
|
388
|
+
const parsed = JSON.parse(rawMessage || '{}');
|
|
389
|
+
const direct = String(parsed?.pushName || '').trim();
|
|
390
|
+
if (direct) return direct;
|
|
391
|
+
|
|
392
|
+
const nested = String(parsed?.message?.extendedTextMessage?.contextInfo?.participantName || '').trim();
|
|
393
|
+
if (nested) return nested;
|
|
394
|
+
} catch {
|
|
395
|
+
return '';
|
|
396
|
+
}
|
|
397
|
+
return '';
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const resolveRankingDisplayName = async (senderId) => {
|
|
401
|
+
if (!senderId) return 'Desconhecido';
|
|
402
|
+
const fallback = `@${String(getJidUser(senderId) || senderId).trim()}`;
|
|
403
|
+
try {
|
|
404
|
+
const rows = await executeQuery(
|
|
405
|
+
`SELECT raw_message FROM ${tables.MESSAGES}
|
|
406
|
+
WHERE sender_id = ?
|
|
407
|
+
AND raw_message IS NOT NULL
|
|
408
|
+
ORDER BY id DESC
|
|
409
|
+
LIMIT 12`,
|
|
410
|
+
[senderId],
|
|
411
|
+
);
|
|
412
|
+
for (const row of rows) {
|
|
413
|
+
const name = extractPushNameFromRaw(row?.raw_message);
|
|
414
|
+
if (name) return name;
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
return fallback;
|
|
418
|
+
}
|
|
419
|
+
return fallback;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const resolveRankingAvatarUrl = async (senderId) => {
|
|
423
|
+
if (!senderId) return null;
|
|
424
|
+
const normalized = normalizeJid(senderId) || senderId;
|
|
425
|
+
try {
|
|
426
|
+
return await profilePictureUrlFromActiveSocket(normalized, 'image');
|
|
427
|
+
} catch {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const buildGlobalRankingSummary = async () => {
|
|
433
|
+
const limit = 5;
|
|
434
|
+
const queryLimit = 12;
|
|
435
|
+
const sampleLimit = 50000;
|
|
436
|
+
const activeSocket = getActiveSocket();
|
|
437
|
+
const botUsers = resolveBotUserCandidates(activeSocket);
|
|
438
|
+
|
|
439
|
+
const whereClauses = ['sender_id IS NOT NULL'];
|
|
440
|
+
const params = [];
|
|
441
|
+
for (const botUser of botUsers) {
|
|
442
|
+
whereClauses.push('sender_id <> ?');
|
|
443
|
+
params.push(`${botUser}@s.whatsapp.net`);
|
|
444
|
+
whereClauses.push('sender_id NOT LIKE ?');
|
|
445
|
+
whereClauses.push('sender_id NOT LIKE ?');
|
|
446
|
+
params.push(`${botUser}@%`, `${botUser}:%`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const where = whereClauses.join(' AND ');
|
|
450
|
+
const recentScopeSql = `SELECT id, sender_id, timestamp, raw_message FROM ${tables.MESSAGES} WHERE ${where} ORDER BY id DESC LIMIT ${sampleLimit}`;
|
|
451
|
+
|
|
452
|
+
const [totalRow] = await executeQuery(`SELECT COUNT(*) AS total FROM (${recentScopeSql}) recent_scope`, params);
|
|
453
|
+
const totalMessages = Number(totalRow?.total || 0);
|
|
454
|
+
|
|
455
|
+
const rows = await executeQuery(
|
|
456
|
+
`SELECT
|
|
457
|
+
recent_scope.sender_id,
|
|
458
|
+
CONCAT('@', SUBSTRING_INDEX(recent_scope.sender_id, '@', 1)) AS display_name,
|
|
459
|
+
COUNT(*) AS total_messages,
|
|
460
|
+
MAX(
|
|
461
|
+
CASE
|
|
462
|
+
WHEN recent_scope.timestamp > 1000000000000 THEN FROM_UNIXTIME(recent_scope.timestamp / 1000)
|
|
463
|
+
WHEN recent_scope.timestamp > 1000000000 THEN FROM_UNIXTIME(recent_scope.timestamp)
|
|
464
|
+
ELSE recent_scope.timestamp
|
|
465
|
+
END
|
|
466
|
+
) AS last_message
|
|
467
|
+
FROM (${recentScopeSql}) recent_scope
|
|
468
|
+
GROUP BY recent_scope.sender_id
|
|
469
|
+
ORDER BY total_messages DESC
|
|
470
|
+
LIMIT ${queryLimit}`,
|
|
471
|
+
params,
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
const typeRows = await executeQuery(
|
|
475
|
+
`SELECT recent_scope.raw_message
|
|
476
|
+
FROM (${recentScopeSql}) recent_scope
|
|
477
|
+
WHERE recent_scope.raw_message IS NOT NULL
|
|
478
|
+
ORDER BY recent_scope.id DESC
|
|
479
|
+
LIMIT 300`,
|
|
480
|
+
params,
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const typeCounts = new Map();
|
|
484
|
+
for (const row of typeRows) {
|
|
485
|
+
const type = parseMessageTypeFromRaw(row?.raw_message);
|
|
486
|
+
typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
|
|
487
|
+
}
|
|
488
|
+
const sortedTypes = Array.from(typeCounts.entries()).sort((left, right) => right[1] - left[1]);
|
|
489
|
+
const topType = sortedTypes[0]?.[0] || null;
|
|
490
|
+
const topTypeCount = Number(sortedTypes[0]?.[1] || 0);
|
|
491
|
+
|
|
492
|
+
const topTotal = rows.reduce((acc, row) => acc + Number(row?.total_messages || 0), 0);
|
|
493
|
+
const topShare = totalMessages > 0 ? Number(((topTotal / totalMessages) * 100).toFixed(2)) : 0;
|
|
494
|
+
|
|
495
|
+
const rowsWithoutBot = rows.filter((row) => !isSenderFromAnyBotUser(row?.sender_id, botUsers)).slice(0, limit);
|
|
496
|
+
|
|
497
|
+
const rowsEnriched = await Promise.all(
|
|
498
|
+
rowsWithoutBot.map(async (row, index) => {
|
|
499
|
+
const total = Number(row?.total_messages || 0);
|
|
500
|
+
const percent = totalMessages > 0 ? Number(((total / totalMessages) * 100).toFixed(2)) : 0;
|
|
501
|
+
const senderId = row?.sender_id || null;
|
|
502
|
+
const displayName = await resolveRankingDisplayName(senderId);
|
|
503
|
+
const avatarUrl = await resolveRankingAvatarUrl(senderId);
|
|
504
|
+
return {
|
|
505
|
+
position: index + 1,
|
|
506
|
+
sender_id: senderId,
|
|
507
|
+
mention_id: senderId,
|
|
508
|
+
display_name: displayName || row?.display_name || senderId || 'Desconhecido',
|
|
509
|
+
avatar_url: avatarUrl,
|
|
510
|
+
total_messages: total,
|
|
511
|
+
percent_of_total: percent,
|
|
512
|
+
last_message: row?.last_message ? new Date(row.last_message).toISOString() : null,
|
|
513
|
+
};
|
|
514
|
+
}),
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
limit,
|
|
519
|
+
sample_limit: sampleLimit,
|
|
520
|
+
total_messages: totalMessages,
|
|
521
|
+
top_share_percent: topShare,
|
|
522
|
+
top_type: topType,
|
|
523
|
+
top_type_count: topTypeCount,
|
|
524
|
+
rows: rowsEnriched,
|
|
525
|
+
updated_at: new Date().toISOString(),
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const getGlobalRankingSummaryCached = async () => {
|
|
530
|
+
const now = Date.now();
|
|
531
|
+
const hasValue = Boolean(globalRankCache.value);
|
|
532
|
+
|
|
533
|
+
if (hasValue && now < globalRankCache.expiresAt) {
|
|
534
|
+
return globalRankCache.value;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (!globalRankCache.pending) {
|
|
538
|
+
globalRankCache.pending = withTimeout(buildGlobalRankingSummary(), 5000)
|
|
539
|
+
.then((data) => {
|
|
540
|
+
globalRankCache.value = data;
|
|
541
|
+
globalRankCache.expiresAt = Date.now() + globalRankRefreshSeconds * 1000;
|
|
542
|
+
return data;
|
|
543
|
+
})
|
|
544
|
+
.finally(() => {
|
|
545
|
+
globalRankCache.pending = null;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (hasValue) {
|
|
550
|
+
return globalRankCache.value;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return globalRankCache.pending;
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const scheduleGlobalRankingPreload = () => {
|
|
557
|
+
if (globalRankRefreshTimer) return;
|
|
558
|
+
|
|
559
|
+
getGlobalRankingSummaryCached().catch((error) => {
|
|
560
|
+
logger.warn('Falha no preload inicial do ranking global.', {
|
|
561
|
+
action: 'global_ranking_preload_init_error',
|
|
562
|
+
error: error?.message,
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
globalRankRefreshTimer = setInterval(() => {
|
|
567
|
+
globalRankCache.expiresAt = 0;
|
|
568
|
+
getGlobalRankingSummaryCached().catch((error) => {
|
|
569
|
+
logger.warn('Falha ao atualizar cache do ranking global em background.', {
|
|
570
|
+
action: 'global_ranking_preload_refresh_error',
|
|
571
|
+
error: error?.message,
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
}, globalRankRefreshSeconds * 1000);
|
|
575
|
+
|
|
576
|
+
if (typeof globalRankRefreshTimer?.unref === 'function') {
|
|
577
|
+
globalRankRefreshTimer.unref();
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const buildLastSevenUtcDateKeys = () => {
|
|
582
|
+
const now = new Date();
|
|
583
|
+
const todayUtc = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
584
|
+
return Array.from({ length: 7 }).map((_, index) => {
|
|
585
|
+
const date = new Date(todayUtc - (6 - index) * 24 * 60 * 60 * 1000);
|
|
586
|
+
return date.toISOString().slice(0, 10);
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const toUtcDayKey = (value) => {
|
|
591
|
+
if (!value) return '';
|
|
592
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
593
|
+
const text = String(value).trim();
|
|
594
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(text)) return text.slice(0, 10);
|
|
595
|
+
const parsed = Date.parse(text);
|
|
596
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString().slice(0, 10) : '';
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const mapRowsByDayKey = (rows, valueField = 'total') => {
|
|
600
|
+
const map = new Map();
|
|
601
|
+
(Array.isArray(rows) ? rows : []).forEach((row) => {
|
|
602
|
+
const dayKey = toUtcDayKey(row?.day_key);
|
|
603
|
+
if (!dayKey) return;
|
|
604
|
+
map.set(dayKey, Number(row?.[valueField] || 0));
|
|
605
|
+
});
|
|
606
|
+
return map;
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const buildMarketplaceGlobalStatsSnapshot = async () => {
|
|
610
|
+
const visiblePublishedVisibility = ['public', 'unlisted'];
|
|
611
|
+
const placeholders = visiblePublishedVisibility.map(() => '?').join(', ');
|
|
612
|
+
const dayKeys = buildLastSevenUtcDateKeys();
|
|
613
|
+
const dayFilterSql = `UTC_DATE() - INTERVAL 6 DAY`;
|
|
614
|
+
|
|
615
|
+
const [packTotalsRows, stickerTotalsRows, stickersWithoutPackRows, engagementTotalsRows, dailyPacksRows, dailyStickersRows, dailyInteractionRows] = await Promise.all([
|
|
616
|
+
executeQuery(
|
|
617
|
+
`SELECT
|
|
618
|
+
COUNT(*) AS total_packs,
|
|
619
|
+
COUNT(DISTINCT publisher) AS creators_total,
|
|
620
|
+
SUM(CASE WHEN created_at >= (UTC_TIMESTAMP() - INTERVAL 7 DAY) THEN 1 ELSE 0 END) AS packs_last_7_days
|
|
621
|
+
FROM ${tables.STICKER_PACK}
|
|
622
|
+
WHERE deleted_at IS NULL
|
|
623
|
+
AND status = 'published'
|
|
624
|
+
AND COALESCE(pack_status, 'ready') = 'ready'
|
|
625
|
+
AND visibility IN (${placeholders})`,
|
|
626
|
+
visiblePublishedVisibility,
|
|
627
|
+
),
|
|
628
|
+
executeQuery(`SELECT COUNT(*) AS total_stickers FROM ${tables.STICKER_ASSET}`),
|
|
629
|
+
executeQuery(
|
|
630
|
+
`SELECT COUNT(*) AS stickers_without_pack
|
|
631
|
+
FROM ${tables.STICKER_ASSET} a
|
|
632
|
+
LEFT JOIN ${tables.STICKER_PACK_ITEM} i ON i.sticker_id = a.id
|
|
633
|
+
WHERE i.sticker_id IS NULL`,
|
|
634
|
+
),
|
|
635
|
+
executeQuery(
|
|
636
|
+
`SELECT
|
|
637
|
+
COALESCE(SUM(e.open_count), 0) AS total_clicks,
|
|
638
|
+
COALESCE(SUM(e.like_count), 0) AS total_likes
|
|
639
|
+
FROM ${tables.STICKER_PACK_ENGAGEMENT} e
|
|
640
|
+
INNER JOIN ${tables.STICKER_PACK} p ON p.id = e.pack_id
|
|
641
|
+
WHERE p.deleted_at IS NULL
|
|
642
|
+
AND p.status = 'published'
|
|
643
|
+
AND COALESCE(p.pack_status, 'ready') = 'ready'
|
|
644
|
+
AND p.visibility IN (${placeholders})`,
|
|
645
|
+
visiblePublishedVisibility,
|
|
646
|
+
),
|
|
647
|
+
executeQuery(
|
|
648
|
+
`SELECT DATE(created_at) AS day_key, COUNT(*) AS total
|
|
649
|
+
FROM ${tables.STICKER_PACK}
|
|
650
|
+
WHERE deleted_at IS NULL
|
|
651
|
+
AND status = 'published'
|
|
652
|
+
AND COALESCE(pack_status, 'ready') = 'ready'
|
|
653
|
+
AND visibility IN (${placeholders})
|
|
654
|
+
AND created_at >= (${dayFilterSql})
|
|
655
|
+
GROUP BY DATE(created_at)`,
|
|
656
|
+
visiblePublishedVisibility,
|
|
657
|
+
),
|
|
658
|
+
executeQuery(
|
|
659
|
+
`SELECT DATE(created_at) AS day_key, COUNT(*) AS total
|
|
660
|
+
FROM ${tables.STICKER_ASSET}
|
|
661
|
+
WHERE created_at >= (${dayFilterSql})
|
|
662
|
+
GROUP BY DATE(created_at)`,
|
|
663
|
+
),
|
|
664
|
+
executeQuery(
|
|
665
|
+
`SELECT DATE(ev.created_at) AS day_key, ev.interaction, COUNT(*) AS total
|
|
666
|
+
FROM ${tables.STICKER_PACK_INTERACTION_EVENT} ev
|
|
667
|
+
INNER JOIN ${tables.STICKER_PACK} p ON p.id = ev.pack_id
|
|
668
|
+
WHERE ev.created_at >= (${dayFilterSql})
|
|
669
|
+
AND ev.interaction IN ('open', 'like')
|
|
670
|
+
AND p.deleted_at IS NULL
|
|
671
|
+
AND p.status = 'published'
|
|
672
|
+
AND COALESCE(p.pack_status, 'ready') = 'ready'
|
|
673
|
+
AND p.visibility IN (${placeholders})
|
|
674
|
+
GROUP BY DATE(ev.created_at), ev.interaction`,
|
|
675
|
+
visiblePublishedVisibility,
|
|
676
|
+
),
|
|
677
|
+
]);
|
|
678
|
+
|
|
679
|
+
const packTotals = packTotalsRows?.[0] || {};
|
|
680
|
+
const stickerTotals = stickerTotalsRows?.[0] || {};
|
|
681
|
+
const stickersWithoutPack = stickersWithoutPackRows?.[0] || {};
|
|
682
|
+
const engagementTotals = engagementTotalsRows?.[0] || {};
|
|
683
|
+
|
|
684
|
+
const dailyPacksByDay = mapRowsByDayKey(dailyPacksRows, 'total');
|
|
685
|
+
const dailyStickersByDay = mapRowsByDayKey(dailyStickersRows, 'total');
|
|
686
|
+
const dailyOpensByDay = new Map();
|
|
687
|
+
const dailyLikesByDay = new Map();
|
|
688
|
+
(Array.isArray(dailyInteractionRows) ? dailyInteractionRows : []).forEach((row) => {
|
|
689
|
+
const dayKey = toUtcDayKey(row?.day_key);
|
|
690
|
+
const interaction = String(row?.interaction || '')
|
|
691
|
+
.trim()
|
|
692
|
+
.toLowerCase();
|
|
693
|
+
const total = Number(row?.total || 0);
|
|
694
|
+
if (!dayKey) return;
|
|
695
|
+
if (interaction === 'open') dailyOpensByDay.set(dayKey, total);
|
|
696
|
+
if (interaction === 'like') dailyLikesByDay.set(dayKey, total);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const seriesLast7Days = dayKeys.map((day) => ({
|
|
700
|
+
date: day,
|
|
701
|
+
packs_published: Number(dailyPacksByDay.get(day) || 0),
|
|
702
|
+
stickers_created: Number(dailyStickersByDay.get(day) || 0),
|
|
703
|
+
clicks: Number(dailyOpensByDay.get(day) || 0),
|
|
704
|
+
likes: Number(dailyLikesByDay.get(day) || 0),
|
|
705
|
+
}));
|
|
706
|
+
|
|
707
|
+
const likesLast7Days = seriesLast7Days.reduce((acc, row) => acc + Number(row.likes || 0), 0);
|
|
708
|
+
const clicksLast7Days = seriesLast7Days.reduce((acc, row) => acc + Number(row.clicks || 0), 0);
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
total_packs: Number(packTotals?.total_packs || 0),
|
|
712
|
+
total_stickers: Number(stickerTotals?.total_stickers || 0),
|
|
713
|
+
total_clicks: Number(engagementTotals?.total_clicks || 0),
|
|
714
|
+
total_likes: Number(engagementTotals?.total_likes || 0),
|
|
715
|
+
packs_last_7_days: Number(packTotals?.packs_last_7_days || 0),
|
|
716
|
+
stickers_without_pack: Number(stickersWithoutPack?.stickers_without_pack || 0),
|
|
717
|
+
creators_total: Number(packTotals?.creators_total || 0),
|
|
718
|
+
clicks_last_7_days: Number(clicksLast7Days || 0),
|
|
719
|
+
likes_last_7_days: Number(likesLast7Days || 0),
|
|
720
|
+
series_last_7_days: seriesLast7Days,
|
|
721
|
+
updated_at: new Date().toISOString(),
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
const getMarketplaceGlobalStatsCached = async () => {
|
|
726
|
+
const now = Date.now();
|
|
727
|
+
const hasValue = Boolean(marketplaceGlobalStatsCache.value);
|
|
728
|
+
if (hasValue && now < marketplaceGlobalStatsCache.expiresAt) {
|
|
729
|
+
return marketplaceGlobalStatsCache.value;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (!marketplaceGlobalStatsCache.pending) {
|
|
733
|
+
marketplaceGlobalStatsCache.pending = withTimeout(buildMarketplaceGlobalStatsSnapshot(), 5000)
|
|
734
|
+
.then((data) => {
|
|
735
|
+
marketplaceGlobalStatsCache.value = data;
|
|
736
|
+
marketplaceGlobalStatsCache.expiresAt = Date.now() + marketplaceGlobalStatsCacheSeconds * 1000;
|
|
737
|
+
return data;
|
|
738
|
+
})
|
|
739
|
+
.finally(() => {
|
|
740
|
+
marketplaceGlobalStatsCache.pending = null;
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (hasValue) return marketplaceGlobalStatsCache.value;
|
|
745
|
+
return marketplaceGlobalStatsCache.pending;
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
return {
|
|
749
|
+
withTimeout,
|
|
750
|
+
getSystemSummaryCached,
|
|
751
|
+
getReadmeSummaryCached,
|
|
752
|
+
resolveBotUserCandidates,
|
|
753
|
+
sanitizeRankingPayloadByBot,
|
|
754
|
+
getGlobalRankingSummaryCached,
|
|
755
|
+
scheduleGlobalRankingPreload,
|
|
756
|
+
getMarketplaceGlobalStatsCached,
|
|
757
|
+
};
|
|
758
|
+
};
|