Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: scaffold community server discord bot #220

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/discord-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/
node_modules/
cooldownDB.sqlite
tsconfig.tsbuildinfo
21 changes: 21 additions & 0 deletions apps/discord-bot/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 RileCraft

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
116 changes: 116 additions & 0 deletions apps/discord-bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<p align="center">
<img src="https://media.discordapp.net/attachments/774290264764055582/1093484780525469757/A_banner_for_a_discord_bots_template_made_using_discord.js.png" height="200" width="400"><br>
<img src="https://img.shields.io/badge/version-1.0.7-05122A?style=for-the-badge">
<a href="https://discord.gg/VStdRr8nP2"><img src="https://img.shields.io/badge/discord-invite-5865f2?style=for-the-badge&logo=discord&logoColor=white"></a>
<img src="https://img.shields.io/github/issues/RileCraft/DiscordBot-Template-ts.svg?style=for-the-badge">
<img src="https://img.shields.io/github/forks/RileCraft/DiscordBot-Template-ts.svg?style=for-the-badge">
<img src="https://img.shields.io/github/stars/RileCraft/DiscordBot-Template-ts.svg?style=for-the-badge">
Comment on lines +4 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Update repository links for this project

The badge links still point to the original template repository (RileCraft/DiscordBot-Template-ts). These should be updated to point to this project's repository.

🧰 Tools
🪛 Markdownlint (0.37.0)

4-4: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


5-5: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


6-6: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


7-7: null
Images should have alternate text (alt text)

(MD045, no-alt-text)

</p>
Comment on lines +1 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add alt text to images for accessibility

Images should have descriptive alt text for accessibility.

Update the image tags:

-  <img src="https://media.discordapp.net/attachments/774290264764055582/1093484780525469757/A_banner_for_a_discord_bots_template_made_using_discord.js.png" height="200" width="400"><br>
-  <img src="https://img.shields.io/badge/version-1.0.7-05122A?style=for-the-badge">
+  <img src="https://media.discordapp.net/attachments/774290264764055582/1093484780525469757/A_banner_for_a_discord_bots_template_made_using_discord.js.png" alt="Discord Bot Template Banner" height="200" width="400"><br>
+  <img src="https://img.shields.io/badge/version-1.0.7-05122A?style=for-the-badge" alt="Version 1.0.7">

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Markdownlint (0.37.0)

2-2: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


3-3: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


4-4: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


5-5: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


6-6: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


7-7: null
Images should have alternate text (alt text)

(MD045, no-alt-text)


# Discord Bot Template TS

The Discord Bot Template provides a solid foundation for creating feature-rich Discord bots using Discord.js. It includes various managers for handling message commands, buttons, select menus, slash commands, context menus, and modal forms. The template offers customization options, colorful logging, and a simple code structure.
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add community server context to introduction

The introduction should be customized to include the specific purpose of this bot implementation - linking server roles to third-party applications (as mentioned in PR #217).

Add a section like:

This Discord bot implementation serves as the foundation for the community server, specifically designed to:
- Manage server roles in conjunction with third-party applications
- Integrate with the community portal (see issue #217)
- Facilitate automated role assignments based on external application states


## Changelog

### IMPORTANT UPDATE 1.0.7

- **Fixed Windows Support and SlashCommands & ContextMenus not Registering.**
- Added dependency of `simple-json-db` for the cooldown system as i rage quit and can't do it with `fs` myself.
- Fixed subDirectories not working for commands.
- Latest Discord.js adaptation.
- Following JavaScript Naming Convention.
- Removed `node-recursive-directory` dependency.
- Support for `AutoCompleteInteraction` added.
- Converted from `CommonJS` to `ESM Module`.
- Improved handling of all events, commands with lower memory usage.
- Main file `bot.ts` has been shifted to `src`.
- Config file has been shifted to `Src`.
- Moved from `Collections` to `Map`.
- `messageCommandsAliases` has been renamed to `messageCommands_Aliases`
- `Quick.DB` has been removed and instead all cooldowns data will be now stored in `CooldownDB.txt` in the root directory using `fs`.
- Refactored command options.
- `chalk` has been replaced with `tasai`.
- Extended all command options support to interactions.
- All custom types and interfaces are exported from `./src/types.ts`.
- `SlashCommands` and `ContextMenus` has been seperated into different folders and managed differently.
- `SlashCommands` have been simplified as now instead of `Guilds/<GuildID>/<Files Here>`, you can use `guilds: ["GUILD ID"]`
- In a slashCommand you do not need to assign the `type: ApplicationCommandType` property as the handler by default assumes it as `ChatInput`.

Comment on lines +14 to +39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove or adapt changelog section

Since this is a new implementation based on the template, the changelog section should either be:

  1. Removed entirely, or
  2. Started fresh with your initial implementation version

Also, maintain professional language in documentation:

-Added dependency of `simple-json-db` for the cooldown system as i rage quit and can't do it with `fs` myself.
+Added dependency of `simple-json-db` for the cooldown system management.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~19-~19: Did you mean “I”?
Context: ...ple-json-dbfor the cooldown system as i rage quit and can't do it withfs` mys...

(I_LOWERCASE_PREMIUM)


[misspelling] ~19-~19: This word is normally spelled with a hyphen.
Context: ...e-json-dbfor the cooldown system as i rage quit and can't do it withfs` myself. - Fix...

(EN_COMPOUNDS_RAGE_QUIT)

## Documentation

For detailed documentation on command options and managers, please refer to the following links:

### Command Options

- [ReturnErrors](/.github/DOCS/commandOptions/returnErrors.md)
- [Ignore](/.github/DOCS/commandOptions/ignore.md)
- [AllClientPermissions](/.github/DOCS/commandOptions/allClientPermissions.md)
- [AllowBots](/.github/DOCS/commandOptions/allowBots.md)
- [AllowInDms](/.github/DOCS/commandOptions/allowInDms.md)
- [AllUserPermissions](/.github/DOCS/commandOptions/allUserPermissions.md)
- [AnyClientPermissions](/.github/DOCS/commandOptions/anyClientPermissions.md)
- [AnyUserPermissions](/.github/DOCS/commandOptions/anyUserPermissions.md)
- [ChannelCooldown](/.github/DOCS/commandOptions/channelCooldown.md)
- [GlobalCooldown](/.github/DOCS/commandOptions/globalCooldown.md)
- [GuildCooldown](/.github/DOCS/commandOptions/guildCooldown.md)
- [OnlyChannels](/.github/DOCS/commandOptions/onlyChannels.md)
- [OnlyGuilds](/.github/DOCS/commandOptions/onlyGuilds.md)
- [OnlyRoles](/.github/DOCS/commandOptions/onlyRoles.md)
- [OnlyUsers](/.github/DOCS/commandOptions/onlyUsers.md)
- [OwnerOnly](/.github/DOCS/commandOptions/ownerOnly.md)

### Managers

- [MessageCommands](/.github/DOCS/managers/messageCommands.md)
- [SelectMenus](/.github/DOCS/managers/selectMenus.md)
- [Buttons](/.github/DOCS/managers/buttons.md)
- [Events](/.github/DOCS/managers/events.md)
- [ContextMenus](/.github/DOCS/managers/contextMenus.md)
- [SlashCommands](/.github/DOCS/managers/slashCommands.md)
- [ModalForms](/.github/DOCS/managers/modalForms.md)

## Features

- Colorful and organized logging.
- Customization options to suit your needs.
- Supports management of message commands, buttons, select menus, slash commands, context menus, and modal forms.
- Includes a variety of commonly used command options (not applicable to events).
- Supports management of custom events.
- Simple and understandable code structure.

Comment on lines +40 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Customize documentation for this implementation

  1. Update documentation links to point to this project's repository
  2. Add documentation specific to community server features:
    • Role management API
    • Third-party application integration
    • Portal integration specifics
  3. Add setup instructions for development environment
🧰 Tools
🪛 LanguageTool

[style] ~78-~78: The phrase “a variety of” may be wordy. To make your writing clearer, consider replacing it.
Context: ...text menus, and modal forms. - Includes a variety of commonly used command options (not appl...

(A_VARIETY_OF)

## Notes

- Recommended Node.js version: 16 and above.
- Global slash commands and context menus may take time to refresh as it is controlled by Discord.
- Guild commands may take time to refresh if there are a large number of different guild commands.
- Collections where command and event data is stored and used:
- `<Client>.messageCommands`: Message commands cache
- `<Client>.messageCommands_Aliases`: Message command aliases cache
- `<Client>.events`: Client events cache
- `<Client>.buttonCommands`: Button interactions cache
- `<Client>.selectMenus`: Select menu interactions cache
- `<Client>.modalForms`: Modal form interactions cache
- `<Client>.slashCommands`: Slash commands cache
- `<Client>.contextMenus`: ContextMenus commands cache

Comment on lines +82 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add documentation for community server integration

The README lacks information about the specific purpose of this bot in the community server context.

Consider adding a new section:

## Community Server Integration

This bot serves as a foundation for linking server roles to third-party applications. Key features include:
- Role management integration capabilities
- Third-party application linking support
- Portal integration (see issue #217)

For development and contribution guidelines specific to the community server features, see [CONTRIBUTING.md].
🧰 Tools
🪛 LanguageTool

[style] ~86-~86: Specify a number, remove phrase, or simply use “many” or “numerous”
Context: ...s may take time to refresh if there are a large number of different guild commands. - Collections...

(LARGE_NUMBER_OF)

## Installation

To get started with the Discord Bot Template, follow these steps:

1. Clone the repository by downloading it as a ZIP file or running the command `git clone https://github.com/rilecraft/discordbot-template-ts`.
2. Navigate to the template's directory and run the command `npm install` (make sure npm is installed).
3. Once all the required modules are installed, open the `src/config.ts` file and fill in the necessary information.
4. Run the command `npm run build && npm run start` to start the bot.
Comment on lines +101 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Update installation instructions

The installation instructions need to be updated:

  1. Update repository URL to point to this project
  2. Add any specific configuration needed for community server integration
  3. Include environment variables setup for portal integration


You can also use `npm run dev` to run the application in development mode, which will refresh the application after any
changes. If the interaction elements loading logs are excessive, set `LOG_READY_EXPLICIT` to `false` in `src/config.ts`.

## Contribution

Contributions to the Discord Bot Template are welcome. To contribute, please follow these guidelines:

1. Fork the `Unstable` branch. **Important: All changes must be made to the Unstable branch.**
2. Make your changes in your forked repository.
3. Open a pull request to the `Unstable` branch, and it will be reviewed promptly.
4. If everything checks out, the pull request will be merged.
Comment on lines +109 to +116
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Align contribution guidelines with project standards

The contribution guidelines should be updated to:

  1. Reference the correct branch strategy for this project
  2. Include specific guidelines for community server features
  3. Add testing requirements for role management features
  4. Include security considerations for handling user data

5 changes: 5 additions & 0 deletions apps/discord-bot/nodemon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"watch": ["src"],
"ext": "ts",
"exec": "tsc --incremental && node dist/bot.js"
}
25 changes: 25 additions & 0 deletions apps/discord-bot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "discord-bot",
"type": "module",
"description": "",
"license": "",
"main": "./src/bot.ts",
"scripts": {
"build": "tsc && echo 'Finishing Building'",
"dev": "nodemon",
"start": "node ./dist/bot.js"
},
"dependencies": {
"discord.js": "^14.14.1",
"ms": "^2.1.3",
"simple-json-db": "^2.0.0",
"tasai": "^1.0.0",
"undici-types": "^5.26.5"
},
"devDependencies": {
"@types/ms": "^0.7.34",
"@types/node": "^20.11.5",
"nodemon": "^3.1.7",
"typescript": "^5.3.3"
}
}
53 changes: 53 additions & 0 deletions apps/discord-bot/src/bot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { DiscordClient } from 'discord.js'
import type { ButtonCommand, ClientEvent, ContextMenu, MessageCommand, ModalForm, SelectMenu, SlashCommand } from './types.js'
import { dirname } from 'node:path'
import { Client, GatewayIntentBits, Partials } from 'discord.js'
import JSONdb from 'simple-json-db'
import { BOT_TOKEN } from './config.js'
import { ButtonManager } from './structures/managers/buttonCommands.js'
import { EventManager } from './structures/managers/events.js'
import { MessageCMDManager } from './structures/managers/messageCommands.js'
import { ModalManager } from './structures/managers/modalForms.js'
import { SelectMenuManager } from './structures/managers/selectMenus.js'
import { SlashManager } from './structures/managers/slashCommands.js'

const __dirname: string = dirname(import.meta.url)
export const rootPath = __dirname;

(async (): Promise<void> => {
const client: DiscordClient = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildPresences,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.MessageContent, // Only for bots with message content intent access.
GatewayIntentBits.DirectMessageReactions,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildWebhooks,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildInvites,
],
Comment on lines +19 to +31
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize Discord intents for security

The bot is requesting more intents than might be necessary, including the privileged MessageContent intent. Consider:

  1. Removing unnecessary intents to follow the principle of least privilege
  2. Documenting why each intent is needed
  3. Ensuring the bot has approval for privileged intents
 intents: [
   GatewayIntentBits.Guilds,
-  GatewayIntentBits.GuildMessages,
-  GatewayIntentBits.GuildPresences,
-  GatewayIntentBits.DirectMessages,
-  GatewayIntentBits.MessageContent, // Only for bots with message content intent access.
-  GatewayIntentBits.DirectMessageReactions,
   GatewayIntentBits.GuildMembers,
-  GatewayIntentBits.GuildMessageReactions,
-  GatewayIntentBits.GuildWebhooks,
   GatewayIntentBits.GuildVoiceStates,
-  GatewayIntentBits.GuildInvites,
 ],

Committable suggestion skipped: line range outside the PR's diff.

partials: [Partials.Channel],
})

client.cooldownDB = new JSONdb('./cooldownDB.json')

client.messageCommands = new Map<string, MessageCommand>()
client.messageCommands_Aliases = new Map<string, string>()
client.events = new Map<string, ClientEvent>()
client.buttonCommands = new Map<string, ButtonCommand>()
client.selectMenus = new Map<string, SelectMenu>()
client.modalForms = new Map<string, ModalForm>()
client.contextMenus = new Map<string, ContextMenu>()
client.slashCommands = new Map<string, SlashCommand>()

await MessageCMDManager(client, __dirname)
await EventManager(client, __dirname)
await ButtonManager(client, __dirname)
await SelectMenuManager(client, __dirname)
await ModalManager(client, __dirname)
await client.login(BOT_TOKEN)
await SlashManager(client, __dirname) // Includes context menu handling as they belong to same command type.
})()
Comment on lines +46 to +53
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling and startup logging

The initialization sequence lacks error handling and logging. Consider:

  1. Adding try-catch blocks for each manager initialization
  2. Adding startup logging for better observability
  3. Implementing a graceful shutdown mechanism
+  try {
     await MessageCMDManager(client, __dirname)
+    console.log('Message commands loaded')
     await EventManager(client, __dirname)
+    console.log('Events loaded')
     await ButtonManager(client, __dirname)
+    console.log('Button commands loaded')
     await SelectMenuManager(client, __dirname)
+    console.log('Select menus loaded')
     await ModalManager(client, __dirname)
+    console.log('Modal forms loaded')
     await client.login(BOT_TOKEN)
+    console.log('Bot logged in successfully')
     await SlashManager(client, __dirname)
+    console.log('Slash commands loaded')
+  } catch (error) {
+    console.error('Failed to initialize bot:', error)
+    process.exit(1)
+  }
+
+  process.on('SIGTERM', () => {
+    console.log('Received SIGTERM, shutting down gracefully')
+    client.destroy()
+    process.exit(0)
+  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await MessageCMDManager(client, __dirname)
await EventManager(client, __dirname)
await ButtonManager(client, __dirname)
await SelectMenuManager(client, __dirname)
await ModalManager(client, __dirname)
await client.login(BOT_TOKEN)
await SlashManager(client, __dirname) // Includes context menu handling as they belong to same command type.
})()
try {
await MessageCMDManager(client, __dirname)
console.log('Message commands loaded')
await EventManager(client, __dirname)
console.log('Events loaded')
await ButtonManager(client, __dirname)
console.log('Button commands loaded')
await SelectMenuManager(client, __dirname)
console.log('Select menus loaded')
await ModalManager(client, __dirname)
console.log('Modal forms loaded')
await client.login(BOT_TOKEN)
console.log('Bot logged in successfully')
await SlashManager(client, __dirname) // Includes context menu handling as they belong to same command type.
console.log('Slash commands loaded')
} catch (error) {
console.error('Failed to initialize bot:', error)
process.exit(1)
}
process.on('SIGTERM', () => {
console.log('Received SIGTERM, shutting down gracefully')
client.destroy()
process.exit(0)
})
})()

5 changes: 5 additions & 0 deletions apps/discord-bot/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const LOG_READY_EXPLICIT = true // Set to 'false' to reduce terminal spam for development server

export const PREFIX: Array<string> = ['Bot Prefix']
export const BOT_TOKEN: string = 'Bot Token'
export const OWNER_IDS: Array<string> = ['Bot Owner Discord ID']
Comment on lines +1 to +5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Store sensitive configuration in environment variables.
BOT_TOKEN and OWNER_IDS should ideally be stored in secure configuration, such as environment variables or a secrets manager, to avoid accidental leaks.

15 changes: 15 additions & 0 deletions apps/discord-bot/src/events/errorManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ClientEvent } from '../types.js'
// import process from 'node:process'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Uncomment the process import

The process import is required for the error handlers. Remove the comment to enable proper error handling functionality.

-// import process from 'node:process'
+import process from 'node:process'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// import process from 'node:process'
import process from 'node:process'


export const Event: ClientEvent = {
name: 'errorManager',
customEvent: true,
run: (): void => {
// process.on('unhandledRejection', (error: Error) => {
// console.log(error)
// })
// process.on('uncaughtException', (error: Error) => {
// console.log(error)
// })
Comment on lines +8 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Uncomment and enhance error handling implementation

The error handlers are currently commented out, leaving the bot without any error management. This could lead to silent failures or unexpected crashes in production.

Consider this enhanced implementation:

-    // process.on('unhandledRejection', (error: Error) => {
-    //   console.log(error)
-    // })
-    // process.on('uncaughtException', (error: Error) => {
-    //   console.log(error)
-    // })
+    process.on('unhandledRejection', (error: Error) => {
+      console.error('[Unhandled Rejection]', {
+        name: error.name,
+        message: error.message,
+        stack: error.stack
+      });
+      // TODO: Add error reporting service integration
+    });
+
+    process.on('uncaughtException', (error: Error) => {
+      console.error('[Uncaught Exception]', {
+        name: error.name,
+        message: error.message,
+        stack: error.stack
+      });
+      // TODO: Add error reporting service integration
+      
+      // Gracefully shutdown after uncaught exception
+      process.exit(1);
+    });

Committable suggestion skipped: line range outside the PR's diff.

},
} // Error Handler to avoid the bot from crashing on error.
68 changes: 68 additions & 0 deletions apps/discord-bot/src/events/interactionCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { DiscordClient, Interaction } from 'discord.js'
import type { ButtonCommand, ClientEvent, ContextMenu, ModalForm, SelectMenu, SlashCommand } from '../types.js'
import commandOptionsChecker from '../structures/commandOptions/processor.js'

export const Event: ClientEvent = {
name: 'interactionCreate',
run: async (interaction: Interaction<'cached'>, client: DiscordClient): Promise<void> => {
if (interaction.isChatInputCommand()) {
const slashCommand: SlashCommand | undefined = client.slashCommands?.get(interaction.commandName)
if (!slashCommand)
return

if (!await commandOptionsChecker(client, interaction, slashCommand, 'SlashCommand'))
return
else slashCommand.run(interaction, client)
}

else if (interaction.isAutocomplete()) {
const slashCommand: SlashCommand | undefined = client.slashCommands?.get(interaction.commandName)
if (!slashCommand || !slashCommand.autocomplete)
return

if (!await commandOptionsChecker(client, interaction, slashCommand, 'SlashCommand'))
return
else slashCommand.autocomplete(interaction, client)
}

else if (interaction.isContextMenuCommand()) {
const contextMenu: ContextMenu | undefined = client.contextMenus?.get(interaction.commandName)
if (!contextMenu)
return

if (!await commandOptionsChecker(client, interaction, contextMenu, 'ContextMenu'))
return
else contextMenu.run(interaction, client)
}

else if (interaction.isAnySelectMenu()) {
const selectMenuCommand: SelectMenu | undefined = client.selectMenus?.get(interaction.values[0]) ?? client.selectMenus?.get(interaction.customId)
if (!selectMenuCommand)
return

if (!await commandOptionsChecker(client, interaction, selectMenuCommand, 'SelectMenu'))
return
else selectMenuCommand.run(interaction, client)
}

else if (interaction.isButton()) {
const buttonInteraction: ButtonCommand | undefined = client.buttonCommands?.get(interaction.customId)
if (!buttonInteraction)
return

if (!await commandOptionsChecker(client, interaction, buttonInteraction, 'Button'))
return
else buttonInteraction.run(interaction, client)
}
Comment on lines +38 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consolidate repeated checks with a helper
Each block checks for existence of the command, calls commandOptionsChecker, and then runs. This repeated pattern could be factored into a helper function to reduce duplication and improve readability.


else if (interaction.isModalSubmit()) {
const modalInteraction: ModalForm | undefined = client.modalForms?.get(interaction.customId)
if (!modalInteraction)
return

if (!await commandOptionsChecker(client, interaction, modalInteraction, 'ModalForm'))
return
else modalInteraction.run(interaction, client)
};
Comment on lines +8 to +66
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor interaction handling to reduce duplication

The interaction handling logic follows a repetitive pattern. Consider refactoring to reduce duplication and improve maintainability.

Create a handler factory:

interface InteractionHandler {
  getCommand: (interaction: Interaction) => Command | undefined;
  validateCommand: (command: Command) => boolean;
  executeCommand: (command: Command, interaction: Interaction, client: DiscordClient) => Promise<void>;
}

const handlers: Record<string, InteractionHandler> = {
  chatInput: {
    getCommand: (interaction) => client.slashCommands?.get(interaction.commandName),
    validateCommand: (command) => !!command,
    executeCommand: async (command, interaction, client) => {
      if (await commandOptionsChecker(client, interaction, command, 'SlashCommand')) {
        command.run(interaction, client);
      }
    }
  },
  // Add other handlers...
};

This would make the code more maintainable and easier to extend.

},
} // InteractionCreate event to handle all interactions and execute them.
Comment on lines +1 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for command execution

The command execution lacks try-catch blocks for error handling. Consider adding error boundaries.

async function executeCommand(command: Command, interaction: Interaction, client: DiscordClient) {
  try {
    await command.run(interaction, client);
  } catch (error) {
    console.error(`Error executing command ${command.name}:`, error);
    // Handle error appropriately
    await interaction.reply({ 
      content: 'An error occurred while executing the command',
      ephemeral: true 
    }).catch(console.error);
  }
}

30 changes: 30 additions & 0 deletions apps/discord-bot/src/events/messageCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { DiscordClient, Message } from 'discord.js'
import type { ClientEvent, MessageCommand } from '../types.js'
import { PREFIX } from '../config.js'
import commandOptionsChecker from '../structures/commandOptions/processor.js'

export const Event: ClientEvent = {
name: 'messageCreate',
run: (message: Message, client: DiscordClient): void => {
if (!Array.isArray(PREFIX))
return
PREFIX.forEach(async (botPrefix: string) => {
if (!message.content.startsWith(botPrefix))
return
const commandName: string = message.content.toLowerCase().slice(botPrefix.length).trim().split(' ')[0]
const command: MessageCommand | undefined = client.messageCommands?.get(commandName) ?? client.messageCommands?.get(client.messageCommands_Aliases?.get(commandName) ?? '')
Comment on lines +14 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety and error handling in command resolution

The command resolution chain has multiple optional chaining operations that could be simplified and made more type-safe.

-      const command: MessageCommand | undefined = client.messageCommands?.get(commandName) ?? client.messageCommands?.get(client.messageCommands_Aliases?.get(commandName) ?? '')
+      const messageCommands = client.messageCommands
+      if (!messageCommands) return
+      
+      const command = messageCommands.get(commandName) ?? 
+        messageCommands.get(client.messageCommands_Aliases?.get(commandName) ?? '')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const commandName: string = message.content.toLowerCase().slice(botPrefix.length).trim().split(' ')[0]
const command: MessageCommand | undefined = client.messageCommands?.get(commandName) ?? client.messageCommands?.get(client.messageCommands_Aliases?.get(commandName) ?? '')
const commandName: string = message.content.toLowerCase().slice(botPrefix.length).trim().split(' ')[0]
const messageCommands = client.messageCommands
if (!messageCommands) return
const command = messageCommands.get(commandName) ??
messageCommands.get(client.messageCommands_Aliases?.get(commandName) ?? '')

if (!command)
return
const args: Array<string> = message.content.slice(botPrefix.length).trim().slice(commandName.length).trim().split(' ')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for command arguments

The current implementation splits arguments without validation, which could lead to security issues.

-      const args: Array<string> = message.content.slice(botPrefix.length).trim().slice(commandName.length).trim().split(' ')
+      const args: Array<string> = message.content
+        .slice(botPrefix.length)
+        .trim()
+        .slice(commandName.length)
+        .trim()
+        .split(' ')
+        .filter(arg => arg.length > 0)
+        .map(arg => arg.replace(/[^\w\s-]/gi, '')) // Basic sanitization
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const args: Array<string> = message.content.slice(botPrefix.length).trim().slice(commandName.length).trim().split(' ')
const args: Array<string> = message.content
.slice(botPrefix.length)
.trim()
.slice(commandName.length)
.trim()
.split(' ')
.filter(arg => arg.length > 0)
.map(arg => arg.replace(/[^\w\s-]/gi, '')) // Basic sanitization

const processedCommandOptionsChecker: boolean = await commandOptionsChecker(client, message, command, 'MessageCommand')

if (!command.allowInDms && !message.guild)
return
if (!command.allowBots && message.author.bot)
return

if (processedCommandOptionsChecker)
await command.run(client, message, args)
})
Comment on lines +11 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace forEach with for...of for async operations

Using forEach with async operations can lead to race conditions and doesn't properly handle promise rejections.

-    PREFIX.forEach(async (botPrefix: string) => {
+    for (const botPrefix of PREFIX) {
+      try {
         if (!message.content.startsWith(botPrefix))
-          return
+          continue;
         // ... rest of the code ...
         if (processedCommandOptionsChecker)
           await command.run(client, message, args)
+      } catch (error) {
+        console.error('Error processing command:', error);
+        // Consider sending an error message to the channel
+      }
-    })
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PREFIX.forEach(async (botPrefix: string) => {
if (!message.content.startsWith(botPrefix))
return
const commandName: string = message.content.toLowerCase().slice(botPrefix.length).trim().split(' ')[0]
const command: MessageCommand | undefined = client.messageCommands?.get(commandName) ?? client.messageCommands?.get(client.messageCommands_Aliases?.get(commandName) ?? '')
if (!command)
return
const args: Array<string> = message.content.slice(botPrefix.length).trim().slice(commandName.length).trim().split(' ')
const processedCommandOptionsChecker: boolean = await commandOptionsChecker(client, message, command, 'MessageCommand')
if (!command.allowInDms && !message.guild)
return
if (!command.allowBots && message.author.bot)
return
if (processedCommandOptionsChecker)
await command.run(client, message, args)
})
for (const botPrefix of PREFIX) {
try {
if (!message.content.startsWith(botPrefix))
continue;
const commandName: string = message.content.toLowerCase().slice(botPrefix.length).trim().split(' ')[0]
const command: MessageCommand | undefined = client.messageCommands?.get(commandName) ?? client.messageCommands?.get(client.messageCommands_Aliases?.get(commandName) ?? '')
if (!command)
return
const args: Array<string> = message.content.slice(botPrefix.length).trim().slice(commandName.length).trim().split(' ')
const processedCommandOptionsChecker: boolean = await commandOptionsChecker(client, message, command, 'MessageCommand')
if (!command.allowInDms && !message.guild)
return
if (!command.allowBots && message.author.bot)
return
if (processedCommandOptionsChecker)
await command.run(client, message, args)
} catch (error) {
console.error('Error processing command:', error);
// Consider sending an error message to the channel
}
}

},
} // MessageCreate event to handle all messages and execute messageCommands (if found).
Loading
Loading