@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.
Files changed (261) hide show
  1. package/.env.example +58 -13
  2. package/.github/workflows/ci.yml +5 -5
  3. package/.github/workflows/codeql.yml +1 -1
  4. package/.github/workflows/db-migration-check.yml +2 -2
  5. package/.github/workflows/dependency-review.yml +1 -1
  6. package/.github/workflows/deploy.yml +2 -2
  7. package/.github/workflows/release.yml +2 -2
  8. package/.github/workflows/security-attest-provenance.yml +2 -2
  9. package/.github/workflows/security-gitleaks.yml +13 -4
  10. package/.github/workflows/security-runner-hardening.yml +2 -2
  11. package/.github/workflows/security-scorecard.yml +1 -1
  12. package/.github/workflows/security-zap-baseline.yml +1 -1
  13. package/.github/workflows/security-zap-full-scan.yml +2 -1
  14. package/.github/workflows/security-zizmor.yml +1 -1
  15. package/.github/workflows/wiki-sync.yml +1 -1
  16. package/.gitleaksignore +9 -0
  17. package/CODE_OF_CONDUCT.md +2 -2
  18. package/GEMINI.md +64 -0
  19. package/README.md +52 -82
  20. package/SECURITY.md +1 -1
  21. package/app/config/index.js +2 -0
  22. package/app/configParts/adminIdentity.js +5 -5
  23. package/app/configParts/baileysConfig.js +230 -58
  24. package/app/configParts/groupUtils.js +5 -0
  25. package/app/configParts/messagePersistenceService.js +145 -4
  26. package/app/configParts/sessionConfig.js +157 -0
  27. package/app/connection/baileysCompatibility.test.js +1 -1
  28. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  29. package/app/connection/socketController.js +660 -158
  30. package/app/connection/socketController.multiSession.test.js +108 -0
  31. package/app/controllers/messageController.js +1 -1
  32. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  33. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  34. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  35. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  36. package/app/controllers/messageProcessingPipeline.js +93 -13
  37. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  38. package/app/modules/adminModule/AGENT.md +1 -1
  39. package/app/modules/adminModule/commandConfig.json +3318 -1347
  40. package/app/modules/adminModule/groupCommandHandlers.js +858 -15
  41. package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
  42. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  43. package/app/modules/aiModule/AGENT.md +47 -30
  44. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  45. package/app/modules/aiModule/catCommand.js +135 -27
  46. package/app/modules/aiModule/commandConfig.json +114 -28
  47. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  48. package/app/modules/gameModule/AGENT.md +1 -1
  49. package/app/modules/gameModule/commandConfig.json +29 -0
  50. package/app/modules/menuModule/AGENT.md +1 -1
  51. package/app/modules/menuModule/commandConfig.json +45 -10
  52. package/app/modules/menuModule/menuCatalogService.js +190 -0
  53. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  54. package/app/modules/menuModule/menuDynamicService.js +511 -0
  55. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  56. package/app/modules/menuModule/menus.js +36 -5
  57. package/app/modules/playModule/AGENT.md +10 -5
  58. package/app/modules/playModule/commandConfig.json +140 -12
  59. package/app/modules/playModule/playCommand.js +1 -1417
  60. package/app/modules/playModule/playCommandConstants.js +80 -0
  61. package/app/modules/playModule/playCommandCore.js +361 -0
  62. package/app/modules/playModule/playCommandHandlers.js +41 -0
  63. package/app/modules/playModule/playCommandMediaClient.js +1872 -0
  64. package/app/modules/playModule/playConfigRuntime.js +245 -4
  65. package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
  66. package/app/modules/quoteModule/AGENT.md +1 -1
  67. package/app/modules/quoteModule/commandConfig.json +29 -0
  68. package/app/modules/quoteModule/quoteCommand.js +3 -2
  69. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  70. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  71. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
  72. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
  73. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
  74. package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
  75. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
  76. package/app/modules/statsModule/AGENT.md +1 -1
  77. package/app/modules/statsModule/commandConfig.json +58 -0
  78. package/app/modules/statsModule/rankingCommon.js +5 -4
  79. package/app/modules/stickerModule/AGENT.md +1 -1
  80. package/app/modules/stickerModule/addStickerMetadata.js +4 -3
  81. package/app/modules/stickerModule/commandConfig.json +145 -0
  82. package/app/modules/stickerModule/stickerCommand.js +1 -1
  83. package/app/modules/stickerPackModule/AGENT.md +1 -1
  84. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  85. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  86. package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
  87. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
  88. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
  89. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
  90. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
  91. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
  92. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
  93. package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
  94. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
  95. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  96. package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
  97. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
  98. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  99. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  100. package/app/modules/systemMetricsModule/pingCommand.js +6 -5
  101. package/app/modules/tiktokModule/AGENT.md +1 -1
  102. package/app/modules/tiktokModule/commandConfig.json +29 -0
  103. package/app/modules/tiktokModule/tiktokCommand.js +2 -1
  104. package/app/modules/userModule/AGENT.md +1 -1
  105. package/app/modules/userModule/commandConfig.json +29 -0
  106. package/app/modules/userModule/userCommand.js +72 -23
  107. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  108. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  109. package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
  110. package/app/observability/metrics.js +136 -0
  111. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  112. package/app/services/ai/conversationRouterService.js +4 -3
  113. package/app/services/ai/geminiService.js +132 -7
  114. package/app/services/ai/geminiService.test.js +59 -2
  115. package/app/services/ai/globalModuleAiHelpService.js +3 -2
  116. package/app/services/ai/messageCommandExecutionService.js +2 -1
  117. package/app/services/ai/moduleAiHelpCoreService.js +45 -14
  118. package/app/services/ai/moduleToolExecutorService.js +3 -2
  119. package/app/services/ai/moduleToolRegistryService.js +2 -1
  120. package/app/services/ai/toolCandidateSelectorService.js +6 -5
  121. package/app/services/auth/googleWebLinkService.js +3 -2
  122. package/app/services/auth/whatsappLoginLinkService.js +3 -2
  123. package/app/services/external/pokeApiService.js +4 -3
  124. package/app/services/group/groupMetadataService.js +24 -1
  125. package/app/services/infra/dbWriteQueue.js +57 -26
  126. package/app/services/infra/featureFlagService.js +2 -1
  127. package/app/services/messaging/captchaService.js +3 -2
  128. package/app/services/messaging/newsBroadcastService.js +846 -29
  129. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  130. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  131. package/app/services/multiSession/groupOwnershipService.js +890 -0
  132. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  133. package/app/services/multiSession/sessionRegistryService.js +293 -0
  134. package/app/services/sticker/stickerFocusService.js +11 -10
  135. package/app/store/aiPromptStore.js +36 -19
  136. package/app/store/conversationSessionStore.js +7 -6
  137. package/app/store/groupConfigStore.js +41 -5
  138. package/app/store/premiumUserStore.js +21 -7
  139. package/app/utils/antiLink/antiLinkModule.js +352 -16
  140. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  141. package/app/workers/aiLearningWorker.js +6 -5
  142. package/app/workers/commandConfigEnrichmentWorker.js +4 -3
  143. package/database/index.js +14 -8
  144. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  145. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  146. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  147. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  148. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  149. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  150. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  151. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  152. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  153. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  154. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  155. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  156. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  157. package/database/schema.sql +102 -1
  158. package/docker-compose.yml +4 -1
  159. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  160. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
  161. package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
  162. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  163. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
  164. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  165. package/docs/security/omnizap-static-security-headers.conf +25 -0
  166. package/docs/wiki/Home.md +1 -1
  167. package/ecosystem.prod.config.cjs +32 -12
  168. package/index.js +57 -23
  169. package/observability/alert-rules.yml +20 -0
  170. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  171. package/observability/mysql-setup.sql +4 -4
  172. package/observability/system-admin-observability.md +26 -0
  173. package/package.json +20 -6
  174. package/public/apple-touch-icon.png +0 -0
  175. package/public/comandos/commands-catalog.json +2853 -3326
  176. package/public/favicon-16x16.png +0 -0
  177. package/public/favicon-32x32.png +0 -0
  178. package/public/favicon.ico +0 -0
  179. package/public/js/apps/apiDocsApp.js +3 -2
  180. package/public/js/apps/commandsReactApp.js +280 -99
  181. package/public/js/apps/createPackApp.js +11 -10
  182. package/public/js/apps/homeReactApp.js +181 -130
  183. package/public/js/apps/loginReactApp.js +1 -1
  184. package/public/js/apps/stickersApp.js +263 -110
  185. package/public/js/apps/termsReactApp.js +73 -24
  186. package/public/js/apps/userApp.js +4 -3
  187. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  188. package/public/js/apps/userReactApp.js +355 -280
  189. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  190. package/public/pages/api-docs.html +1 -1
  191. package/public/pages/aup.html +2 -2
  192. package/public/pages/dpa.html +3 -3
  193. package/public/pages/licenca.html +4 -4
  194. package/public/pages/login.html +1 -1
  195. package/public/pages/notice-and-takedown.html +2 -2
  196. package/public/pages/politica-de-privacidade.html +6 -6
  197. package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
  198. package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
  199. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
  200. package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
  201. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
  202. package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
  203. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
  204. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
  205. package/public/pages/stickers-admin.html +1 -1
  206. package/public/pages/stickers-create.html +1 -1
  207. package/public/pages/stickers.html +6 -6
  208. package/public/pages/suboperadores.html +2 -2
  209. package/public/pages/termos-de-uso-texto-integral.html +6 -6
  210. package/public/pages/termos-de-uso.html +3 -3
  211. package/public/pages/user-password-reset.html +4 -5
  212. package/public/pages/user-systemadm.html +9 -463
  213. package/public/pages/user.html +2 -2
  214. package/scripts/clear-whatsapp-session.sh +123 -0
  215. package/scripts/core-ai-mode.mjs +163 -0
  216. package/scripts/deploy.sh +11 -1
  217. package/scripts/email-broadcast-terms-update.mjs +2 -1
  218. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  219. package/scripts/generate-commands-catalog.mjs +166 -2
  220. package/scripts/generate-module-agents.mjs +2 -1
  221. package/scripts/generate-seo-satellite-pages.mjs +5 -4
  222. package/scripts/github-deploy-notify.mjs +2 -1
  223. package/scripts/github-release-notify.mjs +25 -10
  224. package/scripts/new-whatsapp-session.sh +317 -0
  225. package/scripts/release.sh +2 -19
  226. package/scripts/security-smoketest.mjs +6 -5
  227. package/scripts/security-web-surface-check.mjs +218 -0
  228. package/scripts/sticker-catalog-loadtest.mjs +5 -4
  229. package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
  230. package/server/auth/jwt/webJwtService.js +1 -1
  231. package/server/auth/stickerCatalogAuthContext.js +2 -1
  232. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
  233. package/server/auth/userPassword/userPasswordAuthService.js +2 -1
  234. package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
  235. package/server/auth/webAccount/webAccountHandlers.js +9 -10
  236. package/server/controllers/admin/adminPanelHandlers.js +267 -16
  237. package/server/controllers/admin/systemAdminController.js +267 -0
  238. package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
  239. package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
  240. package/server/controllers/sticker/stickerCatalogController.js +23 -36
  241. package/server/controllers/system/contactController.js +9 -17
  242. package/server/controllers/system/githubController.js +3 -2
  243. package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
  244. package/server/controllers/system/systemController.js +254 -1
  245. package/server/controllers/system/systemMetricsController.js +2 -1
  246. package/server/controllers/userController.js +6 -0
  247. package/server/email/emailTemplateService.js +5 -3
  248. package/server/http/httpServer.js +11 -6
  249. package/server/middleware/rateLimit.js +2 -1
  250. package/server/middleware/securityHeaders.js +20 -1
  251. package/server/routes/admin/systemAdminRouter.js +6 -0
  252. package/server/routes/indexRouter.js +30 -6
  253. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  254. package/server/routes/static/staticPageRouter.js +27 -1
  255. package/server/utils/publicContact.js +31 -0
  256. package/utils/time/timeModule.js +135 -0
  257. package/utils/time/timeModule.test.js +65 -0
  258. package/utils/whatsapp/contactEnv.js +39 -0
  259. package/vite.config.mjs +7 -1
  260. package/public/assets/images/brand-icon-192.png +0 -0
  261. 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 = () => runtime.getModuleConfig();
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 resolvePlayCommandName = (command) => runtime.resolveCommandName(command);
48
- export const getPlayCommandEntry = (command) => runtime.getCommandEntry(command);
49
- export const listEnabledPlayCommands = () => runtime.listEnabledCommands();
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-11T02:35:17.177Z`
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 (Date.now() - entry.createdAt > ttl) {
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: Date.now() });
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-11T02:35:17.177Z`
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 = Date.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 > Date.now()) return cached.image;
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: Date.now() + IMAGE_CACHE_TTL_MS });
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: Date.now() + 90_000 });
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: Date.now() + 60 * 60 * 1000,
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 = new 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();