diff --git a/src/class/Client.ts b/src/class/Client.ts index 28470cc..5753a09 100644 --- a/src/class/Client.ts +++ b/src/class/Client.ts @@ -2,7 +2,7 @@ import eris from 'eris'; import mongoose from 'mongoose'; import { promises as fs } from 'fs'; import { Collection, Command, LocalStorage, Util, ServerManagement, Event } from '.'; -import { File, FileInterface, Member, MemberInterface, Moderation, ModerationInterface, PagerNumber, PagerNumberInterface, Rank, RankInterface, Redirect, RedirectInterface } from '../models'; +import { File, FileInterface, Member, MemberInterface, Moderation, ModerationInterface, PagerNumber, PagerNumberInterface, Rank, RankInterface, Redirect, RedirectInterface, Stat, StatInterface } from '../models'; import { Config } from '../../types'; // eslint-disable-line export default class Client extends eris.Client { @@ -18,18 +18,36 @@ export default class Client extends eris.Client { public serverManagement: ServerManagement; - public db: { File: mongoose.Model, Member: mongoose.Model, Moderation: mongoose.Model, PagerNumber: mongoose.Model, Rank: mongoose.Model, Redirect: mongoose.Model, local: { muted: LocalStorage } }; + public db: { File: mongoose.Model, Member: mongoose.Model, Moderation: mongoose.Model, PagerNumber: mongoose.Model, Rank: mongoose.Model, Redirect: mongoose.Model, Stat: mongoose.Model, local: { muted: LocalStorage } }; constructor(token: string, options?: eris.ClientOptions) { super(token, options); this.commands = new Collection(); this.events = new Collection(); this.intervals = new Collection(); - this.db = { File, Member, Moderation, PagerNumber, Rank, Redirect, local: { muted: new LocalStorage('muted') } }; + this.db = { File, Member, Moderation, PagerNumber, Rank, Redirect, Stat, local: { muted: new LocalStorage('muted') } }; } public async loadDatabase() { await mongoose.connect(this.config.mongoDB, { useNewUrlParser: true, useUnifiedTopology: true, poolSize: 50 }); + + const statMessages = await this.db.Stat.findOne({ name: 'messages' }); + const statCommands = await this.db.Stat.findOne({ name: 'commands' }); + const statPages = await this.db.Stat.findOne({ name: 'pages' }); + const statRequests = await this.db.Stat.findOne({ name: 'requests' }); + + if (!statMessages) { + await (new this.db.Stat({ name: 'messages', value: 0 }).save()); + } + if (!statCommands) { + await (new this.db.Stat({ name: 'commands', value: 0 }).save()); + } + if (!statPages) { + await (new this.db.Stat({ name: 'pages', value: 0 }).save()); + } + if (!statRequests) { + await (new this.db.Stat({ name: 'requests', value: 0 }).save()); + } } public loadPlugins() { diff --git a/src/class/Route.ts b/src/class/Route.ts index 29423e5..0d90eaa 100644 --- a/src/class/Route.ts +++ b/src/class/Route.ts @@ -20,6 +20,7 @@ export default class Route { public init() { this.router.all('*', (req, res, next) => { this.server.client.util.signale.log(`'${req.method}' request from '${req.ip}' to '${req.hostname}${req.path}'.`); + this.server.client.db.Stat.updateOne({ name: 'requests' }, { $inc: { value: 1 } }).exec(); if (this.conf.maintenance === true) res.status(503).json({ code: this.constants.codes.MAINTENANCE_OR_UNAVAILABLE, message: this.constants.messages.MAINTENANCE_OR_UNAVAILABLE }); else if (this.conf.deprecated === true) res.status(501).json({ code: this.constants.codes.DEPRECATED, message: this.constants.messages.DEPRECATED }); else next(); diff --git a/src/commands/index.ts b/src/commands/index.ts index 614abd0..3b77616 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -19,6 +19,7 @@ export { default as page } from './page'; export { default as ping } from './ping'; export { default as rank } from './rank'; export { default as roleinfo } from './roleinfo'; +export { default as stats } from './stats'; export { default as storemessages } from './storemessages'; export { default as unban } from './unban'; export { default as unmute } from './unmute'; diff --git a/src/commands/info.ts b/src/commands/info.ts index 8b994ff..17019df 100644 --- a/src/commands/info.ts +++ b/src/commands/info.ts @@ -4,6 +4,7 @@ import { Client, Command, RichEmbed } from '../class'; import { version as erisVersion } from '../../node_modules/eris/package.json'; import { version as expressVersion } from '../../node_modules/express/package.json'; import { version as mongooseVersion } from '../../node_modules/mongoose/package.json'; +import { version as tsVersion } from '../../node_modules/typescript/package.json'; export default class Info extends Command { constructor(client: Client) { @@ -21,6 +22,8 @@ export default class Info extends Command { embed.setTitle('Information'); embed.setThumbnail(this.client.user.avatarURL); embed.addField('Language(s)', '<:TypeScript:703451285789343774> TypeScript', true); + embed.addField('Runtime', `Node (${process.version})`, true); + embed.addField('Compilers/Transpilers', `TypeScript [tsc] (${tsVersion})`, true); embed.addField('Discord Library', `Eris (${erisVersion})`, true); embed.addField('HTTP Server Library', `Express (${expressVersion})`, true); embed.addField('Database Library', `MongoDB w/ Mongoose ODM (${mongooseVersion})`, true); diff --git a/src/commands/page.ts b/src/commands/page.ts index bcb32bc..dd60b54 100644 --- a/src/commands/page.ts +++ b/src/commands/page.ts @@ -146,6 +146,7 @@ export default class Page extends Command { html: `

Page

${options?.emergencyNumber ? `

[SEN#${options.emergencyNumber}]` : ''}Recipient PN: ${recipientNumber}
Sender PN: ${senderNumber} (${sender ? `${sender.username}#${sender.discriminator}` : ''})
Initial Command: https://discordapp.com/channels/${message.guild.id}/${message.channel.id}/${message.id} (<#${message.channel.id}>)

Pager Code: ${code} (${this.local.codeDict.get(code)})${txt ? `
Message: ${txt}` : ''}`, }); } + this.client.db.Stat.updateOne({ name: 'pages' }, { $inc: { value: 1 } }).exec(); return { status: true, message: `Page to \`${recipientNumber}\` sent.` }; } catch (err) { this.client.util.signale.error(err); diff --git a/src/commands/stats.ts b/src/commands/stats.ts new file mode 100644 index 0000000..6454ba3 --- /dev/null +++ b/src/commands/stats.ts @@ -0,0 +1,36 @@ +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Stats extends Command { + constructor(client: Client) { + super(client); + this.name = 'stats'; + this.description = 'Provides system statistics.'; + this.usage = `${this.client.config.prefix}stats`; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message) { + try { + const messages = await this.client.db.Stat.findOne({ name: 'messages' }); + const commands = await this.client.db.Stat.findOne({ name: 'commands' }); + const pages = await this.client.db.Stat.findOne({ name: 'pages' }); + const requests = await this.client.db.Stat.findOne({ name: 'requests' }); + + const embed = new RichEmbed(); + embed.setTitle('Statistics'); + embed.setThumbnail(this.client.user.avatarURL); + embed.addField('Messages Seen', `${messages.value}`, true); + embed.addField('Commands Executed', `${commands.value}`, true); + embed.addField('HTTP Requests Served', `${requests.value}`, true); + embed.addField('Pages Sent', `${pages.value}`, true); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/events/CommandHandler.ts b/src/events/CommandHandler.ts index 2c7b792..c563175 100644 --- a/src/events/CommandHandler.ts +++ b/src/events/CommandHandler.ts @@ -12,6 +12,7 @@ export default class CommandHandler extends Event { public async run(message: Message) { try { + this.client.db.Stat.updateOne({ name: 'messages' }, { $inc: { value: 1 } }).exec(); if (message.author.bot) return; if (message.content.indexOf(this.client.config.prefix) !== 0) return; const noPrefix: string[] = message.content.slice(this.client.config.prefix.length).trim().split(/ +/g); @@ -25,6 +26,7 @@ export default class CommandHandler extends Event { } this.client.util.signale.log(`User '${message.author.username}#${message.author.discriminator}' ran command '${resolved.cmd.name}' in '${message.channel.id}'.`); await resolved.cmd.run(message, resolved.args); + this.client.db.Stat.updateOne({ name: 'commands' }, { $inc: { value: 1 } }).exec(); } catch (err) { this.client.util.handleError(err, message); } diff --git a/src/models/Stat.ts b/src/models/Stat.ts new file mode 100644 index 0000000..7109699 --- /dev/null +++ b/src/models/Stat.ts @@ -0,0 +1,13 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface StatInterface extends Document { + name: 'messages' | 'commands' | 'pages' | 'requests', + value: number, +} + +const Stat: Schema = new Schema({ + name: String, + value: Number, +}); + +export default model('Stat', Stat); diff --git a/src/models/index.ts b/src/models/index.ts index 8bf8622..fe3f79d 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -4,3 +4,4 @@ export { default as Moderation, ModerationInterface } from './Moderation'; export { default as PagerNumber, PagerNumberInterface, PagerNumberRaw } from './PagerNumber'; export { default as Rank, RankInterface } from './Rank'; export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect'; +export { default as Stat, StatInterface } from './Stat';