@omnizap-system/omnizap 2.6.0 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +58 -13
- package/.github/workflows/ci.yml +5 -5
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/db-migration-check.yml +2 -2
- package/.github/workflows/dependency-review.yml +1 -1
- package/.github/workflows/deploy.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security-attest-provenance.yml +2 -2
- package/.github/workflows/security-gitleaks.yml +13 -4
- package/.github/workflows/security-runner-hardening.yml +2 -2
- package/.github/workflows/security-scorecard.yml +1 -1
- package/.github/workflows/security-zap-baseline.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +2 -1
- package/.github/workflows/security-zizmor.yml +1 -1
- package/.github/workflows/wiki-sync.yml +1 -1
- package/.gitleaksignore +9 -0
- package/CODE_OF_CONDUCT.md +2 -2
- package/GEMINI.md +64 -0
- package/README.md +52 -82
- package/SECURITY.md +1 -1
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +230 -58
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +145 -4
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +660 -158
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +93 -13
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +858 -15
- package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +135 -27
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +140 -12
- package/app/modules/playModule/playCommand.js +1 -1417
- package/app/modules/playModule/playCommandConstants.js +80 -0
- package/app/modules/playModule/playCommandCore.js +361 -0
- package/app/modules/playModule/playCommandHandlers.js +41 -0
- package/app/modules/playModule/playCommandMediaClient.js +1872 -0
- package/app/modules/playModule/playConfigRuntime.js +245 -4
- package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/quoteModule/quoteCommand.js +3 -2
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/statsModule/rankingCommon.js +5 -4
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/addStickerMetadata.js +4 -3
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerModule/stickerCommand.js +1 -1
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
- package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
- package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
- package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/systemMetricsModule/pingCommand.js +6 -5
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/tiktokCommand.js +2 -1
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/userModule/userCommand.js +72 -23
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/conversationRouterService.js +4 -3
- package/app/services/ai/geminiService.js +132 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/globalModuleAiHelpService.js +3 -2
- package/app/services/ai/messageCommandExecutionService.js +2 -1
- package/app/services/ai/moduleAiHelpCoreService.js +45 -14
- package/app/services/ai/moduleToolExecutorService.js +3 -2
- package/app/services/ai/moduleToolRegistryService.js +2 -1
- package/app/services/ai/toolCandidateSelectorService.js +6 -5
- package/app/services/auth/googleWebLinkService.js +3 -2
- package/app/services/auth/whatsappLoginLinkService.js +3 -2
- package/app/services/external/pokeApiService.js +4 -3
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +57 -26
- package/app/services/infra/featureFlagService.js +2 -1
- package/app/services/messaging/captchaService.js +3 -2
- package/app/services/messaging/newsBroadcastService.js +846 -29
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/services/sticker/stickerFocusService.js +11 -10
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/conversationSessionStore.js +7 -6
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/app/workers/aiLearningWorker.js +6 -5
- package/app/workers/commandConfigEnrichmentWorker.js +4 -3
- package/database/index.js +14 -8
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/docs/wiki/Home.md +1 -1
- package/ecosystem.prod.config.cjs +32 -12
- package/index.js +57 -23
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +20 -6
- package/public/apple-touch-icon.png +0 -0
- package/public/comandos/commands-catalog.json +2853 -3326
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/js/apps/apiDocsApp.js +3 -2
- package/public/js/apps/commandsReactApp.js +280 -99
- package/public/js/apps/createPackApp.js +11 -10
- package/public/js/apps/homeReactApp.js +181 -130
- package/public/js/apps/loginReactApp.js +1 -1
- package/public/js/apps/stickersApp.js +263 -110
- package/public/js/apps/termsReactApp.js +73 -24
- package/public/js/apps/userApp.js +4 -3
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +355 -280
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/api-docs.html +1 -1
- package/public/pages/aup.html +2 -2
- package/public/pages/dpa.html +3 -3
- package/public/pages/licenca.html +4 -4
- package/public/pages/login.html +1 -1
- package/public/pages/notice-and-takedown.html +2 -2
- package/public/pages/politica-de-privacidade.html +6 -6
- package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
- package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
- package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
- package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
- package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
- package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
- package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
- package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
- package/public/pages/stickers-admin.html +1 -1
- package/public/pages/stickers-create.html +1 -1
- package/public/pages/stickers.html +6 -6
- package/public/pages/suboperadores.html +2 -2
- package/public/pages/termos-de-uso-texto-integral.html +6 -6
- package/public/pages/termos-de-uso.html +3 -3
- package/public/pages/user-password-reset.html +4 -5
- package/public/pages/user-systemadm.html +9 -463
- package/public/pages/user.html +2 -2
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +11 -1
- package/scripts/email-broadcast-terms-update.mjs +2 -1
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +166 -2
- package/scripts/generate-module-agents.mjs +2 -1
- package/scripts/generate-seo-satellite-pages.mjs +5 -4
- package/scripts/github-deploy-notify.mjs +2 -1
- package/scripts/github-release-notify.mjs +25 -10
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/release.sh +2 -19
- package/scripts/security-smoketest.mjs +6 -5
- package/scripts/security-web-surface-check.mjs +218 -0
- package/scripts/sticker-catalog-loadtest.mjs +5 -4
- package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
- package/server/auth/jwt/webJwtService.js +1 -1
- package/server/auth/stickerCatalogAuthContext.js +2 -1
- package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
- package/server/auth/userPassword/userPasswordAuthService.js +2 -1
- package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
- package/server/auth/webAccount/webAccountHandlers.js +9 -10
- package/server/controllers/admin/adminPanelHandlers.js +267 -16
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
- package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
- package/server/controllers/sticker/stickerCatalogController.js +23 -36
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/githubController.js +3 -2
- package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/system/systemMetricsController.js +2 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +5 -3
- package/server/http/httpServer.js +11 -6
- package/server/middleware/rateLimit.js +2 -1
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/time/timeModule.js +135 -0
- package/utils/time/timeModule.test.js +65 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +7 -1
- package/public/assets/images/brand-icon-192.png +0 -0
- package/scripts/sync-readme-snapshot.mjs +0 -133
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import { executeQuery, TABLES } from '../../../database/index.js';
|
|
2
3
|
import { getJidUser, getProfilePicBuffer, normalizeJid } from '../../config/index.js';
|
|
3
4
|
import { isUserAdmin } from '../../config/index.js';
|
|
@@ -37,6 +38,44 @@ const SOCIAL_DST_EXPR = `JSON_UNQUOTE(
|
|
|
37
38
|
*/
|
|
38
39
|
const buildUsageText = (commandPrefix = DEFAULT_COMMAND_PREFIX) => getUserUsageText('user', { commandPrefix }) || ['Formato de uso:', `${commandPrefix}user perfil <id|telefone>`, '', 'Dica:', '• Você pode mencionar alguém.', '• Ou responder a mensagem do usuário desejado.'].join('\n');
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Extrai metadados do remetente considerando payload novo do pipeline/socket.
|
|
43
|
+
* @param {object} params
|
|
44
|
+
* @param {object} [params.messageInfo]
|
|
45
|
+
* @param {string|null} [params.senderJid]
|
|
46
|
+
* @param {string|object|null} [params.senderIdentity]
|
|
47
|
+
* @returns {string|object|null}
|
|
48
|
+
*/
|
|
49
|
+
const resolveSenderSource = ({ messageInfo, senderJid, senderIdentity }) => {
|
|
50
|
+
const key = messageInfo?.key || {};
|
|
51
|
+
|
|
52
|
+
if (senderIdentity && typeof senderIdentity === 'object') {
|
|
53
|
+
return {
|
|
54
|
+
participant: senderIdentity.participant || key.participant || null,
|
|
55
|
+
participantAlt: senderIdentity.participantAlt || key.participantAlt || key.remoteJidAlt || null,
|
|
56
|
+
jid: senderIdentity.jid || senderJid || null,
|
|
57
|
+
remoteJid: key.remoteJid || null,
|
|
58
|
+
remoteJidAlt: key.remoteJidAlt || null,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
participant: key.participant || null,
|
|
64
|
+
participantAlt: key.participantAlt || key.remoteJidAlt || null,
|
|
65
|
+
jid: senderJid || (typeof senderIdentity === 'string' ? senderIdentity : null),
|
|
66
|
+
remoteJid: key.remoteJid || null,
|
|
67
|
+
remoteJidAlt: key.remoteJidAlt || null,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const hasResolvableIdentity = (value) => {
|
|
72
|
+
if (!value) return false;
|
|
73
|
+
if (typeof value === 'string') return Boolean(value.trim());
|
|
74
|
+
if (typeof value !== 'object') return false;
|
|
75
|
+
|
|
76
|
+
return Boolean(value?.jid || value?.lid || value?.participant || value?.participantAlt || value?.remoteJid || value?.remoteJidAlt || value?.id);
|
|
77
|
+
};
|
|
78
|
+
|
|
40
79
|
/**
|
|
41
80
|
* Extrai o `contextInfo` da mensagem, incluindo estruturas aninhadas.
|
|
42
81
|
* @param {object} messageInfo Estrutura da mensagem recebida pelo bot.
|
|
@@ -90,24 +129,34 @@ const parseTargetArgument = (rawValue) => {
|
|
|
90
129
|
* Define qual usuário será usado como alvo (menção, argumento, reply ou remetente).
|
|
91
130
|
* @param {object} messageInfo Mensagem usada para inferir contexto.
|
|
92
131
|
* @param {string|null} senderJid JID do remetente do comando.
|
|
132
|
+
* @param {string|object|null} senderIdentity Identidade recebida do pipeline (participant/participantAlt/jid).
|
|
93
133
|
* @param {string} targetArg Argumento explícito passado no comando.
|
|
94
134
|
* @returns {{ source: string | object | null, invalidExplicitTarget: boolean }} Fonte escolhida e sinalizador de argumento inválido.
|
|
95
135
|
*/
|
|
96
|
-
const resolveCandidateTarget = (messageInfo, senderJid, targetArg) => {
|
|
136
|
+
const resolveCandidateTarget = (messageInfo, senderJid, senderIdentity, targetArg) => {
|
|
97
137
|
const contextInfo = getContextInfo(messageInfo);
|
|
98
138
|
const mentioned = Array.isArray(contextInfo?.mentionedJid) ? contextInfo.mentionedJid.find(Boolean) || null : null;
|
|
99
139
|
const parsedTarget = parseTargetArgument(targetArg);
|
|
140
|
+
const quotedMessageKey = contextInfo?.quotedMessageKey && typeof contextInfo.quotedMessageKey === 'object' ? contextInfo.quotedMessageKey : null;
|
|
100
141
|
const repliedSource =
|
|
101
|
-
contextInfo?.participant || contextInfo?.participantAlt
|
|
142
|
+
contextInfo?.participant || contextInfo?.participantAlt || quotedMessageKey?.participant || quotedMessageKey?.participantAlt
|
|
102
143
|
? {
|
|
103
|
-
participant: contextInfo
|
|
104
|
-
participantAlt: contextInfo
|
|
144
|
+
participant: contextInfo?.participant || quotedMessageKey?.participant || null,
|
|
145
|
+
participantAlt: contextInfo?.participantAlt || quotedMessageKey?.participantAlt || null,
|
|
146
|
+
remoteJid: quotedMessageKey?.remoteJid || null,
|
|
147
|
+
remoteJidAlt: quotedMessageKey?.remoteJidAlt || null,
|
|
105
148
|
}
|
|
106
149
|
: null;
|
|
150
|
+
const senderSource = resolveSenderSource({
|
|
151
|
+
messageInfo,
|
|
152
|
+
senderJid,
|
|
153
|
+
senderIdentity,
|
|
154
|
+
});
|
|
107
155
|
const hasContextTarget = Boolean(mentioned || repliedSource);
|
|
156
|
+
const normalizedSenderSource = hasResolvableIdentity(senderSource) ? senderSource : senderJid || null;
|
|
108
157
|
|
|
109
158
|
return {
|
|
110
|
-
source: mentioned || parsedTarget.jid || repliedSource ||
|
|
159
|
+
source: mentioned || parsedTarget.jid || repliedSource || normalizedSenderSource || null,
|
|
111
160
|
invalidExplicitTarget: parsedTarget.invalid && !hasContextTarget,
|
|
112
161
|
};
|
|
113
162
|
};
|
|
@@ -242,10 +291,10 @@ const formatPercent = (value, total) => {
|
|
|
242
291
|
/**
|
|
243
292
|
* Calcula a diferença inteira em dias entre dois timestamps.
|
|
244
293
|
* @param {number} fromMs Timestamp inicial em milissegundos.
|
|
245
|
-
* @param {number} [toMs=
|
|
294
|
+
* @param {number} [toMs=__timeNowMs()] Timestamp final em milissegundos.
|
|
246
295
|
* @returns {number} Quantidade de dias inteiros.
|
|
247
296
|
*/
|
|
248
|
-
const toIntegerDays = (fromMs, toMs =
|
|
297
|
+
const toIntegerDays = (fromMs, toMs = __timeNowMs()) => {
|
|
249
298
|
if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || toMs < fromMs) return 0;
|
|
250
299
|
return Math.floor((toMs - fromMs) / DAY_MS);
|
|
251
300
|
};
|
|
@@ -873,7 +922,7 @@ const hasRecentInteraction = (lastMessage) => {
|
|
|
873
922
|
const parsed = lastMessage instanceof Date ? lastMessage.getTime() : new Date(lastMessage).getTime();
|
|
874
923
|
if (!Number.isFinite(parsed)) return false;
|
|
875
924
|
const maxAgeMs = ACTIVE_DAYS_WINDOW * 24 * 60 * 60 * 1000;
|
|
876
|
-
return
|
|
925
|
+
return __timeNowMs() - parsed <= maxAgeMs;
|
|
877
926
|
};
|
|
878
927
|
|
|
879
928
|
/**
|
|
@@ -901,7 +950,7 @@ const isTargetBlocked = async (targetIds) => {
|
|
|
901
950
|
const formatTempoDeCasa = (firstMessage) => {
|
|
902
951
|
const firstMs = toMillis(firstMessage);
|
|
903
952
|
if (!Number.isFinite(firstMs)) return 'N/D';
|
|
904
|
-
const days = toIntegerDays(firstMs,
|
|
953
|
+
const days = toIntegerDays(firstMs, __timeNowMs());
|
|
905
954
|
return `${days} dia(s)`;
|
|
906
955
|
};
|
|
907
956
|
|
|
@@ -913,7 +962,7 @@ const formatTempoDeCasa = (firstMessage) => {
|
|
|
913
962
|
const formatDaysSinceLastMessage = (lastMessage) => {
|
|
914
963
|
const lastMs = toMillis(lastMessage);
|
|
915
964
|
if (!Number.isFinite(lastMs)) return 'N/D';
|
|
916
|
-
return `${toIntegerDays(lastMs,
|
|
965
|
+
return `${toIntegerDays(lastMs, __timeNowMs())} dia(s)`;
|
|
917
966
|
};
|
|
918
967
|
|
|
919
968
|
/**
|
|
@@ -1016,20 +1065,19 @@ const resolveMentionJid = (ids = []) => ids.find((id) => isWhatsAppUserId(id)) |
|
|
|
1016
1065
|
* @param {object} params.messageInfo Mensagem original usada como contexto.
|
|
1017
1066
|
* @param {number|undefined} params.expirationMessage Configuração de expiração de mensagem.
|
|
1018
1067
|
* @param {string} params.senderJid JID de quem executou o comando.
|
|
1068
|
+
* @param {string|object|null} [params.senderIdentity=null] Identidade enriquecida do remetente (participant/participantAlt/jid).
|
|
1019
1069
|
* @param {string[]} [params.args=[]] Argumentos recebidos após o comando.
|
|
1020
1070
|
* @param {boolean} params.isGroupMessage Indica se o contexto é grupo.
|
|
1021
1071
|
* @param {string} [params.commandPrefix=DEFAULT_COMMAND_PREFIX] Prefixo de comandos.
|
|
1022
1072
|
* @returns {Promise<void>} Finaliza após responder ao usuário.
|
|
1023
1073
|
*/
|
|
1024
|
-
export async function handleUserCommand({ sock, remoteJid, messageInfo, expirationMessage, senderJid, args = [], isGroupMessage, commandPrefix = DEFAULT_COMMAND_PREFIX }) {
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const explicitTargetArg = args.slice(1).join(' ').trim();
|
|
1032
|
-
const { source, invalidExplicitTarget } = resolveCandidateTarget(messageInfo, senderJid, explicitTargetArg);
|
|
1074
|
+
export async function handleUserCommand({ sock, remoteJid, messageInfo, expirationMessage, senderJid, senderIdentity = null, args = [], isGroupMessage, commandPrefix = DEFAULT_COMMAND_PREFIX }) {
|
|
1075
|
+
const firstArg = String(args?.[0] || '')
|
|
1076
|
+
.trim()
|
|
1077
|
+
.toLowerCase();
|
|
1078
|
+
const hasExplicitSubcommand = firstArg === 'perfil' || firstArg === 'profile';
|
|
1079
|
+
const explicitTargetArg = hasExplicitSubcommand ? args.slice(1).join(' ').trim() : args.join(' ').trim();
|
|
1080
|
+
const { source, invalidExplicitTarget } = resolveCandidateTarget(messageInfo, senderJid, senderIdentity, explicitTargetArg);
|
|
1033
1081
|
if (invalidExplicitTarget) {
|
|
1034
1082
|
await sendAndStore(sock, remoteJid, { text: `❌ ID ou telefone inválido.\n\n${buildUsageText(commandPrefix)}` }, { quoted: messageInfo, ephemeralExpiration: expirationMessage });
|
|
1035
1083
|
return;
|
|
@@ -1044,11 +1092,12 @@ export async function handleUserCommand({ sock, remoteJid, messageInfo, expirati
|
|
|
1044
1092
|
const senderIds = await resolveSenderIdsForTarget(canonicalTarget);
|
|
1045
1093
|
const normalizedTargetIds = Array.from(new Set([canonicalTarget, ...senderIds].map((value) => normalizeJid(value) || value).filter(Boolean)));
|
|
1046
1094
|
const mentionJid = resolveMentionJid(normalizedTargetIds);
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1095
|
+
const senderSource = resolveSenderSource({
|
|
1096
|
+
messageInfo,
|
|
1097
|
+
senderJid,
|
|
1098
|
+
senderIdentity,
|
|
1051
1099
|
});
|
|
1100
|
+
const senderCanonical = await resolveCanonicalTarget(senderSource);
|
|
1052
1101
|
const rankingTargetId = mentionJid || canonicalTarget;
|
|
1053
1102
|
|
|
1054
1103
|
const [stats, ranking, latestPushName, premiumUsers, blocked, groupAdmin] = await Promise.all([fetchUserStats({ canonicalId: rankingTargetId, senderIds: normalizedTargetIds }), fetchUserRanking(rankingTargetId), fetchLatestPushName(normalizedTargetIds), premiumUserStore.getPremiumUsers(), isTargetBlocked(normalizedTargetIds), isGroupMessage ? isUserAdmin(remoteJid, mentionJid || canonicalTarget) : Promise.resolve(false)]);
|
|
@@ -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/waifuPicsModule/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
|
|
|
@@ -90,9 +90,9 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
90
90
|
- <prefix>wp neko
|
|
91
91
|
- mensagens_uso (variantes):
|
|
92
92
|
- default:
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
-
|
|
93
|
+
- 🖼️ _Como usar:_ <prefix>waifu <categoria>
|
|
94
|
+
- Exemplos rápidos: <prefix>waifu neko | <prefix>wp hug
|
|
95
|
+
- Quer ver todas as categorias disponíveis? Use <prefix>waifuajuda.
|
|
96
96
|
- subcomandos:
|
|
97
97
|
- (nenhum)
|
|
98
98
|
- argumentos:
|
|
@@ -125,12 +125,22 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
125
125
|
- consulta API externa
|
|
126
126
|
- envia imagem no chat
|
|
127
127
|
- respostas_padrao:
|
|
128
|
-
- success:
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
- success: ✅ Pedido recebido! Aqui vai sua imagem.
|
|
129
|
+
Dica: você pode pedir outra categoria no próximo comando (ex.: <prefix>waifu neko).
|
|
130
|
+
- usage_error: ❗ Não entendi o formato do comando.
|
|
131
|
+
Exemplos:
|
|
132
|
+
• <prefix>waifu neko
|
|
133
|
+
• <prefix>waifunsfw waifu
|
|
134
|
+
Para ver todas as categorias: <prefix>waifuajuda
|
|
135
|
+
- permission_error: 🔒 Este comando não está liberado para você neste contexto.
|
|
136
|
+
Se for NSFW em grupo, peça para um admin usar <prefix>nsfw on.
|
|
137
|
+
- sucesso: ✅ Imagem enviada com sucesso!
|
|
138
|
+
Exemplo de próximo pedido: <prefix>waifu hug
|
|
139
|
+
- erro_uso: ❗ Categoria inválida ou ausente.
|
|
140
|
+
Exemplos: <prefix>waifu neko | <prefix>wp waifu
|
|
141
|
+
Veja a lista completa em <prefix>waifuajuda.
|
|
142
|
+
- erro_permissao: 🔒 Não consegui liberar este pedido para seu perfil.
|
|
143
|
+
Se achar que é engano, verifique seu plano e tente novamente.
|
|
134
144
|
- mensagens_sistema:
|
|
135
145
|
- (nao informado)
|
|
136
146
|
- limites_operacionais:
|
|
@@ -211,9 +221,9 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
211
221
|
- <prefix>wpnsfw waifu
|
|
212
222
|
- mensagens_uso (variantes):
|
|
213
223
|
- default:
|
|
214
|
-
- 🔞
|
|
215
|
-
|
|
216
|
-
-
|
|
224
|
+
- 🔞 _Como usar:_ <prefix>waifunsfw <categoria>
|
|
225
|
+
- Exemplos rápidos: <prefix>waifunsfw waifu | <prefix>wpnsfw neko
|
|
226
|
+
- Pré-requisitos: plano Premium; em grupo, NSFW ativo com <prefix>nsfw on.
|
|
217
227
|
- subcomandos:
|
|
218
228
|
- (nenhum)
|
|
219
229
|
- argumentos:
|
|
@@ -247,12 +257,22 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
247
257
|
- consulta API externa
|
|
248
258
|
- envia imagem NSFW no chat
|
|
249
259
|
- respostas_padrao:
|
|
250
|
-
- success:
|
|
251
|
-
|
|
252
|
-
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
- success: ✅ Pedido recebido! Aqui vai sua imagem.
|
|
261
|
+
Dica: você pode pedir outra categoria no próximo comando (ex.: <prefix>waifu neko).
|
|
262
|
+
- usage_error: ❗ Não entendi o formato do comando.
|
|
263
|
+
Exemplos:
|
|
264
|
+
• <prefix>waifu neko
|
|
265
|
+
• <prefix>waifunsfw waifu
|
|
266
|
+
Para ver todas as categorias: <prefix>waifuajuda
|
|
267
|
+
- permission_error: 🔒 Este comando não está liberado para você neste contexto.
|
|
268
|
+
Se for NSFW em grupo, peça para um admin usar <prefix>nsfw on.
|
|
269
|
+
- sucesso: 🔞 Imagem NSFW enviada.
|
|
270
|
+
Exemplo de próximo pedido: <prefix>waifunsfw neko
|
|
271
|
+
- erro_uso: ❗ Categoria NSFW inválida ou ausente.
|
|
272
|
+
Exemplos: <prefix>waifunsfw waifu | <prefix>wpnsfw neko
|
|
273
|
+
Veja opções em <prefix>waifuajuda.
|
|
274
|
+
- erro_permissao: 🔒 Este comando NSFW exige acesso Premium.
|
|
275
|
+
Em grupos, também é preciso NSFW ativo com <prefix>nsfw on.
|
|
256
276
|
- mensagens_sistema:
|
|
257
277
|
- (nao informado)
|
|
258
278
|
- limites_operacionais:
|
|
@@ -332,8 +352,9 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
332
352
|
- <prefix>waifuajuda
|
|
333
353
|
- mensagens_uso (variantes):
|
|
334
354
|
- default:
|
|
335
|
-
- 📖
|
|
336
|
-
-
|
|
355
|
+
- 📖 _Como usar:_ <prefix>waifuajuda
|
|
356
|
+
- Depois do guia, teste: <prefix>waifu neko
|
|
357
|
+
- Para NSFW: <prefix>waifunsfw waifu (Premium; em grupo exige <prefix>nsfw on).
|
|
337
358
|
- subcomandos:
|
|
338
359
|
- (nenhum)
|
|
339
360
|
- argumentos:
|
|
@@ -364,12 +385,21 @@ Este arquivo e destinado a agentes de IA para gerar respostas no contexto dos co
|
|
|
364
385
|
- efeitos_colaterais:
|
|
365
386
|
- envia mensagem de ajuda
|
|
366
387
|
- respostas_padrao:
|
|
367
|
-
- success:
|
|
368
|
-
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
388
|
+
- success: ✅ Pedido recebido! Aqui vai sua imagem.
|
|
389
|
+
Dica: você pode pedir outra categoria no próximo comando (ex.: <prefix>waifu neko).
|
|
390
|
+
- usage_error: ❗ Não entendi o formato do comando.
|
|
391
|
+
Exemplos:
|
|
392
|
+
• <prefix>waifu neko
|
|
393
|
+
• <prefix>waifunsfw waifu
|
|
394
|
+
Para ver todas as categorias: <prefix>waifuajuda
|
|
395
|
+
- permission_error: 🔒 Este comando não está liberado para você neste contexto.
|
|
396
|
+
Se for NSFW em grupo, peça para um admin usar <prefix>nsfw on.
|
|
397
|
+
- sucesso: 📚 Guia enviado com sucesso.
|
|
398
|
+
Agora experimente: <prefix>waifu neko
|
|
399
|
+
- erro_uso: ℹ️ Este comando não precisa de argumentos.
|
|
400
|
+
Use apenas: <prefix>waifuajuda
|
|
401
|
+
- erro_permissao: 🔒 Não consegui mostrar o guia neste contexto.
|
|
402
|
+
Tente novamente no privado ou em um grupo permitido.
|
|
373
403
|
- mensagens_sistema:
|
|
374
404
|
- (nao informado)
|
|
375
405
|
- limites_operacionais:
|
|
@@ -357,6 +357,35 @@
|
|
|
357
357
|
"schema": "legacy_v1_and_v2",
|
|
358
358
|
"legacy_name": "waifu",
|
|
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": "Guia rápido para usar o comando waifu e receber uma imagem de anime (SFW) da categoria escolhida.",
|
|
363
|
+
"quando_usar": ["Quando você quer uma imagem de anime específica (ex.: neko, waifu) sem enviar outras perguntas."],
|
|
364
|
+
"exemplos_reais": [
|
|
365
|
+
{
|
|
366
|
+
"situacao": "Quero uma imagem de neko.",
|
|
367
|
+
"comando": "<prefix>waifu neko",
|
|
368
|
+
"resposta_esperada": "✅ Imagem enviada com sucesso! Exemplo de próximo pedido: <prefix>waifu hug.",
|
|
369
|
+
"variacao": "Usando alias: <prefix>wp neko."
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"situacao": "Não informei a categoria.",
|
|
373
|
+
"comando": "<prefix>waifu",
|
|
374
|
+
"resposta_esperada": "❗ Categoria inválida ou ausente. Exemplos: <prefix>waifu neko | <prefix>wp waifu Veja a lista completa em <prefix>waifuajuda.",
|
|
375
|
+
"variacao": "Tente: <prefix>waifu neko."
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"situacao": "Categoria não suportada.",
|
|
379
|
+
"comando": "<prefix>waifu dragon",
|
|
380
|
+
"resposta_esperada": "❗ Categoria inválida ou ausente. Exemplos: <prefix>waifu neko | <prefix>wp waifu Veja a lista completa em <prefix>waifuajuda.",
|
|
381
|
+
"variacao": "Tente uma categoria comum como neko ou waifu."
|
|
382
|
+
}
|
|
383
|
+
],
|
|
384
|
+
"resposta_esperada": ["O bot envia uma imagem de anime da categoria solicitada (SFW)."],
|
|
385
|
+
"erros_comuns_usuario": ["Esquecer de indicar a categoria", "Usar uma categoria não suportada", "Usar o comando sem o prefixo quando necessário", "Erro de conexão temporário"],
|
|
386
|
+
"passos_se_der_erro": ["1. Verifique se digitou a categoria corretamente. 2. Use o formato com prefixo: <prefix>waifu <categoria> ou <prefix>wp <categoria>. 3. Se receber erro de categoria, peça a lista de categorias com <prefix>waifuajuda e es"],
|
|
387
|
+
"resumo_usuario_origem": "auto_ia_assistida",
|
|
388
|
+
"resumo_usuario_revisao_pendente": true
|
|
360
389
|
}
|
|
361
390
|
},
|
|
362
391
|
{
|
|
@@ -575,6 +604,35 @@
|
|
|
575
604
|
"schema": "legacy_v1_and_v2",
|
|
576
605
|
"legacy_name": "waifunsfw",
|
|
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": "Envie imagens NSFW de waifus em grupos autorizados. Requer Premium e NSFW ativo. Use <prefix>waifunsfw <categoria> ou <prefix>wpnsfw <waifu>.",
|
|
610
|
+
"quando_usar": ["Quando o grupo tiver NSFW ativo e você for assinante Premium.", "Para solicitar uma imagem NSFW de uma categoria válida (ex.: neko, waifu, etc.).", "Para confirmar qual variedade de conteúdo NSFW está disponível no momento."],
|
|
611
|
+
"exemplos_reais": [
|
|
612
|
+
{
|
|
613
|
+
"situacao": "Grupo com NSFW ativo e usuário Premium solicita neko.",
|
|
614
|
+
"comando": "<prefix>waifunsfw neko",
|
|
615
|
+
"resposta_esperada": "🔞 Imagem NSFW enviada. Exemplo de próximo pedido: <prefix>waifunsfw neko.",
|
|
616
|
+
"variacao": "neko."
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
"situacao": "Grupo sem Premium ou NSFW ativo.",
|
|
620
|
+
"comando": "<prefix>waifunsfw neko",
|
|
621
|
+
"resposta_esperada": "🔒 Este comando NSFW exige acesso Premium. Em grupos, também é preciso NSFW ativo com <prefix>nsfw on.",
|
|
622
|
+
"variacao": "premium_required."
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
"situacao": "Categoria NSFW inválida.",
|
|
626
|
+
"comando": "<prefix>waifunsfw manga",
|
|
627
|
+
"resposta_esperada": "❗ Categoria NSFW inválida ou ausente. Exemplos: <prefix>waifunsfw waifu | <prefix>wpnsfw neko Veja opções em <prefix>waifuajuda.",
|
|
628
|
+
"variacao": "categoria_invalida."
|
|
629
|
+
}
|
|
630
|
+
],
|
|
631
|
+
"resposta_esperada": ["🔞 Imagem NSFW enviada. Exemplo de próximo pedido: <prefix>waifunsfw neko", "❗ Categoria NSFW inválida ou ausente. Exemplos: <prefix>waifunsfw waifu | <prefix>wpnsfw neko Veja opções em <prefix>waifuajuda.", "🔒 Este comando NSFW exige acesso Premium. Em grupos, também é preciso NSFW ativo com <prefix>nsfw on."],
|
|
632
|
+
"erros_comuns_usuario": ["Não informar a categoria NSFW", "Categoria informada não é suportada", "NSFW não está ativo no grupo", "Usuário não possui assinatura Premium"],
|
|
633
|
+
"passos_se_der_erro": ["Verifique se o NSFW está ativo no grupo com <prefix>nsfw on e peça ao admin para ativar, se necessário.", "Confirme que você possui uma assinatura Premium válida para usar o comando.", "Certifique-se de digitar uma categoria NSFW suportada, por exemplo neko, waifu, etc.", "Tente novamente com uma categoria diferente ou em outro momento; se o problema persistir, peça ajuda ao administrador do grupo."],
|
|
634
|
+
"resumo_usuario_origem": "auto_ia_assistida",
|
|
635
|
+
"resumo_usuario_revisao_pendente": true
|
|
578
636
|
}
|
|
579
637
|
},
|
|
580
638
|
{
|
|
@@ -771,6 +829,35 @@
|
|
|
771
829
|
"schema": "legacy_v1_and_v2",
|
|
772
830
|
"legacy_name": "waifuajuda",
|
|
773
831
|
"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"]
|
|
832
|
+
},
|
|
833
|
+
"user_experience": {
|
|
834
|
+
"resumo_usuario": "Guia rápido das categorias de fotos anime que posso enviar. Mostra todas as opções disponíveis e como usar o comando. Não é necessário Premium; funciona para planos comum e premium.",
|
|
835
|
+
"quando_usar": ["Quando você quer conhecer todas as categorias de fotos disponíveis sem digitar argumentos", "Quando precisa confirmar que o comando não aceita parâmetros", "Quando desejar usar o alias wppicshelp para abrir o guia rapidamente"],
|
|
836
|
+
"exemplos_reais": [
|
|
837
|
+
{
|
|
838
|
+
"situacao": "Usuário quer abrir o guia rapidamente com o prefix.",
|
|
839
|
+
"comando": "<prefix>waifuajuda",
|
|
840
|
+
"resposta_esperada": "Guia de categorias exibido. Exemplo: <prefix>waifu neko.",
|
|
841
|
+
"variacao": "uso direto com <prefix>."
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
"situacao": "Usuário prefere usar o alias.",
|
|
845
|
+
"comando": "wppicshelp",
|
|
846
|
+
"resposta_esperada": "Guia de categorias exibido. Observação: você pode usar <prefix>waifu neko para abrir uma categoria específica.",
|
|
847
|
+
"variacao": "alias."
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
"situacao": "Usuário está em contexto privado ou grupo permitido.",
|
|
851
|
+
"comando": "<prefix>waifuajuda",
|
|
852
|
+
"resposta_esperada": "Guia pronto. Veja as categorias disponíveis e escolha uma: neko, shinobu, etc.",
|
|
853
|
+
"variacao": "contexto privado ou grupo permitido."
|
|
854
|
+
}
|
|
855
|
+
],
|
|
856
|
+
"resposta_esperada": ["Guia de categorias exibido com sucesso. Use <prefix>waifu neko para ver fotos de uma categoria específica."],
|
|
857
|
+
"erros_comuns_usuario": ["Tentar usar o comando com argumentos, quando ele não exige nenhum.", "Esquecer de incluir o prefixo, por exemplo usar 'waifuajuda' sem o prefixo.", "Tentar usar em contexto onde o guia não pode ser exibido (contextos não suportados)."],
|
|
858
|
+
"passos_se_der_erro": ["Verifique se o comando está sendo usado em um contexto permitido (privado ou grupo permitido).", "Tente novamente com o prefix correto: <prefix>waifuajuda.", "Se o guia não aparecer, tente novamente mais tarde ou entre em contato com o suporte."],
|
|
859
|
+
"resumo_usuario_origem": "auto_ia_assistida",
|
|
860
|
+
"resumo_usuario_revisao_pendente": true
|
|
774
861
|
}
|
|
775
862
|
}
|
|
776
863
|
],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
|
|
1
2
|
import axios from 'axios';
|
|
2
3
|
|
|
3
4
|
import logger from '#logger';
|
|
@@ -278,7 +279,7 @@ const buildRateScopeKey = ({ scope, senderKey, remoteJid }) => {
|
|
|
278
279
|
return `user:${String(senderKey || '').trim() || 'unknown'}`;
|
|
279
280
|
};
|
|
280
281
|
|
|
281
|
-
const pruneRateMapIfNeeded = (now =
|
|
282
|
+
const pruneRateMapIfNeeded = (now = __timeNowMs()) => {
|
|
282
283
|
if (userPlanRateMap.size <= USER_RATE_LIMIT_MAP_MAX_SIZE) return;
|
|
283
284
|
|
|
284
285
|
for (const [key, value] of userPlanRateMap.entries()) {
|
|
@@ -302,7 +303,7 @@ const pruneRateMapIfNeeded = (now = Date.now()) => {
|
|
|
302
303
|
const checkPlanUsageRateLimit = ({ commandKey, userPlan, scope, max, windowMs, senderKey, remoteJid }) => {
|
|
303
304
|
if (!max || !windowMs) return { limited: false, remainingMs: 0 };
|
|
304
305
|
|
|
305
|
-
const now =
|
|
306
|
+
const now = __timeNowMs();
|
|
306
307
|
const scopeKey = buildRateScopeKey({ scope, senderKey, remoteJid });
|
|
307
308
|
const cacheKey = `${String(commandKey || 'waifu')}:${normalizePlanName(userPlan)}:${scopeKey}`;
|
|
308
309
|
const current = userPlanRateMap.get(cacheKey);
|
|
@@ -32,6 +32,8 @@ const METRICS_SERVICE = process.env.METRICS_SERVICE_NAME || process.env.ECOSYSTE
|
|
|
32
32
|
const HTTP_SLO_TARGET_MS = Math.max(50, parseEnvNumber(process.env.HTTP_SLO_TARGET_MS, 750));
|
|
33
33
|
|
|
34
34
|
const QUERY_THRESHOLDS_MS = parseThresholds(process.env.DB_QUERY_ALERT_THRESHOLDS, [500, 1000]);
|
|
35
|
+
const ADMIN_ALERT_SEVERITIES = Object.freeze(['critical', 'high', 'medium', 'low', 'unknown']);
|
|
36
|
+
const ADMIN_FEATURE_FLAG_STATES = Object.freeze(['enabled', 'disabled', 'total']);
|
|
35
37
|
|
|
36
38
|
const registry = new client.Registry();
|
|
37
39
|
let metrics = null;
|
|
@@ -375,6 +377,53 @@ const ensureMetrics = () => {
|
|
|
375
377
|
labelNames: ['outcome'],
|
|
376
378
|
registers: [registry],
|
|
377
379
|
}),
|
|
380
|
+
adminOverviewUpdatedAtSeconds: new client.Gauge({
|
|
381
|
+
name: 'omnizap_admin_overview_updated_at_seconds',
|
|
382
|
+
help: 'Timestamp Unix (s) da ultima atualizacao de snapshot do painel admin',
|
|
383
|
+
registers: [registry],
|
|
384
|
+
}),
|
|
385
|
+
adminOverviewRequestsTotal: new client.Counter({
|
|
386
|
+
name: 'omnizap_admin_overview_requests_total',
|
|
387
|
+
help: 'Total de snapshots do painel admin publicados em metricas',
|
|
388
|
+
labelNames: ['source'],
|
|
389
|
+
registers: [registry],
|
|
390
|
+
}),
|
|
391
|
+
adminCounters: new client.Gauge({
|
|
392
|
+
name: 'omnizap_admin_counters',
|
|
393
|
+
help: 'Contadores agregados do painel admin',
|
|
394
|
+
labelNames: ['counter'],
|
|
395
|
+
registers: [registry],
|
|
396
|
+
}),
|
|
397
|
+
adminDashboardQuick: new client.Gauge({
|
|
398
|
+
name: 'omnizap_admin_dashboard_quick',
|
|
399
|
+
help: 'Metricas rapidas do dashboard admin',
|
|
400
|
+
labelNames: ['metric'],
|
|
401
|
+
registers: [registry],
|
|
402
|
+
}),
|
|
403
|
+
adminSystemHealth: new client.Gauge({
|
|
404
|
+
name: 'omnizap_admin_system_health',
|
|
405
|
+
help: 'Indicadores de saude expostos no painel admin',
|
|
406
|
+
labelNames: ['metric'],
|
|
407
|
+
registers: [registry],
|
|
408
|
+
}),
|
|
409
|
+
adminAlertsTotal: new client.Gauge({
|
|
410
|
+
name: 'omnizap_admin_alerts_total',
|
|
411
|
+
help: 'Total de alertas ativos por severidade no painel admin',
|
|
412
|
+
labelNames: ['severity'],
|
|
413
|
+
registers: [registry],
|
|
414
|
+
}),
|
|
415
|
+
adminFeatureFlagsTotal: new client.Gauge({
|
|
416
|
+
name: 'omnizap_admin_feature_flags_total',
|
|
417
|
+
help: 'Distribuicao de feature flags no painel admin',
|
|
418
|
+
labelNames: ['state'],
|
|
419
|
+
registers: [registry],
|
|
420
|
+
}),
|
|
421
|
+
adminSnapshotItemsTotal: new client.Gauge({
|
|
422
|
+
name: 'omnizap_admin_snapshot_items_total',
|
|
423
|
+
help: 'Total de itens por secao no snapshot do painel admin',
|
|
424
|
+
labelNames: ['section'],
|
|
425
|
+
registers: [registry],
|
|
426
|
+
}),
|
|
378
427
|
};
|
|
379
428
|
|
|
380
429
|
return metrics;
|
|
@@ -502,6 +551,93 @@ export const recordMessagesUpsert = ({ durationMs, type, messagesCount, ok }) =>
|
|
|
502
551
|
}
|
|
503
552
|
};
|
|
504
553
|
|
|
554
|
+
export const setAdminOverviewSnapshot = ({ overview = null, source = 'admin_overview' } = {}) => {
|
|
555
|
+
const m = ensureMetrics();
|
|
556
|
+
if (!m) return;
|
|
557
|
+
if (!overview || typeof overview !== 'object') return;
|
|
558
|
+
|
|
559
|
+
const setGaugeIfFinite = (gauge, labels, value, { clampToZero = false } = {}) => {
|
|
560
|
+
const numeric = Number(value);
|
|
561
|
+
if (!Number.isFinite(numeric)) return;
|
|
562
|
+
gauge.set(labels, clampToZero ? Math.max(0, numeric) : numeric);
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const sourceLabel = normalizeLabel(source, 'admin_overview').slice(0, 32);
|
|
566
|
+
m.adminOverviewRequestsTotal.inc({ source: sourceLabel });
|
|
567
|
+
|
|
568
|
+
const updatedAtMs = Date.parse(String(overview?.updated_at || ''));
|
|
569
|
+
const updatedAtSeconds = Number.isFinite(updatedAtMs) ? updatedAtMs / 1000 : Date.now() / 1000;
|
|
570
|
+
m.adminOverviewUpdatedAtSeconds.set(updatedAtSeconds);
|
|
571
|
+
|
|
572
|
+
const counters = overview?.counters && typeof overview.counters === 'object' ? overview.counters : {};
|
|
573
|
+
const counterKeys = ['total_packs_any_status', 'total_stickers_any_status', 'active_google_sessions', 'known_google_users', 'active_bans', 'visit_events_24h', 'visit_events_7d', 'unique_visitors_7d'];
|
|
574
|
+
for (const key of counterKeys) {
|
|
575
|
+
setGaugeIfFinite(m.adminCounters, { counter: key }, counters?.[key], { clampToZero: true });
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const dashboardQuick = overview?.dashboard_quick && typeof overview.dashboard_quick === 'object' ? overview.dashboard_quick : {};
|
|
579
|
+
const quickKeys = ['bots_online', 'messages_today', 'spam_blocked_today', 'errors_5xx'];
|
|
580
|
+
for (const key of quickKeys) {
|
|
581
|
+
setGaugeIfFinite(m.adminDashboardQuick, { metric: key }, dashboardQuick?.[key], { clampToZero: true });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const systemHealth = overview?.system_health && typeof overview.system_health === 'object' ? overview.system_health : {};
|
|
585
|
+
const healthKeys = ['cpu_percent', 'ram_percent', 'http_latency_p95_ms', 'queue_pending', 'db_total_queries', 'db_slow_queries'];
|
|
586
|
+
for (const key of healthKeys) {
|
|
587
|
+
setGaugeIfFinite(m.adminSystemHealth, { metric: key }, systemHealth?.[key], { clampToZero: true });
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const moderationQueue = Array.isArray(overview?.moderation_queue) ? overview.moderation_queue : [];
|
|
591
|
+
const auditLog = Array.isArray(overview?.audit_log) ? overview.audit_log : [];
|
|
592
|
+
const users = Array.isArray(overview?.users_sessions?.users) ? overview.users_sessions.users : [];
|
|
593
|
+
const activeSessions = Array.isArray(overview?.users_sessions?.active_sessions) ? overview.users_sessions.active_sessions : [];
|
|
594
|
+
const blockedAccounts = Array.isArray(overview?.users_sessions?.blocked_accounts) ? overview.users_sessions.blocked_accounts : [];
|
|
595
|
+
const alerts = Array.isArray(overview?.alerts) ? overview.alerts : [];
|
|
596
|
+
const featureFlags = Array.isArray(overview?.feature_flags) ? overview.feature_flags : [];
|
|
597
|
+
|
|
598
|
+
m.adminSnapshotItemsTotal.set({ section: 'moderation_queue' }, moderationQueue.length);
|
|
599
|
+
m.adminSnapshotItemsTotal.set({ section: 'audit_log' }, auditLog.length);
|
|
600
|
+
m.adminSnapshotItemsTotal.set({ section: 'users' }, users.length);
|
|
601
|
+
m.adminSnapshotItemsTotal.set({ section: 'active_sessions' }, activeSessions.length);
|
|
602
|
+
m.adminSnapshotItemsTotal.set({ section: 'blocked_accounts' }, blockedAccounts.length);
|
|
603
|
+
m.adminSnapshotItemsTotal.set({ section: 'alerts' }, alerts.length);
|
|
604
|
+
m.adminSnapshotItemsTotal.set({ section: 'feature_flags' }, featureFlags.length);
|
|
605
|
+
|
|
606
|
+
const severityCounts = Object.fromEntries(ADMIN_ALERT_SEVERITIES.map((severity) => [severity, 0]));
|
|
607
|
+
for (const alert of alerts) {
|
|
608
|
+
const severityRaw = String(alert?.severity || '')
|
|
609
|
+
.trim()
|
|
610
|
+
.toLowerCase();
|
|
611
|
+
const severity = ADMIN_ALERT_SEVERITIES.includes(severityRaw) ? severityRaw : 'unknown';
|
|
612
|
+
severityCounts[severity] = Number(severityCounts[severity] || 0) + 1;
|
|
613
|
+
}
|
|
614
|
+
for (const severity of ADMIN_ALERT_SEVERITIES) {
|
|
615
|
+
m.adminAlertsTotal.set({ severity }, Number(severityCounts[severity] || 0));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
let enabledFlags = 0;
|
|
619
|
+
let disabledFlags = 0;
|
|
620
|
+
for (const flag of featureFlags) {
|
|
621
|
+
if (flag?.is_enabled) {
|
|
622
|
+
enabledFlags += 1;
|
|
623
|
+
} else {
|
|
624
|
+
disabledFlags += 1;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const totalFlags = enabledFlags + disabledFlags;
|
|
628
|
+
for (const state of ADMIN_FEATURE_FLAG_STATES) {
|
|
629
|
+
if (state === 'enabled') {
|
|
630
|
+
m.adminFeatureFlagsTotal.set({ state }, enabledFlags);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (state === 'disabled') {
|
|
634
|
+
m.adminFeatureFlagsTotal.set({ state }, disabledFlags);
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
m.adminFeatureFlagsTotal.set({ state }, totalFlags);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
505
641
|
export const recordRpgPlayerCreated = (value = 1) => {
|
|
506
642
|
const m = ensureMetrics();
|
|
507
643
|
if (!m) return;
|