feat: installer

This commit is contained in:
hirzidevs
2024-09-10 05:52:54 +07:00
parent 9afcc48d4a
commit d5d644dc5e
12 changed files with 145 additions and 39 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
node_modules/
config-dev.yml
package-lock.json
cache.json
cache.json
.env
+5 -9
View File
@@ -1,8 +1,8 @@
# PteroStats config
# If you need help, join our discord server here: https://discord.gg/zv6maQRah3
version: 4 # Do not change this unless you know what you are doing!
# Bot Configuration
token: "Put Bot Token Here"
presence:
enable: true
text: "Hosting Panel"
@@ -10,15 +10,9 @@ presence:
status: "online" # can be 'online', 'idle', 'dnd', or 'invisible'
# Discord Channel and Refresh Time Configuration
channel: "Put Channel ID Here"
refresh: 10 # How much time the bot will refresh the stats
timeout: 5 # How much time to wait for a node to respond to the bot (if you change this, it will add more time to refresh the stats)
# Panel Configuration
panel:
url: "Put Panel URL Here"
key: "Put Panel APIKEY Here"
# Message and Embed Configuration
# set the option as '' if you want to disable it
message:
@@ -28,10 +22,10 @@ message:
embed:
title_nodes: "Nodes Stats"
title_panel: "Panel Stats"
author: "PteroStats"
author: "Hosting Panel"
color: "5865F2"
description: "Next update {{time}}" # You can use {{time}} to make "in X seconds" time format
footer: "By Hirzi#8701"
footer: "By @HirziDevs"
timestamp: true
thumbnail: ""
image: ""
@@ -42,6 +36,8 @@ button:
row1:
- label: "Panel"
url: "https://panel.example.com"
# - label: "Dashboard"
# url: "https://dash.example.com"
# Status Message Configuration
# How to use custom emoji: https://github.com/HirziDevs/PteroStats#using-custom-emoji
+10 -1
View File
@@ -3,6 +3,8 @@ const fs = require("node:fs");
const yaml = require("js-yaml");
const cliColor = require("cli-color");
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..."))
@@ -10,11 +12,18 @@ if (fs.existsSync("config-dev.yml")) {
}
try {
const testURL = new URL(config.panel.url);
const testURL = new URL(process.env?.PanelURL);
if (!testURL.protocol.startsWith("http")) throw new Error();
} catch {
console.error('Config Error | Invalid URL Format! Example Correct URL: "https://panel.example.com"');
process.exit();
}
if (config.version !== 4) {
console.error('Config Error | Invalid config version! The config has been updated, please get the new config from https://github.com/HirziDevs/PteroStats/blob/dev/config.yml');
process.exit();
}
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Configuration loaded"))
module.exports = config
+1 -1
View File
@@ -12,7 +12,7 @@ module.exports = function convertUnits(value1, value2, unit) {
return `${!percentage ? 0 : percentage}%`;
} else if (units.includes(unit)) {
const formatValue = (value) => Number.isInteger(value) ? value.toLocaleString() : value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
return `${formatValue(value1 * conversionFactors[unit])} ${unit}/${(value2 === 0 || value2 < 1) ? "Unlimited" : formatValue(value2 * conversionFactors[unit]) + " " + unit}`;
} else {
return `${value1.toLocaleString()} MB/${value2.toLocaleString()} MB`;
+2 -2
View File
@@ -1,11 +1,11 @@
const config = require("./config.js");
module.exports = async function getNodeConfiguration(id) {
return fetch(`${new URL(config.panel.url).origin}/api/application/nodes/${id}/configuration`, {
return fetch(`${new URL(process.env?.PanelURL).origin}/api/application/nodes/${id}/configuration`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${config.panel.key}`
"Authorization": `Bearer ${process.env?.PanelKEY}`
},
})
.then((res) => res.json())
+2 -2
View File
@@ -3,11 +3,11 @@ const config = require("./config.js");
const axios = require("axios");
module.exports = async function getAllNodes() {
return axios(`${new URL(config.panel.url).origin}/api/application/nodes?include=servers,location,allocations`, {
return axios(`${new URL(process.env?.PanelURL).origin}/api/application/nodes?include=servers,location,allocations`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${config.panel.key}`
"Authorization": `Bearer ${process.env?.PanelKEY}`
},
})
.then((res) => res.data.data.filter((node) => !config.nodes_settings.blacklist.includes(node.attributes.id)))
+3 -3
View File
@@ -2,12 +2,12 @@ const config = require("./config.js");
const cliColor = require("cli-color");
module.exports = async function getServers() {
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Getting panel servers..."))
return fetch(`${new URL(config.panel.url).origin}/api/application/servers`, {
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Retrieving panel servers..."))
return fetch(`${new URL(process.env?.PanelURL).origin}/api/application/servers`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${config.panel.key}`
"Authorization": `Bearer ${process.env?.PanelKEY}`
},
})
.then((res) => res.json())
+4 -4
View File
@@ -9,18 +9,18 @@ const promiseTimeout = require("./promiseTimeout.js");
const cliColor = require("cli-color");
module.exports = async function getStats() {
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Getting panel nodes..."))
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Retrieving panel nodes..."))
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(`Getting "${node.attributes.name}" configuration...`))
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Fetching "${node.attributes.name}" configuration...`))
const nodeConfig = await getNodeConfiguration(node.attributes.id);
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Getting "${node.attributes.name}" wings status...`))
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Checking "${node.attributes.name}" wings status...`))
const nodeStatus = await promiseTimeout(getWingsStatus(node, nodeConfig.token), config.timeout * 1000);
if (!nodeStatus)
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright(`Node "${node.attributes.name}" is offline`))
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright(`Node "${node.attributes.name}" is currently offline`))
return {
attributes: {
name: node.attributes.name,
+3 -3
View File
@@ -2,12 +2,12 @@ const config = require("./config.js");
const cliColor = require("cli-color");
module.exports = async function getUsers() {
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Getting panel users..."))
return fetch(`${new URL(config.panel.url).origin}/api/application/users`, {
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow("Retrieving panel users..."))
return fetch(`${new URL(process.env?.PanelURL).origin}/api/application/users`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${config.panel.key}`
"Authorization": `Bearer ${process.env?.PanelKEY}`
},
})
.then((res) => res.json())
+77
View File
@@ -0,0 +1,77 @@
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
const fs = require("fs")
const cliColor = require("cli-color")
const questions = [
"Please enter your panel name: ",
"Please enter your panel URL: ",
"Please enter your panel API key: ",
"Please enter your bot token: ",
"Please enter your channel ID: "
];
const answers = [];
const isValidURL = (url) => {
try {
new URL(url);
return true;
} catch (error) {
return false;
}
};
module.exports = function Installer() {
console.log("Welcome! Please fill this question to start PteroStats.");
const askQuestion = (index) => {
if (index < questions.length) {
console.log(questions[index]);
readline.question('> ', answer => {
let isValid = true;
if (index === 1 && !isValidURL(answer)) {
console.log(cliColor.redBright("Invalid URL. Please enter a valid URL."));
isValid = false;
} else if (index === 2 && !/^(plcn_|ptlc_|peli_|ptla_)/.test(answer)) {
console.log(cliColor.redBright("Invalid API key. It must start with 'plcn_' or 'ptlc_'."));
isValid = false;
} else if (index === 4 && !/^\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 (isValid) {
answers.push(isValidURL(answer) ? new URL(answer).origin : answer);
askQuestion(index + 1);
} else {
askQuestion(index);
}
});
} else {
console.log(" \n====================================================")
console.log("Thank you! Here are the details you've provided:");
console.log(`Panel Name : ${cliColor.cyanBright(answers[0])}`);
console.log(`Panel URL : ${cliColor.cyanBright(answers[1])}`);
console.log(`Panel API Key : ${cliColor.cyanBright(answers[2])}`);
console.log(`Discord Bot Token : ${cliColor.cyanBright(answers[3])}`);
console.log(`Discord Channel ID : ${cliColor.cyanBright(answers[4])}`);
console.log("====================================================\n \n")
fs.writeFileSync(".env", `PanelURL=${answers[1]}\nPanelKEY=${answers[2]}\nDiscordBotToken=${answers[3]}\nDiscordChannel=${answers[4]}`, "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(cliColor.cyanBright("Please restart the bot to continue."))
readline.close();
}
};
askQuestion(0);
}
+29 -7
View File
@@ -1,11 +1,30 @@
const { Client, GatewayIntentBits, EmbedBuilder, time, ActivityType } = require("discord.js");
const config = require("./handlers/config.js");
const convertUnits = require("./handlers/convertUnits.js");
const getStats = require("./handlers/getStats.js");
const { Client, GatewayIntentBits, EmbedBuilder, time, ActivityType, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js");
const cachePath = require('node:path').join(__dirname, "./cache.json");
const fs = require("node:fs");
const cliColor = require("cli-color");
console.log(`
_${cliColor.blueBright.bold("Pterodactyl & Pelican")}___ ______ ______
/\\ ___\\ /\\__ _\\ /\\ __ \\ /\\__ _\\ /\\ ___\\
\\ \\___ \\ \\/_ \\ \\/ \\ \\ \\_\\ \\ \\/_/\\ \\/ \\ \\___ \\
\\/\\_____\\ \\ \\_\\ \\ \\_\\ \\_\\ \\ \\_\\ \\/\\_____\\
\\/_____/ \\/_/ \\/_/\\/_/ \\/_/ \\/_____/${cliColor.yellowBright.bold("4.0.0-dev")}`);
console.log(
` \nCopyright © 2022 - ${new Date().getFullYear()} HirziDevs & Contributors` +
" \n \nDiscord: https://discord.znproject.my.id" +
" \n Source: https://github.com/HirziDevs/PteroStats" +
" \nLicense: https://github.com/Hirzidevs/PteroStats/blob/main/LICENSE" +
" \n \nPteroStats is a Discord Bot that designed to check Pterodactyl or Pelican Panel stats and post it to your Discord server. \n \n"
);
if (!fs.existsSync(".env")) return require("./handlers/installer.js")()
require("dotenv").config()
const config = require("./handlers/config.js");
const convertUnits = require("./handlers/convertUnits.js");
const getStats = require("./handlers/getStats.js");
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.greenBright("Starting bot..."));
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("You are using development build! some features may not work."));
@@ -141,7 +160,7 @@ async function createMessage({ cache, panel, nodes, servers, users }) {
if (!cache && !panel) {
embeds[embeds.length - 1].setDescription(
embeds[embeds.length - 1].data.description ? embeds[embeds.length - 1].data.description + "\n\nThere are no nodes to display!" : "There are no nodes to display!"
embeds[embeds.length - 1].data.description ? embeds[embeds.length - 1].data.description + "\n\nThere are no nodes to be display!" : "There are no nodes to be display!"
);
}
@@ -159,10 +178,13 @@ async function createMessage({ cache, panel, nodes, servers, users }) {
)
)
}
try {
const channel = await client.channels.fetch(process.env?.DiscordChannel);
const messages = await channel.messages.fetch({ limit: 10 });
const botMessage = messages.find(msg => msg.author.id === client.user.id);
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.greenBright("Panel stats have been posted to the channel!"));
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.greenBright(`Panel stats successfuly posted to the "${channel.name}" channel!`));
setTimeout(() => startGetStatus(), config.refresh * 1000);
@@ -192,7 +214,7 @@ function DiscordErrorHandler(error) {
}
try {
client.login(config.token);
client.login(process.env?.DiscordBotToken);
} catch {
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error | Invalid Discord Bot Token! Make sure you have the correct token in the config!"));
process.exit();
+7 -6
View File
@@ -1,10 +1,10 @@
{
"name": "pterostats",
"version": "v4.0.0",
"description": "PteroStats is a bot designed to check Pterodactyl Panel and Nodes status and post it to your discord server",
"version": "4.0.0",
"description": "PteroStats is a Discord Bot that designed to check Pterodactyl or Pelican Panel stats and post it to your Discord server.",
"license": "MIT",
"repository": "HirziDevs/PteroStats",
"homepage": "https://pterostats.hirzidevs.xyz",
"homepage": "https://pterostats.znproject.my.id",
"bugs": {
"email": "hirzidevs@gmail.com",
"url": "https://github.com/HirziDevs/PteroStats/issues"
@@ -13,12 +13,13 @@
"start": "node index.js"
},
"dependencies": {
"axios": "^1.7.2",
"axios": "^1.7.7",
"cli-color": "^2.0.4",
"discord.js": "^14.15.3",
"discord.js": "^14.16.1",
"dotenv": "^16.4.5",
"js-yaml": "^4.1.0"
},
"engines": {
"node": ">=16.11.0"
"node": ">=18"
}
}