mirror of
https://github.com/HirziDevs/PteroStats
synced 2026-01-04 04:04:23 +00:00
refactor: improve function names, code readability, and error handling
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
## Introduction
|
||||
PteroStats is a Discord App/Bot that designed to check Pterodactyl or Pelican Panel stats and post it to your Discord server.
|
||||
PteroStats is a Discord App/Bot designed to check Pterodactyl or Pelican Panel stats and post it to your Discord server.
|
||||
|
||||
## Preview
|
||||
<img alt="PteroStats Image Preview" src="https://usercontent.catto.pictures/hirzi/e6f6fe6a-8c0e-4c7a-8b73-d4af752324f4.png" width="300"/>
|
||||
@@ -29,10 +29,10 @@ PteroStats is a Discord App/Bot that designed to check Pterodactyl or Pelican Pa
|
||||
1. [Create your Discord App/Bot](https://discordjs.guide/preparations/adding-your-bot-to-servers.html).
|
||||
2. [Invite your Discord App/Bot to your Discord server](https://discordjs.guide/preparations/adding-your-bot-to-servers.html).
|
||||
3. Download this repository:
|
||||
- [Downloading this repository](https://github.com/HirziDevs/PteroStats/archive/refs/heads/main.zip) and extract it.
|
||||
- [Download this repository](https://github.com/HirziDevs/PteroStats/archive/refs/heads/main.zip) and extract it.
|
||||
- Using Git: Run `git clone https://github.com/HirziDevs/PteroStats.git` in the command line.
|
||||
4. Run `npm install` in the root directory of the app/bot files.
|
||||
5. Run `node index` and answer the provided questions to set up the app/bot.
|
||||
5. Run `node index` and answer the prompted questions to set up the app/bot.
|
||||
|
||||
<img alt="Setup" src="https://usercontent.catto.pictures/hirzi/b8645828-591d-4d52-b6d8-51f8df60440c.png" width="300"/>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function UptimeFormatter(time) {
|
||||
module.exports = function uptimeFormatter(time) {
|
||||
let text = []
|
||||
const days = Math.floor(time / 86400000);
|
||||
const hours = Math.floor(time / 3600000) % 24;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
require("dotenv").config()
|
||||
require("dotenv").config();
|
||||
const { Client, GatewayIntentBits, EmbedBuilder, time, ActivityType, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js");
|
||||
const fs = require("node:fs");
|
||||
const cliColor = require("cli-color");
|
||||
const config = require("./config.js");
|
||||
const path = require("node:path");
|
||||
const config = require("./configuration.js");
|
||||
const convertUnits = require("./convertUnits.js");
|
||||
const getStats = require("./getStats.js");
|
||||
const webhook = require("./webhook.js");
|
||||
const uptimeFormatter = require("./uptimeFormatter.js");
|
||||
|
||||
module.exports = function App() {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.green("Starting app..."));
|
||||
@@ -34,7 +36,7 @@ module.exports = function App() {
|
||||
if (config.log_error) console.error(error)
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Panel is currently offline."));
|
||||
|
||||
fs.readFile(require('node:path').join(__dirname, "../cache.json"), (err, data) => {
|
||||
fs.readFile(path.join(__dirname, "../cache.json"), (err, data) => {
|
||||
if (err) {
|
||||
createMessage({ cache: false, panel: false });
|
||||
return console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Last cache was not found!"));
|
||||
@@ -47,8 +49,8 @@ module.exports = function App() {
|
||||
.setTitle("Panel Offline")
|
||||
.setColor("ED4245")
|
||||
.setDescription(`Panel is currently offline`)
|
||||
)
|
||||
results.uptime = false
|
||||
);
|
||||
results.uptime = false;
|
||||
fs.writeFileSync("cache.json", JSON.stringify(results, null, 2), "utf8");
|
||||
createMessage({
|
||||
cache: true,
|
||||
@@ -132,7 +134,7 @@ module.exports = function App() {
|
||||
`Disk : ${convertUnits(node.attributes.allocated_resources.disk, node.attributes.disk, config.nodes_settings.unit)}` +
|
||||
(node.attributes?.allocated_resources?.cpu ? `\nCPU : ${node.attributes?.allocated_resources?.cpu || 0}%` : "") +
|
||||
(config.nodes_settings.servers ? `\nServers: ${node.attributes.relationships.servers}${config.nodes_settings.allocations_as_max_servers ? ` / ${node.attributes.relationships.allocations}` : ""}` : "") +
|
||||
(config.nodes_settings.uptime ? `\nUptime : ${node.uptime ? require("./UptimeFormatter.js")(Date.now() - node.uptime) : "N/A"}` : "") +
|
||||
(config.nodes_settings.uptime ? `\nUptime : ${node.uptime ? uptimeFormatter(Date.now() - node.uptime) : "N/A"}` : "") +
|
||||
"```"
|
||||
});
|
||||
});
|
||||
@@ -170,7 +172,7 @@ module.exports = function App() {
|
||||
`Nodes : ${nodes.length}\n` +
|
||||
(config.panel_settings.servers ? `Servers: ${servers || "Unknown"}\n` : "") +
|
||||
(config.panel_settings.users ? `Users : ${users || "Unknown"}\n` : "") +
|
||||
(config.panel_settings.uptime ? `Uptime : ${uptime ? require("./UptimeFormatter.js")(Date.now() - uptime) : "N/A"}\n` : "") +
|
||||
(config.panel_settings.uptime ? `Uptime : ${uptime ? uptimeFormatter(Date.now() - uptime) : "N/A"}\n` : "") +
|
||||
"```"
|
||||
});
|
||||
|
||||
@@ -194,10 +196,12 @@ module.exports = function App() {
|
||||
|
||||
if (config.button.enable) {
|
||||
for (const row of ["row1", "row2", "row3", "row4", "row5"]) {
|
||||
if (config.button[row] && config.button[row].length > 0)
|
||||
if (config.button[row].slice(0, 5).filter(button => button.label && button.url).length > 0) components.push(
|
||||
const buttons = config.button[row]?.slice(0, 5).filter(button => button.label && button.url);
|
||||
|
||||
if (buttons && buttons.length > 0) {
|
||||
components.push(
|
||||
new ActionRowBuilder().addComponents(
|
||||
config.button[row].slice(0, 5).filter(button => button.label && button.url).map(button =>
|
||||
buttons.map(button =>
|
||||
new ButtonBuilder()
|
||||
.setLabel(button.label)
|
||||
.setURL(button.url)
|
||||
@@ -205,6 +209,7 @@ module.exports = function App() {
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,16 +228,16 @@ module.exports = function App() {
|
||||
await channel.send({ content: config.message.content || null, embeds, components });
|
||||
}
|
||||
} catch (error) {
|
||||
DiscordErrorHandler(error);
|
||||
handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
function DiscordErrorHandler(error) {
|
||||
function handleDiscordError(error) {
|
||||
try {
|
||||
if (error.rawError?.code === 429) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Error 429 | Your IP has been rate limited by either Discord or your website. If it's a rate limit with Discord, you must wait. If it's a issue with your website, consider whitelisting your server IP."));
|
||||
} else if (error.rawError?.code === 403) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("FORBIDDEN | The channel ID you provided is incorrect. Please double check you have the right ID. If you're not sure, read our documentation: https://github.com/HirziDevs/PteroStats#getting-channel-id"));
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("FORBIDDEN | The channel ID you provided is incorrect. Please double check you have the right ID. If you're not sure, read our documentation: \n>>https://github.com/HirziDevs/PteroStats#getting-channel-id<<"));
|
||||
} else if (error.code === "ENOTFOUND") {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("ENOTFOUND | DNS Error. Ensure your network connection and DNS server are functioning correctly."));
|
||||
} else if (error.rawError?.code === 50001) {
|
||||
@@ -2,11 +2,11 @@ const fs = require("node:fs");
|
||||
const yaml = require("js-yaml");
|
||||
const cliColor = require("cli-color");
|
||||
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Loading configuration..."))
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Loading configuration..."));
|
||||
|
||||
let config = yaml.load(fs.readFileSync("./config.yml", "utf8"));
|
||||
if (fs.existsSync("config-dev.yml")) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Using development configuration..."))
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Using development configuration..."));
|
||||
config = yaml.load(fs.readFileSync("./config-dev.yml", "utf8"));
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ try {
|
||||
}
|
||||
|
||||
if (config.version !== 9) {
|
||||
console.error('Config Error | Invalid config version! The config has been updated, please get the new config from https://github.com/HirziDevs/PteroStats/blob/main/config.yml');
|
||||
console.error('Config Error | Invalid config version! The config has been updated. Please get the new config format from: \n>> https://github.com/HirziDevs/PteroStats/blob/main/config.yml <<');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Configuration loaded"))
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Configuration loaded"));
|
||||
|
||||
module.exports = config
|
||||
module.exports = config;
|
||||
@@ -1,15 +1,15 @@
|
||||
const prettyBytes = require('prettier-bytes');
|
||||
|
||||
module.exports = function convertUnits(value1, value2, unit) {
|
||||
module.exports = function convertUnits(value, max, unit) {
|
||||
unit = unit.toUpperCase();
|
||||
switch (unit) {
|
||||
case 'PERCENTAGE':
|
||||
case 'PERCENT':
|
||||
const percentage = Math.floor((value1 / value2) * 100);
|
||||
const percentage = Math.floor((value / max) * 100);
|
||||
return `${!percentage ? 0 : percentage}%`;
|
||||
case 'BYTE':
|
||||
return `${prettyBytes(value1 * 1000000)} / ${value2 === 0 ? "Unlimited" : prettyBytes(value2 * 1000000)}`;
|
||||
return `${prettyBytes(value * 1000000)} / ${max === 0 ? "Unlimited" : prettyBytes(max * 1000000)}`;
|
||||
default:
|
||||
return `${value1.toLocaleString()} ${unit}/${value2 === 0 ? "Unlimited" : `${value2.toLocaleString()} ${unit}`}`;
|
||||
return `${value.toLocaleString()} ${unit}/${max === 0 ? "Unlimited" : `${max.toLocaleString()} ${unit}`}`;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const config = require("./config.js");
|
||||
const config = require("./configuration.js");
|
||||
|
||||
module.exports = async function getNodeConfiguration(id) {
|
||||
return fetch(`${new URL(process.env?.PanelURL).origin}/api/application/nodes/${id}/configuration`, {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const cliColor = require("cli-color");
|
||||
const config = require("./config.js");
|
||||
const config = require("./configuration.js");
|
||||
const axios = require("axios");
|
||||
|
||||
module.exports = async function getAllNodes() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const config = require("./config.js");
|
||||
const config = require("./configuration.js");
|
||||
const cliColor = require("cli-color");
|
||||
|
||||
module.exports = async function getServers() {
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
const config = require("./config.js");
|
||||
const { EmbedBuilder } = require("discord.js");
|
||||
const fs = require("node:fs");
|
||||
const cliColor = require("cli-color");
|
||||
const path = require('node:path');
|
||||
const webhook = require("./webhook.js");
|
||||
const { EmbedBuilder } = require("discord.js");
|
||||
const config = require("./configuration.js");
|
||||
const getNodesDetails = require("./getNodesDetails.js");
|
||||
const getNodeConfiguration = require("./getNodeConfiguration.js");
|
||||
const getWingsStatus = require("./getWingsStatus.js");
|
||||
const promiseTimeout = require("./promiseTimeout.js");
|
||||
const getServers = require("./getServers.js");
|
||||
const getUsers = require("./getUsers.js");
|
||||
|
||||
module.exports = async function getStats() {
|
||||
let cache = (() => {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(require('node:path').join(__dirname, "../cache.json")))
|
||||
return JSON.parse(fs.readFileSync(path.join(__dirname, "../cache.json")))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})()
|
||||
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Retrieving panel nodes..."))
|
||||
const nodesStats = await require("./getNodesDetails.js")();
|
||||
const nodesStats = await getNodesDetails();
|
||||
if (!nodesStats) throw new Error("Failed to get nodes attributes");
|
||||
|
||||
const statusPromises = nodesStats.slice(0, config.nodes_settings.limit).map(async (node) => {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Fetching ${cliColor.blueBright(node.attributes.name)} configuration...`))
|
||||
const nodeConfig = await require("./getNodeConfiguration.js")(node.attributes.id);
|
||||
const nodeConfig = await getNodeConfiguration(node.attributes.id);
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Checking ${cliColor.blueBright(node.attributes.name)} wings status...`))
|
||||
const nodeStatus = await require("./promiseTimeout.js")(require("./getWingsStatus.js")(node, nodeConfig.token), config.timeout * 1000);
|
||||
const nodeStatus = await promiseTimeout(getWingsStatus(node, nodeConfig.token), config.timeout * 1000);
|
||||
|
||||
let nodeUptime = cache ? (() => {
|
||||
return cache.nodes.find((n) => n.attributes.id === node.attributes.id)?.uptime || Date.now()
|
||||
@@ -72,8 +79,8 @@ module.exports = async function getStats() {
|
||||
uptime: cache ? (() => {
|
||||
return cache.uptime || Date.now()
|
||||
})() : Date.now(),
|
||||
servers: await require("./getServers.js")(),
|
||||
users: await require("./getUsers.js")(),
|
||||
servers: await getServers(),
|
||||
users: await getUsers(),
|
||||
nodes: await Promise.all(statusPromises),
|
||||
isPanelDown: !cache.uptime,
|
||||
timestamp: Date.now()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const config = require("./config.js");
|
||||
const config = require("./configuration.js");
|
||||
const cliColor = require("cli-color");
|
||||
|
||||
module.exports = async function getUsers() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const config = require("./config.js");
|
||||
const config = require("./configuration.js");
|
||||
|
||||
module.exports = async function getWingsStatus(node, nodeToken) {
|
||||
return fetch(`${node.attributes.scheme}://${node.attributes.fqdn}:${node.attributes.daemon_listen}/api/servers`, {
|
||||
|
||||
@@ -2,6 +2,7 @@ const axios = require("axios")
|
||||
const cliColor = require("cli-color")
|
||||
const { Client, GatewayIntentBits } = require("discord.js")
|
||||
const fs = require("fs")
|
||||
const application = require("./application.js");
|
||||
const readline = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
@@ -15,6 +16,14 @@ const questions = [
|
||||
"Please enter your channel ID: "
|
||||
];
|
||||
|
||||
const Question = {
|
||||
panelName: 0,
|
||||
panelUrl: 1,
|
||||
panelApiKey: 2,
|
||||
botToken: 3,
|
||||
channelId: 4,
|
||||
}
|
||||
|
||||
const answers = [];
|
||||
|
||||
const isValidURL = (url) => {
|
||||
@@ -37,18 +46,18 @@ module.exports = function Setup() {
|
||||
readline.question('> ', answer => {
|
||||
let isValid = true;
|
||||
|
||||
if (index === 1 && !isValidURL(answer)) {
|
||||
if (index === Question.panelUrl && !isValidURL(answer)) {
|
||||
console.log(cliColor.redBright('❌ Invalid Panel URL. Please enter a valid URL. Example Correct URL: "https://panel.example.com"'));
|
||||
isValid = false;
|
||||
} else if (index === 2 && !/^(plcn_|ptlc_|peli_|ptla_)/.test(answer)) {
|
||||
} else if (index === Question.panelApiKey && !/^(plcn_|ptlc_|peli_|ptla_)/.test(answer)) {
|
||||
console.log(cliColor.redBright("❌ Invalid Panel API key. It must start with 'plcn_' or 'ptlc_'."));
|
||||
isValid = false;
|
||||
} else if (index === 4 && !/^\d+$/.test(answer)) {
|
||||
} else if (index === Question.channelId && !/^\d+$/.test(answer)) {
|
||||
console.log(cliColor.redBright("❌ Invalid Channel ID. It must be a number."));
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (index === 2 && /^(peli_|ptla_)/.test(answer)) console.log(cliColor.yellow("The use of Application API keys are deprecated, you should use Client API keys"));
|
||||
if (index === Question.panelApiKey && /^(peli_|ptla_)/.test(answer)) console.log(cliColor.yellow("The use of Application API keys are deprecated, you should use Client API keys"));
|
||||
|
||||
if (isValid) {
|
||||
answers.push(isValidURL(answer) ? new URL(answer).origin : answer);
|
||||
@@ -58,11 +67,11 @@ module.exports = function Setup() {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
axios(`${new URL(answers[1]).origin}/api/application/nodes?include=servers,location,allocations`, {
|
||||
axios(`${new URL(answers[Question.panelUrl]).origin}/api/application/nodes?include=servers,location,allocations`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${answers[2]}`
|
||||
"Authorization": `Bearer ${answers[Question.panelApiKey]}`
|
||||
},
|
||||
}).then(() => {
|
||||
console.log(" \n" + cliColor.green("✓ Valid Panel Credentials."));
|
||||
@@ -70,15 +79,15 @@ module.exports = function Setup() {
|
||||
intents: [GatewayIntentBits.Guilds]
|
||||
})
|
||||
|
||||
client.login(answers[3]).then(async () => {
|
||||
client.login(answers[Question.botToken]).then(async () => {
|
||||
console.log(cliColor.green("✓ Valid Discord Bot"));
|
||||
client.channels.fetch(answers[4]).then(() => {
|
||||
client.channels.fetch(answers[Question.channelId]).then(() => {
|
||||
console.log(cliColor.green("✓ Valid Discord Channel"));
|
||||
fs.writeFileSync(".env", `PanelURL=${answers[1]}\nPanelKEY=${answers[2]}\nDiscordBotToken=${answers[3]}\nDiscordChannel=${answers[4]}`, "utf8")
|
||||
fs.writeFileSync(".env", `PanelURL=${answers[Question.panelUrl]}\nPanelKEY=${answers[Question.panelApiKey]}\nDiscordBotToken=${answers[Question.botToken]}\nDiscordChannel=${answers[Question.channelId]}`, "utf8")
|
||||
fs.writeFileSync("config.yml", fs.readFileSync("./config.yml", "utf8").replaceAll("Hosting Panel", answers[0]).replaceAll("https://panel.example.com", answers[1]), "utf-8")
|
||||
console.log(" \n" + cliColor.green(`Configuration saved in ${cliColor.blueBright(".env")} and ${cliColor.blueBright("config.yml")}.\n `));
|
||||
|
||||
require("./app.js")()
|
||||
application()
|
||||
}).catch(() => {
|
||||
console.log(cliColor.redBright("❌ Invalid Channel ID."));
|
||||
console.log(" \n" + cliColor.redBright("Please run the setup again and fill in the correct credentials."));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { WebhookClient, EmbedBuilder } = require("discord.js")
|
||||
const config = require("./config")
|
||||
const config = require("./configuration")
|
||||
const cliColor = require("cli-color")
|
||||
|
||||
module.exports = function Webhook(embed) {
|
||||
|
||||
6
index.js
6
index.js
@@ -1,6 +1,8 @@
|
||||
const fs = require("node:fs");
|
||||
const cliColor = require("cli-color");
|
||||
const package = require("./package.json");
|
||||
const setup = require("./handlers/setup.js");
|
||||
const application = require("./handlers/application.js");
|
||||
|
||||
console.log(
|
||||
` _${cliColor.blueBright.bold(`${cliColor.underline("Ptero")}dact${cliColor.underline("yl & P")}eli${cliColor.underline("can")}`)}___ ______ ______ \n` +
|
||||
@@ -18,6 +20,6 @@ console.log(
|
||||
` \n \n${package.description}\n `
|
||||
);
|
||||
|
||||
if (!fs.existsSync(".env")) return require("./handlers/setup.js")();
|
||||
if (!fs.existsSync(".env")) return setup();
|
||||
|
||||
require("./handlers/app.js")();
|
||||
application();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pterostats",
|
||||
"version": "4.0.0",
|
||||
"description": "PteroStats is a Discord App/Bot that designed to check Pterodactyl or Pelican Panel stats and post it to your Discord server.",
|
||||
"description": "PteroStats is a Discord App/Bot designed to check Pterodactyl or Pelican Panel stats and post it to your Discord server.",
|
||||
"license": "MIT",
|
||||
"repository": "HirziDevs/PteroStats",
|
||||
"homepage": "https://pterostats.znproject.my.id",
|
||||
|
||||
10
setup.js
10
setup.js
@@ -1,6 +1,8 @@
|
||||
const fs = require("node:fs");
|
||||
const cliColor = require("cli-color");
|
||||
const package = require("./package.json");
|
||||
const setup = require("./handlers/setup.js");
|
||||
const application = require("./handlers/application.js");
|
||||
const readline = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
@@ -22,7 +24,7 @@ console.log(
|
||||
` \n \n${package.description}\n `
|
||||
);
|
||||
|
||||
if (!fs.existsSync(".env")) return require("./handlers/setup.js")();
|
||||
if (!fs.existsSync(".env")) return setup();
|
||||
|
||||
console.log(cliColor.yellowBright(
|
||||
"Configuration is already set. Please select one of the following options:\n \n" +
|
||||
@@ -35,12 +37,12 @@ readline.question('> ', async (answer) => {
|
||||
|
||||
switch (answer) {
|
||||
case '2':
|
||||
require('./handlers/setup.js')();
|
||||
setup();
|
||||
break;
|
||||
case '1':
|
||||
require('./handlers/app.js')();
|
||||
application();
|
||||
break;
|
||||
default:
|
||||
console.log(cliColor.redBright('Invalid input. Please type either 1 or 2.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user