@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logger from '#logger';
|
|
2
2
|
import { buildAnimeMenu, buildAiMenu, buildMediaMenu, buildMenuCaption, buildQuoteMenu, buildStatsMenu, buildStickerMenu, buildAdminMenu } from './common.js';
|
|
3
|
+
import { resolveDynamicMenuText } from './menuDynamicService.js';
|
|
3
4
|
import getImageBuffer from '../../utils/http/getImageBufferModule.js';
|
|
4
5
|
import { sendAndStore } from '../../services/messaging/messagePersistenceService.js';
|
|
5
6
|
|
|
@@ -11,10 +12,13 @@ const sanitizeLogValue = (value) =>
|
|
|
11
12
|
.trim();
|
|
12
13
|
|
|
13
14
|
const sendMenuImage = async (sock, remoteJid, messageInfo, expirationMessage, caption) => {
|
|
15
|
+
const safeCaption = String(caption || '').trim() || 'Menu indisponível no momento.';
|
|
14
16
|
const imageUrl = process.env[MENU_IMAGE_ENV];
|
|
15
17
|
if (!imageUrl) {
|
|
16
|
-
logger.
|
|
17
|
-
|
|
18
|
+
logger.warn('IMAGE_MENU environment variable not set. Sending plain-text menu fallback.', {
|
|
19
|
+
action: 'menu_image_env_missing',
|
|
20
|
+
});
|
|
21
|
+
await sendAndStore(sock, remoteJid, { text: safeCaption }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
18
22
|
return;
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -25,7 +29,7 @@ const sendMenuImage = async (sock, remoteJid, messageInfo, expirationMessage, ca
|
|
|
25
29
|
remoteJid,
|
|
26
30
|
{
|
|
27
31
|
image: imageBuffer,
|
|
28
|
-
caption,
|
|
32
|
+
caption: safeCaption,
|
|
29
33
|
},
|
|
30
34
|
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
31
35
|
);
|
|
@@ -33,11 +37,22 @@ const sendMenuImage = async (sock, remoteJid, messageInfo, expirationMessage, ca
|
|
|
33
37
|
logger.error('Error fetching menu image.', {
|
|
34
38
|
error: sanitizeLogValue(error?.message) || 'unknown_error',
|
|
35
39
|
});
|
|
36
|
-
await sendAndStore(sock, remoteJid, { text:
|
|
40
|
+
await sendAndStore(sock, remoteJid, { text: safeCaption }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
37
41
|
}
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
export async function handleMenuCommand(sock, remoteJid, messageInfo, expirationMessage, senderName, commandPrefix, args = []) {
|
|
45
|
+
const dynamicCaption = await resolveDynamicMenuText({
|
|
46
|
+
args,
|
|
47
|
+
senderName,
|
|
48
|
+
commandPrefix,
|
|
49
|
+
remoteJid,
|
|
50
|
+
});
|
|
51
|
+
if (dynamicCaption) {
|
|
52
|
+
await sendMenuImage(sock, remoteJid, messageInfo, expirationMessage, dynamicCaption.trim());
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
const category = args?.[0]?.toLowerCase();
|
|
42
57
|
const categoryMap = new Map([
|
|
43
58
|
['figurinhas', (prefix) => buildStickerMenu(prefix)],
|
|
@@ -61,6 +76,22 @@ export async function handleMenuCommand(sock, remoteJid, messageInfo, expiration
|
|
|
61
76
|
await sendMenuImage(sock, remoteJid, messageInfo, expirationMessage, caption);
|
|
62
77
|
}
|
|
63
78
|
|
|
64
|
-
export async function handleMenuAdmCommand(sock, remoteJid, messageInfo, expirationMessage, commandPrefix) {
|
|
79
|
+
export async function handleMenuAdmCommand(sock, remoteJid, messageInfo, expirationMessage, commandPrefix, args = []) {
|
|
80
|
+
const safeArgs = Array.isArray(args) ? args.map((value) => String(value || '').trim()).filter(Boolean) : [];
|
|
81
|
+
const dynamicArgs = safeArgs.length ? safeArgs : ['categoria', 'admin'];
|
|
82
|
+
const dynamicCaption = await resolveDynamicMenuText({
|
|
83
|
+
args: dynamicArgs,
|
|
84
|
+
senderName: 'admin',
|
|
85
|
+
commandPrefix,
|
|
86
|
+
remoteJid,
|
|
87
|
+
menuCommandName: 'menuadm',
|
|
88
|
+
categoryScopeKeys: ['admin'],
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (dynamicCaption) {
|
|
92
|
+
await sendMenuImage(sock, remoteJid, messageInfo, expirationMessage, dynamicCaption.trim());
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
65
96
|
await sendMenuImage(sock, remoteJid, messageInfo, expirationMessage, buildAdminMenu(commandPrefix).trim());
|
|
66
97
|
}
|
|
@@ -7,13 +7,18 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
7
7
|
- arquivo_base: `app/modules/playModule/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
|
|
|
14
14
|
- module: `playModule`
|
|
15
15
|
- source_files:
|
|
16
16
|
- playCommand.js
|
|
17
|
+
- playCommandHandlers.js
|
|
18
|
+
- playCommandCore.js
|
|
19
|
+
- playCommandMediaClient.js
|
|
20
|
+
- playCommandConstants.js
|
|
21
|
+
- playConfigRuntime.js
|
|
17
22
|
- total_commands: `2`
|
|
18
23
|
- total_enabled_commands: `2`
|
|
19
24
|
|
|
@@ -119,10 +124,10 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
119
124
|
- texto do comando e argumentos
|
|
120
125
|
- contexto da mensagem (citacao e mencoes, quando existir)
|
|
121
126
|
- link ou termo de busca enviado no comando
|
|
122
|
-
- metadados de
|
|
127
|
+
- metadados de busca/download retornados pelo provedor de midia
|
|
123
128
|
- informacoes de tamanho do arquivo para validacao de limite
|
|
124
129
|
- dependencias_externas:
|
|
125
|
-
-
|
|
130
|
+
- provedor de midia local (ytmp3 via API)
|
|
126
131
|
- ffmpeg
|
|
127
132
|
- ffprobe
|
|
128
133
|
- efeitos_colaterais:
|
|
@@ -244,10 +249,10 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
244
249
|
- texto do comando e argumentos
|
|
245
250
|
- contexto da mensagem (citacao e mencoes, quando existir)
|
|
246
251
|
- link ou termo de busca enviado no comando
|
|
247
|
-
- metadados de
|
|
252
|
+
- metadados de busca/download retornados pelo provedor de midia
|
|
248
253
|
- informacoes de tamanho e formato de video para validacao
|
|
249
254
|
- dependencias_externas:
|
|
250
|
-
-
|
|
255
|
+
- provedor de midia local (ytmp3 via API)
|
|
251
256
|
- ffmpeg
|
|
252
257
|
- ffprobe
|
|
253
258
|
- efeitos_colaterais:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": "2.0.0",
|
|
3
3
|
"module": "playModule",
|
|
4
4
|
"enabled": true,
|
|
5
|
-
"source_files": ["playCommand.js", "playCommandHandlers.js", "playCommandCore.js", "
|
|
5
|
+
"source_files": ["playCommand.js", "playCommandHandlers.js", "playCommandCore.js", "playCommandMediaClient.js", "playCommandConstants.js", "playConfigRuntime.js"],
|
|
6
6
|
"defaults": {
|
|
7
7
|
"inheritance_mode": "deep_merge_with_command_overrides",
|
|
8
8
|
"compatibility_mode": "legacy_and_v2_fields",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"janela_ms": null,
|
|
63
63
|
"escopo": "sem_rate_limit_explicito"
|
|
64
64
|
},
|
|
65
|
-
"dependencies": ["
|
|
66
|
-
"dependencias_externas": ["
|
|
65
|
+
"dependencies": ["provedor de midia local (ytmp3 via API)", "ffmpeg", "ffprobe"],
|
|
66
|
+
"dependencias_externas": ["provedor de midia local (ytmp3 via API)", "ffmpeg", "ffprobe"],
|
|
67
67
|
"responses": {
|
|
68
68
|
"success": "Comando executado com sucesso.",
|
|
69
69
|
"usage_error": "Formato de uso inválido. Consulte metodos_de_uso.",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"permissao_necessaria": "usuario comum",
|
|
153
153
|
"limite_de_uso": "arquivo ate PLAY_MAX_MB (padrao 100 MB)",
|
|
154
154
|
"local_de_uso": ["privado", "grupo"],
|
|
155
|
-
"informacoes_coletadas": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo
|
|
155
|
+
"informacoes_coletadas": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo provedor de midia", "informacoes de tamanho do arquivo para validacao de limite"],
|
|
156
156
|
"enabled": true,
|
|
157
157
|
"categoria": "midia",
|
|
158
158
|
"subcomandos": [],
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
"janela_ms": null,
|
|
180
180
|
"escopo": "sem_rate_limit_explicito"
|
|
181
181
|
},
|
|
182
|
-
"dependencias_externas": ["
|
|
182
|
+
"dependencias_externas": ["provedor de midia local (ytmp3 via API)", "ffmpeg", "ffprobe"],
|
|
183
183
|
"efeitos_colaterais": ["baixa mídia temporária", "envia áudio no chat"],
|
|
184
184
|
"respostas_padrao": {
|
|
185
185
|
"sucesso": "Comando executado com sucesso.",
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
},
|
|
229
229
|
"permission": "usuario comum",
|
|
230
230
|
"contexts": ["privado", "grupo"],
|
|
231
|
-
"collected_data": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo
|
|
231
|
+
"collected_data": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo provedor de midia", "informacoes de tamanho do arquivo para validacao de limite"],
|
|
232
232
|
"requirements": {
|
|
233
233
|
"require_group": false,
|
|
234
234
|
"require_group_admin": false,
|
|
@@ -290,7 +290,7 @@
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
},
|
|
293
|
-
"dependencies": ["
|
|
293
|
+
"dependencies": ["provedor de midia local (ytmp3 via API)", "ffmpeg", "ffprobe"],
|
|
294
294
|
"side_effects": ["baixa mídia temporária", "envia áudio no chat"],
|
|
295
295
|
"observability": {
|
|
296
296
|
"event_name": "command.executed",
|
|
@@ -357,6 +357,35 @@
|
|
|
357
357
|
"schema": "legacy_v1_and_v2",
|
|
358
358
|
"legacy_name": "tocar",
|
|
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": "Tocar reproduz/gera áudio a partir de um link do YouTube ou de um termo de busca. Disponível para todos (não requer premium).",
|
|
363
|
+
"quando_usar": ["Quando quiser ouvir o áudio de um vídeo do YouTube sem abrir o navegador.", "Quando você tem um link do YouTube e quer ouvir rapidamente no chat.", "Quando prefere buscar por um termo e ouvir o áudio resultante."],
|
|
364
|
+
"exemplos_reais": [
|
|
365
|
+
{
|
|
366
|
+
"situacao": "Usuário tem o link de um vídeo e quer ouvir o áudio.",
|
|
367
|
+
"comando": "<prefix>tocar https://youtube.com/watch?v=EXEMPLO",
|
|
368
|
+
"resposta_esperada": "Comando executado com sucesso.",
|
|
369
|
+
"variacao": "<prefix>tocar https://youtu.be/EXEMPLO."
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"situacao": "Usuário quer ouvir áudio a partir de um termo de busca.",
|
|
373
|
+
"comando": "<prefix>tocar melhor trilha sonora de cinema",
|
|
374
|
+
"resposta_esperada": "Comando executado com sucesso.",
|
|
375
|
+
"variacao": "<prefix>tocar <termo de busca>."
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"situacao": "Usuário envia o comando sem argumento ou com formato incorreto.",
|
|
379
|
+
"comando": "<prefix>tocar",
|
|
380
|
+
"resposta_esperada": "Formato de uso inválido. Consulte metodos_de_uso.",
|
|
381
|
+
"variacao": "<prefix>tocar <link>."
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
"resposta_esperada": ["Comando executado com sucesso.", "Formato de uso inválido. Consulte metodos_de_uso.", "Permissão insuficiente para executar este comando."],
|
|
385
|
+
"erros_comuns_usuario": ["Não fornecer argumento (link ou termo de busca).", "Link não é de vídeo YouTube válido.", "Formato do comando não segue <prefix>tocar <link> ou <prefix>tocar <termo>.", "Não estar logado com a conta Google quando solicitado.", "Conectividade ruim ou serviço temporariamente indisponível."],
|
|
386
|
+
"passos_se_der_erro": ["Verifique se o comando usa <prefix>tocar <link> ou <prefix>tocar <termo>.", "Copie o link direto do YouTube ou revise o termo de busca.", "Tente novamente em instantes.", "Caso persista, confirme que a conta Google está disponível e tente novamente.", "Se ainda falhar, informe o contexto (link ou termo usado) para suporte."],
|
|
387
|
+
"resumo_usuario_origem": "auto_ia_assistida",
|
|
388
|
+
"resumo_usuario_revisao_pendente": true
|
|
360
389
|
}
|
|
361
390
|
},
|
|
362
391
|
{
|
|
@@ -370,7 +399,7 @@
|
|
|
370
399
|
"permissao_necessaria": "usuario comum",
|
|
371
400
|
"limite_de_uso": "arquivo ate PLAY_MAX_MB (padrao 100 MB)",
|
|
372
401
|
"local_de_uso": ["privado", "grupo"],
|
|
373
|
-
"informacoes_coletadas": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo
|
|
402
|
+
"informacoes_coletadas": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo provedor de midia", "informacoes de tamanho e formato de video para validacao"],
|
|
374
403
|
"enabled": true,
|
|
375
404
|
"categoria": "midia",
|
|
376
405
|
"subcomandos": [],
|
|
@@ -397,7 +426,7 @@
|
|
|
397
426
|
"janela_ms": null,
|
|
398
427
|
"escopo": "sem_rate_limit_explicito"
|
|
399
428
|
},
|
|
400
|
-
"dependencias_externas": ["
|
|
429
|
+
"dependencias_externas": ["provedor de midia local (ytmp3 via API)", "ffmpeg", "ffprobe"],
|
|
401
430
|
"efeitos_colaterais": ["baixa mídia temporária", "envia vídeo no chat"],
|
|
402
431
|
"respostas_padrao": {
|
|
403
432
|
"sucesso": "Comando executado com sucesso.",
|
|
@@ -446,7 +475,7 @@
|
|
|
446
475
|
},
|
|
447
476
|
"permission": "usuario comum",
|
|
448
477
|
"contexts": ["privado", "grupo"],
|
|
449
|
-
"collected_data": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo
|
|
478
|
+
"collected_data": ["identificador do chat (remoteJid)", "identificador do remetente (senderJid)", "texto do comando e argumentos", "contexto da mensagem (citacao e mencoes, quando existir)", "link ou termo de busca enviado no comando", "metadados de busca/download retornados pelo provedor de midia", "informacoes de tamanho e formato de video para validacao"],
|
|
450
479
|
"requirements": {
|
|
451
480
|
"require_group": false,
|
|
452
481
|
"require_group_admin": false,
|
|
@@ -508,7 +537,7 @@
|
|
|
508
537
|
}
|
|
509
538
|
}
|
|
510
539
|
},
|
|
511
|
-
"dependencies": ["
|
|
540
|
+
"dependencies": ["provedor de midia local (ytmp3 via API)", "ffmpeg", "ffprobe"],
|
|
512
541
|
"side_effects": ["baixa mídia temporária", "envia vídeo no chat"],
|
|
513
542
|
"observability": {
|
|
514
543
|
"event_name": "command.executed",
|
|
@@ -575,6 +604,35 @@
|
|
|
575
604
|
"schema": "legacy_v1_and_v2",
|
|
576
605
|
"legacy_name": "tocarvideo",
|
|
577
606
|
"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"]
|
|
607
|
+
},
|
|
608
|
+
"user_experience": {
|
|
609
|
+
"resumo_usuario": "Baixe ou reproduza vídeos do YouTube a partir de um link ou de termos de busca. Requer login Google e assinatura Premium. O conteúdo fica disponível no chat.",
|
|
610
|
+
"quando_usar": ["Quando você tem um link do YouTube que quer baixar para assistir no chat.", "Quando prefere buscar um vídeo pelo título/termo de busca em vez de colar o link.", "Antes de usar, confirme que você tem acesso à assinatura Premium."],
|
|
611
|
+
"exemplos_reais": [
|
|
612
|
+
{
|
|
613
|
+
"situacao": "Baixar vídeo a partir de um link do YouTube.",
|
|
614
|
+
"comando": "<prefix>tocarvideo https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
615
|
+
"resposta_esperada": "Comando executado com sucesso.",
|
|
616
|
+
"variacao": "uso com link direto (alias: playvid)."
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
"situacao": "Buscar vídeo pelo termo de busca.",
|
|
620
|
+
"comando": "<prefix>tocarvideo melhor tutorial de edição de fotos",
|
|
621
|
+
"resposta_esperada": "Comando executado com sucesso.",
|
|
622
|
+
"variacao": "uso com termo de busca."
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
"situacao": "Tentar usar sem Premium.",
|
|
626
|
+
"comando": "<prefix>tocarvideo https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
627
|
+
"resposta_esperada": "Permissão insuficiente para executar este comando.",
|
|
628
|
+
"variacao": "sem assinatura Premium."
|
|
629
|
+
}
|
|
630
|
+
],
|
|
631
|
+
"resposta_esperada": ["Comando executado com sucesso.", "Formato de uso inválido. Consulte metodos_de_uso.", "Permissão insuficiente para executar este comando."],
|
|
632
|
+
"erros_comuns_usuario": ["Esquecer de incluir o link ou termo de busca.", "Enviar link que não é do YouTube.", "Não estar logado com uma conta Google.", "Tentar usar o recurso sem a assinatura Premium."],
|
|
633
|
+
"passos_se_der_erro": ["Verifique se você está usando o formato correto: <prefix>tocarvideo <link_do_youtube> ou <prefix>tocarvideo <termo>.", "Certifique-se de estar logado com uma conta Google válida.", "Confirme que você possui assinatura Premium ativa para este comando.", "Se o erro persistir, tente novamente após confirmar as informações ou entre em contato com o suporte com a captura de tela do erro."],
|
|
634
|
+
"resumo_usuario_origem": "auto_ia_assistida",
|
|
635
|
+
"resumo_usuario_revisao_pendente": true
|
|
578
636
|
}
|
|
579
637
|
}
|
|
580
638
|
],
|
|
@@ -594,7 +652,7 @@
|
|
|
594
652
|
"admin_alert_dedupe_window_ms": 120000
|
|
595
653
|
},
|
|
596
654
|
"opcoes_execucao": {
|
|
597
|
-
"
|
|
655
|
+
"media_provider_args": [],
|
|
598
656
|
"estrategias_formato": {
|
|
599
657
|
"audio": ["bestaudio/best", "best"],
|
|
600
658
|
"video": ["bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/best", "bestvideo*+bestaudio/best", "best"],
|
|
@@ -619,13 +677,13 @@
|
|
|
619
677
|
"ready_title_video": "🎬 Vídeo pronto!",
|
|
620
678
|
"video_fallback_to_audio": "⚠️ Este link retornou somente áudio. Enviando no formato de áudio.",
|
|
621
679
|
"anti_bot_with_cookies": "YouTube solicitou verificação anti-bot. Atualize o arquivo .secrets/cookies.txt e tente novamente.",
|
|
622
|
-
"anti_bot_with_browser_profile": "YouTube solicitou verificação anti-bot
|
|
623
|
-
"anti_bot_without_cookies": "YouTube solicitou verificação anti-bot
|
|
680
|
+
"anti_bot_with_browser_profile": "YouTube solicitou verificação anti-bot no provedor de mídia. Verifique suas credenciais e tente novamente.",
|
|
681
|
+
"anti_bot_without_cookies": "YouTube solicitou verificação anti-bot no provedor de mídia. Tente novamente em alguns minutos.",
|
|
624
682
|
"usage_fallback_audio": "🎵 Uso: <prefix>play <link do YouTube ou termo de busca>",
|
|
625
683
|
"usage_fallback_video": "🎬 Uso: <prefix>playvid <link do YouTube ou termo de busca>",
|
|
626
684
|
"invalid_media_type": "Tipo de mídia inválido.",
|
|
627
|
-
"
|
|
628
|
-
"
|
|
685
|
+
"provider_error_generic": "Falha ao processar mídia no provedor.",
|
|
686
|
+
"provider_timeout_generic": "Timeout ao processar mídia no provedor.",
|
|
629
687
|
"binary_exec_failed": "Falha ao executar <command>.",
|
|
630
688
|
"search_invalid_input": "Você precisa informar um link do YouTube ou termo de busca.",
|
|
631
689
|
"search_not_found": "Nenhum resultado encontrado para a busca.",
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { DEFAULT_YTDLP_BINARY_PATH } from './local/ytDlpInstaller.js';
|
|
5
4
|
|
|
6
5
|
export const DEFAULT_COMMAND_PREFIX = process.env.COMMAND_PREFIX || '/';
|
|
7
6
|
|
|
8
7
|
export const DEFAULT_TIMEOUT_MS = Number.parseInt(process.env.PLAY_TIMEOUT_MS || '900000', 10);
|
|
9
8
|
export const DOWNLOAD_TIMEOUT_MS = Number.parseInt(process.env.PLAY_DOWNLOAD_TIMEOUT_MS || '1800000', 10);
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
12
|
-
export const
|
|
9
|
+
export const MEDIA_INFO_TIMEOUT_MS = Number.parseInt(process.env.PLAY_MEDIA_INFO_TIMEOUT_MS || '120000', 10);
|
|
10
|
+
export const PLAY_YTMP3_ENABLED = String(process.env.PLAY_YTMP3_ENABLED || 'true').toLowerCase() !== 'false';
|
|
11
|
+
export const PLAY_YTMP3_API_BASE_URL = (process.env.PLAY_YTMP3_API_BASE_URL || 'https://hub.ytconvert.org').trim();
|
|
12
|
+
export const PLAY_YTMP3_API_DOWNLOAD_PATH = (process.env.PLAY_YTMP3_API_DOWNLOAD_PATH || '/api/download').trim() || '/api/download';
|
|
13
|
+
export const PLAY_YTMP3_POLL_INTERVAL_MS = Math.max(500, Number.parseInt(process.env.PLAY_YTMP3_POLL_INTERVAL_MS || '2000', 10) || 2000);
|
|
14
|
+
export const PLAY_YTMP3_SEARCH_BASE_URL = (process.env.PLAY_YTMP3_SEARCH_BASE_URL || 'https://yt-meta.ytconvert.org').trim();
|
|
15
|
+
export const PLAY_YTMP3_SEARCH_PATH = (process.env.PLAY_YTMP3_SEARCH_PATH || '/search').trim() || '/search';
|
|
16
|
+
export const PLAY_YTMP3_VIDEO_DEFAULT_QUALITY = (process.env.PLAY_YTMP3_VIDEO_DEFAULT_QUALITY || '720').trim();
|
|
13
17
|
|
|
14
18
|
const PLAY_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
15
19
|
export const PLAY_LOCAL_DIR = path.join(PLAY_MODULE_DIR, 'local');
|
|
16
20
|
export const PLAY_DOWNLOADS_DIR = path.join(PLAY_LOCAL_DIR, 'downloads');
|
|
17
|
-
export const PROJECT_ROOT_DIR = path.resolve(PLAY_MODULE_DIR, '../../..');
|
|
18
|
-
export const DEFAULT_COOKIES_PATH = path.join(PROJECT_ROOT_DIR, '.secrets', 'cookies.txt');
|
|
19
21
|
export const MAX_SEARCH_RESULTS = Math.min(10, Math.max(1, Number.parseInt(process.env.PLAY_SEARCH_RESULTS || '5', 10)));
|
|
20
22
|
|
|
21
23
|
const MAX_MEDIA_MB = Number.parseInt(process.env.PLAY_MAX_MB || '100', 10);
|
|
@@ -42,8 +44,12 @@ export const YTDLS_ENDPOINTS = {
|
|
|
42
44
|
search: 'local:search',
|
|
43
45
|
queueStatus: 'local:queue-status',
|
|
44
46
|
download: 'local:download',
|
|
45
|
-
install: 'local:install',
|
|
46
47
|
thumbnail: 'thumbnail',
|
|
48
|
+
ytmp3Create: 'ytmp3:create',
|
|
49
|
+
ytmp3Poll: 'ytmp3:poll',
|
|
50
|
+
ytmp3Download: 'ytmp3:download',
|
|
51
|
+
ytmp3Search: 'ytmp3:search',
|
|
52
|
+
ytmp3Metadata: 'ytmp3:metadata',
|
|
47
53
|
};
|
|
48
54
|
|
|
49
55
|
export const ERROR_CODES = {
|
|
@@ -5,7 +5,7 @@ import { sendAndStore } from '../../services/messaging/messagePersistenceService
|
|
|
5
5
|
import { getAdminJid } from '../../config/index.js';
|
|
6
6
|
import { getPlayOperationalLimits, getPlayText, getPlayUsageFallbackText, getPlayUsageText, getPlayWaitText } from './playConfigRuntime.js';
|
|
7
7
|
import { DEFAULT_COMMAND_PREFIX, ERROR_CODES, KNOWN_ERROR_CODES, TYPE_CONFIG, YTDLS_ENDPOINTS } from './playCommandConstants.js';
|
|
8
|
-
import { createError, withErrorMeta, normalizePlayError, truncateText,
|
|
8
|
+
import { createError, withErrorMeta, normalizePlayError, truncateText, playMediaClient, formatters, fileUtils, isYouTubeBotCheckCause, buildYouTubeBotCheckUserMessage } from './playCommandMediaClient.js';
|
|
9
9
|
|
|
10
10
|
const adminJid = getAdminJid();
|
|
11
11
|
const adminAlertDedupCache = new Map();
|
|
@@ -113,7 +113,7 @@ const processPlayRequest = async ({ sock, remoteJid, messageInfo, expirationMess
|
|
|
113
113
|
let filePath = null;
|
|
114
114
|
|
|
115
115
|
try {
|
|
116
|
-
const candidateLinks = await
|
|
116
|
+
const candidateLinks = await playMediaClient.resolveYoutubeCandidates(text);
|
|
117
117
|
await sendAndStore(sock, remoteJid, { text: getPlayWaitText(type) || config.waitText }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
118
118
|
|
|
119
119
|
let downloadResult = null;
|
|
@@ -125,13 +125,12 @@ const processPlayRequest = async ({ sock, remoteJid, messageInfo, expirationMess
|
|
|
125
125
|
const candidateLink = candidateLinks[index];
|
|
126
126
|
selectedLink = candidateLink;
|
|
127
127
|
try {
|
|
128
|
-
[downloadResult, videoInfo] = await Promise.all([
|
|
128
|
+
[downloadResult, videoInfo] = await Promise.all([playMediaClient.requestDownloadToFile(candidateLink, type, requestId), playMediaClient.fetchVideoInfo(candidateLink, text)]);
|
|
129
129
|
lastDownloadError = null;
|
|
130
130
|
break;
|
|
131
131
|
} catch (error) {
|
|
132
132
|
lastDownloadError = error;
|
|
133
133
|
if (isYouTubeBotCheckCause(error)) {
|
|
134
|
-
const cookiesPath = ytdlsClient.resolveYtDlpCookiesPath();
|
|
135
134
|
logger.warn('Play download: bloqueio anti-bot detectado; abortando novas tentativas de candidato.', {
|
|
136
135
|
requestId,
|
|
137
136
|
remoteJid,
|
|
@@ -139,7 +138,6 @@ const processPlayRequest = async ({ sock, remoteJid, messageInfo, expirationMess
|
|
|
139
138
|
endpoint: error?.meta?.endpoint || YTDLS_ENDPOINTS.download,
|
|
140
139
|
attempt: index + 1,
|
|
141
140
|
candidateLink,
|
|
142
|
-
cookiesPath: cookiesPath || null,
|
|
143
141
|
cause: truncateText(error?.meta?.cause || error?.message || ''),
|
|
144
142
|
});
|
|
145
143
|
throw withErrorMeta(createError(ERROR_CODES.API, buildYouTubeBotCheckUserMessage()), {
|
|
@@ -197,7 +195,7 @@ const processPlayRequest = async ({ sock, remoteJid, messageInfo, expirationMess
|
|
|
197
195
|
|
|
198
196
|
if (thumbUrl) {
|
|
199
197
|
try {
|
|
200
|
-
thumbBuffer = await
|
|
198
|
+
thumbBuffer = await playMediaClient.fetchThumbnailBuffer(thumbUrl);
|
|
201
199
|
} catch (error) {
|
|
202
200
|
logger.warn('Falha ao baixar thumbnail.', {
|
|
203
201
|
requestId,
|