In this tutorial, we'll create a command bot using Fluxer.Net's powerful CommandService framework. This framework provides automatic argument parsing, type conversion, and a clean module-based structure for organizing your commands.
The CommandService is a framework that makes building command bots easy by handling:
First, let's set up the basic bot with the CommandService:
using System.Reflection;
using Fluxer.Net;
using Fluxer.Net.Commands;
using Fluxer.Net.Data.Models;
using Serilog;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.CreateLogger();
const string token = "YOUR_TOKEN_HERE";
var config = new FluxerConfig
{
Serilog = Log.Logger
};
var apiClient = new ApiClient(token, config);
var gatewayClient = new GatewayClient(token, config);
// Create the CommandService with '/' as the prefix
var commands = new CommandService(
prefixChar: '/',
logger: Log.Logger,
services: null
);
// Register all command modules from your assembly
await commands.AddModulesAsync(Assembly.GetExecutingAssembly());
gatewayClient.Ready += (data) =>
{
Log.Information("Bot ready! Logged in as {Username}", data.User.Username);
};
// Handle incoming messages and execute commands
gatewayClient.MessageCreate += async (data) =>
{
// Ignore messages without an author (system messages, webhooks)
if (data.Author == null) return;
// Check if message starts with the prefix
int argPos = 0;
if (data.Content?.StartsWith('/') == true)
{
argPos = 1; // Skip the prefix character
// Create a command context
var context = new CommandContext(apiClient, gatewayClient, data);
// Execute the command
var result = await commands.ExecuteAsync(context, argPos);
// Handle errors
if (!result.IsSuccess)
{
Log.Warning("Command failed: {Error}", result.Error);
// Optionally send error message to user
if (result.ErrorType == CommandError.BadArgCount ||
result.ErrorType == CommandError.ParseFailed)
{
await apiClient.SendMessage(data.ChannelId, new()
{
Content = $"❌ Error: {result.Error}"
});
}
}
}
};
await gatewayClient.ConnectAsync();
await Task.Delay(-1);
Commands are organized into modules. Create a new file called BasicCommands.cs:
using Fluxer.Net.Commands;
using Fluxer.Net.Commands.Attributes;
namespace MyBot.Modules;
public class BasicCommands : ModuleBase
{
[Command("ping")]
[Summary("Check if the bot is responsive")]
public async Task PingCommand()
{
await ReplyAsync("Pong!");
}
[Command("hello")]
[Alias("hi", "hey")]
[Summary("Get a friendly greeting")]
public async Task HelloCommand()
{
await ReplyAsync($"Hello, <@{Context.User.Id}>!");
}
[Command("info")]
[Summary("Show bot information")]
public async Task InfoCommand()
{
await ReplyAsync(
"**My Bot**\n" +
"Built with Fluxer.Net\n\n" +
"Available Commands:\n" +
"• `/ping` - Check if bot is responsive\n" +
"• `/hello` - Get a greeting\n" +
"• `/info` - Show this information"
);
}
}
All command modules must inherit from ModuleBase, which provides:
Context - Access to the message, user, channel, and API clientsReplyAsync() - Helper method to send a message to the current channel[Command("name")] - Defines a command with the given name[Alias("alt1", "alt2")] - Alternative names for the command[Summary("description")] - Description of what the command doesThe CommandService automatically parses and converts command arguments:
public class MathCommands : ModuleBase
{
[Command("add")]
[Summary("Add two numbers together")]
public async Task AddCommand(int a, int b)
{
await ReplyAsync($"{a} + {b} = {a + b}");
}
[Command("echo")]
[Summary("Echo back your message")]
public async Task EchoCommand([Remainder] string message)
{
await ReplyAsync(message);
}
[Command("greet")]
[Summary("Greet someone by name")]
public async Task GreetCommand(string name = "stranger")
{
await ReplyAsync($"Hello, {name}!");
}
}
The CommandService supports automatic conversion for many types:
string, int, long, ulong, boolfloat, double, decimalDateTime, TimeSpan[Remainder] - Captures all remaining text as one parameter (useful for messages)string name = "stranger"Here's a more complex command that fetches user information:
[Command("userinfo")]
[Summary("Get information about a user")]
public async Task UserInfoCommand(ulong? userId = null)
{
// Use provided user ID or default to command author
ulong targetId = userId ?? Context.User.Id;
try
{
var user = await Context.Client.GetUser(targetId);
var info = $@"**User Information**
**Username:** {user.Username}
**ID:** {user.Id}
**Created:** {user.CreatedAt:MMM dd, yyyy}
**Premium:** {(user.Premium > 0 ? "Yes" : "No")}";
await ReplyAsync(info);
}
catch (Exception ex)
{
await ReplyAsync("Failed to fetch user information.");
}
}
For more visually appealing responses, you can use embeds with the EmbedBuilder:
using Fluxer.Net.Data.Models;
using Fluxer.Net.EmbedBuilder;
[Command("embed")]
[Summary("Show an example rich embed")]
public async Task EmbedCommand()
{
var embed = new EmbedBuilder()
.WithTitle("Example Embed")
.WithDescription("This is a rich embed created with EmbedBuilder!")
.WithColor(0x5865F2) // Blue color
.WithAuthor(
name: Context.User.Username,
iconUrl: Context.User.Avatar != null
? $"https://cdn.fluxer.dev/avatars/{Context.User.Id}/{Context.User.Avatar}.png"
: null
)
.AddField("Field 1", "Inline field", inline: true)
.AddField("Field 2", "Another inline field", inline: true)
.AddField("Full Width", "This field is not inline", inline: false)
.WithFooter("Fluxer.Net")
.WithCurrentTimestamp()
.Build();
await Context.Client.SendMessage(Context.ChannelId, new()
{
Content = "Here's an embed:",
Embeds = new List
See the EmbedBuilder Guide for more examples and advanced usage.
Run your bot and try these commands in a channel:
/ping - Should respond with "Pong!"/hello - Should greet you with a mention/info - Should show bot information/add 5 3 - Should respond with "5 + 3 = 8"/echo Hello World - Should respond with "Hello World"Group related commands together in separate module classes:
// ModerationCommands.cs
public class ModerationCommands : ModuleBase { ... }
// FunCommands.cs
public class FunCommands : ModuleBase { ... }
// UtilityCommands.cs
public class UtilityCommands : ModuleBase { ... }
Always include [Summary] attributes to document your commands:
[Command("kick")]
[Summary("Remove a member from the server")]
public async Task KickCommand(ulong userId, [Remainder] string reason = "No reason")
{
// Implementation
}
Always wrap API calls in try-catch blocks within your commands:
[Command("getuser")]
public async Task GetUserCommand(ulong userId)
{
try
{
var user = await Context.Client.GetUser(userId);
await ReplyAsync($"Found user: {user.Username}");
}
catch (Exception)
{
await ReplyAsync("User not found or an error occurred.");
}
}
The CommandService provides additional advanced features for building sophisticated bots:
Allow multiple names for the same command:
[Command("userinfo")]
[Alias("user", "whois", "profile")]
public async Task UserInfoCommand() { ... }
Add requirements that must be met before a command can execute:
using Fluxer.Net.Commands.Attributes;
[Command("announce")]
[RequireOwner] // Only bot owner can use this
public async Task AnnounceCommand([Remainder] string message)
{
await ReplyAsync($"📢 {message}");
}
[Command("ban")]
[RequireContext(ContextType.Guild)] // Only works in guilds, not DMs
public async Task BanCommand(ulong userId)
{
// Ban logic
}
Override methods in your module to run code before/after command execution:
public class MyCommands : ModuleBase
{
protected override void BeforeExecute()
{
// Runs before every command in this module
Log.Information("User {User} is running a command", Context.User.Username);
}
protected override void AfterExecute()
{
// Runs after every command in this module
Log.Information("Command completed successfully");
}
}
Congratulations! You now have a working command bot using the CommandService framework. Here are some ideas to expand your bot:
Check out the reference documentation to learn more: