mirror of
https://github.com/HirziDevs/PteroStats
synced 2026-01-05 04:56:05 +00:00
Merge branch 'main' into main
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
node_modules/
|
||||
config-dev.yml
|
||||
package-lock.json
|
||||
.vscode
|
||||
node_modules
|
||||
cache.json
|
||||
.env
|
||||
|
||||
129
Indo.md
129
Indo.md
@@ -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/main/Indo.md) | [[Inggris]](https://github.com/HirziDevs/PteroStats/blob/main/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"/>
|
||||
|
||||
## Nodenya online tapi di embed dibilang offline
|
||||
|
||||
Jika kamu mengalami isu ini, atur `log_error` menjadi true di file config dan beri tahu kami di [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 Discord Server](https://discord.gg/pterodactyl)
|
||||
- [PteroBot Support Server](https://discord.gg/zv6maQRah3)
|
||||
- [PteroBot Support Server (Indonesia)](https://discord.gg/EYaFB7WSg6)
|
||||
191
README.md
191
README.md
@@ -1,97 +1,102 @@
|
||||
<div align="center">
|
||||
|
||||
<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"/>
|
||||
# PteroStats
|
||||
|
||||
<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"/>
|
||||
|
||||
</div>
|
||||
|
||||
## Introduction
|
||||
PteroStats is a bot designed to check Pterodactyl Panel and Nodes status 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.
|
||||
|
||||
## Example
|
||||
- Test Panel
|
||||
## Preview
|
||||
<img alt="PteroStats Image Preview" src="https://usercontent.catto.pictures/hirzi/e6f6fe6a-8c0e-4c7a-8b73-d4af752324f4.png" width="300"/>
|
||||
|
||||
<img alt="Example" src="https://i.imgur.com/fzQANo5.gif" width="200"/>
|
||||
<img alt="PteroStats Console Preview" src="https://usercontent.catto.pictures/hirzi/8ce3aac6-5c46-4626-bd14-af994b602f8e.png" width="300"/>
|
||||
|
||||
## Installation
|
||||
- [Introduction](#introduction)
|
||||
- [Example](#example)
|
||||
- [Installation](#installation)
|
||||
- [Getting API key from pterodactyl](#getting-api-key-from-pterodactyl)
|
||||
- [Creating Discord Bot](#creating-discord-bot)
|
||||
- [Inviting Discord Bot](#inviting-discord-bot)
|
||||
- [Getting Channel ID](#getting-channel-id)
|
||||
- [Starting bot](#starting-bot)
|
||||
- [Using Custom Emoji](#using-custom-emoji)
|
||||
- [Blacklist Nodes](#blacklist-nodes)
|
||||
- [The node is online but the embed is read as offline](#the-node-is-online-but-the-embed-is-read-as-offline)
|
||||
- [Docker](#docker)
|
||||
<img alt="PteroStats GIF Preview" src="https://usercontent.catto.pictures/hirzi/ad6e36cc-b582-460b-ab4e-b5e1dacd8b02.gif" width="300"/>
|
||||
|
||||
## Guide
|
||||
- [Starting the App/Bot](#starting-the-appbot)
|
||||
- [Changing Env Configuration](#changing-env-configuration)
|
||||
- [Getting an Panel API key](#getting-panel-api-key)
|
||||
- [Getting a Channel ID](#getting-channel-id)
|
||||
- [Using Custom Emoji](#using-custom-emoji)
|
||||
- [Blacklist Nodes](#blacklist-nodes)
|
||||
- [Notifier](#notifier)
|
||||
- - [Docker](#docker)
|
||||
- [Installation](#installation-1)
|
||||
- [Docker Compose](#docker-compose)
|
||||
- [Docker Run](#docker-run)
|
||||
- [Links](#links)
|
||||
|
||||
### Getting API key from pterodactyl
|
||||
### Starting the App/Bot
|
||||
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:
|
||||
- [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 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"/>
|
||||
|
||||
- [Getting Panel API Key](#getting-panel-api-key)
|
||||
- [Getting a Channel ID](#getting-channel-id)
|
||||
|
||||
6. Run `node index` if you want to start the app/bot again, and you're done!
|
||||
|
||||
<img alt="Console Logging" src="https://usercontent.catto.pictures/hirzi/8ce3aac6-5c46-4626-bd14-af994b602f8e.png" width="300"/>
|
||||
|
||||
### Changing Env Configuration
|
||||
> [!TIP]
|
||||
> You can change other configuration at the `config.yml` file.
|
||||
|
||||
1. Run `node setup` in the root directory of the app/bot files.
|
||||
2. Enter `2` to change configuration.
|
||||
|
||||
<img alt="Change Configuration" src="https://usercontent.catto.pictures/hirzi/f61ebf43-3df8-4b86-93ac-166e2de1edcd.png" width="300"/>
|
||||
|
||||
3. Answer the provided question to set up the app/bot.
|
||||
4. Run `node index` if you want to start the app/bot again, and you're done!
|
||||
|
||||
### Getting Panel API Key
|
||||
> [!WARNING]
|
||||
> The use of Application API keys are **deprecated**, you should use **Client API keys** in the config file
|
||||
> The use of Application API keys is **deprecated**; you should use **Client API keys**.
|
||||
|
||||
1. Go to your `Pterodactyl Panel` and go to `Account Page`.
|
||||
> [!TIP]
|
||||
> Make sure the owner of the Client API key has access to the administrator panel.
|
||||
|
||||
<img alt="Home" src="https://i.imgur.com/Uu97RJO.png" width="400"/>
|
||||
1. Go to your `Pterodactyl` or `Pelican` Panel and navigate to the `Account Page`.
|
||||
|
||||
2. Click on the `API Credentials` button
|
||||
<img alt="Home" src="https://usercontent.catto.pictures/hirzi/6d3e4c63-c5e8-4d94-9d78-07bb937b1dbd.png" width="400"/>
|
||||
|
||||
<img alt="Account Page" src="https://i.imgur.com/sm4THSu.png" width="400"/>
|
||||
2. Click on the `API Credentials` button.
|
||||
|
||||
3. Fill the `Description` and click on the `Create` button
|
||||
<img alt="Account Page" src="https://usercontent.catto.pictures/hirzi/0a2327ee-243a-4dd1-86f4-549f1ab8a91c.png" width="400"/>
|
||||
|
||||
<img alt="Create Client API Key" src="https://i.imgur.com/Q5E0PY4.png" width="400"/>
|
||||
3. Fill in the `Description` and click the `Create` button.
|
||||
|
||||
<img alt="Create Client API Key" src="https://usercontent.catto.pictures/hirzi/7fcf5b7e-0087-4cf2-9e57-fed01292fd10.png" width="400"/>
|
||||
|
||||
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"/>
|
||||
|
||||
### Creating Discord Bot
|
||||
Please refer to [this website](https://discordjs.guide/preparations/setting-up-a-bot-application.html)
|
||||
|
||||
Paste the bot token at the config
|
||||
|
||||
<img alt="Bot Config" src="https://usercontent.catto.pictures/hirzi/4eb4d5ff-6969-4461-b01d-c45888cfc994.png" width="400"/>
|
||||
|
||||
### Inviting Discord Bot
|
||||
Please refer to [this website](https://discordjs.guide/preparations/adding-your-bot-to-servers.html)
|
||||
<img alt="API Key" src="https://usercontent.catto.pictures/hirzi/267cf48a-7229-4bb6-8c77-7d0c0884f6ba.png" width="400"/>
|
||||
|
||||
### Getting Channel ID
|
||||
1. Enable Developer Feature at your discord settings
|
||||
1. Enable Developer Mode in your Discord settings.
|
||||
|
||||
<img alt="Discord User Settings" src="https://usercontent.catto.pictures/hirzi/36894499-b141-488f-98ed-40245c8f6862.png" width="400"/>
|
||||
|
||||
2. Right Click text channel and select `Copy ID`
|
||||
2. Right-click the text channel and select `Copy ID`.
|
||||
|
||||
<img alt="Right Click Channel" src="https://usercontent.catto.pictures/hirzi/9f8352da-df5b-4587-9594-ced9b11a5507.png" width="250"/>
|
||||
|
||||
3. Paste the channel id at the config
|
||||
|
||||
<img alt="Channel Config" src="https://usercontent.catto.pictures/hirzi/b34cdbee-1e24-49f2-8219-efe0344a24f9.png" width="400"/>
|
||||
|
||||
### Starting bot
|
||||
1. Make sure you have done the things above
|
||||
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 `@hirzidevs` or join [our discord support server](https://discord.gg/zv6maQRah3)
|
||||
|
||||
### Using Custom Emoji
|
||||
1. type `\` in server that has custom emoji you want
|
||||
1. Type `\` in the server that has the custom emoji you want.
|
||||
|
||||
<img alt="Type \ on the chat" src="https://usercontent.catto.pictures/hirzi/2e3c821f-92f9-4b5c-863a-e020b2fbc426.png" width="350"/>
|
||||
|
||||
2. Select custom emoji you want
|
||||
2. Select the custom emoji you want.
|
||||
|
||||
<img alt="Select Custom Emoji" src="https://usercontent.catto.pictures/hirzi/7c071727-2adb-4c8c-91d3-21664948a334.png" width="300"/>
|
||||
|
||||
@@ -99,30 +104,67 @@ if you need help contact me on discord `@hirzidevs` or join [our discord support
|
||||
|
||||
<img alt="Copy Emoji ID" src="https://usercontent.catto.pictures/hirzi/bd0084ac-f11b-413d-8b66-580efc011908.png" width="400"/>
|
||||
|
||||
4. Paste the emoji id at the config
|
||||
4. Paste the emoji ID into the config.
|
||||
|
||||
<img alt="Status Config" src="https://usercontent.catto.pictures/hirzi/458ad1d6-019b-4b27-be60-3cbabfa07c06.png" width="400"/>
|
||||
<img alt="Status Config" src="https://usercontent.catto.pictures/hirzi/369cf7af-ae32-4193-9b09-195ba6f71f62.png" width="400"/>
|
||||
|
||||
### Blacklist Nodes
|
||||
1. Select node from node list on admin page
|
||||
1. Select a node from the node list on the admin page.
|
||||
|
||||
<img alt="Nodes List" src="https://usercontent.catto.pictures/hirzi/5699fdbd-7c3c-4fa5-ae2c-d0ccb39cb69e.png" width="400"/>
|
||||
<img alt="Pterodactyl Nodes List" src="https://usercontent.catto.pictures/hirzi/5699fdbd-7c3c-4fa5-ae2c-d0ccb39cb69e.png" width="400"/>
|
||||
|
||||
2. Check the url and copy the node id
|
||||
<img alt="Pelican Nodes List" src="https://usercontent.catto.pictures/hirzi/5994fbf0-03ac-4196-9bb5-e945401f204e.png" width="400"/>
|
||||
|
||||
<img alt="Node Id" src="https://usercontent.catto.pictures/hirzi/45f855fc-6d96-4b23-a96e-892071189d01.png" width="400"/>
|
||||
2. Check the URL and copy the node ID.
|
||||
|
||||
3. Paste the id to the blacklist on config
|
||||
<img alt="Pterodactyl Node ID" src="https://usercontent.catto.pictures/hirzi/45f855fc-6d96-4b23-a96e-892071189d01.png" width="400"/>
|
||||
|
||||
<img alt="Blacklist Config" src="https://usercontent.catto.pictures/hirzi/cfb479bf-64da-43e5-b0d1-f7c0c78bf068.png" width="400"/>
|
||||
<img alt="Pelican Node ID" src="https://usercontent.catto.pictures/hirzi/0ff8d9fc-6125-4fbb-8e19-ff8743cd365c.png" width="400"/>
|
||||
|
||||
You can add more than one node in the blacklist
|
||||
3. Paste the ID into the blacklist in the config.
|
||||
|
||||
<img alt="Blacklist Config" src="https://usercontent.catto.pictures/hirzi/85b6a9b1-8ec9-4395-b5b1-6f85d3f52162.png" width="400"/>
|
||||
<img alt="Blacklist Config" src="https://usercontent.catto.pictures/hirzi/bfae6a04-8dad-4db1-b3d8-05e6db691516.png" width="400"/>
|
||||
|
||||
## The node is online but the embed is read as offline
|
||||
> [!TIP]
|
||||
> You can add more than one node to the blacklist.
|
||||
|
||||
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)
|
||||
<img alt="Blacklist Config" src="https://usercontent.catto.pictures/hirzi/7b5d6c7f-54d9-40ea-b5a6-9192325ba2a0.png" width="400"/>
|
||||
|
||||
## Links
|
||||
=======
|
||||
### Notifier
|
||||
Get a notification on Discord when your panel or specific nodes are currently down.
|
||||
|
||||
<img alt="Notifier Preview" src="https://usercontent.catto.pictures/hirzi/a2b8e36f-7448-4849-a14a-b1eb4ec8fb26.png" width="250"/>
|
||||
|
||||
|
||||
#### Enabling Notifier
|
||||
Open `config.yml` and set `enable` at the notifier configuration to `true`
|
||||
|
||||
<img alt="Notifier Config" src="https://usercontent.catto.pictures/hirzi/b4c3f1d0-e053-402c-8401-4de44926fce6.png" width="300"/>
|
||||
|
||||
#### Getting Discord Webhook URL
|
||||
1. Go to the channel settings of the channel you want to set for the notifier.
|
||||
|
||||
<img alt="Notifier Config" src="https://usercontent.catto.pictures/hirzi/7d7712b9-d9ac-4650-83ac-21dc3f20c3fe.png" width="300"/>
|
||||
|
||||
2. Go to integrations and select `View Webhooks` or `Create Webhook`.
|
||||
|
||||
<img alt="Notifier Config" src="https://usercontent.catto.pictures/hirzi/e251f1e9-6b46-4051-be64-1945a6eaee33.png" width="300"/>
|
||||
|
||||
3. Create a new webhook and copy the Webhook URL
|
||||
|
||||
<img alt="Notifier Config" src="https://usercontent.catto.pictures/hirzi/e0af8410-527a-42e2-b284-48d7eb81456f.png" width="300"/>
|
||||
|
||||
4. Paste the Webhook URL on the webhook notifier configuration.
|
||||
|
||||
<img alt="Notifier Config" src="https://usercontent.catto.pictures/hirzi/b4ec26ad-e426-434e-b8c8-27ddc2916f5f.png" width="300"/>
|
||||
|
||||
|
||||
> [!TIP]
|
||||
> You can change the webhook icon and username on the webhook settings.
|
||||
|
||||
<img alt="Notifier Config" src="https://usercontent.catto.pictures/hirzi/2a4f7aba-9377-4722-bf19-3b7f0cc32772.png" width="300"/>
|
||||
|
||||
## Docker
|
||||
|
||||
@@ -151,11 +193,10 @@ docker run -d --name pterostats -v $(pwd)/config.yml:/app/config.yml ghcr.io/hir
|
||||
docker logs -f pterostats
|
||||
```
|
||||
|
||||
## Links
|
||||
## Reporting a Bug
|
||||
Enable `log_error` in the `config.yml` file and check the console for the error message. After that, report it to our Discord server at [Support Server](https://discord.znproject.my.id).
|
||||
|
||||
- [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://github.com/devnote-dev/ptero-notes/)
|
||||
## Links
|
||||
- [Pterodactyl Discord Server](https://discord.gg/pterodactyl)
|
||||
- [PteroBot Support Server](https://discord.gg/zv6maQRah3)
|
||||
- [Pelican Discord Server](https://discord.gg/pelican-panel)
|
||||
- [Support Server](https://discord.znproject.my.id)
|
||||
153
config.yml
153
config.yml
@@ -1,87 +1,108 @@
|
||||
# PteroStats config
|
||||
# If you need help, join our discord server here: https://discord.gg/zv6maQRah3
|
||||
# PteroStats Configuration File
|
||||
# Need help? Join our Discord server: https://discord.znproject.my.id
|
||||
version: 9 # Warning: Do not change this unless you know what you are doing!
|
||||
|
||||
# Bot Configuration
|
||||
token: "Put bot token here"
|
||||
# App Presence Configuration
|
||||
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'
|
||||
enable: true # Enable or disable app presence.
|
||||
text: "Hosting Panel" # The status text shown by the app.
|
||||
type: "watching" # Available types: 'watching', 'playing', 'listening', 'competing'. (Note: 'streaming' is currently unsupported)
|
||||
status: "online" # Available types: 'online', 'idle', 'dnd', or 'invisible'
|
||||
|
||||
# Discord Channel and Refresh Time Configuration
|
||||
channel: "Put channel id here"
|
||||
refresh: 60 # How much time the bot will refresh the stats
|
||||
timeout: 1 # 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"
|
||||
# Discord Channel and Refresh Timing Configuration
|
||||
refresh: 10 # Interval in seconds for refreshing the panel's stats.
|
||||
timeout: 5 # Timeout in seconds for node responses. Adjusting this will affect the overall refresh time.
|
||||
|
||||
# Message and Embed Configuration
|
||||
# set the option as '' if you want to disable it
|
||||
# To disable an option, leave the value as an empty string: ''
|
||||
message:
|
||||
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
|
||||
content: "" # Custom content for app messages. Set to '' to disable.
|
||||
attachment: "" # Attachments will delay stats refresh since they are uploaded first.
|
||||
|
||||
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: ""
|
||||
image: ""
|
||||
field:
|
||||
title: ""
|
||||
description: "" # You can use {{time}} to make "in X seconds" time format
|
||||
panel:
|
||||
author:
|
||||
name: "Hosting Panel" # Author name for the panel embed.
|
||||
icon: "" # Icon URL for the author of the panel embed.
|
||||
title: "Panel Stats" # Title of the panel stats embed.
|
||||
description: "Next update {{time}}" # Description for the panel. {{time}} will display the next refresh time.
|
||||
timestamp: false # Show the timestamp in the embed (true/false).
|
||||
color: "5865F2" # Embed color in hex format.
|
||||
footer:
|
||||
text: "" # Footer text. Set to '' to disable.
|
||||
icon: "" # Footer icon URL.
|
||||
thumbnail: "" # Thumbnail URL for the embed.
|
||||
image: "" # Image URL for the embed.
|
||||
|
||||
nodes:
|
||||
author:
|
||||
name: "" # Author name for the nodes embed.
|
||||
icon: "" # Icon URL for the author of the nodes embed.
|
||||
title: "Nodes Stats" # Title for the nodes stats embed.
|
||||
description: "" # Description for the nodes stats embed. Can be left empty.
|
||||
timestamp: true # Include a timestamp in the nodes embed (true/false).
|
||||
color: "5865F2" # Embed color in hex format.
|
||||
footer:
|
||||
text: "By @HirziDevs" # Footer text for nodes stats.
|
||||
icon: "" # Footer icon URL.
|
||||
thumbnail: "" # Thumbnail URL for the nodes stats embed.
|
||||
image: "" # Image URL for the nodes stats embed.
|
||||
|
||||
# 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 # Enable or disable buttons in messages.
|
||||
row1:
|
||||
- label: "Panel" # Label for the first button.
|
||||
url: "https://panel.example.com" # URL for the first button.
|
||||
# - label: "Dashboard" # Remove "#" at the start of the line to use this button
|
||||
# url: "https://dash.example.com" # Remove "#" at the start of the line to use this button
|
||||
# row2: # Remove "#" at the start of the line to use this row
|
||||
# - label: "Backup Panel"
|
||||
# url: "https://panel2.example.com"
|
||||
# - label: "Backup Dashboard"
|
||||
# url: "https://dash2.example.com"
|
||||
|
||||
# Status Message Configuration
|
||||
# How to use custom emoji: https://github.com/HirziDevs/PteroStats#using-custom-emoji
|
||||
# For details on using custom emojis, visit: https://github.com/HirziDevs/PteroStats#using-custom-emoji
|
||||
status:
|
||||
online: ":green_circle: Online"
|
||||
offline: ":red_circle: Offline"
|
||||
online: ":green_circle: Online" # Status message for when a node is online.
|
||||
offline: ":red_circle: Offline" # Status message for when a node is offline.
|
||||
|
||||
# Nodes Settings
|
||||
# How to get nodes id: https://github.com/HirziDevs/PteroStats#blacklist-nodes
|
||||
# Node Settings
|
||||
# Instructions for retrieving node IDs: 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: false # enable nodes details i.e memory and disk usage
|
||||
servers: true
|
||||
location: true
|
||||
allocations: true
|
||||
unit: "gb" # Allowed values- "gb", "mb", "tb", or "percent"
|
||||
blacklist: [] # Add node IDs to exclude them from the status embed (e.g., blacklist: [1]).
|
||||
details: true # Show node details such as memory and disk usage (true/false).
|
||||
servers: false # Show server details (true/false).
|
||||
allocations_as_max_servers: false # Show allocations as max servers (true/false).
|
||||
host: false # Show node host (true/false).
|
||||
unit: "byte" # Unit for node usage, Available types: "byte" or "percentage".
|
||||
uptime: true # Enable or disable node uptime (true/false).
|
||||
limit: 100 # Node limit for usage statistics display.
|
||||
|
||||
# Panel Users and Servers Settings
|
||||
panel_settings:
|
||||
status: true # enable panel stats under nodes stats
|
||||
servers: true
|
||||
users: true
|
||||
status: true # Display panel stats above node stats (true/false).
|
||||
host: false # Show panel host (true/false).
|
||||
uptime: true # Enable or disable node uptime (true/false).
|
||||
servers: true # Display servers count (true/false).
|
||||
users: true # Display users count (true/false).
|
||||
|
||||
# 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
|
||||
# Notifier Configuration
|
||||
notifier:
|
||||
enable: false # Enable or disable notifier.
|
||||
webhook: "" # Discord Webhook URL for the notifier.
|
||||
embed:
|
||||
author:
|
||||
name: "" # Author name for the notifier embed.
|
||||
icon: "" # Icon URL for the author of the notifier embed.
|
||||
timestamp: true # Include a timestamp in the notifier embed (true/false).
|
||||
footer:
|
||||
text: "PteroStats Notifier" # Footer text for notifier stats.
|
||||
icon: "" # Footer icon URL.
|
||||
thumbnail: "" # Thumbnail URL for the notifier stats embed.
|
||||
image: "" # Image URL for the notifier stats embed.
|
||||
|
||||
# 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
|
||||
# Error Logging Configuration
|
||||
# Enable logging to console if servers go offline, useful for debugging.
|
||||
log_error: false # Set to true to enable error logging.
|
||||
@@ -1,92 +0,0 @@
|
||||
const { ActivityType } = require("discord.js");
|
||||
const chalk = require("chalk");
|
||||
const checkStatus = require("../handlers/checkStatus");
|
||||
|
||||
module.exports = {
|
||||
name: "ready",
|
||||
once: true,
|
||||
execute(client) {
|
||||
console.log(chalk.cyan("[PteroStats] ") + chalk.green("Bot is up!"));
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.green(
|
||||
"If you need support you can join our discord server https://discord.gg/zv6maQRah3"
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.yellow(
|
||||
"If some node is online but the embed is read as offline, please enable "
|
||||
) +
|
||||
chalk.green("log_error") +
|
||||
chalk.yellow(
|
||||
" on config file and report it at https://discord.gg/zv6maQRah3"
|
||||
)
|
||||
);
|
||||
|
||||
if (client.guilds.cache.size < 1) {
|
||||
return console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red("Error! This bot is not on any discord servers")
|
||||
);
|
||||
}
|
||||
|
||||
if (client.config.timeout < 1) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red("Timeout cannot be less than 1 seconds!")
|
||||
);
|
||||
client.config.timeout = 1;
|
||||
}
|
||||
|
||||
if (client.config.refresh >= 1 && client.config.refresh <= 10) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red("Refresh Time below 10 seconds is not recommended!")
|
||||
);
|
||||
} else if (client.config.refresh < 1) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red("Refresh Time cannot be less than 1 seconds!")
|
||||
);
|
||||
client.config.refresh = 10;
|
||||
}
|
||||
|
||||
if (client.config.presence.text && client.config.presence.type) {
|
||||
switch (client.config.presence.type.toLowerCase()) {
|
||||
case "playing":
|
||||
client.config.presence.type = ActivityType.Playing;
|
||||
break;
|
||||
case "listening":
|
||||
client.config.presence.type = ActivityType.Listening;
|
||||
break;
|
||||
case "competing":
|
||||
client.config.presence.type = ActivityType.Competing;
|
||||
break;
|
||||
default:
|
||||
client.config.presence.type = ActivityType.Watching;
|
||||
}
|
||||
|
||||
client.user.setActivity(client.config.presence.text, {
|
||||
type: client.config.presence.type,
|
||||
});
|
||||
}
|
||||
|
||||
if (client.config.presence.status) {
|
||||
if (
|
||||
!["idle", "online", "dnd", "invisible"].includes(
|
||||
client.config.presence.status.toLowerCase()
|
||||
)
|
||||
)
|
||||
client.config.presence.status = "online";
|
||||
|
||||
client.user.setStatus(client.config.presence.status);
|
||||
}
|
||||
|
||||
checkStatus({ client: client });
|
||||
|
||||
setInterval(async () => {
|
||||
await checkStatus({ client: client });
|
||||
}, client.config.refresh * 1000);
|
||||
},
|
||||
};
|
||||
265
handlers/application.js
Normal file
265
handlers/application.js
Normal file
@@ -0,0 +1,265 @@
|
||||
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 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..."));
|
||||
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds]
|
||||
});
|
||||
|
||||
async function startGetStatus() {
|
||||
try {
|
||||
const results = await getStats();
|
||||
if (results.isPanelDown) webhook(
|
||||
new EmbedBuilder()
|
||||
.setTitle("Panel Online")
|
||||
.setColor("57F287")
|
||||
.setDescription(`Panel is back online`)
|
||||
)
|
||||
createMessage({
|
||||
panel: true,
|
||||
uptime: results.uptime,
|
||||
nodes: results.nodes,
|
||||
servers: results.servers,
|
||||
users: results.users,
|
||||
});
|
||||
} catch (error) {
|
||||
if (config.log_error) console.error(error)
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Panel is currently offline."));
|
||||
|
||||
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!"));
|
||||
}
|
||||
|
||||
try {
|
||||
const results = JSON.parse(data);
|
||||
if (results.uptime) webhook(
|
||||
new EmbedBuilder()
|
||||
.setTitle("Panel Offline")
|
||||
.setColor("ED4245")
|
||||
.setDescription(`Panel is currently offline`)
|
||||
);
|
||||
results.uptime = false;
|
||||
fs.writeFileSync("cache.json", JSON.stringify(results, null, 2), "utf8");
|
||||
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..."));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
client.once("ready", () => {
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.green(`${cliColor.blueBright(client.user.tag)} is online!`));
|
||||
|
||||
if (config.presence.enable) {
|
||||
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, uptime, nodes, servers, users }) {
|
||||
let embed = new EmbedBuilder()
|
||||
.setAuthor({
|
||||
name: config.embed.nodes.author.name || null,
|
||||
iconURL: config.embed.nodes.author.icon || null
|
||||
})
|
||||
.setDescription(config.embed.nodes.description || null)
|
||||
.setTitle(config.embed.nodes.title || null)
|
||||
.setColor(config.embed.nodes.color || null)
|
||||
.setURL(config.embed.nodes.url || null)
|
||||
.setThumbnail(config.embed.nodes.thumbnail || null)
|
||||
|
||||
let embeds = [embed];
|
||||
|
||||
if (config.nodes_settings.details) {
|
||||
nodes.forEach((node, index) => {
|
||||
if (index % 25 === 0 && index !== 0) {
|
||||
embed = new EmbedBuilder().setColor(config.embed.nodes.color);
|
||||
if (embeds.length < 9) embeds.push(embed);
|
||||
}
|
||||
|
||||
embed.addFields({
|
||||
name: `${node.attributes.name} - ${node.status ? config.status.online : config.status.offline}`,
|
||||
value:
|
||||
"```\n" +
|
||||
(config.nodes_settings.host ? `Host : ${node.attributes.fqdn}\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)}` +
|
||||
(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 ? uptimeFormatter(Date.now() - node.uptime) : "N/A"}` : "") +
|
||||
"```"
|
||||
});
|
||||
});
|
||||
} else {
|
||||
embeds[0].setDescription((embed.data.description ? (embed.data.description + "\n\n") : "") + nodes.map(node => `**${node.attributes.name}** - ${node.status ? config.status.online : config.status.offline}`).join("\n"));
|
||||
}
|
||||
|
||||
let panelEmbed = new EmbedBuilder()
|
||||
.setAuthor({
|
||||
name: config.embed.panel.author.name || null,
|
||||
iconURL: config.embed.panel.author.icon || null
|
||||
})
|
||||
.setColor(config.embed.panel.color || null)
|
||||
.setTitle(config.embed.panel.title || null)
|
||||
.setURL(config.embed.panel.url || null)
|
||||
.setTimestamp(config.embed.panel.timestamp ? new Date() : null)
|
||||
.setFooter({
|
||||
text: config.embed.panel.footer.text || null,
|
||||
iconURL: config.embed.panel.footer.icon || null
|
||||
})
|
||||
.setThumbnail(config.embed.panel.thumbnail || null)
|
||||
.setImage(config.embed.panel.image || null)
|
||||
.setDescription(
|
||||
config.embed.panel.description
|
||||
.replace(
|
||||
"{{time}}",
|
||||
time(new Date(Date.now() + (config.refresh * 1000) + 1000), "R")
|
||||
) || null
|
||||
)
|
||||
.addFields({
|
||||
name: `Panel - ${panel ? config.status.online : config.status.offline}`,
|
||||
value:
|
||||
"```\n" +
|
||||
(config.panel_settings.host ? `Host : ${new URL(process.env.PanelURL).host}\n` : "") +
|
||||
`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 ? uptimeFormatter(Date.now() - uptime) : "N/A"}\n` : "") +
|
||||
"```"
|
||||
});
|
||||
|
||||
if (config.panel_settings.status) embeds.unshift(panelEmbed);
|
||||
|
||||
embeds[embeds.length - 1]
|
||||
.setTimestamp(config.embed.nodes.timestamp ? new Date() : null)
|
||||
.setFooter({
|
||||
text: config.embed.nodes.footer.text || null,
|
||||
iconURL: config.embed.nodes.footer.icon || null
|
||||
})
|
||||
.setImage(config.embed.nodes.image || null)
|
||||
|
||||
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 be display!" : "There are no nodes to be display!"
|
||||
);
|
||||
}
|
||||
|
||||
const components = []
|
||||
|
||||
if (config.button.enable) {
|
||||
for (const row of ["row1", "row2", "row3", "row4", "row5"]) {
|
||||
const buttons = config.button[row]?.slice(0, 5).filter(button => button.label && button.url);
|
||||
|
||||
if (buttons && buttons.length > 0) {
|
||||
components.push(
|
||||
new ActionRowBuilder().addComponents(
|
||||
buttons.map(button =>
|
||||
new ButtonBuilder()
|
||||
.setLabel(button.label)
|
||||
.setURL(button.url)
|
||||
.setStyle(ButtonStyle.Link)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.green(`Panel stats successfully posted to the ${cliColor.blueBright(channel.name)} channel!`));
|
||||
|
||||
setTimeout(() => startGetStatus(), config.refresh * 1000);
|
||||
|
||||
if (botMessage) {
|
||||
await botMessage.edit({ content: config.message.content || null, embeds, components });
|
||||
} else {
|
||||
await channel.send({ content: config.message.content || null, embeds, components });
|
||||
}
|
||||
} catch (error) {
|
||||
handleDiscordError(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: \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) {
|
||||
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();
|
||||
} catch (err) {
|
||||
console.error(error)
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
const { EmbedBuilder } = require("discord.js");
|
||||
const axios = require("axios");
|
||||
const axiosRetry = require("axios-retry");
|
||||
const chalk = require("chalk");
|
||||
|
||||
const postStatus = require("./postStatus");
|
||||
|
||||
axiosRetry(axios, { retries: 5 });
|
||||
|
||||
const EMBED_TITLE = "Node Loggin";
|
||||
const MENTION_DELETE_TIMEOUT = 1;
|
||||
|
||||
module.exports = async ({ client }) => {
|
||||
function Embed({ node }) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(EMBED_TITLE)
|
||||
.setDescription("`" + node.name + "` is down!")
|
||||
.setFooter({ text: "Please see console for more details" })
|
||||
.setTimestamp()
|
||||
.setColor("ED4245");
|
||||
}
|
||||
|
||||
if (client.config.channel.startsWith("Put")) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Channel ID")
|
||||
);
|
||||
|
||||
process.exit();
|
||||
} else if (client.config.panel.url.startsWith("Put")) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Panel URL")
|
||||
);
|
||||
|
||||
process.exit();
|
||||
} else if (client.config.panel.key.startsWith("Put")) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Apikey")
|
||||
);
|
||||
|
||||
process.exit();
|
||||
} else if (!client.config.panel.url.startsWith("http")) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Panel URL")
|
||||
);
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red(
|
||||
'1. Make sure the panel url is starts with "https://" or "http://"'
|
||||
)
|
||||
);
|
||||
|
||||
process.exit();
|
||||
}
|
||||
|
||||
if (client.config.panel.url.endsWith("/")) {
|
||||
client.config.panel.url = client.config.panel.url.slice(0, -1);
|
||||
}
|
||||
|
||||
const nodes = [];
|
||||
const embeds = [];
|
||||
|
||||
const panel = {
|
||||
status: false,
|
||||
total_servers: -1,
|
||||
total_users: -1,
|
||||
};
|
||||
|
||||
console.log(chalk.cyan("[PteroStats] ") + chalk.green("Getting nodes stats"));
|
||||
|
||||
try {
|
||||
const users = await axios(
|
||||
client.config.panel.url + "/api/application/users",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + client.config.panel.key,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (users?.status === 200 && users?.data) {
|
||||
const servers = await axios(
|
||||
client.config.panel.url + "/api/application/servers",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + client.config.panel.key,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (servers?.status === 200 && users?.data) {
|
||||
panel.total_users = users?.data?.meta?.pagination?.total || "ERROR";
|
||||
panel.total_servers = servers?.data.meta?.pagination?.total || "ERROR";
|
||||
panel.status = true;
|
||||
|
||||
const res = await axios(
|
||||
client.config.panel.url +
|
||||
"/api/application/nodes?include=servers,location,allocations",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + client.config.panel.key,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (res?.status === 200 && res?.data?.data) {
|
||||
for (const node of res.data.data) {
|
||||
const data = await axios(
|
||||
client.config.panel.url +
|
||||
"/api/application/nodes/" +
|
||||
node.attributes.id +
|
||||
"/configuration",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + client.config.panel.key,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (data?.status === 200 && data?.data) {
|
||||
const body = {
|
||||
id: node.attributes.id,
|
||||
name: node.attributes.name,
|
||||
location:
|
||||
node.attributes.relationships.location.attributes.short,
|
||||
allocations:
|
||||
node.attributes.relationships.allocations.data.length,
|
||||
maintenance: node.attributes.maintenance_mode,
|
||||
total_servers:
|
||||
node.attributes.relationships.servers.data.length,
|
||||
memory_min: node.attributes.allocated_resources.memory,
|
||||
memory_max: node.attributes.memory,
|
||||
disk_min: node.attributes.allocated_resources.disk,
|
||||
disk_max: node.attributes.disk,
|
||||
};
|
||||
|
||||
try {
|
||||
const stats = await axios(
|
||||
node.attributes.scheme +
|
||||
"://" +
|
||||
node.attributes.fqdn +
|
||||
":" +
|
||||
node.attributes.daemon_listen +
|
||||
"/api/servers",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + data.data.token,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (stats?.status === 200 && stats?.data) {
|
||||
body.status = true;
|
||||
} else {
|
||||
body.status = false;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!body?.status) {
|
||||
if (client.config.log_error)
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.yellow(
|
||||
"[Node: " + node.attributes.name + "] "
|
||||
) +
|
||||
chalk.red("Timeout!")
|
||||
);
|
||||
embeds.push(Embed({ node: body }));
|
||||
|
||||
body.status = false;
|
||||
}
|
||||
}, client.config.timeout * 1000);
|
||||
} catch (error) {
|
||||
if (client.config.log_error)
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.yellow("[Node: " + node.attributes.name + "] ") +
|
||||
chalk.red(error)
|
||||
);
|
||||
|
||||
embeds.push(Embed({ node: body }));
|
||||
|
||||
body.status = false;
|
||||
}
|
||||
|
||||
nodes.push(body);
|
||||
} else {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
response: {
|
||||
status: data.status,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
nodes.sort(function (a, b) {
|
||||
return a.id - b.id;
|
||||
});
|
||||
|
||||
await postStatus({ client: client, panel: panel, nodes: nodes });
|
||||
|
||||
if (
|
||||
(client.config.mentions.user.length > 0 ||
|
||||
client.config.mentions.role.length > 0) &&
|
||||
client.config.mentions.channel
|
||||
) {
|
||||
if (
|
||||
Array.isArray(client.config.mentions.user) ||
|
||||
Array.isArray(client.config.mentions.role)
|
||||
) {
|
||||
let mentions = "";
|
||||
|
||||
for (const user of client.config.mentions.user) {
|
||||
if (!isNaN(Number(user))) {
|
||||
mentions += " <@" + user + ">";
|
||||
}
|
||||
}
|
||||
|
||||
for (const role of client.config.mentions.role) {
|
||||
if (!isNaN(Number(role))) {
|
||||
mentions += " <@&" + role + ">";
|
||||
}
|
||||
}
|
||||
|
||||
const channel = await client.channels.cache.get(
|
||||
client.config.mentions.channel
|
||||
);
|
||||
|
||||
if (channel) {
|
||||
const messages = await channel.messages
|
||||
.fetch({ limit: 10 })
|
||||
.then((msg) =>
|
||||
msg
|
||||
.filter(
|
||||
(m) =>
|
||||
m.author.id === client.user.id &&
|
||||
m.embeds[0].data.title === EMBED_TITLE
|
||||
)
|
||||
.first()
|
||||
);
|
||||
|
||||
if (messages) {
|
||||
for (const MsgEmbed of messages.embeds) {
|
||||
for (const [index, embed] of embeds.entries()) {
|
||||
if (
|
||||
MsgEmbed.data.description === embed.data.description
|
||||
) {
|
||||
embeds.splice(index, 1);
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
if (
|
||||
MsgEmbed.data.description.startsWith(
|
||||
"`" + node.name
|
||||
) &&
|
||||
node.status
|
||||
) {
|
||||
await messages.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (embeds.length > 0) {
|
||||
await channel.send({ embeds: embeds });
|
||||
}
|
||||
|
||||
await channel.send({ content: mentions }).then(async (msg) => {
|
||||
setTimeout(async () => {
|
||||
if (msg) {
|
||||
await msg.delete();
|
||||
}
|
||||
}, MENTION_DELETE_TIMEOUT * 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
response: {
|
||||
status: res.status,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
response: {
|
||||
status: servers.status,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
JSON.stringify({
|
||||
response: {
|
||||
status: users.status,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
try {
|
||||
if (typeof error === "string") {
|
||||
error = JSON.parse(error);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (error?.response) {
|
||||
if (error.response?.status === 403) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid apikey")
|
||||
);
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red(
|
||||
"1. Make sure the apikey is from admin page not account page"
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red(
|
||||
"2. Make sure the apikey has read permission on all options"
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red("3. Make sure the apikey is exist")
|
||||
);
|
||||
} else if (error.response?.status === 404) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Panel URL")
|
||||
);
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red(
|
||||
'1. Make sure the panel url is like "https://panel.example.com"'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.cyan("[PteroStats] ") + chalk.red("Error! " + error));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.cyan("[PteroStats] ") + chalk.red("Error! " + error));
|
||||
}
|
||||
}
|
||||
};
|
||||
28
handlers/configuration.js
Normal file
28
handlers/configuration.js
Normal file
@@ -0,0 +1,28 @@
|
||||
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..."));
|
||||
config = yaml.load(fs.readFileSync("./config-dev.yml", "utf8"));
|
||||
}
|
||||
|
||||
try {
|
||||
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 !== 9) {
|
||||
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"));
|
||||
|
||||
module.exports = config;
|
||||
15
handlers/convertUnits.js
Normal file
15
handlers/convertUnits.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const prettyBytes = require('prettier-bytes');
|
||||
|
||||
module.exports = function convertUnits(value, max, unit) {
|
||||
unit = unit.toUpperCase();
|
||||
switch (unit) {
|
||||
case 'PERCENTAGE':
|
||||
case 'PERCENT':
|
||||
const percentage = Math.floor((value / max) * 100);
|
||||
return `${!percentage ? 0 : percentage}%`;
|
||||
case 'BYTE':
|
||||
return `${prettyBytes(value * 1000000)} / ${max === 0 ? "Unlimited" : prettyBytes(max * 1000000)}`;
|
||||
default:
|
||||
return `${value.toLocaleString()} ${unit}/${max === 0 ? "Unlimited" : `${max.toLocaleString()} ${unit}`}`;
|
||||
}
|
||||
}
|
||||
13
handlers/getNodeConfiguration.js
Normal file
13
handlers/getNodeConfiguration.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const config = require("./configuration.js");
|
||||
|
||||
module.exports = async function getNodeConfiguration(id) {
|
||||
return fetch(`${new URL(process.env?.PanelURL).origin}/api/application/nodes/${id}/configuration`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${process.env?.PanelKEY}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data)
|
||||
}
|
||||
44
handlers/getNodesDetails.js
Normal file
44
handlers/getNodesDetails.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const cliColor = require("cli-color");
|
||||
const config = require("./configuration.js");
|
||||
const axios = require("axios");
|
||||
|
||||
module.exports = async function getAllNodes() {
|
||||
return axios(`${new URL(process.env?.PanelURL).origin}/api/application/nodes?include=servers,location,allocations`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${process.env?.PanelKEY}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.data.data.filter((node) => !config.nodes_settings.blacklist.includes(node.attributes.id)))
|
||||
.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
|
||||
})
|
||||
}
|
||||
15
handlers/getServers.js
Normal file
15
handlers/getServers.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const config = require("./configuration.js");
|
||||
const cliColor = require("cli-color");
|
||||
|
||||
module.exports = async function getServers() {
|
||||
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 ${process.env?.PanelKEY}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.data.length)
|
||||
}
|
||||
92
handlers/getStats.js
Normal file
92
handlers/getStats.js
Normal file
@@ -0,0 +1,92 @@
|
||||
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 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(path.join(__dirname, "../cache.json")))
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})()
|
||||
|
||||
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(`Fetching ${cliColor.blueBright(node.attributes.name)} configuration...`))
|
||||
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 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()
|
||||
})() : Date.now()
|
||||
|
||||
if (!nodeUptime && nodeStatus) nodeUptime = Date.now()
|
||||
|
||||
if (!nodeStatus) {
|
||||
nodeUptime = false
|
||||
if (cache && cache.nodes.find((n) => n.attributes.id === node.attributes.id)?.status)
|
||||
webhook(
|
||||
new EmbedBuilder()
|
||||
.setTitle("Node Offline")
|
||||
.setColor("ED4245")
|
||||
.setDescription(`Node \`${node.attributes.name}\` is currently offline`)
|
||||
)
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright(`Node ${cliColor.blueBright(node.attributes.name)} is currently offline.`))
|
||||
} else {
|
||||
if (cache && !cache.nodes.find((n) => n.attributes.id === node.attributes.id)?.status)
|
||||
webhook(
|
||||
new EmbedBuilder()
|
||||
.setTitle("Node Online")
|
||||
.setColor("57F287")
|
||||
.setDescription(`Node \`${node.attributes.name}\` is back online`)
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
attributes: {
|
||||
id: node.attributes.id,
|
||||
name: node.attributes.name,
|
||||
memory: node.attributes.memory,
|
||||
disk: node.attributes.disk,
|
||||
cpu: node.attributes.cpu,
|
||||
fqdn: node.attributes.fqdn,
|
||||
allocated_resources: node.attributes.allocated_resources,
|
||||
relationships: {
|
||||
allocations: node.attributes.relationships.allocations.data.length,
|
||||
servers: node.attributes.relationships.servers.data.length
|
||||
}
|
||||
},
|
||||
uptime: nodeUptime,
|
||||
status: nodeStatus
|
||||
};
|
||||
});
|
||||
|
||||
const data = {
|
||||
uptime: cache ? (() => {
|
||||
return cache.uptime || Date.now()
|
||||
})() : Date.now(),
|
||||
servers: await getServers(),
|
||||
users: await getUsers(),
|
||||
nodes: await Promise.all(statusPromises),
|
||||
isPanelDown: !cache.uptime,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
fs.writeFileSync("cache.json", JSON.stringify(data, null, 2), "utf8");
|
||||
|
||||
return data
|
||||
}
|
||||
15
handlers/getUsers.js
Normal file
15
handlers/getUsers.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const config = require("./configuration.js");
|
||||
const cliColor = require("cli-color");
|
||||
|
||||
module.exports = async function getUsers() {
|
||||
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 ${process.env?.PanelKEY}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.data.length)
|
||||
}
|
||||
17
handlers/getWingsStatus.js
Normal file
17
handlers/getWingsStatus.js
Normal file
@@ -0,0 +1,17 @@
|
||||
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`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${nodeToken}`
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then(() => true)
|
||||
.catch((error) => {
|
||||
if (config.log_error) console.error(error);
|
||||
return false
|
||||
})
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
const {
|
||||
EmbedBuilder,
|
||||
time,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
AttachmentBuilder,
|
||||
} = require("discord.js");
|
||||
const chalk = require("chalk");
|
||||
|
||||
const memorySizeConverter = require("../modules/memorySizeConverter");
|
||||
const percentageCalculator = require("../modules/percentageCalculator");
|
||||
|
||||
const BUFFER_MS = 2000; //Added these extra milliseconds to prevent the stats timer from showing "1 or 2 seconds ago" before updating the stats.
|
||||
|
||||
module.exports = async ({ client, panel, nodes }) => {
|
||||
const channel = await client.channels.cache.get(client.config.channel);
|
||||
if (!channel) {
|
||||
return console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Channel ID")
|
||||
);
|
||||
}
|
||||
|
||||
const files = [];
|
||||
|
||||
const embed = new EmbedBuilder();
|
||||
|
||||
let messages = await channel.messages
|
||||
.fetch({ limit: 10 })
|
||||
.then((msg) => msg.filter((m) => m.author.id === client.user.id).last());
|
||||
|
||||
let text = "";
|
||||
let desc = "";
|
||||
let blacklist = 0;
|
||||
let content = null;
|
||||
|
||||
if (!client.config.nodes_settings.blacklist) {
|
||||
client.config.nodes_settings.blacklist = [];
|
||||
}
|
||||
|
||||
if (
|
||||
!Array.isArray(client.config.nodes_settings.blacklist) &&
|
||||
Number.isInteger(client.config.nodes_settings.blacklist)
|
||||
) {
|
||||
client.config.nodes_settings.blacklist = [
|
||||
client.config.nodes_settings.blacklist,
|
||||
];
|
||||
}
|
||||
|
||||
if (client.guilds.cache.size < 1) {
|
||||
return console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red("Error! This bot is not on any discord servers")
|
||||
);
|
||||
}
|
||||
|
||||
if (messages && messages.embeds.length < 1) {
|
||||
messages.delete();
|
||||
messages = null;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (nodes?.length >= 1) {
|
||||
for (const data of nodes) {
|
||||
if (!client.config.nodes_settings.blacklist.includes(data.id)) {
|
||||
const title =
|
||||
data?.name +
|
||||
": " +
|
||||
String(data?.status)
|
||||
.replace("true", client.config.status.online)
|
||||
.replace("false", client.config.status.offline);
|
||||
|
||||
let description = "```";
|
||||
|
||||
switch (client.config.nodes_settings.unit.toLowerCase()) {
|
||||
case "percent":
|
||||
description =
|
||||
description +
|
||||
"\nMemory : " +
|
||||
percentageCalculator(data.memory_min, data.memory_max) +
|
||||
"\nDisk : " +
|
||||
percentageCalculator(data.disk_min, data.disk_max);
|
||||
break;
|
||||
default:
|
||||
description =
|
||||
description +
|
||||
"\nMemory : " +
|
||||
memorySizeConverter(data.memory_min, client.config.nodes_settings.unit.toLowerCase()) +
|
||||
" / " +
|
||||
memorySizeConverter(data.memory_max, client.config.nodes_settings.unit.toLowerCase()) +
|
||||
"\nDisk : " +
|
||||
memorySizeConverter(data.disk_min, client.config.nodes_settings.unit.toLowerCase()) +
|
||||
" / " +
|
||||
memorySizeConverter(data.disk_max, client.config.nodes_settings.unit.toLowerCase());
|
||||
}
|
||||
|
||||
if (client.config.nodes_settings.servers) {
|
||||
description =
|
||||
description + "\nServers : " + data.total_servers.toLocaleString();
|
||||
}
|
||||
|
||||
if (client.config.nodes_settings.location) {
|
||||
description = description + "\nLocation : " + data.location;
|
||||
}
|
||||
|
||||
if (client.config.nodes_settings.allocations) {
|
||||
description =
|
||||
description +
|
||||
"\nAllocations : " +
|
||||
data.allocations.toLocaleString();
|
||||
}
|
||||
|
||||
description = description + "\n```";
|
||||
|
||||
if (client.config.nodes_settings.details) {
|
||||
text = text + "\n**" + title.replace(":", ":**") + "\n" + description;
|
||||
} else {
|
||||
text = text + "\n**" + title.replace(":", ":**");
|
||||
}
|
||||
} else {
|
||||
blacklist = blacklist + 1;
|
||||
if (nodes.length - client.config.nodes_settings.blacklist.length < 1) {
|
||||
text = "\nThere are no nodes to display";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const format = time(
|
||||
new Date(Date.now() + client.config.refresh * 1000 + BUFFER_MS),
|
||||
"R"
|
||||
);
|
||||
|
||||
embed.setDescription(
|
||||
desc.replaceAll("{{time}}", format) +
|
||||
"\n**Nodes Stats [" +
|
||||
Math.floor(nodes.length - blacklist) +
|
||||
"]**" +
|
||||
text
|
||||
);
|
||||
|
||||
const EmbedFields = [];
|
||||
|
||||
if (client.config.panel_settings.status) {
|
||||
let stats =
|
||||
"**Status:** " +
|
||||
String(panel.status)
|
||||
.replace("true", client.config.status.online)
|
||||
.replace("false", client.config.status.offline) +
|
||||
"\n\n";
|
||||
|
||||
if (client.config.panel_settings.users) {
|
||||
stats =
|
||||
stats +
|
||||
"Users: " +
|
||||
String(panel.total_users).replace("-1", "`Unknown`") +
|
||||
"\n";
|
||||
}
|
||||
|
||||
if (client.config.panel_settings.servers) {
|
||||
stats =
|
||||
stats +
|
||||
"Servers: " +
|
||||
String(panel.total_servers).replace("-1", "`Unknown`");
|
||||
}
|
||||
|
||||
EmbedFields.push({ name: "Panel Stats", value: stats });
|
||||
}
|
||||
|
||||
if (
|
||||
client.config.embed.field.title &&
|
||||
client.config.embed.field.description
|
||||
) {
|
||||
EmbedFields.push({
|
||||
name: client.config.embed.field.title,
|
||||
value: client.config.embed.field.description.replaceAll(
|
||||
"{{time}}",
|
||||
format
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (client.config.embed.timestamp) {
|
||||
embed.setTimestamp();
|
||||
}
|
||||
|
||||
if (EmbedFields.length > 0) {
|
||||
embed.setFields(EmbedFields); //try it and see
|
||||
}
|
||||
|
||||
const row = [];
|
||||
|
||||
if (client.config.button.enable) {
|
||||
const button = new ActionRowBuilder();
|
||||
|
||||
if (
|
||||
client.config.button.btn1.label.length >= 1 &&
|
||||
client.config.button.btn1.url.length >= 1
|
||||
) {
|
||||
button.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel(client.config.button.btn1.label)
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(client.config.button.btn1.url)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
client.config.button.btn2.label.length >= 1 &&
|
||||
client.config.button.btn2.url.length >= 1
|
||||
) {
|
||||
button.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel(client.config.button.btn2.label)
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(client.config.button.btn2.url)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
client.config.button.btn3.label.length >= 1 &&
|
||||
client.config.button.btn3.url.length >= 1
|
||||
) {
|
||||
button.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel(client.config.button.btn3.label)
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(client.config.button.btn3.url)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
client.config.button.btn4.label.length >= 1 &&
|
||||
client.config.button.btn4.url.length >= 1
|
||||
) {
|
||||
button.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel(client.config.button.btn4.label)
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(client.config.button.btn4.url)
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
client.config.button.btn5.label.length >= 1 &&
|
||||
client.config.button.btn5.url.length >= 1
|
||||
) {
|
||||
button.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setLabel(client.config.button.btn5.label)
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL(client.config.button.btn5.url)
|
||||
);
|
||||
}
|
||||
|
||||
row.push(button);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!messages) {
|
||||
channel.send({
|
||||
content: content,
|
||||
embeds: [embed],
|
||||
components: row,
|
||||
files: files,
|
||||
});
|
||||
} else {
|
||||
messages.edit({
|
||||
content: content,
|
||||
embeds: [embed],
|
||||
components: row,
|
||||
files: files,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
console.log(chalk.cyan("[PteroStats] ") + chalk.green("Stats posted!"));
|
||||
} else {
|
||||
if (!messages) {
|
||||
text = "\nThere are no nodes to display.";
|
||||
} else {
|
||||
if (
|
||||
messages?.embeds?.length > 0 &&
|
||||
client.config.embed.title &&
|
||||
messages?.embeds[0]?.data?.title === client.config.embed.title
|
||||
) {
|
||||
text = messages.embeds[0].description.replaceAll(
|
||||
client.config.status.online,
|
||||
client.config.status.offline
|
||||
);
|
||||
|
||||
if (
|
||||
!panel?.status &&
|
||||
String(
|
||||
String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2]
|
||||
).split("")[
|
||||
String(
|
||||
String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2]
|
||||
)?.length - 1
|
||||
] !== "`"
|
||||
) {
|
||||
panel.total_users =
|
||||
String(
|
||||
String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2]
|
||||
).split("")[
|
||||
String(
|
||||
String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[2]
|
||||
)?.length - 1
|
||||
] || 0;
|
||||
panel.total_servers =
|
||||
String(
|
||||
String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[3]
|
||||
)?.split("")[
|
||||
String(
|
||||
String(messages?.embeds[0]?.fields[0]?.value)?.split("\n")[3]
|
||||
)?.length - 1
|
||||
] || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
9
handlers/promiseTimeout.js
Normal file
9
handlers/promiseTimeout.js
Normal file
@@ -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]);
|
||||
}
|
||||
137
handlers/setup.js
Normal file
137
handlers/setup.js
Normal file
@@ -0,0 +1,137 @@
|
||||
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
|
||||
});
|
||||
|
||||
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 Question = {
|
||||
panelName: 0,
|
||||
panelUrl: 1,
|
||||
panelApiKey: 2,
|
||||
botToken: 3,
|
||||
channelId: 4,
|
||||
}
|
||||
|
||||
const answers = [];
|
||||
|
||||
const isValidURL = (url) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = function Setup() {
|
||||
console.log(cliColor.cyanBright("Welcome to PteroStats!"))
|
||||
console.log(cliColor.yellow("Please fill in the following credentials to set up the app.\n "));
|
||||
|
||||
const askQuestion = (index) => {
|
||||
if (index < questions.length) {
|
||||
console.log(questions[index]);
|
||||
|
||||
readline.question('> ', answer => {
|
||||
let isValid = true;
|
||||
|
||||
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 === 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 === Question.channelId && !/^\d+$/.test(answer)) {
|
||||
console.log(cliColor.redBright("❌ Invalid Channel ID. It must be a number."));
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
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);
|
||||
askQuestion(index + 1);
|
||||
} else {
|
||||
askQuestion(index);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
axios(`${new URL(answers[Question.panelUrl]).origin}/api/application/nodes?include=servers,location,allocations`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${answers[Question.panelApiKey]}`
|
||||
},
|
||||
}).then(() => {
|
||||
console.log(" \n" + cliColor.green("✓ Valid Panel Credentials."));
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds]
|
||||
})
|
||||
|
||||
client.login(answers[Question.botToken]).then(async () => {
|
||||
console.log(cliColor.green("✓ Valid Discord Bot"));
|
||||
client.channels.fetch(answers[Question.channelId]).then(() => {
|
||||
console.log(cliColor.green("✓ Valid Discord Channel"));
|
||||
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 `));
|
||||
|
||||
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."));
|
||||
process.exit()
|
||||
})
|
||||
}).catch(() => {
|
||||
console.log(cliColor.redBright("❌ Invalid Discord Bot Token."));
|
||||
console.log(" \n" + cliColor.redBright("Please run the setup again and fill in the correct credentials."));
|
||||
process.exit()
|
||||
})
|
||||
}).catch((error) => {
|
||||
console.log(" \n" + cliColor.redBright("❌ Invalid Panel Credentials."));
|
||||
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}`));
|
||||
}
|
||||
console.log(" \n" + cliColor.redBright("Please run the setup again and fill in the correct credentials."));
|
||||
process.exit()
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
askQuestion(0);
|
||||
}
|
||||
13
handlers/uptimeFormatter.js
Normal file
13
handlers/uptimeFormatter.js
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = function uptimeFormatter(time) {
|
||||
let text = []
|
||||
const days = Math.floor(time / 86400000);
|
||||
const hours = Math.floor(time / 3600000) % 24;
|
||||
const minutes = Math.floor(time / 60000) % 60;
|
||||
const seconds = Math.floor(time / 1000) % 60;
|
||||
if (days > 0) text.push(`${days} days`)
|
||||
if (hours > 0) text.push(`${hours} hours`)
|
||||
if (minutes > 0) text.push(`${minutes} minutes`)
|
||||
if (text.length > 0) text.push(`and ${seconds} seconds`)
|
||||
else text.push(`${seconds} seconds`)
|
||||
return text.join(", ").replace(", and", " and")
|
||||
}
|
||||
33
handlers/webhook.js
Normal file
33
handlers/webhook.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { WebhookClient, EmbedBuilder } = require("discord.js")
|
||||
const config = require("./configuration")
|
||||
const cliColor = require("cli-color")
|
||||
|
||||
module.exports = function Webhook(embed) {
|
||||
if (config.notifier.enable) {
|
||||
try {
|
||||
const webhook = new WebhookClient({
|
||||
url: config.notifier.webhook
|
||||
})
|
||||
webhook.send({
|
||||
embeds: [
|
||||
new EmbedBuilder(embed.data)
|
||||
.setAuthor({
|
||||
name: config.notifier.embed.author.name || null,
|
||||
iconURL: config.notifier.embed.author.icon || null
|
||||
})
|
||||
.setFooter({
|
||||
text: config.notifier.embed.footer.text || null,
|
||||
iconURL: config.notifier.embed.footer.icon || null
|
||||
})
|
||||
.setURL(config.notifier.embed.url || null)
|
||||
.setTimestamp(config.notifier.embed.timestamp ? new Date() : null)
|
||||
.setThumbnail(config.notifier.embed.thumbnail || null)
|
||||
.setImage(config.notifier.embed.image || null)
|
||||
]
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(cliColor.cyanBright("[PteroStats] ") + cliColor.redBright("Invalid Webhook URL"))
|
||||
}
|
||||
}
|
||||
}
|
||||
65
index.js
65
index.js
@@ -1,52 +1,23 @@
|
||||
const intigrityCheck = require("./modules/intigrityCheck");
|
||||
|
||||
if(!intigrityCheck()){
|
||||
return console.log("Intigrity check failed!");
|
||||
}
|
||||
|
||||
const fs = require("node:fs");
|
||||
const chalk = require("chalk");
|
||||
const yaml = require("js-yaml");
|
||||
const { Client, GatewayIntentBits } = require("discord.js");
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
const cliColor = require("cli-color");
|
||||
const package = require("./package.json");
|
||||
|
||||
client.config = yaml.load(fs.readFileSync("./config.yml", "utf8"));
|
||||
console.log(
|
||||
` _${cliColor.blueBright.bold(`${cliColor.underline("Ptero")}dact${cliColor.underline("yl & P")}eli${cliColor.underline("can")}`)}___ ______ ______ \n` +
|
||||
` /\\ ___\\ /\\__ _\\ /\\ __ \\ /\\__ _\\ /\\ ___\\ \n` +
|
||||
` \\ \\___ \\ \\/_ \\ \\/ \\ \\ \\_\\ \\ \\/_/\\ \\/ \\ \\___ \\ \n` +
|
||||
` \\/\\_____\\ \\ \\_\\ \\ \\_\\ \\_\\ \\ \\_\\ \\/\\_____\\ \n` +
|
||||
` \\/_____/ \\/_/ \\/_/\\/_/ \\/_/ \\/_____/${cliColor.yellowBright.bold(`${package.version}`)}`
|
||||
);
|
||||
|
||||
if (
|
||||
client.config.panel.adminkey ||
|
||||
client.config.resource ||
|
||||
client.config.message.image
|
||||
) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") +
|
||||
chalk.red(
|
||||
"You are using old config file, please update your config file at "
|
||||
) +
|
||||
chalk.green(
|
||||
"https://github.com/HirziDevs/PteroStats/blob/main/config.yml"
|
||||
)
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
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 \n${package.description}\n `
|
||||
);
|
||||
|
||||
if (client.config.token.startsWith("Put") || !client.config.token.length) {
|
||||
console.log(
|
||||
chalk.cyan("[PteroStats] ") + chalk.red("Error! Invalid Discord Bot Token")
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
if (!fs.existsSync(".env")) return require("./handlers/setup.js")();
|
||||
|
||||
const eventFiles = fs
|
||||
.readdirSync("./events")
|
||||
.filter((file) => file.endsWith(".js"));
|
||||
|
||||
for (const file of eventFiles) {
|
||||
const event = require(`./events/${file}`);
|
||||
if (event.once) {
|
||||
client.once(event.name, (...args) => event.execute(...args));
|
||||
} else {
|
||||
client.on(event.name, (...args) => event.execute(...args));
|
||||
}
|
||||
}
|
||||
|
||||
client.login(client.config.token);
|
||||
require("./handlers/application.js")();
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
const fs = require("node:fs");
|
||||
const child = require("node:child_process");
|
||||
const path = require("node:path");
|
||||
|
||||
const REQUIRED_PACKAGES = [
|
||||
{
|
||||
name: "axios",
|
||||
version: "1.6.3",
|
||||
},
|
||||
{
|
||||
name: "axios-retry",
|
||||
version: "3.9.1",
|
||||
},
|
||||
{
|
||||
name: "chalk",
|
||||
version: "4.1.2",
|
||||
},
|
||||
{
|
||||
name: "discord.js",
|
||||
version: "14.14.1",
|
||||
},
|
||||
{
|
||||
name: "js-yaml",
|
||||
version: "4.1.0",
|
||||
},
|
||||
];
|
||||
const NODE_VERSION = 16;
|
||||
|
||||
function InstallPackages() {
|
||||
console.log("Required nodejs packages not found!");
|
||||
console.log("Please wait... starting to install all required node packages.");
|
||||
|
||||
let packagesList = "";
|
||||
for(const package of REQUIRED_PACKAGES){
|
||||
packagesList += `\n- ${package.name}@${package.version}`;
|
||||
}
|
||||
console.log("The following packages will be installed:", packagesList);
|
||||
|
||||
console.log(
|
||||
"If the bot can't install the packages, please install them manually."
|
||||
);
|
||||
|
||||
try {
|
||||
let packagesList = "";
|
||||
|
||||
for (const package of REQUIRED_PACKAGES) {
|
||||
packagesList += ` ${package.name}@${package.version}`;
|
||||
}
|
||||
|
||||
child.execSync(`npm i${packagesList}`);
|
||||
console.log('Install complete!, please run "node index" command again!');
|
||||
|
||||
process.exit();
|
||||
} catch (err) {
|
||||
console.log("Error! ", err);
|
||||
console.log("Support Server: https://discord.gg/zv6maQRah3");
|
||||
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = () => {
|
||||
if (Number(process.version.split(".")[0]) < NODE_VERSION) {
|
||||
console.log(
|
||||
`Unsupported NodeJS Version!, Please use NodeJS ${NODE_VERSION}.x or higher.`
|
||||
);
|
||||
|
||||
process.exit();
|
||||
}
|
||||
|
||||
const nodeModulesFolderPath = path.join(__dirname, "../", "node_modules");
|
||||
|
||||
if (fs.existsSync(nodeModulesFolderPath)) {
|
||||
let success = false;
|
||||
let errorMessage = "";
|
||||
|
||||
for (const package of REQUIRED_PACKAGES) {
|
||||
const packageFilePath = path.join(
|
||||
__dirname,
|
||||
"../",
|
||||
"node_modules",
|
||||
package.name,
|
||||
"package.json"
|
||||
);
|
||||
|
||||
if (fs.existsSync(packageFilePath)) {
|
||||
let packageFile = fs.readFileSync(packageFilePath, "utf-8");
|
||||
|
||||
if (packageFile) {
|
||||
packageFile = JSON.parse(packageFile);
|
||||
|
||||
if (packageFile.version !== package.version) {
|
||||
console.log(
|
||||
`Unsupported "${package.name}" version!.\nPlease delete your "node_modules" and "package-lock.json".\nAnd restart the bot.\nPlease make sure to check and remove "npm install" command from your startup params.`
|
||||
);
|
||||
|
||||
process.exit();
|
||||
} else {
|
||||
success = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
|
||||
errorMessage = `Unknown package version- "${package.name}".`;
|
||||
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
|
||||
errorMessage = `Missing package- "${package.name}".`;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
if (errorMessage) {
|
||||
console.log(errorMessage);
|
||||
}
|
||||
|
||||
InstallPackages();
|
||||
}
|
||||
} else {
|
||||
InstallPackages();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
const SUPPORTED_TYPES = ["mb", "gb", "tb"];
|
||||
|
||||
module.exports = (value, type) => {
|
||||
if (value) {
|
||||
value = parseInt(value);
|
||||
|
||||
if (value > 0) {
|
||||
if (!type) {
|
||||
type = "mb";
|
||||
} else {
|
||||
type = type?.toLowerCase() || "mb";
|
||||
|
||||
if (!SUPPORTED_TYPES.includes(type)) {
|
||||
type = "mb";
|
||||
}
|
||||
}
|
||||
|
||||
let result = "";
|
||||
|
||||
switch (type) {
|
||||
case "mb":
|
||||
result = value.toFixed(2).toLocaleString().replace(".00", "") + " MB";
|
||||
break;
|
||||
case "gb":
|
||||
result =
|
||||
(value / 1024).toFixed(2).toLocaleString().replace(".00", "") +
|
||||
" GB";
|
||||
break;
|
||||
case "tb":
|
||||
result =
|
||||
(value / (1024 * 1000))
|
||||
.toFixed(2)
|
||||
.toLocaleString()
|
||||
.replace(".00", "") + " TB";
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return "0";
|
||||
}
|
||||
} else {
|
||||
return "0";
|
||||
}
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
module.exports = (used, total) => {
|
||||
if (used && total) {
|
||||
used = parseInt(used);
|
||||
total = parseInt(total);
|
||||
|
||||
if (used >= 1 && total >= 1) {
|
||||
let percentage = ((used / total) * 100).toFixed(2).toLocaleString().replace(".00", "") + " %";
|
||||
|
||||
return percentage;
|
||||
} else {
|
||||
return "ERROR";
|
||||
}
|
||||
} else {
|
||||
return "ERROR";
|
||||
}
|
||||
};
|
||||
49
package.json
49
package.json
@@ -1,25 +1,26 @@
|
||||
{
|
||||
"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.xyz",
|
||||
"bugs": {
|
||||
"email": "hirzidevs@gmail.com",
|
||||
"url": "https://github.com/HirziDevs/PteroStats/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.6.3",
|
||||
"axios-retry": "^3.9.1",
|
||||
"chalk": "^4.1.2",
|
||||
"discord.js": "^14.14.1",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
}
|
||||
"name": "pterostats",
|
||||
"version": "4.0.0",
|
||||
"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",
|
||||
"bugs": {
|
||||
"email": "hirzi@znproject.my.id",
|
||||
"url": "https://github.com/HirziDevs/PteroStats/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.7.7",
|
||||
"cli-color": "2.0.4",
|
||||
"discord.js": "14.16.2",
|
||||
"dotenv": "16.4.5",
|
||||
"js-yaml": "4.1.0",
|
||||
"prettier-bytes": "1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
46
setup.js
Normal file
46
setup.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const fs = require("node:fs");
|
||||
const cliColor = require("cli-color");
|
||||
const package = require("./package.json");
|
||||
const readline = require('readline').createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
console.log(
|
||||
` _${cliColor.blueBright.bold(`${cliColor.underline("Ptero")}dact${cliColor.underline("yl & P")}eli${cliColor.underline("can")}`)}___ ______ ______ \n` +
|
||||
` /\\ ___\\ /\\__ _\\ /\\ __ \\ /\\__ _\\ /\\ ___\\ \n` +
|
||||
` \\ \\___ \\ \\/_ \\ \\/ \\ \\ \\_\\ \\ \\/_/\\ \\/ \\ \\___ \\ \n` +
|
||||
` \\/\\_____\\ \\ \\_\\ \\ \\_\\ \\_\\ \\ \\_\\ \\/\\_____\\ \n` +
|
||||
` \\/_____/ \\/_/ \\/_/\\/_/ \\/_/ \\/_____/${cliColor.yellowBright.bold(`${package.version}`)}`
|
||||
);
|
||||
|
||||
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 \n${package.description}\n `
|
||||
);
|
||||
|
||||
if (!fs.existsSync(".env")) return require("./handlers/setup.js")();
|
||||
|
||||
console.log(cliColor.yellowBright(
|
||||
"Configuration is already set. Please select one of the following options:\n \n" +
|
||||
`${cliColor.cyanBright("1")} ${cliColor.blueBright("»")} Start the App\n` +
|
||||
`${cliColor.cyanBright("2")} ${cliColor.blueBright("»")} Change configuration\n `
|
||||
));
|
||||
|
||||
readline.question('> ', async (answer) => {
|
||||
readline.close();
|
||||
|
||||
switch (answer) {
|
||||
case '2':
|
||||
require("./handlers/setup.js")();
|
||||
break;
|
||||
case '1':
|
||||
require("./handlers/application.js")();
|
||||
break;
|
||||
default:
|
||||
console.log(cliColor.redBright('Invalid input. Please type either 1 or 2.'));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user