Compare commits

..

14 Commits

Author SHA1 Message Date
05c441b34e Update README.md 2024-10-03 18:11:38 -04:00
2f60c8b7f1 Update README.md 2024-10-03 18:10:59 -04:00
67cd439a7f Update README.md 2024-10-03 18:03:40 -04:00
14c3235802 Update README.md 2024-10-03 18:01:46 -04:00
dlinux-host
7e7ddcb45f Adding User App Support 2024-10-03 18:00:03 -04:00
Raven Scott
26621c9866 modal patch to interactionCreate 2023-04-22 23:11:37 +02:00
Raven Scott
a31234b717 adding discord modal example 2023-04-22 23:08:04 +02:00
Raven Scott
1af66c7c00 Merge branch 'master' of git.codingvm.codes:snxraven/DiscordJS-v13-Template 2022-10-12 00:56:58 -04:00
Raven Scott
9420839679 Changing bitfield to no longer need privileged intents 2022-10-12 00:56:45 -04:00
Raven Scott
93c3dfa65e Adding an Example ContentMenu Command 2022-09-28 19:05:07 -04:00
Raven Scott
cbc6740e46 Removing files 2022-09-28 18:02:34 -04:00
Raven Scott
d88e07a7ed adding a private option within the command config and adding ephemeral support by default 2022-09-28 18:00:36 -04:00
Raven Scott
5d5c9e9a48 update 2022-09-22 12:01:36 -04:00
snxraven
0910992bdf Merge pull request 'Update 'README.md'' (#2) from LauraOrchid/DiscordJS-v14-Template:lauraorchid-patch-1 into master
Reviewed-on: https://git.codingvm.codes/snxraven/DiscordJS-v14-Template/pulls/2
2022-07-23 12:46:04 +00:00
8 changed files with 318 additions and 34 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
.env
package-lock.json

175
README.md
View File

@ -1,35 +1,170 @@
# Discord-Linux Discord.JS v14 Base Template # Discord Bot with Slash Commands and Modals
Only 3 dependencies required to run this bot! This project is a fully-featured Discord bot written in Node.js, using the `discord.js` library. It includes functionalities like handling slash commands, context menu commands, and displaying modals for user input.
Message intents are NOT supported in this bot, this is due to the verification that Discord is enabling. ## Features
Structure: - **Slash Commands**: Commands that can be triggered using `/` in Discord, like `/ping` to check bot latency.
- **Modals**: Interactive modals where users can input data, for example, `/modal-example`.
- **Context Menu Commands**: Right-click context menu commands for users or messages.
- **Ephemeral Replies**: Ability to send private, ephemeral replies to users.
- **Dynamic Command Registration**: Automatically registers commands based on the project directory structure.
- **User Installed App Support**: Automatically registers commands global to Discord via User Apps
**commands** - This folder contains commands ## Installation
**event** - This folder contains files related to discord.js events. (Like "ready", "interactionCreate") ### Prerequisites
**handler** - This folder contains files that read the commands folders contents. Ensure you have the following installed:
**index.js** - This is the main file to run the bot. - [Node.js](https://nodejs.org/) (v16.6.0 or higher)
- [Discord.js](https://discord.js.org/) (v14.x)
- A Discord Bot Token (see [here](https://discord.com/developers/docs/getting-started#configuring-your-bot) for instructions)
### Clone the Repository
```bash
1) Use ```npm i ``` git clone https://git.ssh.surf/snxraven/DiscordJS-v14-Template.git
cd DiscordJS-v14-Template
2) Create a .env file ``` touch .env```
3) Edit .env
```
TOKEN = token
``` ```
4) Go to Handler -- > index.js and change "GUIDIDHERE" to your Discord Server's Guild ID ### Install Dependencies
5) Go into https://discord.com/developers/applications and enable Privileged Intents. ```bash
npm install
```
6) Run the bot ```node index.js``` ### Environment Variables
Create a `.env` file in the root of your project directory with the following content:
Want to make this better? Issue a pull request! ```
TOKEN=YOUR_DISCORD_BOT_TOKEN
DISCORD_CLIENT_ID=APPIDHERE
```
Replace `YOUR_DISCORD_BOT_TOKEN` with your actual bot token from the Discord Developer Portal.
## File Structure
- `index.js`: The entry point of the bot that initializes the client and loads commands and events.
- `handler/index.js`: Dynamically loads commands and events and registers slash commands with Discord.
- `events/`: Folder containing event listeners such as `ready.js` (bot ready event) and `interactionCreate.js` (command handling).
- `commands/`: Folder containing command files. Commands are organized into subfolders based on category.
- `commands/info/`: Contains commands like `/ping` and `/modal-example`.
- `commands/context/`: Contains context menu commands like `ping-test`.
### Commands
- `ping`: Returns the bot's websocket latency with an ephemeral reply.
- `modal-example`: Displays a modal for user input.
- `ping-test`: A context menu command that returns latency when right-clicking a message.
## Running the Bot
To start the bot, run:
```bash
node index.js
```
If everything is set up correctly, you should see the following message:
```
YourBotName is up and ready to go!
```
The bot will automatically register slash commands for every guild it's in.
## Command Examples
### Slash Command: `/ping`
Returns the bot's websocket latency in an embed message.
### Slash Command: `/modal-example`
Opens a modal where users can input text, which is then displayed back to them.
### Context Menu Command: `ping-test`
Available by right-clicking a message and choosing this context command. It shows the bot's latency.
## Adding New Commands
To add a new command:
1. Create a new `.js` file in the `commands/` folder under the appropriate subfolder.
2. Define your command with the structure used in the existing commands.
3. Restart the bot to automatically load the new command.
Example command structure:
```js
module.exports = {
name: "new-command",
description: "Describe your command here",
run: async (client, interaction) => {
// Command logic
},
};
```
## Modals
The bot supports modals for user input. To add a new modal, create a new command in the `commands/` folder and use the `ModalBuilder` from `discord.js` to display a modal.
Example modal code:
```js
const { ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
const { ActionRowBuilder } = require('discord.js');
module.exports = {
name: "modal-example",
description: "Show a demo modal!",
run: async (client, interaction) => {
const modal = new ModalBuilder()
.setCustomId('example-modal')
.setTitle('Example Modal');
const input = new TextInputBuilder()
.setCustomId('input-field')
.setLabel("Your Input")
.setStyle(TextInputStyle.Paragraph);
const row = new ActionRowBuilder().addComponents(input);
modal.addComponents(row);
await interaction.showModal(modal);
}
};
```
## Handling Ephemeral Replies
Commands can return ephemeral (private) responses, making replies visible only to the command invoker. To enable ephemeral replies, add a `private` property to the command definition:
```js
module.exports = {
name: "ping",
description: "Returns latency",
private: true, // This makes the reply private
run: async (client, interaction) => {
// Command logic here
},
};
```
## Events
The bot listens for two primary events:
- `ready`: Triggered when the bot is logged in and ready.
- `interactionCreate`: Triggered when a user interacts with the bot through slash commands, modals, or context menu commands.
## Troubleshooting
- Ensure you have the correct bot token in your `.env` file.
- Make sure your bot has the necessary permissions to register commands in the guilds it's in.
- If commands arent registering, try manually clearing the commands in the Discord Developer Portal or use `guild-specific` commands to speed up testing.

View File

@ -0,0 +1,57 @@
const { EmbedBuilder } = require('discord.js');
const { ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
module.exports = {
name: "modal-example",
description: "Show a demo modal!",
run: async (client, interaction) => {
// Declare a random ID for the modal to ensure unique interactions
const modalId = `modal-${Math.floor(Math.random() * 99999999999999).toString()}`;
// Check if this is a chatInput command interaction
if (!interaction.isChatInputCommand()) return;
// Create the modal
const modal = new ModalBuilder()
.setCustomId(modalId)
.setTitle('This is an example modal');
// Create a text input component for the modal
const modalInputData = new TextInputBuilder()
.setCustomId('modalInput')
.setLabel("What text do you want to send?")
.setStyle(TextInputStyle.Paragraph); // Allows for multi-line text
// Wrap the text input in an action row
const modalInputRow = new ActionRowBuilder().addComponents(modalInputData);
// Add the input row to the modal
modal.addComponents(modalInputRow);
// Show the modal to the user
await interaction.showModal(modal);
// Handle the modal submission within the same interaction listener
client.once('interactionCreate', async (modalInteraction) => {
// Ensure we are handling the correct modal submission
if (!modalInteraction.isModalSubmit() || modalInteraction.customId !== modalId) return;
// Retrieve the data entered by the user
const modalInputDataString = modalInteraction.fields.getTextInputValue('modalInput');
// Create an embed to display the user input
const embed = new EmbedBuilder()
.setColor("#FF0000")
.setTitle("Your input!")
.setDescription(`You said: ${modalInputDataString}`)
.setTimestamp()
.setFooter({ text: `Requested by ${modalInteraction.user.tag}`, iconURL: modalInteraction.user.displayAvatarURL() });
// Reply to the modal submission with the embed
await modalInteraction.reply({ embeds: [embed] });
});
}
};

View File

@ -2,6 +2,7 @@ const { EmbedBuilder } = require('discord.js');
module.exports = { module.exports = {
name: "ping", name: "ping",
private: true, // Mark this command as private for ephemeral replies
description: "Returns websocket latency", description: "Returns websocket latency",
run: async (client, interaction) => { run: async (client, interaction) => {

16
commands/context/ping.js Normal file
View File

@ -0,0 +1,16 @@
const { EmbedBuilder } = require('discord.js');
const {ApplicationCommandType } = require('discord.js');
module.exports = {
name: "ping-test",
type: ApplicationCommandType.Message,
run: async (client, interaction) => {
const embed = new EmbedBuilder()
.setColor("#FF0000")
.setTitle("🏓 Pong!")
.setDescription(`Latency : ${client.ws.ping}ms`)
.setTimestamp()
.setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: `${interaction.user.displayAvatarURL()}` });
interaction.followUp({ embeds: [embed] });
},
};

View File

@ -1,16 +1,66 @@
const client = require("../index"); const client = require("../index");
require("dotenv").config();
const { glob } = require("glob");
const { promisify } = require("util");
const globPromise = promisify(glob);
client.on("interactionCreate", async (interaction) => { client.on("interactionCreate", async (interaction) => {
// Slash Commands
const slashCommands = await globPromise(`${process.cwd()}/commands/*/*.js`);
const arrayOfSlashCommands = [];
// Map the slash commands into data to be processed
slashCommands.map((value) => {
const file = require(value);
const splitted = value.split("/");
const directory = splitted[splitted.length - 2];
if (!file?.name) return;
const properties = { directory, ...file };
client.slashCommands.set(file.name, properties);
if (["MESSAGE", "USER"].includes(file.type)) delete file.description;
// Push the data
const JSONCommand = {
...file,
integration_types: [0, 1], // 0 for guild, 1 for user
contexts: [0, 1, 2], // 0 for guild, 1 for app DMs, 2 for GDMs and other DMs
};
arrayOfSlashCommands.push(JSONCommand);
});
// Slash Command Handling // Slash Command Handling
if (interaction.isChatInputCommand()) { if (interaction.isChatInputCommand()) {
await interaction.deferReply({ ephemeral: false }).catch(() => { }); let commandData = [];
// Filter to find the command for this interaction
await arrayOfSlashCommands.forEach(command => {
if (command.name === interaction.commandName) {
commandData.push(command);
}
});
// Process and parse the command data
const parsedData = commandData[0];
// Defer reply based on privacy settings
if (interaction.commandName === "modal-example") {
console.log("Modal - Skipping defer");
} else {
const isPrivate = parsedData?.private;
await interaction.deferReply({
ephemeral: !!isPrivate,
}).catch(() => {});
}
const cmd = client.slashCommands.get(interaction.commandName); const cmd = client.slashCommands.get(interaction.commandName);
if (!cmd) if (!cmd) {
return interaction.followUp({ content: "An error has occurred" }); return interaction.followUp({ content: "An error has occurred" });
}
const args = []; const args = [];
for (let option of interaction.options.data) { for (let option of interaction.options.data) {
if (option.type === "SUB_COMMAND") { if (option.type === "SUB_COMMAND") {
if (option.name) args.push(option.name); if (option.name) args.push(option.name);
@ -19,8 +69,13 @@ client.on("interactionCreate", async (interaction) => {
}); });
} else if (option.value) args.push(option.value); } else if (option.value) args.push(option.value);
} }
interaction.member = interaction.guild.members.cache.get(interaction.user.id);
// Check if the interaction is in a guild and assign member accordingly
if (interaction.inGuild()) {
interaction.member = interaction.guild.members.cache.get(interaction.user.id);
}
// Run the command
cmd.run(client, interaction, args); cmd.run(client, interaction, args);
} }

View File

@ -2,11 +2,16 @@ require("dotenv").config();
const { glob } = require("glob"); const { glob } = require("glob");
const { promisify } = require("util"); const { promisify } = require("util");
const globPromise = promisify(glob); const globPromise = promisify(glob);
const { REST } = require('@discordjs/rest');
const Discord = require('discord.js');
module.exports = async (client) => { module.exports = async (client) => {
const rest = new REST({ version: '10' }).setToken(process.env.TOKEN);
// Slash Commands // Slash Commands
const slashCommands = await globPromise(`${process.cwd()}/commands/*/*.js`); const slashCommands = await globPromise(`${process.cwd()}/commands/*/*.js`);
const arrayOfSlashCommands = []; const arrayOfSlashCommands = [];
slashCommands.map((value) => { slashCommands.map((value) => {
const file = require(value); const file = require(value);
const splitted = value.split("/"); const splitted = value.split("/");
@ -18,7 +23,15 @@ module.exports = async (client) => {
client.slashCommands.set(file.name, properties); client.slashCommands.set(file.name, properties);
if (["MESSAGE", "USER"].includes(file.type)) delete file.description; if (["MESSAGE", "USER"].includes(file.type)) delete file.description;
arrayOfSlashCommands.push(file);
// Add integration types and contexts to support user apps
const JSONCommand = {
...file,
integration_types: [0, 1], // 0 for guild, 1 for user
contexts: [0, 1, 2], // 0 for guild, 1 for DMs, 2 for GDMs and other DMs
};
arrayOfSlashCommands.push(JSONCommand);
}); });
// Events // Events
@ -27,11 +40,15 @@ module.exports = async (client) => {
// Slash Commands Register // Slash Commands Register
client.on("ready", async () => { client.on("ready", async () => {
// Register for a single guild try {
await client.guilds.cache.get("GUIDIDHERE").commands.set(arrayOfSlashCommands);
// Register for all the guilds the bot is in // Register for all the guilds the bot is in
// await client.application.commands.set(arrayOfSlashCommands); await rest.put(
Discord.Routes.applicationCommands(client.user.id),
{ body: arrayOfSlashCommands }
);
console.log("Successfully registered application commands.");
} catch (error) {
console.error("Error while registering application commands:", error);
}
}); });
}; };

View File

@ -1,7 +1,7 @@
require("dotenv").config(); require("dotenv").config();
const { Client, Collection } = require("discord.js"); const { Client, Collection } = require("discord.js");
const client = new Client({ intents: 32767 }); const client = new Client({ intents: 4097 });
module.exports = client; module.exports = client;
// Global Variables // Global Variables