@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,746 @@
|
|
|
1
|
+
const toNumber = (value, fallback = 0) => {
|
|
2
|
+
const parsed = Number(value);
|
|
3
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const formatName = (name) => {
|
|
7
|
+
const raw = String(name || '').trim();
|
|
8
|
+
if (!raw) return 'Pokemon';
|
|
9
|
+
return raw
|
|
10
|
+
.split('-')
|
|
11
|
+
.map((part) => (part ? `${part[0].toUpperCase()}${part.slice(1)}` : ''))
|
|
12
|
+
.join(' ');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const formatPokemonLabel = ({ name, isShiny = false }) => {
|
|
16
|
+
const label = formatName(name);
|
|
17
|
+
return isShiny ? `✨ ${label}` : label;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SLOT_ICONS = ['1️⃣', '2️⃣', '3️⃣', '4️⃣'];
|
|
21
|
+
|
|
22
|
+
const itemEmoji = (itemKey) => {
|
|
23
|
+
const key = String(itemKey || '').toLowerCase();
|
|
24
|
+
if (key === 'pokeball') return '⚪';
|
|
25
|
+
if (key === 'superpotion') return '🧴';
|
|
26
|
+
if (key === 'potion') return '🧪';
|
|
27
|
+
return '🎒';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const itemMeaning = (item = {}) => {
|
|
31
|
+
const text = String(item?.description || item?.loreText || '')
|
|
32
|
+
.trim()
|
|
33
|
+
.replace(/\s+/g, ' ');
|
|
34
|
+
return text || 'Item utilitário do RPG.';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const itemUseCommand = ({ item = {}, prefix = '/' }) => {
|
|
38
|
+
const key = String(item?.key || '').trim() || '<item>';
|
|
39
|
+
if (item?.isMachine) return `${prefix}rpg tm usar ${key} <1-4>`;
|
|
40
|
+
if (item?.isBerry) return `${prefix}rpg berry usar ${key}`;
|
|
41
|
+
if (item?.isPokeball) return `${prefix}rpg usar ${key} (em batalha)`;
|
|
42
|
+
return `${prefix}rpg usar ${key}`;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const ITEM_CATEGORY_ORDER = ['captura', 'cura', 'berry', 'tm', 'evolucao', 'outros'];
|
|
46
|
+
const ITEM_CATEGORY_LABEL_MAP = new Map([
|
|
47
|
+
['captura', 'Captura'],
|
|
48
|
+
['cura', 'Cura'],
|
|
49
|
+
['berry', 'Berries'],
|
|
50
|
+
['tm', 'TMs'],
|
|
51
|
+
['evolucao', 'Evolução'],
|
|
52
|
+
['outros', 'Outros'],
|
|
53
|
+
['todos', 'Todas'],
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const resolveItemCategoryKey = (item = {}) => {
|
|
57
|
+
const predefined = String(item?.categoryKey || '')
|
|
58
|
+
.trim()
|
|
59
|
+
.toLowerCase();
|
|
60
|
+
if (ITEM_CATEGORY_LABEL_MAP.has(predefined)) return predefined;
|
|
61
|
+
if (item?.isPokeball) return 'captura';
|
|
62
|
+
if (item?.isMedicine) return 'cura';
|
|
63
|
+
if (item?.isBerry) return 'berry';
|
|
64
|
+
if (item?.isMachine) return 'tm';
|
|
65
|
+
if (String(item?.category || '').includes('evolution')) return 'evolucao';
|
|
66
|
+
return 'outros';
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const groupItemsByCategory = (items = []) => {
|
|
70
|
+
const grouped = new Map();
|
|
71
|
+
items.forEach((item) => {
|
|
72
|
+
const categoryKey = resolveItemCategoryKey(item);
|
|
73
|
+
if (!grouped.has(categoryKey)) grouped.set(categoryKey, []);
|
|
74
|
+
grouped.get(categoryKey).push(item);
|
|
75
|
+
});
|
|
76
|
+
return grouped;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const renderCategoryMenu = ({ prefix = '/', command = 'loja', availableCategories = [], selectedCategory = 'todos' }) => {
|
|
80
|
+
const categories = availableCategories.length ? availableCategories : ITEM_CATEGORY_ORDER;
|
|
81
|
+
const tokens = ['todos', ...categories.filter((key) => key !== 'todos')];
|
|
82
|
+
const tags = tokens.map((key) => {
|
|
83
|
+
const label = ITEM_CATEGORY_LABEL_MAP.get(key) || key;
|
|
84
|
+
return key === selectedCategory ? `[${label}]` : label;
|
|
85
|
+
});
|
|
86
|
+
return [`🧭 Categorias: ${tags.join(' | ')}`, `Use: ${prefix}rpg ${command} <todos|captura|cura|berry|tm|evolucao|outros>`];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const hpBar = (current, max, size = 10) => {
|
|
90
|
+
const safeMax = Math.max(1, toNumber(max, 1));
|
|
91
|
+
const safeCurrent = Math.max(0, Math.min(safeMax, toNumber(current, 0)));
|
|
92
|
+
const ratio = safeCurrent / safeMax;
|
|
93
|
+
const filled = Math.max(0, Math.min(size, Math.round(ratio * size)));
|
|
94
|
+
const empty = Math.max(0, size - filled);
|
|
95
|
+
return `${'█'.repeat(filled)}${'░'.repeat(empty)} ${safeCurrent}/${safeMax}`;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const moveLine = (move, index) => {
|
|
99
|
+
const power = toNumber(move?.power, 0);
|
|
100
|
+
const moveName = formatName(move?.displayName || move?.name || `Move ${index + 1}`);
|
|
101
|
+
const type = String(move?.type || 'normal').toUpperCase();
|
|
102
|
+
const slot = SLOT_ICONS[index] || `${index + 1}.`;
|
|
103
|
+
if (power <= 0) {
|
|
104
|
+
return `${slot} ${moveName} (${type})`;
|
|
105
|
+
}
|
|
106
|
+
return `${slot} ${moveName} (${type} • ${power})`;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const normalizeStatusKey = (value) => {
|
|
110
|
+
const key = String(value || '')
|
|
111
|
+
.trim()
|
|
112
|
+
.toLowerCase();
|
|
113
|
+
if (!key) return null;
|
|
114
|
+
if (key === 'paralyze' || key === 'paralysis' || key === 'par') return 'paralysis';
|
|
115
|
+
if (key === 'burn' || key === 'brn') return 'burn';
|
|
116
|
+
if (key === 'poison' || key === 'psn') return 'poison';
|
|
117
|
+
if (key === 'toxic' || key === 'bad-poison') return 'toxic';
|
|
118
|
+
if (key === 'sleep' || key === 'slp') return 'sleep';
|
|
119
|
+
if (key === 'freeze' || key === 'frz') return 'freeze';
|
|
120
|
+
if (key === 'confusion' || key === 'conf') return 'confusion';
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const statusLabel = (statusKey) => {
|
|
125
|
+
if (statusKey === 'paralysis') return '⚡ PAR';
|
|
126
|
+
if (statusKey === 'burn') return '🔥 BRN';
|
|
127
|
+
if (statusKey === 'poison') return '☠ PSN';
|
|
128
|
+
if (statusKey === 'toxic') return '☠ TOX';
|
|
129
|
+
if (statusKey === 'sleep') return '💤 SLP';
|
|
130
|
+
if (statusKey === 'freeze') return '❄️ FRZ';
|
|
131
|
+
if (statusKey === 'confusion') return '🌀 CONF';
|
|
132
|
+
return null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const buildStatusLine = (pokemon = {}) => {
|
|
136
|
+
const candidates = [pokemon?.nonVolatileStatus, ...(Array.isArray(pokemon?.statusEffects) ? pokemon.statusEffects : []), ...(toNumber(pokemon?.confusionTurns, 0) > 0 ? ['confusion'] : [])];
|
|
137
|
+
const labels = [];
|
|
138
|
+
for (const candidate of candidates) {
|
|
139
|
+
const normalized = normalizeStatusKey(candidate);
|
|
140
|
+
if (!normalized) continue;
|
|
141
|
+
const label = statusLabel(normalized);
|
|
142
|
+
if (!label || labels.includes(label)) continue;
|
|
143
|
+
labels.push(label);
|
|
144
|
+
}
|
|
145
|
+
if (!labels.length) return null;
|
|
146
|
+
return labels.join(' | ');
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const buildUsageText = (prefix = '/') => ['🎮 *RPG Pokémon - Comandos Rápidos*', '', `• ${prefix}rpg help`, `• ${prefix}rpg start`, `• ${prefix}rpg perfil`, `• ${prefix}rpg explorar`, `• ${prefix}rpg atacar <1-4>`, `• ${prefix}rpg capturar`, `• ${prefix}rpg time`, `• ${prefix}rpg bolsa`, '', `💡 Guia completo e exemplos: ${prefix}rpg help`].join('\n');
|
|
150
|
+
|
|
151
|
+
export const buildRpgHelpText = (prefix = '/') => {
|
|
152
|
+
const lines = ['📘 *RPG Pokémon - Help Completo*', '', `Prefixo atual: *${prefix}*`, '', '🚀 *Começo Rápido*', `1) ${prefix}rpg start`, `2) ${prefix}rpg perfil`, `3) ${prefix}rpg explorar`, `4) ${prefix}rpg atacar 1`, `5) ${prefix}rpg capturar`, '', '⚔️ *Batalha e Progressão*', `• ${prefix}rpg start`, 'Cria sua conta e entrega kit inicial.', `Exemplo: ${prefix}rpg start`, '', `• ${prefix}rpg perfil`, 'Mostra nível, XP, gold e Pokémon ativo.', `Exemplo: ${prefix}rpg perfil`, '', `• ${prefix}rpg explorar`, 'Inicia encontro selvagem.', `Exemplo: ${prefix}rpg explorar`, '', `• ${prefix}rpg atacar <1-4>`, 'Usa o golpe do slot escolhido.', `Exemplo: ${prefix}rpg atacar 2`, '', `• ${prefix}rpg capturar`, 'Tenta capturar usando Poké Bola comum.', `Exemplo: ${prefix}rpg capturar`, '', `• ${prefix}rpg fugir`, 'Encerra a batalha ativa.', `Exemplo: ${prefix}rpg fugir`, '', `• ${prefix}rpg ginasio`, 'Inicia batalha de ginásio.', `Exemplo: ${prefix}rpg ginasio`, '', '👥 *Time, Itens e Evolução*', `• ${prefix}rpg time`, 'Lista seu time completo e ID de cada Pokémon.', `Exemplo: ${prefix}rpg time`, '', `• ${prefix}rpg escolher <pokemon_id>`, 'Define qual Pokémon fica ativo.', `Exemplo: ${prefix}rpg escolher 12`, '', `• ${prefix}rpg bolsa`, 'Lista itens no inventário.', `Exemplo: ${prefix}rpg bolsa`, '', `• ${prefix}rpg loja`, 'Mostra itens disponíveis para compra.', `Exemplo: ${prefix}rpg loja`, '', `• ${prefix}rpg comprar <item> <qtd>`, 'Compra item da loja.', `Exemplo: ${prefix}rpg comprar pokeball 5`, '', `• ${prefix}rpg usar <item>`, 'Usa item de cura, evolução ou captura.', `Exemplo: ${prefix}rpg usar pocao`, '', `• ${prefix}rpg pokedex`, 'Mostra progresso da Pokédex.', `Exemplo: ${prefix}rpg pokedex`, '', `• ${prefix}rpg evolucao <pokemon|id>`, 'Mostra linha evolutiva do alvo.', `Exemplo: ${prefix}rpg evolucao pikachu`, '', `• ${prefix}rpg missoes`, 'Exibe missões diária/semanal.', `Exemplo: ${prefix}rpg missoes`, '', '🧭 *Viagem e Conteúdo Avançado*', `• ${prefix}rpg viajar [regiao]`, 'Sem região: mostra status. Com região: viaja.', `Exemplo: ${prefix}rpg viajar paldea`, '', `• ${prefix}rpg tm listar`, 'Lista TMs na bolsa.', `Exemplo: ${prefix}rpg tm listar`, '', `• ${prefix}rpg tm usar <tm> <1-4>`, 'Ensina golpe no slot informado.', `Exemplo: ${prefix}rpg tm usar tm-thunderbolt 1`, '', `• ${prefix}rpg berry listar`, 'Lista berries disponíveis.', `Exemplo: ${prefix}rpg berry listar`, '', `• ${prefix}rpg berry usar <item>`, 'Usa berry no Pokémon ativo.', `Exemplo: ${prefix}rpg berry usar oran-berry`, '', `• ${prefix}rpg raid <iniciar|entrar|atacar|status>`, 'Sistema de boss em grupo.', `Exemplo: ${prefix}rpg raid iniciar`, '', '🥊 *PvP e Interação Social*', `• ${prefix}rpg desafiar <jid/@numero>`, 'Cria desafio direto contra outro jogador.', `Exemplo: ${prefix}rpg desafiar @usuario`, '', `• ${prefix}rpg pvp status`, 'Mostra desafios pendentes/ativos.', `Exemplo: ${prefix}rpg pvp status`, '', `• ${prefix}rpg pvp fila <entrar|sair|status>`, 'Matchmaking automático no grupo.', `Exemplo: ${prefix}rpg pvp fila entrar`, '', `• ${prefix}rpg pvp ranking`, 'Ranking semanal de PvP.', `Exemplo: ${prefix}rpg pvp ranking`, '', `• ${prefix}rpg pvp revanche [@usuario]`, 'Cria revanche com último rival ou alvo informado.', `Exemplo: ${prefix}rpg pvp revanche`, '', `• ${prefix}rpg pvp aceitar <id>`, `• ${prefix}rpg pvp recusar <id>`, `• ${prefix}rpg pvp atacar <1-4>`, `• ${prefix}rpg pvp fugir`, '', `• ${prefix}rpg trade <status|propor|aceitar|recusar|cancelar>`, 'Sistema de trocas entre jogadores.', `Exemplo: ${prefix}rpg trade propor @usuario item:potion:2 pokemon:15`, '', `• ${prefix}rpg coop`, 'Status da missão cooperativa semanal.', `Exemplo: ${prefix}rpg coop`, '', `• ${prefix}rpg evento <status|claim>`, 'Evento semanal do grupo e resgate de recompensa.', `Exemplo: ${prefix}rpg evento claim`, '', `• ${prefix}rpg social [status @usuario]`, 'Painel social e vínculo entre jogadores.', `Exemplo: ${prefix}rpg social status @usuario`, '', `• ${prefix}rpg karma <status|top|+|->`, 'Sistema de reputação.', `Exemplo: ${prefix}rpg karma + @usuario`, '', `• ${prefix}rpg engajamento`, 'Métricas de atividade/retensão do grupo.', `Exemplo: ${prefix}rpg engajamento`, '', `💡 *Dicas*`, '1) Use IDs do comando time para escolher/trocar Pokémon ativo.', '2) Em batalha: primeiro ataque para baixar HP, depois capture.', `3) Para ajuda a qualquer momento: use ${prefix}rpg help ou ${prefix}rpg ajuda.`];
|
|
153
|
+
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const buildCooldownText = ({ secondsLeft, prefix = '/' }) => ['⏳ *Cooldown ativo*', `Espere *${secondsLeft}s* para agir novamente.`, '', `💡 Enquanto isso: ${prefix}rpg perfil`].join('\n');
|
|
158
|
+
|
|
159
|
+
export const buildNeedStartText = (prefix = '/') => ['🧭 *Jornada não iniciada*', 'Você ainda não iniciou sua jornada Pokémon.', '', `👉 Use: ${prefix}rpg start`, `💡 Depois: ${prefix}rpg perfil`].join('\n');
|
|
160
|
+
|
|
161
|
+
export const buildStartText = ({ isNewPlayer, starterPokemon, prefix = '/' }) => {
|
|
162
|
+
if (!isNewPlayer) {
|
|
163
|
+
return ['✅ *Conta já existente*', 'Você já possui conta no RPG.', '', `📘 Próximo: ${prefix}rpg perfil`, `🧭 Recomendado: ${prefix}rpg explorar`].join('\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return ['🎉 *Jornada iniciada com sucesso!*', '', `🧩 Parceiro inicial: *${formatPokemonLabel({ name: starterPokemon.displayName || starterPokemon.name, isShiny: starterPokemon.isShiny })}*`, `🆔 ID no seu time: *${starterPokemon.id}*`, ...(starterPokemon?.flavorText ? ['', `📖 ${starterPokemon.flavorText}`] : []), '', '🎁 Kit inicial: 4x Poke Bola + 3x Poção', '', `➡️ Próximos: ${prefix}rpg perfil | ${prefix}rpg explorar`, `💡 Dica: explore com frequência para subir nível e capturar novos Pokémon.`].join('\n');
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const toDisplayText = (value, fallback = 'N/D') => {
|
|
170
|
+
if (value === null || value === undefined) return fallback;
|
|
171
|
+
const text = String(value).trim();
|
|
172
|
+
return text ? text : fallback;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const formatPercent = (value) => {
|
|
176
|
+
if (!Number.isFinite(Number(value))) return 'N/D';
|
|
177
|
+
return `${Math.max(0, Number(value))}%`;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const formatRatio = (value) => {
|
|
181
|
+
if (String(value) === 'inf') return '∞';
|
|
182
|
+
const numeric = Number(value);
|
|
183
|
+
if (!Number.isFinite(numeric)) return 'N/D';
|
|
184
|
+
return numeric.toFixed(2);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const buildProfileText = ({ player, activePokemon, profile = {}, prefix = '/' }) => {
|
|
188
|
+
const summary = profile?.summary || {};
|
|
189
|
+
const combat = profile?.combat || {};
|
|
190
|
+
const collection = profile?.collection || {};
|
|
191
|
+
const progression = profile?.progression || {};
|
|
192
|
+
const economy = profile?.economy || {};
|
|
193
|
+
const social = profile?.social || {};
|
|
194
|
+
const achievements = Array.isArray(profile?.achievements) ? profile.achievements : [];
|
|
195
|
+
const goals = Array.isArray(profile?.goals) ? profile.goals : [];
|
|
196
|
+
|
|
197
|
+
const lines = ['📘 *Seu Perfil RPG*', '', '📌 *Resumo rápido*', `🏅 Nível: *${toNumber(summary?.level, toNumber(player?.level, 1))}*`, `✨ XP: *${toNumber(player?.xp, 0)}*`, `🪙 Gold: *${toNumber(player?.gold, 0)}*`, `🏆 Rank PvP semanal: *${toDisplayText(summary?.pvpWeeklyRank, 'Sem rank')}*`, `🔥 Streak atual: *${toDisplayText(summary?.streak?.label, 'Sem histórico')}*`];
|
|
198
|
+
|
|
199
|
+
if (summary?.isMaxLevel) {
|
|
200
|
+
lines.push('📈 Progresso de nível: *nível máximo alcançado*');
|
|
201
|
+
} else {
|
|
202
|
+
lines.push(`📈 Progresso de nível: *${toNumber(summary?.xpProgressPct, 0)}%* (${toNumber(summary?.xpIntoLevel, 0)}/${toNumber(summary?.xpNeededForNextLevel, 0)} XP no nível)`);
|
|
203
|
+
lines.push(`⏭️ Próximo nível: *Lv.${toNumber(summary?.nextLevel, toNumber(player?.level, 1) + 1)}* (faltam *${toNumber(summary?.xpToNextLevel, 0)} XP*)`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (summary?.weekRefDate) {
|
|
207
|
+
lines.push(`📅 Semana PvP: ${summary.weekRefDate}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (activePokemon) {
|
|
211
|
+
lines.push('');
|
|
212
|
+
lines.push('🧩 *Pokémon ativo*');
|
|
213
|
+
lines.push(`• ${formatPokemonLabel({ name: activePokemon.displayName || activePokemon.name, isShiny: activePokemon.isShiny })} (ID: ${activePokemon.id})`);
|
|
214
|
+
lines.push(`• ❤️ HP: ${hpBar(activePokemon.currentHp, activePokemon.maxHp)}`);
|
|
215
|
+
if (activePokemon.natureName) {
|
|
216
|
+
lines.push(`• 🧬 Nature: *${formatName(activePokemon.natureName)}*`);
|
|
217
|
+
}
|
|
218
|
+
if (activePokemon.genus) {
|
|
219
|
+
lines.push(`• 📚 Espécie: ${activePokemon.genus}`);
|
|
220
|
+
}
|
|
221
|
+
if (activePokemon.abilityName) {
|
|
222
|
+
lines.push(`• ✨ Habilidade: *${formatName(activePokemon.abilityName)}*`);
|
|
223
|
+
}
|
|
224
|
+
if (activePokemon.abilityEffectText) {
|
|
225
|
+
lines.push(`• 🧠 Efeito: ${activePokemon.abilityEffectText}`);
|
|
226
|
+
}
|
|
227
|
+
if (activePokemon.flavorText) {
|
|
228
|
+
lines.push('');
|
|
229
|
+
lines.push(`📖 ${activePokemon.flavorText}`);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
lines.push('');
|
|
233
|
+
lines.push('⚠️ Você ainda não tem Pokémon ativo selecionado.');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
lines.push('');
|
|
237
|
+
lines.push('⚔️ *Time e combate*');
|
|
238
|
+
lines.push(`• PvP semana: ${toNumber(combat?.weeklyWins, 0)}W/${toNumber(combat?.weeklyLosses, 0)}L (${toNumber(combat?.weeklyMatches, 0)} partidas)`);
|
|
239
|
+
lines.push(`• PvP total: ${toNumber(combat?.lifetimeWins, 0)}W/${toNumber(combat?.lifetimeLosses, 0)}L (${toNumber(combat?.lifetimeMatches, 0)} partidas)`);
|
|
240
|
+
lines.push(`• Win rate: ${formatPercent(combat?.winRatePct)} | K/D: ${formatRatio(combat?.kdRatio)}`);
|
|
241
|
+
lines.push(`• Dano médio: ${toDisplayText(combat?.averageDamage)}`);
|
|
242
|
+
lines.push(`• Melhor vitória: ${toDisplayText(combat?.bestVictory, 'Sem vitórias recentes')}`);
|
|
243
|
+
lines.push(`• Pokémon mais usado: ${toDisplayText(combat?.mostUsedPokemon, 'Sem histórico suficiente')}`);
|
|
244
|
+
|
|
245
|
+
lines.push('');
|
|
246
|
+
lines.push('📚 *Captura e coleção*');
|
|
247
|
+
lines.push(`• Capturas totais: ${toNumber(collection?.capturesTotal, 0)}`);
|
|
248
|
+
lines.push(`• Taxa de captura: ${collection?.captureRatePct === null || collection?.captureRatePct === undefined ? 'N/D (histórico não rastreado)' : formatPercent(collection?.captureRatePct)}`);
|
|
249
|
+
lines.push(`• Pokédex: ${toNumber(collection?.pokedexUnique, 0)}/${toNumber(collection?.pokedexTotal, 0)} (${formatPercent(collection?.pokedexCompletionPct)})`);
|
|
250
|
+
lines.push(`• Raros/Shiny: ${collection?.rareCount === null || collection?.rareCount === undefined ? 'Raros N/D' : `Raros ${toNumber(collection?.rareCount, 0)}`} | Shiny ${toNumber(collection?.shinyCount, 0)}`);
|
|
251
|
+
lines.push(`• Última captura: ${collection?.latestCapture ? `${toDisplayText(collection.latestCapture.name)} (#${toNumber(collection.latestCapture.pokeId, 0)}) em ${toDisplayText(collection.latestCapture.capturedAt, 'data indisponível')}` : 'Sem registros recentes'}`);
|
|
252
|
+
|
|
253
|
+
lines.push('');
|
|
254
|
+
lines.push('📈 *Progressão*');
|
|
255
|
+
lines.push(`• Missão diária: ${toNumber(progression?.dailyMission?.explorar, 0)}/${toNumber(progression?.dailyMission?.target?.explorar, 0)} explorar, ${toNumber(progression?.dailyMission?.vitorias, 0)}/${toNumber(progression?.dailyMission?.target?.vitorias, 0)} vitórias, ${toNumber(progression?.dailyMission?.capturas, 0)}/${toNumber(progression?.dailyMission?.target?.capturas, 0)} capturas (${formatPercent(progression?.dailyMissionPct)})`);
|
|
256
|
+
lines.push(`• Missão semanal: ${toNumber(progression?.weeklyMission?.explorar, 0)}/${toNumber(progression?.weeklyMission?.target?.explorar, 0)} explorar, ${toNumber(progression?.weeklyMission?.vitorias, 0)}/${toNumber(progression?.weeklyMission?.target?.vitorias, 0)} vitórias, ${toNumber(progression?.weeklyMission?.capturas, 0)}/${toNumber(progression?.weeklyMission?.target?.capturas, 0)} capturas (${formatPercent(progression?.weeklyMissionPct)})`);
|
|
257
|
+
if (progression?.event) {
|
|
258
|
+
lines.push(`• Evento ativo: ${toDisplayText(progression.event.label)} ${toNumber(progression.event.progress, 0)}/${toNumber(progression.event.target, 0)} (${formatPercent(progression.event.progressPct)}) [${toDisplayText(progression.event.status, 'ativo')}]`);
|
|
259
|
+
} else {
|
|
260
|
+
lines.push('• Evento ativo: indisponível fora de grupo');
|
|
261
|
+
}
|
|
262
|
+
const pendingRewards = Array.isArray(progression?.pendingRewards) ? progression.pendingRewards : [];
|
|
263
|
+
lines.push(`• Recompensas pendentes: ${pendingRewards.length ? pendingRewards.join(', ') : 'Nenhuma'}`);
|
|
264
|
+
|
|
265
|
+
lines.push('');
|
|
266
|
+
lines.push('💰 *Economia*');
|
|
267
|
+
lines.push(`• Saldo atual: ${toNumber(economy?.gold, 0)} gold`);
|
|
268
|
+
lines.push(`• Gasto total: ${economy?.totalSpent === null || economy?.totalSpent === undefined ? 'N/D (histórico não rastreado)' : `${toNumber(economy?.totalSpent, 0)} gold`}`);
|
|
269
|
+
const topItems = Array.isArray(economy?.inventoryTop) ? economy.inventoryTop : [];
|
|
270
|
+
if (topItems.length) {
|
|
271
|
+
lines.push(`• Itens principais: ${topItems.map((item) => `${toDisplayText(item.label)} x${toNumber(item.quantity, 0)}`).join(' | ')}`);
|
|
272
|
+
} else {
|
|
273
|
+
lines.push('• Itens principais: bolsa vazia');
|
|
274
|
+
}
|
|
275
|
+
lines.push(`• Valor estimado da bolsa: ${toNumber(economy?.inventoryEstimatedValue, 0)} gold`);
|
|
276
|
+
|
|
277
|
+
lines.push('');
|
|
278
|
+
lines.push('🤝 *Social e Karma*');
|
|
279
|
+
lines.push(`• Karma: ${toNumber(social?.karmaScore, 0)} (${social?.karmaBonusActive ? 'bônus ativo' : `faltam ${Math.max(0, toNumber(social?.karmaThreshold, 0) - toNumber(social?.karmaScore, 0))} para bônus`})`);
|
|
280
|
+
lines.push(`• Votos: 👍 ${toNumber(social?.positiveVotes, 0)} | 👎 ${toNumber(social?.negativeVotes, 0)}`);
|
|
281
|
+
lines.push(`• Interações sociais úteis: ${toNumber(social?.interactionsTotal, 0)} em ${toNumber(social?.linksTotal, 0)} vínculo(s)`);
|
|
282
|
+
lines.push(`• Melhor amizade/rivalidade: ${toNumber(social?.topFriendship, 0)} / ${toNumber(social?.topRivalry, 0)}`);
|
|
283
|
+
lines.push(`• Contribuição coop (captura/raid): ${toNumber(social?.coopCaptureContribution, 0)}/${toNumber(social?.coopRaidContribution, 0)}`);
|
|
284
|
+
lines.push(`• Contribuição em evento semanal: ${toNumber(social?.eventContribution, 0)}`);
|
|
285
|
+
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push('🏅 *Conquistas*');
|
|
288
|
+
achievements.forEach((badge) => {
|
|
289
|
+
lines.push(`• ${badge}`);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
lines.push('');
|
|
293
|
+
lines.push('🎯 *Metas sugeridas*');
|
|
294
|
+
goals.forEach((goal, index) => {
|
|
295
|
+
lines.push(`${index + 1}. ${goal}`);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
lines.push('');
|
|
299
|
+
lines.push(`➡️ Próximos: ${prefix}rpg explorar | ${prefix}rpg time`);
|
|
300
|
+
lines.push(`💡 Dica: use ${prefix}rpg bolsa, ${prefix}rpg missoes e ${prefix}rpg pvp ranking para avançar nas metas.`);
|
|
301
|
+
return lines.join('\n');
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export const buildTeamText = ({ team, prefix = '/' }) => {
|
|
305
|
+
if (!team.length) {
|
|
306
|
+
return ['🫥 *Seu time está vazio*', '', `👉 Capture um Pokémon em batalha: ${prefix}rpg explorar`, `➡️ Depois: ${prefix}rpg capturar`].join('\n');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const rows = team.map((pokemon) => {
|
|
310
|
+
const marker = pokemon.isActive ? '⭐' : '•';
|
|
311
|
+
const trait = pokemon.natureName || pokemon.abilityName ? ` | ${pokemon.natureName ? `🧬 ${formatName(pokemon.natureName)}` : ''}${pokemon.natureName && pokemon.abilityName ? ' • ' : ''}${pokemon.abilityName ? `✨ ${formatName(pokemon.abilityName)}` : ''}` : '';
|
|
312
|
+
return `${marker} ID ${pokemon.id} | ${formatPokemonLabel({ name: pokemon.displayName || pokemon.name, isShiny: pokemon.isShiny })} Lv.${pokemon.level} | ❤️ ${pokemon.currentHp}/${pokemon.maxHp}${trait}`;
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return ['👥 *Seu Time Pokémon*', '⭐ = ativo', '', ...rows, '', `🔁 Trocar ativo: ${prefix}rpg escolher <pokemon_id>`, `💡 Dica: mantenha o ativo com HP alto antes de explorar.`].join('\n');
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export const buildNeedActivePokemonText = (prefix = '/') => ['⚠️ *Sem Pokémon ativo para batalhar*', '', `👉 Use: ${prefix}rpg time`, `➡️ Depois: ${prefix}rpg escolher <pokemon_id>`].join('\n');
|
|
319
|
+
|
|
320
|
+
export const buildPokemonFaintedText = (prefix = '/') => ['💥 *Seu Pokémon ativo está sem HP*', '', `🔁 Escolha outro: ${prefix}rpg escolher <pokemon_id>`, `💡 Dica: use pocao/superpocao com ${prefix}rpg usar <item>`].join('\n');
|
|
321
|
+
|
|
322
|
+
export const buildBattleStartText = ({ battleSnapshot, prefix = '/' }) => {
|
|
323
|
+
const my = battleSnapshot.my;
|
|
324
|
+
const enemy = battleSnapshot.enemy;
|
|
325
|
+
const lines = [];
|
|
326
|
+
|
|
327
|
+
if (battleSnapshot.mode === 'gym') {
|
|
328
|
+
lines.push('🏟️ *Desafio de Ginásio!*');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (enemy.isShiny) {
|
|
332
|
+
lines.push('✨ *UM POKÉMON SHINY APARECEU!* ✨');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (battleSnapshot.biome?.label) {
|
|
336
|
+
lines.push(`🌍 Bioma: ${battleSnapshot.biome.label}`);
|
|
337
|
+
}
|
|
338
|
+
if (battleSnapshot.travel?.regionKey) {
|
|
339
|
+
lines.push(`🧭 Região: ${formatName(battleSnapshot.travel.regionKey)}`);
|
|
340
|
+
}
|
|
341
|
+
if (enemy.habitat) {
|
|
342
|
+
lines.push(`🏞️ Habitat: ${formatName(enemy.habitat)}`);
|
|
343
|
+
}
|
|
344
|
+
if (enemy.genus) {
|
|
345
|
+
lines.push(`📚 Espécie: ${enemy.genus}`);
|
|
346
|
+
}
|
|
347
|
+
if (enemy.isLegendary || enemy.isMythical) {
|
|
348
|
+
lines.push(enemy.isMythical ? '🌟 Status: Mítico' : '👑 Status: Lendário');
|
|
349
|
+
}
|
|
350
|
+
if (enemy.flavorText) {
|
|
351
|
+
lines.push(`📖 ${enemy.flavorText}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
lines.push('');
|
|
355
|
+
lines.push('⚔️ *Confronto*');
|
|
356
|
+
lines.push(`🐾 Inimigo: *${formatPokemonLabel({ name: enemy.displayName || enemy.name, isShiny: enemy.isShiny })}* Lv.${enemy.level}`);
|
|
357
|
+
lines.push(`❤️ HP inimigo: ${hpBar(enemy.currentHp, enemy.maxHp)}`);
|
|
358
|
+
const enemyStatus = buildStatusLine(enemy);
|
|
359
|
+
if (enemyStatus) lines.push(`🧪 Status inimigo: ${enemyStatus}`);
|
|
360
|
+
lines.push(`🧩 Seu Pokémon: *${formatPokemonLabel({ name: my.displayName || my.name, isShiny: my.isShiny })}* Lv.${my.level}`);
|
|
361
|
+
lines.push(`❤️ Seu HP: ${hpBar(my.currentHp, my.maxHp)}`);
|
|
362
|
+
const myStatus = buildStatusLine(my);
|
|
363
|
+
if (myStatus) lines.push(`🧪 Seu status: ${myStatus}`);
|
|
364
|
+
lines.push('');
|
|
365
|
+
lines.push('📚 *Movimentos disponíveis*');
|
|
366
|
+
lines.push(...my.moves.map(moveLine));
|
|
367
|
+
lines.push('');
|
|
368
|
+
lines.push(`➡️ Ações: ${prefix}rpg atacar <1-4> | ${prefix}rpg capturar | ${prefix}rpg usar pokeball | ${prefix}rpg fugir`);
|
|
369
|
+
lines.push(`💡 Dica: diminua o HP inimigo para aumentar a chance de captura.`);
|
|
370
|
+
|
|
371
|
+
return lines.join('\n');
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
export const buildBattleTurnText = ({ logs = [], battleSnapshot, prefix = '/', rewards = null, evolution = null }) => {
|
|
375
|
+
const my = battleSnapshot.my;
|
|
376
|
+
const enemy = battleSnapshot.enemy;
|
|
377
|
+
|
|
378
|
+
const lines = [...logs, '', `❤️ Seu HP: ${hpBar(my.currentHp, my.maxHp)}`, `❤️ HP inimigo: ${hpBar(enemy.currentHp, enemy.maxHp)}`];
|
|
379
|
+
const myStatus = buildStatusLine(my);
|
|
380
|
+
const enemyStatus = buildStatusLine(enemy);
|
|
381
|
+
if (myStatus) lines.push(`🧪 Seu status: ${myStatus}`);
|
|
382
|
+
if (enemyStatus) lines.push(`🧪 Status inimigo: ${enemyStatus}`);
|
|
383
|
+
|
|
384
|
+
if (enemy.currentHp <= 0 && rewards) {
|
|
385
|
+
lines.push('');
|
|
386
|
+
lines.push(`🏆 *Vitória!* +${rewards.playerXp} XP jogador | +${rewards.pokemonXp} XP Pokémon | +${rewards.gold} gold`);
|
|
387
|
+
if (evolution?.fromName && evolution?.toName) {
|
|
388
|
+
lines.push(`🎉 Seu ${formatName(evolution.fromName)} evoluiu para ${formatName(evolution.toName)}!`);
|
|
389
|
+
}
|
|
390
|
+
lines.push('');
|
|
391
|
+
lines.push(`➡️ Próximo: ${prefix}rpg explorar`);
|
|
392
|
+
lines.push(`💡 Dica: confira missões em ${prefix}rpg missoes`);
|
|
393
|
+
return lines.join('\n');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (my.currentHp <= 0) {
|
|
397
|
+
lines.push('');
|
|
398
|
+
lines.push('💥 Seu Pokémon desmaiou.');
|
|
399
|
+
lines.push('❌ Batalha perdida e encerrada.');
|
|
400
|
+
lines.push(`➡️ Próximo: ${prefix}rpg escolher <pokemon_id>`);
|
|
401
|
+
lines.push(`💡 Dica: recupere HP com ${prefix}rpg usar pocao`);
|
|
402
|
+
return lines.join('\n');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
lines.push('');
|
|
406
|
+
lines.push(`➡️ Ações: ${prefix}rpg atacar <1-4> | ${prefix}rpg capturar | ${prefix}rpg usar pokeball | ${prefix}rpg fugir`);
|
|
407
|
+
return lines.join('\n');
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
export const buildCaptureSuccessText = ({ capturedPokemon, prefix = '/' }) => ['🎉 *Captura concluída!*', '', `✅ Você capturou *${formatPokemonLabel({ name: capturedPokemon.displayName || capturedPokemon.name, isShiny: capturedPokemon.isShiny })}* (ID ${capturedPokemon.id}).`, ...(capturedPokemon?.flavorText ? ['', `📖 ${capturedPokemon.flavorText}`] : []), '', `➡️ Próximos: ${prefix}rpg time | ${prefix}rpg explorar`, `💡 Dica: defina como ativo com ${prefix}rpg escolher ${capturedPokemon.id}`].join('\n');
|
|
411
|
+
|
|
412
|
+
export const buildCaptureBlockedGymText = (prefix = '/') => ['🚫 Em batalha de ginásio não é possível capturar.', '', `➡️ Use: ${prefix}rpg atacar <1-4> ou ${prefix}rpg fugir`].join('\n');
|
|
413
|
+
|
|
414
|
+
export const buildCaptureFailText = ({ logs = [], battleSnapshot, prefix = '/' }) => {
|
|
415
|
+
const my = battleSnapshot.my;
|
|
416
|
+
const enemy = battleSnapshot.enemy;
|
|
417
|
+
|
|
418
|
+
const lines = [...logs, '', `❤️ Seu HP: ${hpBar(my.currentHp, my.maxHp)}`, `❤️ HP inimigo: ${hpBar(enemy.currentHp, enemy.maxHp)}`];
|
|
419
|
+
const myStatus = buildStatusLine(my);
|
|
420
|
+
const enemyStatus = buildStatusLine(enemy);
|
|
421
|
+
if (myStatus) lines.push(`🧪 Seu status: ${myStatus}`);
|
|
422
|
+
if (enemyStatus) lines.push(`🧪 Status inimigo: ${enemyStatus}`);
|
|
423
|
+
|
|
424
|
+
if (my.currentHp <= 0) {
|
|
425
|
+
lines.push('');
|
|
426
|
+
lines.push('❌ Batalha perdida e encerrada.');
|
|
427
|
+
lines.push(`➡️ Próximo: ${prefix}rpg escolher <pokemon_id>`);
|
|
428
|
+
return lines.join('\n');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
lines.push('');
|
|
432
|
+
lines.push(`➡️ Ações: ${prefix}rpg atacar <1-4> | ${prefix}rpg capturar | ${prefix}rpg usar pokeball | ${prefix}rpg fugir`);
|
|
433
|
+
lines.push('💡 Dica: tente capturar com HP inimigo bem baixo.');
|
|
434
|
+
return lines.join('\n');
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
export const buildFleeText = (prefix = '/') => ['🏃 Você fugiu da batalha com segurança.', '', `➡️ Próximo: ${prefix}rpg explorar`].join('\n');
|
|
438
|
+
|
|
439
|
+
export const buildNoBattleText = (prefix = '/') => ['⚠️ Nenhuma batalha ativa no momento.', '', `👉 Use: ${prefix}rpg explorar`].join('\n');
|
|
440
|
+
|
|
441
|
+
export const buildShopText = ({ items, prefix = '/', availableCategories = [], selectedCategory = 'todos', selectedCategoryLabel = 'Todas', invalidCategory = null }) => {
|
|
442
|
+
const lines = ['🛒 *Loja RPG*', ''];
|
|
443
|
+
lines.push(...renderCategoryMenu({ prefix, command: 'loja', availableCategories, selectedCategory }));
|
|
444
|
+
if (invalidCategory) {
|
|
445
|
+
lines.push('', `⚠️ Categoria inválida: *${invalidCategory}*`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!items.length) {
|
|
449
|
+
lines.push('', `📭 Sem itens na categoria *${selectedCategoryLabel || 'Selecionada'}* no momento.`);
|
|
450
|
+
lines.push(`💡 Tente: ${prefix}rpg loja todos`);
|
|
451
|
+
return lines.join('\n');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const grouped = groupItemsByCategory(items);
|
|
455
|
+
const categoryOrder = ITEM_CATEGORY_ORDER.filter((key) => grouped.has(key));
|
|
456
|
+
categoryOrder.forEach((categoryKey) => {
|
|
457
|
+
const categoryLabel = ITEM_CATEGORY_LABEL_MAP.get(categoryKey) || categoryKey;
|
|
458
|
+
lines.push('', `━━━━━━━━ ${categoryLabel} ━━━━━━━━`);
|
|
459
|
+
grouped.get(categoryKey).forEach((item) => {
|
|
460
|
+
lines.push(`• ${itemEmoji(item.key)} *${item.label || item.key}* [${item.key}] — ${item.price} gold`);
|
|
461
|
+
lines.push(` Para que serve: ${itemMeaning(item)}`);
|
|
462
|
+
lines.push(` Como usar: ${itemUseCommand({ item, prefix })}`);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
lines.push('');
|
|
467
|
+
lines.push(`🧾 Comprar: ${prefix}rpg comprar <item> <qtd>`);
|
|
468
|
+
lines.push(`🎒 Usar por nome: ${prefix}rpg usar <item>`);
|
|
469
|
+
lines.push(`🔢 Usar por número da bolsa: ${prefix}rpg usar <slot>`);
|
|
470
|
+
lines.push('💡 Dica: mantenha pokeball e pocao na bolsa antes de explorar.');
|
|
471
|
+
return lines.join('\n');
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
export const buildBuySuccessText = ({ item, quantity, totalPrice, goldLeft, prefix = '/' }) => ['✅ *Compra concluída!*', '', `🛍️ ${quantity}x *${item.label}* por ${totalPrice} gold`, `🪙 Gold restante: *${goldLeft}*`, '', `➡️ Próximos: ${prefix}rpg bolsa | ${prefix}rpg loja`].join('\n');
|
|
475
|
+
|
|
476
|
+
export const buildBuyErrorText = ({ reason = 'erro', rescue = null, prefix = '/' }) => {
|
|
477
|
+
if (reason === 'invalid_item') return `❌ Item inválido.\n\n👉 Confira a loja: ${prefix}rpg loja`;
|
|
478
|
+
if (reason === 'invalid_quantity') return `❌ Quantidade inválida.\n\n👉 Use: ${prefix}rpg comprar <item> <qtd>`;
|
|
479
|
+
if (reason === 'battle_active') return `⚔️ Compra bloqueada durante batalha ativa.\n\n👉 Finalize a batalha com: ${prefix}rpg atacar <1-4> | ${prefix}rpg fugir`;
|
|
480
|
+
if (reason === 'not_enough_gold') {
|
|
481
|
+
if (rescue) {
|
|
482
|
+
return ['🪙 Gold insuficiente para essa compra.', '', `🆘 Ajuda emergencial recebida: +${toNumber(rescue?.grantedGold, 0)} gold e +${toNumber(rescue?.grantedPotions, 0)} Poção`, `🪙 Gold atual: *${toNumber(rescue?.nextGold, 0)}*`, '', `👉 Próximos: ${prefix}rpg usar pocao | ${prefix}rpg explorar`].join('\n');
|
|
483
|
+
}
|
|
484
|
+
return `🪙 Gold insuficiente para essa compra.\n\n💡 Dica: vença batalhas e missões para ganhar mais gold.\n👉 Use: ${prefix}rpg loja`;
|
|
485
|
+
}
|
|
486
|
+
return `❌ Não foi possível processar a compra agora.\n\n👉 Tente novamente: ${prefix}rpg loja`;
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
export const buildBattleAlreadyActiveText = (prefix = '/') => ['⚔️ Você já está em batalha ativa.', '', `➡️ Ações: ${prefix}rpg atacar <1-4> | ${prefix}rpg capturar | ${prefix}rpg usar pokeball | ${prefix}rpg fugir`].join('\n');
|
|
490
|
+
|
|
491
|
+
export const buildUseItemUsageText = (prefix = '/') => ['🎒 *Guia Completo de Uso de Itens*', '', '📌 *Regra principal*', `Sempre envie o comando com o *nome do item* ou com o *número do slot da bolsa*.`, `Ex.: ${prefix}rpg usar pokeball`, `Ex.: ${prefix}rpg usar 1`, '', '🧭 *Passo a passo recomendado*', `1) Abra a bolsa: ${prefix}rpg bolsa`, '2) Escolha o item pelo nome `[chave]` ou pelo número do slot', `3) Use o comando: ${prefix}rpg usar <item|slot>`, '', '🧪 *Formatos de uso por tipo*', `• Item comum (cura/captura/evolução): ${prefix}rpg usar <item|slot>`, `• Poké Bola: ${prefix}rpg usar pokeball (somente em batalha)`, `• TM: ${prefix}rpg tm usar <tm> <1-4>`, `• Berry: ${prefix}rpg berry usar <item>`, '', '⚠️ *Regras importantes*', '• Poké Bola não funciona fora de batalha.', '• Se o Pokémon ativo estiver com HP cheio, item de cura não é gasto.', '• TM consome o item e substitui o golpe do slot escolhido.', '', `💡 Dica: use ${prefix}rpg loja para ver descrição e finalidade de cada item.`].join('\n');
|
|
492
|
+
|
|
493
|
+
export const buildUseItemErrorText = ({ reason = 'invalid_item', prefix = '/' }) => {
|
|
494
|
+
if (reason === 'invalid_item') {
|
|
495
|
+
return ['❌ *Item inválido para uso*', '', 'Possíveis causas:', '1) Nome digitado diferente do item da bolsa.', '2) Você tentou usar TM/berry com comando errado.', '3) O item não é utilizável no momento atual.', '', 'Como corrigir:', `• Veja os nomes exatos: ${prefix}rpg bolsa`, `• Use por nome: ${prefix}rpg usar <item>`, `• Use por slot: ${prefix}rpg usar <numero>`, `• TM: ${prefix}rpg tm usar <tm> <1-4>`, `• Berry: ${prefix}rpg berry usar <item>`, '', buildUseItemUsageText(prefix)].join('\n');
|
|
496
|
+
}
|
|
497
|
+
if (reason === 'no_item') {
|
|
498
|
+
return ['🎒 *Você não tem esse item na bolsa*', '', `• Confira estoque e nome exato: ${prefix}rpg bolsa`, `• Compre na loja: ${prefix}rpg loja`, `• Compra rápida: ${prefix}rpg comprar <item> <qtd>`, '', `Exemplo: ${prefix}rpg comprar pokeball 5`].join('\n');
|
|
499
|
+
}
|
|
500
|
+
if (reason === 'full_hp') {
|
|
501
|
+
return ['❤️ *Seu Pokémon já está com HP cheio*', '', 'Nenhum item de cura foi consumido.', `👉 Continue a jornada: ${prefix}rpg explorar`, `💡 Se quiser, guarde o item para batalhas mais difíceis.`].join('\n');
|
|
502
|
+
}
|
|
503
|
+
if (reason === 'no_active_pokemon') {
|
|
504
|
+
return ['⚠️ *Sem Pokémon ativo*', '', 'Você precisa definir um Pokémon ativo antes de usar itens nele.', `1) Ver time: ${prefix}rpg time`, `2) Escolher ativo: ${prefix}rpg escolher <pokemon_id>`].join('\n');
|
|
505
|
+
}
|
|
506
|
+
if (reason === 'no_battle_for_pokeball') {
|
|
507
|
+
return ['⚪ *Poké Bola só funciona em batalha*', '', `1) Inicie batalha: ${prefix}rpg explorar`, `2) Enfraqueça o alvo: ${prefix}rpg atacar <1-4>`, `3) Capture: ${prefix}rpg usar pokeball`, '', `Atalho: ${prefix}rpg capturar`].join('\n');
|
|
508
|
+
}
|
|
509
|
+
return ['❌ Não foi possível usar item agora.', '', 'Tente novamente seguindo o fluxo abaixo:', `1) ${prefix}rpg bolsa`, `2) ${prefix}rpg usar <item|slot>`, `3) ${prefix}rpg perfil`].join('\n');
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
export const buildUsePotionSuccessText = ({ itemLabel, healedAmount, pokemonName, currentHp, maxHp, quantityLeft, itemLore = null, prefix = '/' }) => {
|
|
513
|
+
const healLine = healedAmount > 0 ? `• Recuperação aplicada: *+${healedAmount} HP*` : '• Recuperação de HP: *0* (efeito de suporte aplicado)';
|
|
514
|
+
const stockLine = quantityLeft <= 2 ? `• 🎒 ${itemLabel} restantes: ${quantityLeft} (estoque baixo)` : `• 🎒 ${itemLabel} restantes: ${quantityLeft}`;
|
|
515
|
+
return ['🧪 *Item usado com sucesso!*', '', `• Item: *${itemLabel}*`, `• Alvo: *${formatName(pokemonName)}*`, healLine, `• ❤️ HP atual: ${currentHp}/${maxHp}`, stockLine, '• Item consumido: *sim*', ...(itemLore ? ['', `📖 ${itemLore}`] : []), '', '🧭 Próximas ações recomendadas:', `• Continuar batalha: ${prefix}rpg atacar <1-4>`, `• Explorar novamente: ${prefix}rpg explorar`, `• Reabastecer itens: ${prefix}rpg loja`].join('\n');
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
export const buildEconomyRescueText = ({ goldGranted = 0, potionGranted = 0, goldTotal = 0, prefix = '/' }) => ['🆘 *Ajuda de emergência liberada!*', '', `🪙 +${toNumber(goldGranted, 0)} gold | 🧪 +${toNumber(potionGranted, 0)} Poção`, `💰 Gold atual: *${toNumber(goldTotal, 0)}*`, '', `➡️ Próximos: ${prefix}rpg usar pocao | ${prefix}rpg explorar`].join('\n');
|
|
519
|
+
|
|
520
|
+
export const buildBagText = ({ items = [], gold = 0, prefix = '/', availableCategories = [], selectedCategory = 'todos', selectedCategoryLabel = 'Todas', invalidCategory = null }) => {
|
|
521
|
+
if (!items.length) {
|
|
522
|
+
return ['🎒 *Sua Bolsa*', '', `🪙 Gold: *${gold}*`, ...renderCategoryMenu({ prefix, command: 'bolsa', availableCategories, selectedCategory }), ...(invalidCategory ? ['', `⚠️ Categoria inválida: *${invalidCategory}*`] : []), '', `📭 Sem itens na categoria *${selectedCategoryLabel || 'Selecionada'}*.`, '', `🛒 Compre em: ${prefix}rpg loja`].join('\n');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const lines = ['🎒 *Sua Bolsa*', '', `🪙 Gold: *${gold}*`, ...renderCategoryMenu({ prefix, command: 'bolsa', availableCategories, selectedCategory })];
|
|
526
|
+
if (invalidCategory) {
|
|
527
|
+
lines.push('', `⚠️ Categoria inválida: *${invalidCategory}*`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const grouped = groupItemsByCategory(items);
|
|
531
|
+
const categoryOrder = ITEM_CATEGORY_ORDER.filter((key) => grouped.has(key));
|
|
532
|
+
categoryOrder.forEach((categoryKey) => {
|
|
533
|
+
const categoryLabel = ITEM_CATEGORY_LABEL_MAP.get(categoryKey) || categoryKey;
|
|
534
|
+
lines.push('', `━━━━━━━━ ${categoryLabel} ━━━━━━━━`);
|
|
535
|
+
grouped.get(categoryKey).forEach((item) => {
|
|
536
|
+
lines.push(`${item.slot || '•'}${item.slot ? ')' : ''} ${itemEmoji(item.key)} ${item.label} [${item.key}] x${item.quantity}`);
|
|
537
|
+
lines.push(` Para que serve: ${itemMeaning(item)}`);
|
|
538
|
+
lines.push(` Usar: ${prefix}rpg usar ${item.slot || item.key}`);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
lines.push('');
|
|
543
|
+
lines.push(`🧾 Usar por nome: ${prefix}rpg usar <item>`);
|
|
544
|
+
lines.push(`🔢 Usar por número: ${prefix}rpg usar <slot>`);
|
|
545
|
+
lines.push(`💡 Dica: confira preços e significado dos itens em ${prefix}rpg loja`);
|
|
546
|
+
return lines.join('\n');
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const missionLine = (label, current, target) => `• ${label}: ${Math.max(0, current)}/${target}`;
|
|
550
|
+
|
|
551
|
+
export const buildMissionsText = ({ daily, weekly, prefix = '/' }) => {
|
|
552
|
+
const lines = ['🎯 *Missões RPG*'];
|
|
553
|
+
|
|
554
|
+
lines.push('', '☀️ *Diária*', missionLine('Explorar', daily.explorar, daily.target.explorar), missionLine('Vitórias', daily.vitorias, daily.target.vitorias), missionLine('Capturas', daily.capturas, daily.target.capturas), daily.claimed ? '✅ Recompensa diária já coletada' : daily.completed ? '🎁 Recompensa diária pronta' : '⏳ Diária em progresso');
|
|
555
|
+
|
|
556
|
+
lines.push('', '📅 *Semanal*', missionLine('Explorar', weekly.explorar, weekly.target.explorar), missionLine('Vitórias', weekly.vitorias, weekly.target.vitorias), missionLine('Capturas', weekly.capturas, weekly.target.capturas), weekly.claimed ? '✅ Recompensa semanal já coletada' : weekly.completed ? '🎁 Recompensa semanal pronta' : '⏳ Semanal em progresso');
|
|
557
|
+
|
|
558
|
+
lines.push('');
|
|
559
|
+
lines.push(`➡️ Próximos: ${prefix}rpg explorar | ${prefix}rpg ginasio`);
|
|
560
|
+
lines.push(`💡 Dica: vença batalhas de ginásio para avançar mais rápido.`);
|
|
561
|
+
return lines.join('\n');
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
export const buildMissionRewardText = (rewardLines = []) => {
|
|
565
|
+
if (!rewardLines.length) return '';
|
|
566
|
+
return rewardLines.join('\n');
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
export const buildChooseSuccessText = ({ pokemon, prefix = '/' }) => ['✅ Pokémon ativo definido!', '', `🧩 *${formatPokemonLabel({ name: pokemon.displayName || pokemon.name, isShiny: pokemon.isShiny })}* (ID ${pokemon.id})`, '', `➡️ Próximo: ${prefix}rpg explorar`].join('\n');
|
|
570
|
+
|
|
571
|
+
export const buildChooseErrorText = (prefix = '/') => `❌ Pokémon não encontrado no seu time.\n\n👉 Use: ${prefix}rpg time`;
|
|
572
|
+
|
|
573
|
+
export const buildGenericErrorText = (prefix = '/') => `❌ Erro ao processar comando RPG.\n\n👉 Tente novamente: ${prefix}rpg perfil`;
|
|
574
|
+
|
|
575
|
+
export const buildPokedexText = ({ uniqueTotal = 0, total = 0, completion = 0, recent = [], prefix = '/' }) => {
|
|
576
|
+
const lines = ['📗 *Sua Pokédex*', '', `✅ Capturados únicos: *${uniqueTotal}*`, `📊 Conclusão: *${completion}%* (${uniqueTotal}/${total || '?'})`];
|
|
577
|
+
|
|
578
|
+
if (recent.length) {
|
|
579
|
+
lines.push('', '🆕 Capturas recentes:');
|
|
580
|
+
recent.forEach((entry) => {
|
|
581
|
+
lines.push(`• #${entry.pokeId} ${formatPokemonLabel({ name: entry.displayName || entry.name, isShiny: false })}`);
|
|
582
|
+
if (entry.note) {
|
|
583
|
+
lines.push(` ↳ ${entry.note}`);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
lines.push('', `➡️ Próximos: ${prefix}rpg explorar | ${prefix}rpg capturar`);
|
|
589
|
+
return lines.join('\n');
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
export const buildEvolutionTreeText = ({ pokemonName, flavorText = null, stages = [], prefix = '/' }) => {
|
|
593
|
+
const safeName = formatName(pokemonName || 'Pokemon');
|
|
594
|
+
const lines = ['🧬 *Árvore Evolutiva*', '', `🔎 Base: *${safeName}*`];
|
|
595
|
+
|
|
596
|
+
if (flavorText) {
|
|
597
|
+
lines.push(`📖 ${flavorText}`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (!Array.isArray(stages) || !stages.length) {
|
|
601
|
+
lines.push('✅ Este Pokémon não possui próximos estágios de evolução.');
|
|
602
|
+
lines.push(`➡️ Próximos: ${prefix}rpg explorar | ${prefix}rpg time`);
|
|
603
|
+
return lines.join('\n');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
lines.push('', '🌱 Próximos estágios e requisitos:');
|
|
607
|
+
stages.forEach((stage) => {
|
|
608
|
+
const depth = Math.max(0, toNumber(stage?.depth, 0));
|
|
609
|
+
const arrow = `${'↳ '.repeat(depth + 1)}`.trimEnd();
|
|
610
|
+
lines.push(`${arrow} ${formatName(stage?.name || 'Pokemon')} — ${stage?.requirement || 'Requisito não especificado'}`);
|
|
611
|
+
});
|
|
612
|
+
lines.push('', `💡 Dica: use ${prefix}rpg usar <item> quando o requisito for por pedra/item.`);
|
|
613
|
+
return lines.join('\n');
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
export const buildTravelStatusText = ({ travel = null, regions = [], prefix = '/' }) => {
|
|
617
|
+
const lines = ['🧭 *Viagem RPG*'];
|
|
618
|
+
|
|
619
|
+
if (travel?.regionKey) {
|
|
620
|
+
const regionLabel = travel.regionLabel || formatName(travel.regionKey);
|
|
621
|
+
const locationLabel = travel.locationLabel || formatName(travel.locationKey || 'desconhecido');
|
|
622
|
+
const areaLabel = travel.areaLabel || formatName(travel.locationAreaKey || 'geral');
|
|
623
|
+
lines.push(`🌍 Região: *${regionLabel}*`, `📍 Local: *${locationLabel}*`, `🗺️ Área: *${areaLabel}*`);
|
|
624
|
+
if (travel?.regionLore) {
|
|
625
|
+
lines.push(`📖 Região: ${travel.regionLore}`);
|
|
626
|
+
}
|
|
627
|
+
if (travel?.locationLore) {
|
|
628
|
+
lines.push(`📖 Local: ${travel.locationLore}`);
|
|
629
|
+
}
|
|
630
|
+
if (travel?.areaLore) {
|
|
631
|
+
lines.push(`📖 Área: ${travel.areaLore}`);
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
lines.push('🌍 Você ainda não definiu uma região.');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (regions.length) {
|
|
638
|
+
lines.push('', 'Regiões disponíveis:');
|
|
639
|
+
regions.forEach((name) => lines.push(`• ${formatName(name)}`));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
lines.push('', `✈️ Viajar: ${prefix}rpg viajar <regiao>`);
|
|
643
|
+
return lines.join('\n');
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
export const buildTravelSetText = ({ travel, prefix = '/' }) => ['✈️ *Viagem atualizada!*', '', `🌍 Região: *${travel?.regionLabel || formatName(travel.regionKey)}*`, `📍 Local: *${travel?.locationLabel || formatName(travel.locationKey || 'desconhecido')}*`, `🗺️ Área: *${travel?.areaLabel || formatName(travel.locationAreaKey || 'geral')}*`, ...(travel?.regionLore ? [`📖 Região: ${travel.regionLore}`] : []), ...(travel?.locationLore ? [`📖 Local: ${travel.locationLore}`] : []), ...(travel?.areaLore ? [`📖 Área: ${travel.areaLore}`] : []), '', `➡️ Próximo: ${prefix}rpg explorar`].join('\n');
|
|
647
|
+
|
|
648
|
+
export const buildTmListText = ({ items = [], prefix = '/' }) => {
|
|
649
|
+
if (!items.length) {
|
|
650
|
+
return ['📀 Você não tem TMs na bolsa.', `🛒 Compre em: ${prefix}rpg loja`, `💡 Dica: filtre por categoria com ${prefix}rpg loja tm`].join('\n');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const lines = ['📀 *Seus TMs*', '', 'Use o nome da TM exatamente como exibido abaixo:'];
|
|
654
|
+
items.forEach((item) => lines.push(`• ${item.label} (${item.quantity})`));
|
|
655
|
+
lines.push('', `🧠 Ensinar golpe: ${prefix}rpg tm usar <tm> <1-4>`, `Exemplo: ${prefix}rpg tm usar tm-thunderbolt 1`);
|
|
656
|
+
return lines.join('\n');
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
export const buildTmUseText = ({ itemLabel, moveName, moveLore = null, slot, pokemonName, prefix = '/' }) => ['📀 *TM usado com sucesso!*', '', `🧩 ${formatName(pokemonName)} aprendeu *${formatName(moveName)}* no slot ${slot}`, ...(moveLore ? [`📖 ${moveLore}`] : []), '', `🎒 TM consumido: ${itemLabel}`, `⚠️ O golpe anterior do slot ${slot} foi substituído.`, '', `➡️ Próximos: ${prefix}rpg atacar <1-4> | ${prefix}rpg explorar`].join('\n');
|
|
660
|
+
|
|
661
|
+
export const buildBerryListText = ({ items = [], prefix = '/' }) => {
|
|
662
|
+
if (!items.length) {
|
|
663
|
+
return ['🍓 Você não tem berries na bolsa.', `🛒 Compre em: ${prefix}rpg loja`, `💡 Dica: filtre por categoria com ${prefix}rpg loja berry`].join('\n');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const lines = ['🍓 *Suas Berries*', '', 'Use o nome da berry como aparece abaixo:'];
|
|
667
|
+
items.forEach((item) => lines.push(`• ${item.label} (${item.quantity})`));
|
|
668
|
+
lines.push('', `🥣 Usar berry: ${prefix}rpg berry usar <item>`, `Exemplo: ${prefix}rpg berry usar oran-berry`);
|
|
669
|
+
return lines.join('\n');
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
export const buildRaidStartText = ({ bossName, level, currentHp, maxHp, expiresInMin, bossLore = null, prefix = '/' }) => ['🐉 *RAID INICIADA!*', '', `Chefe: *${formatName(bossName)}* Lv.${level}`, `❤️ HP Boss: ${hpBar(currentHp, maxHp)}`, ...(bossLore ? ['', `📖 ${bossLore}`] : []), '', `⏱️ Tempo: ${expiresInMin} min`, `➡️ Entrar: ${prefix}rpg raid entrar`, `⚔️ Atacar: ${prefix}rpg raid atacar <1-4>`].join('\n');
|
|
673
|
+
|
|
674
|
+
export const buildRaidStatusText = ({ raid, participants = [], prefix = '/' }) => {
|
|
675
|
+
if (!raid) {
|
|
676
|
+
return `🛡️ Nenhuma raid ativa neste grupo.\n👉 Iniciar: ${prefix}rpg raid iniciar`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const lines = ['🛡️ *Status da Raid*', `Chefe: *${formatName(raid.bossName)}* Lv.${raid.level}`, `❤️ HP Boss: ${hpBar(raid.currentHp, raid.maxHp)}`, ...(raid?.bossLore ? [`📖 ${raid.bossLore}`] : []), `👥 Participantes: ${participants.length}`];
|
|
680
|
+
|
|
681
|
+
if (participants.length) {
|
|
682
|
+
lines.push('', '🏆 Ranking de dano:');
|
|
683
|
+
participants.slice(0, 5).forEach((entry, idx) => {
|
|
684
|
+
lines.push(`${idx + 1}. ${entry.ownerJid} — ${entry.totalDamage} dmg`);
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
lines.push('', `➡️ Ações: ${prefix}rpg raid entrar | ${prefix}rpg raid atacar <1-4>`);
|
|
689
|
+
return lines.join('\n');
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
export const buildRaidAttackText = ({ logs = [], currentHp, maxHp, defeated = false, ranking = [], prefix = '/' }) => {
|
|
693
|
+
const lines = [...logs, `❤️ HP Boss: ${hpBar(currentHp, maxHp)}`];
|
|
694
|
+
|
|
695
|
+
if (defeated) {
|
|
696
|
+
lines.push('🎉 Boss derrotado! Recompensas distribuídas.');
|
|
697
|
+
if (ranking.length) {
|
|
698
|
+
lines.push('', '🏆 Ranking final:');
|
|
699
|
+
ranking.slice(0, 5).forEach((entry, idx) => {
|
|
700
|
+
lines.push(`${idx + 1}. ${entry.ownerJid} — ${entry.totalDamage} dmg`);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
lines.push('', `➡️ Próximo: ${prefix}rpg explorar`);
|
|
704
|
+
return lines.join('\n');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
lines.push(`➡️ Continue: ${prefix}rpg raid atacar <1-4>`);
|
|
708
|
+
return lines.join('\n');
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
export const buildPvpChallengeText = ({ challengeId, challengerJid, opponentJid, challengerPokemonLabel = null, opponentPokemonLabel = null, prefix = '/' }) => ['⚔️ *Desafio PvP criado!*', '', `ID: *${challengeId}*`, `Desafiante: ${challengerJid}`, `Oponente: ${opponentJid}`, ...(challengerPokemonLabel && opponentPokemonLabel ? ['', `🧩 Confronto: *${challengerPokemonLabel}* vs *${opponentPokemonLabel}*`] : []), '', `✅ Aceitar: ${prefix}rpg pvp aceitar ${challengeId}`, `❌ Recusar: ${prefix}rpg pvp recusar ${challengeId}`].join('\n');
|
|
712
|
+
|
|
713
|
+
export const buildPvpStatusText = ({ pending = [], active = null, prefix = '/' }) => {
|
|
714
|
+
const lines = ['🥊 *Status PvP*'];
|
|
715
|
+
|
|
716
|
+
if (active) {
|
|
717
|
+
lines.push('', `Partida ativa: #${active.id}`, ...(active.myPokemonLabel && active.enemyPokemonLabel ? [`🧩 Confronto: *${active.myPokemonLabel}* vs *${active.enemyPokemonLabel}*`] : []), `Turno de: ${active.turnLabel || active.turnJid}`, `Seu Pokémon HP: ${active.myHp}/${active.myMaxHp}`, `Inimigo HP: ${active.enemyHp}/${active.enemyMaxHp}`, `➡️ Ação: ${prefix}rpg pvp atacar <1-4>`);
|
|
718
|
+
} else {
|
|
719
|
+
lines.push('', 'Nenhuma partida ativa no momento.');
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (pending.length) {
|
|
723
|
+
lines.push('', '📨 Desafios pendentes para você:');
|
|
724
|
+
pending.slice(0, 5).forEach((entry) => {
|
|
725
|
+
lines.push(`• #${entry.id} de ${entry.challengerLabel || entry.challengerJid} (${entry.challengerPokemonLabel || 'Pokémon oculto'})`);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
lines.push('', `💡 Criar desafio: ${prefix}rpg desafiar <jid/@numero>`);
|
|
730
|
+
lines.push(`💡 Fila automática: ${prefix}rpg pvp fila entrar`);
|
|
731
|
+
lines.push(`💡 Ranking semanal: ${prefix}rpg pvp ranking`);
|
|
732
|
+
return lines.join('\n');
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
export const buildPvpTurnText = ({ logs = [], myPokemonLabel = null, enemyPokemonLabel = null, myHp, myMaxHp, enemyHp, enemyMaxHp, winnerJid = null, prefix = '/' }) => {
|
|
736
|
+
const lines = [...(myPokemonLabel && enemyPokemonLabel ? [`🧩 Confronto: *${myPokemonLabel}* vs *${enemyPokemonLabel}*`, ''] : []), ...logs, '', `❤️ Seu HP: ${hpBar(myHp, myMaxHp)}`, `❤️ Inimigo HP: ${hpBar(enemyHp, enemyMaxHp)}`];
|
|
737
|
+
if (winnerJid) {
|
|
738
|
+
lines.push('');
|
|
739
|
+
lines.push(`🏁 Vitória de ${winnerJid.label || winnerJid}`);
|
|
740
|
+
lines.push(`➡️ Próximo: ${prefix}rpg explorar`);
|
|
741
|
+
return lines.join('\n');
|
|
742
|
+
}
|
|
743
|
+
lines.push('');
|
|
744
|
+
lines.push(`➡️ Próximo turno: ${prefix}rpg pvp atacar <1-4>`);
|
|
745
|
+
return lines.join('\n');
|
|
746
|
+
};
|