disstat
Version:
Post data from a discord.js client automatically, or manually to DisStat for Discord bot statistic tracking.
144 lines (119 loc) • 4.75 kB
JavaScript
const baseURL = "https://disstat-api.tomatenkuchen.com/v1/"
const autopostInterval = 90000
const { EventEmitter } = require("node:events")
const os = require("node:os")
class DisStat extends EventEmitter {
constructor(apiKeyInput = "", botInput = "") {
super()
this.apiKey = ""
this.botId = ""
this.bot = {}
this.autoposting = false
this.noManualWarningSent = false
this.unpostedCustom = []
if (!apiKeyInput) throw new TypeError("No DisStat API key provided as first argument. You can find the API key on the Manage Bot page of your bot.")
if (!apiKeyInput.startsWith("DS-")) console.warn("[DisStat " + new Date().toLocaleTimeString() + "] The provided API key as first argument doesn't start with \"DS-\".")
this.botId = typeof botInput == "object" ? botInput.user.id : botInput
if (!this.botId) throw new TypeError("Missing (falsy) Discord bot ID provided, but a discord.js bot client or ID is required as second argument")
this.apiKey = apiKeyInput
if (typeof botInput == "object") {
this.startUsage = process.cpuUsage()
this.startTime = Date.now()
this.prevUsage = {}
this.autoposting = true
this.bot = botInput
setTimeout(() => this.autopost(), 30000)
}
}
async autopost() {
this.emit("autopostStart")
const data = {}
if (this.unpostedCustom.length > 0) data.custom = this.unpostedCustom
if (this.bot) {
data.guilds = this.bot.guilds.cache.size
data.shards = this.bot.shard ? this.bot.shard.count : 0
data.users = this.bot.guilds.cache.filter(guild => guild.available).reduce((acc, cur) => acc + cur.memberCount, 0)
data.apiPing = this.bot.ws && this.bot.ws.ping > 0 ? this.bot.ws.ping : null
}
data.ramUsage = process.memoryUsage.rss()
const endUsage = process.cpuUsage()
const elapTime = endUsage.user - this.startUsage.user + endUsage.system - this.startUsage.system
const elapTimeMS = Date.now() - this.startTime
this.startUsage = process.cpuUsage()
this.startTime = Date.now()
data.cpu = parseFloat((100 * elapTime / (1000 * elapTimeMS * os.cpus().length)).toFixed(2))
// TODO: Bandwidth usage
this.noManualWarningSent = true
let result = {}
try {
result = await this.postData(data)
} catch (e) {
this.emit("autopostError", result, data)
console.warn("[DisStat " + new Date().toLocaleTimeString() + "] Failed to post data to DisStat API. Error: " + e.message, result)
setTimeout(() => this.autopost(), autopostInterval)
return
}
setTimeout(() => this.autopost(), autopostInterval)
this.unpostedCustom = []
this.emit("autopostSuccess", data)
}
async getBot(botIdInput = "") {
const response = await fetch(baseURL + "bot/" + (botIdInput || this.botId), {
headers: {
Authorization: this.apiKey,
Accept: "application/json"
}
})
return await response.json()
}
async postData(data = {}) {
if (this.autoposting && !this.noManualWarningSent) {
console.warn("[DisStat " + new Date().toLocaleTimeString() +
"] You are using autoposting, but still manually posting data. This is not recommended, as it can cause duplication and data loss due to overwriting.")
this.noManualWarningSent = true
}
if (!data || typeof data != "object" || Object.keys(data).length == 0) throw new TypeError("No data object provided to postData().")
const response = await fetch(baseURL + "bot/" + this.botId, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: this.apiKey
},
body: JSON.stringify(data)
}).catch(e => {
this.emit("post", e, data)
return e
})
if (response.status == 204) this.emit("post", void 0, data)
else {
const json = await response.json()
this.emit("post", Object.keys(json).length == 0 ? void 0 : json, data)
return json
}
}
postCommand(command = "", userId = void 0, guildId = void 0) {
if (!command || command.trim() == "") return new TypeError("No command name provided to postCommand().")
this.postCustom("command", command, userId, guildId)
}
postCustom(type = "", value1 = void 0, value2 = void 0, value3 = void 0) {
if (!type || type.trim() == "") return new TypeError("No custom graph type provided to postCustom().")
const body = {
type,
value1,
value2,
value3
}
if (this.autoposting) this.unpostedCustom.push(body)
else fetch(baseURL + "bot/" + this.botId + "/custom", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: this.apiKey
},
body: JSON.stringify(body)
})
}
}
module.exports = DisStat