@omnizap-system/omnizap 2.6.1 → 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 +54 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +226 -55
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +143 -3
- 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 +625 -124
- 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 +88 -9
- 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 +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- 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 +132 -25
- 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 +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- 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/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- 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/store/aiPromptStore.js +36 -19
- 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/database/index.js +6 -0
- 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/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-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/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- 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 +12 -5
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +10 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +3 -2
- package/server/http/httpServer.js +8 -4
- 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/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +2 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -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-
|
|
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
|
|
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:
|
|
120
|
-
- planos_permitidos:
|
|
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: ⭐ \
|
|
145
|
+
- premium*only: ⭐ \_Recurso Premium*
|
|
146
146
|
|
|
147
|
-
Este comando é exclusivo para usuários
|
|
148
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
163
|
+
Tente novamente em instantes.
|
|
164
|
+
Se o erro continuar, fale com o admin do sistema no privado.
|
|
153
165
|
|
|
154
|
-
-
|
|
155
|
-
-
|
|
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:
|
|
213
|
-
- access.planos_permitidos:
|
|
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
|
|
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: ⭐ \
|
|
312
|
+
- premium*only: ⭐ \_Recurso Premium*
|
|
306
313
|
|
|
307
|
-
Este comando é exclusivo para usuários
|
|
308
|
-
|
|
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: ⚠️ \
|
|
317
|
+
- openai*nao_configurada: ⚠️ \_Gerador de imagem indisponível*
|
|
311
318
|
|
|
312
|
-
|
|
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
|
|
315
|
-
- imagem_download_falhou: ⚠️ Não consegui
|
|
316
|
-
|
|
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: ❌ \
|
|
323
|
-
|
|
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: {
|
|
@@ -35,14 +35,18 @@ const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
|
|
|
35
35
|
const OWNER_JID = getAdminJid();
|
|
36
36
|
|
|
37
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);
|
|
38
39
|
const sessionCache = new NodeCache({
|
|
39
40
|
stdTTL: SESSION_TTL_SECONDS,
|
|
40
41
|
checkperiod: Math.max(60, Math.floor(SESSION_TTL_SECONDS / 4)),
|
|
41
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();
|
|
42
45
|
let cachedClient = null;
|
|
43
46
|
|
|
44
47
|
const AUDIO_FLAG_ALIASES = new Set(['--audio', '--voz', '--voice', '--tts', '-a']);
|
|
45
48
|
const TEXT_FLAG_ALIASES = new Set(['--texto', '--text', '--txt']);
|
|
49
|
+
const CATPROMPT_RESET_ALIASES = new Set(['reset', 'default', 'padrao', 'padrão']);
|
|
46
50
|
const IMAGE_DETAIL_ALIASES = new Map([
|
|
47
51
|
['low', 'low'],
|
|
48
52
|
['high', 'high'],
|
|
@@ -152,6 +156,10 @@ const resolveAiMessages = (commandName) => {
|
|
|
152
156
|
const mergeMessage = (key, fallback) => String(commandMessages?.[key] || '').trim() || String(fallback || '').trim();
|
|
153
157
|
|
|
154
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.'),
|
|
155
163
|
premiumOnly: mergeMessage('premium_only', ['⭐ *Comando Premium*', '', 'Este comando é exclusivo para usuários premium.', 'Fale com o administrador para liberar o acesso.'].join('\n')),
|
|
156
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')),
|
|
157
165
|
imageTooLarge: mergeMessage('imagem_muito_grande', '⚠️ A imagem enviada ultrapassa o limite de {{limite_mb}} MB. Envie uma imagem menor.'),
|
|
@@ -232,6 +240,11 @@ const resolveCatPromptMaxChars = () => {
|
|
|
232
240
|
return value;
|
|
233
241
|
};
|
|
234
242
|
|
|
243
|
+
const resolveCatPromptResetAliases = () => {
|
|
244
|
+
const config = getAiCommandOptionConfig('catprompt');
|
|
245
|
+
return toSet(config?.parse?.reset_aliases, [...CATPROMPT_RESET_ALIASES]);
|
|
246
|
+
};
|
|
247
|
+
|
|
235
248
|
const getClient = () => {
|
|
236
249
|
if (cachedClient) return cachedClient;
|
|
237
250
|
cachedClient = new OpenAI({
|
|
@@ -310,12 +323,13 @@ const callOpenAI = async (operationFactory, label, timeoutMs) => {
|
|
|
310
323
|
};
|
|
311
324
|
|
|
312
325
|
const sendUsage = async (sock, remoteJid, messageInfo, expirationMessage, commandPrefix = DEFAULT_COMMAND_PREFIX) => {
|
|
326
|
+
const commandMessages = resolveAiMessages('cat');
|
|
313
327
|
const usageText =
|
|
314
328
|
getAiUsageText('cat', {
|
|
315
329
|
commandPrefix,
|
|
316
|
-
header:
|
|
330
|
+
header: commandMessages.usageHeader,
|
|
317
331
|
variant: 'default',
|
|
318
|
-
}) ||
|
|
332
|
+
}) || `${commandMessages.usageHeader}\n*${commandPrefix}cat*`;
|
|
319
333
|
|
|
320
334
|
await sendAndStore(sock, remoteJid, { text: usageText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
321
335
|
};
|
|
@@ -364,24 +378,75 @@ const sendPremiumOnly = async (sock, remoteJid, messageInfo, expirationMessage,
|
|
|
364
378
|
await sendAndStore(sock, remoteJid, { text: messages.premiumOnly }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
365
379
|
};
|
|
366
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
|
+
|
|
367
430
|
const sendPromptUsage = async (sock, remoteJid, messageInfo, expirationMessage, commandPrefix = DEFAULT_COMMAND_PREFIX) => {
|
|
431
|
+
const commandMessages = resolveAiMessages('catprompt');
|
|
368
432
|
const usageText =
|
|
369
433
|
getAiUsageText('catprompt', {
|
|
370
434
|
commandPrefix,
|
|
371
|
-
header:
|
|
435
|
+
header: commandMessages.usageHeader,
|
|
372
436
|
variant: 'default',
|
|
373
|
-
}) ||
|
|
437
|
+
}) || `${commandMessages.usageHeader}\n*${commandPrefix}catprompt*`;
|
|
374
438
|
|
|
375
439
|
await sendAndStore(sock, remoteJid, { text: usageText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
376
440
|
};
|
|
377
441
|
|
|
378
442
|
const sendImageUsage = async (sock, remoteJid, messageInfo, expirationMessage, commandPrefix = DEFAULT_COMMAND_PREFIX) => {
|
|
443
|
+
const commandMessages = resolveAiMessages('catimg');
|
|
379
444
|
const usageText =
|
|
380
445
|
getAiUsageText('catimg', {
|
|
381
446
|
commandPrefix,
|
|
382
|
-
header:
|
|
447
|
+
header: commandMessages.usageHeader,
|
|
383
448
|
variant: 'default',
|
|
384
|
-
}) ||
|
|
449
|
+
}) || `${commandMessages.usageHeader}\n*${commandPrefix}catimg*`;
|
|
385
450
|
|
|
386
451
|
await sendAndStore(sock, remoteJid, { text: usageText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
387
452
|
};
|
|
@@ -665,21 +730,29 @@ export async function handleCatCommand({ sock, remoteJid, messageInfo, expiratio
|
|
|
665
730
|
const commandMessages = resolveAiMessages(commandName);
|
|
666
731
|
const { prompt: rawPrompt, wantsAudio, imageDetail } = parseCatOptions(text || '', resolveCatParseOptions());
|
|
667
732
|
|
|
733
|
+
if (isAiCommandPremiumOnly(commandName)) {
|
|
734
|
+
if (!(await isPremiumAllowed(senderJid))) {
|
|
735
|
+
await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
668
740
|
if (!process.env.OPENAI_API_KEY) {
|
|
669
741
|
logger.warn('handleCatCommand: OPENAI_API_KEY não configurada.');
|
|
670
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
|
+
});
|
|
671
751
|
return;
|
|
672
752
|
}
|
|
673
753
|
|
|
674
754
|
await reactToMessage(sock, remoteJid, messageInfo);
|
|
675
755
|
|
|
676
|
-
if (isAiCommandPremiumOnly(commandName)) {
|
|
677
|
-
if (!(await isPremiumAllowed(senderJid))) {
|
|
678
|
-
await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
756
|
const imageMedia = findImageMedia(messageInfo);
|
|
684
757
|
const imageResult = await buildImageDataUrl(imageMedia, senderJid);
|
|
685
758
|
if (imageResult.error === 'too_large') {
|
|
@@ -782,11 +855,20 @@ export async function handleCatCommand({ sock, remoteJid, messageInfo, expiratio
|
|
|
782
855
|
} catch (audioError) {
|
|
783
856
|
logger.error('handleCatCommand: erro ao gerar audio.', audioError);
|
|
784
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
|
+
});
|
|
785
866
|
}
|
|
786
867
|
}
|
|
787
868
|
}
|
|
788
869
|
|
|
789
|
-
|
|
870
|
+
const responsePrefix = commandMessages.textResponsePrefix;
|
|
871
|
+
await sendAndStore(sock, remoteJid, { text: `${responsePrefix}${outputText}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
790
872
|
} catch (error) {
|
|
791
873
|
logger.error('handleCatCommand: erro ao chamar OpenAI.', error);
|
|
792
874
|
await sendAndStore(
|
|
@@ -797,6 +879,14 @@ export async function handleCatCommand({ sock, remoteJid, messageInfo, expiratio
|
|
|
797
879
|
},
|
|
798
880
|
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
799
881
|
);
|
|
882
|
+
await notifyAdminAiError(sock, {
|
|
883
|
+
commandName,
|
|
884
|
+
stage: 'responses_create',
|
|
885
|
+
remoteJid,
|
|
886
|
+
senderJid,
|
|
887
|
+
messageInfo,
|
|
888
|
+
error,
|
|
889
|
+
});
|
|
800
890
|
}
|
|
801
891
|
}
|
|
802
892
|
|
|
@@ -805,21 +895,29 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
|
|
|
805
895
|
const commandMessages = resolveAiMessages(commandName);
|
|
806
896
|
const { prompt, toolOptions, errors } = parseImageGenOptions(text || '', resolveCatImageGenerationOptions());
|
|
807
897
|
|
|
898
|
+
if (isAiCommandPremiumOnly(commandName)) {
|
|
899
|
+
if (!(await isPremiumAllowed(senderJid))) {
|
|
900
|
+
await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
808
905
|
if (!process.env.OPENAI_API_KEY) {
|
|
809
906
|
logger.warn('handleCatImageCommand: OPENAI_API_KEY não configurada.');
|
|
810
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
|
+
});
|
|
811
916
|
return;
|
|
812
917
|
}
|
|
813
918
|
|
|
814
919
|
await reactToMessage(sock, remoteJid, messageInfo);
|
|
815
920
|
|
|
816
|
-
if (isAiCommandPremiumOnly(commandName)) {
|
|
817
|
-
if (!(await isPremiumAllowed(senderJid))) {
|
|
818
|
-
await sendPremiumOnly(sock, remoteJid, messageInfo, expirationMessage, { commandName });
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
921
|
const imageMedia = findImageMedia(messageInfo);
|
|
824
922
|
const imageResult = await buildImageDataUrl(imageMedia, senderJid);
|
|
825
923
|
if (imageResult.error === 'too_large') {
|
|
@@ -902,7 +1000,7 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
|
|
|
902
1000
|
|
|
903
1001
|
if (!imageBase64) {
|
|
904
1002
|
if (outputText) {
|
|
905
|
-
await sendAndStore(sock, remoteJid, { text:
|
|
1003
|
+
await sendAndStore(sock, remoteJid, { text: `${commandMessages.imageTextResponsePrefix}${outputText}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
906
1004
|
return;
|
|
907
1005
|
}
|
|
908
1006
|
|
|
@@ -918,7 +1016,7 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
|
|
|
918
1016
|
};
|
|
919
1017
|
const mimetype = mimeByFormat[outputFormat] || 'image/png';
|
|
920
1018
|
const imageBuffer = Buffer.from(imageBase64, 'base64');
|
|
921
|
-
const caption = outputText ?
|
|
1019
|
+
const caption = outputText ? `${commandMessages.imageTextResponsePrefix}${outputText}` : commandMessages.imageSuccessCaption;
|
|
922
1020
|
|
|
923
1021
|
await sendAndStore(sock, remoteJid, { image: imageBuffer, caption, mimetype }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
924
1022
|
} catch (error) {
|
|
@@ -931,6 +1029,14 @@ export async function handleCatImageCommand({ sock, remoteJid, messageInfo, expi
|
|
|
931
1029
|
},
|
|
932
1030
|
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
933
1031
|
);
|
|
1032
|
+
await notifyAdminAiError(sock, {
|
|
1033
|
+
commandName,
|
|
1034
|
+
stage: 'responses_create_image',
|
|
1035
|
+
remoteJid,
|
|
1036
|
+
senderJid,
|
|
1037
|
+
messageInfo,
|
|
1038
|
+
error,
|
|
1039
|
+
});
|
|
934
1040
|
}
|
|
935
1041
|
}
|
|
936
1042
|
|
|
@@ -938,6 +1044,7 @@ export async function handleCatPromptCommand({ sock, remoteJid, messageInfo, exp
|
|
|
938
1044
|
const commandName = 'catprompt';
|
|
939
1045
|
const commandMessages = resolveAiMessages(commandName);
|
|
940
1046
|
const promptMaxChars = resolveCatPromptMaxChars();
|
|
1047
|
+
const promptResetAliases = resolveCatPromptResetAliases();
|
|
941
1048
|
const promptText = text?.trim();
|
|
942
1049
|
if (!promptText) {
|
|
943
1050
|
await sendPromptUsage(sock, remoteJid, messageInfo, expirationMessage, commandPrefix);
|
|
@@ -951,8 +1058,8 @@ export async function handleCatPromptCommand({ sock, remoteJid, messageInfo, exp
|
|
|
951
1058
|
}
|
|
952
1059
|
}
|
|
953
1060
|
|
|
954
|
-
const lower = promptText
|
|
955
|
-
if (lower
|
|
1061
|
+
const lower = normalizeText(promptText);
|
|
1062
|
+
if (promptResetAliases.has(lower)) {
|
|
956
1063
|
await aiPromptStore.clearPrompt(senderJid);
|
|
957
1064
|
await sendAndStore(sock, remoteJid, { text: commandMessages.promptResetSuccess }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
958
1065
|
return;
|