@omnizap-system/omnizap 2.6.0 → 2.6.2
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/.env.example +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +660 -158
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +93 -13
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +135 -27
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- 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 +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { fileURLToPath } from 'node:url';
|
|
3
4
|
|
|
@@ -6,9 +7,82 @@ import { createModuleCommandConfigRuntime } from '../../services/ai/moduleComman
|
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = path.dirname(__filename);
|
|
8
9
|
const CONFIG_PATH = path.join(__dirname, 'commandConfig.json');
|
|
10
|
+
const CONFIG_SNAPSHOT_TTL_MS = Math.max(1000, Number.parseInt(process.env.PLAY_CONFIG_SNAPSHOT_TTL_MS || '15000', 10) || 15000);
|
|
9
11
|
|
|
10
12
|
const DEFAULT_TEXTS = {
|
|
11
13
|
usage_header: '',
|
|
14
|
+
error_prefix: '❌ Erro: ',
|
|
15
|
+
generic_error: 'Erro inesperado ao processar sua solicitação.',
|
|
16
|
+
user_error_timeout: 'A operação demorou mais que o esperado. Tente novamente.',
|
|
17
|
+
user_error_technical_generic: 'Não foi possível processar sua solicitação agora. Tente novamente em instantes.',
|
|
18
|
+
admin_error_title: 'Erro no módulo play (diagnóstico).',
|
|
19
|
+
wait_audio: '⏳ Processando sua mídia...',
|
|
20
|
+
wait_video: '⏳ Processando sua mídia...',
|
|
21
|
+
ready_title_audio: '🎵 Áudio pronto!',
|
|
22
|
+
ready_title_video: '🎬 Vídeo pronto!',
|
|
23
|
+
video_fallback_to_audio: '⚠️ Este link retornou somente áudio. Enviando no formato de áudio.',
|
|
24
|
+
anti_bot_with_cookies: 'YouTube solicitou verificação anti-bot. Atualize o arquivo .secrets/cookies.txt e tente novamente.',
|
|
25
|
+
anti_bot_with_browser_profile: 'YouTube solicitou verificação anti-bot no provedor de mídia. Verifique suas credenciais e tente novamente.',
|
|
26
|
+
anti_bot_without_cookies: 'YouTube solicitou verificação anti-bot no provedor de mídia. Tente novamente em alguns minutos.',
|
|
27
|
+
usage_fallback_audio: '🎵 Uso: <prefix>play <link do YouTube ou termo de busca>',
|
|
28
|
+
usage_fallback_video: '🎬 Uso: <prefix>playvid <link do YouTube ou termo de busca>',
|
|
29
|
+
invalid_media_type: 'Tipo de mídia inválido.',
|
|
30
|
+
binary_exec_failed: 'Falha ao executar <command>.',
|
|
31
|
+
provider_error_generic: 'Falha ao processar mídia no provedor.',
|
|
32
|
+
provider_timeout_generic: 'Timeout ao processar mídia no provedor.',
|
|
33
|
+
search_invalid_input: 'Você precisa informar um link do YouTube ou termo de busca.',
|
|
34
|
+
search_not_found: 'Nenhum resultado encontrado para a busca.',
|
|
35
|
+
search_timeout: 'Timeout ao buscar metadados do vídeo.',
|
|
36
|
+
search_failed: 'Não foi possível buscar o vídeo agora.',
|
|
37
|
+
video_unavailable: 'Não foi possível acessar este vídeo agora. Tente outro link.',
|
|
38
|
+
ffmpeg_not_found: 'ffmpeg não encontrado no servidor para processar esta mídia.',
|
|
39
|
+
download_timeout: 'Timeout ao baixar o arquivo.',
|
|
40
|
+
download_failed: 'Falha ao baixar o arquivo localmente.',
|
|
41
|
+
download_file_not_found: 'Não foi possível localizar o arquivo baixado.',
|
|
42
|
+
download_invalid_media: 'Falha ao baixar mídia válida.',
|
|
43
|
+
media_too_big: 'O arquivo excede o limite permitido de <max_mb> MB.',
|
|
44
|
+
probe_timeout: 'Timeout ao analisar o vídeo recebido.',
|
|
45
|
+
probe_failed: 'Falha ao validar o vídeo recebido.',
|
|
46
|
+
transcode_timeout: 'Timeout ao normalizar o vídeo para envio.',
|
|
47
|
+
transcode_failed: 'Falha ao converter o vídeo para um formato compatível.',
|
|
48
|
+
transcode_output_invalid: 'Falha ao gerar vídeo compatível para envio.',
|
|
49
|
+
video_without_streams: 'Não foi possível enviar como vídeo: a mídia não possui faixa de vídeo nem áudio.',
|
|
50
|
+
thumbnail_timeout: 'Timeout ao baixar a thumbnail.',
|
|
51
|
+
thumbnail_failed: 'Falha ao baixar a thumbnail.',
|
|
52
|
+
thumbnail_too_big: 'Thumbnail excede o limite permitido.',
|
|
53
|
+
http_timeout: 'Timeout na requisição HTTP.',
|
|
54
|
+
http_failed: 'Falha na requisição HTTP.',
|
|
55
|
+
content_too_big: 'Conteúdo excede o limite permitido.',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const DEFAULT_OPERATIONAL_LIMITS = {
|
|
59
|
+
max_search_results: 5,
|
|
60
|
+
search_cache_ttl_ms: 60000,
|
|
61
|
+
max_search_cache_entries: 500,
|
|
62
|
+
max_redirects: 2,
|
|
63
|
+
max_concurrent_jobs: 2,
|
|
64
|
+
max_error_body_bytes: 65536,
|
|
65
|
+
max_meta_body_chars: 512,
|
|
66
|
+
retry_backoff_base_ms: 200,
|
|
67
|
+
search_retry_count: 1,
|
|
68
|
+
thumbnail_retry_count: 1,
|
|
69
|
+
thumbnail_timeout_ms: 15000,
|
|
70
|
+
max_thumb_bytes: 5 * 1024 * 1024,
|
|
71
|
+
admin_alert_dedupe_window_ms: 120000,
|
|
72
|
+
ytmp3_poll_interval_ms: 2000,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const DEFAULT_EXECUTION_OPTIONS = {
|
|
76
|
+
estrategias_formato: {
|
|
77
|
+
audio: ['bestaudio/best', 'best'],
|
|
78
|
+
video: ['bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/best', 'bestvideo*+bestaudio/best', 'best'],
|
|
79
|
+
audio_extract: {
|
|
80
|
+
enabled: true,
|
|
81
|
+
format: 'mp3',
|
|
82
|
+
quality: '0',
|
|
83
|
+
},
|
|
84
|
+
video_merge_output_format: 'mp4',
|
|
85
|
+
},
|
|
12
86
|
};
|
|
13
87
|
|
|
14
88
|
const runtime = createModuleCommandConfigRuntime({
|
|
@@ -17,10 +91,106 @@ const runtime = createModuleCommandConfigRuntime({
|
|
|
17
91
|
module: 'playModule',
|
|
18
92
|
commands: [],
|
|
19
93
|
textos: DEFAULT_TEXTS,
|
|
94
|
+
limites_operacionais: DEFAULT_OPERATIONAL_LIMITS,
|
|
95
|
+
opcoes_execucao: DEFAULT_EXECUTION_OPTIONS,
|
|
20
96
|
},
|
|
21
97
|
});
|
|
22
98
|
|
|
99
|
+
let cachedModuleConfig = null;
|
|
100
|
+
let cachedSnapshotExpiresAt = 0;
|
|
101
|
+
let cachedCommandRegistry = null;
|
|
102
|
+
|
|
23
103
|
const renderUsageMethod = (method, commandPrefix) => String(method || '').replaceAll('<prefix>', String(commandPrefix || '/'));
|
|
104
|
+
const renderTemplate = (value, variables = {}) => {
|
|
105
|
+
let text = String(value || '');
|
|
106
|
+
for (const [key, variableValue] of Object.entries(variables || {})) {
|
|
107
|
+
text = text.replaceAll(`<${key}>`, String(variableValue ?? ''));
|
|
108
|
+
}
|
|
109
|
+
return text;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const normalizeCommandToken = (value) =>
|
|
113
|
+
String(value || '')
|
|
114
|
+
.trim()
|
|
115
|
+
.toLowerCase();
|
|
116
|
+
|
|
117
|
+
const toInt = (value, fallback, { min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY } = {}) => {
|
|
118
|
+
const number = Number.parseInt(String(value ?? ''), 10);
|
|
119
|
+
if (!Number.isFinite(number)) return fallback;
|
|
120
|
+
if (number < min) return fallback;
|
|
121
|
+
if (number > max) return fallback;
|
|
122
|
+
return number;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const normalizeStringArray = (value, fallback = []) => {
|
|
126
|
+
if (!Array.isArray(value)) return [...fallback];
|
|
127
|
+
const normalized = value.map((item) => String(item || '').trim()).filter(Boolean);
|
|
128
|
+
return normalized.length ? normalized : [...fallback];
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const normalizeExecutionOptions = (raw) => {
|
|
132
|
+
const source = raw && typeof raw === 'object' ? raw : {};
|
|
133
|
+
const defaultFormat = DEFAULT_EXECUTION_OPTIONS.estrategias_formato;
|
|
134
|
+
const rawFormat = source.estrategias_formato && typeof source.estrategias_formato === 'object' ? source.estrategias_formato : {};
|
|
135
|
+
const rawAudioExtract = rawFormat.audio_extract && typeof rawFormat.audio_extract === 'object' ? rawFormat.audio_extract : {};
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
estrategias_formato: {
|
|
139
|
+
audio: normalizeStringArray(rawFormat.audio, defaultFormat.audio),
|
|
140
|
+
video: normalizeStringArray(rawFormat.video, defaultFormat.video),
|
|
141
|
+
audio_extract: {
|
|
142
|
+
enabled: rawAudioExtract.enabled === undefined ? Boolean(defaultFormat.audio_extract.enabled) : Boolean(rawAudioExtract.enabled),
|
|
143
|
+
format: String(rawAudioExtract.format || defaultFormat.audio_extract.format || '').trim() || defaultFormat.audio_extract.format,
|
|
144
|
+
quality: String(rawAudioExtract.quality || defaultFormat.audio_extract.quality || '').trim() || defaultFormat.audio_extract.quality,
|
|
145
|
+
},
|
|
146
|
+
video_merge_output_format: String(rawFormat.video_merge_output_format || defaultFormat.video_merge_output_format || '').trim() || defaultFormat.video_merge_output_format,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const getPlayModuleConfigSnapshot = () => {
|
|
152
|
+
const now = __timeNowMs();
|
|
153
|
+
if (cachedModuleConfig && now < cachedSnapshotExpiresAt) {
|
|
154
|
+
return cachedModuleConfig;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
cachedModuleConfig = runtime.getModuleConfig();
|
|
158
|
+
cachedSnapshotExpiresAt = now + CONFIG_SNAPSHOT_TTL_MS;
|
|
159
|
+
cachedCommandRegistry = null;
|
|
160
|
+
return cachedModuleConfig;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const buildCommandRegistry = () => {
|
|
164
|
+
if (cachedCommandRegistry) return cachedCommandRegistry;
|
|
165
|
+
|
|
166
|
+
const config = getPlayModuleConfigSnapshot();
|
|
167
|
+
const entries = Array.isArray(config?.commands) ? config.commands : [];
|
|
168
|
+
const aliasToCanonical = new Map();
|
|
169
|
+
const commandEntryByCanonical = new Map();
|
|
170
|
+
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (!entry || entry.enabled === false) continue;
|
|
173
|
+
|
|
174
|
+
const canonical = normalizeCommandToken(entry.name);
|
|
175
|
+
if (!canonical) continue;
|
|
176
|
+
|
|
177
|
+
commandEntryByCanonical.set(canonical, entry);
|
|
178
|
+
aliasToCanonical.set(canonical, canonical);
|
|
179
|
+
|
|
180
|
+
const aliases = Array.isArray(entry.aliases) ? entry.aliases : [];
|
|
181
|
+
for (const alias of aliases) {
|
|
182
|
+
const normalizedAlias = normalizeCommandToken(alias);
|
|
183
|
+
if (!normalizedAlias) continue;
|
|
184
|
+
aliasToCanonical.set(normalizedAlias, canonical);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
cachedCommandRegistry = {
|
|
189
|
+
aliasToCanonical,
|
|
190
|
+
commandEntryByCanonical,
|
|
191
|
+
};
|
|
192
|
+
return cachedCommandRegistry;
|
|
193
|
+
};
|
|
24
194
|
|
|
25
195
|
const resolveUsageLines = (entry, variant) => {
|
|
26
196
|
if (!entry || typeof entry !== 'object') return [];
|
|
@@ -42,11 +212,26 @@ const resolveUsageLines = (entry, variant) => {
|
|
|
42
212
|
return methods.filter(Boolean).map((value) => String(value));
|
|
43
213
|
};
|
|
44
214
|
|
|
45
|
-
export const getPlayModuleConfig = () =>
|
|
215
|
+
export const getPlayModuleConfig = () => getPlayModuleConfigSnapshot();
|
|
216
|
+
|
|
217
|
+
export const resolvePlayCommandName = (command) => {
|
|
218
|
+
const normalized = normalizeCommandToken(command);
|
|
219
|
+
if (!normalized) return null;
|
|
220
|
+
const { aliasToCanonical } = buildCommandRegistry();
|
|
221
|
+
return aliasToCanonical.get(normalized) || null;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const getPlayCommandEntry = (command) => {
|
|
225
|
+
const canonical = resolvePlayCommandName(command);
|
|
226
|
+
if (!canonical) return null;
|
|
227
|
+
const { commandEntryByCanonical } = buildCommandRegistry();
|
|
228
|
+
return commandEntryByCanonical.get(canonical) || null;
|
|
229
|
+
};
|
|
46
230
|
|
|
47
|
-
export const
|
|
48
|
-
|
|
49
|
-
|
|
231
|
+
export const listEnabledPlayCommands = () => {
|
|
232
|
+
const { commandEntryByCanonical } = buildCommandRegistry();
|
|
233
|
+
return [...commandEntryByCanonical.values()];
|
|
234
|
+
};
|
|
50
235
|
|
|
51
236
|
export const getPlayTextConfig = () => {
|
|
52
237
|
const config = getPlayModuleConfig();
|
|
@@ -57,6 +242,62 @@ export const getPlayTextConfig = () => {
|
|
|
57
242
|
};
|
|
58
243
|
};
|
|
59
244
|
|
|
245
|
+
export const getPlayOperationalLimits = () => {
|
|
246
|
+
const config = getPlayModuleConfig();
|
|
247
|
+
const raw = config?.limites_operacionais && typeof config.limites_operacionais === 'object' ? config.limites_operacionais : {};
|
|
248
|
+
return {
|
|
249
|
+
max_search_results: toInt(raw.max_search_results, DEFAULT_OPERATIONAL_LIMITS.max_search_results, { min: 1, max: 10 }),
|
|
250
|
+
search_cache_ttl_ms: toInt(raw.search_cache_ttl_ms, DEFAULT_OPERATIONAL_LIMITS.search_cache_ttl_ms, { min: 1 }),
|
|
251
|
+
max_search_cache_entries: toInt(raw.max_search_cache_entries, DEFAULT_OPERATIONAL_LIMITS.max_search_cache_entries, { min: 1 }),
|
|
252
|
+
max_redirects: toInt(raw.max_redirects, DEFAULT_OPERATIONAL_LIMITS.max_redirects, { min: 0, max: 10 }),
|
|
253
|
+
max_concurrent_jobs: toInt(raw.max_concurrent_jobs, DEFAULT_OPERATIONAL_LIMITS.max_concurrent_jobs, { min: 1, max: 32 }),
|
|
254
|
+
max_error_body_bytes: toInt(raw.max_error_body_bytes, DEFAULT_OPERATIONAL_LIMITS.max_error_body_bytes, { min: 1024 }),
|
|
255
|
+
max_meta_body_chars: toInt(raw.max_meta_body_chars, DEFAULT_OPERATIONAL_LIMITS.max_meta_body_chars, { min: 64 }),
|
|
256
|
+
retry_backoff_base_ms: toInt(raw.retry_backoff_base_ms, DEFAULT_OPERATIONAL_LIMITS.retry_backoff_base_ms, { min: 1 }),
|
|
257
|
+
search_retry_count: toInt(raw.search_retry_count, DEFAULT_OPERATIONAL_LIMITS.search_retry_count, { min: 0, max: 5 }),
|
|
258
|
+
thumbnail_retry_count: toInt(raw.thumbnail_retry_count, DEFAULT_OPERATIONAL_LIMITS.thumbnail_retry_count, { min: 0, max: 5 }),
|
|
259
|
+
thumbnail_timeout_ms: toInt(raw.thumbnail_timeout_ms, DEFAULT_OPERATIONAL_LIMITS.thumbnail_timeout_ms, { min: 1000 }),
|
|
260
|
+
max_thumb_bytes: toInt(raw.max_thumb_bytes, DEFAULT_OPERATIONAL_LIMITS.max_thumb_bytes, { min: 1024 }),
|
|
261
|
+
admin_alert_dedupe_window_ms: toInt(raw.admin_alert_dedupe_window_ms, DEFAULT_OPERATIONAL_LIMITS.admin_alert_dedupe_window_ms, { min: 0 }),
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const getPlayExecutionOptions = () => {
|
|
266
|
+
const config = getPlayModuleConfig();
|
|
267
|
+
const raw = config?.opcoes_execucao && typeof config.opcoes_execucao === 'object' ? config.opcoes_execucao : {};
|
|
268
|
+
return normalizeExecutionOptions(raw);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const getPlayText = (key, fallback = '') => {
|
|
272
|
+
const textConfig = getPlayTextConfig();
|
|
273
|
+
if (typeof key !== 'string' || !key.trim()) return fallback;
|
|
274
|
+
const value = textConfig[key];
|
|
275
|
+
if (typeof value !== 'string' || !value.trim()) return fallback;
|
|
276
|
+
return value;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export const getPlayWaitText = (type) => {
|
|
280
|
+
const normalizedType = String(type || '').toLowerCase();
|
|
281
|
+
const key = normalizedType === 'video' ? 'wait_video' : 'wait_audio';
|
|
282
|
+
const fallback = normalizedType === 'video' ? DEFAULT_TEXTS.wait_video : DEFAULT_TEXTS.wait_audio;
|
|
283
|
+
return getPlayText(key, fallback);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export const getPlayReadyTitle = (type) => {
|
|
287
|
+
const normalizedType = String(type || '').toLowerCase();
|
|
288
|
+
const key = normalizedType === 'video' ? 'ready_title_video' : 'ready_title_audio';
|
|
289
|
+
const fallback = normalizedType === 'video' ? DEFAULT_TEXTS.ready_title_video : DEFAULT_TEXTS.ready_title_audio;
|
|
290
|
+
return getPlayText(key, fallback);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const getPlayUsageFallbackText = (type, commandPrefix = '/') => {
|
|
294
|
+
const normalizedType = String(type || '').toLowerCase();
|
|
295
|
+
const key = normalizedType === 'video' ? 'usage_fallback_video' : 'usage_fallback_audio';
|
|
296
|
+
const fallback = normalizedType === 'video' ? DEFAULT_TEXTS.usage_fallback_video : DEFAULT_TEXTS.usage_fallback_audio;
|
|
297
|
+
const template = getPlayText(key, fallback);
|
|
298
|
+
return renderTemplate(template, { prefix: commandPrefix });
|
|
299
|
+
};
|
|
300
|
+
|
|
60
301
|
export const getPlayUsageText = (command, { commandPrefix = '/', header, variant } = {}) => {
|
|
61
302
|
const entry = getPlayCommandEntry(command);
|
|
62
303
|
const methods = resolveUsageLines(entry, variant);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { after, test } from 'node:test';
|
|
4
|
+
|
|
5
|
+
import { __playMediaClientTestUtils } from './playCommandMediaClient.js';
|
|
6
|
+
|
|
7
|
+
const withEnv = async (overrides, fn) => {
|
|
8
|
+
const previous = new Map();
|
|
9
|
+
for (const [key, value] of Object.entries(overrides || {})) {
|
|
10
|
+
previous.set(key, Object.prototype.hasOwnProperty.call(process.env, key) ? process.env[key] : null);
|
|
11
|
+
if (value === null || value === undefined) {
|
|
12
|
+
delete process.env[key];
|
|
13
|
+
} else {
|
|
14
|
+
process.env[key] = String(value);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return await fn();
|
|
20
|
+
} finally {
|
|
21
|
+
for (const [key, value] of previous.entries()) {
|
|
22
|
+
if (value === null) {
|
|
23
|
+
delete process.env[key];
|
|
24
|
+
} else {
|
|
25
|
+
process.env[key] = value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let closeDatabasePoolForTests = null;
|
|
32
|
+
|
|
33
|
+
after(async () => {
|
|
34
|
+
if (typeof closeDatabasePoolForTests !== 'function') {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await closeDatabasePoolForTests();
|
|
38
|
+
closeDatabasePoolForTests = null;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('resolve candidates deduplica URLs e ignora inválidas', () => {
|
|
42
|
+
const urls = __playMediaClientTestUtils.extractCandidateUrlsFromSearchResult({
|
|
43
|
+
resultado: { url: 'https://www.youtube.com/watch?v=abc123' },
|
|
44
|
+
resultados: [{ url: 'https://www.youtube.com/watch?v=abc123' }, { url: 'https://youtu.be/xyz987' }, { url: 'not-an-url' }, { url: 'https://www.youtube.com/watch?v=zzz000' }],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
assert.deepEqual(urls, ['https://www.youtube.com/watch?v=abc123', 'https://youtu.be/xyz987', 'https://www.youtube.com/watch?v=zzz000']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('ytmp3 principal é elegível para áudio e vídeo com URL do YouTube', () => {
|
|
51
|
+
assert.equal(
|
|
52
|
+
__playMediaClientTestUtils.isYtmp3PrimaryEligible({
|
|
53
|
+
type: 'audio',
|
|
54
|
+
link: 'https://www.youtube.com/watch?v=test1234567A',
|
|
55
|
+
}),
|
|
56
|
+
true,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
assert.equal(
|
|
60
|
+
__playMediaClientTestUtils.isYtmp3PrimaryEligible({
|
|
61
|
+
type: 'video',
|
|
62
|
+
link: 'https://www.youtube.com/watch?v=test1234567A',
|
|
63
|
+
}),
|
|
64
|
+
true,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
assert.equal(
|
|
68
|
+
__playMediaClientTestUtils.isYtmp3PrimaryEligible({
|
|
69
|
+
type: 'audio',
|
|
70
|
+
link: 'https://vimeo.com/1234',
|
|
71
|
+
}),
|
|
72
|
+
false,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('anti-bot: detecta causa e retorna mensagem genérica do provedor', { concurrency: false }, async () => {
|
|
77
|
+
assert.equal(
|
|
78
|
+
__playMediaClientTestUtils.isYouTubeBotCheckCause({
|
|
79
|
+
meta: { cause: 'ERROR: [youtube] Sign in to confirm you’re not a bot.' },
|
|
80
|
+
}),
|
|
81
|
+
true,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const message = __playMediaClientTestUtils.buildYouTubeBotCheckUserMessage();
|
|
85
|
+
assert.match(message, /anti-bot/i);
|
|
86
|
+
assert.ok(!message.includes('PLAY_'));
|
|
87
|
+
assert.ok(!message.toLowerCase().includes('cookies_path'));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('notifyFailure: envia admin só para erro técnico e deduplica alertas', { concurrency: false }, async () => {
|
|
91
|
+
await withEnv(
|
|
92
|
+
{
|
|
93
|
+
USER_ADMIN: '5511999999999',
|
|
94
|
+
},
|
|
95
|
+
async () => {
|
|
96
|
+
const mod = await import(`./playCommandCore.js?test=${__timeNowMs()}-${Math.random().toString(16).slice(2)}`);
|
|
97
|
+
const { closePool } = await import('../../../database/index.js');
|
|
98
|
+
closeDatabasePoolForTests = closePool;
|
|
99
|
+
const utils = mod.__playCommandCoreTestUtils;
|
|
100
|
+
utils.resetAdminAlertDedupCacheForTests();
|
|
101
|
+
|
|
102
|
+
const sent = [];
|
|
103
|
+
const sock = {
|
|
104
|
+
sendMessage: async (jid, content, options) => {
|
|
105
|
+
sent.push({ jid, content, options });
|
|
106
|
+
return {
|
|
107
|
+
key: { remoteJid: jid },
|
|
108
|
+
message: content,
|
|
109
|
+
messageTimestamp: Math.floor(__timeNowMs() / 1000),
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const technicalError = Object.assign(new Error('spawn failed'), {
|
|
115
|
+
code: 'EAPI',
|
|
116
|
+
meta: {
|
|
117
|
+
technical: true,
|
|
118
|
+
endpoint: 'local:download',
|
|
119
|
+
cause: 'spawn failed',
|
|
120
|
+
rawCode: 'EPROCESS',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await utils.notifyFailure(sock, '120363111111111111@g.us', { key: {}, message: {} }, 0, technicalError, {
|
|
125
|
+
type: 'audio',
|
|
126
|
+
requestId: 'req-1',
|
|
127
|
+
});
|
|
128
|
+
assert.equal(sent.length, 2);
|
|
129
|
+
assert.match(sent[1].content.text, /diagnóstico/i);
|
|
130
|
+
|
|
131
|
+
await utils.notifyFailure(sock, '120363111111111111@g.us', { key: {}, message: {} }, 0, technicalError, {
|
|
132
|
+
type: 'audio',
|
|
133
|
+
requestId: 'req-1',
|
|
134
|
+
});
|
|
135
|
+
assert.equal(sent.length, 3);
|
|
136
|
+
|
|
137
|
+
const businessError = Object.assign(new Error('not found'), {
|
|
138
|
+
code: 'ENOTFOUND',
|
|
139
|
+
meta: {
|
|
140
|
+
technical: false,
|
|
141
|
+
endpoint: 'local:search',
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await utils.notifyFailure(sock, '120363111111111111@g.us', { key: {}, message: {} }, 0, businessError, {
|
|
146
|
+
type: 'audio',
|
|
147
|
+
requestId: 'req-2',
|
|
148
|
+
});
|
|
149
|
+
assert.equal(sent.length, 4);
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
});
|
|
@@ -7,7 +7,7 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
7
7
|
- arquivo_base: `app/modules/quoteModule/commandConfig.json`
|
|
8
8
|
- schema_version: `2.0.0`
|
|
9
9
|
- module_enabled: `true`
|
|
10
|
-
- generated_at: `2026-03-
|
|
10
|
+
- generated_at: `2026-03-17T04:04:14.195Z`
|
|
11
11
|
|
|
12
12
|
## Escopo do Modulo
|
|
13
13
|
|
|
@@ -357,6 +357,35 @@
|
|
|
357
357
|
"schema": "legacy_v1_and_v2",
|
|
358
358
|
"legacy_name": "citar",
|
|
359
359
|
"legacy_fields_present": ["descricao", "metodos_de_uso", "permissao_necessaria", "local_de_uso", "informacoes_coletadas", "argumentos", "pre_condicoes", "dependencias_externas", "efeitos_colaterais", "observabilidade", "privacidade", "acesso", "limite_uso_por_plano"]
|
|
360
|
+
},
|
|
361
|
+
"user_experience": {
|
|
362
|
+
"resumo_usuario": "Converta texto em uma figurinha no estilo quote para compartilhar citações.",
|
|
363
|
+
"quando_usar": ["Quando quiser transformar um texto ou mensagem citada em uma figurinha com o estilo 'quote' para compartilhar na conversa.", "Para criar citações rápidas sem digitar outros detalhes, basta digitar o comando com o texto desejado."],
|
|
364
|
+
"exemplos_reais": [
|
|
365
|
+
{
|
|
366
|
+
"situacao": "Criar uma quote simples a partir de um texto próprio.",
|
|
367
|
+
"comando": "<prefix>citar seu texto",
|
|
368
|
+
"resposta_esperada": "Figura citada criada com sucesso.",
|
|
369
|
+
"variacao": "Aparece a figurinha com o seu texto formatado como quote."
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"situacao": "Citar uma frase de outro usuário mencionando-o.",
|
|
373
|
+
"comando": "<prefix>qc @usuário a frase citada",
|
|
374
|
+
"resposta_esperada": "Figura citada criada com sucesso.",
|
|
375
|
+
"variacao": "A citação é gerada a partir da mensagem citada."
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"situacao": "Erro de formato ao não informar o texto.",
|
|
379
|
+
"comando": "<prefix>citar",
|
|
380
|
+
"resposta_esperada": "Formato de uso inválido. Consulte metodos_de_uso.",
|
|
381
|
+
"variacao": "O sistema pede para incluir o texto após o comando."
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
"resposta_esperada": ["Figura citada criada com sucesso.", "Formato de uso inválido. Consulte metodos_de_uso.", "Permissão insuficiente para executar este comando."],
|
|
385
|
+
"erros_comuns_usuario": ["Esquecer de informar o texto após o comando <prefix>citar.", "Usar o comando <prefix>qc sem a estrutura correta (falta @usuario ou texto).", "Texto muito longo ou com caracteres não suportados pela figurinha.", "Não estar logado quando o sistema exigir login Google."],
|
|
386
|
+
"passos_se_der_erro": ["Verifique se você incluiu o texto após o comando.", "Se usar <prefix>qc, confirme a menção @usuário e o conteúdo a citar.", "Garanta que você está conectado com o Google (login requerido) e tente novamente.", "Se o problema persistir, tente novamente após atualizar a página ou contate o suporte."],
|
|
387
|
+
"resumo_usuario_origem": "auto_ia_assistida",
|
|
388
|
+
"resumo_usuario_revisao_pendente": true
|
|
360
389
|
}
|
|
361
390
|
}
|
|
362
391
|
],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { createCanvas, loadImage } from 'canvas';
|
|
2
3
|
import fs from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -259,7 +260,7 @@ const getCachedEmojiImage = (cacheKey) => {
|
|
|
259
260
|
if (!entry) return undefined;
|
|
260
261
|
|
|
261
262
|
const ttl = entry.image ? EMOJI_CACHE_TTL_MS : EMOJI_FAIL_TTL_MS;
|
|
262
|
-
if (
|
|
263
|
+
if (__timeNowMs() - entry.createdAt > ttl) {
|
|
263
264
|
EMOJI_IMAGE_CACHE.delete(cacheKey);
|
|
264
265
|
return undefined;
|
|
265
266
|
}
|
|
@@ -275,7 +276,7 @@ const getCachedEmojiImage = (cacheKey) => {
|
|
|
275
276
|
* @returns {void}
|
|
276
277
|
*/
|
|
277
278
|
const setCachedEmojiImage = (cacheKey, image) => {
|
|
278
|
-
EMOJI_IMAGE_CACHE.set(cacheKey, { image, createdAt:
|
|
279
|
+
EMOJI_IMAGE_CACHE.set(cacheKey, { image, createdAt: __timeNowMs() });
|
|
279
280
|
};
|
|
280
281
|
|
|
281
282
|
/**
|
|
@@ -7,7 +7,7 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
7
7
|
- arquivo_base: `app/modules/rpgPokemonModule/commandConfig.json`
|
|
8
8
|
- schema_version: `2.0.0`
|
|
9
9
|
- module_enabled: `true`
|
|
10
|
-
- generated_at: `2026-03-
|
|
10
|
+
- generated_at: `2026-03-17T04:04:14.195Z`
|
|
11
11
|
|
|
12
12
|
## Escopo do Modulo
|
|
13
13
|
|
|
@@ -377,6 +377,35 @@
|
|
|
377
377
|
"schema": "legacy_v1_and_v2",
|
|
378
378
|
"legacy_name": "pokemon",
|
|
379
379
|
"legacy_fields_present": ["descricao", "metodos_de_uso", "permissao_necessaria", "local_de_uso", "informacoes_coletadas", "argumentos", "pre_condicoes", "dependencias_externas", "efeitos_colaterais", "observabilidade", "privacidade", "acesso", "limite_uso_por_plano"]
|
|
380
|
+
},
|
|
381
|
+
"user_experience": {
|
|
382
|
+
"resumo_usuario": "Comando principal do RPG Pokemon, usado para explorar, batalhar, comprar itens e socializar dentro do jogo pelo chat.",
|
|
383
|
+
"quando_usar": ["Iniciar a aventura: <prefix>pokemon start.", "Explorar o mapa: <prefix>pokemon explorar.", "Batalhar contra inimigos: <prefix>pokemon atacar <id>.", "Obter ajuda ou ver os métodos de uso: <prefix>pokemon help."],
|
|
384
|
+
"exemplos_reais": [
|
|
385
|
+
{
|
|
386
|
+
"situacao": "Novo jogador quer começar a aventura.",
|
|
387
|
+
"comando": "<prefix>pokemon start",
|
|
388
|
+
"resposta_esperada": "Comando executado com sucesso.",
|
|
389
|
+
"variacao": "Prefixo pode variar (ex.: ! ou /)."
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
"situacao": "Explorar o mapa atual.",
|
|
393
|
+
"comando": "<prefix>pokemon explorar",
|
|
394
|
+
"resposta_esperada": "Comando executado com sucesso.",
|
|
395
|
+
"variacao": "Se houver área bloqueada, o jogo mostrará informações da área atual."
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"situacao": "Iniciar batalha contra o oponente 1.",
|
|
399
|
+
"comando": "<prefix>pokemon atacar 1",
|
|
400
|
+
"resposta_esperada": "Comando executado com sucesso.",
|
|
401
|
+
"variacao": "O número pode variar conforme os oponentes disponíveis."
|
|
402
|
+
}
|
|
403
|
+
],
|
|
404
|
+
"resposta_esperada": ["Comando executado com sucesso.", "Formato de uso inválido. Consulte metodos_de_uso.", "Permissão insuficiente para executar este comando."],
|
|
405
|
+
"erros_comuns_usuario": ["Esquecer de informar o subcomando obrigatório (ação).", "Usar um subcomando que não existe ou está incorreto.", "Não estar logado com Google antes de usar o comando.", "Formato incorreto ao enviar parâmetros (ex.: <prefix>pokemon atacar sem o id).", "Tentar usar comandos antes de iniciar o jogo com <prefix>pokemon start."],
|
|
406
|
+
"passos_se_der_erro": ["Leia a mensagem de erro e verifique se o uso está correto consultando <prefix>pokemon help.", "Confira se o subcomando digitado é válido e se o <prefix> está correto.", "Tente novamente com o formato correto, por exemplo <prefix>pokemon start.", "Certifique-se de estar logado com Google, já que é exigido.", "Se o problema continuar, reporte o contexto do erro para o suporte."],
|
|
407
|
+
"resumo_usuario_origem": "auto_ia_assistida",
|
|
408
|
+
"resumo_usuario_revisao_pendente": true
|
|
380
409
|
}
|
|
381
410
|
}
|
|
382
411
|
],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import axios from 'axios';
|
|
2
3
|
import { createCanvas, loadImage } from 'canvas';
|
|
3
4
|
import logger from '#logger';
|
|
@@ -183,7 +184,7 @@ const drawRoundRect = (ctx, x, y, width, height, radius, fillStyle) => {
|
|
|
183
184
|
};
|
|
184
185
|
|
|
185
186
|
const cleanupCache = () => {
|
|
186
|
-
const now =
|
|
187
|
+
const now = __timeNowMs();
|
|
187
188
|
for (const [key, entry] of imageCache.entries()) {
|
|
188
189
|
if (!entry || entry.expiresAt <= now) {
|
|
189
190
|
imageCache.delete(key);
|
|
@@ -202,7 +203,7 @@ const resolveImage = async (imageUrl) => {
|
|
|
202
203
|
|
|
203
204
|
cleanupCache();
|
|
204
205
|
const cached = imageCache.get(url);
|
|
205
|
-
if (cached && cached.expiresAt >
|
|
206
|
+
if (cached && cached.expiresAt > __timeNowMs()) return cached.image;
|
|
206
207
|
|
|
207
208
|
try {
|
|
208
209
|
const response = await axios.get(url, {
|
|
@@ -211,10 +212,10 @@ const resolveImage = async (imageUrl) => {
|
|
|
211
212
|
headers: { Accept: 'image/*' },
|
|
212
213
|
});
|
|
213
214
|
const image = await loadImage(Buffer.from(response.data));
|
|
214
|
-
imageCache.set(url, { image, expiresAt:
|
|
215
|
+
imageCache.set(url, { image, expiresAt: __timeNowMs() + IMAGE_CACHE_TTL_MS });
|
|
215
216
|
return image;
|
|
216
217
|
} catch (error) {
|
|
217
|
-
imageCache.set(url, { image: null, expiresAt:
|
|
218
|
+
imageCache.set(url, { image: null, expiresAt: __timeNowMs() + 90_000 });
|
|
218
219
|
logger.debug('Falha ao carregar sprite para frame de batalha.', {
|
|
219
220
|
imageUrl: url,
|
|
220
221
|
error: error.message,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import test from 'node:test';
|
|
2
3
|
import assert from 'node:assert/strict';
|
|
3
4
|
|
|
@@ -15,7 +16,7 @@ const ensurePokeApiCache = () => {
|
|
|
15
16
|
const setCache = (cache, key, data) => {
|
|
16
17
|
cache.set(key, {
|
|
17
18
|
data,
|
|
18
|
-
expiresAt:
|
|
19
|
+
expiresAt: __timeNowMs() + 60 * 60 * 1000,
|
|
19
20
|
});
|
|
20
21
|
};
|
|
21
22
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
export const BIOME_KEYS = ['floresta', 'cidade', 'caverna'];
|
|
2
3
|
|
|
3
4
|
export const BIOME_DEFINITIONS = {
|
|
@@ -87,7 +88,7 @@ export const resolveDefaultBiomeForGroup = (groupJid) => {
|
|
|
87
88
|
return BIOME_DEFINITIONS[biomeKey];
|
|
88
89
|
};
|
|
89
90
|
|
|
90
|
-
export const resolveMissionRefs = (date =
|
|
91
|
+
export const resolveMissionRefs = (date = __timeNow()) => {
|
|
91
92
|
const year = date.getUTCFullYear();
|
|
92
93
|
const month = date.getUTCMonth();
|
|
93
94
|
const day = date.getUTCDate();
|