├── .gitignore ├── LICENSE ├── README.md ├── admin ├── dashboard.js ├── public │ ├── botProfile.css │ ├── commands.css │ ├── config.css │ ├── errors.css │ ├── guilds.css │ └── style.css └── views │ ├── botprofile.html │ ├── commands.html │ ├── config.html │ ├── errors.html │ ├── guilds.html │ └── index.html ├── cli.js ├── config.json ├── contributing.md ├── setup.js └── src ├── commands └── Community │ └── ping.js ├── events └── handlers │ ├── guildJoinLogs.js │ ├── guildLeaveLogs.js │ ├── interactionCreate.js │ ├── prefixCreate.js │ └── ready.js ├── functions └── handlers │ ├── antiCrash.js │ ├── functionHandler.js │ ├── handelEvents.js │ ├── handleCommands.js │ ├── prefixHandler.js │ ├── requiredIntents.js │ ├── similarity.js │ └── watchFolders.js ├── index.js └── messages └── Community └── ping.js /.gitignore: -------------------------------------------------------------------------------- 1 | package.json 2 | package-lock.json 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2024 Ethical Programmer 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Logo](https://i.ibb.co/HDKDpny/Add-a-heading-1.png) 3 | 4 |
5 | 6 | ![Discord](https://img.shields.io/discord/1188398653530984539?logo=discord&logoColor=%23fff&logoSize=auto&label=Discord&labelColor=%23505050&color=%235E6AE9&link=https%3A%2F%2Fdiscord.gg%2Fethical-programmer-s-1188398653530984539) ![NPM Version](https://img.shields.io/npm/v/create-discobase?logo=npm&label=npm&labelColor=%235C5C5C&color=%23F58142) ![NPM License](https://img.shields.io/npm/l/create-discobase) ![NPM Downloads](https://img.shields.io/npm/dw/create-discobase) 7 | 8 |
9 | 10 | 11 | 12 | # discoBase 13 | 14 | **discoBase** is a lightweight command and event handler package for Discord bots, enabling easy management of commands and events with a structured file organization and dynamic loading system. It allows you to build almost any advanced Discord bot effortlessly. 15 | 16 | ``` 17 | ✨ Supports Latest Discord.js v14. 18 | ``` 19 | 20 | ## 🎉 New Update (v2.0.0) 🎉 21 | - 🚀 Enhanced Terminal Look: Experience a cleaner and more modern terminal display with colorful logs for success, errors, and info messages. 22 | - 📝 Auto-Generated Files: Commands, events, and prefix commands now come with pre-built templates to save you time. 23 | - ⚡ Optimized File Watching: Automatic detection and template insertion for new command, event, and prefix files. 24 | - 🔍 **Error Logging:** Errors encountered during runtime are automatically logged into an `errors` folder for easier debugging. 25 | - 📊 **Discobase Dashboard:** A comprehensive dashboard running on localhost allows you to: 26 | - View statistics such as bot guilds, command counts, and user counts. 27 | - Monitor recent activities (e.g., command creation, deletion, and changes). 28 | - Manage bot settings like banner, avatar, and name directly from the dashboard. 29 | 30 | 31 | ## Features 32 | 33 | - 🎉 Command Handler 34 | - 📅 Event Handler 35 | - ⚙️ Advanced Customization 36 | - 🚀 Asynchronous Support 37 | - 🔄 Dynamic Reloading 38 | - 🛠️ Modular Structure 39 | - 🛡 Never Crash 40 | - 🌐 Compatibility with Advanced Discord Bots 41 | - 🔤 Prefix Commands Support 42 | - ➗ Slash Commands Support 43 | - 🔔 Automatic Detection of Missing Intents 44 | - ⚙️ **Configurable Function Execution:** Allows for setting properties such as `once`, `interval`, `retryAttempts`, `maxExecution`, and `initializer` in your functions to control execution patterns. Ideal for scheduling tasks or retrying operations with ease. 45 | - 🗂️ **Error Logging:** Automatic logging of runtime errors into an `errors` folder. 46 | - 📊 **Discobase Dashboard:** View and manage your bot's statistics and settings easily. 47 | - 🔧 **Discobase Generate Command:** Generate new commands and events with ease. For example: 48 | run this in your terminal after setuping discobase! 49 | ```bash 50 | npm run generate 51 | ``` 52 | 53 | ## Installation 54 | 55 | To create a new **discoBase** project, run the following commands: 56 | 57 | 58 | ```bash 59 | npx create-discobase@latest my-project 60 | ``` 61 | 62 | You can also create a new project in the current directory without specifying a project name: 63 | 64 | ```bash 65 | npx create-discobase@latest 66 | ``` 67 | This will generate a new **discoBase** project in the current directory. 68 | 69 | ## Useful Addon 70 | - [Discobase](https://www.npmjs.com/package/discobase) 71 | 72 | 73 | ## Configuration 74 | 75 | To run this project, you will need to provide the necessary values in the config.json file located in the root directory. The structure of the file is as follows: 76 | 77 | 78 | | Parameter | Type | Description | 79 | | :------------------------------| :------- | :----------------------------------------------------------- | 80 | | `bot.token` | `string` | **Required**. Your Discord bot token | 81 | | `bot.id` | `string` | **Required**. The ID of your Discord bot | 82 | | `bot.admins` | `array` | **Optional**. List of admin user IDs | 83 | | `bot.ownerId` | `string` | **Optional**. The owner's user ID | 84 | | `bot.developerCommandsServerIds`| `array` | **Optional**. Server IDs where developer commands are enabled | 85 | | `database.mongodbUrl` | `string` | **Optional**. MongoDB connection URL | 86 | | `logging.guildJoinLogsId` | `string` | **Optional**. Channel ID for guild join logs | 87 | | `logging.guildLeaveLogsId` | `string` | **Optional**. Channel ID for guild leave logs | 88 | | `logging.commandLogsChannelId` | `string` | **Optional**. Channel ID for command logs | 89 | | `logging.errorLogs` | `string` | **Optional**. Webhook URL for error logging | 90 | | `prefix.value` | `string` | **Optional**. Command prefix for non-slash commands | 91 | 92 | 93 | 94 | ## Command Options 95 | 96 | | Option | Type | Description | 97 | | :------------------ | :---------- | :--------------------------------------------------------------------------------------------------- | 98 | | `ownerOnly` | `boolean` | **Optional**. If `true`, the command can only be run by the bot owner. | 99 | | `adminOnly` | `boolean` | **Optional**. If `true`, the command can only be used by bot admins specified in the config file. | 100 | | `devOnly` | `boolean` | **Optional**. If `true`, the command is only registered/run in specific developer servers. | 101 | | `botPermissions` | `array` | **Optional**. List of permissions the bot needs to execute the command (e.g., `'SendMessages'`, `'ManageChannels'`). | 102 | | `userPermissions` | `array` | **Optional**. List of permissions the user needs to execute the command (e.g., `'Administrator'`, `'KickMembers'`). | 103 | | `cooldown` | `number` | **Optional**. The cooldown time in seconds before the command can be reused. Default is 3 seconds. | 104 | 105 | 106 | ## Function Options 107 | | Property | Type | Description | 108 | |------------------|------------|------------------------------------------------------------------------------------------------------| 109 | | `once` | `boolean` | If `true`, the function will only execute once. If `false`, it can be executed repeatedly. | 110 | | `interval` | `number` | The time interval (in milliseconds) between repeated executions of the function. | 111 | | `retryAttempts` | `number` | Specifies the number of retry attempts if the function fails during execution. | 112 | | `maxExecution` | `number` | Defines the maximum number of times the function can execute. | 113 | | `initializer` | `number` | Initial value or state to use when starting the function; can be used for setup or as a counter. | 114 | 115 | ```js 116 | const exampleFunction = async () => { 117 | console.log("Function executed successfully."); 118 | }; 119 | 120 | exampleFunction.config = { 121 | once: true, 122 | interval: 10000, 123 | retryAttempts: 3, 124 | maxExecution: 5, 125 | initializer: 10 126 | }; 127 | 128 | module.exports = exampleFunction; 129 | 130 | ``` 131 | 132 | 133 | ## Contributing 134 | 135 | Contributions are always welcome! 136 | 137 | See `contributing.md` for ways to get started. 138 | 139 | Please adhere to this project's `code of conduct`. 140 | 141 | 142 | ## Show your support 143 | 144 | Give a ⭐️ if this project helped you! 145 | 146 | 147 | 148 | 149 | 150 | ## Feedback & Suggestion 151 | 152 | If you have any feedback or suggestion, please reach out to us at [Discord Community](https://discord.gg/ethical-programmer-s-1188398653530984539) 153 | 154 | 155 | ## Support 156 | 157 | For support & questions, join our Discord server: [Discord Community](https://discord.gg/ethical-programmer-s-1188398653530984539). -------------------------------------------------------------------------------- /admin/dashboard.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const app = express(); 6 | const port = 3000; 7 | const client = require('../src/index') 8 | const multer = require('multer'); 9 | const upload = multer(); 10 | const { getActivities } = require('../src/functions/handlers/handleCommands'); 11 | 12 | app.use(bodyParser.urlencoded({ extended: true })); 13 | app.use(express.static(path.join(__dirname, 'public'))); 14 | app.use(upload.fields([{ name: 'new-bot-avatar' }, { name: 'new-bot-banner' }])); 15 | 16 | 17 | 18 | function loadConfig() { 19 | const configPath = path.join(__dirname, '../config.json'); 20 | if (fs.existsSync(configPath)) { 21 | return JSON.parse(fs.readFileSync(configPath, 'utf-8')); 22 | } 23 | return {}; 24 | } 25 | 26 | app.get('/', (req, res) => { 27 | res.sendFile(path.join(__dirname, 'views', 'index.html')); 28 | }); 29 | 30 | app.get('/bot', (req, res) => { 31 | res.sendFile(path.join(__dirname, 'views', 'botprofile.html')); 32 | }); 33 | 34 | app.get('/config', (req, res) => { 35 | res.sendFile(path.join(__dirname, 'views', 'config.html')); 36 | }); 37 | 38 | app.get('/commands', (req, res) => { 39 | res.sendFile(path.join(__dirname, 'views', 'commands.html')); 40 | }); 41 | 42 | app.get('/errors', (req, res) => { 43 | res.sendFile(path.join(__dirname, 'views', 'errors.html')); 44 | }); 45 | 46 | app.get('/guilds', (req, res) => { 47 | res.sendFile(path.join(__dirname, 'views', 'guilds.html')); 48 | }); 49 | 50 | app.get('/api/bot-info', async (req, res) => { 51 | try { 52 | 53 | const fetchedUser = await client.users.fetch(client.user.id, { force: true }); 54 | const botStatus = client.presence.status; 55 | const botName = client.user.username; 56 | const botAvatar = `https://cdn.discordapp.com/avatars/${client.user.id}/${client.user.avatar}.png`; 57 | 58 | const botId = client.user.id; 59 | const botBanner = `https://cdn.discordapp.com/banners/${client.user.id}/${fetchedUser.banner}.png`; 60 | 61 | const isVerified = client.user.verified; 62 | res.json({ 63 | botStatus, 64 | botName, 65 | botId, 66 | botBanner, 67 | botAvatar, 68 | isVerified 69 | }) 70 | } catch (err) { 71 | console.error('Error fetching bot info:', err); 72 | res.status(500).json({ error: 'Internal Server Error' }); 73 | } 74 | }) 75 | 76 | app.post('/api/update-bot', async (req, res) => { 77 | const { newBotName } = req.body; 78 | 79 | if (newBotName) { 80 | try { 81 | await client.user.setUsername(newBotName); 82 | } catch (error) { 83 | console.error('Error updating bot name:', error); 84 | return res.json({ success: false, message: 'Failed to update bot name' }); 85 | } 86 | } 87 | 88 | // Update bot avatar if provided 89 | const avatarFile = req.files['new-bot-avatar']?.[0]; 90 | if (avatarFile) { 91 | try { 92 | await client.user.setAvatar(avatarFile.buffer); // Use buffer for the image 93 | } catch (error) { 94 | console.error('Error updating bot avatar:', error); 95 | return res.json({ success: false, message: 'Failed to update bot avatar' }); 96 | } 97 | } 98 | 99 | const bannerFile = req.files['new-bot-banner']?.[0]; 100 | if (bannerFile) { 101 | try { 102 | await client.user.setBanner(bannerFile.buffer); 103 | } catch (error) { 104 | console.error('Error updating bot banner:', error); 105 | return res.json({ success: false, message: 'Failed to update bot banner' }); 106 | } 107 | } 108 | 109 | return res.json({ success: true }); 110 | }); 111 | 112 | app.get('/api/guilds', async (req, res) => { 113 | const guildsData = client.guilds.cache.map(guild => ({ 114 | id: guild.id, 115 | name: guild.name, 116 | icon: guild.iconURL(), 117 | memberCount: guild.memberCount, 118 | })); 119 | 120 | res.json(guildsData); 121 | }); 122 | 123 | app.get('/api/bot-stats', async (req, res) => { 124 | try { 125 | const totalServers = client.guilds.cache.size; 126 | const totalCommands = (client.commands ? client.commands.size : 0) + (client.prefix ? client.prefix.size : 0); 127 | const botName = client.user.username; 128 | const botIcon = `https://cdn.discordapp.com/avatars/${client.user.id}/${client.user.avatar}.png`; 129 | let totalUsers = 0; 130 | client.guilds.cache.forEach(guild => { 131 | totalUsers += guild.memberCount; 132 | }); 133 | 134 | res.json({ 135 | totalServers, 136 | totalUsers, 137 | totalCommands, 138 | botName, 139 | botIcon 140 | }); 141 | } catch (err) { 142 | console.error('Error fetching bot stats:', err); 143 | res.status(500).json({ error: 'Internal Server Error' }); 144 | } 145 | }); 146 | 147 | app.post('/update-config', (req, res) => { 148 | // Load the existing config 149 | const currentConfig = loadConfig(); 150 | 151 | // Update only the fields that are provided 152 | const newConfig = { 153 | bot: { 154 | ...currentConfig.bot, // Keep existing values 155 | ...(req.body.token && { token: req.body.token }), // Update token if provided 156 | ...(req.body.id && { id: req.body.id }), // Update id if provided 157 | ...(req.body.admins && { admins: req.body.admins.split(',') }), // Update admins if provided 158 | ...(req.body.ownerId && { ownerId: req.body.ownerId }), // Update ownerId if provided 159 | ...(req.body.developerCommandsServerIds && { 160 | developerCommandsServerIds: req.body.developerCommandsServerIds.split(','), 161 | }), // Update developerCommandsServerIds if provided 162 | }, 163 | database: { 164 | ...(currentConfig.database || {}), 165 | ...(req.body.mongodbUrl && { mongodbUrl: req.body.mongodbUrl }), 166 | }, 167 | logging: { 168 | ...(currentConfig.logging || {}), 169 | ...(req.body.guildJoinLogsId && { guildJoinLogsId: req.body.guildJoinLogsId }), 170 | ...(req.body.guildLeaveLogsId && { guildLeaveLogsId: req.body.guildLeaveLogsId }), 171 | ...(req.body.commandLogsChannelId && { commandLogsChannelId: req.body.commandLogsChannelId }), 172 | ...(req.body.errorLogs && { errorLogs: req.body.errorLogs }), 173 | }, 174 | prefix: { 175 | ...(currentConfig.prefix || {}), 176 | ...(req.body.prefix && { value: req.body.prefix }), 177 | }, 178 | }; 179 | 180 | fs.writeFileSync(path.join(__dirname, '../config.json'), JSON.stringify(newConfig, null, 2)); 181 | res.redirect('/config?success=true'); 182 | }); 183 | 184 | app.get('/api/commands', (req, res) => { 185 | const slashCommandsDir = path.join(__dirname, '../src/commands'); 186 | const prefixCommandsDir = path.join(__dirname, '../src/messages'); 187 | const commands = { 188 | slash: [], 189 | prefix: [] 190 | }; 191 | 192 | // A function to read commands from a directory 193 | function readCommands(dir, commandArray, type) { 194 | return new Promise((resolve, reject) => { 195 | fs.readdir(dir, (err, files) => { 196 | if (err) { 197 | return reject(`Error reading ${type} commands`); 198 | } 199 | 200 | const promises = files.map(file => { 201 | const filePath = path.join(dir, file); 202 | return new Promise((resolveFile, rejectFile) => { 203 | fs.stat(filePath, (err, stats) => { 204 | if (err) { 205 | return rejectFile('Error reading file stats'); 206 | } 207 | 208 | if (stats.isDirectory()) { 209 | readCommands(filePath, commandArray, type).then(resolveFile).catch(rejectFile); 210 | } else if (path.extname(file) === '.js') { 211 | const command = require(filePath); 212 | if (type === 'slash') { 213 | commandArray.push({ 214 | name: command.data.name, 215 | description: command.data.description 216 | }); 217 | } else { 218 | commandArray.push({ 219 | name: command.name, 220 | description: command.description 221 | }); 222 | } 223 | resolveFile(); 224 | } else { 225 | resolveFile(); // Handle non-JS files gracefully 226 | } 227 | }); 228 | }); 229 | }); 230 | 231 | // Wait for all promises to resolve 232 | Promise.all(promises).then(resolve).catch(reject); 233 | }); 234 | }); 235 | } 236 | 237 | // Read slash commands and then prefix commands 238 | readCommands(slashCommandsDir, commands.slash, 'slash') 239 | .then(() => readCommands(prefixCommandsDir, commands.prefix, 'prefix')) 240 | .then(() => { 241 | res.json(commands); // Send response after both commands are read 242 | }) 243 | .catch(err => { 244 | console.error(err); 245 | res.status(500).json({ error: err }); 246 | }); 247 | }); 248 | 249 | app.get('/api/activities', (req, res) => { 250 | const activities = getActivities(); 251 | res.json(activities); 252 | }); 253 | 254 | app.get('/api/errors', (req, res) => { 255 | const errorsDir = path.join(__dirname, '../errors'); // Path to the errors folder 256 | 257 | // Ensure that the folder exists 258 | if (!fs.existsSync(errorsDir)) { 259 | return res.json({ errors: [], message: 'No errors found' }); 260 | } 261 | 262 | // Read the files from the errors folder 263 | fs.readdir(errorsDir, (err, files) => { 264 | if (err) { 265 | return res.status(500).json({ error: 'Unable to read error logs' }); 266 | } 267 | 268 | // Sort files by date (latest first) 269 | files.sort((a, b) => fs.statSync(path.join(errorsDir, b)).mtime - fs.statSync(path.join(errorsDir, a)).mtime); 270 | 271 | // Prepare an array to store errors 272 | const errorLogs = []; 273 | 274 | // Read the content of each error file 275 | files.forEach(file => { 276 | const filePath = path.join(errorsDir, file); 277 | const content = fs.readFileSync(filePath, 'utf8'); 278 | errorLogs.push({ fileName: file, content }); 279 | }); 280 | 281 | // Send the error logs to the front-end 282 | res.json({ errors: errorLogs }); 283 | }); 284 | }); 285 | 286 | // Start the server 287 | app.listen(port, () => { 288 | console.log(`Admin dashboard running at http://localhost:${port}`); 289 | }); 290 | -------------------------------------------------------------------------------- /admin/public/botProfile.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #a69cec; 3 | --secondary-color: #00cec9; 4 | --background-color: #2d3436; 5 | --card-background: rgba(255, 255, 255, 0.1); 6 | --text-color: #ffffff; 7 | --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 8 | --success-color: #2ecc71; 9 | --error-color: #e74c3c; 10 | } 11 | 12 | * { 13 | margin: 0; 14 | padding: 0; 15 | box-sizing: border-box; 16 | } 17 | 18 | body { 19 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 20 | background: linear-gradient(135deg, var(--background-color), #4834d4); 21 | color: var(--text-color); 22 | line-height: 1.6; 23 | min-height: 100vh; 24 | } 25 | 26 | .navbar { 27 | background-color: rgba(255, 255, 255, 0.1); 28 | backdrop-filter: blur(10px); 29 | padding: 1rem 2rem; 30 | display: flex; 31 | justify-content: space-between; 32 | align-items: center; 33 | position: fixed; 34 | width: 100%; 35 | top: 0; 36 | z-index: 1000; 37 | } 38 | 39 | .logo { 40 | font-size: 1.5rem; 41 | font-weight: bold; 42 | color: var(--text-color); 43 | } 44 | 45 | nav ul { 46 | list-style-type: none; 47 | display: flex; 48 | gap: 2rem; 49 | } 50 | 51 | .nav-link { 52 | color: var(--text-color); 53 | text-decoration: none; 54 | font-weight: bold; 55 | transition: color 0.3s ease, border-bottom 0.3s ease; 56 | padding-bottom: 0.25rem; 57 | } 58 | 59 | .nav-link:hover { 60 | color: var(--secondary-color); 61 | border-bottom: 2px solid var(--secondary-color); 62 | } 63 | 64 | .container { 65 | max-width: 1200px; 66 | margin: 0 auto; 67 | padding: 6rem 2rem 2rem; 68 | } 69 | 70 | .title { 71 | text-align: center; 72 | margin-bottom: 2rem; 73 | font-size: 2.5rem; 74 | color: var(--secondary-color); 75 | } 76 | 77 | .bot-info { 78 | background-color: var(--card-background); 79 | border-radius: 10px; 80 | padding: 2rem; 81 | box-shadow: var(--card-shadow); 82 | margin-bottom: 2rem; 83 | animation: fadeInUp 0.5s ease; 84 | } 85 | 86 | .bot-header { 87 | display: flex; 88 | align-items: center; 89 | margin-bottom: 1rem; 90 | } 91 | 92 | .bot-avatar { 93 | width: 100px; 94 | height: 100px; 95 | border-radius: 50%; 96 | object-fit: cover; 97 | margin-right: 1rem; 98 | border: 3px solid var(--primary-color); 99 | } 100 | 101 | .bot-name-status { 102 | flex-grow: 1; 103 | } 104 | 105 | .bot-name { 106 | font-size: 1.5rem; 107 | color: var(--secondary-color); 108 | margin-bottom: 0.5rem; 109 | } 110 | 111 | .bot-banner { 112 | width: 100%; 113 | height: 200px; 114 | object-fit: cover; 115 | border-radius: 10px; 116 | margin-bottom: 1rem; 117 | } 118 | 119 | .bot-details { 120 | display: grid; 121 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 122 | gap: 1rem; 123 | } 124 | 125 | .bot-detail { 126 | background-color: rgba(255, 255, 255, 0.05); 127 | padding: 1rem; 128 | border-radius: 5px; 129 | } 130 | 131 | .bot-detail h3 { 132 | color: var(--secondary-color); 133 | margin-bottom: 0.5rem; 134 | } 135 | 136 | .bot-status { 137 | display: inline-block; 138 | padding: 0.25rem 0.5rem; 139 | border-radius: 20px; 140 | font-size: 0.9rem; 141 | font-weight: bold; 142 | } 143 | 144 | .status-online { 145 | background-color: var(--success-color); 146 | } 147 | 148 | .status-offline { 149 | background-color: var(--error-color); 150 | } 151 | 152 | .bot-actions { 153 | margin-top: 2rem; 154 | } 155 | 156 | .bot-actions h3 { 157 | color: var(--secondary-color); 158 | margin-bottom: 1rem; 159 | } 160 | 161 | .form-group { 162 | margin-bottom: 1rem; 163 | } 164 | 165 | .bot-button { 166 | display: flex; 167 | align-items: center; 168 | margin-left: 2rem; 169 | /* Adjust spacing as needed */ 170 | } 171 | 172 | .bot-button .nav-link { 173 | display: flex; 174 | align-items: center; 175 | background-color: var(--secondary-color); 176 | padding: 0.5rem 1rem; 177 | border-radius: 20px; 178 | color: var(--text-color); 179 | transition: background-color 0.3s ease; 180 | } 181 | 182 | .bot-button .nav-link:hover { 183 | background-color: #009999; 184 | /* Darker shade for hover effect */ 185 | } 186 | 187 | .bot-icon { 188 | width: 40px; 189 | height: 40px; 190 | border-radius: 50%; 191 | overflow: hidden; 192 | margin-right: 0.5rem; 193 | } 194 | 195 | .bot-icon img { 196 | width: 100%; 197 | height: 100%; 198 | object-fit: cover; 199 | } 200 | 201 | .bot-name { 202 | font-weight: bold; 203 | } 204 | 205 | 206 | label { 207 | display: block; 208 | margin-bottom: 0.5rem; 209 | color: var(--secondary-color); 210 | } 211 | 212 | input[type="text"], 213 | input[type="file"] { 214 | width: 100%; 215 | padding: 0.5rem; 216 | border: none; 217 | background-color: rgba(255, 255, 255, 0.1); 218 | color: var(--text-color); 219 | border-radius: 5px; 220 | } 221 | 222 | button { 223 | background-color: var(--primary-color); 224 | color: var(--text-color); 225 | border: none; 226 | padding: 0.5rem 1rem; 227 | border-radius: 5px; 228 | cursor: pointer; 229 | transition: background-color 0.3s ease; 230 | } 231 | 232 | button:hover { 233 | background-color: var(--secondary-color); 234 | } 235 | 236 | @keyframes fadeInUp { 237 | from { 238 | opacity: 0; 239 | transform: translateY(20px); 240 | } 241 | 242 | to { 243 | opacity: 1; 244 | transform: translateY(0); 245 | } 246 | } 247 | 248 | @media (max-width: 768px) { 249 | .container { 250 | padding: 5rem 1rem 1rem; 251 | } 252 | 253 | .bot-details { 254 | grid-template-columns: 1fr; 255 | } 256 | 257 | .bot-header { 258 | flex-direction: column; 259 | text-align: center; 260 | } 261 | 262 | .bot-avatar { 263 | margin-right: 0; 264 | margin-bottom: 1rem; 265 | } 266 | } -------------------------------------------------------------------------------- /admin/public/commands.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #a69cec; 3 | --secondary-color: #00cec9; 4 | --background-color: #2d3436; 5 | --card-background: rgba(255, 255, 255, 0.1); 6 | --text-color: #ffffff; 7 | --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | * { 11 | margin: 0; 12 | padding: 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | body { 17 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 18 | background: linear-gradient(135deg, var(--background-color), #4834d4); 19 | color: var(--text-color); 20 | line-height: 1.6; 21 | min-height: 100vh; 22 | } 23 | 24 | .navbar { 25 | background-color: rgba(255, 255, 255, 0.1); 26 | backdrop-filter: blur(10px); 27 | padding: 1rem 2rem; 28 | display: flex; 29 | justify-content: space-between; 30 | align-items: center; 31 | position: fixed; 32 | width: 100%; 33 | top: 0; 34 | z-index: 1000; 35 | } 36 | 37 | .logo { 38 | font-size: 1.5rem; 39 | font-weight: bold; 40 | color: var(--text-color); 41 | } 42 | 43 | nav ul { 44 | list-style-type: none; 45 | display: flex; 46 | gap: 2rem; 47 | } 48 | 49 | .nav-link { 50 | color: var(--text-color); 51 | text-decoration: none; 52 | font-weight: bold; 53 | transition: color 0.3s ease, border-bottom 0.3s ease; 54 | padding-bottom: 0.25rem; 55 | } 56 | 57 | .nav-link:hover { 58 | color: var(--secondary-color); 59 | border-bottom: 2px solid var(--secondary-color); 60 | } 61 | 62 | .container { 63 | max-width: 1200px; 64 | margin: 0 auto; 65 | padding: 6rem 2rem 2rem; 66 | } 67 | 68 | .title { 69 | text-align: center; 70 | margin-bottom: 2rem; 71 | font-size: 2.5rem; 72 | color: var(--secondary-color); 73 | } 74 | 75 | .commands-section { 76 | margin-bottom: 3rem; 77 | } 78 | 79 | h2 { 80 | color: var(--secondary-color); 81 | margin-bottom: 1.5rem; 82 | font-size: 1.8rem; 83 | } 84 | 85 | .command-cards { 86 | display: grid; 87 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 88 | gap: 1.5rem; 89 | } 90 | 91 | .command-card { 92 | background-color: var(--card-background); 93 | border-radius: 10px; 94 | padding: 1.5rem; 95 | box-shadow: var(--card-shadow); 96 | transition: transform 0.3s ease, box-shadow 0.3s ease; 97 | opacity: 0; 98 | transform: translateY(20px); 99 | animation: fadeInUp 0.5s ease forwards; 100 | } 101 | 102 | .command-card:hover { 103 | transform: translateY(-5px); 104 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); 105 | } 106 | 107 | .bot-button { 108 | display: flex; 109 | align-items: center; 110 | margin-left: 2rem; 111 | /* Adjust spacing as needed */ 112 | } 113 | 114 | .bot-button .nav-link { 115 | display: flex; 116 | align-items: center; 117 | background-color: var(--secondary-color); 118 | padding: 0.5rem 1rem; 119 | border-radius: 20px; 120 | color: var(--text-color); 121 | transition: background-color 0.3s ease; 122 | } 123 | 124 | .bot-button .nav-link:hover { 125 | background-color: #009999; 126 | /* Darker shade for hover effect */ 127 | } 128 | 129 | .bot-icon { 130 | width: 40px; 131 | height: 40px; 132 | border-radius: 50%; 133 | overflow: hidden; 134 | margin-right: 0.5rem; 135 | } 136 | 137 | .bot-icon img { 138 | width: 100%; 139 | height: 100%; 140 | object-fit: cover; 141 | } 142 | 143 | .bot-name { 144 | font-weight: bold; 145 | } 146 | 147 | 148 | .command-card h3 { 149 | color: var(--primary-color); 150 | margin-bottom: 0.5rem; 151 | font-size: 1.2rem; 152 | } 153 | 154 | .command-card p { 155 | font-size: 0.9rem; 156 | color: rgba(255, 255, 255, 0.8); 157 | } 158 | 159 | @keyframes fadeInUp { 160 | to { 161 | opacity: 1; 162 | transform: translateY(0); 163 | } 164 | } 165 | 166 | @media (max-width: 768px) { 167 | .container { 168 | padding: 5rem 1rem 1rem; 169 | } 170 | 171 | .command-cards { 172 | grid-template-columns: 1fr; 173 | } 174 | } -------------------------------------------------------------------------------- /admin/public/config.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #ff6b6b; 3 | --secondary-color: #4ecdc4; 4 | --background-color: #1a1a2e; 5 | --text-color: #ffffff; 6 | --input-background: #2a2a3e; 7 | --input-text: #ffffff; 8 | --button-hover: #ff8787; 9 | } 10 | 11 | 12 | body { 13 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 14 | background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460); 15 | color: var(--text-color); 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | padding: 2rem; 20 | } 21 | 22 | .container { 23 | background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05)); 24 | backdrop-filter: blur(10px); 25 | border-radius: 20px; 26 | padding: 2rem; 27 | width: 100%; 28 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); 29 | } 30 | 31 | h1 { 32 | text-align: center; 33 | margin-bottom: 2rem; 34 | color: var(--secondary-color); 35 | font-size: 2.5rem; 36 | text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); 37 | } 38 | 39 | form { 40 | display: grid; 41 | gap: 2rem; 42 | } 43 | 44 | .form-row { 45 | display: grid; 46 | grid-template-columns: repeat(3, 1fr); 47 | gap: 1.5rem; 48 | } 49 | 50 | .form-group { 51 | display: flex; 52 | flex-direction: column; 53 | } 54 | 55 | label { 56 | margin-bottom: 0.5rem; 57 | font-weight: bold; 58 | color: var(--secondary-color); 59 | } 60 | 61 | input { 62 | padding: 0.75rem; 63 | border: none; 64 | border-radius: 8px; 65 | background-color: var(--input-background); 66 | color: var(--input-text); 67 | font-size: 1rem; 68 | transition: all 0.3s ease; 69 | } 70 | 71 | input:focus { 72 | outline: none; 73 | box-shadow: 0 0 0 2px var(--secondary-color); 74 | } 75 | 76 | .submit-button { 77 | background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); 78 | color: white; 79 | border: none; 80 | padding: 1rem; 81 | border-radius: 8px; 82 | font-size: 1.1rem; 83 | cursor: pointer; 84 | transition: all 0.3s ease; 85 | text-transform: uppercase; 86 | letter-spacing: 1px; 87 | font-weight: bold; 88 | width: 100%; 89 | max-width: 300px; 90 | justify-self: center; 91 | } 92 | 93 | .submit-button:hover { 94 | background: linear-gradient(135deg, var(--button-hover), var(--secondary-color)); 95 | transform: translateY(-2px); 96 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 97 | } 98 | 99 | .form-group-special { 100 | position: relative; 101 | overflow: hidden; 102 | } 103 | 104 | .form-group-special::before { 105 | content: ''; 106 | position: absolute; 107 | top: -50%; 108 | left: -50%; 109 | width: 200%; 110 | height: 200%; 111 | background: conic-gradient(from 0deg, transparent, var(--primary-color), transparent 30%); 112 | animation: rotate 4s linear infinite; 113 | z-index: -1; 114 | } 115 | 116 | .form-group-special label, 117 | .form-group-special input { 118 | position: relative; 119 | z-index: 1; 120 | } 121 | 122 | @keyframes rotate { 123 | 100% { 124 | transform: rotate(360deg); 125 | } 126 | } 127 | 128 | @media (max-width: 768px) { 129 | .container { 130 | padding: 1.5rem; 131 | } 132 | 133 | h1 { 134 | font-size: 2rem; 135 | } 136 | 137 | .form-row { 138 | grid-template-columns: 1fr; 139 | } 140 | } -------------------------------------------------------------------------------- /admin/public/errors.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #6c5ce7; 3 | --secondary-color: #00cec9; 4 | --background-color: #2d3436; 5 | --card-background: rgba(255, 255, 255, 0.1); 6 | --text-color: #ffffff; 7 | --error-color: #ff6b6b; 8 | --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 9 | } 10 | 11 | * { 12 | margin: 0; 13 | padding: 0; 14 | box-sizing: border-box; 15 | } 16 | 17 | body { 18 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 19 | background: linear-gradient(135deg, var(--background-color), #4834d4); 20 | color: var(--text-color); 21 | line-height: 1.6; 22 | min-height: 100vh; 23 | } 24 | 25 | .navbar { 26 | background-color: rgba(255, 255, 255, 0.1); 27 | backdrop-filter: blur(10px); 28 | padding: 1rem 2rem; 29 | display: flex; 30 | justify-content: space-between; 31 | align-items: center; 32 | position: fixed; 33 | width: 100%; 34 | top: 0; 35 | z-index: 1000; 36 | } 37 | 38 | .logo { 39 | font-size: 1.5rem; 40 | font-weight: bold; 41 | color: var(--text-color); 42 | } 43 | 44 | nav ul { 45 | list-style-type: none; 46 | display: flex; 47 | gap: 2rem; 48 | } 49 | 50 | .nav-link { 51 | color: var(--text-color); 52 | text-decoration: none; 53 | font-weight: bold; 54 | transition: color 0.3s ease, border-bottom 0.3s ease; 55 | padding-bottom: 0.25rem; 56 | } 57 | 58 | .nav-link:hover { 59 | color: var(--secondary-color); 60 | border-bottom: 2px solid var(--secondary-color); 61 | } 62 | 63 | .container { 64 | max-width: 1200px; 65 | margin: 0 auto; 66 | padding: 6rem 2rem 2rem; 67 | } 68 | 69 | .title { 70 | text-align: center; 71 | margin-bottom: 2rem; 72 | font-size: 2.5rem; 73 | color: var(--secondary-color); 74 | } 75 | 76 | .error-container { 77 | background-color: var(--card-background); 78 | border-radius: 10px; 79 | padding: 1.5rem; 80 | box-shadow: var(--card-shadow); 81 | margin-bottom: 1.5rem; 82 | transition: transform 0.3s ease, box-shadow 0.3s ease; 83 | opacity: 0; 84 | transform: translateY(20px); 85 | animation: fadeInUp 0.5s ease forwards; 86 | } 87 | 88 | .error-container:hover { 89 | transform: translateY(-5px); 90 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); 91 | } 92 | 93 | .error-container h3 { 94 | color: var(--error-color); 95 | margin-bottom: 1rem; 96 | font-size: 1.2rem; 97 | } 98 | 99 | .bot-button { 100 | display: flex; 101 | align-items: center; 102 | margin-left: 2rem; 103 | /* Adjust spacing as needed */ 104 | } 105 | 106 | .bot-button .nav-link { 107 | display: flex; 108 | align-items: center; 109 | background-color: var(--secondary-color); 110 | padding: 0.5rem 1rem; 111 | border-radius: 20px; 112 | color: var(--text-color); 113 | transition: background-color 0.3s ease; 114 | } 115 | 116 | .bot-button .nav-link:hover { 117 | background-color: #009999; 118 | /* Darker shade for hover effect */ 119 | } 120 | 121 | .bot-icon { 122 | width: 40px; 123 | height: 40px; 124 | border-radius: 50%; 125 | overflow: hidden; 126 | margin-right: 0.5rem; 127 | } 128 | 129 | .bot-icon img { 130 | width: 100%; 131 | height: 100%; 132 | object-fit: cover; 133 | } 134 | 135 | .bot-name { 136 | font-weight: bold; 137 | } 138 | 139 | pre { 140 | background-color: rgba(0, 0, 0, 0.3); 141 | color: #f8f8f2; 142 | padding: 1rem; 143 | border-radius: 5px; 144 | overflow-x: auto; 145 | font-family: 'Courier New', Courier, monospace; 146 | font-size: 0.9rem; 147 | } 148 | 149 | .no-errors { 150 | text-align: center; 151 | font-size: 1.2rem; 152 | color: var(--secondary-color); 153 | } 154 | 155 | @keyframes fadeInUp { 156 | to { 157 | opacity: 1; 158 | transform: translateY(0); 159 | } 160 | } 161 | 162 | @media (max-width: 768px) { 163 | .container { 164 | padding: 5rem 1rem 1rem; 165 | } 166 | } -------------------------------------------------------------------------------- /admin/public/guilds.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #a69cec; 3 | --secondary-color: #00cec9; 4 | --background-color: #2d3436; 5 | --card-background: rgba(255, 255, 255, 0.1); 6 | --text-color: #ffffff; 7 | --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 8 | } 9 | 10 | * { 11 | margin: 0; 12 | padding: 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | body { 17 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 18 | background: linear-gradient(135deg, var(--background-color), #4834d4); 19 | color: var(--text-color); 20 | line-height: 1.6; 21 | min-height: 100vh; 22 | } 23 | 24 | .navbar { 25 | background-color: rgba(255, 255, 255, 0.1); 26 | backdrop-filter: blur(10px); 27 | padding: 1rem 2rem; 28 | display: flex; 29 | justify-content: space-between; 30 | align-items: center; 31 | position: fixed; 32 | height: 70px; 33 | width: 100%; 34 | top: 0; 35 | z-index: 1000; 36 | } 37 | 38 | .logo { 39 | font-size: 1.5rem; 40 | font-weight: bold; 41 | color: var(--text-color); 42 | } 43 | 44 | nav ul { 45 | list-style-type: none; 46 | display: flex; 47 | gap: 2rem; 48 | align-items: center; 49 | } 50 | 51 | .nav-link { 52 | color: var(--text-color); 53 | text-decoration: none; 54 | font-weight: bold; 55 | transition: color 0.3s ease, border-bottom 0.3s ease; 56 | padding-bottom: 0.25rem; 57 | } 58 | 59 | .nav-link:hover { 60 | color: var(--secondary-color); 61 | border-bottom: 2px solid var(--secondary-color); 62 | } 63 | 64 | .container { 65 | max-width: 1200px; 66 | margin: 0 auto; 67 | padding: 6rem 2rem 2rem; 68 | } 69 | 70 | .title { 71 | text-align: center; 72 | margin-bottom: 2rem; 73 | font-size: 2.5rem; 74 | color: var(--secondary-color); 75 | } 76 | 77 | .guilds-grid { 78 | display: grid; 79 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 80 | gap: 1.5rem; 81 | } 82 | 83 | .guild-card { 84 | background-color: var(--card-background); 85 | border-radius: 10px; 86 | padding: 1.5rem; 87 | box-shadow: var(--card-shadow); 88 | transition: transform 0.3s ease, box-shadow 0.3s ease; 89 | opacity: 0; 90 | transform: translateY(20px); 91 | animation: fadeInUp 0.5s ease forwards; 92 | } 93 | 94 | .guild-card:hover { 95 | transform: translateY(-5px); 96 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); 97 | } 98 | 99 | .guild-header { 100 | display: flex; 101 | align-items: center; 102 | margin-bottom: 1rem; 103 | } 104 | 105 | .guild-icon { 106 | width: 64px; 107 | height: 64px; 108 | border-radius: 50%; 109 | margin-right: 1rem; 110 | object-fit: cover; 111 | } 112 | 113 | .guild-name { 114 | font-size: 1.2rem; 115 | font-weight: bold; 116 | color: var(--primary-color); 117 | } 118 | 119 | .guild-members { 120 | display: flex; 121 | align-items: center; 122 | color: var(--secondary-color); 123 | font-size: 1rem; 124 | } 125 | 126 | .guild-members::before { 127 | content: '\1F465'; 128 | /* Unicode for 'busts in silhouette' emoji */ 129 | margin-right: 0.5rem; 130 | font-size: 1.2rem; 131 | } 132 | 133 | @keyframes fadeInUp { 134 | to { 135 | opacity: 1; 136 | transform: translateY(0); 137 | } 138 | } 139 | 140 | @media (max-width: 768px) { 141 | .container { 142 | padding: 5rem 1rem 1rem; 143 | } 144 | 145 | .guilds-grid { 146 | grid-template-columns: 1fr; 147 | } 148 | } -------------------------------------------------------------------------------- /admin/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: 'Arial', sans-serif; 5 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 6 | color: #ffffff; 7 | min-height: 100vh; 8 | display: flex; 9 | width: 96vw; 10 | flex-direction: column; 11 | } 12 | 13 | 14 | .banner { 15 | margin-top: 80px; 16 | text-align: center; 17 | } 18 | 19 | .banner-img { 20 | width: 103.2%; /* Make the banner image responsive */ 21 | max-height: 300px; /* Adjust as needed */ 22 | object-fit: cover; /* Cover the space nicely */ 23 | } 24 | 25 | .container { 26 | max-width: 1200px; 27 | margin: 0 auto; 28 | padding: 2rem; 29 | flex: 1; 30 | } 31 | 32 | .features { 33 | display: grid; 34 | grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 35 | gap: 2rem; 36 | margin-top: 2rem; /* Reduce space above the features */ 37 | } 38 | 39 | .feature-card { 40 | background-color: rgba(255, 255, 255, 0.1); 41 | border-radius: 10px; 42 | padding: 2rem; 43 | text-align: center; 44 | transition: transform 0.3s ease; 45 | } 46 | 47 | .feature-card:hover { 48 | transform: translateY(-5px); 49 | } 50 | 51 | .feature-card h3 { 52 | font-size: 1.5rem; 53 | margin-bottom: 1rem; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /admin/views/botprofile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscoBase - Bot Information 9 | 10 | 11 | 12 | 13 | 28 | 29 |
30 |

Bot Information

31 | 32 |
33 |
34 | Bot Avatar 35 |
36 |

DiscoBot

37 | Online 38 |
39 |
40 | Bot Banner 41 |
42 |
43 |

Bot ID

44 |

123456789

45 |
46 |
47 |

isVerified

48 |

Loading..

49 |
50 |
51 | 52 |
53 |

Update Bot Information

54 |
55 |
56 | 57 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 | 66 |
67 | 68 |
69 |
70 |
71 |
72 | 73 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /admin/views/commands.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DiscoBase - Commands 8 | 9 | 10 | 11 | 12 | 13 | 14 | 30 | 31 |
32 |

Commands

33 | 34 |
35 |

Slash Commands

36 |
37 |
38 | 39 |
40 |

Prefix Commands

41 |
42 |
43 |
44 | 45 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /admin/views/config.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscoBase - Configuration Management 9 | 10 | 11 | 113 | 114 | 115 | 116 | 117 | 134 | 135 |
136 |

Configuration Management

137 |
138 |
139 |
140 | 141 | 142 |
143 | 144 |
145 | 146 | 147 |
148 | 149 |
150 | 151 | 152 |
153 |
154 | 155 |
156 |
157 | 158 | 159 |
160 | 161 |
162 | 163 | 164 |
165 | 166 |
167 | 168 | 169 |
170 |
171 | 172 |
173 |
174 | 175 | 176 |
177 | 178 |
179 | 180 | 181 |
182 | 183 |
184 | 185 | 186 |
187 |
188 | 189 |
190 |
191 | 192 | 193 |
194 | 195 |
196 | 197 | 198 |
199 |
200 | 201 | 202 |
203 |
204 | 205 | 206 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /admin/views/errors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DiscoBase - Error Logs 8 | 9 | 10 | 11 | 12 | 13 | 28 | 29 |
30 |

Error Logs

31 |
32 |
33 | 34 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /admin/views/guilds.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DiscoBase - Guilds 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 |
29 |

Guilds

30 | 31 |
32 | 33 |
34 |
35 | 36 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /admin/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DiscoBase - Dashboard 8 | 9 | 204 | 205 | 206 | 207 | 227 | 228 |
229 |

Dashboard

230 | 231 |
232 |
233 |

Total Servers

234 |

0

235 |
236 |
237 |

Total Users

238 |

0

239 |
240 |
241 |

Total Commands (Prefix & Slash)

242 |

0

243 |
244 |
245 | 246 |
247 |

Recent Activity

248 | 251 |
252 |
253 | 254 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const { select, text, confirm } = require('@clack/prompts'); 5 | const chalk = require('chalk'); 6 | 7 | // Templates for different file types 8 | const templates = { 9 | command: `const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 10 | 11 | module.exports = { 12 | data: new SlashCommandBuilder() 13 | .setName('your-command') 14 | .setDescription('Describe your command here.'), 15 | 16 | async execute(interaction, client) { 17 | // Command execution logic goes here 18 | } 19 | };`, 20 | prefix: `//! This is a basic structure for a prefix command in discoBase using discord.js 21 | 22 | module.exports = { 23 | name: 'command-name', 24 | description: 'command-description.', 25 | aliases: ['alias_1', 'alias_2'], 26 | run: async (client, message, args) => { 27 | // Command execution logic goes here 28 | }, 29 | };`, 30 | event: `module.exports = { 31 | name: 'event-name', 32 | async execute(eventObject, client) { 33 | // Event handling logic goes here 34 | } 35 | };` 36 | }; 37 | 38 | // Logging function with styling 39 | const logWithStyle = (message, type = 'info') => { 40 | const styles = { 41 | success: chalk.green.bold(`✔ ${message}`), 42 | error: chalk.red.bold(`✖ ${message}`), 43 | info: chalk.blueBright.bold(`ℹ ${message}`), 44 | }; 45 | console.log(styles[type] || message); 46 | }; 47 | 48 | // Function to create a file with content 49 | const createFile = (filePath, template) => { 50 | fs.writeFile(filePath, template.trim(), (err) => { 51 | if (err) return logWithStyle(`Error: ${err.message}`, 'error'); 52 | const relativePath = path.relative(path.join(__dirname, 'src'), filePath); 53 | logWithStyle(`File created at ${relativePath}`, 'success'); 54 | }); 55 | }; 56 | 57 | // Main execution of the 'generate' command 58 | (async () => { 59 | // Ask for the file type 60 | const fileType = await select({ 61 | message: 'Select the type of file to generate:', 62 | options: [ 63 | { value: 'command', label: 'Command' }, 64 | { value: 'event', label: 'Event' }, 65 | { value: 'prefix', label: 'Prefix Command' } 66 | ], 67 | }); 68 | 69 | const fileName = await text({ 70 | message: `Enter the name of the ${fileType} file (without extension):`, 71 | initial: '', 72 | }); 73 | 74 | const folderMap = { 75 | command: 'commands', 76 | event: 'events', 77 | prefix: 'messages' 78 | }; 79 | 80 | const folderSelection = folderMap[fileType]; 81 | const selectedFolderPath = path.join(__dirname, 'src', folderSelection); 82 | 83 | // Check if the folder exists, if not, ask to create it 84 | if (!fs.existsSync(selectedFolderPath)) { 85 | const createFolder = await confirm({ 86 | message: `The folder ${folderSelection} does not exist. Do you want to create it?`, 87 | }); 88 | 89 | if (createFolder) { 90 | fs.mkdirSync(selectedFolderPath, { recursive: true }); 91 | logWithStyle(`Folder ${folderSelection} created successfully.`, 'success'); 92 | } else { 93 | logWithStyle('Folder creation aborted.', 'error'); 94 | return; 95 | } 96 | } 97 | 98 | // Get the subfolders within the selected folder 99 | let subFolders = fs.readdirSync(selectedFolderPath).filter(item => fs.statSync(path.join(selectedFolderPath, item)).isDirectory()); 100 | 101 | // If no subfolders, ask if the user wants to create one 102 | if (subFolders.length === 0) { 103 | const createSubfolder = await confirm({ 104 | message: `No subfolders exist in ${folderSelection}. Would you like to create one?`, 105 | }); 106 | 107 | if (createSubfolder) { 108 | const subfolderName = await text({ 109 | message: `Enter the name of the new subfolder:`, 110 | initial: '', 111 | }); 112 | 113 | const newSubfolderPath = path.join(selectedFolderPath, subfolderName); 114 | fs.mkdirSync(newSubfolderPath, { recursive: true }); 115 | logWithStyle(`Subfolder ${subfolderName} created successfully.`, 'success'); 116 | subFolders = [subfolderName]; 117 | } else { 118 | logWithStyle('Subfolder creation aborted.', 'error'); 119 | return; 120 | } 121 | } 122 | 123 | // Let the user select an existing subfolder or create a new one 124 | const subfolderSelection = await select({ 125 | message: 'Select the subfolder to create the file in (or choose to create a new folder):', 126 | options: [ 127 | ...subFolders.map(subfolder => ({ value: subfolder, label: subfolder })), 128 | { value: 'new', label: 'Create new folder' } 129 | ] 130 | }); 131 | 132 | let subfolderPath; 133 | if (subfolderSelection === 'new') { 134 | const newSubfolderName = await text({ 135 | message: 'Enter the name of the new subfolder:', 136 | initial: '', 137 | }); 138 | subfolderPath = path.join(selectedFolderPath, newSubfolderName); 139 | fs.mkdirSync(subfolderPath, { recursive: true }); 140 | logWithStyle(`New subfolder ${newSubfolderName} created successfully.`, 'success'); 141 | } else { 142 | subfolderPath = path.join(selectedFolderPath, subfolderSelection); 143 | } 144 | 145 | // Create the file 146 | const filePath = path.join(subfolderPath, `${fileName}.js`); 147 | 148 | if (fs.existsSync(filePath)) { 149 | logWithStyle(`File already exists: ${filePath}`, 'error'); 150 | } else { 151 | createFile(filePath, templates[fileType]); 152 | } 153 | })(); 154 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bot": { 3 | "token": "YOUR_BOT_TOKEN", 4 | "id": "YOUR_BOT_ID", 5 | "admins": [ 6 | "ADMIN_1", 7 | "ADMIN_2" 8 | ], 9 | "ownerId": "BOT_OWNER_ID", 10 | "developerCommandsServerIds": [ 11 | "ONLY_DEV_COMMANDS_SERVER_IDS" 12 | ] 13 | }, 14 | "database": { 15 | "mongodbUrl": "YOUR_MONGODB_URL" 16 | }, 17 | "logging": { 18 | "guildJoinLogsId": "GUILD_JOIN_LOGS_CHANNEL_ID", 19 | "guildLeaveLogsId": "GUILD_LEAVE_LOGS_CHANNEL_ID", 20 | "commandLogsChannelId": "COMMAND_LOGS_CHANNEL_ID", 21 | "errorLogs": "YOUR_WEBHOOK_URL" 22 | }, 23 | "prefix": { 24 | "value": "YOUR_BOT_PREFIX" 25 | } 26 | } -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to discoBase 2 | 3 | First of all, thank you for considering contributing to discoBase! Your support helps improve this package and make it better for everyone. 4 | 5 | ## How Can You Contribute? 6 | 7 | - Reporting bugs 8 | - Suggesting features 9 | - Submitting code improvements or new features 10 | - Improving documentation 11 | 12 | ## Guidelines for Contribution 13 | 14 | ### 1. Reporting Bugs 15 | 16 | If you find a bug, please report it by opening an [issue]([https://github.com/your-repo/discoBase/issues](https://github.com/ethical-programmer/discobase/issues)). Provide as much detail as possible: 17 | 18 | - Steps to reproduce the bug 19 | - Version of discoBase you're using 20 | - Any error logs 21 | 22 | ### 2. Suggesting Features 23 | 24 | Do you have an idea for a new feature? We'd love to hear it! Open an [issue]([https://github.com/your-repo/discoBase/issues](https://github.com/ethical-programmer/discobase/issues)) and explain the feature you'd like to see, along with any use cases. 25 | 26 | ### 3. Submitting Code Changes 27 | 28 | To submit a code change: 29 | 30 | 1. **Fork the repository** on GitHub. 31 | 2. **Clone your fork** locally: 32 | ```bash 33 | git clone https://github.com/ethical-programmer/discobase 34 | ``` 35 | 3. **Create a new branch** for your changes: 36 | ```bash 37 | git checkout -b feature/your-feature 38 | ``` 39 | 4. **Make your changes** to the codebase. 40 | 5. **Commit your changes**: 41 | ```bash 42 | git commit -m "Added a cool new feature" 43 | ``` 44 | 6. **Push to your branch**: 45 | ```bash 46 | git push origin feature/your-feature 47 | ``` 48 | 7. **Open a Pull Request** on GitHub. 49 | 50 | ### 4. Coding Standards 51 | 52 | - Use consistent formatting and indentation. 53 | - Write clear, concise commit messages. 54 | - Make sure your code is well-documented and includes comments where necessary. 55 | 56 | ### 5. Testing Your Changes 57 | 58 | Ensure that your changes do not introduce any errors or issues by thoroughly testing them before submitting. You can run the project locally to verify this. 59 | 60 | ### 6. Improving Documentation 61 | 62 | Even if you're not a developer, improving the documentation is a great way to contribute! You can submit fixes for typos or add missing sections that help other users understand the project better. 63 | 64 | --- 65 | 66 | Thank you for taking the time to contribute! -------------------------------------------------------------------------------- /setup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { exec } = require('child_process'); 6 | const { intro, outro, confirm, text, isCancel, spinner } = require('@clack/prompts'); 7 | const chalk = require('chalk'); 8 | 9 | function installPackages(packages, destination) { 10 | return new Promise((resolve, reject) => { 11 | const command = `npm install ${packages.join(' ')}`; 12 | exec(command, { cwd: destination }, (error, stdout, stderr) => { 13 | if (error) { 14 | console.error(chalk.red(`Error installing packages: ${stderr}`)); 15 | reject(error); 16 | } else { 17 | console.log(chalk.green(`Packages installed: ${stdout}`)); 18 | resolve(); 19 | } 20 | }); 21 | }); 22 | } 23 | 24 | const excludeFiles = ['setup.js', 'package.json', 'package-lock.json', '.gitignore', 'README.md']; 25 | function copyProjectStructure(source, destination, includeDashboard) { 26 | if (!fs.existsSync(destination)) { 27 | fs.mkdirSync(destination, { recursive: true }); 28 | } 29 | 30 | const items = fs.readdirSync(source); 31 | 32 | if (destination.endsWith('src')) { 33 | const schemasPath = path.join(destination, 'schemas'); 34 | if (!fs.existsSync(schemasPath)) { 35 | fs.mkdirSync(schemasPath); 36 | } 37 | } 38 | 39 | items.forEach(item => { 40 | const srcPath = path.join(source, item); 41 | const destPath = path.join(destination, item); 42 | 43 | if (excludeFiles.includes(item)) { 44 | return; 45 | } 46 | 47 | if (!includeDashboard && item === 'admin') { 48 | return; 49 | } 50 | 51 | if (fs.lstatSync(srcPath).isDirectory()) { 52 | if (destination.endsWith('commands') || destination.endsWith('messages')) { 53 | const moderationPath = path.join(destination, 'Moderation'); 54 | const otherPath = path.join(destination, 'Other'); 55 | 56 | if (!fs.existsSync(moderationPath)) fs.mkdirSync(moderationPath); 57 | if (!fs.existsSync(otherPath)) fs.mkdirSync(otherPath); 58 | } 59 | 60 | if (destination.endsWith('events')) { 61 | const buttonEventPath = path.join(destination, 'buttons'); 62 | 63 | if (!fs.existsSync(buttonEventPath)) fs.mkdirSync(buttonEventPath); 64 | } 65 | 66 | if (destination.endsWith('functions')) { 67 | const otherFunctionsPath = path.join(destination, 'other'); 68 | if (!fs.existsSync(otherFunctionsPath)) fs.mkdirSync(otherFunctionsPath); 69 | } 70 | 71 | copyProjectStructure(srcPath, destPath, includeDashboard); 72 | } else { 73 | fs.copyFileSync(srcPath, destPath); 74 | } 75 | }); 76 | } 77 | 78 | function createPackageJson(destination) { 79 | const packageJson = { 80 | name: path.basename(destination), 81 | main: "src/index.js", 82 | scripts: { 83 | "start": "node .", 84 | "generate": "node cli.js" 85 | }, 86 | 87 | }; 88 | 89 | fs.writeFileSync(path.join(destination, 'package.json'), JSON.stringify(packageJson, null, 2)); 90 | } 91 | 92 | async function setupProjectStructure() { 93 | intro(chalk.yellowBright('Welcome to Discobase Setup!')); 94 | 95 | let projectName = await text({ 96 | message: 'Enter your bot name (leave blank to create in the current directory):', 97 | validate(value) { 98 | return value.length > 100 ? 'Name too long' : undefined; 99 | } 100 | }); 101 | 102 | if (isCancel(projectName)) { 103 | outro(chalk.red('Setup cancelled.')); 104 | return; 105 | } 106 | 107 | const installDependencies = await confirm({ 108 | message: 'Do you want to install required dependencies for Discobase? [Recommended]', 109 | initialValue: true 110 | }); 111 | 112 | if (isCancel(installDependencies)) { 113 | outro(chalk.red('Setup cancelled.')); 114 | return; 115 | } 116 | 117 | const installDiscord = await confirm({ 118 | message: 'Do you want to install discord.js [Recommended]?', 119 | initialValue: true 120 | }); 121 | 122 | if (isCancel(installDiscord)) { 123 | outro(chalk.red('Setup cancelled.')); 124 | return; 125 | } 126 | 127 | const installMongo = await confirm({ 128 | message: 'Do you want to install MongoDB and Mongoose?', 129 | initialValue: true 130 | }); 131 | 132 | if (isCancel(installMongo)) { 133 | outro(chalk.red('Setup cancelled.')); 134 | return; 135 | } 136 | 137 | const includeDashboard = await confirm({ 138 | message: 'Do you want the Discobase Dashboard?', 139 | initialValue: true 140 | }); 141 | 142 | if (isCancel(includeDashboard)) { 143 | outro(chalk.red('Setup cancelled.')); 144 | return; 145 | } 146 | 147 | const sourcePath = __dirname; 148 | const destinationPath = projectName ? path.join(process.cwd(), projectName) : process.cwd(); 149 | 150 | if (projectName && !fs.existsSync(destinationPath)) { 151 | fs.mkdirSync(destinationPath); 152 | } 153 | 154 | process.chdir(destinationPath); // Change working directory to destinationPath 155 | 156 | const s = spinner(); 157 | s.start(chalk.yellowBright('Copying project structure...')); 158 | copyProjectStructure(sourcePath, destinationPath, includeDashboard); 159 | createPackageJson(destinationPath); 160 | s.stop(chalk.green('Project structure copied successfully.')); 161 | 162 | const packagesToInstall = []; 163 | if (installDiscord) packagesToInstall.push('discord.js'); 164 | if (installMongo) packagesToInstall.push('mongoose'); 165 | if (installDependencies) packagesToInstall.push('chalk@4', 'chokidar', 'axios', '@clack/prompts', 'multer', 'express', 'set-interval-async', 'commander'); 166 | 167 | if (packagesToInstall.length > 0) { 168 | console.log(chalk.yellow('Installing packages...')); 169 | try { 170 | await installPackages(packagesToInstall, destinationPath); // Pass destinationPath to installPackages 171 | console.log(chalk.green('Packages installed successfully.')); 172 | } catch (err) { 173 | console.error(chalk.red(err)); 174 | } 175 | } 176 | 177 | outro(chalk.green('Discobase is installed successfully! Enjoy coding.')); 178 | } 179 | 180 | setupProjectStructure(); 181 | -------------------------------------------------------------------------------- /src/commands/Community/ping.js: -------------------------------------------------------------------------------- 1 | //! This is a basic structure for a slash command in a discoBase using discord.js 2 | 3 | 4 | const { SlashCommandBuilder } = require('discord.js'); 5 | 6 | module.exports = { 7 | //! The 'data' property defines the slash command's structure using SlashCommandBuilder. 8 | data: new SlashCommandBuilder() 9 | //* Name of the slash command. In this case, the command will be '/ping'. 10 | .setName('ping') 11 | 12 | //* A short description of what the command does, shown when users type '/ping' in Discord. 13 | .setDescription('This is the ping command.'), 14 | 15 | //? Optional: Permissions that the bot requires to execute the command. 16 | //? botPermissions: ['SendMessages'], // Example: bot needs permission to send messages. 17 | 18 | //? Optional: Permissions that the user requires to use this command. Uncomment if needed. 19 | //? userPermissions: ['ManageMessages'], // Example: Only users with Manage Messages permission can use this command. 20 | 21 | //? Optional: Set this to true if only bot admins can use this command. 22 | //? adminOnly: true, 23 | 24 | //? Optional: Set this to true if only the bot owner can use this command. 25 | //? ownerOnly: true, 26 | 27 | //? Optional: Set this to true if only developers can use this command. 28 | //? devOnly: true, so if this true this slash command will only register for the server IDs you provided in config.json 29 | 30 | //? Optional: Cooldown period for the command in seconds to prevent spam. 31 | //? cooldown: 10, 32 | 33 | //! The 'execute' function is where the main logic for the command is placed. 34 | async execute(interaction, client) { 35 | try { 36 | 37 | const ping = Date.now() - interaction.createdTimestamp; 38 | const latency = Math.abs(ping); 39 | const latencyFormatted = `${latency.toString().substring(0, 2)}ms`; 40 | const emoji = "⏱️"; 41 | 42 | await interaction.reply(`${emoji} Pong! Latency is ${latencyFormatted}!`); 43 | } catch (error) { 44 | console.error('An error occurred while executing the command:', error); 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/events/handlers/guildJoinLogs.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder } = require('discord.js'); 2 | const config = require('../../../config.json'); 3 | 4 | module.exports = { 5 | name: 'guildCreate', 6 | async execute(guild, client) { 7 | const channelId = config.logging.guildJoinLogsId; 8 | if (channelId === 'GUILD_JOIN_LOGS_CHANNEL_ID') return; 9 | if (channelId) { 10 | const channel = client.channels.cache.get(channelId); 11 | 12 | if (channel) { 13 | const memberCount = guild.memberCount; 14 | 15 | const embed = new EmbedBuilder() 16 | .setColor('#0099ff') 17 | .setTitle(`Joined New Guild: ${guild.name}`) 18 | .addFields( 19 | { name: 'Total Members', value: `${memberCount}`, inline: true }, 20 | { name: 'Guild ID', value: `${guild.id}`, inline: true }, 21 | ) 22 | .setTimestamp() 23 | .setFooter({ text: `Bot joined at` }); 24 | 25 | if (guild.iconURL()) { 26 | embed.setThumbnail(guild.iconURL()); 27 | } 28 | 29 | try { 30 | await channel.send({ embeds: [embed] }); 31 | } catch (error) { 32 | console.error(`Failed to send message to channel ${channelId}:`, error); 33 | } 34 | } else { 35 | console.error(`Channel with ID ${channelId} does not exist or is not a text channel for guild join logs.`); 36 | } 37 | } else { 38 | console.error('No channel ID specified for guild join logs in config.'); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/events/handlers/guildLeaveLogs.js: -------------------------------------------------------------------------------- 1 | const { EmbedBuilder } = require('discord.js'); 2 | const config = require('../../../config.json'); 3 | 4 | module.exports = { 5 | name: 'guildDelete', 6 | async execute(guild, client) { 7 | const channelId = config.logging.guildLeaveLogsId; 8 | 9 | if (channelId) { 10 | const channel = client.channels.cache.get(channelId); 11 | 12 | if (channel || channelId !== 'GUILD_LEAVE_LOGS_CHANNEL_ID') { 13 | const memberCount = guild.memberCount; 14 | 15 | const embed = new EmbedBuilder() 16 | .setColor('Red') 17 | .setTitle(`Leave Guild: ${guild.name}`) 18 | .addFields( 19 | { name: 'Total Members', value: `${memberCount}`, inline: true }, 20 | { name: 'Guild ID', value: `${guild.id}`, inline: true }, 21 | ) 22 | .setTimestamp() 23 | .setFooter({ text: `Bot leaved at` }); 24 | 25 | if (guild.iconURL()) { 26 | embed.setThumbnail(guild.iconURL()); 27 | } 28 | 29 | try { 30 | await channel.send({ embeds: [embed] }); 31 | } catch (error) { 32 | console.error(`Failed to send message to channel ${channelId}:`, error); 33 | } 34 | } else { 35 | console.error(`Channel with ID ${channelId} does not exist or is not a text channel for guild leave logs.`); 36 | } 37 | } else { 38 | console.error('No channel ID specified for guild leave logs in config.'); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/events/handlers/interactionCreate.js: -------------------------------------------------------------------------------- 1 | const { Interaction, Permissions, EmbedBuilder } = require("discord.js"); 2 | const chalk = require("chalk"); 3 | const config = require('../../../config.json'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | const errorsDir = path.join(__dirname, '../../../errors'); // Ensure correct path to the root 8 | 9 | // Function to create the errors directory if it doesn't exist 10 | function ensureErrorDirectoryExists() { 11 | if (!fs.existsSync(errorsDir)) { 12 | fs.mkdirSync(errorsDir); 13 | } 14 | } 15 | 16 | // Function to log errors to a file 17 | function logErrorToFile(error) { 18 | ensureErrorDirectoryExists(); 19 | 20 | // Convert the error object into a string, including the stack trace 21 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 22 | 23 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 24 | const filePath = path.join(errorsDir, fileName); 25 | 26 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 27 | } 28 | 29 | 30 | module.exports = { 31 | name: 'interactionCreate', 32 | async execute(interaction, client) { 33 | 34 | if (!interaction.isCommand()) return; 35 | 36 | const command = client.commands.get(interaction.commandName); 37 | 38 | if (!command) { 39 | console.log(chalk.yellow(`Command "${interaction.commandName}" not found.`)); 40 | return; 41 | } 42 | 43 | if (command.adminOnly) { 44 | if (!config.bot.admins.includes(interaction.user.id)) { 45 | 46 | const embed = new EmbedBuilder() 47 | .setColor('Blue') 48 | .setDescription(`\`❌\` | This command is admin-only. You cannot run this command.`) 49 | 50 | return await interaction.reply({ 51 | embeds: [embed], 52 | ephemeral: true 53 | }); 54 | } 55 | } 56 | 57 | if (command.ownerOnly) { 58 | if (interaction.user.id !== config.bot.ownerId) { 59 | const embed = new EmbedBuilder() 60 | .setColor('Blue') 61 | .setDescription(`\`❌\` | This command is owner-only. You cannot run this command.`) 62 | 63 | return await interaction.reply({ 64 | embeds: [embed], 65 | ephemeral: true 66 | }); 67 | } 68 | } 69 | 70 | if (command.userPermissions) { 71 | const memberPermissions = interaction.member.permissions; 72 | const missingPermissions = command.userPermissions.filter(perm => !memberPermissions.has(perm)); 73 | 74 | if (missingPermissions.length) { 75 | 76 | const embed = new EmbedBuilder() 77 | .setColor('Blue') 78 | .setDescription(`\`❌\` | You lack the necessary permissions to execute this command: \`\`\`${missingPermissions.join(", ")}\`\`\``) 79 | 80 | return await interaction.reply({ 81 | embeds: [embed], 82 | ephemeral: true 83 | }); 84 | } 85 | } 86 | 87 | 88 | 89 | if (command.botPermissions) { 90 | const botPermissions = interaction.guild.members.me.permissions; 91 | const missingBotPermissions = command.botPermissions.filter(perm => !botPermissions.has(perm)); 92 | if (missingBotPermissions.length) { 93 | 94 | 95 | const embed = new EmbedBuilder() 96 | .setColor('Blue') 97 | .setDescription(`\`❌\` | I lack the necessary permissions to execute this command: \`\`\`${missingBotPermissions.join(", ")}\`\`\``) 98 | 99 | return await interaction.reply({ 100 | embeds: [embed], 101 | ephemeral: true 102 | }); 103 | } 104 | } 105 | 106 | const cooldowns = client.cooldowns || new Map(); 107 | const now = Date.now(); 108 | const cooldownAmount = (command.cooldown || 3) * 1000; 109 | const timestamps = cooldowns.get(command.name) || new Map(); 110 | 111 | if (timestamps.has(interaction.user.id)) { 112 | const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount; 113 | 114 | if (now < expirationTime) { 115 | const timeLeft = (expirationTime - now) / 1000; 116 | 117 | const embed = new EmbedBuilder() 118 | .setColor('Blue') 119 | .setDescription(`\`❌\` | Please wait **${timeLeft.toFixed(1)}** more second(s) before reusing the command.`) 120 | 121 | return await interaction.reply({ 122 | embeds: [embed], 123 | ephemeral: true 124 | }); 125 | } 126 | } 127 | 128 | timestamps.set(interaction.user.id, now); 129 | cooldowns.set(command.name, timestamps); 130 | 131 | try { 132 | await command.execute(interaction, client); 133 | // Create an embed to log the command execution 134 | const logEmbed = new EmbedBuilder() 135 | .setColor('#0099ff') 136 | .setTitle('Command Executed') 137 | .addFields( 138 | { name: 'User', value: `${ interaction.user.tag }(${ interaction.user.id })`, inline: true }, 139 | { name: 'Command', value: `/ ${ command.data.name }`, inline: true }, 140 | { name: 'Server', value: `${ interaction.guild.name }(${ interaction.guild.id })`, inline: true }, 141 | { name: 'Timestamp', value: new Date().toLocaleString(), inline: true } 142 | ) 143 | .setTimestamp(); 144 | 145 | // Send the embed to the specified logs channel 146 | if (config.logging.commandLogsChannelId) { 147 | if (config.logging.commandLogsChannelId === 'COMMAND_LOGS_CHANNEL_ID') return; 148 | const logsChannel = client.channels.cache.get(config.logging.commandLogsChannelId); 149 | if (logsChannel) { 150 | await logsChannel.send({ embeds: [logEmbed] }); 151 | } else { 152 | console.error(chalk.yellow(`Logs channel with ID ${ config.logging.commandLogsChannelId } not found.`)); 153 | } 154 | } 155 | } catch (error) { 156 | console.error(chalk.red(`Error executing command "${command.data.name}": `), error); 157 | await interaction.reply({ 158 | content: 'There was an error while executing this command!', 159 | ephemeral: true 160 | }); 161 | logErrorToFile(error) 162 | 163 | } 164 | }, 165 | }; 166 | -------------------------------------------------------------------------------- /src/events/handlers/prefixCreate.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const config = require('../../../config.json'); 3 | const { EmbedBuilder } = require('discord.js'); 4 | const { getSimilarCommands } = require('../../functions/handlers/similarity'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | const errorsDir = path.join(__dirname, '../../../errors'); 9 | 10 | function ensureErrorDirectoryExists() { 11 | if (!fs.existsSync(errorsDir)) { 12 | fs.mkdirSync(errorsDir); 13 | } 14 | } 15 | 16 | function logErrorToFile(error) { 17 | ensureErrorDirectoryExists(); 18 | 19 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 20 | 21 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 22 | const filePath = path.join(errorsDir, fileName); 23 | 24 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 25 | } 26 | 27 | 28 | module.exports = { 29 | name: 'messageCreate', 30 | async execute(message, client) { 31 | const prefix = config.prefix.value; 32 | const content = message.content.toLowerCase(); 33 | if (prefix === '') return; 34 | if (!content.startsWith(prefix) || message.author.bot) return; 35 | 36 | const args = content.slice(prefix.length).trim().split(/ +/); 37 | const commandName = args.shift().toLowerCase(); 38 | 39 | let command = client.prefix.get(commandName); 40 | if (!command) { 41 | command = Array.from(client.prefix.values()).find( 42 | (cmd) => cmd.aliases && cmd.aliases.includes(commandName) 43 | ); 44 | } 45 | 46 | if (!command) { 47 | console.log(chalk.yellow.bold('WARNING: ') + `Unknown command: "${commandName}"`); 48 | 49 | const similarCommands = getSimilarCommands(commandName, Array.from(client.prefix.values())); 50 | if (similarCommands.length > 0) { 51 | const embed = new EmbedBuilder() 52 | .setColor('Blue') 53 | .setDescription(`\`🤔\` | Command not found. Did you mean: ${similarCommands.join(', ')}?`) 54 | 55 | return await message.reply({ embeds: [embed] }); 56 | } else { 57 | return; 58 | } 59 | } 60 | 61 | 62 | if (command.devOnly) { 63 | if (!config.bot.developerCommandsServerIds.includes(message.guild.id)) { 64 | return; 65 | } 66 | } 67 | 68 | 69 | if (!client.cooldowns) { 70 | client.cooldowns = new Map(); 71 | } 72 | 73 | const now = Date.now(); 74 | const cooldownAmount = (command.cooldown || 3) * 1000; 75 | 76 | if (!client.cooldowns.has(command.name)) { 77 | client.cooldowns.set(command.name, new Map()); 78 | } 79 | 80 | const timestamps = client.cooldowns.get(command.name); 81 | 82 | if (timestamps.has(message.author.id)) { 83 | const expirationTime = timestamps.get(message.author.id) + cooldownAmount; 84 | if (now < expirationTime) { 85 | const timeLeft = (expirationTime - now) / 1000; 86 | 87 | const embed = new EmbedBuilder() 88 | .setColor('Blue') 89 | .setDescription(`\`❌\` | Please wait **${timeLeft.toFixed(1)}** more second(s) before reusing the \`${command.name}\` command.`) 90 | 91 | return message.reply({ 92 | embeds: [embed] 93 | }); 94 | } 95 | } 96 | 97 | timestamps.set(message.author.id, now); 98 | 99 | if (command.adminOnly && !config.bot.admins.includes(message.author.id)) { 100 | const embed = new EmbedBuilder() 101 | .setColor('Blue') 102 | .setDescription(`\`❌\` | This command is admin-only. You cannot run this command.`) 103 | 104 | return message.reply({ 105 | embeds: [embed] 106 | }); 107 | } 108 | 109 | 110 | if (command.ownerOnly && message.author.id !== config.bot.ownerId) { 111 | const embed = new EmbedBuilder() 112 | .setColor('Blue') 113 | .setDescription(`\`❌\` | This command is owner-only. You cannot run this command.`) 114 | 115 | return await message.reply({ 116 | embeds: [embed], 117 | }); 118 | } 119 | 120 | if (command.userPermissions) { 121 | const memberPermissions = message.member.permissions; 122 | const missingPermissions = command.userPermissions.filter(perm => !memberPermissions.has(perm)); 123 | if (missingPermissions.length) { 124 | const embed = new EmbedBuilder() 125 | .setColor('Blue') 126 | .setDescription(`\`❌\` | You lack the necessary permissions to execute this command: \`\`\`${missingPermissions.join(", ")}\`\`\``) 127 | 128 | return message.reply({ 129 | embeds: [embed], 130 | }); 131 | } 132 | } 133 | 134 | if (command.botPermissions) { 135 | const botPermissions = message.guild.members.me.permissions; 136 | const missingBotPermissions = command.botPermissions.filter(perm => !botPermissions.has(perm)); 137 | if (missingBotPermissions.length) { 138 | const embed = new EmbedBuilder() 139 | .setColor('Blue') 140 | .setDescription(`\`❌\` | I lack the necessary permissions to execute this command: \`\`\`${missingBotPermissions.join(", ")}\`\`\``) 141 | 142 | return message.reply({ 143 | embeds: [embed], 144 | }); 145 | } 146 | } 147 | 148 | try { 149 | await command.run(client, message, args); 150 | const logEmbed = new EmbedBuilder() 151 | .setColor('Blue') 152 | .setTitle('Command Executed') 153 | .addFields( 154 | { name: 'User', value: `${message.author.tag} (${message.author.id})`, inline: true }, 155 | { name: 'Command', value: `${config.prefix.value}${command.name}`, inline: true }, 156 | { name: 'Server', value: `${message.guild.name} (${message.guild.id})`, inline: true }, 157 | { name: 'Timestamp', value: new Date().toLocaleString(), inline: true } 158 | ) 159 | .setTimestamp(); 160 | 161 | if (config.logging.commandLogsChannelId) { 162 | const logsChannel = client.channels.cache.get(config.logging.commandLogsChannelId); 163 | if (logsChannel) { 164 | await logsChannel.send({ embeds: [logEmbed] }); 165 | } else { 166 | if (config.logging.commandLogsChannelId === 'COMMAND_LOGS_CHANNEL_ID') return; 167 | 168 | console.error(chalk.yellow(`Logs channel with ID ${config.logging.commandLogsChannelId} not found.`)); 169 | } 170 | } 171 | } catch (error) { 172 | console.log(chalk.red.bold('ERROR: ') + `Failed to execute command "${commandName}".`); 173 | console.error(error); 174 | message.reply({ 175 | content: 'There was an error while executing this command!', 176 | }); 177 | logErrorToFile(error) 178 | 179 | } 180 | } 181 | }; 182 | -------------------------------------------------------------------------------- /src/events/handlers/ready.js: -------------------------------------------------------------------------------- 1 | const config = require('../../../config.json'); 2 | const mongoose = require('mongoose'); 3 | const chalk = require('chalk'); 4 | const { ActivityType } = require('discord.js'); 5 | const { prefixHandler } = require('../../functions/handlers/prefixHandler'); 6 | const { handleCommands } = require('../../functions/handlers/handleCommands'); 7 | const path = require('path'); 8 | const mongodbURL = config.database.mongodbUrl; 9 | const fs = require('fs') 10 | 11 | const errorsDir = path.join(__dirname, '../../../errors'); 12 | function ensureErrorDirectoryExists() { 13 | if (!fs.existsSync(errorsDir)) { 14 | fs.mkdirSync(errorsDir); 15 | } 16 | } 17 | 18 | function logErrorToFile(error) { 19 | ensureErrorDirectoryExists(); 20 | 21 | // Convert the error object into a string, including the stack trace 22 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 23 | 24 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 25 | const filePath = path.join(errorsDir, fileName); 26 | 27 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 28 | } 29 | 30 | 31 | module.exports = { 32 | name: 'ready', 33 | once: true, 34 | async execute(client) { 35 | console.log(chalk.green.bold('INFO: ') + 'Bot is ready and connected to Discord!'); 36 | 37 | if (!mongodbURL || mongodbURL === 'YOUR_MONGODB_URL') { 38 | console.log(chalk.yellow.bold('INFO: ') + 'MongoDB URL is not provided or is set to the default placeholder. Skipping MongoDB connection.'); 39 | } else { 40 | try { 41 | await mongoose.connect(mongodbURL); 42 | if (mongoose.connect) { 43 | console.log(chalk.green.bold('SUCCESS: ') + 'Connected to MongoDB successfully!'); 44 | } 45 | } catch (error) { 46 | console.log(chalk.red.bold('ERROR: ') + 'Failed to connect to MongoDB. Please check your MongoDB URL and connection.'); 47 | console.error(error); 48 | logErrorToFile(error); 49 | 50 | } 51 | } 52 | 53 | 54 | 55 | client.user.setPresence({ 56 | activities: [{ 57 | type: ActivityType.Custom, 58 | name: "custom", 59 | state: "🚀 discobase!" 60 | }] 61 | }) 62 | prefixHandler(client, path.join(process.cwd(), 'src/messages')); 63 | handleCommands(client, path.join(process.cwd(), 'src/commands')); 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /src/functions/handlers/antiCrash.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const config = require('../../../config.json'); 3 | const chalk = require('chalk'); 4 | const process = require('node:process'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | function antiCrash() { 9 | const webhookURL = config.logging.errorLogs; 10 | const errorsDir = path.join(__dirname, '../../../errors'); 11 | 12 | function ensureErrorDirectoryExists() { 13 | if (!fs.existsSync(errorsDir)) { 14 | fs.mkdirSync(errorsDir); 15 | } 16 | } 17 | 18 | function logErrorToFile(error) { 19 | ensureErrorDirectoryExists(); 20 | 21 | // Convert the error object into a string, including the stack trace 22 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 23 | 24 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 25 | const filePath = path.join(errorsDir, fileName); 26 | 27 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 28 | } 29 | 30 | 31 | async function sendErrorNotification(message) { 32 | if (!webhookURL || webhookURL === "YOUR_DISCORD_WEBHOOK_URL") { 33 | console.warn(chalk.yellow.bold('WARNING:') + ' No valid webhook URL provided. Unable to send error notifications.'); 34 | return; 35 | } 36 | 37 | const embed = { 38 | title: "Error Notification", 39 | description: message, 40 | color: 0xff0000, 41 | timestamp: new Date(), 42 | footer: { 43 | text: "Bot Error Logger", 44 | }, 45 | }; 46 | 47 | await axios.post(webhookURL, { embeds: [embed] }) 48 | .catch(error => { 49 | console.warn(chalk.yellow.bold('WARNING:') + ' Failed to send error notification:', error.message); 50 | }); 51 | } 52 | 53 | process.on('unhandledRejection', async (reason, promise) => { 54 | const errorMessage = reason.message.includes("Used disallowed intents") 55 | ? 'Used disallowed intents. Please check your bot settings on the Discord developer portal.' 56 | : `Unhandled Rejection at: ${promise} \nReason: ${reason} \nStack: ${reason.stack || 'No stack trace available.'}`; 57 | 58 | console.error(chalk.red.bold('ERROR:') + ' ' + errorMessage); 59 | 60 | logErrorToFile(errorMessage); 61 | 62 | await sendErrorNotification(errorMessage); 63 | }); 64 | 65 | process.on('uncaughtException', async (error) => { 66 | const errorMessage = error.message.includes("Used disallowed intents") 67 | ? 'Used disallowed intents. Please check your bot settings on the Discord developer portal.' 68 | : `Uncaught Exception: ${error.message} \nStack: ${error.stack || 'No stack trace available.'}`; 69 | 70 | console.error(chalk.red.bold('ERROR:') + ' ' + errorMessage); 71 | 72 | logErrorToFile(errorMessage); 73 | 74 | await sendErrorNotification(errorMessage); 75 | }); 76 | } 77 | 78 | module.exports = { antiCrash }; 79 | -------------------------------------------------------------------------------- /src/functions/handlers/functionHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { setIntervalAsync, clearIntervalAsync } = require('set-interval-async/fixed'); 4 | let intervalId; 5 | 6 | const functionsDir = path.join(__dirname, '../'); 7 | 8 | const getAllFunctionFiles = (dir) => { 9 | let results = []; 10 | 11 | const list = fs.readdirSync(dir); 12 | list.forEach(file => { 13 | const filePath = path.join(dir, file); 14 | const stat = fs.statSync(filePath); 15 | 16 | if (stat && stat.isDirectory()) { 17 | results = results.concat(getAllFunctionFiles(filePath)); 18 | } else if (file.endsWith('.js')) { 19 | results.push(filePath); 20 | } 21 | }); 22 | 23 | return results; 24 | }; 25 | 26 | const loadFunctions = () => { 27 | const functionFiles = getAllFunctionFiles(functionsDir); 28 | return functionFiles.map(file => require(file)); 29 | }; 30 | 31 | const handleFunction = async (func) => { 32 | if (typeof func === 'function') { 33 | const config = func.config || {}; 34 | await handleSingleFunction(func.name, func, config); 35 | } 36 | }; 37 | 38 | const handleSingleFunction = async (name, func, config) => { 39 | 40 | const { once, interval, retryAttempts, maxExecution, initializer } = config; 41 | 42 | if (interval && isNaN(interval)) { 43 | console.error(`Invalid interval for function ${name}. Interval must be a number.`); 44 | return; 45 | } 46 | 47 | if (initializer) { 48 | console.log(`Waiting ${initializer} seconds before starting function.`); 49 | await new Promise(resolve => setTimeout(resolve, initializer * 1000)); 50 | } 51 | 52 | let executions = 0; 53 | let retries = 0; 54 | 55 | const runFunction = async () => { 56 | if (executions >= maxExecution) { 57 | console.log(`Max executions reached for ${name}.`); 58 | return clearIntervalAsync(intervalId); 59 | } 60 | 61 | try { 62 | console.log(`Executing function: ${name}...`); 63 | await func(); 64 | executions++; 65 | } catch (error) { 66 | console.error(`Error executing function ${name}, Retrying...`); 67 | retries++; 68 | if (retries >= retryAttempts) { 69 | console.log(`Failed after ${retryAttempts} retries for ${name}.`); 70 | return clearIntervalAsync(intervalId); 71 | } 72 | } 73 | }; 74 | 75 | if (once) { 76 | await runFunction(); 77 | } else if (interval) { 78 | intervalId = setIntervalAsync(runFunction, interval); 79 | } 80 | }; 81 | 82 | const runHandlers = async () => { 83 | const functions = loadFunctions(); 84 | console.log(`Loaded ${functions.length} functions.`); 85 | 86 | for (const func of functions) { 87 | await handleFunction(func); 88 | } 89 | }; 90 | 91 | runHandlers().catch(console.error); 92 | -------------------------------------------------------------------------------- /src/functions/handlers/handelEvents.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { Collection } = require('discord.js'); 6 | const chokidar = require('chokidar'); 7 | const chalk = require('chalk'); 8 | 9 | const debounce = (func, delay) => { 10 | let timeoutId; 11 | return (...args) => { 12 | clearTimeout(timeoutId); 13 | timeoutId = setTimeout(() => { 14 | func.apply(null, args); 15 | }, delay); 16 | }; 17 | }; 18 | 19 | const errorsDir = path.join(__dirname, '../../../errors'); 20 | 21 | function ensureErrorDirectoryExists() { 22 | if (!fs.existsSync(errorsDir)) { 23 | fs.mkdirSync(errorsDir); 24 | } 25 | } 26 | 27 | function logErrorToFile(error) { 28 | ensureErrorDirectoryExists(); 29 | 30 | // Convert the error object into a string, including the stack trace 31 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 32 | 33 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 34 | const filePath = path.join(errorsDir, fileName); 35 | 36 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 37 | } 38 | 39 | 40 | const getShortPath = (filePath) => path.basename(filePath); 41 | 42 | const eventsHandler = async (client, eventsPath) => { 43 | client.events = new Collection(); 44 | 45 | const getFilesRecursively = (dir) => { 46 | let files = []; 47 | const items = fs.readdirSync(dir); 48 | 49 | for (const item of items) { 50 | const fullPath = path.join(dir, item); 51 | if (fs.statSync(fullPath).isDirectory()) { 52 | files = [...files, ...getFilesRecursively(fullPath)]; 53 | } else if (item.endsWith('.js')) { 54 | files.push(fullPath); 55 | } 56 | } 57 | 58 | return files; 59 | }; 60 | 61 | const loadEvent = (file) => { 62 | try { 63 | delete require.cache[require.resolve(file)]; 64 | const event = require(file); 65 | 66 | if (event.name) { 67 | client.events.set(event.name, event); 68 | if (event.once) { 69 | client.once(event.name, (...args) => event.execute(...args, client)); 70 | } else { 71 | client.on(event.name, (...args) => event.execute(...args, client)); 72 | } 73 | 74 | console.log( 75 | chalk.green.bold('SUCCESS: ') + 76 | `Loaded event: ${chalk.cyan.bold(event.name)} from ${chalk.yellow(getShortPath(file))}` 77 | ); 78 | } else { 79 | console.warn(chalk.yellow.bold('WARNING: ') + `File ${chalk.yellow(getShortPath(file))} does not export a valid event name.`); 80 | } 81 | } catch (error) { 82 | console.error(chalk.red.bold('ERROR: ') + `Failed to load event from ${chalk.yellow(getShortPath(file))}:`, error); 83 | logErrorToFile(error) 84 | } 85 | }; 86 | 87 | const loadSchema = (file) => { 88 | try { 89 | delete require.cache[require.resolve(file)]; 90 | const schema = require(file); 91 | 92 | console.log(chalk.green.bold('SUCCESS: ') + `Loaded schema from ${chalk.yellow(getShortPath(file))}`); 93 | } catch (error) { 94 | console.error(chalk.red.bold('ERROR: ') + `Failed to load schema from ${chalk.yellow(getShortPath(file))}:`, error); 95 | logErrorToFile(error) 96 | } 97 | }; 98 | 99 | const unloadEvent = (file) => { 100 | const event = require(file); 101 | if (event.name && client.events.has(event.name)) { 102 | client.removeAllListeners(event.name); 103 | client.events.delete(event.name); 104 | console.log(`Unloaded event: ${event.name}`); 105 | } else { 106 | console.log( 107 | chalk.yellow.bold('WARNING: ') + 108 | `Event "${chalk.red(getShortPath(file))}" not found in client collection.` 109 | ); 110 | } 111 | }; 112 | 113 | const loadAllEvents = (eventDir) => { 114 | const eventFiles = getFilesRecursively(eventDir); 115 | eventFiles.forEach(file => loadEvent(file)); 116 | }; 117 | 118 | const loadAllSchemas = (schemasDir) => { 119 | const schemaFiles = getFilesRecursively(schemasDir); 120 | schemaFiles.forEach(file => loadSchema(file)); 121 | }; 122 | 123 | loadAllEvents(eventsPath); 124 | loadAllSchemas(path.join(__dirname, '../../schemas')); 125 | 126 | const watcher = chokidar.watch([eventsPath, path.join(__dirname, '../../schemas')], { 127 | persistent: true, 128 | ignoreInitial: true, 129 | awaitWriteFinish: true, 130 | ignored: [ 131 | path.join(__dirname, '../../functions/**'), 132 | ], 133 | }); 134 | 135 | watcher 136 | .on('add', (filePath) => { 137 | if (filePath.endsWith('.js')) { 138 | console.log(chalk.blue.bold('WATCHER: ') + `New file added: ${chalk.yellow.bold(getShortPath(filePath))}`); 139 | if (filePath.includes('schemas')) { 140 | loadSchema(filePath); 141 | } else { 142 | loadEvent(filePath); 143 | } 144 | } 145 | }) 146 | .on('change', (filePath) => { 147 | console.log(chalk.blue.bold('WATCHER: ') + `File changed: ${chalk.yellow.bold(getShortPath(filePath))}`); 148 | if (filePath.includes('schemas')) { 149 | loadSchema(filePath); 150 | } else { 151 | unloadEvent(filePath); 152 | loadEvent(filePath); 153 | } 154 | }) 155 | .on('unlink', (filePath) => { 156 | console.log(chalk.blue.bold('WATCHER: ') + `File removed: ${chalk.yellow.bold(getShortPath(filePath))}`); 157 | if (filePath.includes('schemas')) { 158 | } else { 159 | unloadEvent(filePath); 160 | } 161 | }); 162 | }; 163 | 164 | 165 | 166 | module.exports = { eventsHandler }; 167 | -------------------------------------------------------------------------------- /src/functions/handlers/handleCommands.js: -------------------------------------------------------------------------------- 1 | 2 | const { REST, Routes, Collection } = require('discord.js'); 3 | const fs = require('fs'); 4 | const chalk = require('chalk'); 5 | const config = require('../../../config.json'); 6 | const path = require('path'); 7 | const chokidar = require('chokidar'); 8 | const activities = []; 9 | const addActivity = (action, filePath) => { 10 | const timestamp = new Date().toISOString(); 11 | activities.push({ action, filePath, timestamp }); 12 | }; 13 | 14 | const getActivities = () => activities; 15 | 16 | 17 | const log = (message, type = 'INFO') => { 18 | const colors = { 19 | INFO: chalk.blue.bold('INFO:'), 20 | SUCCESS: chalk.green.bold('SUCCESS:'), 21 | ERROR: chalk.red.bold('ERROR:'), 22 | WARNING: chalk.yellow.bold('WARNING:') 23 | }; 24 | console.log(colors[type] + ' ' + message); 25 | }; 26 | 27 | const errorsDir = path.join(__dirname, '../../../errors'); 28 | 29 | function ensureErrorDirectoryExists() { 30 | if (!fs.existsSync(errorsDir)) { 31 | fs.mkdirSync(errorsDir); 32 | } 33 | } 34 | 35 | function logErrorToFile(error) { 36 | ensureErrorDirectoryExists(); 37 | 38 | // Convert the error object into a string, including the stack trace 39 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 40 | 41 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 42 | const filePath = path.join(errorsDir, fileName); 43 | 44 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 45 | } 46 | 47 | 48 | const formatFilePath = (filePath) => { 49 | return path.relative(process.cwd(), filePath); 50 | }; 51 | 52 | const isConfigIncomplete = (key, value, placeholderTokens) => { 53 | return !value || placeholderTokens.includes(value); 54 | }; 55 | 56 | const getAllCommandFiles = (dirPath, arrayOfFiles = []) => { 57 | const files = fs.readdirSync(dirPath); 58 | files.forEach(file => { 59 | const filePath = path.join(dirPath, file); 60 | if (fs.statSync(filePath).isDirectory()) { 61 | arrayOfFiles = getAllCommandFiles(filePath, arrayOfFiles); 62 | } else if (file.endsWith('.js')) { 63 | arrayOfFiles.push(filePath); 64 | } 65 | }); 66 | return arrayOfFiles; 67 | }; 68 | 69 | const loadCommand = (client, filePath) => { 70 | try { 71 | if (filePath.includes('schemas')) { 72 | log(`Ignoring schema file: ${formatFilePath(filePath)}`, 'WARNING'); 73 | return null; 74 | } 75 | 76 | delete require.cache[require.resolve(filePath)]; 77 | const command = require(filePath); 78 | 79 | if (!command.data || !command.data.name || typeof command.data.name !== 'string') { 80 | log(`The command file "${formatFilePath(filePath)}" is missing a valid name property.`, 'ERROR'); 81 | return null; 82 | } 83 | 84 | client.commands.set(command.data.name, command); 85 | return command; 86 | 87 | } catch (error) { 88 | log(`Failed to load command from "${formatFilePath(filePath)}".`, 'ERROR'); 89 | console.error(error); 90 | logErrorToFile(error) 91 | return null; 92 | } 93 | }; 94 | 95 | const loadCommands = (client, commandsPath) => { 96 | const globalCommandArray = []; 97 | const devCommandArray = []; 98 | 99 | const commandFiles = getAllCommandFiles(commandsPath); 100 | 101 | for (const filePath of commandFiles) { 102 | const command = loadCommand(client, filePath); 103 | if (command) { 104 | if (command.devOnly) { 105 | devCommandArray.push(command.data.toJSON()); 106 | } else { 107 | globalCommandArray.push(command.data.toJSON()); 108 | } 109 | } 110 | } 111 | 112 | return { globalCommandArray, devCommandArray }; 113 | }; 114 | 115 | const unregisterCommand = async (commandName, rest, config, devCommandArray) => { 116 | try { 117 | log(`Unregistering global command: ${commandName}`, 'INFO'); 118 | 119 | 120 | 121 | const globalCommands = await rest.get(Routes.applicationCommands(config.bot.id)); 122 | const commandToDelete = globalCommands.find(cmd => cmd.name === commandName); 123 | if (commandToDelete) { 124 | await rest.delete(Routes.applicationCommand(config.bot.id, commandToDelete.id)); 125 | log(`Successfully unregistered global command: ${commandName}`, 'SUCCESS'); 126 | } 127 | 128 | 129 | if (devCommandArray.length > 0 && config.bot.developerCommandsServerIds && config.bot.developerCommandsServerIds.length > 0) { 130 | for (const serverId of config.bot.developerCommandsServerIds) { 131 | const guildCommands = await rest.get(Routes.applicationGuildCommands(config.bot.id, serverId)); 132 | const guildCommandToDelete = guildCommands.find(cmd => cmd.name === commandName); 133 | if (guildCommandToDelete) { 134 | await rest.delete(Routes.applicationGuildCommand(config.bot.id, serverId, guildCommandToDelete.id)); 135 | log(`Successfully unregistered command: ${commandName} from guild ${serverId}`, 'SUCCESS'); 136 | } 137 | } 138 | } 139 | } catch (error) { 140 | log(`Failed to unregister command: ${commandName}`, 'ERROR'); 141 | console.error(error); 142 | logErrorToFile(error) 143 | } 144 | }; 145 | 146 | const registerCommands = async (globalCommandArray, devCommandArray, rest, config) => { 147 | if (globalCommandArray.length > 0) { 148 | try { 149 | log('Started refreshing global application (/) commands.', 'INFO'); 150 | await rest.put( 151 | Routes.applicationCommands(config.bot.id), 152 | { body: globalCommandArray } 153 | ); 154 | log('Successfully reloaded global application (/) commands.', 'SUCCESS'); 155 | } catch (error) { 156 | log('Failed to reload global application (/) commands.', 'ERROR'); 157 | if (error.code === 10002) { 158 | console.error(chalk.red.bold('ERROR: ') + 'Unknown Application. Please check the Discord bot ID provided in your configuration.'); 159 | logErrorToFile(error) 160 | } else { 161 | console.error(chalk.red.bold('ERROR: ') + 'Failed to register commands:', error.message); 162 | logErrorToFile(error) 163 | } 164 | } 165 | } 166 | 167 | if (devCommandArray.length > 0 && config.bot.developerCommandsServerIds && config.bot.developerCommandsServerIds.length > 0) { 168 | const promises = config.bot.developerCommandsServerIds.map(async (serverId) => { 169 | try { 170 | log(`Started refreshing developer guild (/) commands for server: ${serverId}`, 'INFO'); 171 | await rest.put( 172 | Routes.applicationGuildCommands(config.bot.id, serverId), 173 | { body: devCommandArray } 174 | ); 175 | log(`Successfully reloaded developer guild (/) commands for server: ${serverId}`, 'SUCCESS'); 176 | } catch (error) { 177 | log(`Failed to reload developer guild (/) commands for server: ${serverId}`, 'ERROR'); 178 | console.error(error); 179 | logErrorToFile(error) 180 | } 181 | }); 182 | 183 | await Promise.all(promises); 184 | } else { 185 | log('No developer guild server IDs provided, or no developer commands to register.', 'WARNING'); 186 | } 187 | }; 188 | 189 | const handleCommands = async (client, commandsPath) => { 190 | const placeholderTokens = [ 191 | "YOUR_BOT_TOKEN", 192 | "YOUR_MONGODB_URL", 193 | "YOUR_BOT_ID", 194 | "YOUR_DEVELOPER_GUILD_ID", 195 | "YOUR_BOT_OWNER_ID", 196 | "YOUR_DEVELOPER_COMMANDS_SERVER_ID_1", 197 | "YOUR_DEVELOPER_COMMANDS_SERVER_ID_2", 198 | "YOUR_GUILD_JOIN_LOGS_CHANNEL_ID", 199 | "YOUR_GUILD_LEAVE_LOGS_CHANNEL_ID", 200 | "YOUR_COMMAND_LOGS_CHANNEL_ID" 201 | ]; 202 | 203 | if (isConfigIncomplete('botid', config.bot.id, placeholderTokens) || isConfigIncomplete('bottoken', config.bot.token, placeholderTokens)) { 204 | log("Missing or incorrect critical configuration.", 'ERROR'); 205 | if (isConfigIncomplete('botid', config.bot.id, placeholderTokens)) { 206 | log("Bot ID is missing or incorrect. Please replace 'YOUR_BOT_ID' with your actual bot ID in config.json.", 'ERROR'); 207 | } 208 | if (isConfigIncomplete('bottoken', config.bot.token, placeholderTokens)) { 209 | log("Bot token is missing or incorrect. Please replace 'YOUR_BOT_TOKEN' with your actual bot token in config.json.", 'ERROR'); 210 | } 211 | process.exit(1); 212 | } 213 | 214 | if (!client.commands) { 215 | client.commands = new Collection(); 216 | } 217 | 218 | const rest = new REST({ version: '10' }).setToken(config.bot.token); 219 | const { globalCommandArray, devCommandArray } = loadCommands(client, commandsPath); 220 | await registerCommands(globalCommandArray, devCommandArray, rest, config); 221 | const watcher = chokidar.watch([commandsPath, './src/functions', './src/schemas'], { 222 | persistent: true, 223 | ignoreInitial: true, 224 | awaitWriteFinish: true, 225 | }); 226 | 227 | let timeout; 228 | 229 | const registerDebouncedCommands = async () => { 230 | const { globalCommandArray, devCommandArray } = loadCommands(client, commandsPath); 231 | await registerCommands(globalCommandArray, devCommandArray, rest, config); 232 | }; 233 | 234 | watcher 235 | .on('add', (filePath) => { 236 | if (filePath.includes('schemas')) { 237 | log(`Schema file added: ${formatFilePath(filePath)}`, 'WARNING'); 238 | return; 239 | } 240 | 241 | if (filePath.includes('functions')) { 242 | log(`Functions file added: ${formatFilePath(filePath)}`, 'WARNING'); 243 | return; 244 | } 245 | 246 | log(`New command file added: ${formatFilePath(filePath)}`, 'SUCCESS'); 247 | loadCommand(client, filePath); 248 | addActivity('added', filePath); 249 | clearTimeout(timeout); 250 | timeout = setTimeout(registerDebouncedCommands, 5000); 251 | }) 252 | .on('change', (filePath) => { 253 | if (filePath.includes('schemas')) { 254 | log(`Schema file changed: ${formatFilePath(filePath)}`, 'WARNING'); 255 | return; 256 | } 257 | if (filePath.includes('functions')) { 258 | log(`Functions file changed: ${formatFilePath(filePath)}`, 'WARNING') 259 | return; 260 | } 261 | 262 | log(`Command file changed: ${formatFilePath(filePath)}`, 'INFO'); 263 | loadCommand(client, filePath); 264 | addActivity('changed', filePath); 265 | clearTimeout(timeout); 266 | timeout = setTimeout(registerDebouncedCommands, 5000); 267 | }) 268 | .on('unlink', async (filePath) => { 269 | if (filePath.includes('schemas')) { 270 | log(`Schema file removed: ${formatFilePath(filePath)}`, 'WARNING'); 271 | return; 272 | } 273 | 274 | if (filePath.includes('functions')) { 275 | log(`Functions file removed: ${formatFilePath(filePath)}`, 'WARNING'); 276 | return; 277 | } 278 | 279 | const commandName = path.basename(filePath, '.js'); 280 | log(`Command file removed: ${formatFilePath(filePath)}`, 'ERROR'); 281 | client.commands.delete(commandName); 282 | await unregisterCommand(commandName, rest, config, devCommandArray); 283 | addActivity('removed', filePath); 284 | clearTimeout(timeout); 285 | timeout = setTimeout(registerDebouncedCommands, 5000); 286 | }); 287 | 288 | }; 289 | 290 | module.exports = { 291 | handleCommands, 292 | getActivities 293 | }; 294 | -------------------------------------------------------------------------------- /src/functions/handlers/prefixHandler.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const { Collection } = require('discord.js'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const chokidar = require('chokidar'); 7 | const chalk = require('chalk'); 8 | 9 | const debounce = (func, delay) => { 10 | let timeoutId; 11 | return (...args) => { 12 | clearTimeout(timeoutId); 13 | timeoutId = setTimeout(() => { 14 | func.apply(this, args); 15 | }, delay); 16 | }; 17 | }; 18 | 19 | const errorsDir = path.join(__dirname, '../../../errors'); 20 | 21 | function ensureErrorDirectoryExists() { 22 | if (!fs.existsSync(errorsDir)) { 23 | fs.mkdirSync(errorsDir); 24 | } 25 | } 26 | 27 | function logErrorToFile(error) { 28 | ensureErrorDirectoryExists(); 29 | 30 | // Convert the error object into a string, including the stack trace 31 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 32 | 33 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 34 | const filePath = path.join(errorsDir, fileName); 35 | 36 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 37 | } 38 | 39 | 40 | function prefixHandler(client, prefixPath) { 41 | client.prefix = new Collection(); 42 | 43 | const log = (message, type = 'INFO') => { 44 | const colors = { 45 | INFO: chalk.blue.bold('INFO:'), 46 | SUCCESS: chalk.green.bold('SUCCESS:'), 47 | ERROR: chalk.red.bold('ERROR:'), 48 | WARNING: chalk.yellow.bold('WARNING:') 49 | }; 50 | console.log(colors[type] + ' ' + message); 51 | }; 52 | 53 | const loadCommand = (filePath) => { 54 | try { 55 | delete require.cache[require.resolve(filePath)]; 56 | const command = require(filePath); 57 | 58 | if (command.name) { 59 | client.prefix.set(command.name, command); 60 | log(`Loaded Prefix command: ${chalk.green(command.name)}`, 'SUCCESS'); 61 | } else { 62 | log(`Command in ${chalk.yellow(path.basename(filePath))} is missing a name.`, 'WARNING'); 63 | } 64 | } catch (error) { 65 | log(`Failed to load prefix command in ${chalk.red(path.basename(filePath))}`, 'ERROR'); 66 | console.error(error); 67 | logErrorToFile(error) 68 | } 69 | }; 70 | 71 | const unloadCommand = (filePath) => { 72 | const commandName = path.basename(filePath, '.js'); 73 | if (client.prefix.has(commandName)) { 74 | client.prefix.delete(commandName); 75 | log(`Unloaded command: ${chalk.red(commandName)}`, 'SUCCESS'); 76 | } else { 77 | log(`Command "${chalk.yellow(commandName)}" not found in client collection.`, 'WARNING'); 78 | } 79 | }; 80 | 81 | const loadAllCommands = (commandDir) => { 82 | const commandFiles = fs.readdirSync(commandDir); 83 | commandFiles.forEach(file => { 84 | const filePath = path.join(commandDir, file); 85 | const stat = fs.statSync(filePath); 86 | 87 | if (stat.isDirectory()) { 88 | loadAllCommands(filePath); 89 | } else if (file.endsWith('.js')) { 90 | loadCommand(filePath); 91 | } 92 | }); 93 | }; 94 | 95 | loadAllCommands(prefixPath); 96 | 97 | const watcher = chokidar.watch(prefixPath, { 98 | persistent: true, 99 | ignoreInitial: true, 100 | awaitWriteFinish: true, 101 | }); 102 | 103 | const debouncedLoadCommand = debounce(loadCommand, 500); 104 | const debouncedUnloadCommand = debounce(unloadCommand, 500); 105 | 106 | watcher 107 | .on('add', (filePath) => { 108 | if (filePath.endsWith('.js')) { 109 | log(`New command file added: ${chalk.green(path.basename(filePath))}`, 'SUCCESS'); 110 | debouncedLoadCommand(filePath); 111 | } 112 | }) 113 | .on('change', (filePath) => { 114 | if (filePath.endsWith('.js')) { 115 | log(`Command file changed: ${chalk.blue(path.basename(filePath))}`, 'INFO'); 116 | debouncedUnloadCommand(filePath); 117 | debouncedLoadCommand(filePath); 118 | } 119 | }) 120 | .on('unlink', (filePath) => { 121 | if (filePath.endsWith('.js')) { 122 | log(`Command file removed: ${chalk.red(path.basename(filePath))}`, 'ERROR'); 123 | debouncedUnloadCommand(filePath); 124 | } 125 | }); 126 | } 127 | 128 | module.exports = { prefixHandler }; 129 | 130 | -------------------------------------------------------------------------------- /src/functions/handlers/requiredIntents.js: -------------------------------------------------------------------------------- 1 | const { GatewayIntentBits } = require('discord.js'); 2 | 3 | const REQUIRED_INTENTS = { 4 | 'guildCreate': GatewayIntentBits.Guilds, 5 | 'guildUpdate': GatewayIntentBits.Guilds, 6 | 'guildDelete': GatewayIntentBits.Guilds, 7 | 'channelCreate': GatewayIntentBits.Guilds, 8 | 'channelUpdate': GatewayIntentBits.Guilds, 9 | 'channelDelete': GatewayIntentBits.Guilds, 10 | 'channelPinsUpdate': GatewayIntentBits.Guilds, 11 | 'threadCreate': GatewayIntentBits.Guilds, 12 | 'threadUpdate': GatewayIntentBits.Guilds, 13 | 'threadDelete': GatewayIntentBits.Guilds, 14 | 'threadListSync': GatewayIntentBits.Guilds, 15 | 'threadMemberUpdate': GatewayIntentBits.Guilds, 16 | 'threadMembersUpdate': GatewayIntentBits.Guilds, 17 | 'stageInstanceCreate': GatewayIntentBits.Guilds, 18 | 'stageInstanceUpdate': GatewayIntentBits.Guilds, 19 | 'stageInstanceDelete': GatewayIntentBits.Guilds, 20 | 'guildMemberAdd': GatewayIntentBits.GuildMembers, 21 | 'guildMemberUpdate': GatewayIntentBits.GuildMembers, 22 | 'guildMemberRemove': GatewayIntentBits.GuildMembers, 23 | 'threadMembersUpdate': GatewayIntentBits.GuildMembers, 24 | 'guildAuditLogEntryCreate': GatewayIntentBits.GuildModeration, 25 | 'guildBanAdd': GatewayIntentBits.GuildModeration, 26 | 'guildBanRemove': GatewayIntentBits.GuildModeration, 27 | 'guildEmojisUpdate': GatewayIntentBits.GuildEmojisAndStickers, 28 | 'guildStickersUpdate': GatewayIntentBits.GuildEmojisAndStickers, 29 | 'guildIntegrationsUpdate': GatewayIntentBits.GuildIntegrations, 30 | 'integrationCreate': GatewayIntentBits.GuildIntegrations, 31 | 'integrationUpdate': GatewayIntentBits.GuildIntegrations, 32 | 'integrationDelete': GatewayIntentBits.GuildIntegrations, 33 | 'webhooksUpdate': GatewayIntentBits.GuildWebhooks, 34 | 'inviteCreate': GatewayIntentBits.GuildInvites, 35 | 'inviteDelete': GatewayIntentBits.GuildInvites, 36 | 'voiceStateUpdate': GatewayIntentBits.GuildVoiceStates, 37 | 'presenceUpdate': GatewayIntentBits.GuildPresences, 38 | 'messageCreate': GatewayIntentBits.GuildMessages | GatewayIntentBits.DirectMessages | GatewayIntentBits.MessageContent, 39 | 'messageUpdate': GatewayIntentBits.GuildMessages | GatewayIntentBits.DirectMessages, 40 | 'messageDelete': GatewayIntentBits.GuildMessages | GatewayIntentBits.DirectMessages, 41 | 'messageDeleteBulk': GatewayIntentBits.GuildMessages, 42 | 'messageReactionAdd': GatewayIntentBits.GuildMessageReactions, 43 | 'messageReactionRemove': GatewayIntentBits.GuildMessageReactions, 44 | 'messageReactionRemoveAll': GatewayIntentBits.GuildMessageReactions, 45 | 'messageReactionRemoveEmoji': GatewayIntentBits.GuildMessageReactions, 46 | 'typingStart': GatewayIntentBits.GuildMessageTyping, 47 | 'channelPinsUpdate': GatewayIntentBits.GuildMessages, 48 | 'guildScheduledEventCreate': GatewayIntentBits.GuildScheduledEvents, 49 | 'guildScheduledEventUpdate': GatewayIntentBits.GuildScheduledEvents, 50 | 'guildScheduledEventDelete': GatewayIntentBits.GuildScheduledEvents, 51 | 'guildScheduledEventUserAdd': GatewayIntentBits.GuildScheduledEvents, 52 | 'guildScheduledEventUserRemove': GatewayIntentBits.GuildScheduledEvents, 53 | 'autoModerationRuleCreate': GatewayIntentBits.AutoModerationConfiguration, 54 | 'autoModerationRuleUpdate': GatewayIntentBits.AutoModerationConfiguration, 55 | 'autoModerationRuleDelete': GatewayIntentBits.AutoModerationConfiguration, 56 | 'autoModerationActionExecution': GatewayIntentBits.AutoModerationExecution, 57 | }; 58 | 59 | // Updated function 60 | function checkMissingIntents(client) { 61 | const missingIntents = new Set(); 62 | 63 | const intents = Number(client.options.intents.bitfield); 64 | 65 | // Check missing intents based on required intents for each event 66 | for (const eventName of Object.keys(client._events)) { 67 | const required = REQUIRED_INTENTS[eventName]; 68 | if (!required) continue; 69 | if ((intents & required) !== required) { 70 | missingIntents.add(required); 71 | } 72 | } 73 | 74 | if (missingIntents.size === 0) return; 75 | 76 | // Map intents to event names 77 | const EventNames = Object.fromEntries( 78 | Object.entries(REQUIRED_INTENTS).map(([eventName, intent]) => [intent, eventName]) 79 | ); 80 | 81 | const missingIntentNames = [...missingIntents].map(intent => { 82 | // Try to map intent value back to an event name, or display 'Unknown Intent' if not found 83 | return EventNames[intent] || `Unknown Intent (${intent})`; 84 | }); 85 | 86 | console.warn('Warning: Missing intents detected:', missingIntentNames); 87 | } 88 | 89 | module.exports = { checkMissingIntents }; 90 | 91 | -------------------------------------------------------------------------------- /src/functions/handlers/similarity.js: -------------------------------------------------------------------------------- 1 | function getSimilarCommands(commandName, commands) { 2 | const similarityThreshold = 0.6; 3 | const partialMatches = []; 4 | const similarCommands = []; 5 | 6 | for (const command of commands) { 7 | const cmdName = command.name; 8 | 9 | if (cmdName.includes(commandName) || commandName.includes(cmdName)) { 10 | partialMatches.push(cmdName); 11 | } 12 | 13 | const similarity = calculateSimilarity(commandName, cmdName); 14 | if (similarity >= similarityThreshold) { 15 | similarCommands.push({ name: cmdName, similarity }); 16 | } 17 | 18 | if (command.aliases && command.aliases.length > 0) { 19 | for (const alias of command.aliases) { 20 | if (alias.includes(commandName) || commandName.includes(alias)) { 21 | partialMatches.push(alias); 22 | } 23 | 24 | const aliasSimilarity = calculateSimilarity(commandName, alias); 25 | if (aliasSimilarity >= similarityThreshold) { 26 | similarCommands.push({ name: alias, similarity: aliasSimilarity }); 27 | } 28 | } 29 | } 30 | } 31 | 32 | const combinedCommands = new Set([...partialMatches, ...similarCommands.map(cmd => cmd.name)]); 33 | const uniqueCommands = Array.from(combinedCommands); 34 | 35 | similarCommands.sort((a, b) => b.similarity - a.similarity); 36 | 37 | return uniqueCommands.sort((a, b) => { 38 | const aIndex = similarCommands.findIndex(cmd => cmd.name === a); 39 | const bIndex = similarCommands.findIndex(cmd => cmd.name === b); 40 | return bIndex - aIndex; 41 | }); 42 | } 43 | 44 | function calculateSimilarity(str1, str2) { 45 | const len1 = str1.length; 46 | const len2 = str2.length; 47 | const matrix = []; 48 | 49 | for (let i = 0; i <= len1; i++) { 50 | matrix[i] = [i]; 51 | } 52 | for (let j = 0; j <= len2; j++) { 53 | matrix[0][j] = j; 54 | } 55 | 56 | for (let i = 1; i <= len1; i++) { 57 | for (let j = 1; j <= len2; j++) { 58 | const cost = str1[i - 1] === str2[j - 1] ? 0 : 1; 59 | matrix[i][j] = Math.min( 60 | matrix[i - 1][j] + 1, 61 | matrix[i][j - 1] + 1, 62 | matrix[i - 1][j - 1] + cost 63 | ); 64 | } 65 | } 66 | 67 | return 1 - matrix[len1][len2] / Math.max(len1, len2); 68 | } 69 | 70 | module.exports = { getSimilarCommands }; 71 | -------------------------------------------------------------------------------- /src/functions/handlers/watchFolders.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const chokidar = require('chokidar'); 4 | const chalk = require('chalk'); 5 | 6 | const commandsPath = path.join(__dirname, '../../commands'); 7 | const eventsPath = path.join(__dirname, '../../events'); 8 | const prefixPath = path.join(__dirname, '../../messages'); 9 | 10 | const commandTemplate = ` 11 | const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); 12 | 13 | module.exports = { 14 | data: new SlashCommandBuilder() 15 | .setName('your-command') 16 | .setDescription('Describe your command here.'), 17 | async execute(interaction, client) { 18 | // Command execution logic goes here 19 | } 20 | }; 21 | `; 22 | 23 | const prefixTemplate = ` 24 | //! This is a basic structure for a prefix command in discoBase using discord.js 25 | 26 | module.exports = { 27 | name: 'command-name', 28 | description: 'command-description.', 29 | //* Optional: Aliases are alternative names for the command. Example: !p will also trigger the ping command. 30 | aliases: ['alaises_1', 'aliases_2'], 31 | // The run function is the main logic that gets executed when the command is called. 32 | run: async (client, message, args) => { 33 | // Command execution logic goes here 34 | }, 35 | }; 36 | `; 37 | 38 | const eventTemplate = ` 39 | module.exports = { 40 | name: 'event-name', 41 | async execute(eventObject, client) { 42 | // Event handling logic goes here 43 | } 44 | }; 45 | `; 46 | 47 | const commandWatcher = chokidar.watch(commandsPath, { 48 | ignored: /(^|[\/\\])\../, 49 | persistent: true, 50 | ignoreInitial: true, 51 | }); 52 | 53 | const eventWatcher = chokidar.watch(eventsPath, { 54 | ignored: /(^|[\/\\])\../, 55 | persistent: true, 56 | ignoreInitial: true, 57 | }); 58 | 59 | const prefixWatcher = chokidar.watch(prefixPath, { 60 | ignored: /(^|[\/\\])\../, 61 | persistent: true, 62 | ignoreInitial: true, 63 | }); 64 | 65 | const logWithStyle = (message, type = 'info') => { 66 | switch (type) { 67 | case 'success': 68 | console.log(chalk.green.bold(`✔ ${message}`)); 69 | break; 70 | case 'error': 71 | console.log(chalk.red.bold(`✖ ${message}`)); 72 | break; 73 | case 'info': 74 | console.log(chalk.blueBright.bold(`ℹ ${message}`)); 75 | break; 76 | case 'add': 77 | console.log(chalk.cyan.bold(`➕ ${message}`)); 78 | break; 79 | default: 80 | console.log(message); 81 | } 82 | }; 83 | 84 | const getRelativePath = (filePath) => { 85 | const srcPath = path.join(__dirname, '../../'); 86 | return path.relative(srcPath, filePath); 87 | }; 88 | 89 | commandWatcher.on('add', (filePath) => { 90 | const ext = path.extname(filePath); 91 | 92 | if (ext === '.js') { 93 | fs.readFile(filePath, 'utf8', (err, data) => { 94 | if (err) throw err; 95 | 96 | if (data.trim().length === 0) { 97 | fs.writeFile(filePath, commandTemplate.trim(), (err) => { 98 | if (err) throw err; 99 | logWithStyle(`Added basic command structure to ${getRelativePath(filePath)}`, 'add'); 100 | }); 101 | } 102 | }); 103 | } 104 | }); 105 | 106 | prefixWatcher.on('add', (filePath) => { 107 | const ext = path.extname(filePath); 108 | 109 | if (ext === '.js') { 110 | fs.readFile(filePath, 'utf8', (err, data) => { 111 | if (err) throw err; 112 | 113 | if (data.trim().length === 0) { 114 | fs.writeFile(filePath, prefixTemplate.trim(), (err) => { 115 | if (err) throw err; 116 | logWithStyle(`Added basic prefix command structure to ${getRelativePath(filePath)}`, 'add'); 117 | }); 118 | } 119 | }); 120 | } 121 | }); 122 | 123 | eventWatcher.on('add', (filePath) => { 124 | const ext = path.extname(filePath); 125 | const eventName = path.basename(filePath, ext); 126 | 127 | if (ext === '.js') { 128 | fs.readFile(filePath, 'utf8', (err, data) => { 129 | if (err) throw err; 130 | 131 | if (data.trim().length === 0) { 132 | fs.writeFile(filePath, eventTemplate.trim(), (err) => { 133 | if (err) throw err; 134 | logWithStyle(`Added basic event structure for ${eventName} to ${getRelativePath(filePath)}`, 'add'); 135 | }); 136 | } 137 | }); 138 | } 139 | }); 140 | 141 | commandWatcher.on('error', (error) => logWithStyle(`Command watcher error: ${error}`, 'error')); 142 | eventWatcher.on('error', (error) => logWithStyle(`Event watcher error: ${error}`, 'error')); 143 | prefixWatcher.on('error', (error) => logWithStyle(`Prefix watcher error: ${error}`, 'error')); 144 | 145 | logWithStyle('[Info] Watching for new files.', 'info'); 146 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { Client, GatewayIntentBits, Partials } = require(`discord.js`); 2 | const client = new Client({ intents: ['GuildMessages', 'MessageContent', 'DirectMessages', 'GuildMembers', 'Guilds'], }); //Guilds, GuildMembers : REQUIRED 3 | const chalk = require('chalk'); 4 | const config = require('../config.json'); 5 | const fs = require('fs'); 6 | const { eventsHandler } = require('./functions/handlers/handelEvents'); 7 | const path = require('path'); 8 | const { checkMissingIntents } = require('./functions/handlers/requiredIntents'); 9 | const { antiCrash } = require('./functions/handlers/antiCrash'); 10 | antiCrash(); 11 | require('./functions/handlers/watchFolders'); 12 | const adminFolderPath = path.join(__dirname, '../admin'); 13 | const dashboardFilePath = path.join(adminFolderPath, 'dashboard.js'); 14 | 15 | const eventsPath = './events'; 16 | 17 | const errorsDir = path.join(__dirname, '../../../errors'); 18 | 19 | function ensureErrorDirectoryExists() { 20 | if (!fs.existsSync(errorsDir)) { 21 | fs.mkdirSync(errorsDir); 22 | } 23 | } 24 | 25 | function logErrorToFile(error) { 26 | ensureErrorDirectoryExists(); 27 | 28 | // Convert the error object into a string, including the stack trace 29 | const errorMessage = `${error.name}: ${error.message}\n${error.stack}`; 30 | 31 | const fileName = `${new Date().toISOString().replace(/:/g, '-')}.txt`; 32 | const filePath = path.join(errorsDir, fileName); 33 | 34 | fs.writeFileSync(filePath, errorMessage, 'utf8'); 35 | } 36 | 37 | 38 | (async () => { 39 | try { 40 | await client.login(config.bot.token); 41 | console.log(chalk.green.bold('SUCCESS: ') + 'Bot logged in successfully!'); 42 | if (fs.existsSync(adminFolderPath) && fs.existsSync(dashboardFilePath)) { 43 | require(dashboardFilePath); 44 | console.log(chalk.green(chalk.green.bold('SUCCESS: Admin dashboard loaded successfully!.'))); 45 | 46 | } 47 | require('./functions/handlers/functionHandler'); 48 | 49 | await eventsHandler(client, path.join(__dirname, eventsPath)); 50 | checkMissingIntents(client); 51 | } catch (error) { 52 | if (error.message === "An invalid token was provided.") { 53 | console.error(chalk.red.bold('ERROR: ') + 'The token provided for the Discord bot is invalid. Please check your configuration.'); 54 | logErrorToFile(error) 55 | } else { 56 | console.error(chalk.red.bold('ERROR: ') + 'Failed to log in:', error); 57 | logErrorToFile(error) 58 | } 59 | } 60 | })(); 61 | 62 | module.exports = client; 63 | 64 | 65 | 66 | //* You can start writing your custom bot logic from here. Add new features, commands, or events! 67 | -------------------------------------------------------------------------------- /src/messages/Community/ping.js: -------------------------------------------------------------------------------- 1 | //! This is a basic structure for a prefix command in a discoBase using discord.js 2 | 3 | module.exports = { 4 | //* Required: Command name, used to trigger the command. Example: !ping 5 | name: "ping", 6 | 7 | //* Required: A brief description of what the command does, useful for help commands. 8 | description: "This is the ping command.", 9 | 10 | //* Optional: Aliases are alternative names for the command. Example: !p will also trigger the ping command. 11 | aliases: ['p'], 12 | 13 | //? Optional: Permissions that the bot requires to execute the command. 14 | //? botPermissions: ['SendMessages'], // Example: bot needs permission to send messages. 15 | 16 | //? Optional: Permissions that the user requires to use this command. Uncomment if needed. 17 | //? userPermissions: ['ManageMessages'], // Example: Only users with Manage Messages permission can use this command. 18 | 19 | //? Optional: Set this to true if only bot admins can use this command. 20 | //? adminOnly: true, 21 | 22 | //? Optional: Set this to true if only the bot owner can use this command. 23 | //? ownerOnly: true, 24 | 25 | //? Optional: Set this to true if only developers can use this command. 26 | //? devOnly: true, so if this true this slash command will only register for the server IDs you provided in config.json 27 | 28 | //? Optional: Cooldown period for the command in seconds to prevent spam. 29 | //? cooldown: 10, 30 | 31 | // The run function is the main logic that gets executed when the command is called. 32 | run: async (client, message, args) => { 33 | const ping = Date.now() - message.createdTimestamp; 34 | 35 | const latency = Math.abs(ping); 36 | const latencyFormatted = `${latency.toString().substring(0, 2)}ms`; 37 | const emoji = "⏱️"; 38 | 39 | message.reply(`${emoji} Pong! Latency is ${latencyFormatted}!`); 40 | }, 41 | }; 42 | --------------------------------------------------------------------------------