@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,446 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { URL } from 'node:url';
|
|
4
|
+
import logger from '#logger';
|
|
5
|
+
import { downloadMediaMessage, extractMediaDetails, getJidUser } from '../../config/index.js';
|
|
6
|
+
import { addStickerMetadata } from './addStickerMetadata.js';
|
|
7
|
+
import { convertToWebp } from './convertToWebp.js';
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
+
import { sendAndStore } from '../../services/messaging/messagePersistenceService.js';
|
|
10
|
+
import { addStickerToAutoPack } from '../stickerPackModule/autoPackCollectorRuntime.js';
|
|
11
|
+
import { getAdminJid } from '../../config/index.js';
|
|
12
|
+
import { getStickerUsageText } from './stickerConfigRuntime.js';
|
|
13
|
+
|
|
14
|
+
const adminJid = getAdminJid();
|
|
15
|
+
|
|
16
|
+
const TEMP_DIR = path.join(process.cwd(), 'temp', 'stickers');
|
|
17
|
+
const MAX_FILE_SIZE = 1 * 1024 * 1024; // 1MB
|
|
18
|
+
const SUPPORTED_MEDIA_TYPES = new Set(['image', 'video', 'sticker']);
|
|
19
|
+
const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
|
|
20
|
+
const DEFAULT_STICKER_PACK_NAME = (process.env.STICKER_DEFAULT_PACK_NAME || '').trim() || 'https://omnizap.shop/';
|
|
21
|
+
const AUTO_PACK_NOTICE_ENABLED = process.env.STICKER_PACK_AUTO_COLLECT_NOTIFY !== 'false';
|
|
22
|
+
const AUTO_PACK_MAX_ITEMS = Math.max(1, Number(process.env.STICKER_PACK_MAX_ITEMS) || 30);
|
|
23
|
+
const STICKER_WEB_PATH = normalizeBasePath(process.env.STICKER_WEB_PATH, '/stickers');
|
|
24
|
+
const STICKER_WEB_ORIGIN = resolveStickerWebOrigin();
|
|
25
|
+
|
|
26
|
+
function normalizeBasePath(value, fallback) {
|
|
27
|
+
const raw = String(value || '').trim() || fallback;
|
|
28
|
+
const withLeadingSlash = raw.startsWith('/') ? raw : `/${raw}`;
|
|
29
|
+
const withoutTrailingSlash = withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/') ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
|
30
|
+
return withoutTrailingSlash || fallback;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeOrigin(value) {
|
|
34
|
+
const raw = String(value || '').trim();
|
|
35
|
+
if (!raw) return null;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const parsed = new URL(raw);
|
|
39
|
+
if (!parsed.protocol || !parsed.host) return null;
|
|
40
|
+
return parsed.origin;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveStickerWebOrigin() {
|
|
47
|
+
const candidates = [process.env.STICKER_WEB_ORIGIN, process.env.APP_BASE_URL, process.env.PUBLIC_BASE_URL, process.env.SITE_URL, process.env.WEB_URL, process.env.BASE_URL];
|
|
48
|
+
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
const normalized = normalizeOrigin(candidate);
|
|
51
|
+
if (normalized) return normalized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fromDefaultPack = normalizeOrigin(DEFAULT_STICKER_PACK_NAME);
|
|
55
|
+
return fromDefaultPack || 'https://omnizap.shop';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildPackWebUrl(packKey) {
|
|
59
|
+
const normalizedPackKey = String(packKey || '').trim();
|
|
60
|
+
if (!normalizedPackKey) return null;
|
|
61
|
+
|
|
62
|
+
return `${STICKER_WEB_ORIGIN}${STICKER_WEB_PATH}/${encodeURIComponent(normalizedPackKey)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isPackPubliclyVisible(pack) {
|
|
66
|
+
const visibility = String(pack?.visibility || '')
|
|
67
|
+
.trim()
|
|
68
|
+
.toLowerCase();
|
|
69
|
+
const status = String(pack?.status || 'published')
|
|
70
|
+
.trim()
|
|
71
|
+
.toLowerCase();
|
|
72
|
+
const packStatus = String(pack?.pack_status || 'ready')
|
|
73
|
+
.trim()
|
|
74
|
+
.toLowerCase();
|
|
75
|
+
return (visibility === 'public' || visibility === 'unlisted') && status === 'published' && packStatus === 'ready';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resultado da criação/verificação de diretórios temporários do usuário.
|
|
80
|
+
* @typedef {Object} EnsureDirectoriesResult
|
|
81
|
+
* @property {boolean} success - Indica se o diretório está pronto para uso.
|
|
82
|
+
* @property {string} [error] - Mensagem amigável em caso de falha.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Metadados normalizados para o pacote de stickers.
|
|
87
|
+
* @typedef {Object} StickerMetadata
|
|
88
|
+
* @property {string} packName - Nome final do pacote.
|
|
89
|
+
* @property {string} packAuthor - Autor final do pacote.
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Opções de processamento para geração de sticker.
|
|
94
|
+
* @typedef {Object} ProcessStickerOptions
|
|
95
|
+
* @property {boolean} [includeQuotedMedia=true] - Se deve permitir mídia de mensagem citada.
|
|
96
|
+
* @property {boolean} [showAutoPackNotice=true] - Se deve avisar no chat sobre a coleta automática no pack.
|
|
97
|
+
* @property {string} [commandPrefix='/'] - Prefixo para mensagens de ajuda/comando.
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Verifica se o tipo de mídia é suportado para conversão em sticker.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} mediaType - Tipo normalizado retornado pelo Baileys.
|
|
104
|
+
* @returns {boolean} `true` quando o tipo pode ser convertido em sticker.
|
|
105
|
+
*/
|
|
106
|
+
export function isSupportedStickerMediaType(mediaType) {
|
|
107
|
+
return SUPPORTED_MEDIA_TYPES.has(mediaType);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Extrai uma mídia válida para sticker, com suporte opcional a mensagem citada.
|
|
112
|
+
*
|
|
113
|
+
* @param {import('@whiskeysockets/baileys').WAMessage} messageInfo - Mensagem recebida.
|
|
114
|
+
* @param {{ includeQuoted?: boolean }} [options={}] - Opções de extração.
|
|
115
|
+
* @returns {{ mediaType: string, mediaKey: object, isQuoted?: boolean, details?: object }|null}
|
|
116
|
+
*/
|
|
117
|
+
export function extractSupportedStickerMediaDetails(messageInfo, options = {}) {
|
|
118
|
+
const mediaDetails = extractMediaDetails(messageInfo, options);
|
|
119
|
+
if (!mediaDetails || !isSupportedStickerMediaType(mediaDetails.mediaType)) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return mediaDetails;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Garante que o diretório temporário do usuário para stickers existe.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} userId - Identificador do usuário usado no diretório temporário.
|
|
129
|
+
* @returns {Promise<EnsureDirectoriesResult>} Status da preparação do diretório.
|
|
130
|
+
*/
|
|
131
|
+
async function ensureDirectories(userId) {
|
|
132
|
+
if (!userId) {
|
|
133
|
+
logger.error('ensureDirectories: o ID do usuário é obrigatório.');
|
|
134
|
+
return { success: false, error: 'ID do usuário é obrigatório.' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const sanitizedUserId = String(userId).replace(/[^\w.-]/g, '_');
|
|
139
|
+
|
|
140
|
+
const userStickerDir = path.join(TEMP_DIR, sanitizedUserId);
|
|
141
|
+
|
|
142
|
+
await fs.mkdir(TEMP_DIR, { recursive: true });
|
|
143
|
+
|
|
144
|
+
await fs.mkdir(userStickerDir, { recursive: true });
|
|
145
|
+
|
|
146
|
+
return { success: true };
|
|
147
|
+
} catch (error) {
|
|
148
|
+
logger.error(`Erro ao criar diretórios para o usuário ${userId}: ${error.message}`, {
|
|
149
|
+
label: 'ensureDirectories',
|
|
150
|
+
userId,
|
|
151
|
+
error,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return { success: false, error: 'Erro ao preparar diretório do usuário.' };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Verifica se o tamanho da mídia está dentro do limite permitido.
|
|
160
|
+
*
|
|
161
|
+
* @param {{ fileLength?: number }} mediaKey - Estrutura de mídia retornada pelo Baileys.
|
|
162
|
+
* @param {string} mediaType - Tipo normalizado da mídia (ex.: image, video, sticker).
|
|
163
|
+
* @param {number} [maxFileSize=MAX_FILE_SIZE] - Tamanho máximo permitido em bytes.
|
|
164
|
+
* @returns {boolean} `true` quando o tamanho está dentro do limite; caso contrário `false`.
|
|
165
|
+
*/
|
|
166
|
+
function checkMediaSize(mediaKey, mediaType, maxFileSize = MAX_FILE_SIZE) {
|
|
167
|
+
const fileLength = mediaKey?.fileLength || 0;
|
|
168
|
+
const formatBytes = (bytes) => (bytes / (1024 * 1024)).toFixed(2) + ' MB';
|
|
169
|
+
logger.debug(`checkMediaSize Verificando tamanho da mídia (${mediaType}): ${formatBytes(fileLength)}`);
|
|
170
|
+
if (fileLength > maxFileSize) {
|
|
171
|
+
logger.warn(`checkMediaSize Mídia (${mediaType}) muito grande: ${formatBytes(fileLength)}`);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Faz o parsing do texto recebido para packName e packAuthor.
|
|
179
|
+
* Se o texto contiver '/', separa em dois: packName/packAuthor.
|
|
180
|
+
* Caso contrário, usa o texto como packName e o senderName como autor.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} text - Texto extra recebido com o comando.
|
|
183
|
+
* @param {string} senderName - Nome exibido do remetente.
|
|
184
|
+
* @returns {StickerMetadata} Objeto pronto para uso em `addStickerMetadata`.
|
|
185
|
+
*/
|
|
186
|
+
function parseStickerMetaText(text, senderName) {
|
|
187
|
+
let packName = DEFAULT_STICKER_PACK_NAME;
|
|
188
|
+
let packAuthor = senderName || 'OmniZap System';
|
|
189
|
+
if (text) {
|
|
190
|
+
const idx = text.indexOf('/');
|
|
191
|
+
if (idx !== -1) {
|
|
192
|
+
const name = text.slice(0, idx).trim();
|
|
193
|
+
const author = text.slice(idx + 1).trim();
|
|
194
|
+
if (name) packName = name;
|
|
195
|
+
if (author) packAuthor = author;
|
|
196
|
+
} else if (text.trim()) {
|
|
197
|
+
packName = text.trim();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { packName, packAuthor };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function buildAutoPackNoticeText(result, commandPrefix = DEFAULT_COMMAND_PREFIX) {
|
|
204
|
+
if (!result || result.status === 'skipped') {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const pack = result.pack || {};
|
|
209
|
+
const packName = pack.name || 'Minhas Figurinhas';
|
|
210
|
+
const packIdentifier = pack.pack_key || pack.id || '<pack>';
|
|
211
|
+
const packWebUrl = isPackPubliclyVisible(pack) ? buildPackWebUrl(pack.pack_key) : null;
|
|
212
|
+
const profileUrl = `${STICKER_WEB_ORIGIN}${STICKER_WEB_PATH}/profile`;
|
|
213
|
+
const packCommandTarget = String(packIdentifier || '').trim() || '<pack>';
|
|
214
|
+
const itemCount = Array.isArray(pack.items) ? pack.items.length : Number(pack.sticker_count || 0);
|
|
215
|
+
const countLabel = itemCount > 0 ? ` (${itemCount}/${AUTO_PACK_MAX_ITEMS})` : '';
|
|
216
|
+
|
|
217
|
+
if (result.status === 'duplicate') {
|
|
218
|
+
const duplicateLines = [`ℹ️ Essa figurinha já estava no pack automático *${packName}*.`, `Use *${commandPrefix}pack info ${packIdentifier}* para ver o pack ou *${commandPrefix}pack send ${packIdentifier}* para enviar.`];
|
|
219
|
+
if (packWebUrl) {
|
|
220
|
+
duplicateLines.push(`🌐 Link do pack no site: ${packWebUrl}`);
|
|
221
|
+
} else {
|
|
222
|
+
duplicateLines.push(`🔒 Pack privado/não publicado. Abra no painel: ${profileUrl}`);
|
|
223
|
+
}
|
|
224
|
+
return duplicateLines.join('\n');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const savedLines = [`✅ Figurinha adicionada ao pack *${packName}*${countLabel}.`, '', `📋 Gerencie seus packs com *${commandPrefix}pack list*.`, `🚀 Envie agora com *${commandPrefix}pack send ${packCommandTarget}*.`];
|
|
228
|
+
if (packWebUrl) {
|
|
229
|
+
savedLines.push(`🌐 Veja no site: ${packWebUrl}`);
|
|
230
|
+
} else {
|
|
231
|
+
savedLines.push(`🔒 Pack privado/não publicado. Gerencie em: ${profileUrl}`);
|
|
232
|
+
}
|
|
233
|
+
return savedLines.join('\n');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function notifyAutoPackCollection({ sock, remoteJid, messageInfo, expirationMessage, result, commandPrefix }) {
|
|
237
|
+
if (!AUTO_PACK_NOTICE_ENABLED) return;
|
|
238
|
+
|
|
239
|
+
const noticeText = buildAutoPackNoticeText(result, commandPrefix);
|
|
240
|
+
if (!noticeText) return;
|
|
241
|
+
|
|
242
|
+
await sendAndStore(
|
|
243
|
+
sock,
|
|
244
|
+
remoteJid,
|
|
245
|
+
{ text: noticeText },
|
|
246
|
+
{
|
|
247
|
+
quoted: messageInfo,
|
|
248
|
+
ephemeralExpiration: expirationMessage,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Processa uma mensagem para criar e enviar um sticker com metadados customizados.
|
|
255
|
+
*
|
|
256
|
+
* Fluxo: valida mídia/limite, baixa arquivo, converte para WEBP, aplica EXIF e envia ao chat.
|
|
257
|
+
* Erros de processamento geram resposta amigável para o usuário e alerta opcional para admin.
|
|
258
|
+
*
|
|
259
|
+
* @param {import('@whiskeysockets/baileys').WASocket} sock - Socket ativo do Baileys.
|
|
260
|
+
* @param {import('@whiskeysockets/baileys').WAMessage} messageInfo - Mensagem de comando recebida.
|
|
261
|
+
* @param {string} senderJid - JID do remetente.
|
|
262
|
+
* @param {string} remoteJid - JID do chat onde a resposta será enviada.
|
|
263
|
+
* @param {number} expirationMessage - Tempo de expiração (segundos) para mensagens efêmeras.
|
|
264
|
+
* @param {string} senderName - Nome do remetente exibido no chat.
|
|
265
|
+
* @param {string} [extraText=''] - Texto opcional no formato `pack/author` para metadados.
|
|
266
|
+
* @param {ProcessStickerOptions} [options={}] - Comportamento avançado do fluxo.
|
|
267
|
+
* @returns {Promise<void>}
|
|
268
|
+
*/
|
|
269
|
+
export async function processSticker(sock, messageInfo, senderJid, remoteJid, expirationMessage, senderName, extraText = '', options = {}) {
|
|
270
|
+
const { includeQuotedMedia = true, showAutoPackNotice = true, commandPrefix = DEFAULT_COMMAND_PREFIX } = options;
|
|
271
|
+
const uniqueId = uuidv4();
|
|
272
|
+
|
|
273
|
+
let tempMediaPath = null;
|
|
274
|
+
let processingMediaPath = null;
|
|
275
|
+
let stickerPath = null;
|
|
276
|
+
let convertedPath = null;
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const message = messageInfo;
|
|
280
|
+
const from = remoteJid;
|
|
281
|
+
const sender = senderJid;
|
|
282
|
+
const userId = getJidUser(sender);
|
|
283
|
+
const sanitizedUserId = (userId || 'anon').replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
284
|
+
|
|
285
|
+
const dirResult = await ensureDirectories(sanitizedUserId);
|
|
286
|
+
if (!dirResult.success) {
|
|
287
|
+
logger.error(`processSticker Erro ao garantir diretórios: ${dirResult.error}`);
|
|
288
|
+
await sendAndStore(sock, adminJid, {
|
|
289
|
+
text: `❌ Erro ao preparar diretórios do usuário: ${dirResult.error}`,
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const mediaDetails = extractMediaDetails(message, { includeQuoted: includeQuotedMedia });
|
|
295
|
+
if (!mediaDetails) {
|
|
296
|
+
const maxSizeLabel = `${(MAX_FILE_SIZE / (1024 * 1024)).toFixed(0)} MB`;
|
|
297
|
+
const usageText = getStickerUsageText('sticker', { commandPrefix }) || `Use *${commandPrefix}sticker* (ou *${commandPrefix}s*) respondendo a uma imagem, video ou figurinha.`;
|
|
298
|
+
await sendAndStore(sock, senderJid, { react: { text: '❓', key: messageInfo.key } });
|
|
299
|
+
await sendAndStore(
|
|
300
|
+
sock,
|
|
301
|
+
from,
|
|
302
|
+
{
|
|
303
|
+
text: `Olá ${senderName} \n\n*❌ Não foi possível processar sua solicitação.*\n\n` + '> Você não enviou nem marcou nenhuma mídia.\n\n' + `📌 ${usageText}\n\n` + `📦 Tamanho máximo permitido: *${maxSizeLabel}*.\n\n` + '> _*💡 Dica: desative o modo HD antes de enviar para reduzir o tamanho do arquivo e evitar falhas.*_',
|
|
304
|
+
},
|
|
305
|
+
{ quoted: message, ephemeralExpiration: expirationMessage },
|
|
306
|
+
);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const { mediaType, mediaKey } = mediaDetails;
|
|
311
|
+
if (!isSupportedStickerMediaType(mediaType)) {
|
|
312
|
+
const usageText = getStickerUsageText('sticker', { commandPrefix }) || `Use *${commandPrefix}sticker* (ou *${commandPrefix}s*) respondendo a uma imagem, video ou figurinha.`;
|
|
313
|
+
await sendAndStore(sock, senderJid, { react: { text: '❓', key: messageInfo.key } });
|
|
314
|
+
await sendAndStore(
|
|
315
|
+
sock,
|
|
316
|
+
from,
|
|
317
|
+
{
|
|
318
|
+
text: '*❌ Tipo de mídia não suportado para criar sticker.*' + '\n\n- Tipos aceitos: *imagem, vídeo ou figurinha*.' + `\n\n- 📌 ${usageText}`,
|
|
319
|
+
},
|
|
320
|
+
{ quoted: message, ephemeralExpiration: expirationMessage },
|
|
321
|
+
);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!checkMediaSize(mediaKey, mediaType)) {
|
|
326
|
+
await sendAndStore(sock, senderJid, { react: { text: '❓', key: messageInfo.key } });
|
|
327
|
+
const fileLength = mediaKey?.fileLength || 0;
|
|
328
|
+
const formatBytes = (bytes) => (bytes / (1024 * 1024)).toFixed(2) + ' MB';
|
|
329
|
+
const enviado = formatBytes(fileLength);
|
|
330
|
+
const limite = formatBytes(MAX_FILE_SIZE);
|
|
331
|
+
let sugestaoTempo = '';
|
|
332
|
+
if (mediaType === 'video' && mediaKey.seconds && fileLength && fileLength > 0) {
|
|
333
|
+
const taxaBytesPorSegundo = fileLength / mediaKey.seconds;
|
|
334
|
+
const maxSegundos = Math.floor(MAX_FILE_SIZE / taxaBytesPorSegundo);
|
|
335
|
+
sugestaoTempo = `\n\n_*💡 Dica: Para este vídeo, tente cortar para até ${maxSegundos} segundos com a mesma qualidade.*_`;
|
|
336
|
+
}
|
|
337
|
+
await sendAndStore(
|
|
338
|
+
sock,
|
|
339
|
+
from,
|
|
340
|
+
{
|
|
341
|
+
text: '*❌ Não foi possível processar a mídia.*' + `\n\n- O arquivo enviado tem *${enviado}* e o limite permitido é de *${limite}*.` + '\n\n- 📌 Por favor, envie um arquivo menor ou reduza a qualidade antes de reenviar.' + sugestaoTempo,
|
|
342
|
+
},
|
|
343
|
+
{ quoted: message, ephemeralExpiration: expirationMessage },
|
|
344
|
+
);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const userStickerDir = path.join(TEMP_DIR, sanitizedUserId);
|
|
349
|
+
tempMediaPath = await downloadMediaMessage(mediaKey, mediaType, userStickerDir);
|
|
350
|
+
if (!tempMediaPath) {
|
|
351
|
+
const msgErro = '*❌ Não foi possível baixar a mídia enviada.*\n\n- Isso pode ocorrer por instabilidade na rede, mídia expirada ou formato não suportado.\n- Por favor, tente reenviar a mídia ou envie outro arquivo.';
|
|
352
|
+
await sendAndStore(sock, from, { text: msgErro }, { quoted: message, ephemeralExpiration: expirationMessage });
|
|
353
|
+
if (adminJid) {
|
|
354
|
+
await sendAndStore(sock, adminJid, {
|
|
355
|
+
text: `🚨 Falha no download da mídia para sticker.\nUsuário: ${senderJid}\nChat: ${remoteJid}\nTipo: ${mediaType}\nMensagem: ${JSON.stringify(messageInfo)}\n`,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const mediaExtension = path.extname(tempMediaPath);
|
|
362
|
+
processingMediaPath = path.join(userStickerDir, `media_${uniqueId}${mediaExtension}`);
|
|
363
|
+
await fs.rename(tempMediaPath, processingMediaPath);
|
|
364
|
+
logger.info(`processSticker Mídia original renomeada para: ${processingMediaPath}`);
|
|
365
|
+
tempMediaPath = null;
|
|
366
|
+
|
|
367
|
+
convertedPath = await convertToWebp(processingMediaPath, mediaType, sanitizedUserId, uniqueId);
|
|
368
|
+
|
|
369
|
+
const { packName, packAuthor } = parseStickerMetaText(extraText, senderName);
|
|
370
|
+
stickerPath = await addStickerMetadata(convertedPath, packName, packAuthor, {
|
|
371
|
+
senderName,
|
|
372
|
+
userId,
|
|
373
|
+
});
|
|
374
|
+
let stickerBuffer = null;
|
|
375
|
+
try {
|
|
376
|
+
stickerBuffer = await fs.readFile(stickerPath);
|
|
377
|
+
} catch (bufferErr) {
|
|
378
|
+
logger.error(`processSticker Erro ao ler buffer do sticker: ${bufferErr.message}`);
|
|
379
|
+
const msgErro = '*❌ Não foi possível finalizar o sticker.*\n\n- Ocorreu um erro ao acessar o arquivo temporário do sticker.\n- Tente reenviar a mídia ou envie outro arquivo.';
|
|
380
|
+
await sendAndStore(sock, from, { text: msgErro }, { quoted: message, ephemeralExpiration: expirationMessage });
|
|
381
|
+
if (adminJid) {
|
|
382
|
+
await sendAndStore(sock, adminJid, {
|
|
383
|
+
text: `🚨 Erro ao ler buffer do sticker.\nUsuário: ${senderJid}\nChat: ${remoteJid}\nErro: ${bufferErr.message}\nMensagem: ${JSON.stringify(messageInfo)}\n`,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
await sendAndStore(sock, from, { sticker: stickerBuffer }, { quoted: message, ephemeralExpiration: expirationMessage });
|
|
391
|
+
|
|
392
|
+
// Coleta automática: toda figurinha gerada pelo usuário é adicionada ao pack dele.
|
|
393
|
+
setImmediate(() => {
|
|
394
|
+
addStickerToAutoPack({
|
|
395
|
+
ownerJid: senderJid,
|
|
396
|
+
senderName,
|
|
397
|
+
stickerBuffer,
|
|
398
|
+
})
|
|
399
|
+
.then(async (collectResult) => {
|
|
400
|
+
if (!showAutoPackNotice) return;
|
|
401
|
+
|
|
402
|
+
await notifyAutoPackCollection({
|
|
403
|
+
sock,
|
|
404
|
+
remoteJid: from,
|
|
405
|
+
messageInfo: message,
|
|
406
|
+
expirationMessage,
|
|
407
|
+
result: collectResult,
|
|
408
|
+
commandPrefix,
|
|
409
|
+
});
|
|
410
|
+
})
|
|
411
|
+
.catch((collectError) => {
|
|
412
|
+
logger.warn('Falha ao coletar figurinha automática no pack do usuário.', {
|
|
413
|
+
action: 'sticker_pack_auto_collect_failed',
|
|
414
|
+
owner_jid: senderJid,
|
|
415
|
+
error: collectError.message,
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
} catch (sendErr) {
|
|
420
|
+
logger.error(`processSticker Erro ao enviar o sticker: ${sendErr.message}`);
|
|
421
|
+
const msgErro = '*❌ Não foi possível enviar o sticker ao chat.*\n\n- Ocorreu um erro inesperado ao tentar enviar o arquivo.\n- Tente novamente ou envie outra mídia.';
|
|
422
|
+
await sendAndStore(sock, from, { text: msgErro }, { quoted: message, ephemeralExpiration: expirationMessage });
|
|
423
|
+
if (adminJid) {
|
|
424
|
+
await sendAndStore(sock, adminJid, {
|
|
425
|
+
text: `🚨 Erro ao enviar sticker.\nUsuário: ${senderJid}\nChat: ${remoteJid}\nErro: ${sendErr.message}\nMensagem: ${JSON.stringify(messageInfo)}\n`,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} catch (error) {
|
|
430
|
+
logger.error(`processSticker Erro ao processar sticker: ${error.message}`, {
|
|
431
|
+
error: error.stack,
|
|
432
|
+
});
|
|
433
|
+
const msgErro = '*❌ Não foi possível criar o sticker.*\n\n- Ocorreu um erro inesperado durante o processamento.\n- Tente novamente ou envie outra mídia.';
|
|
434
|
+
await sendAndStore(sock, remoteJid, { text: msgErro }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
435
|
+
if (adminJid) {
|
|
436
|
+
await sendAndStore(sock, adminJid, {
|
|
437
|
+
text: `🚨 Erro fatal ao processar sticker.\nUsuário: ${senderJid}\nChat: ${remoteJid}\nErro: ${error.message}\nStack: ${error.stack}\nMensagem: ${JSON.stringify(messageInfo)}\n`,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
} finally {
|
|
441
|
+
const filesToClean = [tempMediaPath, processingMediaPath, stickerPath, convertedPath].filter(Boolean);
|
|
442
|
+
for (const file of filesToClean) {
|
|
443
|
+
await fs.unlink(file).catch((err) => logger.warn(`processSticker Falha ao limpar arquivo temporário ${file}: ${err.message}`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
import { createModuleCommandConfigRuntime } from '../../services/ai/moduleCommandConfigRuntimeService.js';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const CONFIG_PATH = path.join(__dirname, 'commandConfig.json');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_TEXTS = {
|
|
11
|
+
usage_header: '',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const runtime = createModuleCommandConfigRuntime({
|
|
15
|
+
configPath: CONFIG_PATH,
|
|
16
|
+
fallbackConfig: {
|
|
17
|
+
module: 'stickerModule',
|
|
18
|
+
commands: [],
|
|
19
|
+
textos: DEFAULT_TEXTS,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const renderUsageMethod = (method, commandPrefix) => String(method || '').replaceAll('<prefix>', String(commandPrefix || '/'));
|
|
24
|
+
|
|
25
|
+
const resolveUsageLines = (entry, variant) => {
|
|
26
|
+
if (!entry || typeof entry !== 'object') return [];
|
|
27
|
+
|
|
28
|
+
const usageMessages = entry?.mensagens_uso && typeof entry.mensagens_uso === 'object' ? entry.mensagens_uso : null;
|
|
29
|
+
|
|
30
|
+
if (usageMessages) {
|
|
31
|
+
const variantKey = typeof variant === 'string' ? variant.trim() : '';
|
|
32
|
+
const picked = (variantKey && usageMessages[variantKey]) || usageMessages.default || null;
|
|
33
|
+
if (Array.isArray(picked)) {
|
|
34
|
+
return picked.filter(Boolean).map((value) => String(value));
|
|
35
|
+
}
|
|
36
|
+
if (typeof picked === 'string' && picked.trim()) {
|
|
37
|
+
return [picked.trim()];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const methods = Array.isArray(entry?.metodos_de_uso) ? entry.metodos_de_uso : [];
|
|
42
|
+
return methods.filter(Boolean).map((value) => String(value));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const getStickerModuleConfig = () => runtime.getModuleConfig();
|
|
46
|
+
|
|
47
|
+
export const resolveStickerCommandName = (command) => runtime.resolveCommandName(command);
|
|
48
|
+
export const getStickerCommandEntry = (command) => runtime.getCommandEntry(command);
|
|
49
|
+
export const listEnabledStickerCommands = () => runtime.listEnabledCommands();
|
|
50
|
+
|
|
51
|
+
export const getStickerTextConfig = () => {
|
|
52
|
+
const config = getStickerModuleConfig();
|
|
53
|
+
const raw = config?.textos && typeof config.textos === 'object' ? config.textos : {};
|
|
54
|
+
return {
|
|
55
|
+
...DEFAULT_TEXTS,
|
|
56
|
+
...raw,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const getStickerUsageText = (command, { commandPrefix = '/', header, variant } = {}) => {
|
|
61
|
+
const entry = getStickerCommandEntry(command);
|
|
62
|
+
const methods = resolveUsageLines(entry, variant);
|
|
63
|
+
if (!methods.length) return '';
|
|
64
|
+
|
|
65
|
+
const prefixHeader = typeof header === 'string' ? header : getStickerTextConfig().usage_header || DEFAULT_TEXTS.usage_header;
|
|
66
|
+
const lines = methods.map((method) => renderUsageMethod(method, commandPrefix));
|
|
67
|
+
return prefixHeader ? [prefixHeader, ...lines].join('\n') : lines.join('\n');
|
|
68
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
|
|
6
|
+
import logger from '#logger';
|
|
7
|
+
import { downloadMediaMessage, extractMediaDetails, getJidUser } from '../../config/index.js';
|
|
8
|
+
import { sendAndStore } from '../../services/messaging/messagePersistenceService.js';
|
|
9
|
+
import { getStickerUsageText } from './stickerConfigRuntime.js';
|
|
10
|
+
|
|
11
|
+
const TEMP_DIR = path.join(process.cwd(), 'temp', 'sticker-convert');
|
|
12
|
+
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
|
|
13
|
+
const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
|
|
14
|
+
|
|
15
|
+
const isAnimatedSticker = async (sticker, inputPath) => {
|
|
16
|
+
if (sticker?.isAnimated === true) return true;
|
|
17
|
+
if (sticker?.isAnimated === false) return false;
|
|
18
|
+
|
|
19
|
+
const needles = [Buffer.from('ANIM'), Buffer.from('ANMF')];
|
|
20
|
+
const maxNeedle = Math.max(...needles.map((needle) => needle.length));
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
let resolved = false;
|
|
24
|
+
let tail = Buffer.alloc(0);
|
|
25
|
+
let found = false;
|
|
26
|
+
|
|
27
|
+
const finalize = (result) => {
|
|
28
|
+
if (resolved) return;
|
|
29
|
+
resolved = true;
|
|
30
|
+
resolve(result);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const stream = createReadStream(inputPath, { highWaterMark: 64 * 1024 });
|
|
34
|
+
stream.on('data', (chunk) => {
|
|
35
|
+
const buffer = tail.length ? Buffer.concat([tail, chunk]) : chunk;
|
|
36
|
+
if (needles.some((needle) => buffer.includes(needle))) {
|
|
37
|
+
found = true;
|
|
38
|
+
stream.destroy();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
tail = buffer.slice(-Math.max(0, maxNeedle - 1));
|
|
42
|
+
});
|
|
43
|
+
stream.on('error', () => finalize(false));
|
|
44
|
+
stream.on('end', () => finalize(found));
|
|
45
|
+
stream.on('close', () => finalize(found));
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const resolveStickerMessage = (messageInfo) => {
|
|
50
|
+
const mediaDetails = extractMediaDetails(messageInfo);
|
|
51
|
+
if (!mediaDetails || mediaDetails.mediaType !== 'sticker') {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
sticker: mediaDetails.mediaKey,
|
|
57
|
+
isQuoted: mediaDetails.isQuoted,
|
|
58
|
+
details: mediaDetails.details,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const ensureDir = async (dirPath) => {
|
|
63
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const pickConverterClass = (moduleRef) => moduleRef?.default || moduleRef?.Converter || moduleRef?.WebpConv || moduleRef?.webpconv || null;
|
|
67
|
+
|
|
68
|
+
export async function handleStickerConvertCommand({ sock, remoteJid, messageInfo, expirationMessage, senderJid, commandPrefix = DEFAULT_COMMAND_PREFIX }) {
|
|
69
|
+
const resolved = resolveStickerMessage(messageInfo);
|
|
70
|
+
if (!resolved) {
|
|
71
|
+
const usageText = getStickerUsageText('toimg', { commandPrefix }) || `Use *${commandPrefix}toimg* (ou *${commandPrefix}tovideo*/*${commandPrefix}tovid*) respondendo a uma figurinha.`;
|
|
72
|
+
await sendAndStore(
|
|
73
|
+
sock,
|
|
74
|
+
remoteJid,
|
|
75
|
+
{
|
|
76
|
+
text: `❌ Envie ou responda a uma figurinha para converter.\n\n${usageText}`,
|
|
77
|
+
},
|
|
78
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { sticker, details } = resolved;
|
|
84
|
+
const fileLength = details?.fileLength || sticker?.fileLength || 0;
|
|
85
|
+
if (fileLength > MAX_FILE_SIZE) {
|
|
86
|
+
const sizeMb = (fileLength / (1024 * 1024)).toFixed(2);
|
|
87
|
+
await sendAndStore(sock, remoteJid, { text: `❌ Figurinha muito grande (${sizeMb} MB). Envie uma menor.` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const uniqueId = uuidv4();
|
|
92
|
+
const userId = getJidUser(senderJid) || senderJid || 'anon';
|
|
93
|
+
const sanitizedUserId = String(userId).replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
94
|
+
const userDir = path.join(TEMP_DIR, sanitizedUserId);
|
|
95
|
+
|
|
96
|
+
let downloadedPath = null;
|
|
97
|
+
let webpPath = null;
|
|
98
|
+
let convertedPath = null;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await ensureDir(userDir);
|
|
102
|
+
|
|
103
|
+
downloadedPath = await downloadMediaMessage(sticker, 'sticker', userDir);
|
|
104
|
+
if (!downloadedPath) {
|
|
105
|
+
await sendAndStore(sock, remoteJid, { text: '❌ Não foi possível baixar a figurinha. Tente novamente.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
webpPath = path.join(userDir, `sticker_${uniqueId}.webp`);
|
|
110
|
+
await fs.rename(downloadedPath, webpPath);
|
|
111
|
+
downloadedPath = null;
|
|
112
|
+
|
|
113
|
+
const isAnimated = await isAnimatedSticker(sticker, webpPath);
|
|
114
|
+
if (isAnimated) {
|
|
115
|
+
await sendAndStore(
|
|
116
|
+
sock,
|
|
117
|
+
remoteJid,
|
|
118
|
+
{
|
|
119
|
+
document: { stream: createReadStream(webpPath) },
|
|
120
|
+
mimetype: 'image/webp',
|
|
121
|
+
fileName: `sticker_${uniqueId}.webp`,
|
|
122
|
+
caption: '📦 Figurinha animada exportada como arquivo (sem conversão).',
|
|
123
|
+
},
|
|
124
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const webpConvModule = await import('webp-conv');
|
|
130
|
+
const ConverterClass = pickConverterClass(webpConvModule);
|
|
131
|
+
if (!ConverterClass) {
|
|
132
|
+
throw new Error('webp-conv: classe de conversor não encontrada.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const forcedOutput = path.join(userDir, `sticker_${uniqueId}.png`);
|
|
136
|
+
const converter = new ConverterClass();
|
|
137
|
+
convertedPath = await converter.convertJobs({ input: webpPath, output: forcedOutput });
|
|
138
|
+
|
|
139
|
+
await sendAndStore(
|
|
140
|
+
sock,
|
|
141
|
+
remoteJid,
|
|
142
|
+
{
|
|
143
|
+
image: { stream: createReadStream(convertedPath) },
|
|
144
|
+
caption: '🖼️ Figurinha convertida em imagem.',
|
|
145
|
+
},
|
|
146
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
147
|
+
);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
logger.error(`handleStickerConvertCommand: erro ao converter figurinha: ${error.message}`, {
|
|
150
|
+
error: error.stack,
|
|
151
|
+
});
|
|
152
|
+
await sendAndStore(sock, remoteJid, { text: '❌ Não foi possível converter a figurinha agora. Tente novamente.' }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
153
|
+
} finally {
|
|
154
|
+
const cleanupFiles = [downloadedPath, webpPath, convertedPath].filter(Boolean);
|
|
155
|
+
for (const file of cleanupFiles) {
|
|
156
|
+
await fs.unlink(file).catch((err) => logger.warn(`handleStickerConvertCommand: falha ao limpar ${file}: ${err.message}`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|