@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,734 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import { URL } from 'node:url';
|
|
5
|
+
import client from 'prom-client';
|
|
6
|
+
import logger from '../utils/logger/loggerModule.js';
|
|
7
|
+
|
|
8
|
+
const parseEnvBool = (value, fallback) => {
|
|
9
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
10
|
+
const normalized = String(value).trim().toLowerCase();
|
|
11
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
12
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
13
|
+
return fallback;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const parseEnvNumber = (value, fallback) => {
|
|
17
|
+
const parsed = Number(value);
|
|
18
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const parseThresholds = (value, fallback) => {
|
|
22
|
+
if (!value) return fallback;
|
|
23
|
+
const parsed = String(value)
|
|
24
|
+
.split(',')
|
|
25
|
+
.map((item) => Number(item.trim()))
|
|
26
|
+
.filter((item) => Number.isFinite(item) && item > 0);
|
|
27
|
+
return parsed.length ? parsed : fallback;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const METRICS_ENABLED = parseEnvBool(process.env.METRICS_ENABLED, true);
|
|
31
|
+
const METRICS_PORT = parseEnvNumber(process.env.METRICS_PORT, 9102);
|
|
32
|
+
const METRICS_HOST = process.env.METRICS_HOST || '0.0.0.0';
|
|
33
|
+
const METRICS_PATH = process.env.METRICS_PATH || '/metrics';
|
|
34
|
+
const METRICS_SERVICE = process.env.METRICS_SERVICE_NAME || process.env.ECOSYSTEM_NAME || 'omnizap';
|
|
35
|
+
|
|
36
|
+
const QUERY_THRESHOLDS_MS = parseThresholds(process.env.DB_QUERY_ALERT_THRESHOLDS, [500, 1000]);
|
|
37
|
+
|
|
38
|
+
const registry = new client.Registry();
|
|
39
|
+
let metrics = null;
|
|
40
|
+
let server = null;
|
|
41
|
+
let serverStarted = false;
|
|
42
|
+
let stickerCatalogModulePromise = null;
|
|
43
|
+
|
|
44
|
+
const normalizeLabel = (value, fallback = 'unknown') => {
|
|
45
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
46
|
+
return String(value);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const ensureMetrics = () => {
|
|
50
|
+
if (!METRICS_ENABLED) return null;
|
|
51
|
+
if (metrics) return metrics;
|
|
52
|
+
|
|
53
|
+
registry.setDefaultLabels({ service: METRICS_SERVICE });
|
|
54
|
+
client.collectDefaultMetrics({ register: registry, prefix: 'omnizap_' });
|
|
55
|
+
|
|
56
|
+
metrics = {
|
|
57
|
+
dbQueryDurationMs: new client.Histogram({
|
|
58
|
+
name: 'omnizap_db_query_duration_ms',
|
|
59
|
+
help: 'Duracao de queries MySQL em ms',
|
|
60
|
+
buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
61
|
+
labelNames: ['type', 'table', 'status'],
|
|
62
|
+
registers: [registry],
|
|
63
|
+
}),
|
|
64
|
+
dbQueryTotal: new client.Counter({
|
|
65
|
+
name: 'omnizap_db_query_total',
|
|
66
|
+
help: 'Total de queries MySQL',
|
|
67
|
+
labelNames: ['type', 'table', 'status'],
|
|
68
|
+
registers: [registry],
|
|
69
|
+
}),
|
|
70
|
+
dbQueryOverMsTotal: new client.Counter({
|
|
71
|
+
name: 'omnizap_db_query_over_ms_total',
|
|
72
|
+
help: 'Total de queries acima de limiares em ms',
|
|
73
|
+
labelNames: ['threshold', 'type', 'table', 'status'],
|
|
74
|
+
registers: [registry],
|
|
75
|
+
}),
|
|
76
|
+
dbSlowQueriesTotal: new client.Counter({
|
|
77
|
+
name: 'omnizap_db_slow_queries_total',
|
|
78
|
+
help: 'Total de queries lentas (com base em DB_SLOW_QUERY_MS)',
|
|
79
|
+
labelNames: ['type', 'table'],
|
|
80
|
+
registers: [registry],
|
|
81
|
+
}),
|
|
82
|
+
dbWriteTotal: new client.Counter({
|
|
83
|
+
name: 'omnizap_db_write_total',
|
|
84
|
+
help: 'Total de writes por operacao',
|
|
85
|
+
labelNames: ['operation', 'table'],
|
|
86
|
+
registers: [registry],
|
|
87
|
+
}),
|
|
88
|
+
dbLastQueryDurationMs: new client.Gauge({
|
|
89
|
+
name: 'omnizap_db_last_query_duration_ms',
|
|
90
|
+
help: 'Duracao da ultima query observada em ms',
|
|
91
|
+
labelNames: ['type', 'table', 'status'],
|
|
92
|
+
registers: [registry],
|
|
93
|
+
}),
|
|
94
|
+
dbInFlight: new client.Gauge({
|
|
95
|
+
name: 'omnizap_db_in_flight',
|
|
96
|
+
help: 'Queries em voo no pool',
|
|
97
|
+
registers: [registry],
|
|
98
|
+
}),
|
|
99
|
+
errorsTotal: new client.Counter({
|
|
100
|
+
name: 'omnizap_errors_total',
|
|
101
|
+
help: 'Total de erros por escopo',
|
|
102
|
+
labelNames: ['scope'],
|
|
103
|
+
registers: [registry],
|
|
104
|
+
}),
|
|
105
|
+
queueDepth: new client.Gauge({
|
|
106
|
+
name: 'omnizap_queue_depth',
|
|
107
|
+
help: 'Backlog das filas internas',
|
|
108
|
+
labelNames: ['queue'],
|
|
109
|
+
registers: [registry],
|
|
110
|
+
}),
|
|
111
|
+
messagesUpsertDurationMs: new client.Histogram({
|
|
112
|
+
name: 'omnizap_messages_upsert_duration_ms',
|
|
113
|
+
help: 'Duracao de processamento de messages.upsert em ms',
|
|
114
|
+
buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
115
|
+
labelNames: ['type', 'status'],
|
|
116
|
+
registers: [registry],
|
|
117
|
+
}),
|
|
118
|
+
messagesUpsertTotal: new client.Counter({
|
|
119
|
+
name: 'omnizap_messages_upsert_total',
|
|
120
|
+
help: 'Total de eventos messages.upsert',
|
|
121
|
+
labelNames: ['type', 'status'],
|
|
122
|
+
registers: [registry],
|
|
123
|
+
}),
|
|
124
|
+
messagesUpsertMessagesTotal: new client.Counter({
|
|
125
|
+
name: 'omnizap_messages_upsert_messages_total',
|
|
126
|
+
help: 'Total de mensagens processadas por messages.upsert',
|
|
127
|
+
labelNames: ['type'],
|
|
128
|
+
registers: [registry],
|
|
129
|
+
}),
|
|
130
|
+
rpgPlayersTotal: new client.Counter({
|
|
131
|
+
name: 'rpg_players_total',
|
|
132
|
+
help: 'Total de jogadores criados no RPG Pokemon',
|
|
133
|
+
registers: [registry],
|
|
134
|
+
}),
|
|
135
|
+
rpgBattlesStartedTotal: new client.Counter({
|
|
136
|
+
name: 'rpg_battles_started_total',
|
|
137
|
+
help: 'Total de batalhas iniciadas no RPG Pokemon',
|
|
138
|
+
registers: [registry],
|
|
139
|
+
}),
|
|
140
|
+
rpgBattlesTotal: new client.Counter({
|
|
141
|
+
name: 'rpg_battles_total',
|
|
142
|
+
help: 'Total de batalhas iniciadas no RPG Pokemon (alias geral)',
|
|
143
|
+
registers: [registry],
|
|
144
|
+
}),
|
|
145
|
+
rpgCapturesTotal: new client.Counter({
|
|
146
|
+
name: 'rpg_captures_total',
|
|
147
|
+
help: 'Total de capturas realizadas no RPG Pokemon',
|
|
148
|
+
registers: [registry],
|
|
149
|
+
}),
|
|
150
|
+
rpgCaptureAttemptsTotal: new client.Counter({
|
|
151
|
+
name: 'rpg_capture_attempts_total',
|
|
152
|
+
help: 'Total de tentativas de captura no RPG Pokemon',
|
|
153
|
+
labelNames: ['result'],
|
|
154
|
+
registers: [registry],
|
|
155
|
+
}),
|
|
156
|
+
rpgFleesTotal: new client.Counter({
|
|
157
|
+
name: 'rpg_flees_total',
|
|
158
|
+
help: 'Total de fugas de batalha no RPG Pokemon',
|
|
159
|
+
registers: [registry],
|
|
160
|
+
}),
|
|
161
|
+
rpgBattleDurationSeconds: new client.Histogram({
|
|
162
|
+
name: 'rpg_battle_duration_seconds',
|
|
163
|
+
help: 'Duração de batalhas do RPG em segundos',
|
|
164
|
+
buckets: [5, 10, 20, 30, 45, 60, 90, 120, 180, 240, 300, 600, 900],
|
|
165
|
+
labelNames: ['mode', 'outcome'],
|
|
166
|
+
registers: [registry],
|
|
167
|
+
}),
|
|
168
|
+
rpgActionsTotal: new client.Counter({
|
|
169
|
+
name: 'rpg_actions_total',
|
|
170
|
+
help: 'Total de ações/comandos do RPG Pokemon',
|
|
171
|
+
labelNames: ['action', 'status'],
|
|
172
|
+
registers: [registry],
|
|
173
|
+
}),
|
|
174
|
+
rpgSessionDurationSeconds: new client.Histogram({
|
|
175
|
+
name: 'rpg_session_duration_seconds',
|
|
176
|
+
help: 'Duração amostrada das sessões do RPG em segundos',
|
|
177
|
+
buckets: [5, 10, 20, 30, 60, 120, 300, 600, 900, 1800, 3600],
|
|
178
|
+
registers: [registry],
|
|
179
|
+
}),
|
|
180
|
+
rpgRaidsStartedTotal: new client.Counter({
|
|
181
|
+
name: 'rpg_raids_started_total',
|
|
182
|
+
help: 'Total de raids iniciadas no RPG',
|
|
183
|
+
registers: [registry],
|
|
184
|
+
}),
|
|
185
|
+
rpgRaidsCompletedTotal: new client.Counter({
|
|
186
|
+
name: 'rpg_raids_completed_total',
|
|
187
|
+
help: 'Total de raids concluídas no RPG',
|
|
188
|
+
registers: [registry],
|
|
189
|
+
}),
|
|
190
|
+
rpgPvpChallengesTotal: new client.Counter({
|
|
191
|
+
name: 'rpg_pvp_challenges_total',
|
|
192
|
+
help: 'Total de desafios PvP criados no RPG',
|
|
193
|
+
registers: [registry],
|
|
194
|
+
}),
|
|
195
|
+
rpgPvpCompletedTotal: new client.Counter({
|
|
196
|
+
name: 'rpg_pvp_completed_total',
|
|
197
|
+
help: 'Total de PvP concluídos no RPG',
|
|
198
|
+
registers: [registry],
|
|
199
|
+
}),
|
|
200
|
+
rpgPvpQueueTotal: new client.Counter({
|
|
201
|
+
name: 'rpg_pvp_queue_total',
|
|
202
|
+
help: 'Total de eventos da fila PvP (join/match/leave/expire)',
|
|
203
|
+
labelNames: ['status'],
|
|
204
|
+
registers: [registry],
|
|
205
|
+
}),
|
|
206
|
+
rpgTradesTotal: new client.Counter({
|
|
207
|
+
name: 'rpg_trades_total',
|
|
208
|
+
help: 'Total de trocas de jogadores no RPG',
|
|
209
|
+
labelNames: ['status'],
|
|
210
|
+
registers: [registry],
|
|
211
|
+
}),
|
|
212
|
+
rpgCoopCompletedTotal: new client.Counter({
|
|
213
|
+
name: 'rpg_coop_completed_total',
|
|
214
|
+
help: 'Total de missoes cooperativas concluídas',
|
|
215
|
+
registers: [registry],
|
|
216
|
+
}),
|
|
217
|
+
rpgWeeklyEventCompletedTotal: new client.Counter({
|
|
218
|
+
name: 'rpg_weekly_event_completed_total',
|
|
219
|
+
help: 'Total de eventos semanais de grupo concluídos',
|
|
220
|
+
registers: [registry],
|
|
221
|
+
}),
|
|
222
|
+
rpgKarmaVotesTotal: new client.Counter({
|
|
223
|
+
name: 'rpg_karma_votes_total',
|
|
224
|
+
help: 'Total de votos de karma no RPG',
|
|
225
|
+
labelNames: ['type'],
|
|
226
|
+
registers: [registry],
|
|
227
|
+
}),
|
|
228
|
+
rpgGroupRetentionRatio: new client.Histogram({
|
|
229
|
+
name: 'rpg_group_retention_ratio',
|
|
230
|
+
help: 'Retenção diária de usuários por grupo (0-1)',
|
|
231
|
+
buckets: [0, 0.1, 0.25, 0.4, 0.55, 0.7, 0.85, 1],
|
|
232
|
+
registers: [registry],
|
|
233
|
+
}),
|
|
234
|
+
rpgShinyFoundTotal: new client.Counter({
|
|
235
|
+
name: 'rpg_shiny_found_total',
|
|
236
|
+
help: 'Total de encontros shiny no RPG Pokemon',
|
|
237
|
+
registers: [registry],
|
|
238
|
+
}),
|
|
239
|
+
rpgEvolutionsTotal: new client.Counter({
|
|
240
|
+
name: 'rpg_evolutions_total',
|
|
241
|
+
help: 'Total de evolucoes de Pokemon no RPG',
|
|
242
|
+
registers: [registry],
|
|
243
|
+
}),
|
|
244
|
+
socialXpConvertedTotal: new client.Counter({
|
|
245
|
+
name: 'social_xp_converted_total',
|
|
246
|
+
help: 'XP social convertido em bonus de RPG',
|
|
247
|
+
labelNames: ['action'],
|
|
248
|
+
registers: [registry],
|
|
249
|
+
}),
|
|
250
|
+
socialXpConversionRate: new client.Histogram({
|
|
251
|
+
name: 'social_xp_conversion_rate',
|
|
252
|
+
help: 'Taxa de conversao aplicada no consumo de XP social',
|
|
253
|
+
buckets: [0, 0.1, 0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5],
|
|
254
|
+
labelNames: ['action'],
|
|
255
|
+
registers: [registry],
|
|
256
|
+
}),
|
|
257
|
+
socialXpCapHitsTotal: new client.Counter({
|
|
258
|
+
name: 'social_xp_cap_hits_total',
|
|
259
|
+
help: 'Total de vezes que o cap de XP social bloqueou ganho/conversao',
|
|
260
|
+
labelNames: ['scope'],
|
|
261
|
+
registers: [registry],
|
|
262
|
+
}),
|
|
263
|
+
pokeApiCacheHitTotal: new client.Counter({
|
|
264
|
+
name: 'pokeapi_cache_hit_total',
|
|
265
|
+
help: 'Total de cache hits no cliente da PokéAPI',
|
|
266
|
+
registers: [registry],
|
|
267
|
+
}),
|
|
268
|
+
stickerAutoPackCycleDurationMs: new client.Histogram({
|
|
269
|
+
name: 'omnizap_sticker_autopack_cycle_duration_ms',
|
|
270
|
+
help: 'Duracao do ciclo de auto-pack em ms',
|
|
271
|
+
buckets: [50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000],
|
|
272
|
+
registers: [registry],
|
|
273
|
+
}),
|
|
274
|
+
stickerAutoPackAssetsScannedTotal: new client.Counter({
|
|
275
|
+
name: 'omnizap_sticker_autopack_assets_scanned_total',
|
|
276
|
+
help: 'Total de assets escaneados no auto-pack',
|
|
277
|
+
registers: [registry],
|
|
278
|
+
}),
|
|
279
|
+
stickerAutoPackAssetsAddedTotal: new client.Counter({
|
|
280
|
+
name: 'omnizap_sticker_autopack_assets_added_total',
|
|
281
|
+
help: 'Total de assets adicionados em packs no auto-pack',
|
|
282
|
+
registers: [registry],
|
|
283
|
+
}),
|
|
284
|
+
stickerAutoPackDuplicateRate: new client.Gauge({
|
|
285
|
+
name: 'omnizap_sticker_autopack_duplicate_rate',
|
|
286
|
+
help: 'Taxa de duplicidade observada no ciclo de auto-pack (0-1)',
|
|
287
|
+
registers: [registry],
|
|
288
|
+
}),
|
|
289
|
+
stickerAutoPackRejectionRate: new client.Gauge({
|
|
290
|
+
name: 'omnizap_sticker_autopack_rejection_rate',
|
|
291
|
+
help: 'Taxa de rejeicao no ciclo de auto-pack (0-1)',
|
|
292
|
+
registers: [registry],
|
|
293
|
+
}),
|
|
294
|
+
stickerAutoPackFillRate: new client.Gauge({
|
|
295
|
+
name: 'omnizap_sticker_autopack_fill_rate',
|
|
296
|
+
help: 'Taxa de packs completos em relacao ao target_size (0-1)',
|
|
297
|
+
registers: [registry],
|
|
298
|
+
}),
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return metrics;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const loadStickerCatalogModule = async () => {
|
|
305
|
+
if (!stickerCatalogModulePromise) {
|
|
306
|
+
stickerCatalogModulePromise = import('../modules/stickerPackModule/stickerPackCatalogHttp.js');
|
|
307
|
+
}
|
|
308
|
+
return stickerCatalogModulePromise;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export const isMetricsEnabled = () => METRICS_ENABLED;
|
|
312
|
+
|
|
313
|
+
export const startMetricsServer = () => {
|
|
314
|
+
if (!METRICS_ENABLED || serverStarted) return;
|
|
315
|
+
ensureMetrics();
|
|
316
|
+
server = http.createServer(async (req, res) => {
|
|
317
|
+
const host = req.headers.host || `${METRICS_HOST}:${METRICS_PORT}`;
|
|
318
|
+
let parsedUrl;
|
|
319
|
+
try {
|
|
320
|
+
parsedUrl = new URL(req.url || '/', `http://${host}`);
|
|
321
|
+
} catch {
|
|
322
|
+
parsedUrl = new URL(req.url || '/', 'http://localhost');
|
|
323
|
+
}
|
|
324
|
+
const pathname = parsedUrl.pathname;
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const stickerCatalogModule = await loadStickerCatalogModule();
|
|
328
|
+
const handledByCatalog = await stickerCatalogModule.maybeHandleStickerCatalogRequest(req, res, {
|
|
329
|
+
pathname,
|
|
330
|
+
url: parsedUrl,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
if (handledByCatalog) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
} catch (error) {
|
|
337
|
+
logger.error('Erro ao inicializar rotas web de sticker packs.', {
|
|
338
|
+
error: error.message,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!pathname.startsWith(METRICS_PATH)) {
|
|
343
|
+
res.statusCode = 404;
|
|
344
|
+
res.end('Not Found');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const body = await registry.metrics();
|
|
350
|
+
res.statusCode = 200;
|
|
351
|
+
res.setHeader('Content-Type', registry.contentType);
|
|
352
|
+
res.end(body);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
res.statusCode = 500;
|
|
355
|
+
res.end('Metrics error');
|
|
356
|
+
logger.error('Erro ao gerar /metrics', { error: error.message });
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
server.listen(METRICS_PORT, METRICS_HOST, () => {
|
|
361
|
+
serverStarted = true;
|
|
362
|
+
logger.info('Servidor /metrics iniciado', {
|
|
363
|
+
host: METRICS_HOST,
|
|
364
|
+
port: METRICS_PORT,
|
|
365
|
+
path: METRICS_PATH,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
loadStickerCatalogModule()
|
|
369
|
+
.then((module) => {
|
|
370
|
+
const config = typeof module.getStickerCatalogConfig === 'function' ? module.getStickerCatalogConfig() : null;
|
|
371
|
+
if (!config?.enabled) return;
|
|
372
|
+
logger.info('Catalogo web de sticker packs habilitado', {
|
|
373
|
+
web_path: config.webPath,
|
|
374
|
+
api_base_path: config.apiBasePath,
|
|
375
|
+
orphan_api_path: config.orphanApiPath,
|
|
376
|
+
data_public_path: config.dataPublicPath,
|
|
377
|
+
data_public_dir: config.dataPublicDir,
|
|
378
|
+
host: METRICS_HOST,
|
|
379
|
+
port: METRICS_PORT,
|
|
380
|
+
});
|
|
381
|
+
})
|
|
382
|
+
.catch((error) => {
|
|
383
|
+
logger.warn('Nao foi possivel carregar configuracao do catalogo de sticker packs.', {
|
|
384
|
+
error: error.message,
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
server.on('error', (error) => {
|
|
390
|
+
logger.error('Falha ao iniciar servidor /metrics', { error: error.message });
|
|
391
|
+
});
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
export const stopMetricsServer = async () => {
|
|
395
|
+
if (!serverStarted || !server) return;
|
|
396
|
+
const current = server;
|
|
397
|
+
server = null;
|
|
398
|
+
serverStarted = false;
|
|
399
|
+
await new Promise((resolve, reject) => {
|
|
400
|
+
current.close((error) => {
|
|
401
|
+
if (error) {
|
|
402
|
+
reject(error);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
resolve();
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
export const recordError = (scope = 'app') => {
|
|
411
|
+
const m = ensureMetrics();
|
|
412
|
+
if (!m) return;
|
|
413
|
+
const labelScope = normalizeLabel(scope, 'app');
|
|
414
|
+
m.errorsTotal.inc({ scope: labelScope });
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export const setQueueDepth = (queue, depth) => {
|
|
418
|
+
const m = ensureMetrics();
|
|
419
|
+
if (!m) return;
|
|
420
|
+
const value = Number(depth);
|
|
421
|
+
if (!Number.isFinite(value)) return;
|
|
422
|
+
m.queueDepth.set({ queue: normalizeLabel(queue, 'unknown') }, value);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
export const setDbInFlight = (value) => {
|
|
426
|
+
const m = ensureMetrics();
|
|
427
|
+
if (!m) return;
|
|
428
|
+
const numeric = Number(value);
|
|
429
|
+
if (!Number.isFinite(numeric)) return;
|
|
430
|
+
m.dbInFlight.set(numeric);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
export const recordDbQuery = ({ durationMs, type, table, ok, isSlow }) => {
|
|
434
|
+
const m = ensureMetrics();
|
|
435
|
+
if (!m) return;
|
|
436
|
+
const duration = Number(durationMs);
|
|
437
|
+
if (!Number.isFinite(duration)) return;
|
|
438
|
+
|
|
439
|
+
const labels = {
|
|
440
|
+
type: normalizeLabel(type, 'OTHER'),
|
|
441
|
+
table: normalizeLabel(table, 'unknown'),
|
|
442
|
+
status: ok ? 'ok' : 'error',
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
m.dbQueryDurationMs.observe(labels, duration);
|
|
446
|
+
m.dbQueryTotal.inc(labels);
|
|
447
|
+
m.dbLastQueryDurationMs.set(labels, duration);
|
|
448
|
+
|
|
449
|
+
QUERY_THRESHOLDS_MS.forEach((threshold) => {
|
|
450
|
+
if (duration >= threshold) {
|
|
451
|
+
m.dbQueryOverMsTotal.inc({
|
|
452
|
+
threshold: String(threshold),
|
|
453
|
+
...labels,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if (isSlow) {
|
|
459
|
+
m.dbSlowQueriesTotal.inc({ type: labels.type, table: labels.table });
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
export const recordDbWrite = ({ operation, table }) => {
|
|
464
|
+
const m = ensureMetrics();
|
|
465
|
+
if (!m) return;
|
|
466
|
+
const op = normalizeLabel(operation, 'unknown');
|
|
467
|
+
const tableLabel = normalizeLabel(table, 'unknown');
|
|
468
|
+
m.dbWriteTotal.inc({ operation: op, table: tableLabel });
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
export const recordMessagesUpsert = ({ durationMs, type, messagesCount, ok }) => {
|
|
472
|
+
const m = ensureMetrics();
|
|
473
|
+
if (!m) return;
|
|
474
|
+
|
|
475
|
+
const duration = Number(durationMs);
|
|
476
|
+
const eventType = normalizeLabel(type, 'unknown');
|
|
477
|
+
const status = ok ? 'ok' : 'error';
|
|
478
|
+
|
|
479
|
+
if (Number.isFinite(duration)) {
|
|
480
|
+
m.messagesUpsertDurationMs.observe({ type: eventType, status }, duration);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
m.messagesUpsertTotal.inc({ type: eventType, status });
|
|
484
|
+
|
|
485
|
+
const count = Number(messagesCount);
|
|
486
|
+
if (Number.isFinite(count) && count > 0) {
|
|
487
|
+
m.messagesUpsertMessagesTotal.inc({ type: eventType }, count);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
export const recordRpgPlayerCreated = (value = 1) => {
|
|
492
|
+
const m = ensureMetrics();
|
|
493
|
+
if (!m) return;
|
|
494
|
+
const numeric = Number(value);
|
|
495
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
496
|
+
m.rpgPlayersTotal.inc(numeric);
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
export const recordRpgBattleStarted = (value = 1) => {
|
|
500
|
+
const m = ensureMetrics();
|
|
501
|
+
if (!m) return;
|
|
502
|
+
const numeric = Number(value);
|
|
503
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
504
|
+
m.rpgBattlesStartedTotal.inc(numeric);
|
|
505
|
+
m.rpgBattlesTotal.inc(numeric);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
export const recordRpgCapture = (value = 1) => {
|
|
509
|
+
const m = ensureMetrics();
|
|
510
|
+
if (!m) return;
|
|
511
|
+
const numeric = Number(value);
|
|
512
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
513
|
+
m.rpgCapturesTotal.inc(numeric);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
export const recordRpgCaptureAttempt = (result = 'failed', value = 1) => {
|
|
517
|
+
const m = ensureMetrics();
|
|
518
|
+
if (!m) return;
|
|
519
|
+
const numeric = Number(value);
|
|
520
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
521
|
+
m.rpgCaptureAttemptsTotal.inc({ result: normalizeLabel(result, 'failed') }, numeric);
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
export const recordRpgFlee = (value = 1) => {
|
|
525
|
+
const m = ensureMetrics();
|
|
526
|
+
if (!m) return;
|
|
527
|
+
const numeric = Number(value);
|
|
528
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
529
|
+
m.rpgFleesTotal.inc(numeric);
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
export const recordRpgBattleDuration = ({ mode = 'wild', outcome = 'unknown', seconds }) => {
|
|
533
|
+
const m = ensureMetrics();
|
|
534
|
+
if (!m) return;
|
|
535
|
+
const numeric = Number(seconds);
|
|
536
|
+
if (!Number.isFinite(numeric) || numeric < 0) return;
|
|
537
|
+
m.rpgBattleDurationSeconds.observe(
|
|
538
|
+
{
|
|
539
|
+
mode: normalizeLabel(mode, 'wild'),
|
|
540
|
+
outcome: normalizeLabel(outcome, 'unknown'),
|
|
541
|
+
},
|
|
542
|
+
numeric,
|
|
543
|
+
);
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
export const recordRpgAction = ({ action = 'unknown', status = 'ok', value = 1 }) => {
|
|
547
|
+
const m = ensureMetrics();
|
|
548
|
+
if (!m) return;
|
|
549
|
+
const numeric = Number(value);
|
|
550
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
551
|
+
m.rpgActionsTotal.inc(
|
|
552
|
+
{
|
|
553
|
+
action: normalizeLabel(action, 'unknown'),
|
|
554
|
+
status: normalizeLabel(status, 'ok'),
|
|
555
|
+
},
|
|
556
|
+
numeric,
|
|
557
|
+
);
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
export const recordRpgSessionDuration = (seconds) => {
|
|
561
|
+
const m = ensureMetrics();
|
|
562
|
+
if (!m) return;
|
|
563
|
+
const numeric = Number(seconds);
|
|
564
|
+
if (!Number.isFinite(numeric) || numeric < 0) return;
|
|
565
|
+
m.rpgSessionDurationSeconds.observe(numeric);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
export const recordRpgRaidStarted = (value = 1) => {
|
|
569
|
+
const m = ensureMetrics();
|
|
570
|
+
if (!m) return;
|
|
571
|
+
const numeric = Number(value);
|
|
572
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
573
|
+
m.rpgRaidsStartedTotal.inc(numeric);
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
export const recordRpgRaidCompleted = (value = 1) => {
|
|
577
|
+
const m = ensureMetrics();
|
|
578
|
+
if (!m) return;
|
|
579
|
+
const numeric = Number(value);
|
|
580
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
581
|
+
m.rpgRaidsCompletedTotal.inc(numeric);
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
export const recordRpgPvpChallenge = (value = 1) => {
|
|
585
|
+
const m = ensureMetrics();
|
|
586
|
+
if (!m) return;
|
|
587
|
+
const numeric = Number(value);
|
|
588
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
589
|
+
m.rpgPvpChallengesTotal.inc(numeric);
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
export const recordRpgPvpCompleted = (value = 1) => {
|
|
593
|
+
const m = ensureMetrics();
|
|
594
|
+
if (!m) return;
|
|
595
|
+
const numeric = Number(value);
|
|
596
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
597
|
+
m.rpgPvpCompletedTotal.inc(numeric);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
export const recordRpgPvpQueue = (status = 'join', value = 1) => {
|
|
601
|
+
const m = ensureMetrics();
|
|
602
|
+
if (!m) return;
|
|
603
|
+
const numeric = Number(value);
|
|
604
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
605
|
+
m.rpgPvpQueueTotal.inc({ status: normalizeLabel(status, 'join') }, numeric);
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
export const recordRpgTrade = (status = 'created', value = 1) => {
|
|
609
|
+
const m = ensureMetrics();
|
|
610
|
+
if (!m) return;
|
|
611
|
+
const numeric = Number(value);
|
|
612
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
613
|
+
m.rpgTradesTotal.inc({ status: normalizeLabel(status, 'created') }, numeric);
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
export const recordRpgCoopCompleted = (value = 1) => {
|
|
617
|
+
const m = ensureMetrics();
|
|
618
|
+
if (!m) return;
|
|
619
|
+
const numeric = Number(value);
|
|
620
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
621
|
+
m.rpgCoopCompletedTotal.inc(numeric);
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
export const recordRpgWeeklyEventCompleted = (value = 1) => {
|
|
625
|
+
const m = ensureMetrics();
|
|
626
|
+
if (!m) return;
|
|
627
|
+
const numeric = Number(value);
|
|
628
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
629
|
+
m.rpgWeeklyEventCompletedTotal.inc(numeric);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
export const recordRpgKarmaVote = (type = 'up', value = 1) => {
|
|
633
|
+
const m = ensureMetrics();
|
|
634
|
+
if (!m) return;
|
|
635
|
+
const numeric = Number(value);
|
|
636
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
637
|
+
m.rpgKarmaVotesTotal.inc({ type: normalizeLabel(type, 'up') }, numeric);
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
export const recordRpgGroupRetentionRatio = (ratio) => {
|
|
641
|
+
const m = ensureMetrics();
|
|
642
|
+
if (!m) return;
|
|
643
|
+
const numeric = Number(ratio);
|
|
644
|
+
if (!Number.isFinite(numeric) || numeric < 0) return;
|
|
645
|
+
m.rpgGroupRetentionRatio.observe(Math.min(1, numeric));
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
export const recordPokeApiCacheHit = (value = 1) => {
|
|
649
|
+
const m = ensureMetrics();
|
|
650
|
+
if (!m) return;
|
|
651
|
+
const numeric = Number(value);
|
|
652
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
653
|
+
m.pokeApiCacheHitTotal.inc(numeric);
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
export const recordRpgShinyFound = (value = 1) => {
|
|
657
|
+
const m = ensureMetrics();
|
|
658
|
+
if (!m) return;
|
|
659
|
+
const numeric = Number(value);
|
|
660
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
661
|
+
m.rpgShinyFoundTotal.inc(numeric);
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
export const recordRpgEvolution = (value = 1) => {
|
|
665
|
+
const m = ensureMetrics();
|
|
666
|
+
if (!m) return;
|
|
667
|
+
const numeric = Number(value);
|
|
668
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
669
|
+
m.rpgEvolutionsTotal.inc(numeric);
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
export const recordSocialXpConverted = ({ value = 1, action = 'unknown' } = {}) => {
|
|
673
|
+
const m = ensureMetrics();
|
|
674
|
+
if (!m) return;
|
|
675
|
+
const numeric = Number(value);
|
|
676
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return;
|
|
677
|
+
m.socialXpConvertedTotal.inc({ action: normalizeLabel(action, 'unknown') }, numeric);
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
export const recordSocialXpConversionRate = ({ rate, action = 'unknown' } = {}) => {
|
|
681
|
+
const m = ensureMetrics();
|
|
682
|
+
if (!m) return;
|
|
683
|
+
const numeric = Number(rate);
|
|
684
|
+
if (!Number.isFinite(numeric) || numeric < 0) return;
|
|
685
|
+
m.socialXpConversionRate.observe({ action: normalizeLabel(action, 'unknown') }, numeric);
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
export const recordSocialXpCapHit = ({ scope = 'earn' } = {}) => {
|
|
689
|
+
const m = ensureMetrics();
|
|
690
|
+
if (!m) return;
|
|
691
|
+
m.socialXpCapHitsTotal.inc({ scope: normalizeLabel(scope, 'earn') });
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
export const recordStickerAutoPackCycle = ({
|
|
695
|
+
durationMs,
|
|
696
|
+
assetsScanned = 0,
|
|
697
|
+
assetsAdded = 0,
|
|
698
|
+
duplicateRate = null,
|
|
699
|
+
rejectionRate = null,
|
|
700
|
+
fillRate = null,
|
|
701
|
+
} = {}) => {
|
|
702
|
+
const m = ensureMetrics();
|
|
703
|
+
if (!m) return;
|
|
704
|
+
|
|
705
|
+
const duration = Number(durationMs);
|
|
706
|
+
if (Number.isFinite(duration) && duration >= 0) {
|
|
707
|
+
m.stickerAutoPackCycleDurationMs.observe(duration);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const scanned = Number(assetsScanned);
|
|
711
|
+
if (Number.isFinite(scanned) && scanned > 0) {
|
|
712
|
+
m.stickerAutoPackAssetsScannedTotal.inc(scanned);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const added = Number(assetsAdded);
|
|
716
|
+
if (Number.isFinite(added) && added > 0) {
|
|
717
|
+
m.stickerAutoPackAssetsAddedTotal.inc(added);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const duplicate = Number(duplicateRate);
|
|
721
|
+
if (Number.isFinite(duplicate)) {
|
|
722
|
+
m.stickerAutoPackDuplicateRate.set(Math.max(0, Math.min(1, duplicate)));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const rejection = Number(rejectionRate);
|
|
726
|
+
if (Number.isFinite(rejection)) {
|
|
727
|
+
m.stickerAutoPackRejectionRate.set(Math.max(0, Math.min(1, rejection)));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const fill = Number(fillRate);
|
|
731
|
+
if (Number.isFinite(fill)) {
|
|
732
|
+
m.stickerAutoPackFillRate.set(Math.max(0, Math.min(1, fill)));
|
|
733
|
+
}
|
|
734
|
+
};
|