diff --git a/events/ready.js b/events/ready.js index ab21a3a..1a5a0ed 100644 --- a/events/ready.js +++ b/events/ready.js @@ -1,55 +1,90 @@ -const { ActivityType } = require('discord.js') -const chalk = require('chalk') -const checkStatus = require('../handlers/checkStatus') +const { ActivityType } = require("discord.js"); +const chalk = require("chalk"); +const checkStatus = require("../handlers/checkStatus"); module.exports = { - name: 'ready', - once: true, - execute(client) { - console.log(chalk.cyan('[PteroStats] ') + chalk.green('Bot is up!')) - console.log(chalk.cyan('[PteroStats] ') + chalk.green('If you need support you can join our discord server https://discord.gg/zv6maQRah3')) - console.log(chalk.cyan('[PteroStats] ') + chalk.yellow('If some node is online but the embed is read as offline, please enable ') + chalk.green('log_error') + chalk.yellow(' on config file and report it at https://discord.gg/zv6maQRah3')) + name: "ready", + once: true, + execute(client) { + console.log(chalk.cyan("[PteroStats] ") + chalk.green("Bot is up!")); + console.log( + chalk.cyan("[PteroStats] ") + + chalk.green( + "If you need support you can join our discord server https://discord.gg/zv6maQRah3" + ) + ); + console.log( + chalk.cyan("[PteroStats] ") + + chalk.yellow( + "If some node is online but the embed is read as offline, please enable " + ) + + chalk.green("log_error") + + chalk.yellow( + " on config file and report it at https://discord.gg/zv6maQRah3" + ) + ); - if (client.guilds.cache.size < 1) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! This bot is not on any discord servers')) - if (client.config.timeout < 1) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Timeout cannot be less than 1 seconds!')) - client.config.timeout = 1 - } + if (client.guilds.cache.size < 1) + return console.log( + chalk.cyan("[PteroStats] ") + + chalk.red("Error! This bot is not on any discord servers") + ); + if (client.config.timeout < 1) { + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red("Timeout cannot be less than 1 seconds!") + ); + client.config.timeout = 1; + } - if (client.config.refresh > 1 && client.config.refresh < 10) console.log(chalk.cyan('[PteroStats] ') + chalk.red('Refresh Time below 10 seconds is not recommended!')) - else if (client.config.refresh < 1) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Refresh Time cannot be less than 1 seconds!')) - client.config.refresh = 10 - } + if (client.config.refresh > 1 && client.config.refresh < 10) + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red("Refresh Time below 10 seconds is not recommended!") + ); + else if (client.config.refresh < 1) { + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red("Refresh Time cannot be less than 1 seconds!") + ); + client.config.refresh = 10; + } - if (client.config.presence.text && client.config.presence.type) { - switch (client.config.presence.type.toLowerCase()) { - case 'playing': - client.config.presence.type = ActivityType.Playing - break; - case 'listening': - client.config.presence.type = ActivityType.Listening - break; - case 'competing': - client.config.presence.type = ActivityType.Competing - break; - default: - client.config.presence.type = ActivityType.Watching - } + if (client.config.presence.text && client.config.presence.type) { + switch (client.config.presence.type.toLowerCase()) { + case "playing": + client.config.presence.type = ActivityType.Playing; + break; + case "listening": + client.config.presence.type = ActivityType.Listening; + break; + case "competing": + client.config.presence.type = ActivityType.Competing; + break; + default: + client.config.presence.type = ActivityType.Watching; + } - client.user.setActivity(client.config.presence.text, { type: client.config.presence.type }) - } + client.user.setActivity(client.config.presence.text, { + type: client.config.presence.type, + }); + } - if (client.config.presence.status) { - if (!['idle', 'online', 'dnd', 'invisible'].includes(client.config.presence.status.toLowerCase())) client.config.presence.status = 'online' + if (client.config.presence.status) { + if ( + !["idle", "online", "dnd", "invisible"].includes( + client.config.presence.status.toLowerCase() + ) + ) + client.config.presence.status = "online"; - client.user.setStatus(client.config.presence.status); - } + client.user.setStatus(client.config.presence.status); + } - checkStatus({ client: client }) + checkStatus({ client: client }); - setInterval(() => { - checkStatus({ client: client }) - }, client.config.refresh * 1000) - } -} \ No newline at end of file + setInterval(async () => { + await checkStatus({ client: client }); + }, client.config.refresh * 1000); + }, +}; diff --git a/handlers/checkStatus.js b/handlers/checkStatus.js index 240782d..e5a2002 100644 --- a/handlers/checkStatus.js +++ b/handlers/checkStatus.js @@ -1,189 +1,343 @@ -const { EmbedBuilder } = require('discord.js') -const axios = require('axios') -const chalk = require('chalk') +const { EmbedBuilder } = require("discord.js"); +const axios = require("axios"); +const axiosRetry = require("axios-retry"); +const chalk = require("chalk"); -const postStatus = require('./postStatus') +const postStatus = require("./postStatus"); -module.exports = function checkStatus({ client }) { +axiosRetry(axios, { retries: 5 }); - function Embed({ node }) { - return new EmbedBuilder() - .setTitle('Node Logging') //if you wanted to change this please change at line 175 too - .setDescription('`' + node.name + '` is down!') - .setFooter({ text: 'Please see console for more details' }) - .setTimestamp() - .setColor('ED4245') - } +module.exports = async ({ client }) => { + function Embed({ node }) { + return new EmbedBuilder() + .setTitle("Node Logging") //if you wanted to change this please change at line 175 too + .setDescription("`" + node.name + "` is down!") + .setFooter({ text: "Please see console for more details" }) + .setTimestamp() + .setColor("ED4245"); + } - if (client.config.channel.startsWith('Put')) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid Channel ID')) - process.exit() - } else if (client.config.panel.url.startsWith('Put')) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid Panel URL')) - process.exit() - } else if (client.config.panel.key.startsWith('Put')) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid Apikey')) - process.exit() - } else if (!client.config.panel.url.startsWith('http')) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid Panel URL')) - console.log(chalk.cyan('[PteroStats] ') + chalk.red('1. Make sure the panel url is starts with "https://" or "http://"')) - process.exit() - } + if (client.config.channel.startsWith("Put")) { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Channel ID") + ); + process.exit(); + } else if (client.config.panel.url.startsWith("Put")) { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Panel URL") + ); + process.exit(); + } else if (client.config.panel.key.startsWith("Put")) { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Apikey") + ); + process.exit(); + } else if (!client.config.panel.url.startsWith("http")) { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Panel URL") + ); + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red( + '1. Make sure the panel url is starts with "https://" or "http://"' + ) + ); + process.exit(); + } - if (client.config.panel.url.endsWith('/')) client.config.panel.url = client.config.panel.url.slice(0, -1) + if (client.config.panel.url.endsWith("/")) + client.config.panel.url = client.config.panel.url.slice(0, -1); - const nodes = [] - const embeds = [] + const nodes = []; + const embeds = []; - const panel = { - status: false, - total_servers: -1, - total_users: -1, - } + const panel = { + status: false, + total_servers: -1, + total_users: -1, + }; - console.log(chalk.cyan('[PteroStats] ') + chalk.green('Getting nodes stats')) + console.log(chalk.cyan("[PteroStats] ") + chalk.green("Getting nodes stats")); - const panelStats = new Promise((resolve, reject) => { - axios(client.config.panel.url + '/api/application/users', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + client.config.panel.key - } - }).then((users) => { - axios(client.config.panel.url + '/api/application/servers', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + client.config.panel.key - } - }).then((servers) => { - panel.total_users = users.data.meta.pagination.total - panel.total_servers = servers.data.meta.pagination.total - panel.status = true + try { + const users = await axios( + client.config.panel.url + "/api/application/users", + { + method: "GET", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + client.config.panel.key, + }, + } + ); - resolve() - }) - }).catch((error) => { - if (error.response) { - if (error.response.status === 403) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid apikey')) - console.log(chalk.cyan('[PteroStats] ') + chalk.red('1. Make sure the apikey is from admin page not account page')) - console.log(chalk.cyan('[PteroStats] ') + chalk.red('2. Make sure the apikey has read permission on all options')) - console.log(chalk.cyan('[PteroStats] ') + chalk.red('3. Make sure the apikey is exist')) - } else if (error.response.status === 404) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid Panel URL')) - console.log(chalk.cyan('[PteroStats] ') + chalk.red('1. Make sure the panel url is like "https://panel.example.com"')) - } else console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! ' + error)) - } else console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! ' + error)) - resolve() - }) - }) + if (users?.status === 200 && users?.data) { + const servers = await axios( + client.config.panel.url + "/api/application/servers", + { + method: "GET", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + client.config.panel.key, + }, + } + ); - const nodeStats = new Promise((resolve, reject) => { - axios(client.config.panel.url + '/api/application/nodes?include=servers,location,allocations', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + client.config.panel.key - } - }).then((res) => { - res.data.data.forEach((node, i) => { - axios(client.config.panel.url + '/api/application/nodes/' + node.attributes.id + '/configuration', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + client.config.panel.key - } - }).then((data) => { - const body = { - id: node.attributes.id, - name: node.attributes.name, - location: node.attributes.relationships.location.attributes.short, - allocations: node.attributes.relationships.allocations.data.length, - maintenance: node.attributes.maintenance_mode, - total_servers: node.attributes.relationships.servers.data.length, - memory_min: node.attributes.allocated_resources.memory, - memory_max: node.attributes.memory, - disk_min: node.attributes.allocated_resources.disk, - disk_max: node.attributes.disk, - } + if (servers?.status === 200 && users?.data) { + panel.total_users = users?.data?.meta?.pagination?.total || "ERROR"; + panel.total_servers = servers?.data.meta?.pagination?.total || "ERROR"; + panel.status = true; - const stats = new Promise((statsResolve, statsReject) => { - axios(node.attributes.scheme + '://' + node.attributes.fqdn + ':' + node.attributes.daemon_listen + '/api/servers', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + data.data.token - } - }).then(() => { - body.status = true - return statsResolve() - }).catch((error) => { - if (client.config.log_error) console.log(chalk.cyan('[PteroStats] ') + chalk.yellow('[Node: ' + node.attributes.name + '] ') + chalk.red(error)) - embeds.push(Embed({ node: body })) - body.status = false - return statsResolve() - }) - setTimeout(() => { - if (body.status === undefined) { - if (client.config.log_error) console.log(chalk.cyan('[PteroStats] ') + chalk.yellow('[Node: ' + node.attributes.name + '] ') + chalk.red('Timeout!')) - embeds.push(Embed({ node: body })) - body.status = false - return statsResolve() - } - }, client.config.timeout * 1000) - }) - stats.then(() => { - nodes.push(body) - if (nodes.length === res.data.data.length) resolve() - }) - }).catch(() => resolve()) - }) - }).catch(() => resolve()) - }) + const res = await axios( + client.config.panel.url + + "/api/application/nodes?include=servers,location,allocations", + { + method: "GET", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + client.config.panel.key, + }, + } + ); - panelStats.then(() => { - nodeStats.then(async () => { - nodes.sort(function (a, b) { return a.id - b.id }) - postStatus({ client: client, panel: panel, nodes: nodes }) + if (res?.status === 200 && res?.data?.data) { + for (const node of res.data.data) { + const data = await axios( + client.config.panel.url + + "/api/application/nodes/" + + node.attributes.id + + "/configuration", + { + method: "GET", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + client.config.panel.key, + }, + } + ); - // this feature is still in testing - if (client.config.mentions.user.length > 0 || client.config.mentions.role.length > 0 && client.config.mentions.channel) { - if (Array.isArray(client.config.mentions.user) || Array.isArray(client.config.mentions.role)) { - let mentions = '' + if (data?.status === 200 && data?.data) { + const body = { + id: node.attributes.id, + name: node.attributes.name, + location: + node.attributes.relationships.location.attributes.short, + allocations: + node.attributes.relationships.allocations.data.length, + maintenance: node.attributes.maintenance_mode, + total_servers: + node.attributes.relationships.servers.data.length, + memory_min: node.attributes.allocated_resources.memory, + memory_max: node.attributes.memory, + disk_min: node.attributes.allocated_resources.disk, + disk_max: node.attributes.disk, + }; - await client.config.mentions.user.forEach((user) => { - if (!isNaN(Number(user))) { - mentions = mentions + ' <@' + user + '>' - } - }) - await client.config.mentions.role.forEach((role) => { - if (!isNaN(Number(role))) { - mentions = mentions + ' <@&' + role + '>' - } - }) + try { + const stats = await axios( + node.attributes.scheme + + "://" + + node.attributes.fqdn + + ":" + + node.attributes.daemon_listen + + "/api/servers", + { + method: "GET", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: "Bearer " + data.data.token, + }, + } + ); - const channel = await client.channels.cache.get(client.config.mentions.channel) - if (channel) { - const messages = await channel.messages.fetch({ limit: 10 }).then(msg => msg.filter(m => m.author.id === client.user.id && m.embeds[0].data.title === 'Node Logging').first()) - if (messages) messages.embeds.forEach((MsgEmbed) => { - embeds.forEach((embed, i) => { - if (MsgEmbed.data.description === embed.data.description) embeds.splice(i, 1) - nodes.forEach((node) => { - if (MsgEmbed.data.description.startsWith('`' + node.name) && node.status === true) messages.delete() - }) - }) - }) - if (embeds.length > 0) channel.send({ content: mentions, embeds: embeds }) - } - } - } - }) - }) -} \ No newline at end of file + if (stats?.status === 200 && stats?.data) { + body.status = true; + } else { + body.status = false; + } + + setTimeout(() => { + if (!body?.status) { + if (client.config.log_error) + console.log( + chalk.cyan("[PteroStats] ") + + chalk.yellow( + "[Node: " + node.attributes.name + "] " + ) + + chalk.red("Timeout!") + ); + embeds.push(Embed({ node: body })); + + body.status = false; + + return statsResolve(); + } + }, client.config.timeout * 1000); + } catch (error) { + if (client.config.log_error) + console.log( + chalk.cyan("[PteroStats] ") + + chalk.yellow("[Node: " + node.attributes.name + "] ") + + chalk.red(error) + ); + embeds.push(Embed({ node: body })); + + body.status = false; + } + + nodes.push(body); + } else { + throw new Error( + JSON.stringify({ + response: { + status: data.status, + }, + }) + ); + } + } + + nodes.sort(function (a, b) { + return a.id - b.id; + }); + + await postStatus({ client: client, panel: panel, nodes: nodes }); + + if ( + client.config.mentions.user.length > 0 || + (client.config.mentions.role.length > 0 && + client.config.mentions.channel) + ) { + if ( + Array.isArray(client.config.mentions.user) || + Array.isArray(client.config.mentions.role) + ) { + let mentions = ""; + + await client.config.mentions.user.forEach((user) => { + if (!isNaN(Number(user))) { + mentions = mentions + " <@" + user + ">"; + } + }); + await client.config.mentions.role.forEach((role) => { + if (!isNaN(Number(role))) { + mentions = mentions + " <@&" + role + ">"; + } + }); + + const channel = await client.channels.cache.get( + client.config.mentions.channel + ); + if (channel) { + const messages = await channel.messages + .fetch({ limit: 10 }) + .then((msg) => + msg + .filter( + (m) => + m.author.id === client.user.id && + m.embeds[0].data.title === "Node Logging" + ) + .first() + ); + if (messages) + messages.embeds.forEach((MsgEmbed) => { + for (const embed of embeds) { + if (MsgEmbed.data.description === embed.data.description) + embeds.splice(i, 1); + nodes.forEach((node) => { + if ( + MsgEmbed.data.description.startsWith( + "`" + node.name + ) && + node.status === true + ) + messages.delete(); + }); + } + }); + if (embeds.length > 0) + channel.send({ content: mentions, embeds: embeds }); + } + } + } + } else { + throw new Error( + JSON.stringify({ + response: { + status: res.status, + }, + }) + ); + } + } else { + throw new Error( + JSON.stringify({ + response: { + status: servers.status, + }, + }) + ); + } + } else { + throw new Error( + JSON.stringify({ + response: { + status: users.status, + }, + }) + ); + } + } catch (error) { + try { + if (typeof error === "string") { + error = JSON.parse(error); + } + } catch {} + + if (error?.response) { + if (error.response?.status === 403) { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid apikey") + ); + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red( + "1. Make sure the apikey is from admin page not account page" + ) + ); + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red( + "2. Make sure the apikey has read permission on all options" + ) + ); + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red("3. Make sure the apikey is exist") + ); + } else if (error.response?.status === 404) { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Panel URL") + ); + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red( + '1. Make sure the panel url is like "https://panel.example.com"' + ) + ); + } else { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! " + error) + ); + } + } else { + console.log(chalk.cyan("[PteroStats] ") + chalk.red("Error! " + error)); + } + } +}; diff --git a/handlers/postStatus.js b/handlers/postStatus.js index c4977ab..d07cbc7 100644 --- a/handlers/postStatus.js +++ b/handlers/postStatus.js @@ -1,169 +1,365 @@ -const { EmbedBuilder, time, ActionRowBuilder, ButtonBuilder, ButtonStyle, AttachmentBuilder } = require('discord.js') -const chalk = require('chalk') +const { + EmbedBuilder, + time, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + AttachmentBuilder, +} = require("discord.js"); +const chalk = require("chalk"); -module.exports = async function postStatus({ client, panel, nodes }) { +const memorySizeConverter = require("../modules/memorySizeConverter"); +const percentageCalculator = require("../modules/percentageCalculator"); - const channel = await client.channels.cache.get(client.config.channel) - if (!channel) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid Channel ID')) +const BUFFER_MS = 2000; //Added these extra milliseconds to prevent the stats timer from showing "1 or 2 seconds ago" before updating the stats. - const files = [] - const embed = new EmbedBuilder() - let messages = await channel.messages.fetch({ limit: 10 }).then(msg => msg.filter(m => m.author.id === client.user.id).last()) - let text = '' - let desc = '' - let blacklist = 0 - let content = null +module.exports = async ({ client, panel, nodes }) => { + const channel = await client.channels.cache.get(client.config.channel); + if (!channel) { + return console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Channel ID") + ); + } - if (!client.config.nodes_settings.blacklist) client.config.nodes_settings.blacklist = [] - if (!Array.isArray(client.config.nodes_settings.blacklist) && Number.isInteger(client.config.nodes_settings.blacklist)) client.config.nodes_settings.blacklist = [client.config.nodes_settings.blacklist] - if (client.guilds.cache.size < 1) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! This bot is not on any discord servers')) - if (messages && messages.embeds.length < 1) { - messages.delete() - messages = null - } + const files = []; - if (client.config.message.content) content = client.config.message.content - if (client.config.message.attachment) files.push(new AttachmentBuilder(client.config.message.attachment)) - if (client.config.embed.title) embed.setTitle(client.config.embed.title) - if (client.config.embed.description) desc = client.config.embed.description + '\n' - if (client.config.embed.color) embed.setColor(client.config.embed.color) - if (client.config.embed.footer) embed.setFooter({ text: client.config.embed.footer }) - if (client.config.embed.thumbnail) embed.setThumbnail(client.config.embed.thumbnail) - if (client.config.embed.image) embed.setImage(client.config.embed.image) + const embed = new EmbedBuilder(); - panel.total_users = panel.total_users.toLocaleString() - panel.total_servers = panel.total_servers.toLocaleString() + let messages = await channel.messages + .fetch({ limit: 10 }) + .then((msg) => msg.filter((m) => m.author.id === client.user.id).last()); - const stats = new Promise((resolve, reject) => { - if (nodes.length !== 0) { - nodes.forEach((data, i) => { - if (!client.config.nodes_settings.blacklist.includes(data.id)) { - const title = data.name + ': ' + String(data.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) - let description = '```' - switch (client.config.nodes_settings.unit.toLowerCase()) { - case 'tb': - description = description + - '\nMemory : ' + Math.floor(data.memory_min / (1024 * 1000)).toLocaleString() + ' TB / ' + Math.floor(data.memory_max / (1024 * 1000)).toLocaleString() + ' TB' + - '\nDisk : ' + Math.floor(data.disk_min / (1024 * 1000)).toLocaleString() + ' TB / ' + Math.floor(data.disk_max / (1024 * 1000)).toLocaleString() + ' TB' - break; - case 'gb': - description = description + - '\nMemory : ' + Math.floor(data.memory_min / 1024).toLocaleString() + ' GB / ' + Math.floor(data.memory_max / 1024).toLocaleString() + ' GB' + - '\nDisk : ' + Math.floor(data.disk_min / 1024).toLocaleString() + ' GB / ' + Math.floor(data.disk_max / 1024).toLocaleString() + ' GB' - break; - case 'percent': - description = description + - '\nMemory : ' + Math.floor(data.memory_min / data.memory_max * 100) + ' %' + - '\nDisk : ' + Math.floor(data.disk_min / data.disk_max * 100) + ' %' - break; - default: - description = description + - '\nMemory : ' + data.memory_min.toLocaleString() + ' MB / ' + data.memory_max.toLocaleString() + ' MB' + - '\nDisk : ' + data.disk_min.toLocaleString() + ' MB / ' + data.disk_max.toLocaleString() + ' MB' - } + let text = ""; + let desc = ""; + let blacklist = 0; + let content = null; - if (client.config.nodes_settings.servers) description = description + '\nServers : ' + data.total_servers.toLocaleString() - if (client.config.nodes_settings.location) description = description + '\nLocation : ' + data.location - if (client.config.nodes_settings.allocations) description = description + '\nAllocations : ' + data.allocations.toLocaleString() + if (!client.config.nodes_settings.blacklist) { + client.config.nodes_settings.blacklist = []; + } - description = description + '\n```' + if ( + !Array.isArray(client.config.nodes_settings.blacklist) && + Number.isInteger(client.config.nodes_settings.blacklist) + ) { + client.config.nodes_settings.blacklist = [ + client.config.nodes_settings.blacklist, + ]; + } - if (client.config.nodes_settings.details) { - text = text + '\n**' + title.replace(':', ':**') + '\n' + description - } else { - text = text + '\n**' + title.replace(':', ':**') - } - } else { - blacklist = blacklist + 1 - if (nodes.length - client.config.nodes_settings.blacklist.length < 1) text = '\nThere is no nodes to display' - } + if (client.guilds.cache.size < 1) { + return console.log( + chalk.cyan("[PteroStats] ") + + chalk.red("Error! This bot is not on any discord servers") + ); + } - if (i + 1 === nodes.length) resolve() - }) - } else if (nodes.length < 1) { - if (!messages) { - text = '\nThere is no nodes to display' - resolve() - } else { - if (messages.embeds.length > 0 && client.config.embed.title && messages.embeds[0].data.title === client.config.embed.title) { - text = messages.embeds[0].description.replaceAll(client.config.status.online, client.config.status.offline) - if (!panel.status && String(String(messages.embeds[0].fields[0].value).split('\n')[2]).split('')[String(String(messages.embeds[0].fields[0].value).split('\n')[2]).length - 1] !== '`') { - panel.total_users = String(String(messages.embeds[0].fields[0].value).split('\n')[2]).split('')[String(String(messages.embeds[0].fields[0].value).split('\n')[2]).length - 1] - panel.total_servers = String(String(messages.embeds[0].fields[0].value).split('\n')[3]).split('')[String(String(messages.embeds[0].fields[0].value).split('\n')[3]).length - 1] - } - } - resolve() - } - } - }) + if (messages && messages.embeds.length < 1) { + messages.delete(); + messages = null; + } - stats.then(async () => { - const format = await time(new Date(Date.now() + client.config.refresh * 1000), 'R') - embed.setDescription(desc.replaceAll('{{time}}', format) + '\n**Nodes Stats [' + Math.floor(nodes.length - blacklist) + ']**' + text) - const EmbedFields = [] + if (client.config.message.content) { + content = client.config.message.content; + } - if (client.config.panel_settings.status) { - let stats = '**Status:** ' + String(panel.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) + '\n\n' + if (client.config.message.attachment) { + files.push(new AttachmentBuilder(client.config.message.attachment)); + } + if (client.config.embed.title) { + embed.setTitle(client.config.embed.title); + } - if (client.config.panel_settings.users) stats = stats + 'Users: ' + String(panel.total_users).replace('-1', '`Unknown`') + '\n' - if (client.config.panel_settings.servers) stats = stats + 'Servers: ' + String(panel.total_servers).replace('-1', '`Unknown`') + if (client.config.embed.description) { + desc = client.config.embed.description + "\n"; + } - EmbedFields.push({ name: 'Panel Stats', value: stats }) - } + if (client.config.embed.color) { + embed.setColor(client.config.embed.color); + } - if (client.config.embed.field.title && client.config.embed.field.description) EmbedFields.push({ name: client.config.embed.field.title, value: client.config.embed.field.description.replaceAll('{{time}}', format) }) - if (client.config.embed.timestamp) embed.setTimestamp() - if (EmbedFields.length > 0) embed.addFields(EmbedFields) + if (client.config.embed.footer) { + embed.setFooter({ text: client.config.embed.footer }); + } - const row = [] + if (client.config.embed.thumbnail) { + embed.setThumbnail(client.config.embed.thumbnail); + } - if (client.config.button.enable) { - const button = new ActionRowBuilder - if (client.config.button.btn1.label.length !== 0 && client.config.button.btn1.url.length !== 0) { - button.addComponents( - new ButtonBuilder() - .setLabel(client.config.button.btn1.label) - .setStyle(ButtonStyle.Link) - .setURL(client.config.button.btn1.url) - ) - } - if (client.config.button.btn2.label.length !== 0 && client.config.button.btn2.url.length !== 0) { - button.addComponents( - new ButtonBuilder() - .setLabel(client.config.button.btn2.label) - .setStyle(ButtonStyle.Link) - .setURL(client.config.button.btn2.url) - ) - } - if (client.config.button.btn3.label.length !== 0 && client.config.button.btn3.url.length !== 0) { - button.addComponents( - new ButtonBuilder() - .setLabel(client.config.button.btn3.label) - .setStyle(ButtonStyle.Link) - .setURL(client.config.button.btn3.url) - ) - } - if (client.config.button.btn4.label.length !== 0 && client.config.button.btn4.url.length !== 0) { - button.addComponents( - new ButtonBuilder() - .setLabel(client.config.button.btn4.label) - .setStyle(ButtonStyle.Link) - .setURL(client.config.button.btn4.url) - ) - } - if (client.config.button.btn5.label.length !== 0 && client.config.button.btn5.url.length !== 0) { - button.addComponents( - new ButtonBuilder() - .setLabel(client.config.button.btn5.label) - .setStyle(ButtonStyle.Link) - .setURL(client.config.button.btn5.url) - ) - } - row.push(button) - } + if (client.config.embed.image) { + embed.setImage(client.config.embed.image); + } - if (!messages) channel.send({ content: content, embeds: [embed], components: row, files: files }) - else messages.edit({ content: content, embeds: [embed], components: row, files: files }) - console.log(chalk.cyan('[PteroStats] ') + chalk.green('Stats posted!')) - }) -} \ No newline at end of file + panel.total_users = panel.total_users.toLocaleString(); + panel.total_servers = panel.total_servers.toLocaleString(); + + if (nodes?.length >= 1) { + for (const data of nodes) { + if (!client.config.nodes_settings.blacklist.includes(data.id)) { + const title = + data?.name + + ": " + + String(data?.status) + .replace("true", client.config.status.online) + .replace("false", client.config.status.offline); + + let description = "```"; + + switch (client.config.nodes_settings.unit.toLowerCase()) { + case "tb": + description = + description + + "\nMemory : " + + memorySizeConverter(data.memory_min, "TB") + + " / " + + memorySizeConverter(data.memory_max, "TB") + + "\nDisk : " + + memorySizeConverter(data.disk_min, "TB") + + " / " + + memorySizeConverter(data.disk_max, "TB"); + break; + case "gb": + description = + description + + "\nMemory : " + + memorySizeConverter(data.memory_min, "GB") + + " / " + + memorySizeConverter(data.memory_max, "GB") + + "\nDisk : " + + memorySizeConverter(data.disk_min, "GB") + + " / " + + memorySizeConverter(data.disk_max, "GB"); + break; + case "percent": + description = + description + + "\nMemory : " + + percentageCalculator(data.memory_min, data.memory_max) + + "\nDisk : " + + percentageCalculator(data.disk_min, data.disk_max); + break; + default: + description = + description + + "\nMemory : " + + memorySizeConverter(data.memory_min, "MB") + + " / " + + memorySizeConverter(data.memory_max, "MB") + + "\nDisk : " + + memorySizeConverter(data.disk_min, "MB") + + " / " + + memorySizeConverter(data.disk_max, "MB"); + } + + if (client.config.nodes_settings.servers) { + description = + description + "\nServers : " + data.total_servers.toLocaleString(); + } + + if (client.config.nodes_settings.location) { + description = description + "\nLocation : " + data.location; + } + + if (client.config.nodes_settings.allocations) { + description = + description + + "\nAllocations : " + + data.allocations.toLocaleString(); + } + + description = description + "\n```"; + + if (client.config.nodes_settings.details) { + text = text + "\n**" + title.replace(":", ":**") + "\n" + description; + } else { + text = text + "\n**" + title.replace(":", ":**"); + } + } else { + blacklist = blacklist + 1; + if (nodes.length - client.config.nodes_settings.blacklist.length < 1) { + text = "\nThere is no nodes to display"; + } + } + } + + const format = time( + new Date(Date.now() + client.config.refresh * 1000 + BUFFER_MS), + "R" + ); + + embed.setDescription( + desc.replaceAll("{{time}}", format) + + "\n**Nodes Stats [" + + Math.floor(nodes.length - blacklist) + + "]**" + + text + ); + + const EmbedFields = []; + + if (client.config.panel_settings.status) { + let stats = + "**Status:** " + + String(panel.status) + .replace("true", client.config.status.online) + .replace("false", client.config.status.offline) + + "\n\n"; + + if (client.config.panel_settings.users) { + stats = + stats + + "Users: " + + String(panel.total_users).replace("-1", "`Unknown`") + + "\n"; + } + + if (client.config.panel_settings.servers) { + stats = + stats + + "Servers: " + + String(panel.total_servers).replace("-1", "`Unknown`"); + } + + EmbedFields.push({ name: "Panel Stats", value: stats }); + } + + if ( + client.config.embed.field.title && + client.config.embed.field.description + ) { + EmbedFields.push({ + name: client.config.embed.field.title, + value: client.config.embed.field.description.replaceAll( + "{{time}}", + format + ), + }); + } + + if (client.config.embed.timestamp) { + embed.setTimestamp(); + } + + if (EmbedFields.length > 0) { + embed.setFields(EmbedFields); //try it and see + } + + const row = []; + + if (client.config.button.enable) { + const button = new ActionRowBuilder(); + + if (client.config.button.btn1.label.length >= 1 && client.config.button.btn1.url.length >= 1) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn1.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn1.url) + ); + } + + if (client.config.button.btn2.label.length >= 1 && client.config.button.btn2.url.length >= 1) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn2.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn2.url) + ); + } + + if (client.config.button.btn3.label.length >= 1 && client.config.button.btn3.url.length >= 1) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn3.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn3.url) + ); + } + + if (client.config.button.btn4.label.length >= 1 && client.config.button.btn4.url.length >= 1) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn4.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn4.url) + ); + } + + if (client.config.button.btn5.label.length >= 1 && client.config.button.btn5.url.length >= 1) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn5.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn5.url) + ); + } + + row.push(button); + } + + try { + if (!messages) { + channel.send({ + content: content, + embeds: [embed], + components: row, + files: files, + }); + } else { + messages.edit({ + content: content, + embeds: [embed], + components: row, + files: files, + }); + } + } catch (error) { + console.log(error); + } + + console.log(chalk.cyan("[PteroStats] ") + chalk.green("Stats posted!")); + } else { + if (!messages) { + text = "\nThere are no nodes to display."; + } else { + if ( + messages?.embeds?.length > 0 && + client.config.embed.title && + messages?.embeds[0]?.data?.title === client.config.embed.title + ) { + text = messages.embeds[0].description.replaceAll( + client.config.status.online, + client.config.status.offline + ); + + if ( + !panel?.status && + String( + String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2] + ).split("")[ + String( + String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2] + )?.length - 1 + ] !== "`" + ) { + panel.total_users = + String( + String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2] + ).split("")[ + String( + String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2] + )?.length - 1 + ] || 0; + panel.total_servers = + String( + String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[3] + )?.split("")[ + String( + String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[3] + )?.length - 1 + ] || 0; + } + } + } + } +}; diff --git a/index.js b/index.js index 82a7657..940587e 100644 --- a/index.js +++ b/index.js @@ -1,75 +1,52 @@ -const fs = require('fs'); -const child = require('child_process'); +const intigrityCheck = require("./modules/intigrityCheck"); -function InstallPackages() { - console.log('You didn\'t install the required node packages first!') - console.log('Please wait... starting to install all required node packages using child process') - console.log('If the bot can\'t install the package please install it manually') - try { - child.execSync('npm i') - console.log('Install complete!, please run "node index" command again!') - process.exit() - } catch (err) { - console.log('Error! ', err) - console.log('Support Server: https://discord.gg/zv6maQRah3') - process.exit() - } +if(!intigrityCheck()){ + return console.log("Intigrity check failed!"); } -if (Number(process.version.split('.')[0]) < 16) { - console.log('Invalid NodeJS Version!, Please use NodeJS 16.x or upper') - process.exit() -} -if (fs.existsSync('./node_modules')) { - if (fs.existsSync('./node_modules/discord.js')) { - const check = require('./node_modules/discord.js/package.json') - if (Number(check.version.split('.')[0]) !== 14) { - console.log('Invalid Discord.JS Version!, Please use Discord.JS 14.x') - process.exit() - } - } else InstallPackages() - if (fs.existsSync('./node_modules/axios')) { - const check = require('./node_modules/axios/package.json') - if (Number(check.version.split('.')[1]) > 1) { - console.log('Invalid Axios Version!, Please use Axios 1.1.3') - process.exit() - } - } else InstallPackages() - if (fs.existsSync('./node_modules/chalk')) { - const check = require('./node_modules/chalk/package.json') - if (Number(check.version.split('.')[0]) > 4) { - console.log('Invalid Chalk Version!, Please use Chalk 4.1.2') - process.exit() - } - } else InstallPackages() - if (!fs.existsSync('./node_modules/js-yaml')) InstallPackages() -} else InstallPackages() - -const chalk = require('chalk'); -const yaml = require('js-yaml'); -const { Client, GatewayIntentBits } = require('discord.js'); +const fs = require("node:fs"); +const chalk = require("chalk"); +const yaml = require("js-yaml"); +const { Client, GatewayIntentBits } = require("discord.js"); const client = new Client({ intents: [GatewayIntentBits.Guilds] }); -client.config = yaml.load(fs.readFileSync('./config.yml', 'utf8')); +client.config = yaml.load(fs.readFileSync("./config.yml", "utf8")); -if (client.config.panel.adminkey || client.config.resource || client.config.message.image) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('You are using old config file, please update your config file at ') + chalk.green('https://github.com/HirziDevs/PteroStats/blob/main/config.yml')) - process.exit() -} -if (client.config.token.startsWith('Put') || !client.config.token.length) { - console.log(chalk.cyan('[PteroStats] ') + chalk.red('Error! Invalid Discord Bot Token')) - process.exit() +if ( + client.config.panel.adminkey || + client.config.resource || + client.config.message.image +) { + console.log( + chalk.cyan("[PteroStats] ") + + chalk.red( + "You are using old config file, please update your config file at " + ) + + chalk.green( + "https://github.com/HirziDevs/PteroStats/blob/main/config.yml" + ) + ); + process.exit(); } -const eventFiles = fs.readdirSync('./events').filter(file => file.endsWith('.js')); +if (client.config.token.startsWith("Put") || !client.config.token.length) { + console.log( + chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Discord Bot Token") + ); + process.exit(); +} + +const eventFiles = fs + .readdirSync("./events") + .filter((file) => file.endsWith(".js")); for (const file of eventFiles) { - const event = require(`./events/${file}`); - if (event.once) { - client.once(event.name, (...args) => event.execute(...args)); - } else { - client.on(event.name, (...args) => event.execute(...args)); - } + const event = require(`./events/${file}`); + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); + } else { + client.on(event.name, (...args) => event.execute(...args)); + } } -client.login(client.config.token); \ No newline at end of file +client.login(client.config.token); diff --git a/modules/intigrityCheck.js b/modules/intigrityCheck.js new file mode 100644 index 0000000..0bd261e --- /dev/null +++ b/modules/intigrityCheck.js @@ -0,0 +1,127 @@ +const fs = require("node:fs"); +const child = require("node:child_process"); +const path = require("node:path"); + +const REQUIRED_PACKAGES = [ + { + name: "axios", + version: "1.4.0", + }, + { + name: "axios-retry", + version: "3.5.1", + }, + { + name: "chalk", + version: "4.1.2", + }, + { + name: "discord.js", + version: "14.11.0", + }, + { + name: "js-yaml", + version: "4.1.0", + }, +]; +const NODE_VERSION = 16; + +function InstallPackages() { + console.log("Required nodejs packages not found!"); + console.log("Please wait... starting to install all required node packages."); + console.log( + "If the bot can't install the packages please install them manually." + ); + + try { + let packagesList = ""; + + for (const package of REQUIRED_PACKAGES) { + packagesList += ` ${package.name}@${package.version}`; + } + + child.execSync(`npm i${packagesList}`); + console.log('Install complete!, please run "node index" command again!'); + + process.exit(); + } catch (err) { + console.log("Error! ", err); + console.log("Support Server: https://discord.gg/zv6maQRah3"); + + process.exit(); + } +} + +module.exports = () => { + if (Number(process.version.split(".")[0]) < NODE_VERSION) { + console.log( + `Unsupported NodeJS Version!, Please use NodeJS ${NODE_VERSION}.x or higher.` + ); + + process.exit(); + } + + const nodeModulesFolderPath = path.join(__dirname, "../", "node_modules"); + + if (fs.existsSync(nodeModulesFolderPath)) { + let success = false; + let errorMessage = ""; + + for (const package of REQUIRED_PACKAGES) { + const packageFilePath = path.join( + __dirname, + "../", + "node_modules", + package.name, + "package.json" + ); + + if (fs.existsSync(packageFilePath)) { + let packageFile = fs.readFileSync(packageFilePath, 'utf-8'); + + if (packageFile) { + packageFile = JSON.parse(packageFile); + + if ( + Number(packageFile.version.split(".")[1]) !== + Number(package.version.split(".")[1]) + ) { + console.log( + `Unsupported "${package.name}" version!.\nPlease delete your "node_modules" and "package-lock.json".\nAnd restart the bot.` + ); + + process.exit(); + } else { + success = true; + + continue; + } + } else { + success = false; + + errorMessage = `Unknown package version- "${package.name}".`; + + break; + } + } else { + success = false; + + errorMessage = `Missing package- "${package.name}".`; + + break; + } + } + + if (!success) { + if (errorMessage) { + console.log(errorMessage); + } + + InstallPackages(); + } + } else { + InstallPackages(); + } + + return true; +}; diff --git a/modules/memorySizeConverter.js b/modules/memorySizeConverter.js new file mode 100644 index 0000000..22c6fc8 --- /dev/null +++ b/modules/memorySizeConverter.js @@ -0,0 +1,44 @@ +const SUPPORTED_TYPES = ["MB", "GB", "TB"]; + +module.exports = (value, type) => { + if (value && type) { + value = parseInt(value); + + if (value > 0) { + if (SUPPORTED_TYPES.includes(type)) { + let result = ""; + + switch (type) { + case "MB": + result = + value.toFixed(2).toLocaleString().replace(".00", "") + " MB"; + break; + case "GB": + result = + (value / 1024).toFixed(2).toLocaleString().replace(".00", "") + + " GB"; + break; + case "TB": + result = + (value / (1024 * 1000)) + .toFixed(2) + .toLocaleString() + .replace(".00", "") + " TB"; + break; + default: + result = + value.toFixed(2).toLocaleString().replace(".00", "") + " MB"; + break; + } + + return result; + } else { + return "INVALID TYPE"; + } + } else { + return "INVALID VALUE"; + } + } else { + return "ERROR"; + } +}; diff --git a/modules/percentageCalculator.js b/modules/percentageCalculator.js new file mode 100644 index 0000000..7fa9462 --- /dev/null +++ b/modules/percentageCalculator.js @@ -0,0 +1,16 @@ +module.exports = (used, total) => { + if (used && total) { + used = parseInt(used); + total = parseInt(total); + + if (used >= 1 && total >= 1) { + let percentage = ((used / total) * 100).toFixed(2).toLocaleString().replace(".00", "") + " %"; + + return percentage; + } else { + return "ERROR"; + } + } else { + return "ERROR"; + } +}; diff --git a/package.json b/package.json index 9a00948..e98022d 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,13 @@ "start": "node index.js" }, "dependencies": { - "axios": "1.1.3", - "chalk": "4.1.2", - "discord.js": "^14.7.1", + "axios": "^1.4.0", + "axios-retry": "^3.5.1", + "chalk": "^4.1.2", + "discord.js": "^14.11.0", "js-yaml": "^4.1.0" }, "engines": { "node": ">=16.9.0" } -} \ No newline at end of file +}