diff --git a/src/structure/components/commands/developer/Stats.js b/src/structure/components/commands/developer/Stats.js new file mode 100644 index 0000000..d3b3241 --- /dev/null +++ b/src/structure/components/commands/developer/Stats.js @@ -0,0 +1,206 @@ +const os = require('os'); +const { Util } = require('../../../../utilities'); +const { SlashCommand } = require('../../../interfaces'); + +class StatsCommand extends SlashCommand { + + constructor(client) { + + super(client, { + name: 'stats', + module: 'developer', + aliases: [ ], + description: 'Statistics about the bot', + restricted: true, + arguments: [ + { + name: 'log', + type: 'BOOLEAN', + types: ['FLAG'], + description: 'Logs the output in the console.' + } + ], + clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'], + showUsage: false, + archivable: false + }); + + } + + async execute(invoker) { + + // TODO: + // Add some stuff that only shows when a dev runs the command, for instance amount of cached messages etc + const { shard } = this.client; + const author = await invoker.userWrapper(); + + //Shards eval + const evalFunc = (client) => { + return { + users: client.users.cache.size, + guilds: client.guilds.cache.size, + channels: client.channels.cache.size, + memory: Math.floor(process.memoryUsage().heapUsed / 1024 / 1024), + uptime: client.uptime + }; + }; + + //Manager eval + const mEvalFunc = (client) => { + return { + memory: Math.floor(process.memoryUsage().heapUsed / 1024 / 1024), + uptime: Date.now() - client.readyAt + }; + }; + + const shardResults = await shard.broadcastEval(evalFunc).catch((error) => this.client.logger.error(error)); + const managerResult = await this.client.managerEval(mEvalFunc).catch((error) => this.client.logger.error(error)); + + //Manager values + const mainValues = { + uptime: Util.humanise(Math.floor(managerResult.uptime / 1000)), + memory: managerResult.memory, + shards: shard.count, + library: require('discord.js').version + }; + + //Current shard + const shardValues = { + cachedUsers: this.client.users.cache.size, + guilds: this.client.guilds.cache.size, + channels: this.client.channels.cache.size, + uptime: Util.humanise(Math.floor(this.client.uptime / 1000)), + memory: Math.floor(process.memoryUsage().heapUsed / 1024 / 1024) + }; + + //Compile shard data + const totalValues = shardResults.reduce((acc, curr) => { + Object.entries(curr).forEach(([key, val]) => { + if (!acc[key]) acc[key] = 0; + acc[key] += val; + }); + return acc; + }, {}); + totalValues.uptime = Util.humanise(Math.floor(totalValues.uptime / shard.count / 1000)); + totalValues.avgMem = Math.floor(totalValues.memory / shard.count); + + //System information + const CPU = os.cpus(); + const memoryFree = (os.freemem() / 1024 / 1024 / 1024).toFixed(1); + const memoryTotal = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1); + + const sysInfo = { + devs: await this.client.resolveUsers(this.client._options.discord.developers).then((owners) => { + return owners.map((o) => o.tag).join(', '); + }), + cpu: CPU[0].model, + cores: CPU.length, + memoryUsed: (memoryTotal - memoryFree).toFixed(1), + memoryTotal, + osType: os.type(), + uptime: Math.floor(Date.now()/1000) - os.uptime(), + hostname: os.hostname() + }; + + //Command statistics - probably expand this further + const { commands } = this.client.registry; + + const commandValues = { + invokes: commands.reduce((acc, cmd) => acc + cmd._invokes.success + cmd._invokes.fail, 0), + success: commands.reduce((acc, cmd) => acc + cmd._invokes.success, 0), + fail: commands.reduce((acc, cmd) => acc + cmd._invokes.fail, 0), + successExecTime: Math.round(commands.reduce((acc, cmd) => acc + cmd._invokes.successTime, 0) / commands.size), + failExecTime: Math.round(commands.reduce((acc, cmd) => acc + cmd._invokes.failTime, 0) / commands.size), + }; + commandValues.avgTime = Math.round((commandValues.successExecTime + commandValues.failExecTime) / 2); + + const embed = { + title: invoker.format('COMMAND_STATS_TITLE', { + client: this.client.user.tag, + version: require('../../../../../package.json').version + }), + description: invoker.format('COMMAND_STATS_SYSTEM_VALUE', sysInfo), + fields: [ + { + name: invoker.format('COMMAND_STATS_MAIN'), + value: invoker.format('COMMAND_STATS_MAIN_VALUE', mainValues), + inline: true + }, + { + name: invoker.format('COMMAND_STATS_CURRENT_SHARD', { shard: this.client.shardId }), + value: invoker.format('COMMAND_STATS_CURRENT_SHARDS_VALUE', shardValues), + inline: true + }, + { + name: invoker.format('COMMAND_STATS_TOTAL'), + value: invoker.format('COMMAND_STATS_TOTAL_VALUE', totalValues), + inline: true + }, + { + name: invoker.format('COMMAND_STATS_COMMANDS'), + value: invoker.format('COMMAND_STATS_COMMANDS_VALUE', commandValues), + inline: true + } + ] + }; + + if (author.developer) { + + //Mongo DB + const stats = await this.client.mongodb.stats(); + const dbStats = { + db: stats.db, + collections: stats.collections, + objects: stats.objects, + avgObjSize: (stats.avgObjSize / 1024 / 1024).toFixed(1), + totalSize: (stats.dataSize / 1024 / 1024 / 1024).toFixed(2) + }; + + embed.fields.push({ + name: invoker.format('COMMAND_STATS_MONGODB'), + value: invoker.format('COMMAND_STATS_MONGODB_VALUE', dbStats), + inline: true + }); + + //Other + const evalFunc2 = () => { + let msg = 0, + mem = 0; + // eslint-disable-next-line no-return-assign + this.channels.cache.forEach((c) => { + msg += c.messages?.cache.size || 0; + }); + this.guilds.cache.forEach((g) => { + mem += g.members.cache.size; + }); + return { + cachedMessages: msg, + cachedMembers: mem + }; + }; + const result = await shard.broadcastEval(evalFunc2).catch((error) => this.client.logger.error(error.stack)); + const other = result.reduce((acc, curr) => { + Object.entries(curr).forEach(([key, val]) => { + if (!acc[key]) acc[key] = 0; + acc[key] += val; + }); + return acc; + }, {}); + other.memberAvg = Math.floor(other.cachedMembers / shard.count); + other.messageAvg = Math.floor(other.cachedMessages / shard.count); + + embed.fields.push({ + name: invoker.format('COMMAND_STATS_CACHE'), + value: invoker.format('COMMAND_STATS_CACHE_VALUE', other), + inline: true + }); + + } + + return invoker.reply({ embed }); + + } + +} + +module.exports = StatsCommand; \ No newline at end of file