rashi-discord-bot-lib
Version:
π Powerful Discord bot framework with built-in database, event handling, and utilities
363 lines β’ 17.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BotStatistics = void 0;
const cli_table3_1 = __importDefault(require("cli-table3"));
const Logger_1 = require("./Logger");
async function loadChalk() {
try {
const imp = new Function('m', 'return import(m)');
const mod = await imp('chalk');
const chalk = mod.default ?? mod;
return {
bold: {
blue: (s) => chalk.bold.blue(s),
green: (s) => chalk.bold.green(s),
magenta: (s) => chalk.bold.magenta(s),
yellow: (s) => chalk.bold.yellow(s),
cyan: (s) => chalk.bold.cyan(s),
red: (s) => chalk.bold.red(s),
},
blue: (s) => chalk.blue(s),
green: (s) => chalk.green(s),
magenta: (s) => chalk.magenta(s),
cyan: (s) => chalk.cyan(s),
yellow: (s) => chalk.yellow(s),
red: (s) => chalk.red(s),
white: (s) => chalk.white(s),
};
}
catch {
const id = (x) => x;
return {
bold: { blue: id, green: id, magenta: id, yellow: id, cyan: id, red: id },
blue: id, green: id, magenta: id, cyan: id, yellow: id, red: id, white: id,
};
}
}
/* -------------------------------------------------------------------------- */
/* CLASS */
/* -------------------------------------------------------------------------- */
class BotStatistics {
musicStats;
logger;
startTime;
commandUsage;
eventTriggers;
printedOnce = false; // β¬
οΈ nowoΕΔ
totalCommandsExecuted = 0;
totalEventsTriggered = 0;
responseTimes = [];
errorCount = 0;
totalRequests = 0;
// Liczniki zaΕadowanych elementΓ³w (nie wykonanych)
loadedSlashCommands = 0;
loadedPrefixCommands = 0;
loadedEvents = 0;
// Pojedyncze staty bazy (wsteczna kompatybilnoΕΔ)
dbStats;
// NOWE: lista storeβΓ³w (Verse + Mongo + co chcesz)
dbStores = [];
constructor(logger) {
this.logger = logger ?? new Logger_1.Logger();
this.startTime = new Date();
this.commandUsage = new Map();
this.eventTriggers = new Map();
}
/* ------------ DB: SETTERY ------------- */
// Stare API β dla zgodnoΕci
setDatabaseStats(stats) {
this.dbStats = stats;
}
setMusicStats(stats) {
this.musicStats = stats;
}
// Nowe API β wiele storeβΓ³w
setDatabaseStores(stores) {
this.dbStores = stores ?? [];
}
resetPrintedFlag() { this.printedOnce = false; }
// Wygodny helper dla VerseDB
async loadDatabaseStatsFrom(db) {
if (!db) {
this.dbStats = undefined;
return;
}
try {
const stats = await db.getStats?.();
if (!stats) {
this.dbStats = undefined;
return;
}
this.dbStats = {
keys: stats.keys ?? 0,
size: stats.size ?? 0,
adapter: stats.adapter ?? 'unknown',
};
}
catch {
this.dbStats = undefined;
}
}
/* ------------- LIFECYCLE -------------- */
initialize(client) {
this.setupEventTracking(client);
}
setLoadedCommands(slashCount, prefixCount) {
this.loadedSlashCommands = slashCount;
this.loadedPrefixCommands = prefixCount;
}
setLoadedEvents(eventCount) {
this.loadedEvents = eventCount;
}
/* ------------- TRACKING --------------- */
trackCommand(commandName, type, responseTime) {
const current = this.commandUsage.get(commandName) ?? { count: 0, type };
this.commandUsage.set(commandName, { count: current.count + 1, type });
this.totalCommandsExecuted++;
this.totalRequests++;
if (typeof responseTime === 'number') {
this.responseTimes.push(responseTime);
if (this.responseTimes.length > 100)
this.responseTimes.shift();
}
}
trackEvent(eventName) {
const current = this.eventTriggers.get(eventName) ?? 0;
this.eventTriggers.set(eventName, current + 1);
this.totalEventsTriggered++;
}
trackError() {
this.errorCount++;
this.totalRequests++;
}
/* ------------- RENDER ----------------- */
async generateTableStats(client, options, databaseStats) {
if (this.printedOnce)
return; // β¬
οΈ blokada
this.printedOnce = true; // β¬
οΈ ustaw po wejΕciu
const chalk = await loadChalk();
const dbStats = databaseStats ?? this.dbStats; // fallback (single)
try {
const botInfoTable = new cli_table3_1.default({
head: [chalk.bold.blue('Bot Information'), chalk.bold.green('Value')],
colWidths: [25, 40],
style: { head: ['cyan'], border: ['grey'] },
});
const statsTable = new cli_table3_1.default({
head: [chalk.bold.magenta('Statistics'), chalk.bold.cyan('Count')],
colWidths: [25, 40],
style: { head: ['magenta'], border: ['grey'] },
});
// NOWE: tabela wielu storeβΓ³w
const databaseTable = new cli_table3_1.default({
head: [chalk.bold.green('Store'), chalk.bold.blue('Adapter'), chalk.bold.cyan('Details')],
colWidths: [18, 18, 58],
style: { head: ['green'], border: ['grey'] },
});
const uptime = this.formatUptime(Date.now() - this.startTime.getTime());
const mem = process.memoryUsage();
const rawPing = Math.round(client.ws.ping);
const pingText = rawPing >= 0 ? `${rawPing}ms` : 'N/A';
// πΆ MUSIC table
const musicTable = new cli_table3_1.default({
head: [chalk.bold.green('Music'), chalk.bold.blue('Value')],
colWidths: [25, 55],
style: { head: ['green'], border: ['grey'] },
});
// π§ PATCH: MusicManager integration for Shoukaku (fixes "Disabled / Not initialized")
const music = client.music;
if (!this.musicStats && music?.getNodesStatus) {
try {
const nodes = music.getNodesStatus();
const connected = nodes.filter((n) => n.state === 'CONNECTED').length;
const total = music?.config?.nodes?.length ?? 0;
this.setMusicStats({
enabled: true,
library: music.kazagumo ? 'Kazagumo + Shoukaku' : 'Shoukaku',
nodesConnected: connected,
nodesTotal: total,
defaultSearch: music?.config?.defaultSearchEngine ?? 'unknown',
});
}
catch (err) {
this.setMusicStats({
enabled: true,
library: 'Shoukaku',
});
}
}
if (this.musicStats) {
const ms = this.musicStats;
musicTable.push([chalk.green('Status'), chalk.white(ms.enabled ? 'Enabled' : 'Disabled')], ...(ms.library ? [[chalk.green('Library'), chalk.cyan(ms.library)]] : []), ...(ms.defaultSearch ? [[chalk.green('Default Search'), chalk.cyan(ms.defaultSearch)]] : []), ...((ms.nodesConnected != null || ms.nodesTotal != null)
? [[chalk.green('Nodes'), chalk.white(`${ms.nodesConnected ?? 0}/${ms.nodesTotal ?? 0}`)]]
: []), ...(ms.players != null ? [[chalk.green('Players'), chalk.white(String(ms.players))]] : []), ...(ms.playing != null ? [[chalk.green('Playing'), chalk.white(String(ms.playing))]] : []));
}
else {
musicTable.push([chalk.green('Status'), chalk.white('Disabled / Not initialized')]);
}
botInfoTable.push([chalk.cyan('Name'), chalk.white(client.user?.username ?? 'Unknown')], [chalk.cyan('ID'), chalk.white(client.user?.id ?? 'Unknown')], [chalk.cyan('Tag'), chalk.white(client.user?.tag ?? 'Unknown')], [chalk.cyan('Uptime'), chalk.green(uptime)], [chalk.cyan('Started At'), chalk.white(this.startTime.toLocaleString())], [chalk.cyan('Node.js'), chalk.white(process.version)]);
statsTable.push([chalk.magenta('Guilds'), chalk.yellow(String(client.guilds.cache.size))], [chalk.magenta('Users'), chalk.yellow(String(client.users.cache.size))], [chalk.magenta('Channels'), chalk.yellow(String(client.channels.cache.size))], [chalk.magenta('Events Loaded'), chalk.green(String(this.loadedEvents))], [chalk.magenta('Events Triggered'), chalk.blue(String(this.totalEventsTriggered))], [chalk.magenta('Slash Commands'), chalk.cyan(String(this.loadedSlashCommands))], [chalk.magenta('Prefix Commands'), chalk.cyan(String(this.loadedPrefixCommands))], [chalk.magenta('Commands Executed'), chalk.red(String(this.totalCommandsExecuted))]);
// Render listy storeβΓ³w (preferowane)
if (this.dbStores.length) {
for (const s of this.dbStores) {
const details = [
`status: ${s.status === 'connected' ? 'Connected' : 'Down'}`,
s.keys != null ? `keys: ${s.keys}` : null,
s.size != null ? `size: ${s.size} bytes` : null,
s.info ?? null,
].filter(Boolean).join(' β’ ');
databaseTable.push([chalk.white(s.name), chalk.cyan(s.adapter), chalk.white(details)]);
}
}
else if (dbStats) {
// Fallback: pojedyncze staty (stare API)
const details = [
'status: Connected',
dbStats.keys != null ? `keys: ${dbStats.keys}` : null,
dbStats.size != null ? `size: ${dbStats.size} bytes` : null,
].filter(Boolean).join(' β’ ');
databaseTable.push([chalk.white('Database'), chalk.cyan(dbStats.adapter), chalk.white(details)]);
}
else {
databaseTable.push([chalk.white('Database'), '-', chalk.red('status: Not connected')]);
}
if (options?.BotInfo) {
const settingsTable = new cli_table3_1.default({
head: [chalk.bold.green('Settings'), chalk.bold.blue('Value')],
colWidths: [25, 55],
style: { head: ['green'], border: ['grey'] },
});
const bi = options.BotInfo;
if (bi.ownerId)
settingsTable.push([chalk.green('Bot Owner'), chalk.white(`<@${bi.ownerId}>`)]);
if (bi.botInvite)
settingsTable.push([chalk.green('Bot Invite'), chalk.blue(bi.botInvite)]);
if (bi.serverInvite)
settingsTable.push([chalk.green('Server Invite'), chalk.blue(bi.serverInvite)]);
if (dbStats)
settingsTable.push([chalk.green('Database'), chalk.cyan(`${dbStats.adapter} (${dbStats.keys} keys)`)]);
if (databaseStats)
settingsTable.push([chalk.green('Database'), chalk.cyan(`${databaseStats.adapter} (${databaseStats.keys} keys)`)]);
console.log(chalk.green('\nβοΈ Bot Settings & Configuration:\n'));
console.log(settingsTable.toString());
}
const mostUsedCommand = this.getMostUsedCommand();
if (mostUsedCommand) {
const commandTable = new cli_table3_1.default({
head: [chalk.bold.cyan('Most Used Command'), chalk.bold.yellow('Details')],
colWidths: [25, 40],
style: { head: ['cyan'], border: ['grey'] },
});
commandTable.push([chalk.cyan('Command Name'), chalk.white(mostUsedCommand.name)], [chalk.cyan('Type'), chalk.yellow(mostUsedCommand.type)], [chalk.cyan('Uses'), chalk.red(String(mostUsedCommand.uses))]);
console.log(chalk.cyan('\nπ Command Champion:\n'));
console.log(commandTable.toString());
}
console.log(chalk.blue('\nπ€ Bot Information:\n'));
console.log(botInfoTable.toString());
console.log(chalk.magenta('\nπ Statistics Overview:\n'));
console.log(statsTable.toString());
console.log(chalk.cyan('\nπΆ Music:\n'));
console.log(musicTable.toString());
const i18nTable = new cli_table3_1.default({
head: [chalk.bold.blue('π i18n (Translation System)'), chalk.bold.green('Value')],
colWidths: [30, 50],
style: { head: ['blue'], border: ['grey'] },
});
console.log(chalk.blue('\nπ Translation System (i18n):\n'));
console.log(i18nTable.toString());
console.log(chalk.green('\nποΈ Database:\n'));
console.log(databaseTable.toString());
}
catch (err) {
this.logger.warn('cli-table3/chalk not available, falling back to simple display');
console.log(await this.generateStatsDisplay(client));
}
}
async generateStatsDisplay(client) {
const uptime = this.formatUptime(Date.now() - this.startTime.getTime());
const mem = process.memoryUsage();
return `
ββββββββββββββββββββββββββββββββββββββββ
β BOT STATISTICS β
β βββββββββββββββββββββββββββββββββββββββ£
β Bot Information β
β β’ Name: ${(client.user?.username ?? 'Unknown').padEnd(28)}β
β β’ Uptime: ${uptime.padEnd(25)}β
β β’ Guilds: ${String(client.guilds.cache.size).padEnd(26)}β
β β’ Users: ${String(client.users.cache.size).padEnd(27)}β
β β’ Channels: ${String(client.channels.cache.size).padEnd(24)}β
β βββββββββββββββββββββββββββββββββββββββ£
β Commands & Events β
β β’ Slash Commands: ${String(this.loadedSlashCommands).padEnd(18)}β
β β’ Prefix Commands: ${String(this.loadedPrefixCommands).padEnd(17)}β
β β’ Events Loaded: ${String(this.loadedEvents).padEnd(19)}β
β β’ Commands Executed: ${String(this.totalCommandsExecuted).padEnd(15)}β
β βββββββββββββββββββββββββββββββββββββββ£
β Performance β
β β’ Memory: ${this.formatBytes(mem.heapUsed).padEnd(23)}β
β β’ API Latency: ${String(Math.round(client.ws.ping)).padEnd(19)}msβ
β β’ Error Rate: ${this.getErrorRate().toFixed(1).padEnd(20)}%β
ββββββββββββββββββββββββββββββββββββββββ
`.trim();
}
/* ------------- HELPERY ---------------- */
get loadedEventsCount() { return this.loadedEvents; }
get loadedSlashCommandsCount() { return this.loadedSlashCommands; }
get loadedPrefixCommandsCount() { return this.loadedPrefixCommands; }
setupEventTracking(client) {
const originalEmit = client.emit.bind(client);
client.emit = (event, ...args) => {
this.trackEvent(event);
return originalEmit(event, ...args);
};
}
getMostUsedCommand() {
let mostUsed = null;
let maxUses = 0;
this.commandUsage.forEach((data, name) => {
if (data.count > maxUses) {
maxUses = data.count;
mostUsed = { name, uses: data.count, type: data.type };
}
});
return mostUsed;
}
getAverageResponseTime() {
if (this.responseTimes.length === 0)
return 0;
const sum = this.responseTimes.reduce((a, b) => a + b, 0);
return Math.round(sum / this.responseTimes.length);
}
getErrorRate() {
if (this.totalRequests === 0)
return 0;
return (this.errorCount / this.totalRequests) * 100;
}
formatBytes(bytes) {
if (bytes === 0)
return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const val = (bytes / Math.pow(k, i)).toFixed(2);
return `${val} ${sizes[i]}`;
}
formatUptime(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0)
return `${days}d ${hours % 24}h ${minutes % 60}m`;
if (hours > 0)
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
if (minutes > 0)
return `${minutes}m ${seconds % 60}s`;
return `${seconds}s`;
}
}
exports.BotStatistics = BotStatistics;
//# sourceMappingURL=BotStatistics.js.map