Overview
Bots can respond to user interactions through message commands (parsing message content) and future interaction types like slash commands. This guide covers implementing command handling in your bot.Message Commands
The most common way to implement bot commands is by parsing message content fromMESSAGE_CREATE events.
Basic Command Handler
interface Command {
name: string;
description: string;
execute: (message: Message, args: string[]) => Promise<void>;
}
const commands = new Map<string, Command>();
const PREFIX = '!';
// Register commands
commands.set('ping', {
name: 'ping',
description: 'Check bot latency',
execute: async (message, args) => {
const sent = await sendMessage(message.channel_id, 'Pinging...');
const latency = new Date(sent.timestamp).getTime() -
new Date(message.timestamp).getTime();
await editMessage(message.channel_id, sent.id, `Pong! Latency: ${latency}ms`);
}
});
commands.set('help', {
name: 'help',
description: 'List all commands',
execute: async (message, args) => {
const commandList = Array.from(commands.values())
.map(cmd => `\`${PREFIX}${cmd.name}\` - ${cmd.description}`)
.join('\n');
await sendMessage(message.channel_id, {
embeds: [{
title: 'Available Commands',
description: commandList,
color: 0x5865F2
}]
});
}
});
// Handle MESSAGE_CREATE event
function handleMessageCreate(message: any) {
// Ignore messages from bots
if (message.author.bot) return;
// Check if message starts with prefix
if (!message.content.startsWith(PREFIX)) return;
// Parse command and arguments
const args = message.content.slice(PREFIX.length).trim().split(/\s+/);
const commandName = args.shift()?.toLowerCase();
if (!commandName) return;
// Find and execute command
const command = commands.get(commandName);
if (!command) {
sendMessage(message.channel_id, `Unknown command: \`${commandName}\``);
return;
}
try {
await command.execute(message, args);
} catch (error) {
console.error(`Error executing ${commandName}:`, error);
await sendMessage(
message.channel_id,
'An error occurred while executing that command.'
);
}
}
Advanced Argument Parsing
interface CommandArgument {
name: string;
type: 'string' | 'number' | 'user' | 'channel';
required: boolean;
description: string;
}
interface ParsedArgs {
[key: string]: string | number | null;
}
function parseArguments(
args: string[],
schema: CommandArgument[]
): ParsedArgs | null {
const parsed: ParsedArgs = {};
for (let i = 0; i < schema.length; i++) {
const arg = schema[i];
const value = args[i];
if (!value && arg.required) {
return null; // Missing required argument
}
if (!value) {
parsed[arg.name] = null;
continue;
}
switch (arg.type) {
case 'number':
const num = parseInt(value, 10);
if (isNaN(num)) return null;
parsed[arg.name] = num;
break;
case 'user':
// Parse user mention: <@123456789>
const userMatch = value.match(/^<@!?(\d+)>$/);
if (!userMatch) return null;
parsed[arg.name] = userMatch[1];
break;
case 'channel':
// Parse channel mention: <#123456789>
const channelMatch = value.match(/^<#(\d+)>$/);
if (!channelMatch) return null;
parsed[arg.name] = channelMatch[1];
break;
default:
parsed[arg.name] = value;
}
}
return parsed;
}
// Example usage
commands.set('kick', {
name: 'kick',
description: 'Kick a user from the server',
arguments: [
{ name: 'user', type: 'user', required: true, description: 'User to kick' },
{ name: 'reason', type: 'string', required: false, description: 'Kick reason' }
],
execute: async (message, args) => {
const parsed = parseArguments(args, command.arguments);
if (!parsed) {
await sendMessage(message.channel_id,
'Usage: `!kick <@user> [reason]`'
);
return;
}
const userId = parsed.user as string;
const reason = parsed.reason as string || 'No reason provided';
try {
await kickGuildMember(message.guild_id, userId, reason);
await sendMessage(message.channel_id,
`Kicked <@${userId}> for: ${reason}`
);
} catch (error) {
await sendMessage(message.channel_id,
'Failed to kick user. Check bot permissions.'
);
}
}
});
Permission Checks
Always verify permissions before executing privileged commands:import { Permissions } from '@fluxer/constants/src/ChannelConstants';
async function hasPermission(
guildId: string,
userId: string,
permission: bigint
): Promise<boolean> {
const response = await fetch(
`https://api.fluxer.app/v1/guilds/${guildId}/members/${userId}`,
{
headers: { 'Authorization': `Bot ${BOT_TOKEN}` }
}
);
if (!response.ok) return false;
const member = await response.json();
// Check member roles for permission
for (const roleId of member.roles) {
const role = await getGuildRole(guildId, roleId);
if ((BigInt(role.permissions) & permission) === permission) {
return true;
}
}
return false;
}
// Usage in command
commands.set('ban', {
name: 'ban',
description: 'Ban a user from the server',
execute: async (message, args) => {
// Check if user has BAN_MEMBERS permission
if (!await hasPermission(
message.guild_id,
message.author.id,
Permissions.BAN_MEMBERS
)) {
await sendMessage(message.channel_id,
'You do not have permission to ban members.'
);
return;
}
// Check if bot has BAN_MEMBERS permission
const botMember = await fetch(
`https://api.fluxer.app/v1/guilds/${message.guild_id}/members/@me`,
{ headers: { 'Authorization': `Bot ${BOT_TOKEN}` } }
).then(r => r.json());
if (!await hasPermission(
message.guild_id,
botMember.user.id,
Permissions.BAN_MEMBERS
)) {
await sendMessage(message.channel_id,
'I do not have permission to ban members.'
);
return;
}
// Execute ban...
}
});
Rate Limiting Commands
Prevent spam by implementing cooldowns:const cooldowns = new Map<string, Map<string, number>>();
function checkCooldown(
commandName: string,
userId: string,
cooldownSeconds: number
): boolean {
if (!cooldowns.has(commandName)) {
cooldowns.set(commandName, new Map());
}
const commandCooldowns = cooldowns.get(commandName)!;
const now = Date.now();
const cooldownEnd = commandCooldowns.get(userId) || 0;
if (now < cooldownEnd) {
const remaining = ((cooldownEnd - now) / 1000).toFixed(1);
return false; // Still on cooldown
}
commandCooldowns.set(userId, now + (cooldownSeconds * 1000));
return true;
}
// Usage
commands.set('roll', {
name: 'roll',
description: 'Roll a random number',
cooldown: 5, // 5 second cooldown
execute: async (message, args) => {
if (!checkCooldown('roll', message.author.id, 5)) {
// Don't send error message, silently ignore
return;
}
const roll = Math.floor(Math.random() * 100) + 1;
await sendMessage(message.channel_id, `You rolled: ${roll}`);
}
});
Command Categories
Organize commands into categories:interface CommandCategory {
name: string;
description: string;
commands: Map<string, Command>;
}
const categories = new Map<string, CommandCategory>();
// Register categories
categories.set('utility', {
name: 'Utility',
description: 'General utility commands',
commands: new Map([
['ping', pingCommand],
['info', infoCommand],
['help', helpCommand]
])
});
categories.set('moderation', {
name: 'Moderation',
description: 'Server moderation commands',
commands: new Map([
['kick', kickCommand],
['ban', banCommand],
['warn', warnCommand]
])
});
// Enhanced help command
commands.set('help', {
name: 'help',
description: 'List all commands or get info about a specific command',
execute: async (message, args) => {
if (args.length === 0) {
// List all categories
const fields = Array.from(categories.values()).map(category => ({
name: category.name,
value: `${category.description}\n` +
`Commands: ${Array.from(category.commands.keys()).map(c => `\`${c}\``).join(', ')}`,
inline: false
}));
await sendMessage(message.channel_id, {
embeds: [{
title: 'Bot Commands',
description: `Use \`${PREFIX}help <command>\` for detailed info`,
fields,
color: 0x5865F2
}]
});
} else {
// Show specific command info
const commandName = args[0].toLowerCase();
let command: Command | undefined;
for (const category of categories.values()) {
command = category.commands.get(commandName);
if (command) break;
}
if (!command) {
await sendMessage(message.channel_id,
`Unknown command: \`${commandName}\``
);
return;
}
await sendMessage(message.channel_id, {
embeds: [{
title: `Command: ${command.name}`,
description: command.description,
fields: command.arguments?.map(arg => ({
name: arg.required ? `<${arg.name}>` : `[${arg.name}]`,
value: `${arg.description} (${arg.type})`,
inline: true
})) || [],
footer: { text: `Usage: ${PREFIX}${command.name}` },
color: 0x5865F2
}]
});
}
}
});
Dynamic Command Loading
Load commands from separate files:// commands/ping.ts
export default {
name: 'ping',
description: 'Check bot latency',
execute: async (message, args) => {
await sendMessage(message.channel_id, 'Pong!');
}
};
// commands/index.ts
import { readdirSync } from 'fs';
import { join } from 'path';
export async function loadCommands() {
const commandFiles = readdirSync(join(__dirname, 'commands'))
.filter(file => file.endsWith('.ts') && file !== 'index.ts');
for (const file of commandFiles) {
const command = await import(join(__dirname, 'commands', file));
commands.set(command.default.name, command.default);
console.log(`Loaded command: ${command.default.name}`);
}
console.log(`Loaded ${commands.size} commands`);
}
Message Reactions
Use reactions for interactive menus:const REACTION_NUMBERS = ['1οΈβ£', '2οΈβ£', '3οΈβ£', '4οΈβ£', '5οΈβ£'];
commands.set('poll', {
name: 'poll',
description: 'Create a poll',
execute: async (message, args) => {
if (args.length < 3) {
await sendMessage(message.channel_id,
'Usage: `!poll <question> <option1> <option2> ...`'
);
return;
}
const question = args[0];
const options = args.slice(1, 6); // Max 5 options
const pollMessage = await sendMessage(message.channel_id, {
embeds: [{
title: 'π Poll',
description: question,
fields: options.map((opt, i) => ({
name: `${REACTION_NUMBERS[i]} Option ${i + 1}`,
value: opt,
inline: false
})),
color: 0x5865F2
}]
});
// Add reaction options
for (let i = 0; i < options.length; i++) {
await addReaction(
message.channel_id,
pollMessage.id,
REACTION_NUMBERS[i]
);
}
}
});
Embed Responses
Create rich embed responses:interface EmbedField {
name: string;
value: string;
inline?: boolean;
}
interface Embed {
title?: string;
description?: string;
url?: string;
color?: number;
timestamp?: string;
footer?: { text: string; icon_url?: string };
thumbnail?: { url: string };
image?: { url: string };
author?: { name: string; url?: string; icon_url?: string };
fields?: EmbedField[];
}
function createEmbed(options: Embed): Embed {
return {
...options,
timestamp: options.timestamp || new Date().toISOString()
};
}
// Usage
commands.set('serverinfo', {
name: 'serverinfo',
description: 'Show server information',
execute: async (message, args) => {
const guild = await fetch(
`https://api.fluxer.app/v1/guilds/${message.guild_id}`,
{ headers: { 'Authorization': `Bot ${BOT_TOKEN}` } }
).then(r => r.json());
await sendMessage(message.channel_id, {
embeds: [createEmbed({
title: guild.name,
thumbnail: { url: guild.icon ? `https://cdn.fluxer.app/icons/${guild.id}/${guild.icon}.png` : '' },
fields: [
{ name: 'Owner', value: `<@${guild.owner_id}>`, inline: true },
{ name: 'Members', value: guild.member_count.toString(), inline: true },
{ name: 'Created', value: new Date(guild.id / 4194304 + 1420070400000).toLocaleDateString(), inline: true }
],
color: 0x5865F2
})]
});
}
});
Error Handling
Implement comprehensive error handling:class CommandError extends Error {
constructor(
message: string,
public userMessage: string,
public code?: string
) {
super(message);
this.name = 'CommandError';
}
}
async function executeCommand(
command: Command,
message: any,
args: string[]
) {
try {
await command.execute(message, args);
} catch (error) {
console.error(`Error in ${command.name}:`, error);
let userMessage = 'An unexpected error occurred.';
if (error instanceof CommandError) {
userMessage = error.userMessage;
} else if (error instanceof Error) {
// Check for specific API errors
if (error.message.includes('Missing Permissions')) {
userMessage = 'I don\'t have permission to do that.';
} else if (error.message.includes('Unknown')) {
userMessage = 'That resource could not be found.';
}
}
await sendMessage(message.channel_id, {
embeds: [{
title: 'β Error',
description: userMessage,
color: 0xED4245
}]
});
}
}
Logging and Analytics
Track command usage:interface CommandMetrics {
uses: number;
errors: number;
totalExecutionTime: number;
}
const metrics = new Map<string, CommandMetrics>();
function recordCommandUse(
commandName: string,
success: boolean,
executionTime: number
) {
if (!metrics.has(commandName)) {
metrics.set(commandName, {
uses: 0,
errors: 0,
totalExecutionTime: 0
});
}
const metric = metrics.get(commandName)!;
metric.uses++;
if (!success) metric.errors++;
metric.totalExecutionTime += executionTime;
}
commands.set('stats', {
name: 'stats',
description: 'Show bot statistics',
execute: async (message, args) => {
const fields = Array.from(metrics.entries())
.sort((a, b) => b[1].uses - a[1].uses)
.slice(0, 10)
.map(([name, metric]) => ({
name: `\`${name}\``,
value: `Uses: ${metric.uses} | Errors: ${metric.errors} | Avg: ${(metric.totalExecutionTime / metric.uses).toFixed(2)}ms`,
inline: false
}));
await sendMessage(message.channel_id, {
embeds: [{
title: 'π Bot Statistics',
description: 'Top 10 most used commands',
fields,
color: 0x5865F2
}]
});
}
});
Best Practices
Do:
- Validate all user input before processing
- Check permissions before executing privileged commands
- Provide helpful error messages
- Implement cooldowns to prevent spam
- Log command usage for debugging
- Use embeds for structured responses
- Execute commands from other bots
- Ignore rate limits
- Store sensitive data in command arguments
- Respond to every message (check prefix first)
- Block the event loop with long-running operations
Next Steps
Webhooks
Use webhooks for event delivery and integrations
Message API
Learn about sending messages with the REST API
Permissions
Understanding the permission system
Embeds
Create rich embed messages