@kaikybrofc/omnizap-system 2.1.8
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 +534 -0
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/RELEASE-v2.1.2.md +83 -0
- package/app/config/adminIdentity.js +87 -0
- package/app/config/baileysConfig.js +693 -0
- package/app/config/groupUtils.js +388 -0
- package/app/connection/socketController.js +992 -0
- package/app/controllers/messageController.js +354 -0
- package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
- package/app/modules/adminModule/groupEventHandlers.js +355 -0
- package/app/modules/aiModule/catCommand.js +1006 -0
- package/app/modules/broadcastModule/noticeCommand.js +416 -0
- package/app/modules/gameModule/diceCommand.js +67 -0
- package/app/modules/menuModule/common.js +311 -0
- package/app/modules/menuModule/menus.js +59 -0
- package/app/modules/playModule/playCommand.js +1615 -0
- package/app/modules/quoteModule/quoteCommand.js +851 -0
- package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
- package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
- package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
- package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
- package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
- package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
- package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
- package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
- package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
- package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
- package/app/modules/statsModule/globalRankingCommand.js +65 -0
- package/app/modules/statsModule/noMessageCommand.js +288 -0
- package/app/modules/statsModule/rankingCommand.js +60 -0
- package/app/modules/statsModule/rankingCommon.js +889 -0
- package/app/modules/stickerModule/addStickerMetadata.js +239 -0
- package/app/modules/stickerModule/convertToWebp.js +390 -0
- package/app/modules/stickerModule/stickerCommand.js +454 -0
- package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
- package/app/modules/stickerModule/stickerTextCommand.js +657 -0
- package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
- package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
- package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
- package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
- package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
- package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
- package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
- package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
- package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
- package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
- package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
- package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
- package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
- package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
- package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
- package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
- package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
- package/app/modules/stickerPackModule/stickerPackService.js +788 -0
- package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
- package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
- package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
- package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
- package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
- package/app/modules/systemMetricsModule/pingCommand.js +421 -0
- package/app/modules/tiktokModule/tiktokCommand.js +798 -0
- package/app/modules/userModule/userCommand.js +1217 -0
- package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
- package/app/observability/metrics.js +734 -0
- package/app/services/captchaService.js +492 -0
- package/app/services/dbWriteQueue.js +572 -0
- package/app/services/groupMetadataService.js +279 -0
- package/app/services/lidMapService.js +663 -0
- package/app/services/messagePersistenceService.js +56 -0
- package/app/services/newsBroadcastService.js +351 -0
- package/app/services/pokeApiService.js +398 -0
- package/app/services/queueUtils.js +57 -0
- package/app/services/socketState.js +7 -0
- package/app/store/aiPromptStore.js +38 -0
- package/app/store/groupConfigStore.js +58 -0
- package/app/store/premiumUserStore.js +36 -0
- package/app/utils/antiLink/antiLinkModule.js +804 -0
- package/app/utils/http/getImageBufferModule.js +18 -0
- package/app/utils/json/jsonSanitizer.js +113 -0
- package/app/utils/json/jsonSanitizer.test.js +40 -0
- package/app/utils/logger/loggerModule.js +262 -0
- package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
- package/database/index.js +2052 -0
- package/database/init.js +516 -0
- package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
- package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
- package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
- package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
- package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
- package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
- package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
- package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
- package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
- package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
- package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
- package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
- package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
- package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
- package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
- package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
- package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
- package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
- package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
- package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
- package/docker-compose.yml +103 -0
- package/ecosystem.prod.config.cjs +35 -0
- package/eslint.config.js +61 -0
- package/index.js +437 -0
- package/ml/clip_classifier/Dockerfile +16 -0
- package/ml/clip_classifier/README.md +120 -0
- package/ml/clip_classifier/adaptive_scoring.py +40 -0
- package/ml/clip_classifier/classifier.py +654 -0
- package/ml/clip_classifier/embedding_store.py +481 -0
- package/ml/clip_classifier/env_loader.py +15 -0
- package/ml/clip_classifier/llm_label_expander.py +144 -0
- package/ml/clip_classifier/main.py +213 -0
- package/ml/clip_classifier/requirements.txt +10 -0
- package/ml/clip_classifier/similarity_engine.py +74 -0
- package/observability/alert-rules.yml +60 -0
- package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
- package/observability/grafana/dashboards/omnizap-overview.json +170 -0
- package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
- package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
- package/observability/loki-config.yml +38 -0
- package/observability/mysql-exporter.cnf +5 -0
- package/observability/mysql-setup.sql +46 -0
- package/observability/prometheus.yml +32 -0
- package/observability/promtail-config.yml +84 -0
- package/package.json +109 -0
- package/public/api-docs/index.html +144 -0
- package/public/css/github-project-panel.css +297 -0
- package/public/css/stickers-admin.css +1272 -0
- package/public/css/styles.css +671 -0
- package/public/index.html +1311 -0
- package/public/js/apps/apiDocsApp.js +310 -0
- package/public/js/apps/createPackApp.js +2069 -0
- package/public/js/apps/homeApp.js +396 -0
- package/public/js/apps/stickersAdminApp.js +1744 -0
- package/public/js/apps/stickersApp.js +4830 -0
- package/public/js/catalog.js +1019 -0
- package/public/js/github-panel/components/CommitList.js +34 -0
- package/public/js/github-panel/components/ErrorState.js +16 -0
- package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
- package/public/js/github-panel/components/ReleaseList.js +38 -0
- package/public/js/github-panel/components/SkeletonPanel.js +22 -0
- package/public/js/github-panel/components/StatCard.js +15 -0
- package/public/js/github-panel/index.js +15 -0
- package/public/js/github-panel/useGithubRepoData.js +154 -0
- package/public/js/github-panel/vendor/react.js +11 -0
- package/public/js/runtime/react-runtime.js +19 -0
- package/public/licenca/index.html +106 -0
- package/public/stickers/admin/index.html +23 -0
- package/public/stickers/create/index.html +47 -0
- package/public/stickers/index.html +48 -0
- package/public/termos-de-uso/index.html +125 -0
- package/scripts/cache-bust.mjs +107 -0
- package/scripts/deploy.sh +458 -0
- package/scripts/github-deploy-notify.mjs +174 -0
- package/scripts/release.sh +129 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import logger from '../../utils/logger/loggerModule.js';
|
|
2
|
+
import { getSystemMetrics } from '../../utils/systemMetrics/systemMetricsModule.js';
|
|
3
|
+
import { sendAndStore } from '../../services/messagePersistenceService.js';
|
|
4
|
+
|
|
5
|
+
const METRICS_ENDPOINT =
|
|
6
|
+
process.env.METRICS_ENDPOINT ||
|
|
7
|
+
`http://localhost:${process.env.METRICS_PORT || 9102}${process.env.METRICS_PATH || '/metrics'}`;
|
|
8
|
+
const METRICS_TIMEOUT_MS = Number(process.env.METRICS_PING_TIMEOUT_MS || 1500);
|
|
9
|
+
|
|
10
|
+
const formatLoadAverage = (values) => values.map((value) => value.toFixed(2)).join(' | ');
|
|
11
|
+
|
|
12
|
+
const formatBytes = (bytes) => {
|
|
13
|
+
if (!Number.isFinite(bytes)) return 'n/a';
|
|
14
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
15
|
+
let value = bytes;
|
|
16
|
+
let idx = 0;
|
|
17
|
+
while (value >= 1024 && idx < units.length - 1) {
|
|
18
|
+
value /= 1024;
|
|
19
|
+
idx += 1;
|
|
20
|
+
}
|
|
21
|
+
return `${value.toFixed(value >= 100 ? 0 : 2)}${units[idx]}`;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const formatSeconds = (seconds) => {
|
|
25
|
+
if (!Number.isFinite(seconds)) return 'n/a';
|
|
26
|
+
const total = Math.max(0, Math.floor(seconds));
|
|
27
|
+
const days = Math.floor(total / 86400);
|
|
28
|
+
const hours = Math.floor((total % 86400) / 3600);
|
|
29
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
30
|
+
const secs = total % 60;
|
|
31
|
+
const time = [hours, minutes, secs].map((value) => String(value).padStart(2, '0')).join(':');
|
|
32
|
+
return days > 0 ? `${days}d ${time}` : time;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const parseMetricNumber = (value) => {
|
|
36
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
37
|
+
if (typeof value !== 'string') return null;
|
|
38
|
+
const parsed = Number(value.replace(',', '.').trim());
|
|
39
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getStatusLevel = (value, warnAt, criticalAt) => {
|
|
43
|
+
const numericValue = parseMetricNumber(value);
|
|
44
|
+
if (numericValue === null) return { emoji: '⚪', label: 'sem dado' };
|
|
45
|
+
if (numericValue >= criticalAt) return { emoji: '🔴', label: 'crítico' };
|
|
46
|
+
if (numericValue >= warnAt) return { emoji: '🟡', label: 'atenção' };
|
|
47
|
+
return { emoji: '🟢', label: 'ok' };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const formatStatusLevel = (status) => `${status.emoji} ${status.label}`;
|
|
51
|
+
|
|
52
|
+
const padNumber = (value) => String(value).padStart(2, '0');
|
|
53
|
+
|
|
54
|
+
const formatDateTime = (date = new Date()) =>
|
|
55
|
+
`${padNumber(date.getDate())}/${padNumber(date.getMonth() + 1)}/${date.getFullYear()} ${padNumber(
|
|
56
|
+
date.getHours(),
|
|
57
|
+
)}:${padNumber(date.getMinutes())}:${padNumber(date.getSeconds())}`;
|
|
58
|
+
|
|
59
|
+
const parseLabels = (raw) => {
|
|
60
|
+
if (!raw) return {};
|
|
61
|
+
const labels = {};
|
|
62
|
+
const regex = /(\w+)="((?:\\.|[^"\\])*)"/g;
|
|
63
|
+
let match;
|
|
64
|
+
while ((match = regex.exec(raw)) !== null) {
|
|
65
|
+
labels[match[1]] = match[2].replace(/\\"/g, '"');
|
|
66
|
+
}
|
|
67
|
+
return labels;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const parsePrometheusText = (text) => {
|
|
71
|
+
const series = new Map();
|
|
72
|
+
const lines = text.split('\n');
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
76
|
+
const [metricPart, valuePart] = trimmed.split(/\s+/, 2);
|
|
77
|
+
if (!metricPart || !valuePart) continue;
|
|
78
|
+
const value = Number(valuePart);
|
|
79
|
+
if (!Number.isFinite(value)) continue;
|
|
80
|
+
const labelStart = metricPart.indexOf('{');
|
|
81
|
+
let name = metricPart;
|
|
82
|
+
let labels = {};
|
|
83
|
+
if (labelStart !== -1) {
|
|
84
|
+
name = metricPart.slice(0, labelStart);
|
|
85
|
+
const labelBody = metricPart.slice(labelStart + 1, metricPart.lastIndexOf('}'));
|
|
86
|
+
labels = parseLabels(labelBody);
|
|
87
|
+
}
|
|
88
|
+
const list = series.get(name) || [];
|
|
89
|
+
list.push({ labels, value });
|
|
90
|
+
series.set(name, list);
|
|
91
|
+
}
|
|
92
|
+
return series;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const pickValue = (series, name, filter = null) => {
|
|
96
|
+
const list = series.get(name) || [];
|
|
97
|
+
if (!list.length) return null;
|
|
98
|
+
if (!filter) return list[0].value;
|
|
99
|
+
const hit = list.find((entry) => filter(entry.labels));
|
|
100
|
+
return hit ? hit.value : null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const sumValues = (series, name, filter = null) => {
|
|
104
|
+
const list = series.get(name) || [];
|
|
105
|
+
return list.reduce((acc, entry) => {
|
|
106
|
+
if (filter && !filter(entry.labels)) return acc;
|
|
107
|
+
if (!Number.isFinite(entry.value)) return acc;
|
|
108
|
+
return acc + entry.value;
|
|
109
|
+
}, 0);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getLabelValue = (series, name, labelKey) => {
|
|
113
|
+
const list = series.get(name) || [];
|
|
114
|
+
const entry = list.find((item) => item.labels && item.labels[labelKey]);
|
|
115
|
+
return entry ? entry.labels[labelKey] : null;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const buildPingMessage = ({
|
|
119
|
+
systemMetrics,
|
|
120
|
+
metricsSummary,
|
|
121
|
+
metricsOk,
|
|
122
|
+
metricsError,
|
|
123
|
+
latencyMs,
|
|
124
|
+
generatedAt,
|
|
125
|
+
}) => {
|
|
126
|
+
const responseTime = Number.isFinite(latencyMs) ? `${Math.max(0, Math.round(latencyMs))}ms` : 'n/a';
|
|
127
|
+
|
|
128
|
+
const hostCpuStatus = getStatusLevel(systemMetrics.usoCpuPercentual, 65, 85);
|
|
129
|
+
const hostMemoryStatus = getStatusLevel(systemMetrics.usoMemoriaPercentual, 75, 90);
|
|
130
|
+
|
|
131
|
+
const load1 = Array.isArray(systemMetrics.cargaMedia) ? systemMetrics.cargaMedia[0] : null;
|
|
132
|
+
const loadPerCore = Number.isFinite(load1) && systemMetrics.totalCpus > 0 ? load1 / systemMetrics.totalCpus : null;
|
|
133
|
+
const loadStatus = getStatusLevel(loadPerCore, 0.9, 1.2);
|
|
134
|
+
const loadPerCoreText = loadPerCore === null ? 'n/a' : loadPerCore.toFixed(2);
|
|
135
|
+
|
|
136
|
+
const systemPart = `
|
|
137
|
+
🖥️ *Servidor (máquina)*
|
|
138
|
+
• Host: ${systemMetrics.hostname}
|
|
139
|
+
• SO: ${systemMetrics.plataforma} ${systemMetrics.release} (${systemMetrics.arquitetura})
|
|
140
|
+
• Uptime do sistema: ${systemMetrics.uptimeSistema}
|
|
141
|
+
• CPU da máquina: ${formatStatusLevel(hostCpuStatus)} • ${systemMetrics.usoCpuPercentual}% (uso geral)
|
|
142
|
+
• Carga (1m|5m|15m): ${formatLoadAverage(systemMetrics.cargaMedia)}
|
|
143
|
+
• Pressão por núcleo (1m): ${loadPerCoreText} • ${formatStatusLevel(loadStatus)}
|
|
144
|
+
• RAM: ${formatStatusLevel(hostMemoryStatus)} • ${systemMetrics.memoriaUsada} / ${systemMetrics.memoriaTotal} (${systemMetrics.usoMemoriaPercentual}%)
|
|
145
|
+
`.trim();
|
|
146
|
+
|
|
147
|
+
if (!metricsOk) {
|
|
148
|
+
return `
|
|
149
|
+
🏓 *Pong! Painel de saúde (modo básico)*
|
|
150
|
+
🕐 Atualizado em: ${formatDateTime(generatedAt)}
|
|
151
|
+
⚡ Tempo de resposta: ${responseTime}
|
|
152
|
+
🧭 Legenda: 🟢 ok • 🟡 atenção • 🔴 crítico • ⚪ sem dado
|
|
153
|
+
|
|
154
|
+
${systemPart}
|
|
155
|
+
|
|
156
|
+
⚠️ *Métricas avançadas indisponíveis*
|
|
157
|
+
• Motivo: ${metricsError || 'sem detalhes'}
|
|
158
|
+
• Endpoint: ${METRICS_ENDPOINT}
|
|
159
|
+
• Timeout: ${METRICS_TIMEOUT_MS}ms
|
|
160
|
+
|
|
161
|
+
💡 *Dica:* as métricas avançadas vêm do endpoint */metrics* (Prometheus).
|
|
162
|
+
`.trim();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const openFds = parseMetricNumber(metricsSummary.openFds);
|
|
166
|
+
const maxFds = parseMetricNumber(metricsSummary.maxFds);
|
|
167
|
+
const fdsUsage = openFds !== null && maxFds && maxFds > 0 ? (openFds / maxFds) * 100 : null;
|
|
168
|
+
const fdsStatus = getStatusLevel(fdsUsage, 60, 80);
|
|
169
|
+
const fdsUsageText = fdsUsage === null ? 'n/a' : `${fdsUsage.toFixed(1)}%`;
|
|
170
|
+
|
|
171
|
+
const lagP99 = parseMetricNumber(metricsSummary.lagP99);
|
|
172
|
+
const lagStatus = getStatusLevel(lagP99, 120, 300);
|
|
173
|
+
|
|
174
|
+
const dbTotal = parseMetricNumber(metricsSummary.dbTotal) || 0;
|
|
175
|
+
const dbSlow = parseMetricNumber(metricsSummary.dbSlow) || 0;
|
|
176
|
+
const slowRate = dbTotal > 0 ? (dbSlow / dbTotal) * 100 : null;
|
|
177
|
+
const dbStatus = getStatusLevel(slowRate, 5, 15);
|
|
178
|
+
const slowRateText = slowRate === null ? 'n/a' : `${slowRate.toFixed(2)}%`;
|
|
179
|
+
|
|
180
|
+
const queueValues = [metricsSummary.queues.messages, metricsSummary.queues.chats, metricsSummary.queues.lid_map]
|
|
181
|
+
.map((value) => parseMetricNumber(value))
|
|
182
|
+
.filter((value) => value !== null);
|
|
183
|
+
const queuePeak = queueValues.length ? Math.max(...queueValues) : null;
|
|
184
|
+
const queueStatus = getStatusLevel(queuePeak, 30, 120);
|
|
185
|
+
const queuePeakText = queuePeak === null ? 'n/a' : String(Math.round(queuePeak));
|
|
186
|
+
|
|
187
|
+
const processPart = `
|
|
188
|
+
⚙️ *Processo do bot*
|
|
189
|
+
• Uptime do processo: ${metricsSummary.processUptime}
|
|
190
|
+
• Node.js: ${metricsSummary.nodeVersion}
|
|
191
|
+
• CPU acumulada: total ${metricsSummary.cpuTotalSec}s (user ${metricsSummary.cpuUserSec}s | sys ${metricsSummary.cpuSysSec}s)
|
|
192
|
+
• Memória do processo: RSS ${metricsSummary.rss} | Heap ${metricsSummary.heap} | VMem ${metricsSummary.vmem}
|
|
193
|
+
• FDs abertos: ${metricsSummary.openFds}/${metricsSummary.maxFds} (${fdsUsageText}) • ${formatStatusLevel(fdsStatus)}
|
|
194
|
+
`.trim();
|
|
195
|
+
|
|
196
|
+
const nodePart = `
|
|
197
|
+
🧠 *Event Loop (responsividade)*
|
|
198
|
+
• Lag p50: ${metricsSummary.lagP50}ms (comportamento normal)
|
|
199
|
+
• Lag p90: ${metricsSummary.lagP90}ms (picos frequentes)
|
|
200
|
+
• Lag p99: ${metricsSummary.lagP99}ms (pior caso recente)
|
|
201
|
+
• Status do loop: ${formatStatusLevel(lagStatus)} (quanto menor o lag, melhor)
|
|
202
|
+
`.trim();
|
|
203
|
+
|
|
204
|
+
const dbPart = `
|
|
205
|
+
🗄️ *Banco*
|
|
206
|
+
• Queries totais: ${metricsSummary.dbTotal}
|
|
207
|
+
• Queries lentas: ${metricsSummary.dbSlow} (${slowRateText}) • ${formatStatusLevel(dbStatus)}
|
|
208
|
+
• Writes: messages ${metricsSummary.writes.messages} | lid_map ${metricsSummary.writes.lid_map} | groups ${metricsSummary.writes.groups_metadata}
|
|
209
|
+
• Últimas latências (ms): messages ${metricsSummary.lastQuery.messages} | lid_map ${metricsSummary.lastQuery.lid_map} | groups ${metricsSummary.lastQuery.groups_metadata}
|
|
210
|
+
`.trim();
|
|
211
|
+
|
|
212
|
+
const queuePart = `
|
|
213
|
+
📦 *Filas internas (backlog)*
|
|
214
|
+
• messages ${metricsSummary.queues.messages} | chats ${metricsSummary.queues.chats} | lid_map ${metricsSummary.queues.lid_map}
|
|
215
|
+
• Pico atual: ${queuePeakText} • ${formatStatusLevel(queueStatus)} (quanto menor, melhor)
|
|
216
|
+
`.trim();
|
|
217
|
+
|
|
218
|
+
const upsertPart = `
|
|
219
|
+
📬 *messages.upsert (entrada de mensagens)*
|
|
220
|
+
• Eventos recebidos: append ${metricsSummary.upsertEvents.append} | notify ${metricsSummary.upsertEvents.notify}
|
|
221
|
+
• Mensagens processadas: append ${metricsSummary.upsertMessages.append} | notify ${metricsSummary.upsertMessages.notify}
|
|
222
|
+
`.trim();
|
|
223
|
+
|
|
224
|
+
const glossaryPart = `
|
|
225
|
+
📖 *Glossário rápido*
|
|
226
|
+
• RSS: memória real em RAM usada pelo processo
|
|
227
|
+
• Heap: memória JavaScript gerenciada pelo Node.js
|
|
228
|
+
• VMem: memória virtual reservada pelo processo
|
|
229
|
+
• Lag: atraso do Node para executar tarefas
|
|
230
|
+
`.trim();
|
|
231
|
+
|
|
232
|
+
return `
|
|
233
|
+
🏓 *Pong! Painel de saúde do Omnizap*
|
|
234
|
+
🕐 Atualizado em: ${formatDateTime(generatedAt)}
|
|
235
|
+
⚡ Tempo de resposta: ${responseTime}
|
|
236
|
+
🧭 Legenda: 🟢 ok • 🟡 atenção • 🔴 crítico • ⚪ sem dado
|
|
237
|
+
|
|
238
|
+
${systemPart}
|
|
239
|
+
|
|
240
|
+
${processPart}
|
|
241
|
+
|
|
242
|
+
${nodePart}
|
|
243
|
+
|
|
244
|
+
${dbPart}
|
|
245
|
+
|
|
246
|
+
${queuePart}
|
|
247
|
+
|
|
248
|
+
${upsertPart}
|
|
249
|
+
|
|
250
|
+
${glossaryPart}
|
|
251
|
+
`.trim();
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const fetchMetricsSnapshot = async () => {
|
|
255
|
+
const controller =
|
|
256
|
+
typeof globalThis.AbortController === 'function'
|
|
257
|
+
? new globalThis.AbortController()
|
|
258
|
+
: null;
|
|
259
|
+
const timeout = setTimeout(() => controller?.abort(), METRICS_TIMEOUT_MS);
|
|
260
|
+
try {
|
|
261
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
262
|
+
throw new Error('fetch indisponível');
|
|
263
|
+
}
|
|
264
|
+
const response = await globalThis.fetch(METRICS_ENDPOINT, controller ? { signal: controller.signal } : {});
|
|
265
|
+
if (!response.ok) {
|
|
266
|
+
throw new Error(`HTTP ${response.status}`);
|
|
267
|
+
}
|
|
268
|
+
const text = await response.text();
|
|
269
|
+
const series = parsePrometheusText(text);
|
|
270
|
+
|
|
271
|
+
const processStart = pickValue(series, 'omnizap_process_start_time_seconds');
|
|
272
|
+
const nowSec = Date.now() / 1000;
|
|
273
|
+
const processUptime = processStart ? formatSeconds(nowSec - processStart) : 'n/a';
|
|
274
|
+
|
|
275
|
+
const cpuUserSec = pickValue(series, 'omnizap_process_cpu_user_seconds_total');
|
|
276
|
+
const cpuSysSec = pickValue(series, 'omnizap_process_cpu_system_seconds_total');
|
|
277
|
+
const cpuTotalSec = pickValue(series, 'omnizap_process_cpu_seconds_total');
|
|
278
|
+
|
|
279
|
+
const rss = pickValue(series, 'omnizap_process_resident_memory_bytes');
|
|
280
|
+
const vmem = pickValue(series, 'omnizap_process_virtual_memory_bytes');
|
|
281
|
+
const heap = pickValue(series, 'omnizap_process_heap_bytes');
|
|
282
|
+
|
|
283
|
+
const openFds = pickValue(series, 'omnizap_process_open_fds');
|
|
284
|
+
const maxFds = pickValue(series, 'omnizap_process_max_fds');
|
|
285
|
+
|
|
286
|
+
const lagP50 = pickValue(series, 'omnizap_nodejs_eventloop_lag_p50_seconds');
|
|
287
|
+
const lagP90 = pickValue(series, 'omnizap_nodejs_eventloop_lag_p90_seconds');
|
|
288
|
+
const lagP99 = pickValue(series, 'omnizap_nodejs_eventloop_lag_p99_seconds');
|
|
289
|
+
|
|
290
|
+
const nodeVersion = getLabelValue(series, 'omnizap_nodejs_version_info', 'version') || 'n/a';
|
|
291
|
+
|
|
292
|
+
const dbTotal = sumValues(series, 'omnizap_db_query_total');
|
|
293
|
+
const dbSlow = sumValues(series, 'omnizap_db_slow_queries_total');
|
|
294
|
+
|
|
295
|
+
const writesByTable = {};
|
|
296
|
+
const writeSeries = series.get('omnizap_db_write_total') || [];
|
|
297
|
+
writeSeries.forEach((entry) => {
|
|
298
|
+
const table = entry.labels?.table || 'unknown';
|
|
299
|
+
writesByTable[table] = (writesByTable[table] || 0) + entry.value;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const lastQuerySeries = series.get('omnizap_db_last_query_duration_ms') || [];
|
|
303
|
+
const lastQuery = {};
|
|
304
|
+
lastQuerySeries.forEach((entry) => {
|
|
305
|
+
const table = entry.labels?.table;
|
|
306
|
+
if (!table) return;
|
|
307
|
+
lastQuery[table] = entry.value;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const queueSeries = series.get('omnizap_queue_depth') || [];
|
|
311
|
+
const queues = {};
|
|
312
|
+
queueSeries.forEach((entry) => {
|
|
313
|
+
const queue = entry.labels?.queue;
|
|
314
|
+
if (!queue) return;
|
|
315
|
+
queues[queue] = entry.value;
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const upsertEvents = {};
|
|
319
|
+
const upsertSeries = series.get('omnizap_messages_upsert_total') || [];
|
|
320
|
+
upsertSeries.forEach((entry) => {
|
|
321
|
+
const type = entry.labels?.type || 'unknown';
|
|
322
|
+
upsertEvents[type] = (upsertEvents[type] || 0) + entry.value;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const upsertMessages = {};
|
|
326
|
+
const upsertMsgSeries = series.get('omnizap_messages_upsert_messages_total') || [];
|
|
327
|
+
upsertMsgSeries.forEach((entry) => {
|
|
328
|
+
const type = entry.labels?.type || 'unknown';
|
|
329
|
+
upsertMessages[type] = (upsertMessages[type] || 0) + entry.value;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const formatNumber = (value, digits = 2) =>
|
|
333
|
+
Number.isFinite(value) ? value.toFixed(digits) : 'n/a';
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
processUptime,
|
|
337
|
+
cpuUserSec: formatNumber(cpuUserSec),
|
|
338
|
+
cpuSysSec: formatNumber(cpuSysSec),
|
|
339
|
+
cpuTotalSec: formatNumber(cpuTotalSec),
|
|
340
|
+
rss: formatBytes(rss),
|
|
341
|
+
heap: formatBytes(heap),
|
|
342
|
+
vmem: formatBytes(vmem),
|
|
343
|
+
openFds: Number.isFinite(openFds) ? Math.round(openFds) : 'n/a',
|
|
344
|
+
maxFds: Number.isFinite(maxFds) ? Math.round(maxFds) : 'n/a',
|
|
345
|
+
nodeVersion,
|
|
346
|
+
lagP50: Number.isFinite(lagP50) ? (lagP50 * 1000).toFixed(2) : 'n/a',
|
|
347
|
+
lagP90: Number.isFinite(lagP90) ? (lagP90 * 1000).toFixed(2) : 'n/a',
|
|
348
|
+
lagP99: Number.isFinite(lagP99) ? (lagP99 * 1000).toFixed(2) : 'n/a',
|
|
349
|
+
dbTotal: Math.round(dbTotal),
|
|
350
|
+
dbSlow: Math.round(dbSlow),
|
|
351
|
+
writes: {
|
|
352
|
+
messages: Math.round(writesByTable.messages || 0),
|
|
353
|
+
lid_map: Math.round(writesByTable.lid_map || 0),
|
|
354
|
+
groups_metadata: Math.round(writesByTable.groups_metadata || 0),
|
|
355
|
+
},
|
|
356
|
+
lastQuery: {
|
|
357
|
+
messages: Number.isFinite(lastQuery.messages) ? lastQuery.messages.toFixed(2) : 'n/a',
|
|
358
|
+
lid_map: Number.isFinite(lastQuery.lid_map) ? lastQuery.lid_map.toFixed(2) : 'n/a',
|
|
359
|
+
groups_metadata: Number.isFinite(lastQuery.groups_metadata)
|
|
360
|
+
? lastQuery.groups_metadata.toFixed(2)
|
|
361
|
+
: 'n/a',
|
|
362
|
+
},
|
|
363
|
+
queues: {
|
|
364
|
+
messages: Math.round(queues.messages || 0),
|
|
365
|
+
chats: Math.round(queues.chats || 0),
|
|
366
|
+
lid_map: Math.round(queues.lid_map || 0),
|
|
367
|
+
},
|
|
368
|
+
upsertEvents: {
|
|
369
|
+
append: Math.round(upsertEvents.append || 0),
|
|
370
|
+
notify: Math.round(upsertEvents.notify || 0),
|
|
371
|
+
},
|
|
372
|
+
upsertMessages: {
|
|
373
|
+
append: Math.round(upsertMessages.append || 0),
|
|
374
|
+
notify: Math.round(upsertMessages.notify || 0),
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
} finally {
|
|
378
|
+
clearTimeout(timeout);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
export async function handlePingCommand({ sock, remoteJid, messageInfo, expirationMessage }) {
|
|
383
|
+
try {
|
|
384
|
+
const startedAt = Date.now();
|
|
385
|
+
const systemMetrics = getSystemMetrics();
|
|
386
|
+
let metricsSummary = null;
|
|
387
|
+
let metricsOk = false;
|
|
388
|
+
let metricsError = null;
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
metricsSummary = await fetchMetricsSnapshot();
|
|
392
|
+
metricsOk = true;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
metricsError = error.message;
|
|
395
|
+
logger.warn('Falha ao buscar métricas Prometheus para /ping.', { error: error.message });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const text = buildPingMessage({
|
|
399
|
+
systemMetrics,
|
|
400
|
+
metricsSummary,
|
|
401
|
+
metricsOk,
|
|
402
|
+
metricsError,
|
|
403
|
+
latencyMs: Date.now() - startedAt,
|
|
404
|
+
generatedAt: new Date(),
|
|
405
|
+
});
|
|
406
|
+
await sendAndStore(
|
|
407
|
+
sock,
|
|
408
|
+
remoteJid,
|
|
409
|
+
{ text },
|
|
410
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
411
|
+
);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
logger.error('Erro ao gerar status do sistema:', { error: error.message });
|
|
414
|
+
await sendAndStore(
|
|
415
|
+
sock,
|
|
416
|
+
remoteJid,
|
|
417
|
+
{ text: 'Erro ao obter informações do sistema.' },
|
|
418
|
+
{ quoted: messageInfo, ephemeralExpiration: expirationMessage },
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|