99 lines
3.8 KiB
JavaScript
99 lines
3.8 KiB
JavaScript
const { CronJob } = require('cron');
|
|
const fs = require('node:fs/promises');
|
|
const logger = require('../logger.js');
|
|
const resolvePath = require('../pathResolver.js');
|
|
const { SERVER_ID, EXTENDED_LOGS, CACHE_FILE } = require('../../config.js').MAIN;
|
|
|
|
const RESOLVED_CACHE_FILE = resolvePath(CACHE_FILE);
|
|
|
|
const pad = n => n.toString().padStart(2, '0');
|
|
const formatHourRange = h => `${pad(h)}:00-${pad(h)}:59`;
|
|
const pluralizeReport = n => (n === 1 ? 'report' : 'reports');
|
|
|
|
const summaryEmbed = async () => {
|
|
const now = new Date();
|
|
const midnightLocal = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const yesterday = new Date(midnightLocal.getTime() - 86400000);
|
|
const yesterdayString = `${yesterday.getFullYear()}-${pad(yesterday.getMonth() + 1)}-${pad(yesterday.getDate())}`;
|
|
const baseTs = Math.floor(Date.UTC(yesterday.getFullYear(), yesterday.getMonth(), yesterday.getDate()) / 1000);
|
|
|
|
if (EXTENDED_LOGS) {
|
|
logger.info(`Daily summary » Using base timestamp: ${baseTs} (${new Date(baseTs * 1000).toISOString()}) for ${yesterdayString}`);
|
|
}
|
|
|
|
let data;
|
|
if (SERVER_ID === 'development') {
|
|
logger.info('Daily summary » Using test data instead of reading cache file [DEV]');
|
|
const generateRandomIp = () => Array(4).fill(0).map(() => Math.floor(Math.random() * 256)).join('.');
|
|
const generateTimestamps = () => baseTs + Math.floor(Math.random() * 86400);
|
|
const entries = Array.from({ length: Math.floor(Math.random() * 1001) + 4000 }, () => `${generateRandomIp()} ${generateTimestamps()}`);
|
|
data = entries.join('\n');
|
|
} else {
|
|
try {
|
|
await fs.access(RESOLVED_CACHE_FILE);
|
|
data = (await fs.readFile(RESOLVED_CACHE_FILE, 'utf8')).trim();
|
|
if (!data) return logger.warn(`Daily summary » Cache file exists but is empty: ${RESOLVED_CACHE_FILE}`);
|
|
} catch (err) {
|
|
return logger.warn(`Daily summary » Failed to access/read cache file: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
try {
|
|
const hourlySummary = {};
|
|
const seen = new Set();
|
|
|
|
for (const line of data.split('\n')) {
|
|
const [ip, tsStr] = line.split(' ');
|
|
const ts = parseInt(tsStr, 10);
|
|
if (!ip || isNaN(ts)) continue;
|
|
|
|
const key = `${ip}_${ts}`;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
|
|
const date = new Date(ts * 1000);
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
if (dateStr !== yesterdayString) continue;
|
|
|
|
const hour = date.getUTCHours();
|
|
hourlySummary[hour] = (hourlySummary[hour] || 0) + 1;
|
|
}
|
|
|
|
if (!Object.keys(hourlySummary).length) {
|
|
return logger.warn(`Daily summary » No reports found for ${yesterdayString}`);
|
|
}
|
|
|
|
const sortedChrono = Object.entries(hourlySummary)
|
|
.map(([h, c]) => [parseInt(h), c])
|
|
.sort((a, b) => a[0] - b[0]);
|
|
|
|
const top3Sorted = [...Object.entries(hourlySummary)]
|
|
.map(([h, c]) => [parseInt(h), c])
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 3);
|
|
|
|
const top3 = top3Sorted.map(([h]) => h);
|
|
const total = sortedChrono.reduce((sum, [, c]) => sum + c, 0);
|
|
const summaryStr = sortedChrono
|
|
.map(([h, c]) => `${formatHourRange(h)} → ${c} ${pluralizeReport(c)}${top3.includes(h) ? ' 🔥' : ''}`)
|
|
.join('\n');
|
|
logger.info(`Midnight. Summary of IP address reports (${total}) from ${yesterdayString}:\n${summaryStr}`);
|
|
|
|
const peakStr = top3Sorted
|
|
.map(([h, c]) => `${formatHourRange(h)} → ${c} ${pluralizeReport(c)}`)
|
|
.join('\n');
|
|
logger.info(`Top 3 peaks:\n${peakStr}`);
|
|
|
|
await logger.webhook(
|
|
`Midnight. Summary of IP address reports from yesterday.\nGood night to you, sleep well! 😴\n### ${total} ${pluralizeReport(total)} from ${yesterdayString}\n\`\`\`${summaryStr}\`\`\`\n### Top 3 peaks\n\`\`\`${peakStr}\`\`\``,
|
|
0x00FF39
|
|
);
|
|
} catch (err) {
|
|
logger.error(`Daily summary » Unexpected error:\n${err.stack}`);
|
|
}
|
|
};
|
|
|
|
module.exports = async () => {
|
|
// await summaryEmbed();
|
|
new CronJob('0 0 * * *', summaryEmbed, null, true);
|
|
}; |