diff --git a/Indo.md b/Indo.md new file mode 100644 index 0000000..74b6970 --- /dev/null +++ b/Indo.md @@ -0,0 +1,81 @@ +
+ +![PteroStats Banner](https://cdn.discordapp.com/attachments/626755594526916629/978478722489393153/20220524_090325.png) + +## Bahasa / Language +[[Indonesia]](https://github.com/HirziDevs/PteroStats/blob/dev/Indo.MD) | [[Inggris]](https://github.com/HirziDevs/PteroStats/blob/dev/README.md) + +
+ +## Pengenalan +PteroStats adalah bot yang dirancang untuk memeriksa status panel pterodactyl dan dikirim ke server discord + +## Instalasi + +### Mendapatkan apikey dari pterodactyl +- Pergi ke `panel admin pterodactyl` dan pergi ke `Application API` + + ![Idk](https://usercontent.catto.pictures/hirzi/d5225df9-7395-491b-a214-dcd110b12308.png) + +- Klik tombol `Create New` + + ![Admin Panel](https://usercontent.catto.pictures/hirzi/5ac33e25-ac37-416a-99a6-46d860a51645.png) + +- Set semua permission ke `read` dan untuk description kamu bisa mengisi apa saja + + ![Application API](https://usercontent.catto.pictures/hirzi/a0c4a721-e1eb-483f-9a36-0c2aaa213186.png) + +- Copy apikey-nya. + + ![Copy the apikey](https://usercontent.catto.pictures/hirzi/086111e0-0ffa-48ee-8839-801e0c3678cc.png) + +### Membuat Discord Bot +Kalian bisa cek [website ini](https://discordjs.guide/preparations/setting-up-a-bot-application.html) + +### 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 + + ![User Settings](https://usercontent.catto.pictures/hirzi/c5e825d1-c323-4b19-a11b-e2f004d4906e.png) + +2. Copy Channel ID-nya + + ![Channel ID](https://usercontent.catto.pictures/hirzi/e5fa4f62-b28f-45fd-a544-429f23899edb.png) + +### Memulai Bot +- Masukan `token` bot discord di `line token` yang terdapat di file `config.yml` +- Copy `id channel` diserver discord kamu dan masukan ke line `channel` di file `config.yml` +- Masukan `apikey` dan `url` pterodactyl di `line panel` di file `config.yml` +- Jalankan command `npm install` di folder yang berisi file bot +- 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 + + ![Idk](https://usercontent.catto.pictures/hirzi/1f59b255-7c5d-48f2-ab93-5358429cec83.png) + +2. Pilih custom emoji yang kamu mau + + ![Idk](https://usercontent.catto.pictures/hirzi/38098261-7257-4e4d-8945-4ac5c252c952.png) + +3. Copy textnya! + + ![Idk](https://usercontent.catto.pictures/hirzi/33800ccf-9ed5-4d54-9747-2983b23e1755.png) + +## Permission apikey + +Pilih Aktifkan `read` di semua opsi permission, jika tetap error pilih `read & write` di semua opsi permission + +![Admin Apikey Permission](https://media.discordapp.net/attachments/819757140155564062/876320084992331816/Screenshot_2021-08-15-11-20-05-56.jpg) + +## Links + +- [Pterodactyl Panel](https://pterodactyl.io) +- [Pterodactyl Api Documentation](https://dashflo/docs/api/pterodactyl/v1) +- [Pterodactyl Discord Server]((https://discord.gg/pterodactyl)) +- [PteroBot Support Server](https://discord.gg/zv6maQRah3) +- [PteroBot Support Server (Indonesia)](https://discord.gg/EYaFB7WSg6) \ No newline at end of file diff --git a/README.md b/README.md index 46124a1..d84f81b 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,81 @@ -# THIS BUILD HASN'T BEEN TESTED WITH NODE THAT WINGS DOWN +
-# PteroStats +![PteroStats Banner](https://cdn.discordapp.com/attachments/626755594526916629/978478722489393153/20220524_090325.png) -![GitHub](https://img.shields.io/github/package-json/v/HirziDevs/PteroStats?style=flat-square) -![GitHub](https://img.shields.io/github/license/HirziDevs/PteroStats?style=flat-square) +## Language / Bahasa +[[English]](https://github.com/HirziDevs/PteroStats/blob/dev/README.md) | [[Indonesia]](https://github.com/HirziDevs/PteroStats/blob/dev/Indo.MD) -![img](https://media.discordapp.net/attachments/796259732683227157/861126504246411264/20210704_130856.jpg) +
## Introduction - PteroStats is a bot designed to check Pterodactyl Panel and Nodes status and post it to your discord server -- Written in Javascript, CloudServer is faster and more stable. -- PteroControl can be used with any server on Pterodactyl, irregardless of whether it's on shared hosting or your own hosted panel - -PteroStats is still **under development** and we welcome contributions. - -### How it works? - -PteroStats checks [pterodactyl](https://pterodactyl.io) nodes wings with [axios](https://www.npmjs.com/package/axios) to get nodes wings status, if the api didn't reply that mean the node is having [wings/daemon](https://pterodactyl.io/wings/1.0/installing.html) down and mark the node as offline - -### Screenshot - -- [**ItzyStore**](https://discord.gg/PS4Mf6DBzt) (No screenshot for resource becouse it will be long image) -![img](https://media.discordapp.net/attachments/796259732683227157/863359897210060820/IMG_20210710_164939.jpg) -- [**SpaceCloud**](https://discord.gg/28z8CYmPEY) -![img](https://media.discordapp.net/attachments/586738538448420881/866624597171372032/IMG_20210719_171633.jpg) - ## Installation -- `fill in the required informations in the config.yml file` -- `Run npm install in the root directory of the bot files` -- `Run node index.js and you are done` +### Getting apikey from pterodactyl +- Go to your `pterodactyl admin page` and go to `Application API`. -if you need help contact me on discord `Hirzi#8701` or join [our discord server here](https://discord.gg/zv6maQRah3) + ![Idk](https://usercontent.catto.pictures/hirzi/d5225df9-7395-491b-a214-dcd110b12308.png) -### Setuping Config +- Click on the `Create New` button -You need to put right config to make the bot work at [config.yml](https://github.com/HirziDevs/PteroStats/blob/main/config.yml) file -``` -# PteroStats config -# If you need help, join our discord server: https://discord.gg/zv6maQRah3 + ![Idk](https://usercontent.catto.pictures/hirzi/5ac33e25-ac37-416a-99a6-46d860a51645.png) -# Bot Info's -token: 'BOT TOKEN' # Put bot token here, check https://discord.dev to create and get bot token -botstatus: - enable: false # Enable Custom Status (MUST BE "true" OR "false") - text: 'Hosting Panel' # Bot Status Message - type: 'WATCHING' # Bot Status Type. Ex: PLAYING, WATCHING, LISTENING, STREAMING +- Set all options permission to `read` and for description you can put whatever you want -# Channel and RefreshTime Configuration -channel: 'CHANNEL ID' # Put channel id here where the embed will be sended -refreshtime: 60 # Time when the embed edited/refreshed (MUST BE A SECONDS) (RECOMMENDED MORE THAN 20 SECONDS) + ![Idk](https://usercontent.catto.pictures/hirzi/a0c4a721-e1eb-483f-9a36-0c2aaa213186.png) -# Panel Info's -panel: - url: 'HOST PANEL LINK' # Put panel url here. Example: https://panel.purenodes.net - adminkey: 'ADMIN APIKEY' # Put Admin Apikey here. check https://your.host.url/admin/api (your.host.url is an example link) to get the Admin ApiKey +- Copy the apikey. -# Embed Configuration -embed: - title: 'EMBED TITLE' # Embed Title here. Ex: PureNodes Stats - color: 'E5BE11' # Embed Hex color here. - description: - enable: false # Enable Embed Description (MUST BE "true" OR "false") - text: 'EMBED DESCRIPTION' # Embed Description - footer: - enable: true # Enable Embed Footer (MUST BE "true" OR "false") - text: 'By Hirzi#8701' # Embed Footer - timestamp: true # Enable Embed TimeStamp (MUST BE "true" OR "false") + ![Idk](https://usercontent.catto.pictures/hirzi/086111e0-0ffa-48ee-8839-801e0c3678cc.png) -# Status Message Configuration -status: - online: ':green_circle: Online' # Message if the status is Online - offline: ':red_circle: Offline' # Message if the status is Offline - check: ':orange_circle: Checking' # Message if the status is Checking +### Creating Discord Bot +Please refer to [this website](https://discordjs.guide/preparations/setting-up-a-bot-application.html) -# Node Resource -resource: - enable: false # Enable resource option ex [Ram: 2gb/5gb] bellow node name (MUST BE "true" OR "false") - servers: true # Enable Total server on the node to resource text (MUST BE "true" OR "false") - allocations: true # Enable Total Allocation on the node to resource text (MUST BE "true" OR "false") - location: true # Enable location short name on the node to resource text (MUST BE "true" OR "false") - unit: 'gb' # Must be 'mb', 'gb', or 'percent' +### Inviting Discord Bot +Please refer to [this website](https://discordjs.guide/preparations/adding-your-bot-to-servers.html) -# Developers feature -debug: false # Enable and Disable debug log to console -debugaxios: false #Enable and Disable axios error logs -``` +### Getting Channel ID +1. Enable Developer Feature at your discord settings -## Other -### FAQ + ![Idk](https://usercontent.catto.pictures/hirzi/c5e825d1-c323-4b19-a11b-e2f004d4906e.png) -Q: Can i use pterodactyl v0.7? +2. Copy Channel ID -A: No, the pterodactyl v0.7 is not supported + ![Idk](https://usercontent.catto.pictures/hirzi/e5fa4f62-b28f-45fd-a544-429f23899edb.png) -- +### Starting bot +- Put discord bot token in `config.yml` at `token line`. +- Put your pterodactyl `apikey` and `url` in `config.yml` at `panel line`. +- Copy `channel id` from your discord server and put it in `config.yml` file at `channel line`. +- Run `npm install` in the root directory of the bot files. +- Run `node index` and you are done. -Q: How much nodes can i add? +if you need help contact me on discord `Hirzi#8701` or join [our discord support server](https://discord.gg/zv6maQRah3) -A: You can add as much your panel have +### Using Custom Emoji +1. type `\` in guild that has custom emoji you want -- + ![Idk](https://usercontent.catto.pictures/hirzi/1f59b255-7c5d-48f2-ab93-5358429cec83.png) -Q: How i can get support? +2. Select custom emoji you want -A: You can join our [discord server](https://discord.gg/zv6maQRah3) + ![Idk](https://usercontent.catto.pictures/hirzi/38098261-7257-4e4d-8945-4ac5c252c952.png) -### Links +3. Copy the text! -* __[PteroBot Discord](https://discord.gg/zv6maQRah3)__ -* __[Pterodactyl Panel](https://pterodactyl.io)__ -* __[Pterodactyl API](https://dashflo.net/docs/api/pterodactyl/v1)__ + ![Idk](https://usercontent.catto.pictures/hirzi/33800ccf-9ed5-4d54-9747-2983b23e1755.png) + +## Admin Apikey Permission + +enable `read` on all options, if still didn't work enable `read & write` on all options + +![Admin Apikey Permission](https://media.discordapp.net/attachments/819757140155564062/876320084992331816/Screenshot_2021-08-15-11-20-05-56.jpg) + +## Links + +- [Pterodactyl Panel](https://pterodactyl.io) +- [Pterodactyl Api Documentation](https://dashflo/docs/api/pterodactyl/v1) +- [Pterodactyl Discord Server]((https://discord.gg/pterodactyl)) +- [PteroBot Support Server](https://discord.gg/zv6maQRah3) +- [PteroBot Support Server (Indonesia)](https://discord.gg/EYaFB7WSg6) \ No newline at end of file diff --git a/config.yml b/config.yml index 2c8a473..98a358b 100644 --- a/config.yml +++ b/config.yml @@ -1,48 +1,65 @@ # PteroStats config # If you need help, join our discord server here: https://discord.gg/zv6maQRah3 -# Bot Info's -token: 'BOT TOKEN' # Put bot token here, check https://discord.dev to create and get bot token -botstatus: - enable: false # Enable Custom Status (MUST BE "true" OR "false") - text: 'Hosting Panel' # Bot Status Message - type: 'WATCHING' # Bot Status Type. Ex: PLAYING, WATCHING, LISTENING, STREAMING +# Bot Configuration +token: 'Put bot token here' +bot_status: + enable: false + text: 'Hosting Panel' + type: 'WATCHING' # can be 'WATCHING', 'PLAYING', 'LISTENING', or 'COMPETING'. for 'STREAMING' is not working for now -# Channel and RefreshTime Configuration -channel: 'CHANNEL ID' # Put channel id here where the embed will be sended -refreshtime: 60 # Time when the embed edited/refreshed (MUST BE A SECONDS) (RECOMMENDED MORE THAN 20 SECONDS) +# Discord Channel and Refresh Time Configuration +channel: 'Put channel id here' +refresh: 60 -# Panel Info's +# Panel Configuration panel: - url: 'HOST PANEL LINK' # Put panel url here. Example: https://panel.purenodes.net - adminkey: 'ADMIN APIKEY' # Put Admin Apikey here. check https://your.host.url/admin/api (your.host.url is an example link) to get the Admin ApiKey + url: 'Put panel url here' + key: 'Put panel apikey here' # Embed Configuration +# set to false if you want to disable embed option embed: - title: 'EMBED TITLE' # Embed Title here. Ex: PureNodes Stats - color: 'E5BE11' # Embed Hex color here. - description: - enable: false # Enable Embed Description (MUST BE "true" OR "false") - text: 'EMBED DESCRIPTION' # Embed Description - footer: - enable: true # Enable Embed Footer (MUST BE "true" OR "false") - text: 'By Hirzi#8701' # Embed Footer - timestamp: true # Enable Embed TimeStamp (MUST BE "true" OR "false") + title: 'PteroStats' + color: '5865F2' + description: 'Next update {{time}}' # You can use {{time}} to make "in X seconds" time format + footer: 'By Hirzi#8701' + timestamp: true + thumbnail: false + image: false + field: + enable: false + title: 'Custom Field' + description: 'Custom Field' # You can use {{time}} to make "in X seconds" time format +# 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: '' + # Status Message Configuration status: - online: ':green_circle: Online' # Message if the status is Online - offline: ':red_circle: Offline' # Message if the status is Offline - check: ':orange_circle: Checking' # Message if the status is Checking + online: ':green_circle: Online' # TODO how to use custom emoji + offline: ':red_circle: Offline' # Node Resource resource: - enable: true # Enable resource option ex [Ram: 2gb/5gb] bellow node name (MUST BE "true" OR "false") - servers: true # Enable Total server on the node to resource text (MUST BE "true" OR "false") - allocations: true # Enable Total Allocation on the node to resource text (MUST BE "true" OR "false") - location: true # Enable location short name on the node to resource text (MUST BE "true" OR "false") - unit: 'gb' # Must be 'mb', 'gb', or 'percent' - -# Developers feature -debug: false # Enable and Disable debug log to console -debugaxios: false #Enable and Disable axios error logs + enable: false + servers: true + location: true + allocations: true + unit: 'gb' # You can use "gb", "mb", or "percent" \ No newline at end of file diff --git a/events/ready.js b/events/ready.js index fed6075..0f3aa4b 100644 --- a/events/ready.js +++ b/events/ready.js @@ -1,295 +1,29 @@ -module.exports = client => { +const chalk = require('chalk') +const checkStatus = require('../handlers/checkStatus') - //Code are very sensitive, please changes things on config.yml instead - - const { MessageEmbed } = require('discord.js') - const axios = require('axios') - const db = require('quick.db') - const nodetable = new db.table('node') - const paneltable = new db.table('panel') - const chalk = require('chalk') - const config = client.config +module.exports = { + name: 'ready', + once: true, + async 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')) - let enablecs = config.botstatus.enable - let cs = config.botstatus.text - let stype = config.botstatus.type - let ch = client.channels.cache.find(cn => cn.id === config.channel) - let time = config.refreshtime + if (client.guilds.cache.size === 0) return console.log(chalk.cyan('[PteroStats]') + chalk.red(' There is bot is not in servers, please invite the bot first!')) - let hosturl = config.panel.url - let adminapikey = config.panel.adminkey - - let statusonline = config.status.online - let statusoffline = config.status.offline - let checking = config.status.check - let resource = config.resource.enable - let serverres = config.resource.servers - let serverport = config.resource.allocations - let serverloc = config.resource.location - let unit = config.resource.unit - - let title = config.embed.title - let color = config.embed.color - let desc = config.embed.description.text - let footer = config.embed.footer.text - let enablets = config.embed.timestamp - let enabledesc = config.embed.description.enable - let enablef = config.embed.footer.enable - - let debug = config.debug - let debugerror = config.debugaxios - - if (debug === true) { - console.log(chalk.red('=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=')) - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.green('Debug Mode: ') + chalk.cyan('true')) - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.green('Debug Axios Mode: ') + chalk.cyan(debugerror)) - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.green('Resource: ') + chalk.cyan(resource)) - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.green('Custom Status: ') + chalk.cyan(enablecs)) - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.green('Enable Timestamp: ') + chalk.cyan(enablets)) - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.green('Enable Description: ') + chalk.cyan(enabledesc)) - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.green('Enable Footer: ') + chalk.cyan(enablef)) - } - - if (!hosturl.includes('http')) hosturl = 'http://' + hosturl - let unapi = hosturl + '/api' - let api = unapi.replace('//api', '/api') - - if (enablecs === true) { - client.user.setActivity(cs, { type: stype }) - } else { - client.user.setActivity(title + ' Panel Stats', { type: 'WATCHING' }) - } - - console.log(chalk.red('=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=')) - console.log(chalk.green('Name: ') + chalk.cyan('PteroStats')) - console.log(chalk.green('Version: ') + chalk.cyan('Stable pre-v1.5.0')) - console.log(chalk.green('Refresh Time: ') + chalk.cyan(time + ' Seconds')) - console.log(chalk.green('Bot Status: ') + chalk.cyan('Online')) - console.log(chalk.green('Support: ') + chalk.cyan('https://discord.gg/zv6maQRah3')) - console.log(chalk.red('=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=')) - console.log(chalk.red('You are using dev build, something not wanted might be happen!')) - - if(paneltable.get('URL') === null) console.log(chalk.cyan('It seems you are using our bot for first time, thank you for choosing our bot, if you need help you can join our support server!')) - if(paneltable.get('URL') !== api) console.log(chalk.cyan('Panel url changed, please allow the bot to check the nodes status for ' + time + ' seconds')) - paneltable.set('URL',api) - - setInterval(() => { - if (isNaN(time)) return console.log(chalk.cyan('[PteroStats Checker] ') + chalk.red(time + ' is not a number!')) - if (!hosturl.includes('.')) return console.log(chalk.cyan('[PteroStats Checker] ') + chalk.red(hosturl + ' is invalid url!')) - if (adminapikey.length < 48) return console.log(chalk.cyan('[PteroStats Checker] ') + chalk.red('Invalid Admin Apikey!!')) - - let list = [] - axios(api + '/application/nodes/', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + adminapikey - } - }).then(response => { - let data = response.data.data - data.forEach(nodes => { - let id = nodes.attributes.id - axios(api + '/application/nodes/' + id + '?include=servers,location,allocations', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + adminapikey - } - }).then(node => { - axios(api + '/application/nodes/' + id + '/configuration', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + adminapikey + if (client.config.bot_status.enable) { + if (!['PLAYING', 'WATCHING', 'LISTENING', 'COMPETING'].includes(client.config.status.type)) { + console.log('Invalid Status Type!, Can be "WATCHING", "PLAYING", "LISTENING", or "COMPETING"') + } else { + client.user.setActivity(client.config.status.text, { type: client.config.status.type }) } - }).then(data => { - axios(node.data.attributes.scheme + '://' + node.data.attributes.fqdn + ':' + node.data.attributes.daemon_listen + '/api/servers', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + data.data.token - } - }).then(status => { - let ram = 'temp' - let disk = 'temp' - - const mode = node.data.attributes.maintenance_mode - const loc = '[Locations: ' + node.data.attributes.relationships.location.attributes.short + ']' - const port = '[Allocations: ' + node.data.attributes.relationships.allocations.data.length + ']' - const servers = '[Servers: ' + node.data.attributes.relationships.servers.data.length + ']' - const rampercent = '[Ram: ' + Math.floor(node.data.attributes.allocated_resources.memory / node.data.attributes.memory * 100) + '%/100%]' - const diskpercent = '[Disk: ' + Math.floor(node.data.attributes.allocated_resources.disk / node.data.attributes.disk * 100) + '%/100%]' - const rammega = '[Ram: ' + node.data.attributes.allocated_resources.memory + 'MB/' + node.data.attributes.memory + 'MB]' - const diskmega = '[Disk: ' + node.data.attributes.allocated_resources.disk + 'MB/' + node.data.attributes.disk + 'MB]' - const ramgiga = '[Ram: ' + Math.floor(node.data.attributes.allocated_resources.memory / 1000) + 'GB/' + Math.floor(node.data.attributes.memory / 1000) + 'GB]' - const diskgiga = '[Disk: ' + Math.floor(node.data.attributes.allocated_resources.disk / 1000) + 'GB/' + Math.floor(node.data.attributes.disk / 1000) + 'GB]' - if (unit === 'mb') { - disk = diskmega - ram = rammega - } - if (unit === 'gb') { - disk = diskgiga - ram = ramgiga - } - if (unit === 'percent') { - disk = diskpercent - ram = rampercent - } - - nodetable.set('node' + id, { - ram: ram, - disk: disk, - status: true, - servers: servers, - location: loc, - port: port, - mode: mode - }) - - }).catch((err) => { - let servers = '[Servers: N/A]' - let loc = '[Location: N/A]' - let port = '[Allocations: N/A]' - let ram = '[Ram: N/A]' - let disk = '[Disk: N/A]' - - console.log(chalk.cyan('[PteroStats Checker] ') + chalk.red(node.data.attributes.name + ' is down!')) - if (debugerror === true) console.log(chalk.magenta('[PteroStats Debug] ') + chalk.red(err) + chalk.cyan(' Need Support? https://discord.gg/zv6maQRah3')) - - nodetable.set('node' + id, { - ram: ram, - disk: disk, - status: false, - servers: servers, - location: loc, - port: port, - mode: false - }) - }) - }).catch((err) => { - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.red(err) + chalk.cyan(' Need Support? https://discord.gg/zv6maQRah3')) - }) - }).catch((err) => { - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.red(err) + chalk.cyan(' Need Support? https://discord.gg/zv6maQRah3')) - }) - - let stats = nodetable.get('node' + id) - let msgStats = '' - if (stats === null) msgStats = '**' + nodes.attributes.name + '**: ' + checking - if (stats) { - let statsname = '**' + nodes.attributes.name + '**: ' - - if (stats.status === true) statsname = statsname + statusonline - if (stats.status === false) statsname = statsname + statusoffline - - if (stats.mode === true) statsname = statsname + ' [Maintance]' - - if (resource === true) statsname = statsname + '\n```\n' + stats.ram + '\n' + stats.disk - if (serverloc === true) statsname = statsname + '\n' + stats.location - if (serverport === true) statsname = statsname + '\n' + stats.port - if (serverres === true) statsname = statsname + '\n' + stats.servers - if (resource === false) statsname = statsname + '\n' - - if (resource === true) msgStats = statsname + '```\n' } - if (debug === true) console.log(chalk.magenta('[PteroStats Debug] ') + chalk.blue(nodes.attributes.name + ': ' + stats.status)) - list.push(msgStats) - }) + if (client.config.refresh < 10) console.log('Refresh below 10 seconds is not recommended!') - axios(api + '/application/servers', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + adminapikey - } - }).then(ser => { - let res = ser.data.meta.pagination.total - paneltable.set('serverCount', res) - }).catch((err) => { - paneltable.set('serverCount', 'N/A') - console.log(chalk.cyan('[PteroStats Checker] ') + chalk.red('Panel is down')) - if (debugerror === true) console.log(chalk.magenta('[PteroStats Debug] ') + err) - }) + checkStatus(client) - axios(api + '/application/users', { - method: 'GET', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + adminapikey - } - }).then(usr => { - let res = usr.data.meta.pagination.total - paneltable.set('userCount', res) - }).catch((err) => { - paneltable.set('userCount', 'N/A') - console.log(chalk.cyan('[PteroStats Checker] ') + chalk.red('Panel is down!')) - if (debugerror === true) console.log(chalk.magenta('[PteroStats Debug] ') + err) - }) - - let userCount = paneltable.get('userCount') - let serverCount = paneltable.get('serverCount') - - if (userCount === null) userCount = checking - if (serverCount === null) serverCount = checking - - if (userCount !== 'N/A') paneltable.set('panel', '**Panel**: ' + statusonline) - if (userCount === 'N/A') { - paneltable.set('panel', '**Panel**: ' + statusoffline) - console.log(chalk.cyan('[PteroStats Checker] ') + chalk.red('panel is down!')) - } - if (userCount === checking) paneltable.set('panel', '**Panel**: ' + checking) - let panel = paneltable.get('panel') + '\n\nUsers: ' + userCount + '\nServers: ' + serverCount - - if (panel === null) panel = '**Panel**: ' + checking + '\n\nUsers: ' + userCount + '\nServers: ' + serverCount - - let nodes - list.forEach((d) => { - if (!nodes) return nodes = d - nodes = nodes + d - }) - - console.log(chalk.cyan(['[PteroStats Checker] ']) + chalk.green('Connected to ' + list.length + ' nodes') - let nodeCount = '[Total ' + list.length + ']' - - if (debug === true) console.log(chalk.magenta('[PteroStats Debug] ') + chalk.blue(nodes)) - if (nodes === undefined) { - nodes = checking + ' Please wait ' + time + ' seconds' - console.log(chalk.cyan(['[PteroStats Checker] ']) + chalk.yellow(checking + ' Please wait ' + time + ' seconds')) - } - - let embedfooter = 'Updated every ' + time + ' seconds' - if (enablef === true) embedfooter = 'Updated every ' + time + ' seconds | ' + footer - - let embed = new MessageEmbed() - .setTitle(title) - .setColor(color) - .addField('Panel Stats', panel) - .setFooter(embedfooter) - .setThumbnail(client.user.avatarURL()) - if (enablets === true) { - embed.setTimestamp() - } - if (enabledesc === true) { - embed.setDescription(desc + '\n**Nodes Stats' + nodeCount + '**\n' + nodes) - } else { - embed.setDescription('\n**Nodes Stats' + nodeCount + '**\n' + nodes) - } - - ch.send(embed).then(msg => { msg.delete({ timeout: time + '000' }) }) - - console.log(chalk.cyan('[PteroStats Checker] ') + chalk.green('Posted Stats')) - if (panel !== null) console.log(chalk.cyan('[PteroStats Checker] ') + chalk.green('Stats Updated')) - console.log(chalk.cyan('[PteroStats Checker] ') + chalk.green('Updating Stats in ' + time + ' Seconds')) - - }).catch((err) => { - console.log(chalk.magenta('[PteroStats Debug] ') + chalk.red(err) + chalk.cyan(' Need Support? https://discord.gg/zv6maQRah3')) - }) - }, time + '000') -} + setInterval(async () => { + checkStatus(client) + }, client.config.refresh * 1000) + } +} \ No newline at end of file diff --git a/handlers/checkStatus.js b/handlers/checkStatus.js new file mode 100644 index 0000000..e06c184 --- /dev/null +++ b/handlers/checkStatus.js @@ -0,0 +1,125 @@ +const axios = require('axios') +const chalk = require('chalk') + +const postStatus = require('./postStatus') + +module.exports = async function checkStatus(client) { + + const nodes = [] + + const panel = { + id: 1, + status: false, + total_servers: -1, + total_users: -1, + } + console.log(chalk.cyan('[PteroStats]') + chalk.green(' Getting nodes stats')) + const panelStats = new Promise((resolve, reject) => { + axios(client.config.panel.url + '/api/application/users', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then(usr => { + axios(client.config.panel.url + '/api/application/servers', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then(async (ser) => { + panel.total_users = usr.data.meta.pagination.total + panel.total_servers = ser.data.meta.pagination.total + panel.status = true + + resolve() + }) + }).catch(async (err) => { + if (err.response) { + if (err.response.status === 403) { + console.log('[PteroStats] Err! Invalid apikey') + console.log('[PteroStats] 1. Make sure the apikey is from admin page not account page') + console.log('[PteroStats] 2. Make sure the apikey has read permission on all options') + console.log('[PteroStats] 3. Make sure the apikey is exist') + } else if (err.response.status === 404) { + console.log('[PteroStats] Err! Invalid URL Panel') + console.log('[PteroStats] 1. Make sure the panel url is like "https://panel.example.com"') + } else { + console.log(err) + } + } else { + console.log('[PteroStats] ' + err) + } + resolve() + }) + }) + + const nodeStats = new Promise((resolve, reject) => { + axios(client.config.panel.url + '/api/application/nodes?include=servers,location,allocations', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then(async (res) => { + res.data.data.forEach(async (node, i) => { + axios(client.config.panel.url + '/api/application/nodes/' + node.attributes.id + '/configuration', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then(async (data) => { + const body = { + id: node.attributes.id, + name: node.attributes.name, + location: node.attributes.relationships.location.attributes.short, + allocations: node.attributes.relationships.allocations.data.length, + status: true, + maintenance: node.attributes.maintenance_mode, + total_servers: node.attributes.relationships.servers.data.length, + memory_min: node.attributes.allocated_resources.memory, + memory_max: node.attributes.memory, + disk_min: node.attributes.allocated_resources.disk, + disk_max: node.attributes.disk, + } + + const stats = new Promise((statsResolve, statsReject) => { + axios(node.attributes.scheme + '://' + node.attributes.fqdn + ':' + node.attributes.daemon_listen + '/api/servers', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + data.data.token + } + }).then(async (status) => { + statsResolve() + }).catch(async (err) => { + body.status = false + statsResolve() + }) + }) + stats.then(() => { + nodes.push(body) + resolve() + }) + }).catch(async (err) => { + resolve() + }) + }) + }).catch(async (err) => { + resolve() + }) + }) + + panelStats.then(() => { + nodeStats.then(() => { + postStatus(client, panel, nodes) + }) + }) +} \ No newline at end of file diff --git a/handlers/postStatus.js b/handlers/postStatus.js new file mode 100644 index 0000000..6c0026d --- /dev/null +++ b/handlers/postStatus.js @@ -0,0 +1,153 @@ +const { MessageEmbed, Formatters, MessageActionRow, MessageButton } = require('discord.js') +const chalk = require('chalk') + +module.exports = async function postStatus(client, panel, nodes) { + + const channel = await client.channels.cache.get(client.config.channel) + + if (!channel) return console.log('Invalid Channel ID') + + let messages = await channel.messages.fetch({ limit: 10 }) + messages = messages.filter(m => m.author.id === client.user.id).last(); + if (messages && messages.embeds.length === 0) { + messages.delete() + messages = null + } + + const format = await Formatters.time(new Date(Date.now() + client.config.refresh * 1000), 'R') + const embed = new MessageEmbed() + + let text = '' + let desc = '' + + if (client.config.embed.title) embed.setTitle(client.config.embed.title) + if (client.config.embed.description) desc = client.config.embed.description.replaceAll('{{time}}', format) + '\n' + if (client.config.embed.color) embed.setColor(client.config.embed.color) + if (client.config.embed.footer) embed.setFooter({ text: client.config.embed.footer }) + if (client.config.embed.thumbnail) embed.setThumbnail(client.config.embed.thumbnail) + if (client.config.embed.image) embed.setImage(client.config.embed.image) + + panel.total_users = panel.total_users.toLocaleString() + panel.total_servers = panel.total_servers.toLocaleString() + + const stats = new Promise((resolve, reject) => { + if (nodes.length !== 0) { + nodes.forEach((data, i) => { + const title = data.name + ': ' + String(data.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) + let description = '```' + '\n' + + 'Status : ' + String(data.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) + '\n' + + switch (client.config.resource.unit) { + case 'gb': + description = description + + 'Memory : ' + Math.floor(data.memory_min / 1000).toLocaleString() + ' Gb /' + Math.floor(data.memory_max / 1000).toLocaleString() + ' Gb\n' + + 'Disk : ' + Math.floor(data.disk_min / 1000).toLocaleString() + 'Gb /' + Math.floor(data.disk_max / 1000).toLocaleString() + ' Gb\n' + break; + case 'percent': + description = description + + 'Memory : ' + Math.floor(data.memory_min / data.memory_max * 100) + ' %\n' + + 'Disk : ' + Math.floor(data.disk_min / data.disk_max * 100) + ' %\n' + break; + default: + description = description + + 'Memory : ' + data.memory_min.toLocaleString() + ' Mb /' + data.memory_max.toLocaleString() + ' Mb\n' + + 'Disk : ' + data.disk_min.toLocaleString() + 'Mb /' + data.disk_max.toLocaleString() + ' Mb\n' + } + + if (client.config.resource.location) description = description + 'Location: ' + data.location + '\n' + if (client.config.resource.allocations) description = description + 'Servers : ' + data.allocations.toLocaleString() + '\n' + if (client.config.resource.servers) description = description + 'Servers : ' + data.total_servers.toLocaleString() + '\n' + + description = description + '```' + + if (client.config.resource.enable) { + text = text + '\n**' + title.replace(':', ':**') + '\n' + description + } else { + text = text + '\n**' + title.replace(':', ':**') + } + + if (i + 1 === nodes.length) resolve() + }) + } else if (nodes.length === 0) { + if (!messages) { + text = 'There is no nodes to display' + resolve() + } else { + text = messages.embeds[0].fields[0].value.replaceAll(client.config.status.online, client.config.status.offline) + if (!panel.status && String(String(messages.embeds[0].fields[1].value).split('\n')[2]).split('')[String(String(messages.embeds[0].fields[1].value).split('\n')[2]).length - 1] !== '`') { + panel.total_users = String(String(messages.embeds[0].fields[1].value).split('\n')[2]).split('')[String(String(messages.embeds[0].fields[1].value).split('\n')[2]).length - 1] + panel.total_servers = String(String(messages.embeds[0].fields[1].value).split('\n')[3]).split('')[String(String(messages.embeds[0].fields[1].value).split('\n')[3]).length - 1] + } + resolve() + } + } + }) + + stats.then(async () => { + + embed.setDescription(desc + '\n**Nodes Stats [' + nodes.length + ']**' + text) + embed.addField('Panel Stats', + '**Status:** ' + String(panel.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) + '\n\n' + + 'Users: ' + String(panel.total_users).replace('-1', '`Unknown`') + '\n' + + 'Servers: ' + String(panel.total_servers).replace('-1', '`Unknown`') + ) + + if (client.config.embed.field.enable) { + embed.addField(client.config.embed.field.title, client.config.embed.field.description.replaceAll('{{time}}', format)) + } + + embed.setTimestamp() + + let row = [] + + if (client.config.button.enable) { + row = new MessageActionRow + if (client.config.button.btn1.label.length !== 0 && client.config.button.btn1.label.link.length !== 0) { + row.addComponents( + new MessageButton() + .setLabel(client.config.button.btn1.label) + .setStyle('LINK') + .setURL(client.config.button.btn1.url) + ) + } + if (client.config.button.btn2.label.length !== 0 && client.config.button.btn2.label.link.length !== 0) { + row.addComponents( + new MessageButton() + .setLabel(client.config.button.btn2.label) + .setStyle('LINK') + .setURL(client.config.button.btn2.url) + ) + } + if (client.config.button.btn3.label.length !== 0 && client.config.button.btn3.label.link.length !== 0) { + row.addComponents( + new MessageButton() + .setLabel(client.config.button.btn3.label) + .setStyle('LINK') + .setURL(client.config.button.btn3.url) + ) + } + if (client.config.button.btn4.label.length !== 0 && client.config.button.btn4.label.link.length !== 0) { + row.addComponents( + new MessageButton() + .setLabel(client.config.button.btn4.label) + .setStyle('LINK') + .setURL(client.config.button.btn4.url) + ) + } + if (client.config.button.btn5.label.length !== 0 && client.config.button.btn5.label.link.length !== 0) { + row.addComponents( + new MessageButton() + .setLabel(client.config.button.btn5.label) + .setStyle('LINK') + .setURL(client.config.button.btn5.url) + ) + } + + row = [row] + } + + if (!messages) channel.send({ embeds: [embed] }) + else messages.edit({ embeds: [embed], components: row }) + console.log(chalk.cyan('[PteroStats]') + chalk.green(' stats posted!')) + }) +} \ No newline at end of file diff --git a/index.js b/index.js index 4cf5d39..122ea6e 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,44 @@ -const { Client, Collection } = require('discord.js') -const fs = require('fs') -const client = new Client() -const yaml = require('js-yaml') -const config = yaml.load(fs.readFileSync('./config.yml', 'utf8')) +const fs = require('fs'); + +if (Number(process.version.split('.')[0]) < 16) { + console.log('Invalid NodeJS Version!, Please use NodeJS 16.x or upper') + process.exit() +} +if (fs.existsSync('./node_modules')) { + const check = require('./node_modules/discord.js/package.json') + if (Number(check.version.split('.')[0]) !== 13) { + console.log('Invalid Discord.JS Version!, Please use Discord.JS 13.x') + process.exit() + } +} else { + console.log('There is no node_modules!, please install the package first by using "npm i"') + process.exit() +} + +const yaml = require('js-yaml'); +const { Client, Intents } = require('discord.js'); +const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] }); + +// Load Config +const config = yaml.load(fs.readFileSync('./config.yml', 'utf8')); client.config = config -fs.readdir('./events/', (err, files) => { - if (err) return console.error(err) - files.forEach(file => { - const event = require(`./events/${file}`) - const eventName = file.split('.')[0] - client.on(eventName, event.bind(null, client)) - }) -}) +// Read Events Files +const eventFiles = fs.readdirSync('./events').filter(file => file.endsWith('.js')); -if (config.token === 'BOT TOKEN') console.log(chalk.blue('[PteroStats Checker] ') + chalk.red('Invalid Token, Check ') + chalk.green('config.yml') + chalk.red(' file to change token')) -client.login(config.token) +for (const file of eventFiles) { + const event = require(`./events/${file}`); + if (event.once) { + client.once(event.name, (client) => event.execute(client)); + } else { + client.on(event.name, (client) => event.execute(client)); + } +} + +// Login to bot +try { + client.login(config.token); +} catch (Err) { + console.log('Invalid discord bot token') + process.exit() +} \ No newline at end of file diff --git a/package.json b/package.json index 498bad0..397c07f 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,24 @@ { "name": "pterostats", - "version": "v1.5.0", + "version": "v2.0.0", "description": "PteroStats is a bot designed to check Panel, Nodes, and Databases status and post it to your discord server", "license": "Apache-2.0", "repository": "HirziDevs/PteroStats", "homepage": "https://pterostats.hirzidevs.net", "bugs": { - "email": "hirzigamingyt@gmail.com", + "email": "hirzidevs@gmail.com", "url": "https://github.com/HirziDevs/PteroStats" }, "scripts": { "start": "node index.js" }, "dependencies": { - "axios": "^0.21.1", - "chalk": "^4.1.1", - "js-yaml": "^4.1.0", - "discord.js": "^12.5.3", - "quick.db": "^7.1.3" + "axios": "^0.26.1", + "chalk": "4.1.1", + "discord.js": "^13.7.0", + "js-yaml": "^4.1.0" }, "engines": { - "node": "^12.x" + "node": "16.x" } }