@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
@@ -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/aiModule/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
 
@@ -77,7 +77,7 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
77
77
  - enabled: true
78
78
  - categoria: ia
79
79
  - descricao: Perguntas para IA com suporte opcional a resposta em audio.
80
- - permissao_necessaria: usuario comum
80
+ - permissao_necessaria: usuario premium
81
81
  - version: 1.0.0
82
82
  - stability: stable
83
83
  - deprecated: nao
@@ -116,8 +116,8 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
116
116
  - janela_ms: null
117
117
  - escopo: sem_rate_limit_explicito
118
118
  - acesso:
119
- - somente_premium: nao
120
- - planos_permitidos: comum, premium
119
+ - somente_premium: sim
120
+ - planos_permitidos: premium
121
121
  - limite_uso_por_plano:
122
122
  - comum: max=8, janela_ms=300000, escopo=usuario
123
123
  - premium: max=40, janela_ms=300000, escopo=usuario
@@ -142,22 +142,29 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
142
142
  - erro_uso: Formato de uso inválido. Consulte metodos_de_uso.
143
143
  - erro_permissao: Permissão insuficiente para executar este comando.
144
144
  - mensagens_sistema:
145
- - premium*only: ⭐ \_Comando Premium*
145
+ - premium*only: ⭐ \_Recurso Premium*
146
146
 
147
- Este comando é exclusivo para usuários premium.
148
- Fale com o administrador para liberar o acesso.
147
+ Este comando é exclusivo para usuários Premium.
148
+ Para liberar o acesso, fale com o admin do sistema no privado.
149
+
150
+ - openai*nao_configurada: ⚠️ \_IA indisponível no momento*
151
+
152
+ Este recurso está em manutenção.
153
+ Se precisar de ajuda, fale com o admin do sistema no privado.
149
154
 
150
- - openai*nao_configurada: ⚠️ \_OpenAI não configurada*
155
+ - imagem_muito_grande: ⚠️ A imagem está muito grande para análise (limite {{limite_mb}} MB). Envie uma imagem menor.
156
+ - imagem_download_falhou: ⚠️ Não consegui ler sua imagem agora. Reenvie a imagem.
157
+ Se o erro continuar, fale com o admin do sistema no privado.
158
+ - resposta_vazia: ⚠️ Não consegui montar uma resposta agora. Tente novamente em instantes.
159
+ - audio_muito_longo: ⚠️ A resposta ficou grande para áudio. Vou te enviar em texto.
160
+ - audio_falhou: ⚠️ Não consegui gerar o áudio agora. Vou te responder em texto.
161
+ - erro*openai: ❌ \_Não consegui responder agora*
151
162
 
152
- Defina a variável _OPENAI_API_KEY_ no `.env` para usar o comando _cat_.
163
+ Tente novamente em instantes.
164
+ Se o erro continuar, fale com o admin do sistema no privado.
153
165
 
154
- - imagem_muito_grande: ⚠️ A imagem enviada ultrapassa o limite de {{limite_mb}} MB. Envie uma imagem menor.
155
- - imagem_download_falhou: ⚠️ Não consegui baixar a imagem. Tente reenviar.
156
- - resposta_vazia: ⚠️ Não consegui gerar uma resposta agora. Tente novamente.
157
- - audio_muito_longo: ⚠️ A resposta ficou longa demais para áudio. Enviando em texto.
158
- - audio_falhou: ⚠️ Não consegui gerar o áudio agora. Enviando texto.
159
- - erro*openai: ❌ \_Erro ao falar com a IA*
160
- Tente novamente em alguns instantes.
166
+ - usage*header: 🤖 \_Comando CAT*
167
+ - resposta_prefixo_texto: 🐈‍⬛
161
168
  - limites_operacionais:
162
169
  - (nao informado)
163
170
  - opcoes:
@@ -209,8 +216,8 @@ Defina a variável _OPENAI_API_KEY_ no `.env` para usar o comando _cat_.
209
216
  - rate_limit.max: null
210
217
  - rate_limit.janela_ms: null
211
218
  - rate_limit.escopo: sem_rate_limit_explicito
212
- - access.somente_premium: false
213
- - access.planos_permitidos: comum, premium
219
+ - access.somente_premium: true
220
+ - access.planos_permitidos: premium
214
221
  - plan_limits.comum.max: 8
215
222
  - plan_limits.comum.janela_ms: 300000
216
223
  - plan_limits.comum.escopo: usuario
@@ -234,7 +241,7 @@ Defina a variável _OPENAI_API_KEY_ no `.env` para usar o comando _cat_.
234
241
  - enabled: true
235
242
  - categoria: ia
236
243
  - descricao: Gera/edita imagem com IA por prompt.
237
- - permissao_necessaria: usuario comum
244
+ - permissao_necessaria: usuario premium
238
245
  - version: 1.0.0
239
246
  - stability: stable
240
247
  - deprecated: nao
@@ -302,25 +309,33 @@ Defina a variável _OPENAI_API_KEY_ no `.env` para usar o comando _cat_.
302
309
  - erro_uso: Formato de uso inválido. Consulte metodos_de_uso.
303
310
  - erro_permissao: Permissão insuficiente para executar este comando.
304
311
  - mensagens_sistema:
305
- - premium*only: ⭐ \_Comando Premium*
312
+ - premium*only: ⭐ \_Recurso Premium*
306
313
 
307
- Este comando é exclusivo para usuários premium.
308
- Fale com o administrador para liberar o acesso.
314
+ Este comando é exclusivo para usuários Premium.
315
+ Para liberar o acesso, fale com o admin do sistema no privado.
309
316
 
310
- - openai*nao_configurada: ⚠️ \_OpenAI não configurada*
317
+ - openai*nao_configurada: ⚠️ \_Gerador de imagem indisponível*
311
318
 
312
- Defina a variável _OPENAI_API_KEY_ no `.env` para usar o comando _catimg_.
319
+ Este recurso está em manutenção.
320
+ Se precisar de ajuda, fale com o admin do sistema no privado.
313
321
 
314
- - imagem_muito_grande: ⚠️ A imagem enviada ultrapassa o limite de {{limite_mb}} MB. Envie uma imagem menor.
315
- - imagem_download_falhou: ⚠️ Não consegui baixar a imagem. Tente reenviar.
316
- - opcoes_invalidas: ⚠️ Opções inválidas no comando.
322
+ - imagem_muito_grande: ⚠️ A imagem está muito grande para edição (limite {{limite_mb}} MB). Envie uma imagem menor.
323
+ - imagem_download_falhou: ⚠️ Não consegui ler sua imagem agora. Reenvie a imagem.
324
+ Se o erro continuar, fale com o admin do sistema no privado.
325
+ - opcoes_invalidas: ⚠️ Algumas opções do comando estão inválidas.
317
326
  Detalhes: {{detalhes}}
318
327
 
319
328
  Use _{{prefix}}catimg_ sem opções para ver o formato correto.
320
329
 
321
- - resposta_vazia: ⚠️ Não consegui gerar a imagem agora. Tente novamente.
322
- - erro*openai: ❌ \_Erro ao falar com a IA*
323
- Tente novamente em alguns instantes.
330
+ - resposta_vazia: ⚠️ Não consegui gerar a imagem agora. Tente novamente em instantes.
331
+ - erro*openai: ❌ \_Não consegui gerar sua imagem agora*
332
+
333
+ Tente novamente em instantes.
334
+ Se o erro continuar, fale com o admin do sistema no privado.
335
+
336
+ - usage*header: 🖼️ \_Imagem IA*
337
+ - resposta_prefixo_texto_imagem: 🖼️
338
+ - imagem_caption_sucesso: 🖼️ Imagem gerada.
324
339
  - limites_operacionais:
325
340
  - (nao informado)
326
341
  - opcoes:
@@ -489,6 +504,7 @@ Fale com o administrador para liberar o acesso.
489
504
  - prompt_muito_longo: ⚠️ Prompt muito longo. Limite: {{max_chars}} caracteres.
490
505
  - prompt_reset_sucesso: ✅ Prompt da IA restaurado para o padrão.
491
506
  - prompt_update_sucesso: ✅ Prompt da IA atualizado para você.
507
+ - usage*header: 🧠 \_Prompt da IA*
492
508
  - limites_operacionais:
493
509
  - prompt_max_chars: 2000
494
510
  - opcoes:
@@ -505,6 +521,7 @@ Fale com o administrador para liberar o acesso.
505
521
  - set_status_reset.type: configuration_window
506
522
  - set_status_reset.allowed_actions: set, status, reset
507
523
  - set_status_reset.action_argument: valor
524
+ - parse.reset_aliases: reset, default, padrao, padrão
508
525
  - observabilidade:
509
526
  - event_name: command.executed
510
527
  - analytics_event: whatsapp_command_catprompt
@@ -114,6 +114,7 @@ export const getAiCommandOptionConfig = (command) => {
114
114
  parse: {
115
115
  audio_flags: Array.isArray(parse.audio_flags) ? parse.audio_flags.map((item) => String(item || '')).filter(Boolean) : [],
116
116
  text_flags: Array.isArray(parse.text_flags) ? parse.text_flags.map((item) => String(item || '')).filter(Boolean) : [],
117
+ reset_aliases: Array.isArray(parse.reset_aliases) ? parse.reset_aliases.map((item) => String(item || '')).filter(Boolean) : [],
117
118
  image_detail_aliases: normalizeMapKeys(parse.image_detail_aliases || {}),
118
119
  },
119
120
  geracao_imagem: {
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import OpenAI from 'openai';
2
3
  import NodeCache from 'node-cache';
3
4
  import fs from 'node:fs/promises';
@@ -34,14 +35,18 @@ const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
34
35
  const OWNER_JID = getAdminJid();
35
36
 
36
37
  const SESSION_TTL_SECONDS = Number.parseInt(process.env.OPENAI_SESSION_TTL_SECONDS || '21600', 10);
38
+ const ADMIN_ALERT_DEDUPE_WINDOW_MS_RAW = Number.parseInt(process.env.AI_ADMIN_ALERT_DEDUPE_WINDOW_MS || '120000', 10);
37
39
  const sessionCache = new NodeCache({
38
40
  stdTTL: SESSION_TTL_SECONDS,
39
41
  checkperiod: Math.max(60, Math.floor(SESSION_TTL_SECONDS / 4)),
40
42
  });
43
+ const ADMIN_ALERT_DEDUPE_WINDOW_MS = Number.isFinite(ADMIN_ALERT_DEDUPE_WINDOW_MS_RAW) && ADMIN_ALERT_DEDUPE_WINDOW_MS_RAW > 0 ? ADMIN_ALERT_DEDUPE_WINDOW_MS_RAW : 120000;
44
+ const adminAlertDedupCache = new Map();
41
45
  let cachedClient = null;
42
46
 
43
47
  const AUDIO_FLAG_ALIASES = new Set(['--audio', '--voz', '--voice', '--tts', '-a']);
44
48
  const TEXT_FLAG_ALIASES = new Set(['--texto', '--text', '--txt']);
49
+ const CATPROMPT_RESET_ALIASES = new Set(['reset', 'default', 'padrao', 'padrão']);
45
50
  const IMAGE_DETAIL_ALIASES = new Map([
46
51
  ['low', 'low'],
47
52
  ['high', 'high'],
@@ -151,6 +156,10 @@ const resolveAiMessages = (commandName) => {
151
156
  const mergeMessage = (key, fallback) => String(commandMessages?.[key] || '').trim() || String(fallback || '').trim();
152
157
 
153
158
  return {
159
+ usageHeader: mergeMessage('usage_header', '🤖 *Comando*'),
160
+ textResponsePrefix: mergeMessage('resposta_prefixo_texto', ''),
161
+ imageTextResponsePrefix: mergeMessage('resposta_prefixo_texto_imagem', '🖼️ '),
162
+ imageSuccessCaption: mergeMessage('imagem_caption_sucesso', '🖼️ Imagem gerada.'),
154
163
  premiumOnly: mergeMessage('premium_only', ['⭐ *Comando Premium*', '', 'Este comando é exclusivo para usuários premium.', 'Fale com o administrador para liberar o acesso.'].join('\n')),
155
164
  openAiNotConfigured: mergeMessage('openai_nao_configurada', ['⚠️ *OpenAI não configurada*', '', `Defina a variável *OPENAI_API_KEY* no \`.env\` para usar o comando *${commandName}*.`].join('\n')),
156
165
  imageTooLarge: mergeMessage('imagem_muito_grande', '⚠️ A imagem enviada ultrapassa o limite de {{limite_mb}} MB. Envie uma imagem menor.'),
@@ -231,6 +240,11 @@ const resolveCatPromptMaxChars = () => {
231
240
  return value;
232
241
  };
233
242
 
243
+ const resolveCatPromptResetAliases = () => {
244
+ const config = getAiCommandOptionConfig('catprompt');
245
+ return toSet(config?.parse?.reset_aliases, [...CATPROMPT_RESET_ALIASES]);
246
+ };
247
+
234
248
  const getClient = () => {
235
249
  if (cachedClient) return cachedClient;
236
250
  cachedClient = new OpenAI({
@@ -309,12 +323,13 @@ const callOpenAI = async (operationFactory, label, timeoutMs) => {
309
323
  };
310
324
 
311
325
  const sendUsage = async (sock, remoteJid, messageInfo, expirationMessage, commandPrefix = DEFAULT_COMMAND_PREFIX) => {
326
+ const commandMessages = resolveAiMessages('cat');
312
327
  const usageText =
313
328
  getAiUsageText('cat', {
314
329
  commandPrefix,
315
- header: '🤖 *Comando CAT*',
330
+ header: commandMessages.usageHeader,
316
331
  variant: 'default',
317
- }) || ['🤖 *Comando CAT*', '', 'Use assim:', `*${commandPrefix}cat* [--audio] sua pergunta`, `*${commandPrefix}cat* (responda ou envie uma imagem com legenda)`, '', 'Opções:', '--audio | --texto', '--detail low | high | auto', '', 'Exemplo:', `*${commandPrefix}cat* Explique como funciona a fotossíntese.`, `*${commandPrefix}cat* --audio Resuma a imagem.`].join('\n');
332
+ }) || `${commandMessages.usageHeader}\n*${commandPrefix}cat*`;
318
333
 
319
334
  await sendAndStore(sock, remoteJid, { text: usageText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
320
335
  };
@@ -363,24 +378,75 @@ const sendPremiumOnly = async (sock, remoteJid, messageInfo, expirationMessage,
363
378
  await sendAndStore(sock, remoteJid, { text: messages.premiumOnly }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
364
379
  };
365
380
 
381
+ const shouldSendAdminAlert = ({ commandName = '', stage = '', remoteJid = '', senderJid = '', errorMessage = '' } = {}) => {
382
+ const dedupeKey = `${normalizeText(commandName)}|${normalizeText(stage)}|${normalizeText(remoteJid)}|${normalizeText(senderJid)}|${normalizeText(errorMessage)}`;
383
+ const nowMs = __timeNowMs();
384
+ const lastSentAt = adminAlertDedupCache.get(dedupeKey);
385
+ if (Number.isFinite(lastSentAt) && nowMs - lastSentAt < ADMIN_ALERT_DEDUPE_WINDOW_MS) {
386
+ return false;
387
+ }
388
+
389
+ adminAlertDedupCache.set(dedupeKey, nowMs);
390
+ for (const [key, ts] of adminAlertDedupCache.entries()) {
391
+ if (!Number.isFinite(ts) || nowMs - ts > ADMIN_ALERT_DEDUPE_WINDOW_MS) {
392
+ adminAlertDedupCache.delete(key);
393
+ }
394
+ }
395
+ return true;
396
+ };
397
+
398
+ const notifyAdminAiError = async (sock, { commandName = 'cat', stage = 'unknown', remoteJid = '', senderJid = '', messageInfo = null, error = null } = {}) => {
399
+ try {
400
+ const adminJid = normalizeJid((await resolveAdminJid().catch(() => null)) || OWNER_JID || '');
401
+ if (!adminJid) return;
402
+
403
+ const normalizedRemote = normalizeJid(remoteJid || '') || String(remoteJid || '').trim() || 'desconhecido';
404
+ const normalizedSender = normalizeJid(senderJid || '') || String(senderJid || '').trim() || 'desconhecido';
405
+ const errorMessage = String(error?.message || error || 'erro desconhecido').trim() || 'erro desconhecido';
406
+ const errorStatus = error?.status || error?.statusCode || error?.response?.status || null;
407
+ const messageId = messageInfo?.key?.id || 'sem_id';
408
+ const shouldSend = shouldSendAdminAlert({
409
+ commandName,
410
+ stage,
411
+ remoteJid: normalizedRemote,
412
+ senderJid: normalizedSender,
413
+ errorMessage,
414
+ });
415
+ if (!shouldSend) return;
416
+
417
+ const statusLine = errorStatus ? `\n- Status: ${errorStatus}` : '';
418
+ await sendAndStore(sock, adminJid, {
419
+ text: `🚨 *Alerta IA*\n\n- Comando: ${commandName}\n- Etapa: ${stage}\n- Chat: ${normalizedRemote}\n- Usuário: ${normalizedSender}\n- MsgID: ${messageId}${statusLine}\n- Erro: ${errorMessage}\n- Horário (UTC): ${__timeNowIso()}`,
420
+ });
421
+ } catch (notifyError) {
422
+ logger.warn('notifyAdminAiError: falha ao notificar admin no privado.', {
423
+ error: notifyError?.message || String(notifyError),
424
+ commandName,
425
+ stage,
426
+ });
427
+ }
428
+ };
429
+
366
430
  const sendPromptUsage = async (sock, remoteJid, messageInfo, expirationMessage, commandPrefix = DEFAULT_COMMAND_PREFIX) => {
431
+ const commandMessages = resolveAiMessages('catprompt');
367
432
  const usageText =
368
433
  getAiUsageText('catprompt', {
369
434
  commandPrefix,
370
- header: '🧠 *Prompt da IA*',
435
+ header: commandMessages.usageHeader,
371
436
  variant: 'default',
372
- }) || ['🧠 *Prompt da IA*', '', 'Use assim:', `*${commandPrefix}catprompt* seu novo prompt`, '', 'Para voltar ao padrão:', `*${commandPrefix}catprompt reset*`].join('\n');
437
+ }) || `${commandMessages.usageHeader}\n*${commandPrefix}catprompt*`;
373
438
 
374
439
  await sendAndStore(sock, remoteJid, { text: usageText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
375
440
  };
376
441
 
377
442
  const sendImageUsage = async (sock, remoteJid, messageInfo, expirationMessage, commandPrefix = DEFAULT_COMMAND_PREFIX) => {
443
+ const commandMessages = resolveAiMessages('catimg');
378
444
  const usageText =
379
445
  getAiUsageText('catimg', {
380
446
  commandPrefix,
381
- header: '🖼️ *Imagem IA*',
447
+ header: commandMessages.usageHeader,
382
448
  variant: 'default',
383
- }) || ['🖼️ *Imagem IA*', '', 'Use assim:', `*${commandPrefix}catimg* seu prompt`, `*${commandPrefix}catimg* (responda uma imagem com legenda para editar)`, '', 'Opções:', '--size 1024x1024 | 1024x1536 | 1536x1024 | auto', '--quality low | medium | high | auto', '--format png | jpeg | webp', '--background transparent | opaque | auto', '--compression 0-100', '', 'Exemplo:', `*${commandPrefix}catimg* --size 1536x1024 Um gato astronauta em aquarela.`].join('\n');
449
+ }) || `${commandMessages.usageHeader}\n*${commandPrefix}catimg*`;
384
450
 
385
451
  await sendAndStore(sock, remoteJid, { text: usageText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
386
452
  };
@@ -664,21 +730,29 @@ export async function handleCatCommand({ sock, remoteJid, messageInfo, expiratio
664
730
  const commandMessages = resolveAiMessages(commandName);
665
731
  const { prompt: rawPrompt, wantsAudio, imageDetail } = parseCatOptions(text || '', resolveCatParseOptions());
666
732
 
733
+ if (isAiCommandPremiumOnly(commandName)) {
734
+ if (!(await isPremiumAllowed(senderJid))) {
735
+ await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
736
+ return;
737
+ }
738
+ }
739
+
667
740
  if (!process.env.OPENAI_API_KEY) {
668
741
  logger.warn('handleCatCommand: OPENAI_API_KEY não configurada.');
669
742
  await sendAndStore(sock, remoteJid, { text: commandMessages.openAiNotConfigured }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
743
+ await notifyAdminAiError(sock, {
744
+ commandName,
745
+ stage: 'openai_api_key_missing',
746
+ remoteJid,
747
+ senderJid,
748
+ messageInfo,
749
+ error: new Error('OPENAI_API_KEY não configurada para comando cat'),
750
+ });
670
751
  return;
671
752
  }
672
753
 
673
754
  await reactToMessage(sock, remoteJid, messageInfo);
674
755
 
675
- if (isAiCommandPremiumOnly(commandName)) {
676
- if (!(await isPremiumAllowed(senderJid))) {
677
- await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
678
- return;
679
- }
680
- }
681
-
682
756
  const imageMedia = findImageMedia(messageInfo);
683
757
  const imageResult = await buildImageDataUrl(imageMedia, senderJid);
684
758
  if (imageResult.error === 'too_large') {
@@ -742,7 +816,7 @@ export async function handleCatCommand({ sock, remoteJid, messageInfo, expiratio
742
816
 
743
817
  sessionCache.set(sessionKey, {
744
818
  previousResponseId: response.id,
745
- updatedAt: Date.now(),
819
+ updatedAt: __timeNowMs(),
746
820
  });
747
821
 
748
822
  if (!outputText) {
@@ -781,11 +855,20 @@ export async function handleCatCommand({ sock, remoteJid, messageInfo, expiratio
781
855
  } catch (audioError) {
782
856
  logger.error('handleCatCommand: erro ao gerar audio.', audioError);
783
857
  await sendAndStore(sock, remoteJid, { text: commandMessages.audioFailedFallback }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
858
+ await notifyAdminAiError(sock, {
859
+ commandName,
860
+ stage: 'audio_speech_create',
861
+ remoteJid,
862
+ senderJid,
863
+ messageInfo,
864
+ error: audioError,
865
+ });
784
866
  }
785
867
  }
786
868
  }
787
869
 
788
- await sendAndStore(sock, remoteJid, { text: `🐈‍⬛ ${outputText}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
870
+ const responsePrefix = commandMessages.textResponsePrefix;
871
+ await sendAndStore(sock, remoteJid, { text: `${responsePrefix}${outputText}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
789
872
  } catch (error) {
790
873
  logger.error('handleCatCommand: erro ao chamar OpenAI.', error);
791
874
  await sendAndStore(
@@ -796,6 +879,14 @@ export async function handleCatCommand({ sock, remoteJid, messageInfo, expiratio
796
879
  },
797
880
  { quoted: messageInfo, ephemeralExpiration: expirationMessage },
798
881
  );
882
+ await notifyAdminAiError(sock, {
883
+ commandName,
884
+ stage: 'responses_create',
885
+ remoteJid,
886
+ senderJid,
887
+ messageInfo,
888
+ error,
889
+ });
799
890
  }
800
891
  }
801
892
 
@@ -804,21 +895,29 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
804
895
  const commandMessages = resolveAiMessages(commandName);
805
896
  const { prompt, toolOptions, errors } = parseImageGenOptions(text || '', resolveCatImageGenerationOptions());
806
897
 
898
+ if (isAiCommandPremiumOnly(commandName)) {
899
+ if (!(await isPremiumAllowed(senderJid))) {
900
+ await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
901
+ return;
902
+ }
903
+ }
904
+
807
905
  if (!process.env.OPENAI_API_KEY) {
808
906
  logger.warn('handleCatImageCommand: OPENAI_API_KEY não configurada.');
809
907
  await sendAndStore(sock, remoteJid, { text: commandMessages.openAiNotConfigured }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
908
+ await notifyAdminAiError(sock, {
909
+ commandName,
910
+ stage: 'openai_api_key_missing',
911
+ remoteJid,
912
+ senderJid,
913
+ messageInfo,
914
+ error: new Error('OPENAI_API_KEY não configurada para comando catimg'),
915
+ });
810
916
  return;
811
917
  }
812
918
 
813
919
  await reactToMessage(sock, remoteJid, messageInfo);
814
920
 
815
- if (isAiCommandPremiumOnly(commandName)) {
816
- if (!(await isPremiumAllowed(senderJid))) {
817
- await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
818
- return;
819
- }
820
- }
821
-
822
921
  const imageMedia = findImageMedia(messageInfo);
823
922
  const imageResult = await buildImageDataUrl(imageMedia, senderJid);
824
923
  if (imageResult.error === 'too_large') {
@@ -893,7 +992,7 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
893
992
 
894
993
  sessionCache.set(sessionKey, {
895
994
  previousResponseId: response.id,
896
- updatedAt: Date.now(),
995
+ updatedAt: __timeNowMs(),
897
996
  });
898
997
 
899
998
  const imageOutputs = Array.isArray(response.output) ? response.output.filter((output) => output.type === 'image_generation_call' && output.result) : [];
@@ -901,7 +1000,7 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
901
1000
 
902
1001
  if (!imageBase64) {
903
1002
  if (outputText) {
904
- await sendAndStore(sock, remoteJid, { text: `🖼️ ${outputText}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
1003
+ await sendAndStore(sock, remoteJid, { text: `${commandMessages.imageTextResponsePrefix}${outputText}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
905
1004
  return;
906
1005
  }
907
1006
 
@@ -917,7 +1016,7 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
917
1016
  };
918
1017
  const mimetype = mimeByFormat[outputFormat] || 'image/png';
919
1018
  const imageBuffer = Buffer.from(imageBase64, 'base64');
920
- const caption = outputText ? `🖼️ ${outputText}` : '🖼️ Imagem gerada.';
1019
+ const caption = outputText ? `${commandMessages.imageTextResponsePrefix}${outputText}` : commandMessages.imageSuccessCaption;
921
1020
 
922
1021
  await sendAndStore(sock, remoteJid, { image: imageBuffer, caption, mimetype }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
923
1022
  } catch (error) {
@@ -930,6 +1029,14 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
930
1029
  },
931
1030
  { quoted: messageInfo, ephemeralExpiration: expirationMessage },
932
1031
  );
1032
+ await notifyAdminAiError(sock, {
1033
+ commandName,
1034
+ stage: 'responses_create_image',
1035
+ remoteJid,
1036
+ senderJid,
1037
+ messageInfo,
1038
+ error,
1039
+ });
933
1040
  }
934
1041
  }
935
1042
 
@@ -937,6 +1044,7 @@ export async function handleCatPromptCommand({ sock, remoteJid, messageInfo, exp
937
1044
  const commandName = 'catprompt';
938
1045
  const commandMessages = resolveAiMessages(commandName);
939
1046
  const promptMaxChars = resolveCatPromptMaxChars();
1047
+ const promptResetAliases = resolveCatPromptResetAliases();
940
1048
  const promptText = text?.trim();
941
1049
  if (!promptText) {
942
1050
  await sendPromptUsage(sock, remoteJid, messageInfo, expirationMessage, commandPrefix);
@@ -950,8 +1058,8 @@ export async function handleCatPromptCommand({ sock, remoteJid, messageInfo, exp
950
1058
  }
951
1059
  }
952
1060
 
953
- const lower = promptText.toLowerCase();
954
- if (lower === 'reset' || lower === 'default' || lower === 'padrao' || lower === 'padrão') {
1061
+ const lower = normalizeText(promptText);
1062
+ if (promptResetAliases.has(lower)) {
955
1063
  await aiPromptStore.clearPrompt(senderJid);
956
1064
  await sendAndStore(sock, remoteJid, { text: commandMessages.promptResetSuccess }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
957
1065
  return;