diff --git a/README.md b/README.md index 59a2f02..9829ffe 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,7 @@ menu question max length cannot be higher than question options ports <1024 require root -- TODO: topic and question answer values not encrypted? -- TODO: post stats -- TODO: settings bundle download - TODO: update notifications -- TODO: check inline to-dos creation requires an interaction: diff --git a/package.json b/package.json index 3b387dc..280ff73 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "keyv": "^4.5.0", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", + "md5": "^2.3.0", "ms": "^2.1.3", "node-dir": "^0.1.17", "node-emoji": "^1.11.0", diff --git a/src/index.js b/src/index.js index 35b5791..9a71ee7 100644 --- a/src/index.js +++ b/src/index.js @@ -63,7 +63,7 @@ const config = YAML.parse(fs.readFileSync('./user/config.yml', 'utf8')); const log = logger(config); process.on('unhandledRejection', error => { - log.notice(`Discord Tickets v${pkg.version} on Node.js v${process.versions.node} (${process.platform})`); + log.notice(`Discord Tickets v${pkg.version} on Node.js ${process.version} (${process.platform})`); log.notice('An error was not caught'); if (error instanceof Error) log.warn(`Uncaught ${error.name}`); log.error(error); diff --git a/src/lib/misc.js b/src/lib/misc.js new file mode 100644 index 0000000..8b0d15a --- /dev/null +++ b/src/lib/misc.js @@ -0,0 +1 @@ +module.exports.msToMins = ms => Number((ms / 1000 / 60).toFixed(1)); \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 9287648..ca75621 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -1,5 +1,8 @@ const { Listener } = require('@eartharoid/dbf'); +const md5 = require('md5'); const ms = require('ms'); +const { version } = require('../../../package.json'); +const { msToMins } = require('../../lib/misc'); module.exports = class extends Listener { constructor(client, options) { @@ -114,5 +117,51 @@ module.exports = class extends Listener { setPresence(); if (client.config.presence.activities.length > 1) setInterval(() => setPresence(), client.config.presence.interval * 1000); + + if (client.config.stats) { + const send = async () => { + const tickets = await client.prisma.ticket.findMany({ + select: { + createdAt: true, + firstResponseAt: true, + }, + }); + const closedTickets = tickets.filter(t => t.closedAt); + const users = await client.prisma.user.findMany({ select: { messageCount: true } }); + const stats = { + activated_users: users.length, + arch: process.arch, + avg_resolution_time: msToMins(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avg_response_time: msToMins(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + categories: await client.prisma.category.count(), + database: process.env.DB_PROVIDER, + guilds: client.guilds.cache.size, + id: md5(client.user.id), + members: client.guilds.cache.reduce((t, g) => t + g.memberCount, 0), + messages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted, + node: process.version, + os: process.platform, + tags: await client.prisma.tag.count(), + tickets: tickets.length, + version, + }; + try { + const res = await fetch('https://stats.discordtickets.app/api/v3/houston', { + body: JSON.stringify(stats), + headers: { 'content-type': 'application/json' }, + method: 'POST', + }); + if (!res.ok) throw res; + client.log.success('Posted client stats'); + client.log.verbose(stats); + client.log.debug(res); + } catch (error) { + client.log.error('An error occurred whilst posting stats', stats, error); + } + }; + + send(); + setInterval(() => send(), ms('12h')); + } } }; diff --git a/user/example.config.yml b/user/example.config.yml index f549d7a..b823c24 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -31,4 +31,5 @@ presence: interval: 20 # seconds, only used if activities.length > 1 status: online # online|idle|invisible|dnd overrides: - disableArchives: false \ No newline at end of file + disableArchives: false +stats: true \ No newline at end of file