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

Add 2 new features (see description) #53

Merged
merged 15 commits into from
Dec 17, 2023
Merged
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
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# <samp>DiscordJS-V14-Bot-Template</samp> v2

The simplified and popular Discord bot commands & events handler built with discord.js version 14 and written in JavaScript. This handler can load up to 4 different type of commands; Prefix, Slash, User context and Message context. It can also handles components; Buttons, Modal submits, Select menus (any type).
The simplified and popular Discord bot commands & events handler built with discord.js version 14 and written in JavaScript. This handler can load up to 4 different type of commands; Prefix, Slash, User context and Message context. It can also handles components; Buttons, Modal submits, Select menus (any type) and autocomplete.

Did you like my project? Click on the star button (⭐️) right above your screen, thank you!

Expand All @@ -16,6 +16,7 @@ Did you like my project? Click on the star button (⭐️) right above your scre
- Buttons
- Select menus
- Modals
- Autocomplete
- Easy and simple to use.
- Advanced command options ([click here](#command-options)).
- Updated to latest discord.js version.
Expand All @@ -34,6 +35,7 @@ module.exports = {
aliases: string[],
permissions?: PermissionResolvable,
cooldown?: number,
globalCooldown?: boolean,
developers?: boolean,
nsfw?: boolean
},
Expand All @@ -47,6 +49,7 @@ module.exports = {
structure: SlashCommandBuilder | ContextMenuCommandBuilder,
options?: {
cooldown?: number,
globalCooldown?: boolean,
developers?: boolean,
nsfw?: boolean
},
Expand Down Expand Up @@ -128,6 +131,7 @@ module.exports = {
nsfwMessage: string, // ← If the command's channel is not NSFW
developerMessage: string, // ← If the author of the command isn't a developer
cooldownMessage: string, // ← If the author of the command has cooldown
globalCooldownMessage: string, // ← If the author of the command has global cooldown
notHasPermissionMessage: string, // ← If the author of the command doesn't have required permissions
missingDevIDsMessage: string // ← If the developers IDs from the array are missing.
}
Expand Down Expand Up @@ -192,8 +196,9 @@ The command options, each property is optional, which means it's allowed to prov

- `permissions` (**PermissionFlagsBits** | **string**): The required permissions for the command, available to message commands only.
- `cooldown` (**number**): The cooldown of the command, in milliseconds.
- `developers` (**boolean**): Whenever the command is executable only to the developers of the bot.
- `nsfw` (**boolean**): Whenever this command is executable only in NSFW channels.
- `globalCooldown` (**boolean**): Determines whether the cooldown is global or not.
- `developers` (**boolean**): Determines whether the command is executable only to the developers of the bot.
- `nsfw` (**boolean**): Determines whether this command is executable only in NSFW channels.

## Component options
The component options, each property is optional which means it's allowed to provide an `undefined` value to one of these properties below.
Expand Down
22 changes: 21 additions & 1 deletion component.example.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,24 @@ module.exports = {
run: async (client, interaction) => {

}
};
};

// auto complete interaction

const { } = require('discord.js');
const ExtendedClient = require('../../class/ExtendedClient');

module.exports = {
commandName: '', // The name of the command that has the autocomplete action
options: {
public: true // Whether this autocomplete is public or restricted
},
/**
*
* @param {ExtendedClient} client
* @param {import('discord.js').AutocompleteInteraction} interaction
*/
run: async (client, interaction) => {
// Autocomplete interaction logic
}
};
3 changes: 2 additions & 1 deletion src/class/ExtendedClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ module.exports = class extends Client {
components: {
buttons: new Collection(),
selects: new Collection(),
modals: new Collection()
modals: new Collection(),
autocomplete: new Collection()
}
};
applicationcommandsArray = [];
Expand Down
25 changes: 25 additions & 0 deletions src/commands/slash/Utility/autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const ExtendedClient = require('../../../class/ExtendedClient');

module.exports = {
structure: new SlashCommandBuilder()
.setName('autocomplete')
.setDescription('An example of the autocomplete system')
.addStringOption(option =>
option.setName('fruit')
.setDescription('Choose a fruit')
.setAutocomplete(true)
.setRequired(true)),
options: {
public: true
},
/**
*
* @param {ExtendedClient} client
* @param {import('discord.js').CommandInteraction} interaction
*/
run: async (client, interaction) => {
const chosenFruit = interaction.options.getString('fruit');
await interaction.reply(`You chose the fruit: ${chosenFruit}`);
}
};
20 changes: 20 additions & 0 deletions src/components/autocomplete/example-autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { AutocompleteInteraction } = require('discord.js');
const ExtendedClient = require('../../class/ExtendedClient');

module.exports = {
commandName: 'autocomplete',
options: {
public: true
},
/**
*
* @param {ExtendedClient} client
* @param {AutocompleteInteraction} interaction
*/
run: async (client, interaction) => {
const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape', 'Honeydew'];
const currentInput = interaction.options.getFocused();
const filteredFruits = fruits.filter(fruit => fruit.toLowerCase().startsWith(currentInput.toLowerCase()));
await interaction.respond(filteredFruits.map(fruit => ({ name: fruit, value: fruit })));
}
};
14 changes: 14 additions & 0 deletions src/events/Guild/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,19 @@ module.exports = {

return;
};

if (interaction.isAutocomplete()) {
const component = client.collection.components.autocomplete.get(interaction.commandName);

if (!component) return;

try {
component.run(client, interaction);
} catch (error) {
log(error, 'error');
}

return;
}
}
};
35 changes: 18 additions & 17 deletions src/events/Guild/interactionCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,46 +86,47 @@ module.exports = {
}

if (command.options?.cooldown) {
const isGlobalCooldown = command.options.globalCooldown;
const cooldownKey = isGlobalCooldown ? 'global_' + command.structure.name : interaction.user.id;
const cooldownFunction = () => {
let data = cooldown.get(interaction.user.id);
let data = cooldown.get(cooldownKey);

data.push(interaction.commandName);

cooldown.set(interaction.user.id, data);
cooldown.set(cooldownKey, data);

setTimeout(() => {
let data = cooldown.get(interaction.user.id);
let data = cooldown.get(cooldownKey);

data = data.filter((v) => v !== interaction.commandName);

if (data.length <= 0) {
cooldown.delete(interaction.user.id);
cooldown.delete(cooldownKey);
} else {
cooldown.set(interaction.user.id, data);
cooldown.set(cooldownKey, data);
}
}, command.options?.cooldown);
}, command.options.cooldown);
};

if (cooldown.has(interaction.user.id)) {
let data = cooldown.get(interaction.user.id);
if (cooldown.has(cooldownKey)) {
let data = cooldown.get(cooldownKey);

if (data.some((v) => v === interaction.commandName)) {
const cooldownMessage = isGlobalCooldown
? config.messageSettings.globalCooldownMessage ?? "Slow down buddy! This command is on a global cooldown"
: config.messageSettings.cooldownMessage ?? "Slow down buddy! You're too fast to use this command";

await interaction.reply({
content:
config.messageSettings.cooldownMessage !== undefined &&
config.messageSettings.cooldownMessage !== null &&
config.messageSettings.cooldownMessage !== ""
? config.messageSettings.cooldownMessage
: "Slow down buddy! You're too fast to use this command",
content: cooldownMessage,
ephemeral: true,
});

return;
} else {
cooldownFunction();
}
} else {
cooldown.set(interaction.user.id, [interaction.commandName]);

cooldown.set(cooldownKey, [interaction.commandName]);
cooldownFunction();
}
}
Expand All @@ -135,4 +136,4 @@ module.exports = {
log(error, "err");
}
},
};
};
1 change: 1 addition & 0 deletions src/example.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
nsfwMessage: "The current channel is not a NSFW channel.",
developerMessage: "You are not authorized to use this command.",
cooldownMessage: "Slow down buddy! You're too fast to use this command.",
globalCooldownMessage: "Slow down buddy! This command is on a global cooldown.",
notHasPermissionMessage: "You do not have the permission to use this command.",
notHasPermissionComponent: "You do not have the permission to use this component.",
missingDevIDsMessage: "This is a developer only command, but unable to execute due to missing user IDs in configuration file."
Expand Down
25 changes: 15 additions & 10 deletions src/handlers/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,19 @@ module.exports = (client) => {
continue;
};

client.collection.components.modals.set(module.customId, module);

} else if (dir === 'autocomplete') {
if (!module.commandName || !module.run) {
log(`Unable to load the autocomplete component ${file} due to missing 'commandName' or 'run' properties.`, 'warn');
continue;
}

client.collection.components.autocomplete.set(module.commandName, module);

log(`Loaded new autocomplete component: ${file}`, 'info');
} else {
log('Invalid component type: ' + file, 'warn');

continue;
};

log('Loaded new component: ' + file, 'info');
};
};
};
log(`Invalid component type: ${file}`, 'warn');
}
}
}
};