Security News
PyPI Now Supports iOS and Android Wheels for Mobile Python Development
PyPI now supports iOS and Android wheels, making it easier for Python developers to distribute mobile packages.
Interactions and Command handler 4 Dummies
With npm
npm i ic4d
with yarn
yarn add ic4d
const ic4d = require("ic4d");
// or
const {
/* Classess you need seprated by a comma */
} = require("ic4d");
and for yall ts lovers (I tried using this in ts and damn is it hard work.)
import * as ic4d from "ic4d";
// or
import /* Classes and/or Interfaces you need separated by a comma */ "ic4d";
If any method/function has no return documentation, it returns void.
For TS Lovers:
Here's the example bot if you don't like reading
this is what you're index.js should look something like.
Refer here for the sharding version of index.js
require("dotenv").config();
const { Client, IntentsBitField } = require("discord.js");
const path = require("path");
const { CommandHandler, ReadyHandler, CoreHandler } = require("ic4d");
const commandsPath = path.join(__dirname, "..", "commands");
const logpath = path.join(__dirname, "..", "logs");
const runFlags = {
devs: ["671549251024584725"],
testGuildId: "808701451399725116",
};
const client = new Client({
intents: [
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildMessages,
],
});
const core = new CoreHandler(client, logPath);
const handler = new CommandHandler(core, commandsPath, runFlags);
const ready = new ReadyHandler(
core,
undefined,
async (client) => {
console.log(`${client.user.tag} is ready!`);
},
async () => {
await handler.registerCommands();
}
);
(async () => {
ready.execute();
await handler.handleCommands();
})();
client.login(process.env.TOKEN);
And in any file in any folder under your specified commands
directory
const { EmbedBuilder, SlashCommandBuilder } = require("discord.js");
const { SlashCommandObject, SlashCommandManager } = require("ic4d");
const ping = new SlashCommandManager({
data: new SlashCommandBuilder().setName("ping").setDescription("Pong!"),
async execute(interaction, client) {
try {
const sent = await interaction.reply({
embeds: [new EmbedBuilder().setDescription("Pinging...")],
fetchReply: true,
});
interaction.editReply({
embeds: [
new EmbedBuilder()
.setTitle("Pong!")
.setFields([
{
name: "Roundtrip latency",
value: `${
sent.createdTimestamp -
interaction.createdTimestamp
}ms`,
inline: true,
},
{
name: "Websocket heartbeat",
value: `${client.ws.ping}ms.`,
inline: true,
},
])
.setColor("Green"),
],
});
} catch (error) {
console.error(error);
}
},
});
ping.category = "misc"; // if you want to add your own property to the exorted object, you can do this
module.exports = ping;
// or
module.exports = { ping };
This is a class which is needed as the first parameter of the ReadyHandler
, CommandHandler
and InteractionHandler
constructors.
You don't need to use or touch any of the methods and properties in this class. Do that and your bot may not work lol
client
: Client
shardClient
parameter and leave this undefined)logToFolder
: string | false
const { CoreHandler } = require("ic4d");
const { Client } = require("discord.js");
const client = new Client();
const core = new CoreHandler(client, "./logs");
Ready handler is a handler that runs a set of functions when the bot starts.
core
:CoreHandler
shardClient
: Client
client
instance from the coreHandler will be used by default....functions
: ((client?: Client) => Promise<void> | void )[] = []
execute()
method is called, and the ready event has been emitted. Functions may take one parameter (client) or none.const { ReadyHandler, CoreHandler } = require("ic4d");
const core = new CoreHandler(client, "./logs");
const ready = new ReadyHandler(
core,
undefined, // we're not sharding for shit so leave it undefined.
(client) => {
console.log(`${client.user.tag} is ready!`);
},
() => {
console.log("Lol another function");
}
);
execute()
Start listening for the bot's ready event and execute functions once the event is called.
const ready = new ReadyHandler(client, ...)
ready.execute()
Command Handler, which handles slash command creation, deletion, editing and running of slash commands
core
:CoreHandler
path
: string
runFlags
: RunFlags
loaderOptions
: LoaderOptions
handlerFlags
: HandlerFlags
shardClient
: Client
client
instance from the coreHandler will be used by default.const { CommandHandler, CoreHandler } = require("ic4d");
const path = require("path");
const core = new CoreHandler(client, "./logs");
const handler = new CommandHandler(core, path.join(__dirname, "commands"));
registerCommands()
(asynchronous function)
logNoChanges
: boolean
serverId
: string
const handler = new CommandHandler(client, path.join(__dirname, "commands"));
async () => {
await handler.registerCommands();
};
handleCommands()
(asynchronous function)
middleWare
: ( ( commandObject: Object, interaction?: ChatInputCommandInteraction ) => number | Promise )[]
postWare
: ( ( commandObject: Object, interaction?: ChatInputCommandInteraction ) => any )[]
const handler = new CommandHandler(core, path.join(__dirname, "commands"));
const blacklisted = ["302983482377931"];
const blacklist = (commandObject, interaction) => {
if (commandObject.blacklist && blacklisted.includes(interaction.user.id)) {
interaction.reply({
content: "Daang you blacklisted my guy.",
ephemeral: true,
});
return 1;
}
return 0;
};
const addXp = (commandObject, interaction) => {
if (commandObject.category != "economy") return;
interaction.reply({
content: "Ayo! Free Xp +2",
ephemeral: true,
});
};
await handler.handleCommands([blacklist], [addXp]);
[!NOTE]
This is ONLY formiddleWare
and does NOT apply topostWare
functions. It does not matter whatpostWare
functions return as ic4d does not use that value at all.
Middleware is to define your own custom functions you want to run when a command is run by anyone. This can be a function to check for cooldown or function to add XP to a user.
Middleware that the package already contains is :
The Middleware must take in these parameters
commandObject
: This is the commandObject that every command contains, this can check for the commands name, description, options, choices or a custom property you wishinteraction
(optional): If the middleware function requires you to take in interaction for some reason, here you go 😃And should always return 1 or another number. If it returns 1 it counts as a fail so the function won't proceed. Another number returned is okay seen as a pass and the function continues. (If you don't understand, if a normal user tries to run a dev command, it will return 1, which means it wont run and their met with a fail message)
Here i define a command with the custom property canBeServerDisabled
const {SlashCommandManager} = require("ic4d");
const {SlashCommandBuilder} = require("discord.js");
const rob = new SlashCommandManager({
data: new SlashCommandBuilder()
.setName("rob")
.setDescription("Rob users")
execute: (interaction, client) => {
interaction.reply("bang bang!");
},
});
rob.canBeServerDisabled = true;
module.exports = rob
And in my middleware function i check if the command has been server disabled, if the property is enabled.
const isServerDisabled = (name) => {
// check to see if the function has been disabled by the server, if so return true, otherwise false.
};
const middleWare = (commandObject, interaction) => {
// you can name the parameters whatever you want, ass long as you remember which one is which.
if (
commandObject.canBeServerDisabled &&
isServerDisabled(commandObject.name)
) {
interaction.reply({
content: "This command is server disabled",
ephemeral: true,
});
return 1;
}
return 0;
};
handler.handleCommands([middleWare]); // pass the function alone without brackets or its parameters, i'll do that magic
Handler to handle interactions.
Context Menus work a bit differently then the other interactions, please refer to registerContextMenus()
core
:CoreHandler
path
: string
loaderOptions
: LoaderOptions
flags
: InteractionHandlerFlags
shardClient
: Client
client
instance from the coreHandler will be used by default.const { InteractionHandler, CoreHandler } = require("ic4d");
const path = require("path");
const core = new CoreHandler(client, "./logs");
const interactions = new InteractionHandler(
core,
path.join(__dirname, "commands")
);
start()
Start listening for all the available interactions. (Context Menus, Buttons, Select Menus and Modals)
authorOnlyMsg
: string
...middleWare
: ((interaction?: Interaction) => number)[]
interactions.start();
buttons()
Start listening for button interactions.
authorOnlyMsg
: string
...middleWare
: ((interaction?: Interaction) => number)[]
interactions.buttons();
selectMenus()
Start listening for select menu interactions.
authorOnlyMsg
: string
...middleWare
: ((interaction?: Interaction) => number)[]
interactions.selectMenu();
modals()
Start listening for modal interactions. (After their registered)
...middleWare
: ((interaction?: Interaction) => number)[]
interactions.modals();
contextMenus()
Start listening for context menu interactions. (After their registered)
...middleWare
: ((interaction?: Interaction) => number)[]
interactions.contextMenus();
Exactly like Command Middleware, where 1 will return and any number will continue execution. Only difference is here the only parameter you get is interaction.
function isAuthor(interaction) {
// the handler does this for you (check the InteractionObject) but im writing this as an example only.
const author = interaction.message.interaction.user.id;
const clicker = interaction.member.user.id;
return clicker === author ? 1 : 0;
}
function lucky(interaction) {
// randdom one
return 1 == 1 ? 1 : 0; // will always return 1.
}
// some other code
interactions.buttons("This isn't your button!", isAuthor); // this will only run for buttons.
interactions.start(undefined, lucky); // will run for every interactions
registerContextMenus()
(asynchronous function)
Registers Context Menus that are found in the path given tot he InteractionHandler.
logAll
: string
serverId
: string
await interactions.registerContextMenus();
This class represents a single command that is immediately exported from a file in the "commands"
directory of your choosing
[!NOTE] Methods can be chained together
Exmaple:
const { SlashCommandManager } = require("ic4d");
const command = new SlashCommandManager();
module.exports = command;
commandObject
: {
data: SlashCommandBuilder;
execute: (
interaction: ChatInputCommandInteraction,
client?: Client,
addInteractionVariables?: (k: { [key: string]: any }) => void
) => void | Promise<void>
data
property which contains the command's data from the discord.js provided class SlashCommandBuilder
and the execute
property which takes in a function with the interaction
and client
parameter.addInteractionVaribles
is a function that can be used in the execute method to pass variables to buttons, modals and select menus (See here)Example:
const { SlashCommandManager } = require("ic4d");
const command = new SlashCommandManager({
data: new SlashCommandBuilder()
.setName("ping")
.setDescription("Pong!")
.addAttachmentOption((option) =>
option.setName("user").setDescription("Ping a user for no reason.")
),
execute: (interaction, client) => {
interaction.reply("pong!!");
},
});
module.exports = command;
setUserPermissions
Sets the permissions required by the user to execute the command.
...perms
: bigint[]
bigint
s provided by discord.js (PermissionFlagsBits)returns
: self
Example:
const { SlashCommandManager } = require("ic4d");
const { PermissionFlagsBits } = require("discord.js");
const command = new SlashCommandManager(/* command cofig */).setUserPermissions(
PermissionFlagsBits.Administrator
);
module.exports = command;
setBotPermissions
Sets the permissions needed for the bot to execute the command.
...perms
: bigint[]
bigint
s provided by discord.js (PermissionFlagsBits)returns
: self
Example:
const { SlashCommandManager } = require("ic4d");
const { PermissionFlagsBits } = require("discord.js");
const command = new SlashCommandManager(/* command cofig */).setBotPermissions(
PermissionFlagsBits.Administrator
);
module.exports = command;
setDeleted
Sets the commmand to be deleted, If command has already been deleted, it will be skipped when loaded again.
bool
: boolean
returns
: self
Example:
const { SlashCommandManager } = require("ic4d");
const command = new SlashCommandManager(/* command cofig */).setDeleted(true);
module.exports = command;
addInteractions
Appends related interactions to the slash command, only way for slash commands and other interactions to appear in the same file.
...interactions
: InteractionBuilder[]
returns
: self
const { SlashCommandManager, InteractionBuilder } = require("ic4d");
const command = new SlashCommandManager(/* command cofig */).addInteractions(
new InteractionBuilder() /*...*/
);
module.exports = command;
Represents a single interaction that isn't a chat input (slash command) or context menu. (This class can however be passed into a rest parameter in SlashCommandManager or in it's own separate file by itself.) Builder for Context Menus: ContextMenuBuilder
[!NOTE] Methods can be chained together
Example:
const { InteractionBuilder } = require("ic4d");
const button = new InteractionBuilder()
.setCustomId("button-1")
.setType("button")
.setCallback((i) => {
i.update("whats up");
})
.setOnlyAuthor(true);
No parameters are passed, so no documentation :) yay. (I hate documenting.)
setCustomId
Sets the custom ID of the interaction.
customId
: string
returns
: self
const button = new InteractionBuilder().setCustomId("my-cool-button");
setType
Sets the type of the interaction. (Either "selectMenu", "button" or "modal")
type
: InteractionTypeStrings
returns
: self
const selectMenu = new InteractionBuilder().setType("selectMenu");
setCallback
Function to be called when the interaction is called. (Is that how you say it?)
fn
: (
interaction: InteractionTypeStringsMap<this["type"]>,
client?: Client,
variables?: { [key:string]: any }
) => void | Promise<void>
(interaction, client, variables)
)returns
: self
const selectMenu = new InteractionBuilder().setCallback((i) => {
i.update("Client parameter is optional");
});
setOnlyAuthor
Set whether or not the interaction can only be interacted with by the author of the interaction.
bool
: boolean
returns
: self
const button = new InteractionBuilder().setOnlyAuthor(true);
setTimeout
Sets the interaction to have a timeout.
fn
:(
interaction: ChatInputCommandInteraction,
client?: Client,
variables?: { [key:string]: any }
) => void | Promise<void>
timeout
: number
returns
: self
const a = new InteractionBuilder().setTimeout((i) => {
i.editReply("Damn the time expired brodie");
}, 10_000);
Builder for context menus, since they are special.
context
: {
data: ContextMenuCommandBuilder;
execute: (
interaction: ContextMenuCommandInteraction,
client?: Client
) => void;
}
data
property that is an instance of ContextMenuBuilder
provided by discord.js and a function called execute
to execute when the context menu is called.const {
ApplicationCommandType,
ContextMenuCommandBuilder,
} = require("discord.js");
const { ContextMenuBuilder } = require("ic4d");
const user = new ContextMenuBuilder({
data: new ContextMenuCommandBuilder()
.setName("Get User Avatar")
.setType(ApplicationCommandType.User),
execute: (interaction, client) => {
const user = interaction.targetUser;
interaction.reply({
ephemeral: true,
content: user.displayAvatarURL(),
});
},
});
module.exports = user;
setDeleted
Sets the context menu to be deleted, If context menu has already been deleted, it will be skipped when loaded again.
deleted
: boolean
returns
: self
const user = new ContextMenuBuilder().setDeleted(true);
An interface representing the configuration flags used for running commands in the bot.
This configuration is specifically used to control various runtime aspects of command execution.
Default value: undefined
The ID of the test guild for command testing purposes. If provided, commands will be deployed only to this guild.
Default value: []
An array of Discord user IDs (snowflakes) that have developer privileges.
Commands or functionalities restricted to developers will be accessible to users with IDs in this array.
Default value: "Only developers are allowed to run this command."
The message shown when a command restricted to developers is executed by a non-developer.
Default value: "Not enough permissions."
The message displayed when a user lacks the necessary permissions to execute a command.
Default value: "I don't have enough permissions."
The message displayed when the bot lacks the necessary permissions to execute a command.
const obj: RunFlags = {
testGuildId: "808701451399725116",
devs: ["671549251024584725"],
onlyDev: "Text to display when a user runs a developer command.",
userNoPerms: "Text to display when the user has insufficient permissions",
botNoPerms: "Text to display when the bot has insufficient permissions",
};
An interface that represents anything you can do with the commands when they are run, BUT before YOUR code executes.
Default value: false
Enable debugger mode. Prints(almost) everything that happens behind the scenes of course not with the API itself.
Default value: false
Disabling Logging of the Command Loader. Not advisable but hey it's your bot.
Default value: false
Whether or not this is the production version of the bot. If set to true, commands labelled isDev
will NOT be loaded. (Use the setDev()
method in SlashCommandManager)
Default value: false
Clears ALL application commands on startup. (Slash commands, User commands, and Message commands.)
Default value: (CoreHandler value) or false
When debugger mode is enabled, Either log to console or a file.
const obj: LoaderOptions = {
debugger: true,
production: true,
disableLogs: true,
};
An interface that represents anything you can do with the interactions when they are run, BUT before YOUR code executes.
Default value: false
Enable Debugger mode. Prints (almost) everything that happens behind the scenes of course not with the API itself.
Default value: false
Disabling Logging of the Context Menu Loader. Not advised but hey it's your bot. Default is false.
Default value: false
Clears Context Menus
Default value: (CoreHandler value) or false
When debugger mode is enabled, Either log to console or a file.
Interface that represents default string values for the loader to log to the console when it encounters a command/context menu.
Make sure you keep NAME
in the string or else you will not know what happened to which command.
If there is no log in the console for a specific command, then it has been loaded, there are no edits and it has not been deleted.
Note:
These have multiple default values, as context menus and commands are different.
What to show for context menus/commands that load in
What to show for context menus/commands that gets edited.
What to show for context menus/commands that gets deleted.
What to show for context menus/commands that gets skipped. (Deleted and still marked as deleted.)
What to show for context menus/commands that gets loaded, but has no changes
const obj: LoaderOptions = {
loadedNoChanges: "NAME was loaded. No changes were made to NAME.",
loaded: "NAME has been registered successfully.",
edited: "NAME has been edited.",
deleted: "NAME has been deleted.",
skipped: "NAME was skipped. (Command deleted or set to delete.)",
};
Type alias for the strings "selectMenu"
, "modal"
and "button"
Yes this did not need documenting, but here it is.
Here's the exact definition because I genuinely don't know how to explain this.
export type InteractionTypeStringsMap<U extends string> = U extends "modal"
? ModalSubmitInteraction
: U extends "selectMenu"
? AnySelectMenuInteraction
: U extends "button"
? ButtonInteraction
: never;
This is the exact same code from Quick Example except it's sharding compatible
require("dotenv").config();
const { Client, IntentsBitField, ShardingManager } = require("discord.js");
const path = require("path");
const { CommandHandler, ReadyHandler, CoreHandler } = require("ic4d");
const commandsPath = path.join(__dirname, "..", "commands");
const logPath = path.join(__dirname, "..", "logs");
const runFlags = {
devs: ["671549251024584725"],
testGuildId: "808701451399725116",
};
// Use ShardingManager to manage shards
const manager = new ShardingManager(__filename, {
token: process.env.TOKEN,
totalShards: "auto", // Discord.js will automatically decide shard count
});
manager.on("shardCreate", (shard) => {
console.log(`Shard ${shard.id} launched.`);
});
// Core bot logic for each shard
if (!process.env.SHARDING_MANAGER) {
const client = new Client({
intents: [
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildMessages,
],
});
// (1) Pass `undefined` to the CoreHandler constructor
const core = new CoreHandler(undefined, logPath);
// (2) Provide the shard client in ReadyHandler
const ready = new ReadyHandler(
core,
client, // Shard-specific client
async (shardClient) => {
console.log(`${shardClient.user.tag} is ready!`);
},
async () => {
await handler.registerCommands();
}
);
// (3) Pass the shard client to CommandHandler
const handler = new CommandHandler(
core,
commandsPath,
runFlags,
undefined,
undefined,
client
);
(async () => {
await client.login(process.env.TOKEN);
ready.execute();
await handler.handleCommands();
})();
}
// Launch all shards
manager.spawn();
Method 1 involves:
Setting and retrieving an outside variable which the slash and interaction callback can receive
Method 2 involves:
Adding a property to the slash command object itself and retrieving that property later in the interaction callback.
// method 1
let variable = "";
const test = new SlashCommandManager({
data: new SlashCommandBuilder()
.setName("test")
.setDescription("Just a test")
.addStringOption((option) =>
option
.setName("string")
.setDescription("some input")
.setRequired(true)
),
async execute(interaction, client) {
try {
await interaction.deferReply();
const itemName = interaction.options.get("string").value;
// method 1 (global variable)
variable = itemName;
// method 2 (adding a property)
test.variable = itemName;
await interaction.editReply({
content: "Huh?",
components: [
new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId("hello")
.setLabel("click now")
.setStyle(ButtonStyle.Primary)
),
],
});
} catch (e) {
errorHandler(e, client, interaction, EmbedBuilder);
}
},
}).addInteractions(button);
const button = new InteractionBuilder()
.setType("button")
.setCustomId("hello")
.setCallback(async (i, c) => {
// method 1
const itemName = variable;
//method 2
const itemName = test.variable;
await i.update({ content: itemName, components: [] });
});
module.exports = test;
Method 1 issue:
Concurrency issues: The main problem with this method is that if multiple users run the command simultaneously, the variable is overwritten by the most recent execution. This means that data intended for one user could be exposed or altered by another user’s command, leading to unexpected behavior and security issues.
Method 2 issue(s):
Shared State: Although this method keeps the variable associated with the specific command object, it still doesn't address the issue of multiple users running the command. If two users execute the command, the stored value will change to reflect the most recent one, causing similar issues to the global variable method.
Not Scoped Per Interaction: This approach doesn’t effectively isolate variables for individual interactions, which can lead to data being inadvertently shared between users.
This uses a new function passed into SlashCommandManager execute function (in the constructor object), which you can use to pass anything to the interactions associated with this command. (Returns void);
k
: { [key: string]: any }
Then to use these variables, in either the setCallback or setTimeout method of the InteractionBuilder, add the optional parameter variables
which returns the object you passed (hopefully)
Example:
const test = new SlashCommandManager({
data: new SlashCommandBuilder()
.setName("test")
.setDescription("Just a test")
.addStringOption((option) =>
option
.setName("string")
.setDescription("some input")
.setRequired(true)
),
// here we add the function in the parameter list
async execute(interaction, client, addInteractionVariables) {
try {
await interaction.deferReply();
const itemName = interaction.options.get("string").value;
await interaction.editReply({
content: "Huh?",
components: [
new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId("hello")
.setLabel("click now")
.setStyle(ButtonStyle.Primary)
),
],
});
// using the function
addInteractionVariables({ itemName });
} catch (e) {
errorHandler(e, client, interaction, EmbedBuilder);
}
},
}).addInteractions(button);
const button = new InteractionBuilder()
.setType("button")
.setCustomId("hello")
// add the variables to the parameter list
.setCallback(async (i, c, variables) => {
// using the variable in the callback
await i.update({ content: variables.itemName, components: [] });
});
This method will always find the variables associated with the message sent by the bot. (This method may not work if you have something like a modal before a bot response as it uses the messageId as a unique identifier.)
(Better explanation)
addInteractionVariables()
function ties variables to unique message Ids AND the custom IDs of the slash command, ensuring that data remains scoped to specific interactions and is not shared or overwritten between users. This approach enhances data safety by preventing concurrency issues and ensuring only the relevant interaction can access its data. It also sorta simplifies the process of passing data between commands and interactions
If any of the approaches don't help your use case. It's always best to just use Message collectors
Example: This function is in the commands
direcotory as it is used by multiple commands, but is not a commands itself.
const function a(userBalance) {
return userBalance > 0 ? true : false;
}
module.exports = a;
isCommand
(Set to false) property with the function.const function a(userBalance) {
return userBalance > 0 ? true : false;
}
module.exports = {a, isCommand = false};
Huge credit to underctrl, Code would've not been possible if i did not watch his helpful discord.js tutorials! I had to give him credit because this package is based off moving all those files fromm his tutorial into one package.
He probably has a way better package, so go check his out!
FAQs
Discord.js Interaction and Command handler 4 Dummies
The npm package ic4d receives a total of 4 weekly downloads. As such, ic4d popularity was classified as not popular.
We found that ic4d demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now supports iOS and Android wheels, making it easier for Python developers to distribute mobile packages.
Security News
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.