@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,700 @@
|
|
|
1
|
+
import { URL } from 'node:url';
|
|
2
|
+
import { isUserAdmin, updateGroupParticipants } from '../../config/index.js';
|
|
3
|
+
import { getJidUser, isLidJid, isSameJidUser, isWhatsAppJid, normalizeJid } from '../../config/index.js';
|
|
4
|
+
import groupConfigStore from '../../store/groupConfigStore.js';
|
|
5
|
+
import logger from '#logger';
|
|
6
|
+
import { sendAndStore } from '../../services/messaging/messagePersistenceService.js';
|
|
7
|
+
import { extractSenderInfoFromMessage, resolveUserId } from '../../config/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base de redes conhecidas e seus domínios oficiais para permitir por categoria.
|
|
11
|
+
* @type {Record<string, string[]>}
|
|
12
|
+
*/
|
|
13
|
+
export const KNOWN_NETWORKS = {
|
|
14
|
+
youtube: ['youtube.com', 'youtu.be', 'music.youtube.com', 'm.youtube.com', 'shorts.youtube.com', 'youtube-nocookie.com'],
|
|
15
|
+
instagram: ['instagram.com', 'instagr.am'],
|
|
16
|
+
facebook: ['facebook.com', 'fb.com', 'fb.watch', 'm.facebook.com', 'l.facebook.com'],
|
|
17
|
+
tiktok: ['tiktok.com', 'vm.tiktok.com', 'vt.tiktok.com'],
|
|
18
|
+
twitter: ['twitter.com', 'x.com', 't.co', 'mobile.twitter.com'],
|
|
19
|
+
linkedin: ['linkedin.com', 'lnkd.in'],
|
|
20
|
+
twitch: ['twitch.tv', 'clips.twitch.tv'],
|
|
21
|
+
discord: ['discord.com', 'discord.gg', 'discordapp.com', 'discordapp.net'],
|
|
22
|
+
whatsapp: ['chat.whatsapp.com', 'wa.me'],
|
|
23
|
+
telegram: ['t.me', 'telegram.me', 'telesco.pe'],
|
|
24
|
+
reddit: ['reddit.com', 'redd.it'],
|
|
25
|
+
pinterest: ['pinterest.com', 'pin.it'],
|
|
26
|
+
snapchat: ['snapchat.com', 'snap.com'],
|
|
27
|
+
kwai: ['kwai.com', 'kw.ai'],
|
|
28
|
+
likee: ['likee.video'],
|
|
29
|
+
vimeo: ['vimeo.com', 'player.vimeo.com'],
|
|
30
|
+
dailymotion: ['dailymotion.com', 'dai.ly'],
|
|
31
|
+
rumble: ['rumble.com'],
|
|
32
|
+
kick: ['kick.com'],
|
|
33
|
+
soundcloud: ['soundcloud.com'],
|
|
34
|
+
spotify: ['spotify.com', 'open.spotify.com'],
|
|
35
|
+
deezer: ['deezer.com', 'deezer.page.link'],
|
|
36
|
+
applemusic: ['music.apple.com'],
|
|
37
|
+
shazam: ['shazam.com'],
|
|
38
|
+
bandcamp: ['bandcamp.com'],
|
|
39
|
+
amazonmusic: ['music.amazon.com'],
|
|
40
|
+
imdb: ['imdb.com'],
|
|
41
|
+
letterboxd: ['letterboxd.com'],
|
|
42
|
+
goodreads: ['goodreads.com'],
|
|
43
|
+
medium: ['medium.com'],
|
|
44
|
+
substack: ['substack.com'],
|
|
45
|
+
behance: ['behance.net'],
|
|
46
|
+
dribbble: ['dribbble.com'],
|
|
47
|
+
deviantart: ['deviantart.com'],
|
|
48
|
+
artstation: ['artstation.com'],
|
|
49
|
+
figma: ['figma.com', 'figma.io'],
|
|
50
|
+
github: ['github.com', 'gist.github.com', 'github.io'],
|
|
51
|
+
gitlab: ['gitlab.com'],
|
|
52
|
+
bitbucket: ['bitbucket.org'],
|
|
53
|
+
npm: ['npmjs.com'],
|
|
54
|
+
pypi: ['pypi.org'],
|
|
55
|
+
stackoverflow: ['stackoverflow.com', 'stackexchange.com'],
|
|
56
|
+
quora: ['quora.com'],
|
|
57
|
+
stackshare: ['stackshare.io'],
|
|
58
|
+
producthunt: ['producthunt.com'],
|
|
59
|
+
hackernews: ['news.ycombinator.com'],
|
|
60
|
+
google: ['google.com', 'goo.gl', 'g.co', 'maps.google.com'],
|
|
61
|
+
maps: ['google.com', 'maps.google.com', 'goo.gl', 'g.page'],
|
|
62
|
+
playstore: ['play.google.com'],
|
|
63
|
+
appstore: ['apps.apple.com'],
|
|
64
|
+
steam: ['steamcommunity.com', 'store.steampowered.com', 'steamdb.info'],
|
|
65
|
+
epicgames: ['epicgames.com'],
|
|
66
|
+
discordbots: ['top.gg', 'discords.com', 'discordbotlist.com'],
|
|
67
|
+
cloudflare: ['cloudflare.com', 'pages.dev', 'workers.dev'],
|
|
68
|
+
heroku: ['heroku.com', 'herokuapp.com'],
|
|
69
|
+
vercel: ['vercel.app', 'vercel.com'],
|
|
70
|
+
netlify: ['netlify.app', 'netlify.com'],
|
|
71
|
+
firebase: ['firebase.google.com', 'web.app'],
|
|
72
|
+
hostinger: ['hostinger.com'],
|
|
73
|
+
wix: ['wix.com', 'wixsite.com'],
|
|
74
|
+
squarespace: ['squarespace.com'],
|
|
75
|
+
wordpress: ['wordpress.com', 'wordpress.org'],
|
|
76
|
+
blogger: ['blogger.com', 'blogspot.com'],
|
|
77
|
+
tumblr: ['tumblr.com'],
|
|
78
|
+
weibo: ['weibo.com'],
|
|
79
|
+
vk: ['vk.com'],
|
|
80
|
+
okru: ['ok.ru'],
|
|
81
|
+
line: ['line.me'],
|
|
82
|
+
wechat: ['wechat.com', 'weixin.qq.com', 'we.chat'],
|
|
83
|
+
qq: ['qq.com'],
|
|
84
|
+
signal: ['signal.org'],
|
|
85
|
+
skype: ['skype.com'],
|
|
86
|
+
slack: ['slack.com'],
|
|
87
|
+
zoom: ['zoom.us', 'zoom.com'],
|
|
88
|
+
meet: ['meet.google.com'],
|
|
89
|
+
teams: ['microsoft.com', 'teams.microsoft.com'],
|
|
90
|
+
canva: ['canva.com'],
|
|
91
|
+
notion: ['notion.so', 'notion.site'],
|
|
92
|
+
trello: ['trello.com'],
|
|
93
|
+
asana: ['asana.com'],
|
|
94
|
+
monday: ['monday.com'],
|
|
95
|
+
clickup: ['clickup.com'],
|
|
96
|
+
airtable: ['airtable.com'],
|
|
97
|
+
coursera: ['coursera.org'],
|
|
98
|
+
udemy: ['udemy.com'],
|
|
99
|
+
udacity: ['udacity.com'],
|
|
100
|
+
edx: ['edx.org'],
|
|
101
|
+
khanacademy: ['khanacademy.org'],
|
|
102
|
+
duolingo: ['duolingo.com'],
|
|
103
|
+
roblox: ['roblox.com'],
|
|
104
|
+
minecraft: ['minecraft.net', 'minecraft.net.br'],
|
|
105
|
+
valorant: ['valorant.com'],
|
|
106
|
+
riot: ['riotgames.com'],
|
|
107
|
+
leagueoflegends: ['leagueoflegends.com'],
|
|
108
|
+
dota2: ['dota2.com'],
|
|
109
|
+
csgo: ['counter-strike.net'],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Delimitadores básicos para tokenização manual (sem regex).
|
|
114
|
+
*/
|
|
115
|
+
const WHITESPACE_CHARS = new Set([' ', '\n', '\r', '\t', '\f', '\v']);
|
|
116
|
+
const EDGE_PUNCTUATION_CHARS = new Set([',', '!', '?', ';', ':', ')', '(', '[', ']', '{', '}', '<', '>', '"', "'", '`', '…']);
|
|
117
|
+
const TOKEN_SEPARATOR_CHARS = new Set([',', ';', '|']);
|
|
118
|
+
const HOST_TERMINATORS = new Set(['/', '?', '#', ':', '\\', ',', ';']);
|
|
119
|
+
const URL_HINTS = ['https://', 'http://', 'www.'];
|
|
120
|
+
const STRICT_TLD_SUFFIXES = new Set(['com', 'net', 'org', 'edu', 'gov', 'mil', 'io', 'me', 'tv', 'co', 'cc', 'gg', 'gl', 'ly', 'so', 'br', 'us', 'uk', 'eu', 'de', 'fr', 'es', 'pt', 'it', 'nl', 'be', 'ch', 'at', 'se', 'no', 'fi', 'dk', 'ie', 'pl', 'cz', 'sk', 'hu', 'ro', 'bg', 'gr', 'ru', 'ua', 'tr', 'il', 'ae', 'sa', 'qa', 'eg', 'ma', 'tn', 'dz', 'za', 'ng', 'ke', 'gh', 'in', 'pk', 'bd', 'lk', 'cn', 'jp', 'kr', 'tw', 'hk', 'sg', 'my', 'th', 'vn', 'ph', 'id', 'au', 'nz', 'ca', 'mx', 'ar', 'cl', 'pe', 'uy', 'py', 'bo', 'ec', 've', 'do', 'cu', 'pa', 'cr', 'gt', 'hn', 'ni', 'sv', 'pr', 'com.br', 'net.br', 'org.br', 'gov.br', 'edu.br', 'jus.br', 'mil.br', 'co.uk', 'org.uk', 'gov.uk', 'ac.uk', 'co.jp', 'ne.jp', 'or.jp', 'go.jp', 'ac.jp', 'com.au', 'net.au', 'org.au', 'edu.au', 'gov.au', 'com.mx', 'com.ar', 'com.co', 'com.pe', 'com.tr', 'com.sg', 'com.my', 'com.ph', 'co.in', 'firm.in', 'net.in', 'org.in', 'gen.in', 'ind.in', 'co.id', 'or.id', 'go.id', 'web.id', 'co.za', 'org.za', 'net.za', 'com.ng', 'com.gh', 'com.eg', 'com.sa', 'com.qa', 'com.ae', 'page.link', 'g.page']);
|
|
121
|
+
const EXTRA_TLD_SUFFIXES = new Set(['ai', 'app', 'dev', 'xyz', 'site', 'online', 'store', 'shop', 'blog', 'tech', 'cloud', 'digital', 'live', 'media', 'news', 'one', 'top', 'club', 'vip', 'fun', 'games', 'game', 'space', 'world', 'today', 'agency', 'email', 'center', 'company', 'group', 'solutions', 'systems', 'services', 'network', 'social', 'design', 'studio', 'photo', 'video', 'audio', 'music', 'art', 'wiki', 'finance', 'capital', 'money', 'loans', 'insurance', 'legal', 'law', 'health', 'care', 'clinic', 'dental', 'academy', 'school', 'college', 'university', 'education', 'training', 'support', 'chat', 'forum', 'community', 'events', 'travel', 'tours', 'hotel', 'homes', 'house', 'auto', 'cars', 'bike', 'food', 'restaurant', 'cafe', 'bar', 'pizza', 'delivery', 'fashion', 'beauty', 'style', 'fit', 'fitness', 'sports', 'download']);
|
|
122
|
+
const ANY_TLD_SUFFIXES = new Set([...STRICT_TLD_SUFFIXES, ...EXTRA_TLD_SUFFIXES]);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Tokeniza texto por espaço/quebra de linha sem regex.
|
|
126
|
+
* @param {string} text
|
|
127
|
+
* @returns {string[]}
|
|
128
|
+
*/
|
|
129
|
+
const tokenizeText = (text) => {
|
|
130
|
+
if (!text) return [];
|
|
131
|
+
const tokens = [];
|
|
132
|
+
let currentToken = '';
|
|
133
|
+
|
|
134
|
+
for (const char of text) {
|
|
135
|
+
if (WHITESPACE_CHARS.has(char)) {
|
|
136
|
+
if (currentToken) {
|
|
137
|
+
tokens.push(currentToken);
|
|
138
|
+
currentToken = '';
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
currentToken += char;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (currentToken) tokens.push(currentToken);
|
|
146
|
+
return tokens;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Divide tokens compostos (site.com,site2.com|site3.com) sem regex.
|
|
151
|
+
* @param {string} token
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
const splitCompositeToken = (token) => {
|
|
155
|
+
const parts = [];
|
|
156
|
+
let currentPart = '';
|
|
157
|
+
|
|
158
|
+
for (let i = 0; i < token.length; i += 1) {
|
|
159
|
+
const char = token[i];
|
|
160
|
+
const isArrowSeparator = char === '>' && i > 0 && token[i - 1] === '-';
|
|
161
|
+
|
|
162
|
+
if (TOKEN_SEPARATOR_CHARS.has(char) || isArrowSeparator) {
|
|
163
|
+
if (isArrowSeparator && currentPart.endsWith('-')) {
|
|
164
|
+
currentPart = currentPart.slice(0, -1);
|
|
165
|
+
}
|
|
166
|
+
if (currentPart) {
|
|
167
|
+
parts.push(currentPart);
|
|
168
|
+
currentPart = '';
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
currentPart += char;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (currentPart) parts.push(currentPart);
|
|
176
|
+
return parts;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Remove pontuação comum das bordas do token.
|
|
181
|
+
* @param {string} token
|
|
182
|
+
* @returns {string}
|
|
183
|
+
*/
|
|
184
|
+
const stripEdgePunctuation = (token) => {
|
|
185
|
+
let start = 0;
|
|
186
|
+
let end = token.length;
|
|
187
|
+
|
|
188
|
+
while (start < end && EDGE_PUNCTUATION_CHARS.has(token[start])) start += 1;
|
|
189
|
+
while (end > start && EDGE_PUNCTUATION_CHARS.has(token[end - 1])) end -= 1;
|
|
190
|
+
|
|
191
|
+
return token.slice(start, end);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const isAsciiLetter = (char) => char >= 'a' && char <= 'z';
|
|
195
|
+
const isDigit = (char) => char >= '0' && char <= '9';
|
|
196
|
+
const isDomainLabelChar = (char) => isAsciiLetter(char) || isDigit(char) || char === '-';
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Normaliza host removendo "www." e pontos nas bordas.
|
|
200
|
+
* @param {string} host
|
|
201
|
+
* @returns {string}
|
|
202
|
+
*/
|
|
203
|
+
const normalizeHost = (host) => {
|
|
204
|
+
if (!host) return '';
|
|
205
|
+
let normalized = host.toLowerCase();
|
|
206
|
+
|
|
207
|
+
while (normalized.startsWith('.')) {
|
|
208
|
+
normalized = normalized.slice(1);
|
|
209
|
+
}
|
|
210
|
+
while (normalized.endsWith('.')) {
|
|
211
|
+
normalized = normalized.slice(0, -1);
|
|
212
|
+
}
|
|
213
|
+
while (normalized.startsWith('www.')) {
|
|
214
|
+
normalized = normalized.slice(4);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return normalized;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Retorna quantos labels formam o TLD conhecido (1, 2 ou 3).
|
|
222
|
+
* @param {string[]} labels
|
|
223
|
+
* @param {Set<string>} suffixSet
|
|
224
|
+
* @returns {number}
|
|
225
|
+
*/
|
|
226
|
+
const getKnownTldLabelCount = (labels, suffixSet) => {
|
|
227
|
+
if (labels.length >= 3) {
|
|
228
|
+
const lastThree = labels.slice(-3).join('.');
|
|
229
|
+
if (suffixSet.has(lastThree)) return 3;
|
|
230
|
+
}
|
|
231
|
+
if (labels.length >= 2) {
|
|
232
|
+
const lastTwo = labels.slice(-2).join('.');
|
|
233
|
+
if (suffixSet.has(lastTwo)) return 2;
|
|
234
|
+
}
|
|
235
|
+
const lastOne = labels[labels.length - 1];
|
|
236
|
+
if (suffixSet.has(lastOne)) return 1;
|
|
237
|
+
return 0;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Conta labels de TLD somente na lista strict.
|
|
242
|
+
* @param {string[]} labels
|
|
243
|
+
* @returns {number}
|
|
244
|
+
*/
|
|
245
|
+
const getStrictTldLabelCount = (labels) => getKnownTldLabelCount(labels, STRICT_TLD_SUFFIXES);
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Conta labels de TLD aceitando strict + extra.
|
|
249
|
+
* @param {string[]} labels
|
|
250
|
+
* @returns {number}
|
|
251
|
+
*/
|
|
252
|
+
const getAnyTldLabelCount = (labels) => getKnownTldLabelCount(labels, ANY_TLD_SUFFIXES);
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Extrai o root registrável com base nos TLDs strict.
|
|
256
|
+
* @param {string} domain
|
|
257
|
+
* @returns {string}
|
|
258
|
+
*/
|
|
259
|
+
const getStrictRegistrableRootDomain = (domain) => {
|
|
260
|
+
const labels = domain.split('.');
|
|
261
|
+
const tldLabelCount = getStrictTldLabelCount(labels);
|
|
262
|
+
if (tldLabelCount === 0 || labels.length <= tldLabelCount) return '';
|
|
263
|
+
return labels.slice(-(tldLabelCount + 1)).join('.');
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Valida a estrutura do domínio sem regex.
|
|
268
|
+
* @param {string} domain
|
|
269
|
+
* @returns {boolean}
|
|
270
|
+
*/
|
|
271
|
+
const isValidDomainStructure = (domain) => {
|
|
272
|
+
if (!domain || domain.length > 253 || !domain.includes('.')) return false;
|
|
273
|
+
const labels = domain.split('.');
|
|
274
|
+
|
|
275
|
+
for (const label of labels) {
|
|
276
|
+
if (!label || label.length > 63) return false;
|
|
277
|
+
if (label[0] === '-' || label[label.length - 1] === '-') return false;
|
|
278
|
+
|
|
279
|
+
for (const char of label) {
|
|
280
|
+
if (!isDomainLabelChar(char)) return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return true;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Verifica se o domínio termina com TLD/sufixo conhecido.
|
|
289
|
+
* @param {string} domain
|
|
290
|
+
* @returns {boolean}
|
|
291
|
+
*/
|
|
292
|
+
const hasStrictKnownTldSuffix = (domain) => {
|
|
293
|
+
const labels = domain.split('.');
|
|
294
|
+
return getStrictTldLabelCount(labels) > 0;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Verifica se o domínio termina com TLD/sufixo conhecido (strict + extra).
|
|
299
|
+
* @param {string} domain
|
|
300
|
+
* @returns {boolean}
|
|
301
|
+
*/
|
|
302
|
+
const hasAnyKnownTldSuffix = (domain) => {
|
|
303
|
+
const labels = domain.split('.');
|
|
304
|
+
return getAnyTldLabelCount(labels) > 0;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const KNOWN_NETWORK_EXACT_DOMAINS = new Set();
|
|
308
|
+
const KNOWN_NETWORK_SUBDOMAIN_ROOTS = new Set();
|
|
309
|
+
|
|
310
|
+
for (const domains of Object.values(KNOWN_NETWORKS)) {
|
|
311
|
+
for (const domain of domains) {
|
|
312
|
+
const normalizedDomain = domain.toLowerCase();
|
|
313
|
+
KNOWN_NETWORK_EXACT_DOMAINS.add(normalizedDomain);
|
|
314
|
+
|
|
315
|
+
const rootDomain = getStrictRegistrableRootDomain(normalizedDomain);
|
|
316
|
+
if (rootDomain) {
|
|
317
|
+
KNOWN_NETWORK_SUBDOMAIN_ROOTS.add(rootDomain);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Verifica domínios oficiais já mapeados em KNOWN_NETWORKS.
|
|
324
|
+
* @param {string} domain
|
|
325
|
+
* @returns {boolean}
|
|
326
|
+
*/
|
|
327
|
+
const isKnownNetworkDomain = (domain) => {
|
|
328
|
+
const normalizedDomain = domain.toLowerCase();
|
|
329
|
+
if (KNOWN_NETWORK_EXACT_DOMAINS.has(normalizedDomain)) return true;
|
|
330
|
+
|
|
331
|
+
const rootDomain = getStrictRegistrableRootDomain(normalizedDomain);
|
|
332
|
+
if (rootDomain && rootDomain !== normalizedDomain && KNOWN_NETWORK_SUBDOMAIN_ROOTS.has(rootDomain)) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Fallback para sufixos fora da lista de TLDs (ex.: goo.gl), sem varrer array inteiro.
|
|
337
|
+
let dotIndex = normalizedDomain.indexOf('.');
|
|
338
|
+
while (dotIndex !== -1) {
|
|
339
|
+
const parentDomain = normalizedDomain.slice(dotIndex + 1);
|
|
340
|
+
if (KNOWN_NETWORK_EXACT_DOMAINS.has(parentDomain)) return true;
|
|
341
|
+
dotIndex = normalizedDomain.indexOf('.', dotIndex + 1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return false;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Busca o primeiro indicativo de URL no token.
|
|
349
|
+
* @param {string} token
|
|
350
|
+
* @returns {number}
|
|
351
|
+
*/
|
|
352
|
+
const findUrlHintIndex = (token) => {
|
|
353
|
+
let firstIndex = -1;
|
|
354
|
+
|
|
355
|
+
for (const hint of URL_HINTS) {
|
|
356
|
+
const index = token.indexOf(hint);
|
|
357
|
+
if (index !== -1 && (firstIndex === -1 || index < firstIndex)) {
|
|
358
|
+
firstIndex = index;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return firstIndex;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Extrai host de token com http(s)/www usando URL nativo.
|
|
367
|
+
* @param {string} token
|
|
368
|
+
* @returns {string | null}
|
|
369
|
+
*/
|
|
370
|
+
const extractDomainFromUrlToken = (token) => {
|
|
371
|
+
const lowerToken = token.toLowerCase();
|
|
372
|
+
const hintIndex = findUrlHintIndex(lowerToken);
|
|
373
|
+
if (hintIndex === -1) return null;
|
|
374
|
+
if (hintIndex > 0) {
|
|
375
|
+
const previousChar = lowerToken[hintIndex - 1];
|
|
376
|
+
if (previousChar === '@' || isAsciiLetter(previousChar) || isDigit(previousChar)) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let urlCandidate = token.slice(hintIndex);
|
|
382
|
+
const normalizedCandidate = urlCandidate.toLowerCase();
|
|
383
|
+
if (!normalizedCandidate.startsWith('http://') && !normalizedCandidate.startsWith('https://')) {
|
|
384
|
+
urlCandidate = `https://${urlCandidate}`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const parsedUrl = new URL(urlCandidate);
|
|
389
|
+
const host = normalizeHost(parsedUrl.hostname);
|
|
390
|
+
if (!isValidDomainStructure(host)) return null;
|
|
391
|
+
if (!hasAnyKnownTldSuffix(host) && !isKnownNetworkDomain(host)) return null;
|
|
392
|
+
return host;
|
|
393
|
+
} catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Extrai host de token sem protocolo, validando domínio manualmente.
|
|
400
|
+
* @param {string} token
|
|
401
|
+
* @returns {string | null}
|
|
402
|
+
*/
|
|
403
|
+
const extractDomainFromPlainToken = (token) => {
|
|
404
|
+
const lowerToken = token.toLowerCase();
|
|
405
|
+
if (lowerToken.includes('@')) return null;
|
|
406
|
+
|
|
407
|
+
let host = '';
|
|
408
|
+
for (const char of lowerToken) {
|
|
409
|
+
if (HOST_TERMINATORS.has(char)) break;
|
|
410
|
+
host += char;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
host = normalizeHost(host);
|
|
414
|
+
if (!isValidDomainStructure(host)) return null;
|
|
415
|
+
if (!hasStrictKnownTldSuffix(host) && !isKnownNetworkDomain(host)) return null;
|
|
416
|
+
|
|
417
|
+
return host;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Extrai domínios válidos de um texto sem uso de regex.
|
|
422
|
+
* @param {string} text
|
|
423
|
+
* @returns {string[]}
|
|
424
|
+
*/
|
|
425
|
+
export const extractDomainsNoRegex = (text) => {
|
|
426
|
+
const tokens = tokenizeText(text);
|
|
427
|
+
if (tokens.length === 0) return [];
|
|
428
|
+
const domains = new Set();
|
|
429
|
+
|
|
430
|
+
for (const token of tokens) {
|
|
431
|
+
const partialTokens = splitCompositeToken(token);
|
|
432
|
+
for (const partialToken of partialTokens) {
|
|
433
|
+
const cleanedToken = stripEdgePunctuation(partialToken);
|
|
434
|
+
if (!cleanedToken) continue;
|
|
435
|
+
|
|
436
|
+
const urlDomain = extractDomainFromUrlToken(cleanedToken);
|
|
437
|
+
if (urlDomain) {
|
|
438
|
+
domains.add(urlDomain);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const plainDomain = extractDomainFromPlainToken(cleanedToken);
|
|
443
|
+
if (plainDomain) domains.add(plainDomain);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return Array.from(domains);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Normaliza e remove duplicados da allowlist.
|
|
452
|
+
* @param {string[]} allowedDomains
|
|
453
|
+
* @returns {string[]}
|
|
454
|
+
*/
|
|
455
|
+
const normalizeAllowedDomains = (allowedDomains = []) => {
|
|
456
|
+
const normalizedDomains = new Set();
|
|
457
|
+
|
|
458
|
+
for (const allowedDomain of allowedDomains) {
|
|
459
|
+
const normalizedDomain = normalizeHost(String(allowedDomain || ''));
|
|
460
|
+
if (normalizedDomain) normalizedDomains.add(normalizedDomain);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return Array.from(normalizedDomains);
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Aceita o domínio exato ou subdomínios de um permitido já normalizado.
|
|
468
|
+
* @param {string} domain
|
|
469
|
+
* @param {string[]} normalizedAllowedDomains
|
|
470
|
+
* @returns {boolean}
|
|
471
|
+
*/
|
|
472
|
+
const isDomainAllowed = (domain, normalizedAllowedDomains) => normalizedAllowedDomains.some((allowedDomain) => domain === allowedDomain || domain.endsWith(`.${allowedDomain}`));
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Monta a lista final de domínios permitidos (redes conhecidas + personalizados).
|
|
476
|
+
* @param {string[]} allowedNetworks
|
|
477
|
+
* @param {string[]} allowedCustomDomains
|
|
478
|
+
* @returns {string[]}
|
|
479
|
+
*/
|
|
480
|
+
const getAllowedDomains = (allowedNetworks = [], allowedCustomDomains = []) => {
|
|
481
|
+
const domains = [];
|
|
482
|
+
for (const network of allowedNetworks) {
|
|
483
|
+
if (KNOWN_NETWORKS[network]) {
|
|
484
|
+
domains.push(...KNOWN_NETWORKS[network]);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return [...domains, ...allowedCustomDomains];
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Retorna true quando existir um link que não esteja na lista permitida.
|
|
492
|
+
* @param {string} text
|
|
493
|
+
* @param {string[]} normalizedAllowedDomains
|
|
494
|
+
* @returns {boolean}
|
|
495
|
+
*/
|
|
496
|
+
export const isLinkDetected = (text, normalizedAllowedDomains = []) => {
|
|
497
|
+
const domains = extractDomainsNoRegex(text);
|
|
498
|
+
if (domains.length === 0) return false;
|
|
499
|
+
if (normalizedAllowedDomains.length === 0) return true;
|
|
500
|
+
return domains.some((domain) => !isDomainAllowed(domain, normalizedAllowedDomains));
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const normalizeOptionalJid = (value) => {
|
|
504
|
+
if (typeof value !== 'string') return '';
|
|
505
|
+
return normalizeJid(value.trim());
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const uniqueNormalizedJids = (values = []) => {
|
|
509
|
+
const unique = [];
|
|
510
|
+
const seen = new Set();
|
|
511
|
+
for (const value of values) {
|
|
512
|
+
const normalized = normalizeOptionalJid(value);
|
|
513
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
514
|
+
seen.add(normalized);
|
|
515
|
+
unique.push(normalized);
|
|
516
|
+
}
|
|
517
|
+
return unique;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const isSameUserSafe = (jidA, jidB) => {
|
|
521
|
+
if (!jidA || !jidB) return false;
|
|
522
|
+
try {
|
|
523
|
+
return isSameJidUser(jidA, jidB);
|
|
524
|
+
} catch {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const isSenderBot = (botJid, candidates) => {
|
|
530
|
+
const normalizedBot = normalizeOptionalJid(botJid);
|
|
531
|
+
if (!normalizedBot) return false;
|
|
532
|
+
return candidates.some((candidate) => isSameUserSafe(candidate, normalizedBot));
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const resolveSenderContextForAntiLink = async ({ messageInfo, senderJid, senderIdentity }) => {
|
|
536
|
+
const key = messageInfo?.key || {};
|
|
537
|
+
const senderInfo = extractSenderInfoFromMessage(messageInfo);
|
|
538
|
+
const resolvedByMessage = normalizeOptionalJid(await resolveUserId(senderInfo).catch(() => ''));
|
|
539
|
+
const identityRaw = typeof senderIdentity === 'string' ? normalizeOptionalJid(senderIdentity) : '';
|
|
540
|
+
const keyParticipant = normalizeOptionalJid(key?.participant);
|
|
541
|
+
const keyParticipantAlt = normalizeOptionalJid(key?.participantAlt);
|
|
542
|
+
const keyRemoteAlt = normalizeOptionalJid(key?.remoteJidAlt);
|
|
543
|
+
const identityParticipant = normalizeOptionalJid(senderIdentity?.participant);
|
|
544
|
+
const identityParticipantAlt = normalizeOptionalJid(senderIdentity?.participantAlt);
|
|
545
|
+
const identityJid = normalizeOptionalJid(senderIdentity?.jid);
|
|
546
|
+
const explicitSender = normalizeOptionalJid(senderJid);
|
|
547
|
+
const senderInfoJid = normalizeOptionalJid(senderInfo?.jid);
|
|
548
|
+
const senderInfoLid = normalizeOptionalJid(senderInfo?.lid);
|
|
549
|
+
const senderInfoAlt = normalizeOptionalJid(senderInfo?.participantAlt);
|
|
550
|
+
|
|
551
|
+
const senderCandidates = uniqueNormalizedJids([explicitSender, resolvedByMessage, keyParticipant, keyParticipantAlt, keyRemoteAlt, senderInfoJid, senderInfoLid, senderInfoAlt, identityParticipant, identityParticipantAlt, identityJid, identityRaw]);
|
|
552
|
+
|
|
553
|
+
const removalCandidates = [];
|
|
554
|
+
const lidCandidates = [];
|
|
555
|
+
const pnCandidates = [];
|
|
556
|
+
for (const candidate of senderCandidates) {
|
|
557
|
+
if (isLidJid(candidate)) lidCandidates.push(candidate);
|
|
558
|
+
else if (isWhatsAppJid(candidate)) pnCandidates.push(candidate);
|
|
559
|
+
}
|
|
560
|
+
removalCandidates.push(...lidCandidates, ...pnCandidates);
|
|
561
|
+
|
|
562
|
+
const mentionJid = pnCandidates[0] || removalCandidates[0] || senderCandidates[0] || '';
|
|
563
|
+
const primarySenderId = resolvedByMessage || explicitSender || senderInfoJid || senderInfoLid || senderCandidates[0] || '';
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
senderInfo,
|
|
567
|
+
senderCandidates,
|
|
568
|
+
removalCandidates,
|
|
569
|
+
mentionJid,
|
|
570
|
+
primarySenderId,
|
|
571
|
+
};
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
const removeParticipantWithFallback = async (sock, remoteJid, candidates = []) => {
|
|
575
|
+
let lastError = null;
|
|
576
|
+
for (const candidate of candidates) {
|
|
577
|
+
try {
|
|
578
|
+
await updateGroupParticipants(sock, remoteJid, [candidate], 'remove');
|
|
579
|
+
return candidate;
|
|
580
|
+
} catch (error) {
|
|
581
|
+
lastError = error;
|
|
582
|
+
logger.debug('Falha ao remover participante com ID candidato. Tentando próximo.', {
|
|
583
|
+
action: 'antilink_remove_candidate_failed',
|
|
584
|
+
groupId: remoteJid,
|
|
585
|
+
participantId: candidate,
|
|
586
|
+
error: error?.message,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (lastError) throw lastError;
|
|
591
|
+
return '';
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Aplica a regra de antilink do grupo. Retorna true quando removeu e deve pular o restante.
|
|
596
|
+
* @param {Object} params
|
|
597
|
+
* @param {import('@whiskeysockets/baileys').WASocket} params.sock
|
|
598
|
+
* @param {Object} params.messageInfo
|
|
599
|
+
* @param {string} params.extractedText
|
|
600
|
+
* @param {string} params.remoteJid
|
|
601
|
+
* @param {string} params.senderJid
|
|
602
|
+
* @param {{participant?: string|null, participantAlt?: string|null, jid?: string|null}|string|null} [params.senderIdentity]
|
|
603
|
+
* @param {string} params.botJid
|
|
604
|
+
* @returns {Promise<boolean>}
|
|
605
|
+
*/
|
|
606
|
+
export const handleAntiLink = async ({ sock, messageInfo, extractedText, remoteJid, senderJid, senderIdentity, botJid }) => {
|
|
607
|
+
const groupConfig = await groupConfigStore.getGroupConfig(remoteJid);
|
|
608
|
+
if (!groupConfig || !groupConfig.antilinkEnabled) return false;
|
|
609
|
+
|
|
610
|
+
const allowedDomains = getAllowedDomains(groupConfig.antilinkAllowedNetworks || [], groupConfig.antilinkAllowedDomains || []);
|
|
611
|
+
const normalizedAllowedDomains = normalizeAllowedDomains(allowedDomains);
|
|
612
|
+
if (!isLinkDetected(extractedText, normalizedAllowedDomains)) return false;
|
|
613
|
+
|
|
614
|
+
const senderContext = await resolveSenderContextForAntiLink({
|
|
615
|
+
messageInfo,
|
|
616
|
+
senderJid,
|
|
617
|
+
senderIdentity,
|
|
618
|
+
});
|
|
619
|
+
if (!senderContext.primarySenderId && senderContext.senderCandidates.length === 0) return false;
|
|
620
|
+
|
|
621
|
+
let isAdmin = await isUserAdmin(remoteJid, {
|
|
622
|
+
id: senderContext.primarySenderId || null,
|
|
623
|
+
jid: senderContext.senderInfo?.jid || senderContext.primarySenderId || null,
|
|
624
|
+
lid: senderContext.senderInfo?.lid || null,
|
|
625
|
+
participantAlt: senderContext.senderInfo?.participantAlt || null,
|
|
626
|
+
participant: messageInfo?.key?.participant || null,
|
|
627
|
+
remoteJidAlt: messageInfo?.key?.remoteJidAlt || null,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
if (!isAdmin && senderContext.primarySenderId) {
|
|
631
|
+
isAdmin = await isUserAdmin(remoteJid, senderContext.primarySenderId);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const senderIsBot = isSenderBot(botJid, senderContext.senderCandidates);
|
|
635
|
+
|
|
636
|
+
if (!isAdmin && !senderIsBot) {
|
|
637
|
+
if (senderContext.removalCandidates.length === 0) {
|
|
638
|
+
logger.warn('Antilink detectou link, mas não encontrou ID válido para remoção.', {
|
|
639
|
+
action: 'antilink_no_removal_candidate',
|
|
640
|
+
groupId: remoteJid,
|
|
641
|
+
senderCandidates: senderContext.senderCandidates,
|
|
642
|
+
});
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
try {
|
|
647
|
+
const removedParticipantId = await removeParticipantWithFallback(sock, remoteJid, senderContext.removalCandidates);
|
|
648
|
+
if (!removedParticipantId) {
|
|
649
|
+
throw new Error('Nenhum candidato de participante pôde ser removido.');
|
|
650
|
+
}
|
|
651
|
+
const senderMention = senderContext.mentionJid || removedParticipantId || senderContext.primarySenderId;
|
|
652
|
+
const senderUser = getJidUser(senderMention);
|
|
653
|
+
await sendAndStore(sock, remoteJid, {
|
|
654
|
+
text: `🚫 @${senderUser || 'usuario'} foi removido por enviar um link.`,
|
|
655
|
+
mentions: senderMention ? [senderMention] : [],
|
|
656
|
+
});
|
|
657
|
+
await sendAndStore(sock, remoteJid, { delete: messageInfo.key });
|
|
658
|
+
|
|
659
|
+
logger.info(`Usuário ${removedParticipantId || senderContext.primarySenderId} removido do grupo ${remoteJid} por enviar link.`, {
|
|
660
|
+
action: 'antilink_remove',
|
|
661
|
+
groupId: remoteJid,
|
|
662
|
+
userId: removedParticipantId || senderContext.primarySenderId,
|
|
663
|
+
senderCandidates: senderContext.senderCandidates,
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
return true;
|
|
667
|
+
} catch (error) {
|
|
668
|
+
logger.error(`Falha ao remover usuário com antilink: ${error.message}`, {
|
|
669
|
+
action: 'antilink_error',
|
|
670
|
+
groupId: remoteJid,
|
|
671
|
+
userId: senderContext.primarySenderId,
|
|
672
|
+
senderCandidates: senderContext.senderCandidates,
|
|
673
|
+
error: error.stack,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
} else if (isAdmin && !senderIsBot) {
|
|
677
|
+
try {
|
|
678
|
+
const senderMention = senderContext.mentionJid || senderContext.primarySenderId;
|
|
679
|
+
const senderUser = getJidUser(senderMention);
|
|
680
|
+
await sendAndStore(sock, remoteJid, {
|
|
681
|
+
text: `ⓘ @${senderUser || 'admin'} (admin) enviou um link.`,
|
|
682
|
+
mentions: senderMention ? [senderMention] : [],
|
|
683
|
+
});
|
|
684
|
+
logger.info(`Admin ${senderContext.primarySenderId} enviou um link no grupo ${remoteJid} (aviso enviado).`, {
|
|
685
|
+
action: 'antilink_admin_link_detected',
|
|
686
|
+
groupId: remoteJid,
|
|
687
|
+
userId: senderContext.primarySenderId,
|
|
688
|
+
});
|
|
689
|
+
} catch (error) {
|
|
690
|
+
logger.error(`Falha ao enviar aviso de link de admin: ${error.message}`, {
|
|
691
|
+
action: 'antilink_admin_warning_error',
|
|
692
|
+
groupId: remoteJid,
|
|
693
|
+
userId: senderContext.primarySenderId,
|
|
694
|
+
error: error.stack,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
return false;
|
|
700
|
+
};
|