-
Notifications
You must be signed in to change notification settings - Fork 0
Home
A lightweight, dependency-free Discord bot SDK for .NET 10 that provides direct access to Discord API v10 (REST + Gateway).
- Beginner's Guide - Complete guide for Discord bot beginners
- Command Permissions ⭐ NEW v1.8.0 - Control who can use slash commands with dual-layer permissions
- Working with Entities - Comprehensive guide to channels, guilds, members, and more
- Components - Buttons, select menus, file attachments, and interactive components
- Getting Started - Build your first bot in minutes
- Installation - NuGet and source reference setup
- Examples - Easy-to-follow examples for beginners
- API Reference - Complete API documentation
- Discord Community - Join our Discord server for support and discussions
dotnet add package SimpleDiscordDotNetNuGet: https://www.nuget.org/packages/SimpleDiscordDotNet/ GitHub: https://github.com/SimpleDiscordNet/SimpleDiscordNet
- Slash commands with full option support - Attribute-based handlers with constraints, choices, autocomplete, and all Discord option types (STRING, INTEGER, NUMBER, BOOLEAN, USER, CHANNEL, ROLE)
- 🆕 v1.8.0: Dual-layer command permissions - Discord-level defaults + runtime per-guild custom rules
- Components and modals - Buttons, select menus, and forms
- Zero external dependencies - BCL only
- Builder pattern and DI-friendly
- Global static event hub for logs and domain events
- Source generator for zero-reflection command/component discovery
- Native AOT compatible - Full trimming and AOT support with 100% zero reflection
- Memory-optimized - Span and Memory APIs for 30-50% less GC pressure
- Horizontal sharding - 3 modes: single process, multi-shard, or distributed coordinator/worker
- Enhanced InteractionContext - Direct Member and Guild access without cache lookups
- HTTPS-secured sharding - TLS 1.3+ for distributed coordinator communication
- Messages: send, edit, delete, bulk delete, pin/unpin
- Reactions: add, remove, clear, get users who reacted
- Embeds & Components: rich embeds, buttons, selects, modals
- Permissions: channel overwrites (per-role and per-member)
- Roles: create, edit, delete, assign/remove, permission checking
- Channels: create, edit, delete, categories, text, voice
- Threads: create, join, leave, add/remove members
- Moderation: kick, ban, unban, role assignment
- Events: comprehensive gateway events for all entity changes
- Rate Limiting: advanced bucket tracking, monitoring, and zero message loss
- Access cached Guilds/Channels/Members/Users/Roles from anywhere
-
🆕 Live observable collections - Thread-safe
INotifyCollectionChangedfor WPF/MAUI/Avalonia UI binding - Filtered collections (Categories, TextChannels, VoiceChannels, Threads)
- Helper methods for querying by guild, category, role
- Type-safe, read-only snapshots (backward compatible)
- Getting Started - Your first Discord bot
- Installation - Setup and configuration
- Dependency Injection - Using with DI containers
- Commands - Slash command creation and handling
- Command Permissions ⭐ NEW v1.8.0 - Control who can use commands
- Components - Buttons, selects, modals, and forms
- Messages & Embeds - Sending rich messages
- Reactions - Adding and managing reactions
- Permissions & Roles - Role and permission management
- Channels & Categories - Channel organization
- Threads - Thread creation and management
- Moderation - Member moderation tools
- DiscordContext - Ambient data access from anywhere
- Events - Handling Discord gateway events
- Sharding - Horizontal scaling with distributed coordinator/worker architecture
- Performance Optimizations - Memory and CPU optimization techniques
- Rate Limit Monitoring - Advanced rate limiting with monitoring
- API Reference - Quick method reference
- FAQ - Common questions and troubleshooting
using SimpleDiscordNet;
using SimpleDiscordNet.Commands;
using SimpleDiscordNet.Primitives;
// Define your commands with options
public sealed class MyCommands
{
[SlashCommand("greet", "Greet someone")]
public async Task GreetAsync(
InteractionContext ctx,
[CommandOption("name", "Person's name", MinLength = 2, MaxLength = 32)]
string name,
[CommandOption("style", "Greeting style", Choices = "Friendly:👋,Formal:🤝,Casual:✌️")]
string style)
{
// ✨ NEW in v1.4.0: Direct access to Member and Guild
string memberName = ctx.Member?.User.Username ?? "Unknown";
string guildName = ctx.Guild?.Name ?? "DM";
await ctx.RespondAsync($"{style} Hello, {name}! Called by {memberName} in {guildName}", ephemeral: true);
}
}
// Build and start your bot
var bot = DiscordBot.NewBuilder()
.WithToken(File.ReadAllText("token.txt").Trim())
.WithIntents(DiscordIntents.Guilds)
.WithDevelopmentMode(true)
.WithDevelopmentGuild("YOUR_DEV_GUILD_ID")
.Build();
await bot.StartAsync();
await Task.Delay(Timeout.Infinite);SimpleDiscordDotNet is designed with these principles:
- Simplicity First - Extremely easy to use with minimal boilerplate
- Zero Dependencies - No external packages, only BCL
- Performance - Memory-optimized with Span, Memory, and zero-allocation APIs
- Modern C# - Built with C# 14 and .NET 10 features including span-based APIs
- AOT Ready - Compatible with Native AOT compilation with 100% zero reflection
- Well Documented - Every public method has XML docs with examples
New Member Voice Control Operations:
// Deafen/undeafen members in voice channels
await bot.DeafenMemberAsync(guildId, userId);
await bot.UndeafenMemberAsync(guildId, userId);
// Move members between voice channels
await bot.MoveMemberToVoiceChannelAsync(guildId, userId, targetChannelId);
// Disconnect member from voice
await bot.DisconnectMemberFromVoiceAsync(guildId, userId);Guild Bans Management:
// Get all banned users
var bans = await bot.GetGuildBansAsync(guildId);
foreach (var ban in bans)
{
Console.WriteLine($"{ban.user.username}: {ban.reason}");
}
// Get specific ban
var ban = await bot.GetGuildBanAsync(guildId, userId);Audit Log Access with Filtering:
// Get audit log with filters
var auditLog = await bot.GetAuditLogAsync(
guildId,
userId: moderatorId, // Filter by who performed the action
actionType: (int)AuditLogAction.MemberBanAdd, // Filter by action type
limit: 100);
// Process audit log entries
foreach (var entry in auditLog.audit_log_entries)
{
Console.WriteLine($"Action: {entry.action_type}, Reason: {entry.reason}");
}All operations support both string and ulong overloads for maximum flexibility!
Control who can use your slash commands with a powerful two-layer system:
Layer 1: Discord-Level Default Permissions
// Only visible to users with BanMembers permission
[RequirePermissions(PermissionFlags.BanMembers)]
[SlashCommand("ban", "Ban a user")]
public async Task BanCommand(InteractionContext ctx, DiscordUser user) { }Layer 2: Runtime Per-Guild Custom Rules
// Further restrict to specific role in this guild
bot.Permissions.RegisterGuildRule("123456789", "ban", ctx =>
ctx.Member?.Roles.Any(r => r.Id == "moderator_role_id") ?? false);
// Load from database for dynamic configuration
var guildConfigs = await database.GetAllGuildConfigsAsync();
foreach (var config in guildConfigs)
{
bot.Permissions.RegisterGuildRule(config.GuildId, "ban", ctx =>
config.ModeratorRoleIds.Any(roleId =>
ctx.Member?.Roles.Any(r => r.Id == roleId) ?? false));
}Features:
- 100% AOT/Trim Compatible - No reflection, no dynamic code
- Shard-Safe - Works across all sharding modes
- Thread-Safe - Concurrent dictionary with locks
- Beginner-Friendly - Simple attribute + delegate API
- Dynamic - Change permissions per-guild without redeploying
See Command Permissions for complete guide and examples.
Thread-safe collections with INotifyCollectionChanged support for real-time UI data binding:
// WPF Example
public class MainViewModel
{
public MainViewModel()
{
// Bind directly to live collections
Guilds = DiscordContext.LiveGuilds;
var guildId = 123456789;
Channels = DiscordContext.GetLiveChannelsForGuild(guildId);
Members = DiscordContext.GetLiveMembersForGuild(guildId);
}
public ObservableConcurrentDictionary<ulong, DiscordGuild> Guilds { get; }
public ObservableConcurrentList<DiscordChannel> Channels { get; }
public ObservableConcurrentList<DiscordMember> Members { get; }
}
// Configure UI thread marshaling
var bot = DiscordBot.NewBuilder()
.WithToken(token)
.WithSynchronizationContext(SynchronizationContext.Current) // ✨ UI thread marshaling
.Build();Features:
-
Thread-safe - Built on
ConcurrentDictionaryandReaderWriterLockSlim -
Batch operations -
BeginBatchUpdate(),AddOrUpdateRange()to prevent UI thrashing on large servers -
UI thread marshaling - Optional
SynchronizationContextfor automatic UI thread dispatching - Backward compatible - All existing snapshot APIs preserved
See Working with Entities for complete examples (WPF, MAUI, Avalonia).
Full support for advanced Discord component features:
// Default values in select menus
var userSelect = new UserSelect("team",
defaultValues: new[] { SelectDefaultValue.User(userId) });
// Access resolved entity data
[UserSelectHandler("team")]
public async Task HandleTeam(InteractionContext ctx)
{
var users = ctx.GetResolvedUsers(); // Full user objects!
foreach (var user in users)
Console.WriteLine($"{user.Username}#{user.Discriminator}");
}
// Emoji support in options
var options = new[]
{
new SelectOption("Red", "red", emojiName: "🔴"),
new SelectOption("Blue", "blue", emojiName: "🔵")
};Type-safe handler registration:
[ButtonHandler("confirm")] // Buttons only
[UserSelectHandler("users")] // User selects only
[RoleSelectHandler("roles")] // Role selects only
[ChannelSelectHandler("channels")] // Channel selects only
[MentionableSelectHandler("any")] // Users and rolesSend files easily with multiple overloads:
var builder = new MessageBuilder()
.WithContent("Here are the files!")
.AddFile("report.pdf", pdfBytes)
.AddFile("data.json", jsonStream);
await ctx.RespondAsync(builder);
// Async stream support
await builder.AddFileAsync("download.zip", httpStream);Fetch and manage message history:
// Get recent messages
var messages = await channel.GetMessagesAsync(50);
foreach (var msg in messages)
Console.WriteLine(msg.Content);
// Bulk delete
await channel.BulkDeleteMessagesAsync(10);All collection methods now return IEnumerable<T> for better LINQ support:
var channels = await bot.GetGuildChannelsAsync(guildId);
var textChannels = channels.Where(c => c.Type == ChannelType.GuildText);Issues and PRs are welcome on GitHub! Keep the code dependency-free and aligned with the existing style.
SimpleDiscordDotNet is licensed under the Apache License, Version 2.0.
Ready to get started? Head to Getting Started to build your first bot!