mirror of
https://github.com/HirziDevs/PteroStats
synced 2026-08-17 04:36:21 +00:00
Recode PteroStats and Support Pelican Panel
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
config-dev.yml
|
||||
package-lock.json
|
||||
cache.json
|
||||
@@ -1,129 +0,0 @@
|
||||
<div align="center">
|
||||
|
||||
<img alt="PteroStats Banner" src="https://cdn.discordapp.com/attachments/626755594526916629/978478722489393153/20220524_090325.png" width="400"/>
|
||||
|
||||
## Bahasa / Language
|
||||
[[Indonesia]](https://github.com/HirziDevs/PteroStats/blob/dev/Indo.md) | [[Inggris]](https://github.com/HirziDevs/PteroStats/blob/dev/README.md)
|
||||
|
||||
</div>
|
||||
|
||||
## Pengenalan
|
||||
PteroStats adalah bot yang dirancang untuk memeriksa status panel pterodactyl dan dikirim ke server discord
|
||||
|
||||
## Contoh
|
||||
- Test Panel
|
||||
|
||||
<img alt="Example" src="https://cdn.discordapp.com/attachments/988796533430448148/991520721467613224/example.gif" width="200"/>
|
||||
|
||||
- [Calvs Cloud](https://discord.gg/ssCQjhrBJN)
|
||||
|
||||
<img alt="Calvs Cloud" src="https://media.discordapp.net/attachments/819757140155564062/1037353043487887410/unknown.png" width="200">
|
||||
|
||||
## Instalasi
|
||||
1. [Mendapatkan apikey dari pterodactyl](#mendapatkan-apikey-dari-pterodactyl)
|
||||
2. [Membuat Discord Bot](#membuat-discord-bot)
|
||||
3. [Menginvite Discord Bot](#menginvite-discord-bot)
|
||||
4. [Mendapatkan Channel ID](#mendapatkan-channel-id)
|
||||
5. [Memulai Bot](#memulai-bot)
|
||||
|
||||
- [Mengunakan custom emoji](#mengunakan-custom-emoji)
|
||||
- [Blacklist Nodes](#blacklist-nodes)
|
||||
|
||||
### Mendapatkan apikey dari pterodactyl
|
||||
1. Pergi ke `panel admin pterodactyl` dan pergi ke `Application API`
|
||||
|
||||
<img alt="Admin Panel" src="https://usercontent.catto.pictures/hirzi/aabafe57-cbfe-4d7f-9d6d-4a63a7f23d4c.png" width="400"/>
|
||||
|
||||
2. Klik tombol `Create New`
|
||||
|
||||
<img alt="Application API Page" src="https://usercontent.catto.pictures/hirzi/f916f0c6-0968-4125-8226-ba4daa1de902.png" width="400"/>
|
||||
|
||||
3. Set semua permission ke `read` dan untuk description kamu bisa mengisi apa saja
|
||||
|
||||
<img alt="Create Application API" src="https://usercontent.catto.pictures/hirzi/3e4575cb-4f52-4bd9-9091-36fda20bedad.png" width="400"/>
|
||||
|
||||
4. Copy apikey-nya.
|
||||
|
||||
<img alt="Application API List" src="https://usercontent.catto.pictures/hirzi/9142b0b3-0556-4741-840c-6976c3fe3ad4.png" width="400"/>
|
||||
|
||||
5. Paste panel apikeynya dan panel urlnya di config
|
||||
|
||||
<img alt="Panel Config" src="https://usercontent.catto.pictures/hirzi/2b9365b8-69d2-4fa0-8eac-3efc8591b765.png" width="400"/>
|
||||
|
||||
### Membuat Discord Bot
|
||||
Kalian bisa cek [website ini](https://discordjs.guide/preparations/setting-up-a-bot-application.html)
|
||||
|
||||
Paste bot tokennya di config
|
||||
|
||||
<img alt="Bot Config" src="https://usercontent.catto.pictures/hirzi/4eb4d5ff-6969-4461-b01d-c45888cfc994.png" width="400"/>
|
||||
|
||||
### Menginvite Discord Bot
|
||||
Kalian bisa cek [website ini](https://discordjs.guide/preparations/adding-your-bot-to-servers.html)
|
||||
|
||||
### Mendapatkan Channel ID
|
||||
1. Aktifkan `Developer Mode` di settings discord kamu
|
||||
|
||||
<img alt="Discord User Settings" src="https://usercontent.catto.pictures/hirzi/36894499-b141-488f-98ed-40245c8f6862.png" width="400"/>
|
||||
|
||||
2. Klik kanan teks channel dan pilih `Copy ID`
|
||||
|
||||
<img alt="Right Click Channel" src="https://usercontent.catto.pictures/hirzi/9f8352da-df5b-4587-9594-ced9b11a5507.png" width="250"/>
|
||||
|
||||
3. Paste id channelnya di config
|
||||
|
||||
<img alt="Channel Config" src="https://usercontent.catto.pictures/hirzi/b34cdbee-1e24-49f2-8219-efe0344a24f9.png" width="400"/>
|
||||
|
||||
### Memulai Bot
|
||||
1. Pastikan kamu telah melakukan semua yang ada diatas
|
||||
2. Jalankan command `npm install` di folder yang berisi file bot
|
||||
3. Jalankan command `node index` dan kamu selesai!
|
||||
|
||||
Jika kamu mendapat masalah bisa dm `Hirzi#8701` didiscord atau join [server support kami](https://discord.gg/zv6maQRah3)
|
||||
|
||||
### Mengunakan custom emoji
|
||||
1. ketik `\` di server yang ada custom emojinya
|
||||
|
||||
<img alt="Type \ on the chat" src="https://usercontent.catto.pictures/hirzi/2e3c821f-92f9-4b5c-863a-e020b2fbc426.png" width="350"/>
|
||||
|
||||
2. Pilih custom emoji yang kamu mau
|
||||
|
||||
<img alt="Select Custom Emoji" src="https://usercontent.catto.pictures/hirzi/7c071727-2adb-4c8c-91d3-21664948a334.png" width="300"/>
|
||||
|
||||
3. Copy textnya!
|
||||
|
||||
<img alt="Copy Emoji ID" src="https://usercontent.catto.pictures/hirzi/bd0084ac-f11b-413d-8b66-580efc011908.png" width="400"/>
|
||||
|
||||
4. Paste id emojinya di config
|
||||
|
||||
<img alt="Status Config" src="https://usercontent.catto.pictures/hirzi/458ad1d6-019b-4b27-be60-3cbabfa07c06.png" width="400"/>
|
||||
|
||||
### Blacklist Nodes
|
||||
1. Pilih node yang ada di node list admin page
|
||||
|
||||
<img alt="Nodes List" src="https://usercontent.catto.pictures/hirzi/5699fdbd-7c3c-4fa5-ae2c-d0ccb39cb69e.png" width="400"/>
|
||||
|
||||
2. Cek urlnya dan copy id nodenya
|
||||
|
||||
<img alt="Node Id" src="https://usercontent.catto.pictures/hirzi/45f855fc-6d96-4b23-a96e-892071189d01.png" width="400"/>
|
||||
|
||||
3. Masukan ke blacklist di config
|
||||
|
||||
<img alt="Blacklist Config" src="https://usercontent.catto.pictures/hirzi/cfb479bf-64da-43e5-b0d1-f7c0c78bf068.png" width="400"/>
|
||||
|
||||
Kamu bisa memasukan lebih dari 1 node untuk di blacklist
|
||||
|
||||
<img alt="Blacklist Config" src="https://usercontent.catto.pictures/hirzi/85b6a9b1-8ec9-4395-b5b1-6f85d3f52162.png" width="400"/>
|
||||
|
||||
## Permission apikey
|
||||
|
||||
Jika kamu mengalami error 403 coba aktifkan `read & write` di semua opsi permission
|
||||
|
||||
<img alt="Application API Permission" src="https://usercontent.catto.pictures/hirzi/273d5b86-5249-42d0-bfc6-1dcfcae4efe7.png" width="400"/>
|
||||
|
||||
## Links
|
||||
|
||||
- [Pterodactyl Panel](https://pterodactyl.io)
|
||||
- [Pterodactyl Api Documentation](https://dashflo/docs/api/pterodactyl/v1)
|
||||
- [Pterodactyl Discord Server](https://discord.gg/pterodactyl)
|
||||
- [PteroBot Support Server](https://discord.gg/zv6maQRah3)
|
||||
- [PteroBot Support Server (Indonesia)](https://discord.gg/EYaFB7WSg6)
|
||||
@@ -1,26 +1,20 @@
|
||||
<div align="center">
|
||||
|
||||
<img alt="PteroStats Banner" src="https://cdn.discordapp.com/attachments/626755594526916629/978478722489393153/20220524_090325.png" width="400"/>
|
||||
<img alt="PteroStats Banner" src="https://images-ext-2.discordapp.net/external/oRPpwML4JUV0HbsPKtsghvIjS5ZrVwqH2KQ4tevg_Jg/https/repository-images.githubusercontent.com/381250920/e9acc9c2-2fbd-4fb0-8554-9788146d817e" width="400"/>
|
||||
|
||||
## Language / Bahasa
|
||||
[[English]](https://github.com/HirziDevs/PteroStats/blob/dev/README.md) | [[Indonesia]](https://github.com/HirziDevs/PteroStats/blob/dev/Indo.md)
|
||||
|
||||
</div>
|
||||
|
||||
### This is a development build! some features may not work.
|
||||
## Introduction
|
||||
PteroStats is a bot designed to check Pterodactyl Panel and Nodes status and post it to your discord server
|
||||
|
||||
## Example
|
||||
- Test Panel
|
||||
|
||||
<img alt="Example" src="https://cdn.discordapp.com/attachments/988796533430448148/991520721467613224/example.gif" width="200"/>
|
||||
|
||||
- [Calvs Cloud](https://discord.gg/ssCQjhrBJN)
|
||||
|
||||
<img alt="Calvs Cloud" src="https://media.discordapp.net/attachments/819757140155564062/1037353043487887410/unknown.png" width="200">
|
||||
<img alt="Example" src="https://i.imgur.com/fzQANo5.gif" width="200"/>
|
||||
|
||||
## Installation
|
||||
1. [Getting apikey from pterodactyl](#getting-apikey-from-pterodactyl)
|
||||
1. [Getting API key from pterodactyl](#getting-apikey-from-pterodactyl)
|
||||
2. [Creating Discord Bot](#creating-discord-bot)
|
||||
3. [Inviting Discord Bot](#inviting-discord-bot)
|
||||
4. [Getting Channel ID](#getting-channel-id)
|
||||
@@ -29,24 +23,28 @@ PteroStats is a bot designed to check Pterodactyl Panel and Nodes status and pos
|
||||
- [Using Custom Emoji](#using-custom-emoji)
|
||||
- [Blacklist Nodes](#blacklist-nodes)
|
||||
|
||||
### Getting apikey from pterodactyl
|
||||
1. Go to your `pterodactyl admin page` and go to `Application API`.
|
||||
### Getting API key from pterodactyl
|
||||
|
||||
<img alt="Admin Panel" src="https://usercontent.catto.pictures/hirzi/aabafe57-cbfe-4d7f-9d6d-4a63a7f23d4c.png" width="400"/>
|
||||
> [!WARNING]
|
||||
> The use of Application API keys are **deprecated**, you should use **Client API keys** in the config file
|
||||
|
||||
2. Click on the `Create New` button
|
||||
1. Go to your `Pterodactyl Panel` and go to `Account Page`.
|
||||
|
||||
<img alt="Application API Page" src="https://usercontent.catto.pictures/hirzi/f916f0c6-0968-4125-8226-ba4daa1de902.png" width="400"/>
|
||||
<img alt="Home" src="https://i.imgur.com/Uu97RJO.png" width="400"/>
|
||||
|
||||
3. Set all options permission to `read` and for description you can put whatever you want
|
||||
2. Click on the `API Credentials` button
|
||||
|
||||
<img alt="Create Application API" src="https://usercontent.catto.pictures/hirzi/3e4575cb-4f52-4bd9-9091-36fda20bedad.png" width="400"/>
|
||||
<img alt="Account Page" src="https://i.imgur.com/sm4THSu.png" width="400"/>
|
||||
|
||||
4. Copy the apikey.
|
||||
3. Fill the `Description` and click on the `Create` button
|
||||
|
||||
<img alt="Application API List" src="https://usercontent.catto.pictures/hirzi/9142b0b3-0556-4741-840c-6976c3fe3ad4.png" width="400"/>
|
||||
<img alt="Create Client API Key" src="https://i.imgur.com/Q5E0PY4.png" width="400"/>
|
||||
|
||||
5. Paste the panel apikey and panel url at the config
|
||||
4. Copy the API key.
|
||||
|
||||
<img alt="API Key" src="https://i.imgur.com/7goShy8.png" width="400"/>
|
||||
|
||||
5. Paste the panel API key and panel url at the config
|
||||
|
||||
<img alt="Panel Config" src="https://usercontent.catto.pictures/hirzi/2b9365b8-69d2-4fa0-8eac-3efc8591b765.png" width="400"/>
|
||||
|
||||
@@ -78,10 +76,10 @@ Please refer to [this website](https://discordjs.guide/preparations/adding-your-
|
||||
2. Run `npm install` in the root directory of the bot files.
|
||||
3. Run `node index` and you are done.
|
||||
|
||||
if you need help contact me on discord `Hirzi#8701` or join [our discord support server](https://discord.gg/zv6maQRah3)
|
||||
if you need help contact me on discord `@hirzidevs` or join [our discord support server](https://discord.gg/zv6maQRah3)
|
||||
|
||||
### Using Custom Emoji
|
||||
1. type `\` in guild that has custom emoji you want
|
||||
1. type `\` in server that has custom emoji you want
|
||||
|
||||
<img alt="Type \ on the chat" src="https://usercontent.catto.pictures/hirzi/2e3c821f-92f9-4b5c-863a-e020b2fbc426.png" width="350"/>
|
||||
|
||||
@@ -114,16 +112,15 @@ You can add more than one node in the blacklist
|
||||
|
||||
<img alt="Blacklist Config" src="https://usercontent.catto.pictures/hirzi/85b6a9b1-8ec9-4395-b5b1-6f85d3f52162.png" width="400"/>
|
||||
|
||||
## Apikey permission
|
||||
## The node is online but the embed is read as offline
|
||||
|
||||
If you having issue with 403 error try to enable `read & write` on all options
|
||||
|
||||
<img alt="Application API Permission" src="https://usercontent.catto.pictures/hirzi/273d5b86-5249-42d0-bfc6-1dcfcae4efe7.png" width="400"/>
|
||||
If you having this issue, you can enable `log_error` on the config file and report it to our discord server at [Support Server](https://discord.gg/zv6maQRah3)
|
||||
|
||||
## Links
|
||||
|
||||
- [PteroStats DiscordJS v13](https://github.com/HirziDevs/PteroStats/tree/3d0512c3323ecf079101104c7ecf3c94d265e298)
|
||||
- [PteroStats DiscordJS v12](https://github.com/HirziDevs/PteroStats/tree/bcfa266be64dda11955f0bf9732da086bcea522c)
|
||||
- [Pterodactyl Panel](https://pterodactyl.io)
|
||||
- [Pterodactyl Api Documentation](https://dashflo/docs/api/pterodactyl/v1)
|
||||
- [Pterodactyl API Documentation](https://github.com/devnote-dev/ptero-notes/)
|
||||
- [Pterodactyl Discord Server](https://discord.gg/pterodactyl)
|
||||
- [PteroBot Support Server](https://discord.gg/zv6maQRah3)
|
||||
- [PteroBot Support Server (Indonesia)](https://discord.gg/EYaFB7WSg6)
|
||||
+65
-55
@@ -2,75 +2,85 @@
|
||||
# If you need help, join our discord server here: https://discord.gg/zv6maQRah3
|
||||
|
||||
# Bot Configuration
|
||||
token: 'Put bot token here'
|
||||
bot_status:
|
||||
enable: false
|
||||
text: 'Hosting Panel'
|
||||
type: 'WATCHING' # can be 'WATCHING', 'PLAYING', 'LISTENING', or 'COMPETING'. for 'STREAMING' is not working for now
|
||||
token: "Put Bot Token Here"
|
||||
presence:
|
||||
text: "Hosting Panel"
|
||||
type: "watching" # can be 'watching', 'playing', 'listening', or 'competing'. 'streaming' is not working for now
|
||||
status: "online" # can be 'online', 'idle', 'dnd', or 'invisible'
|
||||
|
||||
# Discord Channel and Refresh Time Configuration
|
||||
channel: 'Put channel id here'
|
||||
refresh: 60
|
||||
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'
|
||||
url: "Put Panel URL Here"
|
||||
key: "Put Panel APIKEY Here"
|
||||
|
||||
# Message Configuration
|
||||
# set to false if you want to disable option
|
||||
# Message and Embed Configuration
|
||||
# set the option as '' if you want to disable it
|
||||
message:
|
||||
content: false
|
||||
attachment: false # If you enable attachment on message it upload the attachment first before sending or editing message and it will delay the stats
|
||||
content: ""
|
||||
attachment: "" # If you enable attachment on message it will upload the attachment first before sending or editing message and will result in delayed stats
|
||||
|
||||
embed:
|
||||
title: 'PteroStats'
|
||||
color: '5865F2'
|
||||
description: 'Next update {{time}}' # You can use {{time}} to make "in X seconds" time format
|
||||
footer: 'By Hirzi#8701'
|
||||
timestamp: true
|
||||
thumbnail: false
|
||||
image: false
|
||||
field:
|
||||
enable: false
|
||||
title: 'Custom Field'
|
||||
description: 'Custom Field' # You can use {{time}} to make "in X seconds" time format
|
||||
title_nodes: "Nodes Stats"
|
||||
title_panel: "Panel Stats"
|
||||
author: "PteroStats"
|
||||
color: "5865F2"
|
||||
description: "Next update {{time}}" # You can use {{time}} to make "in X seconds" time format
|
||||
footer: "By Hirzi#8701"
|
||||
timestamp: true
|
||||
thumbnail: ""
|
||||
image: ""
|
||||
|
||||
# Message Button Configuration
|
||||
button:
|
||||
enable: true
|
||||
btn1:
|
||||
label: 'PteroStats'
|
||||
url: 'https://github.com/HirziDevs/PteroStats'
|
||||
btn2:
|
||||
label: ''
|
||||
url: ''
|
||||
btn3:
|
||||
label: ''
|
||||
url: ''
|
||||
btn4:
|
||||
label: ''
|
||||
url: ''
|
||||
btn5:
|
||||
label: ''
|
||||
url: ''
|
||||
enable: true
|
||||
btn1:
|
||||
label: "PteroStats"
|
||||
url: "https://github.com/HirziDevs/PteroStats"
|
||||
btn2:
|
||||
label: ""
|
||||
url: ""
|
||||
btn3:
|
||||
label: ""
|
||||
url: ""
|
||||
btn4:
|
||||
label: ""
|
||||
url: ""
|
||||
btn5:
|
||||
label: ""
|
||||
url: ""
|
||||
|
||||
# Status Message Configuration
|
||||
# How to use custom emoji: https://github.com/HirziDevs/PteroStats#using-custom-emoji
|
||||
status:
|
||||
online: ':green_circle: Online'
|
||||
offline: ':red_circle: Offline'
|
||||
online: ":green_circle: Online"
|
||||
offline: ":red_circle: Offline"
|
||||
|
||||
# Nodes Resource
|
||||
nodes_resource:
|
||||
blacklist: [] # You can add node id to remove the node from status embed (Example: "blacklist: [1]")
|
||||
enable: false
|
||||
servers: true
|
||||
location: true
|
||||
allocations: true
|
||||
unit: 'gb' # You can use "gb", "mb", "tb", or "percent"
|
||||
# Nodes Settings
|
||||
# How to get nodes id: https://github.com/HirziDevs/PteroStats#blacklist-nodes
|
||||
nodes_settings:
|
||||
blacklist: [] # You can add node id to remove the node from status embed (Example: "blacklist: [1]")
|
||||
details: true # enable nodes details i.e memory and disk usage
|
||||
servers: true
|
||||
location: true
|
||||
unit: "mb" # Allowed values- "gb", "mb", "tb", or "%"
|
||||
limit: 100
|
||||
|
||||
# Panel Users and Servers
|
||||
panel_resource:
|
||||
enable: true
|
||||
servers: true
|
||||
users: true
|
||||
# Panel Users and Servers Settings
|
||||
panel_settings:
|
||||
status: true # enable panel stats under nodes stats
|
||||
servers: true
|
||||
users: true
|
||||
|
||||
# Mentions a User or Role if any node is offline (this feature is still in testing, please report if you have a problem)
|
||||
mentions: # to enable atleast put 1 ID on user or role bellow
|
||||
user: [] # Put User ID here (Example: "user: ['548867757517570058', '816219634390663230']")
|
||||
role: [] # Put Role ID here (Example: "role: ['796083838236622858', '858198863973187585']")
|
||||
channel: "" # Put Channel ID here for the logging
|
||||
|
||||
# Log error to console if a server offline (enable this when you have a problem that you want to report)
|
||||
log_error: false # set to "true" to enable
|
||||
@@ -1,29 +0,0 @@
|
||||
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'))
|
||||
|
||||
if (client.guilds.cache.size < 1) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! This bot is not on any discord servers'))
|
||||
|
||||
if (client.config.bot_status.enable && client.config.bot_status.text.length > 0) {
|
||||
if (!['PLAYING', 'WATCHING', 'LISTENING', 'COMPETING'].includes(client.config.bot_status.type.toUpperCase() || client.config.bot_status.type.length < 1)) {
|
||||
console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Status Type!, Can be "WATCHING", "PLAYING", "LISTENING", or "COMPETING"'))
|
||||
} else {
|
||||
client.user.setActivity(client.config.bot_status.text, { type: client.config.bot_status.type.toUpperCase() })
|
||||
}
|
||||
}
|
||||
|
||||
if (client.config.refresh < 10) console.log('Refresh lower than 10 seconds is not recommended!')
|
||||
|
||||
checkStatus(client)
|
||||
|
||||
setInterval(() => {
|
||||
checkStatus(client)
|
||||
}, client.config.refresh * 1000)
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
const axios = require('axios')
|
||||
const chalk = require('chalk')
|
||||
|
||||
const postStatus = require('./postStatus')
|
||||
|
||||
module.exports = function checkStatus(client) {
|
||||
|
||||
if (client.config.channel.startsWith('Put')) {
|
||||
console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Channel ID'))
|
||||
process.exit()
|
||||
} else if (client.config.panel.url.startsWith('Put')) {
|
||||
console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Panel URL'))
|
||||
process.exit()
|
||||
} else if (client.config.panel.key.startsWith('Put')) {
|
||||
console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Apikey'))
|
||||
process.exit()
|
||||
} else if (!client.config.panel.url.startsWith('http')) {
|
||||
console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! 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)
|
||||
|
||||
const nodes = []
|
||||
|
||||
const panel = {
|
||||
status: false,
|
||||
total_servers: -1,
|
||||
total_users: -1,
|
||||
}
|
||||
|
||||
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((usr) => {
|
||||
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((ser) => {
|
||||
panel.total_users = usr.data.meta.pagination.total
|
||||
panel.total_servers = ser.data.meta.pagination.total
|
||||
panel.status = true
|
||||
|
||||
resolve()
|
||||
})
|
||||
}).catch((err) => {
|
||||
if (err.response) {
|
||||
if (err.response.status === 403) {
|
||||
console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! 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 (err.response.status === 404) {
|
||||
console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! 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('Err! ' + err))
|
||||
} else console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! ' + err))
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
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,
|
||||
status: true,
|
||||
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,
|
||||
}
|
||||
|
||||
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((status) => {
|
||||
return statsResolve()
|
||||
}).catch((err) => {
|
||||
body.status = false
|
||||
return statsResolve()
|
||||
})
|
||||
setTimeout(() => {
|
||||
body.status = false
|
||||
return statsResolve()
|
||||
}, 1000)
|
||||
})
|
||||
stats.then(() => {
|
||||
nodes.push(body)
|
||||
if (nodes.length === res.data.data.length) resolve()
|
||||
})
|
||||
}).catch((err) => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}).catch((err) => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
panelStats.then(() => {
|
||||
nodeStats.then(() => {
|
||||
nodes.sort(function (a, b) { return a.id - b.id })
|
||||
postStatus(client, panel, nodes)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
const fs = require("node:fs");
|
||||
const yaml = require("js-yaml");
|
||||
const cliColor = require("cli-color");
|
||||
|
||||
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..."))
|
||||
config = yaml.load(fs.readFileSync("./config-dev.yml", "utf8"));
|
||||
}
|
||||
|
||||
validateURL(config.panel.url);
|
||||
|
||||
function validateURL(url) {
|
||||
try {
|
||||
const testURL = new URL(url);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
@@ -0,0 +1,20 @@
|
||||
module.exports = function convertUnits(value1, value2, unit) {
|
||||
unit = unit.toUpperCase();
|
||||
const units = ['MB', 'GB', 'TB'];
|
||||
const conversionFactors = {
|
||||
'MB': 1,
|
||||
'GB': 1 / 1024,
|
||||
'TB': 1 / (1024 * 1024)
|
||||
};
|
||||
|
||||
if (unit === '%' || unit === "PERCENTAGE" || unit === "PERCENT") {
|
||||
const percentage = Math.floor((value1 / value2) * 100);
|
||||
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`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
const config = require("./config.js");
|
||||
|
||||
module.exports = async function getNodeConfiguration(id) {
|
||||
return fetch(`${new URL(config.panel.url).origin}/api/application/nodes/${id}/configuration`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${config.panel.key}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
const cliColor = require("cli-color");
|
||||
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`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${config.panel.key}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.data.data)
|
||||
.catch((error) => {
|
||||
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.code === "ECONNREFUSED") {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("ECONNREFUSED | Connection refused. Ensure the panel is running and reachable."));
|
||||
} else if (error.code === "ETIMEDOUT") {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("ETIMEDOUT | Connection timed out. The panel took too long to respond."));
|
||||
} else if (error.code === "ECONNRESET") {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("ECONNRESET | Connection reset by peer. The panel closed the connection unexpectedly."));
|
||||
} else if (error.code === "EHOSTUNREACH") {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("EHOSTUNREACH | Host unreachable. The panel is down or not reachable."));
|
||||
} else if (error.response) {
|
||||
if (error.response.status === 401) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("401 | Unauthorized. Invalid Application Key or API Key doesn't have permission to perform this action."));
|
||||
} else if (error.response.status === 403) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("403 | Forbidden. Invalid Application Key or API Key doesn't have permission to perform this action."));
|
||||
} else if (error.response.status === 404) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("404 | Not Found. Invalid Panel URL or the Panel doesn't exist."));
|
||||
} else if (error.response.status === 429) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("429 | Too Many Requests. You have sent too many requests in a given amount of time."));
|
||||
} else if ([500, 502, 503, 504].includes(error.response.status)) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("500 | Internal Server Error. This is an error with your panel, PteroStats is not the cause."));
|
||||
} else {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright(`${error.response.status} | Unexpected error: ${error.response.statusText}`));
|
||||
}
|
||||
} else {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright(`Unexpected error: ${error.message}`));
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${config.panel.key}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.data.length)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
const config = require("./config.js");
|
||||
const fs = require("node:fs");
|
||||
const getNodesDetails = require("./getNodesDetails.js");
|
||||
const getNodeConfiguration = require("./getNodeConfiguration.js");
|
||||
const getWingsStatus = require("./getWingsStatus.js");
|
||||
const getServers = require("./getServers.js");
|
||||
const getUsers = require("./getUsers.js");
|
||||
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..."))
|
||||
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...`))
|
||||
const nodeConfig = await getNodeConfiguration(node.attributes.id);
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.yellow(`Getting "${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`))
|
||||
return {
|
||||
attributes: {
|
||||
name: node.attributes.name,
|
||||
memory: node.attributes.memory,
|
||||
disk: node.attributes.disk,
|
||||
cpu: node.attributes.cpu,
|
||||
allocated_resources: node.attributes.allocated_resources,
|
||||
relationships: {
|
||||
allocations: node.attributes.relationships.allocations.data.length,
|
||||
servers: node.attributes.relationships.servers.data.length
|
||||
}
|
||||
},
|
||||
status: nodeStatus
|
||||
};
|
||||
});
|
||||
|
||||
const data = {
|
||||
servers: await getServers(),
|
||||
users: await getUsers(),
|
||||
nodes: await Promise.all(statusPromises),
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
fs.writeFileSync("cache.json", JSON.stringify(data, null, 2), "utf8");
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${config.panel.key}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.data.length)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = async function getWingsStatus(node, nodeToken) {
|
||||
return fetch(`${node.attributes.scheme}://${node.attributes.fqdn}:${node.attributes.daemon_listen}/api/servers`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${nodeToken}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
const { EmbedBuilder, time, ActionRowBuilder, ButtonBuilder, ButtonStyle, AttachmentBuilder } = require('discord.js')
|
||||
const chalk = require('chalk')
|
||||
|
||||
module.exports = async function postStatus(client, panel, nodes) {
|
||||
|
||||
if (!client.config.nodes_resource) client.config.nodes_resource = client.config.resource
|
||||
if (!client.config.nodes_resource.blacklist) client.config.nodes_resource.blacklist = []
|
||||
if (!Array.isArray(client.config.nodes_resource.blacklist) && Number.isInteger(client.config.nodes_resource.blacklist)) client.config.nodes_resource.blacklist = [client.config.nodes_resource.blacklist]
|
||||
if (!client.config.message.attachment) client.config.message.attachment = client.config.message.image
|
||||
|
||||
if (client.guilds.cache.size < 1) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! This bot is not on any discord servers'))
|
||||
|
||||
const channel = await client.channels.cache.get(client.config.channel)
|
||||
|
||||
if (!channel) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Channel ID'))
|
||||
|
||||
let messages = await channel.messages.fetch({ limit: 10 })
|
||||
messages = messages.filter(m => m.author.id === client.user.id).last();
|
||||
if (messages && messages.embeds.length < 1) {
|
||||
messages.delete()
|
||||
messages = null
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
let text = ''
|
||||
let desc = ''
|
||||
let blacklist = 0
|
||||
|
||||
let content = 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)
|
||||
|
||||
panel.total_users = panel.total_users.toLocaleString()
|
||||
panel.total_servers = panel.total_servers.toLocaleString()
|
||||
|
||||
const stats = new Promise((resolve, reject) => {
|
||||
if (nodes.length !== 0) {
|
||||
nodes.forEach((data, i) => {
|
||||
if (!client.config.nodes_resource.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_resource.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'
|
||||
}
|
||||
|
||||
if (client.config.nodes_resource.servers) description = description + '\nServers : ' + data.total_servers.toLocaleString()
|
||||
if (client.config.nodes_resource.location) description = description + '\nLocation : ' + data.location
|
||||
if (client.config.nodes_resource.allocations) description = description + '\nAllocations : ' + data.allocations.toLocaleString()
|
||||
|
||||
description = description + '\n```'
|
||||
|
||||
if (client.config.nodes_resource.enable) {
|
||||
text = text + '\n**' + title.replace(':', ':**') + '\n' + description
|
||||
} else {
|
||||
text = text + '\n**' + title.replace(':', ':**')
|
||||
}
|
||||
} else {
|
||||
blacklist = blacklist + 1
|
||||
if (nodes.length - client.config.nodes_resource.blacklist.length < 1) text = '\nThere is no nodes to display'
|
||||
}
|
||||
|
||||
if (i + 1 === nodes.length) resolve()
|
||||
})
|
||||
} else if (nodes.length < 1) {
|
||||
if (!messages) {
|
||||
text = '\nThere is no nodes to display'
|
||||
resolve()
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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.panel_resource.enable) {
|
||||
let stats = '**Status:** ' + String(panel.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) + '\n\n'
|
||||
|
||||
if (client.config.panel_resource.users) stats = stats + 'Users: ' + String(panel.total_users).replace('-1', '`Unknown`') + '\n'
|
||||
if (client.config.panel_resource.servers) stats = stats + 'Servers: ' + String(panel.total_servers).replace('-1', '`Unknown`')
|
||||
|
||||
EmbedFields.push({ name: 'Panel Stats', value: stats })
|
||||
}
|
||||
|
||||
if (client.config.embed.field.enable) 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)
|
||||
|
||||
const row = []
|
||||
|
||||
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 (!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!'))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module.exports = function promiseTimeout(promise, ms) {
|
||||
const timeout = new Promise((resolve) => {
|
||||
const id = setTimeout(() => {
|
||||
clearTimeout(id);
|
||||
resolve(false);
|
||||
}, ms);
|
||||
});
|
||||
return Promise.race([promise, timeout]);
|
||||
}
|
||||
@@ -1,52 +1,185 @@
|
||||
const fs = require('fs');
|
||||
const child = require('child_process');
|
||||
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 cachePath = require('node:path').join(__dirname, "./cache.json");
|
||||
const fs = require("node:fs");
|
||||
const cliColor = require("cli-color");
|
||||
|
||||
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')) {
|
||||
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 {
|
||||
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')
|
||||
try {
|
||||
child.execSync('npm i')
|
||||
console.log('Install complete!, please run "node index" command again!')
|
||||
process.exit()
|
||||
} catch (err) {
|
||||
console.log('Err! ', err)
|
||||
console.log('Support Server: https://discord.gg/zv6maQRah3')
|
||||
process.exit()
|
||||
}
|
||||
}
|
||||
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."));
|
||||
|
||||
const chalk = require('chalk');
|
||||
const yaml = require('js-yaml');
|
||||
const { Client, GatewayIntentBits } = require('discord.js');
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds]
|
||||
});
|
||||
|
||||
const config = yaml.load(fs.readFileSync('./config.yml', 'utf8'));
|
||||
client.config = config
|
||||
async function startGetStatus() {
|
||||
try {
|
||||
const results = await getStats();
|
||||
createMessage({
|
||||
panel: true,
|
||||
nodes: results.nodes,
|
||||
servers: results.servers,
|
||||
users: results.users,
|
||||
});
|
||||
} catch {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Panel is offline"));
|
||||
|
||||
const eventFiles = fs.readdirSync('./events').filter(file => file.endsWith('.js'));
|
||||
fs.readFile(cachePath, (err, data) => {
|
||||
if (err) {
|
||||
createMessage({ cache: false, panel: false });
|
||||
return console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Last cache was not found!"));
|
||||
}
|
||||
|
||||
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));
|
||||
try {
|
||||
const results = JSON.parse(data);
|
||||
createMessage({
|
||||
cache: true,
|
||||
panel: false,
|
||||
nodes: results.nodes,
|
||||
servers: results.servers,
|
||||
users: results.users,
|
||||
});
|
||||
} catch {
|
||||
createMessage({ cache: false, panel: false });
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Something went wrong with cache data..."));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (client.config.token.startsWith('Put') || client.config.token.length < 1) {
|
||||
console.log(chalk.cyan('[PteroStats]') + chalk.red(' Err! Invalid Discord Bot Token'))
|
||||
process.exit()
|
||||
client.once("ready", () => {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.greenBright(`${client.user.tag} is online!`));
|
||||
|
||||
if (config.presence.text && config.presence.type) {
|
||||
switch (config.presence.type.toLowerCase()) {
|
||||
case "playing":
|
||||
config.presence.type = ActivityType.Playing;
|
||||
break;
|
||||
case "listening":
|
||||
config.presence.type = ActivityType.Listening;
|
||||
break;
|
||||
case "competing":
|
||||
config.presence.type = ActivityType.Competing;
|
||||
break;
|
||||
default:
|
||||
config.presence.type = ActivityType.Watching;
|
||||
}
|
||||
|
||||
client.user.setActivity(config.presence.text, {
|
||||
type: config.presence.type,
|
||||
});
|
||||
}
|
||||
|
||||
if (config.presence.status) {
|
||||
if (!["idle", "online", "dnd", "invisible"].includes(
|
||||
config.presence.status.toLowerCase()
|
||||
))
|
||||
config.presence.status = "online";
|
||||
|
||||
client.user.setStatus(config.presence.status);
|
||||
}
|
||||
|
||||
startGetStatus();
|
||||
});
|
||||
|
||||
async function createMessage({ cache, panel, nodes, servers, users }) {
|
||||
let embed = new EmbedBuilder()
|
||||
.setTitle(config.embed.title_nodes)
|
||||
.setColor(config.embed.color);
|
||||
|
||||
let embeds = [embed];
|
||||
|
||||
if (config.nodes_settings.details) {
|
||||
nodes.forEach((node, index) => {
|
||||
if (index % 25 === 0 && index !== 0) {
|
||||
embed = new EmbedBuilder().setColor(config.embed.color);
|
||||
if (embeds.length < 9) embeds.push(embed);
|
||||
}
|
||||
|
||||
embed.addFields({
|
||||
name: `${node.attributes.name} - ${node.status ? config.status.online : config.status.offline}`,
|
||||
value:
|
||||
"```\n" +
|
||||
`Memory: ${convertUnits(node.attributes.allocated_resources.memory, node.attributes.memory, config.nodes_settings.unit)}\n` +
|
||||
`Disk : ${convertUnits(node.attributes.allocated_resources.disk, node.attributes.disk, config.nodes_settings.unit)}\n` +
|
||||
`CPU : ${Math.floor(node.attributes.cpu / node.attributes.allocated_resources.cpu * 100) || 0}%` +
|
||||
"```"
|
||||
});
|
||||
});
|
||||
} else {
|
||||
embeds[0].setDescription(nodes.map(node => `**${node.attributes.name}** - ${node.status ? config.status.online : config.status.offline}`).join("\n"));
|
||||
}
|
||||
|
||||
let panelEmbed = new EmbedBuilder()
|
||||
.setColor(config.embed.color)
|
||||
.setTitle(config.embed.title_panel)
|
||||
.addFields({
|
||||
name: `Panel - ${panel ? config.status.online : config.status.offline}`,
|
||||
value:
|
||||
"```\n" +
|
||||
`Nodes : ${nodes.length}\n` +
|
||||
(config.panel_settings.servers ? `Servers: ${servers || "Unknown"}\n` : "") +
|
||||
(config.panel_settings.users ? `Users : ${users || "Unknown"}\n` : "") +
|
||||
"```"
|
||||
});
|
||||
|
||||
if (config.panel_settings.status) embeds.unshift(panelEmbed);
|
||||
|
||||
embeds[0]
|
||||
.setAuthor(config.embed.title ? { name: config.embed.author } : null)
|
||||
.setDescription(
|
||||
embeds[0].data.description ? config.embed.description.replace("{{time}}", time(new Date(Date.now() + (config.refresh * 1000) + 1000), "R")) + "\n\n" + embeds[0].data.description : config.embed.description.replace("{{time}}", time(new Date(Date.now() + (config.refresh * 1000) + 1000), "R"))
|
||||
);
|
||||
|
||||
embeds[embeds.length - 1]
|
||||
.setFooter(config.embed.footer ? { text: config.embed.footer } : null);
|
||||
|
||||
if (config.embed.timestamp) embeds[embeds.length - 1].setTimestamp();
|
||||
|
||||
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!"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const channel = await client.channels.fetch(config.channel);
|
||||
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!"));
|
||||
|
||||
setTimeout(() => startGetStatus(), config.refresh * 1000);
|
||||
|
||||
if (botMessage) {
|
||||
await botMessage.edit({ content: config.message.content || null, embeds });
|
||||
} else {
|
||||
await channel.send({ content: config.message.content || null, embeds });
|
||||
}
|
||||
} catch (error) {
|
||||
DiscordErrorHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
client.login(config.token);
|
||||
function DiscordErrorHandler(error) {
|
||||
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) {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error | Your discord bot doesn't have access to see/send message/edit message in the channel!"));
|
||||
} else if (error.rawError?.errors && Object?.values(error.rawError.errors)[0]._errors[0].code === "MAX_EMBED_SIZE_EXCEEDED") {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error | Embed message limit exceeded! Please limit or decrease the nodes that need to be shown in the config!"));
|
||||
} else if (error.rawError?.errors && Object?.values(error.rawError.errors)[0]._errors[0].code) {
|
||||
console.log(Object.values(error.rawError.errors)[0]._errors[0].message);
|
||||
} else {
|
||||
console.error(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Discord Error"), error);
|
||||
}
|
||||
process.exit();
|
||||
}
|
||||
|
||||
try {
|
||||
client.login(config.token);
|
||||
} 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();
|
||||
}
|
||||
|
||||
+22
-22
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "pterostats",
|
||||
"version": "v3.0.0",
|
||||
"description": "PteroStats is a bot designed to check Pterodactyl Panel and Nodes status and post it to your discord server",
|
||||
"license": "MIT",
|
||||
"repository": "HirziDevs/PteroStats",
|
||||
"homepage": "https://pterostats.hirzidevs.net",
|
||||
"bugs": {
|
||||
"email": "hirzidevs@gmail.com",
|
||||
"url": "https://github.com/HirziDevs/PteroStats"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.1.3",
|
||||
"chalk": "4.1.1",
|
||||
"discord.js": "^14.6.0",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.x"
|
||||
}
|
||||
"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",
|
||||
"license": "MIT",
|
||||
"repository": "HirziDevs/PteroStats",
|
||||
"homepage": "https://pterostats.hirzidevs.xyz",
|
||||
"bugs": {
|
||||
"email": "hirzidevs@gmail.com",
|
||||
"url": "https://github.com/HirziDevs/PteroStats/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"cli-color": "^2.0.4",
|
||||
"discord.js": "^14.15.3",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user