diff --git a/Indo.md b/Indo.md new file mode 100644 index 0000000..3436700 --- /dev/null +++ b/Indo.md @@ -0,0 +1,129 @@ +
+ +PteroStats Banner + +## 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 + +## Contoh +- Test Panel + + Example + +- [Calvs Cloud](https://discord.gg/ssCQjhrBJN) + + Calvs Cloud + +## 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` + + Admin Panel + +2. Klik tombol `Create New` + + Application API Page + +3. Set semua permission ke `read` dan untuk description kamu bisa mengisi apa saja + + Create Application API + +4. Copy apikey-nya. + + Application API List + +5. Paste panel apikeynya dan panel urlnya di config + + Panel Config + +### Membuat Discord Bot +Kalian bisa cek [website ini](https://discordjs.guide/preparations/setting-up-a-bot-application.html) + +Paste bot tokennya di config + +Bot Config + +### 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 + + Discord User Settings + +2. Klik kanan teks channel dan pilih `Copy ID` + + Right Click Channel + +3. Paste id channelnya di config + + Channel Config + +### 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 + + Type \ on the chat + +2. Pilih custom emoji yang kamu mau + + Select Custom Emoji + +3. Copy textnya! + + Copy Emoji ID + +4. Paste id emojinya di config + + Status Config + +### Blacklist Nodes +1. Pilih node yang ada di node list admin page + + Nodes List + +2. Cek urlnya dan copy id nodenya + + Node Id + +3. Masukan ke blacklist di config + + Blacklist Config + +Kamu bisa memasukan lebih dari 1 node untuk di blacklist + +Blacklist Config + +## Permission apikey + +Jika kamu mengalami error 403 coba aktifkan `read & write` di semua opsi permission + +Application API Permission + +## 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/LICENSE b/LICENSE index 261eeb9..df40dd6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +MIT License - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Copyright (c) 2022 HirziDevs - 1. Definitions. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 40e817b..e8a4391 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,129 @@ -# PteroStats +
-![Version](https://img.shields.io/github/package-json/v/HirziDevs/PteroStats?style=flat-square) -![License](https://img.shields.io/github/license/HirziDevs/PteroStats?style=flat-square) +PteroStats Banner -![PteroStats Banner](https://media.discordapp.net/attachments/796259732683227157/861126504246411264/20210704_130856.jpg) +## Language / Bahasa +[[English]](https://github.com/HirziDevs/PteroStats/blob/dev/README.md) | [[Indonesia]](https://github.com/HirziDevs/PteroStats/blob/dev/Indo.md) + +
## 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 +## Example +- Test Panel -PteroStats is still **under development** and we welcome contributions. + Example -### How it works? +- [Calvs Cloud](https://discord.gg/ssCQjhrBJN) -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 - -- [**TreeCloud**](https://discord.gg/TCBhPan6SY) -![TreeCloud PteroStats](https://media.discordapp.net/attachments/819757140155564062/880346867949518848/Screenshot_2021-08-26-14-03-15-50_572064f74bd5f9fa804b05334aa4f912.jpg) -- [**ItzyStore**](https://discord.gg/PS4Mf6DBzt) -![ItzyStore PteroStats](https://media.discordapp.net/attachments/796259732683227157/863359897210060820/IMG_20210710_164939.jpg) -- [**SpaceCloud**](https://discord.gg/28z8CYmPEY) -![SpaceCloud PteroStats](https://media.discordapp.net/attachments/586738538448420881/866624597171372032/IMG_20210719_171633.jpg) + Calvs Cloud ## Installation +1. [Getting apikey from pterodactyl](#getting-apikey-from-pterodactyl) +2. [Creating Discord Bot](#creating-discord-bot) +3. [Inviting Discord Bot](#inviting-discord-bot) +4. [Getting Channel ID](#getting-channel-id) +5. [Starting bot](#starting-bot) -- `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` +- [Using Custom Emoji](#using-custom-emoji) +- [Blacklist Nodes](#blacklist-nodes) -if you need help contact me on discord `Hirzi#8701` or join [our discord server here](https://discord.gg/zv6maQRah3) +### Getting apikey from pterodactyl +1. Go to your `pterodactyl admin page` and go to `Application API`. -## Admin Apikey Permission + Admin Panel -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) +2. Click on the `Create New` button -### Setuping Config + Application API Page -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 +3. Set all options permission to `read` and for description you can put whatever you want -# 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 + Create Application API -# 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) +4. Copy the apikey. -# 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 + Application API List -# 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") +5. Paste the panel apikey and panel url at the config -# 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 + Panel Config -# 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' +### Creating Discord Bot +Please refer to [this website](https://discordjs.guide/preparations/setting-up-a-bot-application.html) -# Developers feature -debug: false # Enable and Disable debug log to console -debugaxios: false #Enable and Disable axios error logs -``` +Paste the bot token at the config -## Other -### FAQ +Bot Config -Q: Can i use pterodactyl v0.7? +### Inviting Discord Bot +Please refer to [this website](https://discordjs.guide/preparations/adding-your-bot-to-servers.html) -A: No, the pterodactyl v0.7 is not supported +### Getting Channel ID +1. Enable Developer Feature at your discord settings -- + Discord User Settings -Q: How much nodes can i add? +2. Right Click text channel and select `Copy ID` -A: The bot will automatically add nodes from your panel + Right Click Channel -- +3. Paste the channel id at the config -Q: How i can get support? + Channel Config -A: You can join our [discord server](https://discord.gg/zv6maQRah3) +### 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. -### Links +if you need help contact me on discord `Hirzi#8701` or join [our discord support server](https://discord.gg/zv6maQRah3) -* __[PteroBot Discord](https://discord.gg/zv6maQRah3)__ -* __[Pterodactyl Panel](https://pterodactyl.io)__ -* __[Pterodactyl API](https://dashflo.net/docs/api/pterodactyl/v1)__ +### Using Custom Emoji +1. type `\` in guild that has custom emoji you want -### Special Thanks to + Type \ on the chat -- ItsJustNickDev(from Discord) +2. Select custom emoji you want + + Select Custom Emoji + +3. Copy the text! + + Copy Emoji ID + +4. Paste the emoji id at the config + + Status Config + +### Blacklist Nodes +1. Select node from node list on admin page + + Nodes List + +2. Check the url and copy the node id + + Node Id + +3. Paste the id to the blacklist on config + + Blacklist Config + +You can add more than one node in the blacklist + +Blacklist Config + +## Apikey permission + +If you having issue with 403 error try to enable `read & write` on all options + +Application API Permission + +## 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..fe1759b 100644 --- a/config.yml +++ b/config.yml @@ -1,48 +1,76 @@ # 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 -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") +# Message Configuration +# set to false if you want to disable option +message: + content: false + attachment: false # If you enable attachment on message it upload the attachment first before sending or editing message and it will delay the stats + +embed: + title: 'PteroStats' + color: '5865F2' + description: 'Next update {{time}}' # You can use {{time}} to make "in X seconds" time format + footer: 'By Hirzi#8701' + timestamp: true + thumbnail: false + image: false + field: + enable: false + title: 'Custom Field' + description: 'Custom Field' # You can use {{time}} to make "in X seconds" time format + +# 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' + 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' +# Nodes Resource +nodes_resource: + blacklist: [] # You can add node id to remove the node from status embed (Example: "blacklist: [1]") + enable: false + servers: true + location: true + allocations: true + unit: 'gb' # You can use "gb", "mb", "tb", or "percent" -# Developers feature -debug: false # Enable and Disable debug log to console -debugaxios: false #Enable and Disable axios error logs +# Panel Users and Servers +panel_resource: + enable: true + servers: true + users: true \ No newline at end of file diff --git a/events/ready.js b/events/ready.js index c7e28f9..0927b0e 100644 --- a/events/ready.js +++ b/events/ready.js @@ -1,299 +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, + 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 < 1) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! This bot is not on any discord servers')) - let hosturl = config.panel.url - let adminapikey = config.panel.adminkey + if (client.config.bot_status.enable && client.config.bot_status.text.length > 0) { + if (!['PLAYING', 'WATCHING', 'LISTENING', 'COMPETING'].includes(client.config.bot_status.type.toUpperCase() || client.config.bot_status.type.length < 1)) { + console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Status Type!, Can be "WATCHING", "PLAYING", "LISTENING", or "COMPETING"')) + } else { + client.user.setActivity(client.config.bot_status.text, { type: client.config.bot_status.type.toUpperCase() }) + } + } - 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 + if (client.config.refresh < 10) console.log('Refresh lower than 10 seconds is not recommended!') - 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 + checkStatus(client) - 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 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('=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=')) - - 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(async (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 - } - }).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 + '\n' - if (`${stats}` !== 'null') { - let statsname = '**' + nodes.attributes.name + '**: ' - - if (stats.status === true) statsname = statsname + statusonline - if (stats.status === false) statsname = statsname + statusoffline - - if (resource === false) msgStats = statsname + '\n' - - 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' - } - - list.push(msgStats) - }) - - 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) - }) - - 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) - } - - let messages = await ch.messages.fetch({limit: 10}) - messages = messages.filter(m => m.author.id === client.user.id).last(); - if (messages == null) ch.send(embed) - else messages.edit(embed) - - - 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(() => { + 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..2bab4e7 --- /dev/null +++ b/handlers/checkStatus.js @@ -0,0 +1,144 @@ +const axios = require('axios') +const chalk = require('chalk') + +const postStatus = require('./postStatus') + +module.exports = function checkStatus(client) { + + if (client.config.channel.startsWith('Put')) { + console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Channel ID')) + process.exit() + } else if (client.config.panel.url.startsWith('Put')) { + console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Panel URL')) + process.exit() + } else if (client.config.panel.key.startsWith('Put')) { + console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Apikey')) + process.exit() + } else if (!client.config.panel.url.startsWith('http')) { + console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Panel URL')) + console.log(chalk.cyan('[PteroStats] ') + chalk.red('1. Make sure the panel url is starts with "https://" or "http://"')) + process.exit() + } + + if (client.config.panel.url.endsWith('/')) client.config.panel.url = client.config.panel.url.slice(0, -1) + + const nodes = [] + + const panel = { + status: false, + total_servers: -1, + total_users: -1, + } + + console.log(chalk.cyan('[PteroStats] ') + chalk.green('Getting nodes stats')) + + const panelStats = new Promise((resolve, reject) => { + axios(client.config.panel.url + '/api/application/users', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then((usr) => { + axios(client.config.panel.url + '/api/application/servers', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then((ser) => { + panel.total_users = usr.data.meta.pagination.total + panel.total_servers = ser.data.meta.pagination.total + panel.status = true + + resolve() + }) + }).catch((err) => { + if (err.response) { + if (err.response.status === 403) { + console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid apikey')) + console.log(chalk.cyan('[PteroStats] ') + chalk.red('1. Make sure the apikey is from admin page not account page')) + console.log(chalk.cyan('[PteroStats] ') + chalk.red('2. Make sure the apikey has read permission on all options')) + console.log(chalk.cyan('[PteroStats] ') + chalk.red('3. Make sure the apikey is exist')) + } else if (err.response.status === 404) { + console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Panel URL')) + console.log(chalk.cyan('[PteroStats] ') + chalk.red('1. Make sure the panel url is like "https://panel.example.com"')) + } else console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! ' + err)) + } else console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! ' + err)) + resolve() + }) + }) + + const nodeStats = new Promise((resolve, reject) => { + axios(client.config.panel.url + '/api/application/nodes?include=servers,location,allocations', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then((res) => { + res.data.data.forEach((node, i) => { + axios(client.config.panel.url + '/api/application/nodes/' + node.attributes.id + '/configuration', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + client.config.panel.key + } + }).then((data) => { + const body = { + id: node.attributes.id, + name: node.attributes.name, + location: node.attributes.relationships.location.attributes.short, + allocations: node.attributes.relationships.allocations.data.length, + status: true, + maintenance: node.attributes.maintenance_mode, + total_servers: node.attributes.relationships.servers.data.length, + memory_min: node.attributes.allocated_resources.memory, + memory_max: node.attributes.memory, + disk_min: node.attributes.allocated_resources.disk, + disk_max: node.attributes.disk, + } + + const stats = new Promise((statsResolve, statsReject) => { + axios(node.attributes.scheme + '://' + node.attributes.fqdn + ':' + node.attributes.daemon_listen + '/api/servers', { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + data.data.token + } + }).then((status) => { + return statsResolve() + }).catch((err) => { + body.status = false + return statsResolve() + }) + setTimeout(() => { + body.status = false + return statsResolve() + }, 1000) + }) + stats.then(() => { + nodes.push(body) + if (nodes.length === res.data.data.length) resolve() + }) + }).catch((err) => { + resolve() + }) + }) + }).catch((err) => { + resolve() + }) + }) + + panelStats.then(() => { + nodeStats.then(() => { + nodes.sort(function (a, b) { return a.id - b.id }) + postStatus(client, panel, nodes) + }) + }) +} \ No newline at end of file diff --git a/handlers/postStatus.js b/handlers/postStatus.js new file mode 100644 index 0000000..7c57495 --- /dev/null +++ b/handlers/postStatus.js @@ -0,0 +1,176 @@ +const { EmbedBuilder, time, ActionRowBuilder, ButtonBuilder, ButtonStyle, AttachmentBuilder } = require('discord.js') +const chalk = require('chalk') + +module.exports = async function postStatus(client, panel, nodes) { + + if (!client.config.nodes_resource) client.config.nodes_resource = client.config.resource + if (!client.config.nodes_resource.blacklist) client.config.nodes_resource.blacklist = [] + if (!Array.isArray(client.config.nodes_resource.blacklist) && Number.isInteger(client.config.nodes_resource.blacklist)) client.config.nodes_resource.blacklist = [client.config.nodes_resource.blacklist] + if (!client.config.message.attachment) client.config.message.attachment = client.config.message.image + + if (client.guilds.cache.size < 1) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! This bot is not on any discord servers')) + + const channel = await client.channels.cache.get(client.config.channel) + + if (!channel) return console.log(chalk.cyan('[PteroStats] ') + chalk.red('Err! Invalid Channel ID')) + + let messages = await channel.messages.fetch({ limit: 10 }) + messages = messages.filter(m => m.author.id === client.user.id).last(); + if (messages && messages.embeds.length < 1) { + messages.delete() + messages = null + } + + const embed = new EmbedBuilder() + + let text = '' + let desc = '' + let blacklist = 0 + + let content = null + const files = [] + + if (client.config.message.content) content = client.config.message.content + if (client.config.message.attachment) files.push(new AttachmentBuilder(client.config.message.attachment)) + + if (client.config.embed.title) embed.setTitle(client.config.embed.title) + if (client.config.embed.description) desc = client.config.embed.description + '\n' + if (client.config.embed.color) embed.setColor(client.config.embed.color) + if (client.config.embed.footer) embed.setFooter({ text: client.config.embed.footer }) + if (client.config.embed.thumbnail) embed.setThumbnail(client.config.embed.thumbnail) + if (client.config.embed.image) embed.setImage(client.config.embed.image) + + panel.total_users = panel.total_users.toLocaleString() + panel.total_servers = panel.total_servers.toLocaleString() + + const stats = new Promise((resolve, reject) => { + if (nodes.length !== 0) { + nodes.forEach((data, i) => { + if (!client.config.nodes_resource.blacklist.includes(data.id)) { + const title = data.name + ': ' + String(data.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) + let description = '```' + switch (client.config.nodes_resource.unit.toLowerCase()) { + case 'tb': + description = description + + '\nMemory : ' + Math.floor(data.memory_min / (1024 * 1000)).toLocaleString() + ' TB / ' + Math.floor(data.memory_max / (1024 * 1000)).toLocaleString() + ' TB' + + '\nDisk : ' + Math.floor(data.disk_min / (1024 * 1000)).toLocaleString() + ' TB / ' + Math.floor(data.disk_max / (1024 * 1000)).toLocaleString() + ' TB' + break; + case 'gb': + description = description + + '\nMemory : ' + Math.floor(data.memory_min / 1024).toLocaleString() + ' GB / ' + Math.floor(data.memory_max / 1024).toLocaleString() + ' GB' + + '\nDisk : ' + Math.floor(data.disk_min / 1024).toLocaleString() + ' GB / ' + Math.floor(data.disk_max / 1024).toLocaleString() + ' GB' + break; + case 'percent': + description = description + + '\nMemory : ' + Math.floor(data.memory_min / data.memory_max * 100) + ' %' + + '\nDisk : ' + Math.floor(data.disk_min / data.disk_max * 100) + ' %' + break; + default: + description = description + + '\nMemory : ' + data.memory_min.toLocaleString() + ' MB / ' + data.memory_max.toLocaleString() + ' MB' + + '\nDisk : ' + data.disk_min.toLocaleString() + ' MB / ' + data.disk_max.toLocaleString() + ' MB' + } + + if (client.config.nodes_resource.servers) description = description + '\nServers : ' + data.total_servers.toLocaleString() + if (client.config.nodes_resource.location) description = description + '\nLocation : ' + data.location + if (client.config.nodes_resource.allocations) description = description + '\nAllocations : ' + data.allocations.toLocaleString() + + description = description + '\n```' + + if (client.config.nodes_resource.enable) { + text = text + '\n**' + title.replace(':', ':**') + '\n' + description + } else { + text = text + '\n**' + title.replace(':', ':**') + } + } else { + blacklist = blacklist + 1 + if (nodes.length - client.config.nodes_resource.blacklist.length < 1) text = '\nThere is no nodes to display' + } + + if (i + 1 === nodes.length) resolve() + }) + } else if (nodes.length < 1) { + if (!messages) { + text = '\nThere is no nodes to display' + resolve() + } else { + text = messages.embeds[0].description.replaceAll(client.config.status.online, client.config.status.offline) + if (!panel.status && String(String(messages.embeds[0].fields[0].value).split('\n')[2]).split('')[String(String(messages.embeds[0].fields[0].value).split('\n')[2]).length - 1] !== '`') { + panel.total_users = String(String(messages.embeds[0].fields[0].value).split('\n')[2]).split('')[String(String(messages.embeds[0].fields[0].value).split('\n')[2]).length - 1] + panel.total_servers = String(String(messages.embeds[0].fields[0].value).split('\n')[3]).split('')[String(String(messages.embeds[0].fields[0].value).split('\n')[3]).length - 1] + } + resolve() + } + } + }) + + stats.then(async () => { + const format = await time(new Date(Date.now() + client.config.refresh * 1000), 'R') + embed.setDescription(desc.replaceAll('{{time}}', format) + '\n**Nodes Stats [' + Math.floor(nodes.length - blacklist) + ']**' + text) + const EmbedFields = [] + + if (client.config.panel_resource.enable) { + let stats = '**Status:** ' + String(panel.status).replace('true', client.config.status.online).replace('false', client.config.status.offline) + '\n\n' + + if (client.config.panel_resource.users) stats = stats + 'Users: ' + String(panel.total_users).replace('-1', '`Unknown`') + '\n' + if (client.config.panel_resource.servers) stats = stats + 'Servers: ' + String(panel.total_servers).replace('-1', '`Unknown`') + + EmbedFields.push({ name: 'Panel Stats', value: stats }) + } + + if (client.config.embed.field.enable) EmbedFields.push({ name: client.config.embed.field.title, value: client.config.embed.field.description.replaceAll('{{time}}', format) }) + if (client.config.embed.timestamp) embed.setTimestamp() + if (EmbedFields.length > 0) embed.addFields(EmbedFields) + + const row = [] + + if (client.config.button.enable) { + const button = new ActionRowBuilder + if (client.config.button.btn1.label.length !== 0 && client.config.button.btn1.url.length !== 0) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn1.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn1.url) + ) + } + if (client.config.button.btn2.label.length !== 0 && client.config.button.btn2.url.length !== 0) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn2.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn2.url) + ) + } + if (client.config.button.btn3.label.length !== 0 && client.config.button.btn3.url.length !== 0) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn3.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn3.url) + ) + } + if (client.config.button.btn4.label.length !== 0 && client.config.button.btn4.url.length !== 0) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn4.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn4.url) + ) + } + if (client.config.button.btn5.label.length !== 0 && client.config.button.btn5.url.length !== 0) { + button.addComponents( + new ButtonBuilder() + .setLabel(client.config.button.btn5.label) + .setStyle(ButtonStyle.Link) + .setURL(client.config.button.btn5.url) + ) + } + row.push(button) + } + + if (!messages) channel.send({ content: content, embeds: [embed], components: row, files: files }) + else messages.edit({ content: content, embeds: [embed], components: row, files: files }) + console.log(chalk.cyan('[PteroStats] ') + chalk.green('Stats posted!')) + }) +} \ No newline at end of file diff --git a/index.js b/index.js index 9172a9c..9098250 100644 --- a/index.js +++ b/index.js @@ -1,19 +1,52 @@ -const { Client, Collection } = require('discord.js') -const fs = require('fs') -const client = new Client() -const yaml = require('js-yaml') -const chalk = require('chalk') -const config = yaml.load(fs.readFileSync('./config.yml', 'utf8')) +const fs = require('fs'); +const child = require('child_process'); + +if (Number(process.version.split('.')[0]) < 16) { + console.log('Invalid NodeJS Version!, Please use NodeJS 16.x or upper') + process.exit() +} +if (fs.existsSync('./node_modules')) { + const check = require('./node_modules/discord.js/package.json') + if (Number(check.version.split('.')[0]) !== 14) { + console.log('Invalid Discord.JS Version!, Please use Discord.JS 14.x') + process.exit() + } +} else { + console.log('You didn\'t install the required node packages first!') + console.log('Please wait... starting to install all required node packages using child process') + try { + child.execSync('npm i') + console.log('Install complete!, please run "node index" command again!') + process.exit() + } catch (err) { + console.log('Err! ', err) + console.log('Support Server: https://discord.gg/zv6maQRah3') + process.exit() + } +} + +const chalk = require('chalk'); +const yaml = require('js-yaml'); +const { Client, GatewayIntentBits } = require('discord.js'); +const client = new Client({ intents: [GatewayIntentBits.Guilds] }); + +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)) - }) -}) +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, (...args) => event.execute(...args)); + } else { + client.on(event.name, (...args) => event.execute(...args)); + } +} + +if (client.config.token.startsWith('Put') || client.config.token.length < 1) { + console.log(chalk.cyan('[PteroStats]') + chalk.red(' Err! Invalid Discord Bot Token')) + process.exit() +} + +client.login(config.token); \ No newline at end of file diff --git a/package.json b/package.json index d5c17cc..36fbe00 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,24 @@ { - "name": "pterostats", - "version": "v1.5.5", - "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": "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" - }, - "engines": { - "node": "^12.x" - } -} + "name": "pterostats", + "version": "v3.0.0", + "description": "PteroStats is a bot designed to check Pterodactyl Panel and Nodes status and post it to your discord server", + "license": "MIT", + "repository": "HirziDevs/PteroStats", + "homepage": "https://pterostats.hirzidevs.net", + "bugs": { + "email": "hirzidevs@gmail.com", + "url": "https://github.com/HirziDevs/PteroStats" + }, + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "axios": "^1.1.3", + "chalk": "4.1.1", + "discord.js": "^14.6.0", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": "^16.x" + } +} \ No newline at end of file