waengine 1.7.3 → 1.7.4

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.
@@ -0,0 +1,458 @@
1
+ import { getStorage } from "./storage.js";
2
+
3
+ export class AnalyticsManager {
4
+ constructor(client) {
5
+ this.client = client;
6
+ this.storage = getStorage();
7
+ this.metrics = new Map();
8
+ this.alerts = [];
9
+ this.monitoring = {
10
+ cpu: 0,
11
+ memory: 0,
12
+ responseTime: 0,
13
+ uptime: Date.now()
14
+ };
15
+
16
+ // Start monitoring
17
+ this.startMonitoring();
18
+ }
19
+
20
+ // ===== MESSAGE ANALYTICS =====
21
+
22
+ /**
23
+ * Track message event
24
+ */
25
+ trackMessage(messageData) {
26
+ const timestamp = Date.now();
27
+ const date = new Date().toISOString().split('T')[0];
28
+ const hour = new Date().getHours();
29
+
30
+ // Daily stats
31
+ this.storage.write.in("analytics").increment(`daily.${date}.messages`, 1);
32
+ this.storage.write.in("analytics").increment(`hourly.${date}.${hour}`, 1);
33
+
34
+ // Message type stats
35
+ this.storage.write.in("analytics").increment(`messageTypes.${messageData.type}`, 1);
36
+
37
+ // User stats
38
+ if (messageData.from) {
39
+ this.storage.write.in("analytics").increment(`users.${messageData.from}.messages`, 1);
40
+ this.storage.write.in("analytics").set(`users.${messageData.from}.lastSeen`, timestamp);
41
+ }
42
+
43
+ // Group stats
44
+ if (messageData.isGroup) {
45
+ this.storage.write.in("analytics").increment(`groups.${messageData.chatId}.messages`, 1);
46
+ }
47
+
48
+ // Geographic data (if available)
49
+ if (messageData.location) {
50
+ this.storage.write.in("analytics").increment(`geographic.${messageData.location.country}`, 1);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Track command usage
56
+ */
57
+ trackCommand(command, userId, success = true) {
58
+ const date = new Date().toISOString().split('T')[0];
59
+
60
+ this.storage.write.in("analytics").increment(`commands.${command}.total`, 1);
61
+ this.storage.write.in("analytics").increment(`commands.${command}.daily.${date}`, 1);
62
+
63
+ if (success) {
64
+ this.storage.write.in("analytics").increment(`commands.${command}.success`, 1);
65
+ } else {
66
+ this.storage.write.in("analytics").increment(`commands.${command}.errors`, 1);
67
+ }
68
+
69
+ // User command stats
70
+ this.storage.write.in("analytics").increment(`users.${userId}.commands.${command}`, 1);
71
+ }
72
+
73
+ /**
74
+ * Track response time
75
+ */
76
+ trackResponseTime(duration) {
77
+ const date = new Date().toISOString().split('T')[0];
78
+
79
+ // Store response times for averaging
80
+ const responseTimes = this.storage.read.from("analytics").get(`responseTimes.${date}`) || [];
81
+ responseTimes.push(duration);
82
+
83
+ // Keep only last 1000 response times per day
84
+ if (responseTimes.length > 1000) {
85
+ responseTimes.shift();
86
+ }
87
+
88
+ this.storage.write.in("analytics").set(`responseTimes.${date}`, responseTimes);
89
+
90
+ // Update current monitoring
91
+ this.monitoring.responseTime = duration;
92
+ }
93
+
94
+ // ===== USER ANALYTICS =====
95
+
96
+ /**
97
+ * Get user engagement metrics
98
+ */
99
+ getUserEngagement(userId) {
100
+ const userData = this.storage.read.from("analytics").get(`users.${userId}`) || {};
101
+
102
+ return {
103
+ totalMessages: userData.messages || 0,
104
+ lastSeen: userData.lastSeen ? new Date(userData.lastSeen) : null,
105
+ commands: userData.commands || {},
106
+ isActive: userData.lastSeen && (Date.now() - userData.lastSeen) < 24 * 60 * 60 * 1000,
107
+ engagementScore: this.calculateEngagementScore(userData)
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Calculate user engagement score
113
+ */
114
+ calculateEngagementScore(userData) {
115
+ const messages = userData.messages || 0;
116
+ const commands = Object.values(userData.commands || {}).reduce((sum, count) => sum + count, 0);
117
+ const lastSeen = userData.lastSeen || 0;
118
+ const daysSinceLastSeen = (Date.now() - lastSeen) / (24 * 60 * 60 * 1000);
119
+
120
+ let score = 0;
121
+
122
+ // Message activity (0-40 points)
123
+ score += Math.min(messages * 0.1, 40);
124
+
125
+ // Command usage (0-30 points)
126
+ score += Math.min(commands * 0.5, 30);
127
+
128
+ // Recency (0-30 points)
129
+ if (daysSinceLastSeen < 1) score += 30;
130
+ else if (daysSinceLastSeen < 7) score += 20;
131
+ else if (daysSinceLastSeen < 30) score += 10;
132
+
133
+ return Math.round(score);
134
+ }
135
+
136
+ /**
137
+ * Get most active users
138
+ */
139
+ getMostActiveUsers(limit = 10) {
140
+ const users = this.storage.read.from("analytics").get("users") || {};
141
+
142
+ return Object.entries(users)
143
+ .map(([userId, data]) => ({
144
+ userId,
145
+ messages: data.messages || 0,
146
+ commands: Object.values(data.commands || {}).reduce((sum, count) => sum + count, 0),
147
+ lastSeen: data.lastSeen ? new Date(data.lastSeen) : null,
148
+ engagementScore: this.calculateEngagementScore(data)
149
+ }))
150
+ .sort((a, b) => b.engagementScore - a.engagementScore)
151
+ .slice(0, limit);
152
+ }
153
+
154
+ // ===== PERFORMANCE MONITORING =====
155
+
156
+ /**
157
+ * Start system monitoring
158
+ */
159
+ startMonitoring() {
160
+ setInterval(() => {
161
+ this.updateSystemMetrics();
162
+ this.checkAlerts();
163
+ }, 30000); // Every 30 seconds
164
+ }
165
+
166
+ /**
167
+ * Update system metrics
168
+ */
169
+ updateSystemMetrics() {
170
+ const memUsage = process.memoryUsage();
171
+
172
+ this.monitoring = {
173
+ cpu: process.cpuUsage(),
174
+ memory: {
175
+ used: memUsage.heapUsed,
176
+ total: memUsage.heapTotal,
177
+ percentage: (memUsage.heapUsed / memUsage.heapTotal) * 100
178
+ },
179
+ uptime: Date.now() - this.monitoring.uptime,
180
+ timestamp: Date.now()
181
+ };
182
+
183
+ // Store metrics
184
+ const date = new Date().toISOString().split('T')[0];
185
+ const hour = new Date().getHours();
186
+
187
+ this.storage.write.in("analytics").set(`performance.${date}.${hour}`, {
188
+ memory: this.monitoring.memory.percentage,
189
+ responseTime: this.monitoring.responseTime,
190
+ timestamp: Date.now()
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Check for performance alerts
196
+ */
197
+ checkAlerts() {
198
+ const alerts = [];
199
+
200
+ // Memory usage alert
201
+ if (this.monitoring.memory.percentage > 80) {
202
+ alerts.push({
203
+ type: 'memory',
204
+ level: 'warning',
205
+ message: `High memory usage: ${this.monitoring.memory.percentage.toFixed(1)}%`,
206
+ timestamp: Date.now()
207
+ });
208
+ }
209
+
210
+ // Response time alert
211
+ if (this.monitoring.responseTime > 5000) {
212
+ alerts.push({
213
+ type: 'response_time',
214
+ level: 'warning',
215
+ message: `Slow response time: ${this.monitoring.responseTime}ms`,
216
+ timestamp: Date.now()
217
+ });
218
+ }
219
+
220
+ // Store alerts
221
+ if (alerts.length > 0) {
222
+ this.alerts.push(...alerts);
223
+ this.storage.write.in("analytics").push("alerts", alerts);
224
+
225
+ // Emit alert events
226
+ alerts.forEach(alert => {
227
+ this.client.emit('performance_alert', alert);
228
+ });
229
+ }
230
+ }
231
+
232
+ // ===== DETAILED STATISTICS =====
233
+
234
+ /**
235
+ * Get comprehensive analytics report
236
+ */
237
+ getDetailedStats(days = 7) {
238
+ const endDate = new Date();
239
+ const startDate = new Date(endDate.getTime() - (days * 24 * 60 * 60 * 1000));
240
+
241
+ const stats = {
242
+ overview: this.getOverviewStats(),
243
+ messages: this.getMessageStats(days),
244
+ users: this.getUserStats(days),
245
+ commands: this.getCommandStats(days),
246
+ performance: this.getPerformanceStats(days),
247
+ geographic: this.getGeographicStats(),
248
+ timeDistribution: this.getTimeDistribution(days)
249
+ };
250
+
251
+ return stats;
252
+ }
253
+
254
+ /**
255
+ * Get overview statistics
256
+ */
257
+ getOverviewStats() {
258
+ const users = this.storage.read.from("analytics").get("users") || {};
259
+ const messageTypes = this.storage.read.from("analytics").get("messageTypes") || {};
260
+
261
+ return {
262
+ totalUsers: Object.keys(users).length,
263
+ activeUsers: Object.values(users).filter(user =>
264
+ user.lastSeen && (Date.now() - user.lastSeen) < 24 * 60 * 60 * 1000
265
+ ).length,
266
+ totalMessages: Object.values(messageTypes).reduce((sum, count) => sum + count, 0),
267
+ uptime: Date.now() - this.monitoring.uptime,
268
+ currentMemoryUsage: this.monitoring.memory.percentage,
269
+ averageResponseTime: this.monitoring.responseTime
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Get message statistics
275
+ */
276
+ getMessageStats(days) {
277
+ const messageTypes = this.storage.read.from("analytics").get("messageTypes") || {};
278
+ const dailyStats = [];
279
+
280
+ for (let i = 0; i < days; i++) {
281
+ const date = new Date(Date.now() - (i * 24 * 60 * 60 * 1000))
282
+ .toISOString().split('T')[0];
283
+ const dayMessages = this.storage.read.from("analytics").get(`daily.${date}.messages`) || 0;
284
+
285
+ dailyStats.push({
286
+ date,
287
+ messages: dayMessages
288
+ });
289
+ }
290
+
291
+ return {
292
+ byType: messageTypes,
293
+ daily: dailyStats.reverse(),
294
+ totalMessages: Object.values(messageTypes).reduce((sum, count) => sum + count, 0)
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Get user statistics
300
+ */
301
+ getUserStats(days) {
302
+ const users = this.storage.read.from("analytics").get("users") || {};
303
+ const activeUsers = Object.values(users).filter(user =>
304
+ user.lastSeen && (Date.now() - user.lastSeen) < days * 24 * 60 * 60 * 1000
305
+ );
306
+
307
+ return {
308
+ total: Object.keys(users).length,
309
+ active: activeUsers.length,
310
+ newUsers: this.getNewUsersCount(days),
311
+ topUsers: this.getMostActiveUsers(10),
312
+ engagementDistribution: this.getEngagementDistribution()
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Get command statistics
318
+ */
319
+ getCommandStats(days) {
320
+ const commands = this.storage.read.from("analytics").get("commands") || {};
321
+
322
+ return Object.entries(commands).map(([command, data]) => ({
323
+ command,
324
+ total: data.total || 0,
325
+ success: data.success || 0,
326
+ errors: data.errors || 0,
327
+ successRate: data.total ? ((data.success || 0) / data.total * 100).toFixed(1) : 0
328
+ })).sort((a, b) => b.total - a.total);
329
+ }
330
+
331
+ /**
332
+ * Get performance statistics
333
+ */
334
+ getPerformanceStats(days) {
335
+ const performanceData = [];
336
+
337
+ for (let i = 0; i < days; i++) {
338
+ const date = new Date(Date.now() - (i * 24 * 60 * 60 * 1000))
339
+ .toISOString().split('T')[0];
340
+
341
+ for (let hour = 0; hour < 24; hour++) {
342
+ const hourData = this.storage.read.from("analytics").get(`performance.${date}.${hour}`);
343
+ if (hourData) {
344
+ performanceData.push({
345
+ date,
346
+ hour,
347
+ memory: hourData.memory,
348
+ responseTime: hourData.responseTime
349
+ });
350
+ }
351
+ }
352
+ }
353
+
354
+ return {
355
+ data: performanceData.reverse(),
356
+ alerts: this.alerts.slice(-50), // Last 50 alerts
357
+ averageMemory: performanceData.reduce((sum, d) => sum + d.memory, 0) / performanceData.length || 0,
358
+ averageResponseTime: performanceData.reduce((sum, d) => sum + d.responseTime, 0) / performanceData.length || 0
359
+ };
360
+ }
361
+
362
+ /**
363
+ * Get geographic distribution
364
+ */
365
+ getGeographicStats() {
366
+ return this.storage.read.from("analytics").get("geographic") || {};
367
+ }
368
+
369
+ /**
370
+ * Get time distribution
371
+ */
372
+ getTimeDistribution(days) {
373
+ const hourlyData = {};
374
+
375
+ for (let i = 0; i < days; i++) {
376
+ const date = new Date(Date.now() - (i * 24 * 60 * 60 * 1000))
377
+ .toISOString().split('T')[0];
378
+
379
+ for (let hour = 0; hour < 24; hour++) {
380
+ const messages = this.storage.read.from("analytics").get(`hourly.${date}.${hour}`) || 0;
381
+ hourlyData[hour] = (hourlyData[hour] || 0) + messages;
382
+ }
383
+ }
384
+
385
+ return hourlyData;
386
+ }
387
+
388
+ // ===== HELPER METHODS =====
389
+
390
+ /**
391
+ * Get new users count
392
+ */
393
+ getNewUsersCount(days) {
394
+ const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
395
+ const users = this.storage.read.from("analytics").get("users") || {};
396
+
397
+ return Object.values(users).filter(user =>
398
+ user.firstSeen && user.firstSeen > cutoff
399
+ ).length;
400
+ }
401
+
402
+ /**
403
+ * Get engagement distribution
404
+ */
405
+ getEngagementDistribution() {
406
+ const users = this.storage.read.from("analytics").get("users") || {};
407
+ const distribution = { low: 0, medium: 0, high: 0 };
408
+
409
+ Object.values(users).forEach(userData => {
410
+ const score = this.calculateEngagementScore(userData);
411
+ if (score < 30) distribution.low++;
412
+ else if (score < 70) distribution.medium++;
413
+ else distribution.high++;
414
+ });
415
+
416
+ return distribution;
417
+ }
418
+
419
+ /**
420
+ * Export analytics data
421
+ */
422
+ exportAnalytics(format = 'json') {
423
+ const data = this.getDetailedStats(30); // 30 days
424
+
425
+ if (format === 'csv') {
426
+ return this.convertToCSV(data);
427
+ }
428
+
429
+ return data;
430
+ }
431
+
432
+ /**
433
+ * Convert data to CSV format
434
+ */
435
+ convertToCSV(data) {
436
+ // Simplified CSV conversion
437
+ const csv = [];
438
+
439
+ // Daily messages CSV
440
+ csv.push('Date,Messages');
441
+ data.messages.daily.forEach(day => {
442
+ csv.push(`${day.date},${day.messages}`);
443
+ });
444
+
445
+ return csv.join('\n');
446
+ }
447
+
448
+ /**
449
+ * Reset analytics data
450
+ */
451
+ resetAnalytics() {
452
+ this.storage.delete.from("analytics").all();
453
+ this.metrics.clear();
454
+ this.alerts = [];
455
+
456
+ return true;
457
+ }
458
+ }