UNPKG

sharding-stats

Version:

A cool Dashboard & Manager for showing Live Stats and managing your Discord Bot/Shards

275 lines (255 loc) 11.7 kB
const Strategy = require("passport-discord"); const passport = require("passport"); const Events = require('events'); const express = require('express'); const path = require('path'); const form = require('../Structures/formdata.js'); const Schema = require("../Structures/schema.js"); const Code = require('./code.js') const FormData = new form(); const session = require("express-session"); const url = require("url"); const MemoryStore = require("memorystore")(session); class Server extends Events { constructor(app, config = {}) { super() this.app = app; this.totalClusters = 0; this.totalShards = 0; this.config = config; this.killShard = new Map(); this.code = new Code(config); this._validateOptions(); this._buildApi(); if (!this.config?.selfHost) { this._applytoApp(); this._buildRoute(); } } /** * @returns {import("../Structures/jsDocStyles.js").StatsDataReturnData} */ getStatsData() { const shardData = FormData.shardData(0, { all: true }); const totalData = FormData.totalData(); return { raw: { shards: shardData, total: totalData }, pretty: { shards: FormData.humanize(shardData), total: FormData.humanize(totalData), } } } getConfig(type) { if (this.config.selfHost) { throw new Error(`You cannot you this with selfhost`) } if (type == `server`) { return this.app; } else if (type == `dashboard`) { return this.config; } else if (type == `all`) { let data = { dash: this.config, server: this.app } return data; } }; /** * @typedef {import("../Structures/jsDocStyles.js").rawShardsData} rawShardsData * @returns { {cluster:number, shards: rawShardsData[] }[]} */ chunkShardsToClusterArrays() { /** @type {import("../Structures/jsDocStyles.js").rawShardsData} */ const shardData = FormData.shardData(0, { all: true }); let returnData = []; if (!shardData?.length || typeof shardData?.[0]?.cluster === "undefined") return returnData; for (const element of shardData) { const clusterId = typeof element.cluster !== "undefined" && !isNaN(element.cluster) ? Number(element.cluster) : shardData?.filter(x => typeof x.cluster !== "undefined" && !isNaN(x.cluster)).length > 0 ? -1 : undefined; const index = returnData.findIndex(x => x.cluster === clusterId); if (index < 0) returnData.push({ cluster: clusterId, shards: [element] }); else returnData[index].shards.push(element); } returnData = returnData.sort((a, b) => a.cluster - b.cluster); returnData.map(x => { x.shards = x.shards.sort((a, b) => a.id - b.id) return x; }) return returnData; } _applytoApp() { this.app.use(session({ store: new MemoryStore({ checkPeriod: 86400000 }), secret: `#@%#&^$^$%@$^$&%#$%@#$%$^%&$%^#$%@#$%#E%#%@$FEErfgr3g#%GT%536c53cc6%5%tv%4y4hrgrggrgrgf4nas`, resave: false, saveUninitialized: false })) this.app.use(express.static(path.join(__dirname, `../Frontend`))); this.app.engine('html', require('ejs').renderFile); this.app.set('view engine', 'ejs'); this.app.set('views', path.join(__dirname, `../Frontend`)); passport.serializeUser((user, done) => done(null, user)) passport.deserializeUser((obj, done) => done(null, obj)) passport.use(new Strategy({ clientID: this.config.bot.client_id, clientSecret: this.config.bot.client_secret, callbackURL: this.config.redirect_uri, scope: this.config.scope }, (accessToken, refreshToken, profile, done) => { process.nextTick(() => done(null, profile)) })) this.app.use(passport.initialize()); this.app.use(passport.session()); } _buildApi() { this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); this.app.post('/stats', (req, res) => { try { if (!req.headers.authorization) return res.status(404).end() const authProvided = req.headers.authorization const authKey = Buffer.from(this.config.authorizationkey).toString('base64'); if (authKey !== authProvided) return res.status(404).end(); if (req.body?.totalClusters && !isNaN(req.body.totalClusters)) this.totalClusters = Number(req.body.totalClusters); if (req.body?.totalShards && !isNaN(req.body.totalShards)) this.totalShards = Number(req.body.totalShards); if (this.totalClusters || this.totalShards) checkShards(this.totalShards, this.totalClusters) const rawdata = new Schema(req.body).toObject(); FormData._patch(rawdata); setTimeout(() => { this._checkIfShardAlive(rawdata.id) }, this.config.markShardsDeadafter) if (this.killShard.has(rawdata.id)) { this.killShard.delete(rawdata.id) res.send({ kill: true, shard: rawdata.id }) } else { res.send({ status: ` Successfully completed the opration` }) } return res.end(); } catch (error) { this.emit('error', error) } }) } _buildRoute() { const checkAuth = (req, res, next) => { if (req.isAuthenticated()) return next(); req.session.backURL = req.url; res.redirect("/login"); } this.app.get("/login", (req, res, next) => { if (req.session.backURL) { req.session.backURL = req.session.backURL } else if (req.headers.referer) { const parsed = url.parse(req.headers.referer); if (parsed.hostname == this.app.locals.domain) { req.session.backURL = parsed.path } } else { req.session.backURL = "/" } next(); }, passport.authenticate("discord", { prompt: "none" })); this.app.get(`/callback`, passport.authenticate(`discord`, { failureRedirect: "/" }), async (req, res) => { if (this.config.bannedUsers?.includes?.(req?.user?.id)) { req.session.destroy(() => { res.render('denied') req.logout(); }); } else { res.redirect(`/`) } }); this.app.get('/', checkAuth, async (req, res) => { const shardData = FormData.shardData(0, { all: true }) const totalData = FormData.totalData() res.render('index', { shards: FormData.humanize(shardData), total: FormData.humanize(totalData), posts: FormData.humanize(FormData.shardData(0, { all: true })), config: this.config, data: FormData.humanize(FormData.totalData()), user: req.user }) }) this.app.get('/stats', checkAuth, async (req, res) => { const shardData = FormData.shardData(0, { all: true }) const totalData = FormData.totalData() // console.log(totalData) if (!this.config.owners.includes(req.user.id)) { return res.render('denied') } res.render('starter', { shards: FormData.humanize(shardData), total: FormData.humanize(totalData), posts: FormData.humanize(FormData.shardData(0, { all: true })), config: this.config, data: FormData.humanize(FormData.totalData()), user: req.user, humanize_time: (ms) => { if (ms < 0) ms = -ms; const time = { "day": Math.floor(ms / 86400000), "hr": Math.floor(ms / 3600000) % 24, "min": Math.floor(ms / 60000) % 60, "sec": Math.floor(ms / 1000) % 60, }; return Object.entries(time) .filter(val => val[1] !== 0) .map(val => val[1] + ' ' + (val[1] !== 1 ? val[0] + 's' : val[0])) .join(', '); } }) }) this.app.get("/status", (req, res) => { try { const shardData = FormData.shardData(0, { all: true }) const totalData = FormData.totalData() res.send({ shards: FormData.humanize(shardData), total: FormData.humanize(totalData) }); return; } catch (error) { this.emit('error', error) } }) this.app.get("/api/shard", (req, res) => { try { const shardid = req.query.shardid; let data = FormData.shardData(Number(shardid)); if (!data) data = new Schema({ id: shardid }).toObject(); res.send(FormData.humanize(data)) return res.end(); } catch (error) { this.emit('error', error) } }) } _checkIfShardAlive(shardID) { const data = FormData.shardData(Number(shardID)); if (!data) return; const diff = Number(data.lastupdated + this.config.markShardsDeadafter - 1000); if (diff > Date.now()) return; data.upsince = 0; data.status = 5; data.message = `Died | No Message Recieved`; FormData._patch(data); return; } _validateOptions() { if (!this.config.bot) throw new Error(`Missing Bot Parameters such as redirect_uri, client_secret...`) if (!this.config.bot.client_id) throw new Error(`Missing Parameter: client_id has not been provided`) if (!this.config.bot.client_secret) throw new Error(`Missing Parameter: client_secret has not been provided`) if (!this.config.stats_uri) throw new Error(`Missing Parameter: stats_uri has not been provided`) if (!this.config.redirect_uri) throw new Error(`Missing Parameter: redirect_uri has not been provided`) if (!this.config.owners?.length) throw new Error(`Missing Parameter: owners["owner-DC-Id"] has not been provided`); if (!this.config.scope) this.config.scope = ['identify']; if (!this.config.postinterval) this.config.postinterval = 2500; if (!this.config.markShardsDeadafter) this.config.markShardsDeadafter = 10000; if (!this.config.loginExpire) this.config.loginExpire = 60000 * 15; if (!this.config.login_path) this.config.login_path = ''; if (this.config.postinterval > this.config.markShardsDeadafter) throw new Error(`Post Interval can not be bigger than the "maskShardasDeadafter" Argument`) } } module.exports = Server;