diff --git a/README.md b/README.md
index a031c91..1bc9e65 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
## 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
@@ -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.
diff --git a/handlers/UptimeFormatter.js b/handlers/UptimeFormatter.js
index 711d9e9..1d214ca 100644
--- a/handlers/UptimeFormatter.js
+++ b/handlers/UptimeFormatter.js
@@ -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;
diff --git a/handlers/app.js b/handlers/application.js
similarity index 93%
rename from handlers/app.js
rename to handlers/application.js
index 69105e1..3cbc9cf 100644
--- a/handlers/app.js
+++ b/handlers/application.js
@@ -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) {
diff --git a/handlers/config.js b/handlers/configuration.js
similarity index 76%
rename from handlers/config.js
rename to handlers/configuration.js
index 86322fc..ccf1cce 100644
--- a/handlers/config.js
+++ b/handlers/configuration.js
@@ -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
\ No newline at end of file
+module.exports = config;
\ No newline at end of file
diff --git a/handlers/convertUnits.js b/handlers/convertUnits.js
index d0dce92..6a116f1 100644
--- a/handlers/convertUnits.js
+++ b/handlers/convertUnits.js
@@ -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}`}`;
}
}
\ No newline at end of file
diff --git a/handlers/getNodeConfiguration.js b/handlers/getNodeConfiguration.js
index ebbb4b7..c704d15 100644
--- a/handlers/getNodeConfiguration.js
+++ b/handlers/getNodeConfiguration.js
@@ -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`, {
diff --git a/handlers/getNodesDetails.js b/handlers/getNodesDetails.js
index a4f75e5..869ee27 100644
--- a/handlers/getNodesDetails.js
+++ b/handlers/getNodesDetails.js
@@ -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() {
diff --git a/handlers/getServers.js b/handlers/getServers.js
index 5e96b77..1a8aab5 100644
--- a/handlers/getServers.js
+++ b/handlers/getServers.js
@@ -1,4 +1,4 @@
-const config = require("./config.js");
+const config = require("./configuration.js");
const cliColor = require("cli-color");
module.exports = async function getServers() {
diff --git a/handlers/getStats.js b/handlers/getStats.js
index 7579e9d..58aa52a 100644
--- a/handlers/getStats.js
+++ b/handlers/getStats.js
@@ -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()
diff --git a/handlers/getUsers.js b/handlers/getUsers.js
index 19e403d..1b71b3d 100644
--- a/handlers/getUsers.js
+++ b/handlers/getUsers.js
@@ -1,4 +1,4 @@
-const config = require("./config.js");
+const config = require("./configuration.js");
const cliColor = require("cli-color");
module.exports = async function getUsers() {
diff --git a/handlers/getWingsStatus.js b/handlers/getWingsStatus.js
index 69975df..68d5c10 100644
--- a/handlers/getWingsStatus.js
+++ b/handlers/getWingsStatus.js
@@ -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`, {
diff --git a/handlers/setup.js b/handlers/setup.js
index 78a88d8..37cbb13 100644
--- a/handlers/setup.js
+++ b/handlers/setup.js
@@ -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."));
diff --git a/handlers/webhook.js b/handlers/webhook.js
index 84db3e0..b1a79bf 100644
--- a/handlers/webhook.js
+++ b/handlers/webhook.js
@@ -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) {
diff --git a/index.js b/index.js
index 64d16cd..fd4383c 100644
--- a/index.js
+++ b/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")();
\ No newline at end of file
+application();
diff --git a/package.json b/package.json
index 7419d15..647f718 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/setup.js b/setup.js
index 0de3947..ae62cbf 100644
--- a/setup.js
+++ b/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.'));
}
-});
\ No newline at end of file
+});