commandkit
Advanced tools
Comparing version 0.0.8 to 0.0.9
@@ -7,2 +7,13 @@ # Change Log | ||
## [0.0.9] - 2023-08-09 | ||
### Added | ||
- Support for developer role IDs | ||
- Ability to skip built-in validations by setting `skipBuiltInValidations` to true inside the `CommandKit` constructor | ||
### Changed | ||
- Change validations to run custom user validations first, then CommandKit's built-in validations. | ||
## [0.0.8] - 2023-07-03 | ||
@@ -12,3 +23,3 @@ | ||
- Support for nested files inside of each event folder. | ||
- Support for nested files inside of each event folder. | ||
@@ -19,3 +30,3 @@ ## [0.0.7] - 2023-07-02 | ||
- Give validation functions access to the full command object (commandObj) excluding the run function (as that is handled by the command handler), as opposed to just the `data` and `options` properties. | ||
- Give validation functions access to the full command object (commandObj) excluding the run function (as that is handled by the command handler), as opposed to just the `data` and `options` properties. | ||
@@ -26,3 +37,3 @@ ## [0.0.6] - 2023-07-02 | ||
- Fixed a bug where wrong event names were being registered on Windows. | ||
- Fixed a bug where wrong event names were being registered on Windows. | ||
@@ -33,3 +44,3 @@ ## [0.0.5] - 2023-07-02 | ||
- Ability to automatically update application commands (guilds and global) when there's changes to the description or number of options (slash commands only). | ||
- Ability to automatically update application commands (guilds and global) when there's changes to the description or number of options (slash commands only). | ||
@@ -40,3 +51,3 @@ ## [0.0.4] - 2023-07-01 | ||
- Update package.json with new URLs, scripts, and version | ||
- Update package.json with new URLs, scripts, and version | ||
@@ -43,0 +54,0 @@ ## [0.0.3] - 2023-07-01 |
@@ -34,3 +34,3 @@ "use strict"; | ||
}); | ||
validationFunctions = validationHandler.getValidations(); | ||
validationHandler.getValidations().forEach((v) => validationFunctions.push(v)); | ||
} | ||
@@ -44,3 +44,5 @@ // Command handler | ||
devUserIds: this._data.devUserIds || [], | ||
validations: validationFunctions, | ||
devRoleIds: this._data.devRoleIds || [], | ||
customValidations: validationFunctions, | ||
skipBuiltInValidations: this._data.skipBuiltInValidations || false, | ||
}); | ||
@@ -47,0 +49,0 @@ this._data.commands = commandHandler.getCommands(); |
@@ -8,6 +8,6 @@ import { CommandHandlerData, CommandHandlerOptions } from './typings'; | ||
_buildCommands(): void; | ||
_buildValidations(): void; | ||
_registerCommands(): void; | ||
_handleCommands(): void; | ||
_areSlashCommandsDifferent(appCommand: any, localCommand: any): true | undefined; | ||
getCommands(): (SlashCommandObject | ContextCommandObject)[]; | ||
} |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.CommandHandler = void 0; | ||
const get_paths_1 = require("../../utils/get-paths"); | ||
const registerCommands_1 = __importDefault(require("./functions/registerCommands")); | ||
const handleCommands_1 = __importDefault(require("./functions/handleCommands")); | ||
const path_1 = __importDefault(require("path")); | ||
class CommandHandler { | ||
@@ -10,2 +16,3 @@ _data; | ||
...options, | ||
builtInValidations: [], | ||
commands: [], | ||
@@ -17,2 +24,3 @@ }; | ||
this._buildCommands(); | ||
this._buildValidations(); | ||
this._registerCommands(); | ||
@@ -36,222 +44,18 @@ this._handleCommands(); | ||
} | ||
_registerCommands() { | ||
const client = this._data.client; | ||
const commands = this._data.commands; | ||
client.once('ready', async () => { | ||
const devGuilds = []; | ||
for (const devGuildId of this._data.devGuildIds) { | ||
const guild = client.guilds.cache.get(devGuildId); | ||
if (!guild) { | ||
console.log(`⏩ Ignoring: Guild ${devGuildId} does not exist or client isn't in this guild.`); | ||
continue; | ||
} | ||
devGuilds.push(guild); | ||
_buildValidations() { | ||
const validationFilePaths = (0, get_paths_1.getFilePaths)(path_1.default.join(__dirname, 'validations'), true).filter((path) => path.endsWith('.js')); | ||
for (const validationFilePath of validationFilePaths) { | ||
const validationFunction = require(validationFilePath); | ||
if (typeof validationFunction !== 'function') { | ||
continue; | ||
} | ||
const appCommands = client.application?.commands; | ||
await appCommands?.fetch(); | ||
const devGuildCommands = []; | ||
for (const guild of devGuilds) { | ||
const guildCommands = guild.commands; | ||
await guildCommands?.fetch(); | ||
devGuildCommands.push(guildCommands); | ||
} | ||
for (const command of commands) { | ||
// <!-- Delete command if options.deleted --> | ||
if (command.options?.deleted) { | ||
const targetCommand = appCommands?.cache.find((cmd) => cmd.name === command.data.name); | ||
if (!targetCommand) { | ||
console.log(`⏩ Ignoring: Command "${command.data.name}" is globally marked as deleted.`); | ||
} | ||
else { | ||
targetCommand.delete().then(() => { | ||
console.log(`🚮 Deleted command "${command.data.name}" globally.`); | ||
}); | ||
} | ||
for (const guildCommands of devGuildCommands) { | ||
const targetCommand = guildCommands.cache.find((cmd) => cmd.name === command.data.name); | ||
if (!targetCommand) { | ||
console.log(`⏩ Ignoring: Command "${command.data.name}" is marked as deleted for ${guildCommands.guild.name}.`); | ||
} | ||
else { | ||
targetCommand.delete().then(() => { | ||
console.log(`🚮 Deleted command "${command.data.name}" in ${guildCommands.guild.name}.`); | ||
}); | ||
} | ||
} | ||
continue; | ||
} | ||
// <!-- Edit command if there's any changes --> | ||
let commandData = command.data; | ||
let editedCommand = false; | ||
(() => { | ||
// global | ||
const appGlobalCommand = appCommands?.cache.find((cmd) => cmd.name === command.data.name); | ||
if (appGlobalCommand) { | ||
const commandsAreDifferent = this._areSlashCommandsDifferent(appGlobalCommand, commandData); | ||
if (commandsAreDifferent) { | ||
appGlobalCommand | ||
.edit(commandData) | ||
.then(() => { | ||
console.log(`✅ Edited command "${commandData.name}" globally.`); | ||
}) | ||
.catch((error) => { | ||
console.log(`❌ Failed to edit command "${commandData.name}" globally.`); | ||
console.error(error); | ||
}); | ||
editedCommand = true; | ||
} | ||
} | ||
// guilds | ||
for (const guildCommands of devGuildCommands) { | ||
const appGuildCommand = guildCommands.cache.find((cmd) => cmd.name === commandData.name); | ||
if (appGuildCommand) { | ||
const commandsAreDifferent = this._areSlashCommandsDifferent(appGuildCommand, commandData); | ||
if (commandsAreDifferent) { | ||
appGuildCommand | ||
.edit(commandData) | ||
.then(() => { | ||
console.log(`✅ Edited command "${commandData.name}" in ${guildCommands.guild.name}.`); | ||
}) | ||
.catch((error) => { | ||
console.log(`❌ Failed to edit command "${commandData.name}" in ${guildCommands.guild.name}.`); | ||
console.error(error); | ||
}); | ||
editedCommand = true; | ||
} | ||
} | ||
} | ||
})(); | ||
if (editedCommand) | ||
continue; | ||
// <!-- Registration --> | ||
// guild-based command registration | ||
if (command.options?.devOnly) { | ||
if (!devGuilds.length) { | ||
console.log(`⏩ Ignoring: Cannot register command "${command.data.name}" as no valid "devGuildIds" were provided.`); | ||
continue; | ||
} | ||
for (const guild of devGuilds) { | ||
const cmdExists = guild.commands.cache.some((cmd) => cmd.name === command.data.name); | ||
if (cmdExists) | ||
continue; | ||
guild?.commands | ||
.create(command.data) | ||
.then(() => { | ||
console.log(`✅ Registered command "${command.data.name}" in ${guild.name}.`); | ||
}) | ||
.catch((error) => { | ||
console.log(`❌ Failed to register command "${command.data.name}" in ${guild.name}.`); | ||
console.error(error); | ||
}); | ||
} | ||
} | ||
// global command registration | ||
else { | ||
const cmdExists = appCommands?.cache.some((cmd) => cmd.name === command.data.name); | ||
if (cmdExists) | ||
continue; | ||
appCommands | ||
?.create(command.data) | ||
.then(() => { | ||
console.log(`✅ Registered command "${command.data.name}" globally.`); | ||
}) | ||
.catch((error) => { | ||
console.log(`❌ Failed to register command "${command.data.name}" globally.`); | ||
console.error(error); | ||
}); | ||
} | ||
} | ||
}); | ||
this._data.builtInValidations.push(validationFunction); | ||
} | ||
} | ||
_registerCommands() { | ||
(0, registerCommands_1.default)(this); | ||
} | ||
_handleCommands() { | ||
const client = this._data.client; | ||
client.on('interactionCreate', async (interaction) => { | ||
if (!interaction.isChatInputCommand() && !interaction.isContextMenuCommand()) | ||
return; | ||
const targetCommand = this._data.commands.find((cmd) => cmd.data.name === interaction.commandName); | ||
if (!targetCommand) | ||
return; | ||
// Options validation | ||
// options.guildOnly | ||
if (targetCommand.options?.guildOnly && !interaction.inGuild()) { | ||
interaction.reply({ | ||
content: '❌ This command can only be used inside a server.', | ||
ephemeral: true, | ||
}); | ||
return; | ||
} | ||
// options.devOnly | ||
if (targetCommand.options?.devOnly) { | ||
const isDevUser = this._data.devUserIds.includes(interaction.user.id); | ||
if (!isDevUser) { | ||
interaction.reply({ | ||
content: '❌ This command can only be used by developers.', | ||
ephemeral: true, | ||
}); | ||
return; | ||
} | ||
} | ||
// options.userPermissions | ||
const memberPermissions = interaction.memberPermissions; | ||
if (targetCommand.options?.userPermissions && memberPermissions) { | ||
for (const permission of targetCommand.options.userPermissions) { | ||
const hasPermission = memberPermissions.has(permission); | ||
if (!hasPermission) { | ||
interaction.reply({ | ||
content: `❌ You do not have enough permission to run this command. Required permission: \`${permission}\``, | ||
ephemeral: true, | ||
}); | ||
return; | ||
} | ||
} | ||
} | ||
// options.botPermissions | ||
const botMember = interaction.guild?.members.me; | ||
if (targetCommand.options?.botPermissions && botMember) { | ||
for (const permission of targetCommand.options.botPermissions) { | ||
const hasPermission = botMember.permissions.has(permission); | ||
if (!hasPermission) { | ||
interaction.reply({ | ||
content: `❌ I do not have enough permission to execute this command. Required permission: \`${permission}\``, | ||
ephemeral: true, | ||
}); | ||
return; | ||
} | ||
} | ||
} | ||
// Run user validation functions | ||
const validationFunctions = this._data.validations; | ||
const { data, options, run, ...rest } = targetCommand; | ||
const commandObj = { | ||
data: targetCommand.data, | ||
options: targetCommand.options, | ||
...rest, | ||
}; | ||
let canRun = true; | ||
for (const validationFunction of validationFunctions) { | ||
const stopValidationLoop = await validationFunction({ interaction, client, commandObj }); | ||
if (stopValidationLoop) { | ||
canRun = false; | ||
break; | ||
} | ||
} | ||
if (canRun) { | ||
targetCommand.run({ interaction, client }); | ||
} | ||
}); | ||
(0, handleCommands_1.default)(this); | ||
} | ||
_areSlashCommandsDifferent(appCommand, localCommand) { | ||
if (!appCommand.options) | ||
appCommand.options = []; | ||
if (!localCommand.options) | ||
localCommand.options = []; | ||
if (!appCommand.description) | ||
appCommand.description = ''; | ||
if (!localCommand.description) | ||
localCommand.description = ''; | ||
if (localCommand.description !== appCommand.description || | ||
localCommand.options.length !== appCommand.options.length) { | ||
return true; | ||
} | ||
} | ||
getCommands() { | ||
@@ -258,0 +62,0 @@ return this._data.commands; |
{ | ||
"name": "commandkit", | ||
"version": "0.0.8", | ||
"main": "dist/index.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"build": "tsc" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/notunderctrl/commandkit" | ||
}, | ||
"homepage": "https://commandkit.underctrl.io", | ||
"keywords": [ | ||
"discord.js", | ||
"command handler", | ||
"event handler", | ||
"command validations" | ||
], | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"discord.js": "^14.11.0", | ||
"tsc": "^2.0.4" | ||
} | ||
"name": "commandkit", | ||
"version": "0.0.9", | ||
"main": "dist/index.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"build": "tsc" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/notunderctrl/commandkit" | ||
}, | ||
"homepage": "https://commandkit.underctrl.io", | ||
"keywords": [ | ||
"discord.js", | ||
"command handler", | ||
"event handler", | ||
"command validations" | ||
], | ||
"devDependencies": { | ||
"discord.js": "^14.12.1", | ||
"typescript": "^5.1.6" | ||
} | ||
} |
# CommandKit | ||
CommandKit is a library that makes it easy to handle commands (+validations), and events in your Discord.js projects. | ||
CommandKit is a library that makes it easy to handle commands (+ validations), and events in your Discord.js projects. | ||
_Tested with Discord.js version `v14.11.0`_ | ||
**Supports Discord.js version 14** | ||
# Features | ||
- Very beginner friendly 🚀 | ||
- Support for slash and context menu commands ✅ | ||
- Automatic command registration, edits, and deletion 🤖 | ||
- Supports multiple development servers 🤝 | ||
- Supports multiple users as bot developers 👥 | ||
- Object oriented 💻 | ||
- Very beginner friendly 🚀 | ||
- Support for slash and context menu commands ✅ | ||
- Automatic command registration, edits, and deletion 🤖 | ||
- Supports multiple development servers 🤝 | ||
- Supports multiple users as bot developers 👥 | ||
- Object oriented 💻 | ||
@@ -40,9 +40,7 @@ # Documentation | ||
This is a simple overview of how to set up this library with all the options. | ||
This is a simple overview of how to set up this library with all the options. You can read more in the [full documentation](https://commandkit.underctrl.io) | ||
**It's highly recommended you check out the [documentation](https://commandkit.underctrl.io) to fully understand how to work with this library.** | ||
```js | ||
// index.js | ||
const { Client, IntentsBitField } = require('discord.js'); | ||
const { Client, GatewayIntentBits } = require('discord.js'); | ||
const { CommandKit } = require('commandkit'); | ||
@@ -52,23 +50,33 @@ const path = require('path'); | ||
const client = new Client({ | ||
intents: [IntentsBitField.Flags.Guilds], | ||
intents: [ | ||
GatewayIntentBits.Guilds, | ||
GatewayIntentBits.GuildMessages, | ||
GatewayIntentBits.MessageContent, | ||
], | ||
}); | ||
new CommandKit({ | ||
// Your discord.js client object | ||
client, | ||
// Your discord.js client object | ||
client, | ||
// Path to the commands folder | ||
commandsPath: path.join(__dirname, 'commands'), | ||
// Path to the commands folder | ||
commandsPath: path.join(__dirname, 'commands'), | ||
// Path to the events folder | ||
eventsPath: path.join(__dirname, 'events'), | ||
// Path to the events folder | ||
eventsPath: path.join(__dirname, 'events'), | ||
// Path to the validations folder (only valid if "commandsPath" was provided) | ||
validationsPath: path.join(__dirname, 'validations'), | ||
// Path to the validations folder (only valid if "commandsPath" was provided) | ||
validationsPath: path.join(__dirname, 'validations'), | ||
// Array of development server IDs (used to register and run devOnly commands) | ||
devGuildIds: ['DEV_SERVER_ID_1', 'DEV_SERVER_ID_2'], | ||
// Array of development server IDs (used to register and run devOnly commands) | ||
devGuildIds: ['DEV_SERVER_ID_1', 'DEV_SERVER_ID_2'], | ||
// Array of developer user IDs (used for devOnly commands) | ||
devUserIds: ['DEV_USER_ID_1', 'DEV_USER_ID_2'], | ||
// Array of developer user IDs (used for devOnly commands) | ||
devUserIds: ['DEV_USER_ID_1', 'DEV_USER_ID_2'], | ||
// Array of developer role IDs (used for devOnly commands) | ||
devRoleIds: ['DEV_ROLE_ID_1', 'DEV_ROLE_ID_2'], | ||
// A property that disables CommandKit's built-in validations | ||
skipBuiltInValidations: true, | ||
}); | ||
@@ -75,0 +83,0 @@ |
{ | ||
"compilerOptions": { | ||
"outDir": "dist", | ||
"strict": true, | ||
"noImplicitAny": true, | ||
"esModuleInterop": true, | ||
"strictNullChecks": true, | ||
"target": "ES2022", | ||
"moduleResolution": "Node", | ||
"module": "CommonJS", | ||
"declaration": true | ||
}, | ||
"include": ["src/**/*"] | ||
"compilerOptions": { | ||
"outDir": "dist", | ||
"strict": true, | ||
"noImplicitAny": true, | ||
"esModuleInterop": true, | ||
"strictNullChecks": true, | ||
"target": "ES2022", | ||
"moduleResolution": "Node", | ||
"module": "CommonJS", | ||
"declaration": true | ||
}, | ||
"include": ["src/**/*"] | ||
} |
import { | ||
Client, | ||
APIApplicationCommandOption, | ||
ContextMenuCommandType, | ||
Interaction, | ||
PermissionResolvable, | ||
SlashCommandBuilder, | ||
ContextMenuCommandBuilder, | ||
Client, | ||
APIApplicationCommandOption, | ||
ContextMenuCommandType, | ||
Interaction, | ||
PermissionResolvable, | ||
SlashCommandBuilder, | ||
ContextMenuCommandBuilder, | ||
} from 'discord.js'; | ||
export interface CommandKitOptions { | ||
client: Client; | ||
commandsPath?: string; | ||
eventsPath?: string; | ||
validationsPath?: string; | ||
devGuildIds?: string[]; | ||
devUserIds?: string[]; | ||
client: Client; | ||
commandsPath?: string; | ||
eventsPath?: string; | ||
validationsPath?: string; | ||
devGuildIds?: string[]; | ||
devUserIds?: string[]; | ||
devRoleIds?: string[]; | ||
skipBuiltInValidations?: boolean; | ||
} | ||
export interface CommandKitData extends CommandKitOptions { | ||
commands: Array<SlashCommandObject | ContextCommandObject>; | ||
commands: Array<SlashCommandObject | ContextCommandObject>; | ||
} | ||
export interface SlashCommandObject { | ||
data: | ||
| SlashCommandBuilder | ||
| { | ||
name: string; | ||
name_localizations?: any; | ||
description: string; | ||
dm_permission?: boolean; | ||
options?: APIApplicationCommandOption[]; | ||
}; | ||
options?: { | ||
guildOnly?: boolean; | ||
devOnly?: boolean; | ||
deleted?: boolean; | ||
userPermissions?: PermissionResolvable[]; | ||
botPermissions?: PermissionResolvable[]; | ||
}; | ||
run: ({}: { interaction: Interaction; client: Client }) => void; | ||
data: | ||
| SlashCommandBuilder | ||
| { | ||
name: string; | ||
name_localizations?: any; | ||
description: string; | ||
dm_permission?: boolean; | ||
options?: APIApplicationCommandOption[]; | ||
}; | ||
options?: { | ||
guildOnly?: boolean; | ||
devOnly?: boolean; | ||
deleted?: boolean; | ||
userPermissions?: PermissionResolvable[]; | ||
botPermissions?: PermissionResolvable[]; | ||
}; | ||
run: ({}: { interaction: Interaction; client: Client }) => void; | ||
} | ||
export interface ContextCommandObject { | ||
data: | ||
| ContextMenuCommandBuilder | ||
| { | ||
name: string; | ||
name_localizations?: any; | ||
type: ContextMenuCommandType; | ||
dm_permission?: boolean; | ||
}; | ||
options?: { | ||
guildOnly?: boolean; | ||
devOnly?: boolean; | ||
deleted?: boolean; | ||
userPermissions?: PermissionResolvable[]; | ||
botPermissions?: PermissionResolvable[]; | ||
}; | ||
run: ({}: { interaction: Interaction; client: Client }) => void; | ||
data: | ||
| ContextMenuCommandBuilder | ||
| { | ||
name: string; | ||
name_localizations?: any; | ||
type: ContextMenuCommandType; | ||
dm_permission?: boolean; | ||
}; | ||
options?: { | ||
guildOnly?: boolean; | ||
devOnly?: boolean; | ||
deleted?: boolean; | ||
userPermissions?: PermissionResolvable[]; | ||
botPermissions?: PermissionResolvable[]; | ||
}; | ||
run: ({}: { interaction: Interaction; client: Client }) => void; | ||
} |
32670
34
709
84
5