Discord.Net¶
Discord.Net is an unofficial C# wrapper around the Discord Chat Service. It offers several methods to create automated operations, bots, or even custom clients.
Feel free to join us in the Discord API chat.
Warning
This is a beta!
This library has been built thanks to a community effort reverse engineering the Discord client. As the API is still unofficial, it may change at any time without notice, breaking this library as well. Discord.Net itself is still in development (and is currently undergoing a rewrite) and you may encounter breaking changes throughout development until the official Discord API is released.
It is highly recommended that you always use the latest version and please report any bugs you find to our Discord chat.
This Documentation is currently undergoing a rewrite. Some pages (marked with a wrench) are not updated, or are not completed yet. Please submit any feedback to foxbot#0282
on Discord.
Getting Started¶
Requirements¶
Discord.Net currently requires logging in with a user account, however Discord will soon require the use of Bot Accounts. You can register a bot account here.
Bot Accounts must be added to a server, you must use the OAuth 2 Flow to add them to servers.
Installation¶
You can get Discord.Net from NuGet:
If you have trouble installing from NuGet, try installing dependencies manually.
You can also pull the latest source from GitHub
As an alternative, precompiled binaries are available on our Continuous Integration server.
Async¶
Discord.Net uses C# tasks extensively - nearly all operations return one. It is highly recommended that these tasks be awaited whenever possible. To do so requires the calling method be marked as async, which can be problematic in a console application. An example of how to get around this is provided below.
For more information, go to MSDN’s Await-Async section.
First Steps¶
using Discord;
class Program
{
static void Main(string[] args) => new Program().Start();
private DiscordClient _client;
public void Start()
{
_client = new DiscordClient();
_client.MessageReceived += async (s, e) =>
{
if (!e.Message.IsAuthor)
await e.Channel.SendMessage(e.Message.Text);
};
_client.ExecuteAndWait(async () => {
await _client.Connect("aaaaabbbbbbcccccdddddeeeeefffffggggg", TokenType.Bot)
});
}
}
First Steps Annotated¶
The above example will be enough for you to create a basic Echo Bot. Keep in mind, echo bots are discouraged in public servers, so make sure your bot is only in your testing server.
That might have been a lot, so let’s go through each line.
using Discord;
- This is the first line, and it declares that we will be using the Discord.Net API in our class.
Next, we create the Program class, as you would generally do in C#.
static void Main(string[] args) => new Program().Start();
- It’s not good practice to run all of your code in one static method, so we create a new non-static instance of Program
and run the Start
method.
private DiscordClient _client;
- Here we define the main DiscordClient
that we will be using in our project. It’s standard convention to name the private DiscordClient _client
, and I encourage that you do so also.
public void Start()
- As explained above, it’s not good practice to run everything out of Main, so here is our new Main method.
_client = new DiscordClient();
- Here, we define _client
as a new DiscordClient
, so we can begin to use it.
_client.MessageReceived += async (s, e) => {
- This is a lambda, a feature which allows us to define functions or handlers inline, without creating a new method. Here, we are hooking into the MessageReceived
event on the DiscordClient. The async (s, e)
indicates that the lambda will be an async function, and we are passing two parameters, s(ender) and e(vent args) into it.
if (!e.Message.IsAuthor)
- This ensures that we did not create the message that was received. This helps to keep us from creating an infinite echo bot.
await e.Channel.SendMessage(e.Message.Text)
- Here, we are sending a message to the channel the message was received in. The contents of the message we are sending is identical to that of the message we received.
};
- Close up the lambda
_client.ExecuteAndWait(async () => {
- This invokes the ExecuteAndWait
function of the DiscordClient, which allows us to run async code in a non-async method, and block the Console app until the Discord Client disconnects. Inside this function, we are creating an async lambda with no parameters (ExecuteAndWait takes an Action
, so we cannot use and parameters).
await _client.Connect("aaabbbccc");
- Next, we are going to connect our client, using the bot token that Discord provides us. If you are unsure of how to access your token, see this image.
Finally, we close up our lambdas and program.
Logging¶
Discord.Net will log all of its events/exceptions using a built-in LogManager. This LogManager can be accessed through DiscordClient.Log
Usage¶
To handle Log Messages through Discord.Net’s Logger, you must hook into the Log.Message<LogMessageEventArgs> Event.
The LogManager does not provide a string-based result for the message, you must put your own message format together using the data provided through LogMessageEventArgs See the Example for a snippet of logging.
Logging Your Own Data¶
The LogManager included in Discord.Net can also be used to log your own messages.
You can use DiscordClient.Log.Log(LogSeverity, Source, Message, Exception), or one of the shortcut helpers, to log data.
Example: .. code-block:: c#
- _client.MessageReceived += async (s, e) {
- // Log a new Message with Severity Info, Sourced from ‘MessageReceived’, with the Message Contents. _client.Log.Info(“MessageReceived”, e.Message.Text, null);
};
Example¶
class Program
{
private static DiscordBotClient _client;
static void Main(string[] args)
{
var client = new DiscordClient(x =>
{
LogLevel = LogSeverity.Info
});
_client.Log.Message += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}");
client.ExecuteAndWait(async () =>
{
await client.Connect("discordtest@email.com", "Password123");
if (!client.Servers.Any())
await client.AcceptInvite("aaabbbcccdddeee");
});
}
}
Server Management¶
Discord.Net will allow you to manage most settings of a Discord server.
Usage¶
You can create Channels, Invites, and Roles on a server using the CreateChannel, CreateInvite, and CreateRole function of a Server, respectively.
You may also edit a server’s name, icon, and region.
// Create a Channel and retrieve the Channel object
var _channel = await _server.CreateChannel("announcements", ChannelType.Text);
// Create an Invite and retrieve the Invite object
var _invite = await _server.CreateInvite(maxAge: null, maxUses: 25, tempMembership: false, withXkcd: false);
// Create a Role and retrieve the Role object
var _role = await _server.CreateRole(name: "Bots", permissions: null, color: Color.DarkMagenta, isHoisted: false);
// Edit a server
var _ioStream = new System.IO.StreamReader("clock-0500-1952.png").BaseStream
_server.Edit(name: "19:52 | UTC-05:00", region: "east", icon: _ioStream, iconType: ImageType.Png);
// Prune Users
var _pruneCount = await _server.PruneUsers(30, true);
Invite Parameters¶
maxAge: The time (in seconds) until the invite expires. Use null for infinite. maxUses: The maximum amount of uses the invite has before it expires. tempMembership: Whether or not to kick a user when they disconnect. withXkcd: Generate the invite with an XKCD 936 style URL
Role Parameters¶
name: The name of the role permissions: A set of ServerPermissions for the role to use by default color: The color of the role, recommended to use Discord.Color isHoisted: Whether a role’s users should be displayed separately from other users in the user list.
Edit Parameters¶
name: The server’s name region: The region the voice server is hosted in icon: A System.IO.Stream that will read an image file iconType: The type of image being sent (png/jpeg).
User Management¶
Banning¶
To ban a user, invoke the Ban function on a Server object.
_server.Ban(_user, 30);
The pruneDays parameter, which defaults to 0, will remove all messages from a user dating back to the specified amount of days.
Permissions¶
There are two types of permissions: Channel Permissions and Server Permissions.
Permission Overrides¶
Channel Permissions are expressed using an enum, PermValue
.
The three states are fairly straightforward -
PermValue.Allow
: Allow the user to perform a permission.
PermValue.Deny
: Deny the user to perform a permission.
PermValue.Inherit
: The user will inherit the permission from its role.
Channel Permissions¶
Channel Permissions are controlled using a set of flags:
Flag | Type | Description |
---|---|---|
AttachFiles | Text | Send files to a channel. |
Connect | Voice | Connect to a voice channel. |
CreateInstantInvite | General | Create an invite to the channel. |
DeafenMembers | Voice | Prevent users of a voice channel from hearing other users (server-wide). |
EmbedLinks | Text | Create embedded links. |
ManageChannel | General | Manage a channel. |
ManageMessages | Text | Remove messages in a channel. |
ManagePermissions | General | Manage the permissions of a channel. |
MentionEveryone | Text | Use @everyone in a channel. |
MoveMembers | Voice | Move members around in voice channels. |
MuteMembers | Voice | Mute users of a voice channel (server-wide). |
ReadMessageHistory | Text | Read the chat history of a voice channel. |
ReadMessages | Text | Read any messages in a text channel; exposes the text channel to users. |
SendMessages | Text | Send messages in a text channel. |
SendTTSMessages | Text | Send TTS messages in a text channel. |
Speak | Voice | Speak in a voice channel. |
UseVoiceActivation | Voice | Use Voice Activation in a text channel (for large channels where PTT is preferred) |
Each flag is a PermValue; see the section above.
Setting Channel Permissions¶
To set channel permissions, create a new ChannelPermissionOverrides
, and specify the flags/values that you want to override.
Then, update the user, by doing Channel.AddPermissionsRule(_user, _overwrites);
Roles¶
Accessing/modifying permissions for roles is done the same way as user permissions, just using the overload for a Role. See above sections.
Server Permissions¶
Server Permissions can be viewed with User.ServerPermissions
, but at the time of this writing cannot be set.
A user’s server permissions also contain the default values for it’s channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions:
Flag | Type | Description |
---|---|---|
BanMembers | Server | Ban users from the server. |
KickMembers | Server | Kick users from the server. They can still rejoin. |
ManageRoles | Server | Manage roles on the server, and their permissions. |
ManageChannels | Server | Manage channels that exist on the server (add, remove them) |
ManageServer | Server | Manage the server settings. |
Example¶
// Find a User's Channel Permissions
var UserPerms = _channel.GetPermissionsRule(_user);
// Set a User's Channel Permissions
var NewOverwrites = new ChannelPermissionOverrides(sendMessages: PermValue.Deny);
await channel.AddPermissionsRule(_user, NewOverwrites);
Commands¶
This page is incomplete. While the information below is accurate, it should be noted that it is not thorough.
The Discord.Net.Commands package DiscordBotClient extends DiscordClient with support for commands.
Setup¶
To use Commands, you must first install the CommandService to your DiscordClient.
_client.UsingCommands(x => {
x.PrefixChar = '$';
x.HelpMode = HelpMode.Public;
});
By default, your bot will also respond to @mentions. It is reccomended you leave this feature enabled, and in crowded servers, require a mention to trigger your bot.
Example (Simple)¶
//Since we have setup our CommandChar to be '~', we will run this command by typing ~greet
_client.GetService<CommandService>().CreateCommand("greet") //create command greet
.Alias(new string[] { "gr", "hi" }) //add 2 aliases, so it can be run with ~gr and ~hi
.Description("Greets a person.") //add description, it will be shown when ~help is used
.Parameter("GreetedPerson", ParameterType.Required) //as an argument, we have a person we want to greet
.Do(async e =>
{
await e.Channel.SendMessage($"{e.User.Name} greets {e.GetArg("GreetedPerson")}");
//sends a message to channel with the given text
});
Example (Groups)¶
//we would run our commands with ~do greet X and ~do bye X
_client.GetService<CommandService>().CreateGroup("do", cgb =>
{
cgb.CreateCommand("greet")
.Alias(new string[] { "gr", "hi" })
.Description("Greets a person.")
.Parameter("GreetedPerson", ParameterType.Required)
.Do(async e =>
{
await e.Channel.SendMessage($"{e.User.Name} greets {e.GetArg("GreetedPerson")}");
});
cgb.CreateCommand("bye")
.Alias(new string[] { "bb", "gb" })
.Description("Greets a person.")
.Parameter("GreetedPerson", ParameterType.Required)
.Do(async e =>
{
await e.Channel.SendMessage($"{e.User.Name} says goodbye to {e.GetArg("GreetedPerson")}");
});
});
Voice¶
Installation¶
Before setting up the AudioService, you must first install the package from NuGet or GitHub.
Add the package to your solution, and then import the namespace Discord.Audio
.
Discord.Audio also relies on the Opus Audio Library for encoding audio. You must acquire a compiled binary of Opus, and place it in the directory your project runs from (/ on dnx, /bin/debug on net45). A 32-bit binary is available for your convienence.
You will also need libsodium for voice encryption. You must acquire a compiled binary of libsodium, and place it in the directory your project runs from. A precompiled binary is also available here. You may also find 64-bit/linux releases from here.
Setup¶
To use audio, you must install the AudioService to your DiscordClient.
var _client = new DiscordClient();
_client.UsingAudio(x => // Opens an AudioConfigBuilder so we can configure our AudioService
{
x.Mode = AudioMode.Outgoing; // Tells the AudioService that we will only be sending audio
});
Joining a Channel¶
Joining Voice Channels is pretty straight-forward, and is required to send Audio. This will also allow us to get an IAudioClient, which we will later use to send Audio.
var voiceChannel = _client.FindServers("Music Bot Server").FirstOrDefault().VoiceChannels.FirstOrDefault(); // Finds the first VoiceChannel on the server 'Music Bot Server'
var _vClient = await _client.GetService<AudioService>() // We use GetService to find the AudioService that we installed earlier. In previous versions, this was equivelent to _client.Audio()
.Join(voiceChannel); // Join the Voice Channel, and return the IAudioClient.
The client will sustain a connection to this channel until it is kicked, disconnected from Discord, or told to Disconnect.
The IAudioClient¶
The IAudioClient is used to connect/disconnect to/from a Voice Channel, and to send audio to that Voice Channel.
-
IAudioClient.Disconnect();
Disconnects the IAudioClient from the Voice Server.
-
IAudioClient.Join(Channel);
Moves the IAudioClient to another channel on the Voice Server, or starts a connection if one has already been terminated.
Note
Because versions previous to 0.9 do not discretely differentiate between Text and Voice Channels, you may want to ensure that users cannot request the audio client to join a text channel, as this will throw an exception, leading to potentially unexpected behavior
-
IAudioClient.Wait();
Blocks the current thread until the sending audio buffer has cleared out.
-
IAudioClient.Clear();
Clears the sending audio buffer.
-
IAudioClient.Send(byte[] data, int offset, int count);
Adds a stream of data to the Audio Client’s internal buffer, to be sent to Discord. Follows the standard c# Stream.Send() format.
Broadcasting¶
There are multiple approaches to broadcasting audio. Discord.Net will convert your audio packets into Opus format, so the only work you need to do is converting your audio into a format that Discord will accept. The format Discord takes is 16-bit 48000Hz PCM.
Broadcasting with NAudio¶
NAudio is one of the easiest approaches to sending audio, although it is not multi-platform compatible. The following example will show you how to read an mp3 file, and send it to Discord. You can download NAudio from NuGet.
using NAudio;
using NAudio.Wave;
using NAudio.CoreAudioApi;
public void SendAudio(string filePath)
{
var channelCount = _client.GetService<AudioService>().Config.Channels; // Get the number of AudioChannels our AudioService has been configured to use.
var OutFormat = new WaveFormat(48000, 16, channelCount); // Create a new Output Format, using the spec that Discord will accept, and with the number of channels that our client supports.
using (var MP3Reader = new Mp3FileReader(filePath)) // Create a new Disposable MP3FileReader, to read audio from the filePath parameter
using (var resampler = new MediaFoundationResampler(MP3Reader, OutFormat)) // Create a Disposable Resampler, which will convert the read MP3 data to PCM, using our Output Format
{
resampler.ResamplerQuality = 60; // Set the quality of the resampler to 60, the highest quality
int blockSize = OutFormat.AverageBytesPerSecond / 50; // Establish the size of our AudioBuffer
byte[] buffer = new byte[blockSize];
int byteCount;
while((byteCount = resampler.Read(buffer, 0, blockSize)) > 0) // Read audio into our buffer, and keep a loop open while data is present
{
if (byteCount < blockSize)
{
// Incomplete Frame
for (int i = byteCount; i < blockSize; i++)
buffer[i] = 0;
}
_vClient.Send(buffer, 0, blockSize); // Send the buffer to Discord
}
}
}
Broadcasting with FFmpeg¶
FFmpeg allows for a more advanced approach to sending audio, although it is multiplatform safe. The following example will show you how to stream a file to Discord.
public void SendAudio(string pathOrUrl)
{
var process = Process.Start(new ProcessStartInfo { // FFmpeg requires us to spawn a process and hook into its stdout, so we will create a Process
FileName = "ffmpeg",
Arguments = $"-i {pathOrUrl} " + // Here we provide a list of arguments to feed into FFmpeg. -i means the location of the file/URL it will read from
"-f s16le -ar 48000 -ac 2 pipe:1", // Next, we tell it to output 16-bit 48000Hz PCM, over 2 channels, to stdout.
UseShellExecute = false,
RedirectStandardOutput = true // Capture the stdout of the process
});
Thread.Sleep(2000); // Sleep for a few seconds to FFmpeg can start processing data.
int blockSize = 3840; // The size of bytes to read per frame; 1920 for mono
byte[] buffer = new byte[blockSize];
int byteCount;
while (true) // Loop forever, so data will always be read
{
byteCount = process.StandardOutput.BaseStream // Access the underlying MemoryStream from the stdout of FFmpeg
.Read(buffer, 0, blockSize); // Read stdout into the buffer
if (byteCount == 0) // FFmpeg did not output anything
break; // Break out of the while(true) loop, since there was nothing to read.
_vClient.Send(buffer, 0, byteCount); // Send our data to Discord
}
_vClient.Wait(); // Wait for the Voice Client to finish sending data, as ffMPEG may have already finished buffering out a song, and it is unsafe to return now.
}
Note
The code-block above assumes that your client is configured to stream 2-channel audio. It also may prematurely end a song. FFmpeg can — especially when streaming from a URL — stop to buffer data from a source, and cause your output stream to read empty data. Because the snippet above does not safely track for failed attempts, or buffers, an empty buffer will cause playback to stop. This is also not ‘memory-friendly’.
Multi-Server Broadcasting¶
Warning
Multi-Server broadcasting is not supported by Discord, will cause performance issues for you, and is not encouraged. Proceed with caution.
To prepare for Multi-Server Broadcasting, you must first enable it in your config.
From here on, it is as easy as creating an IAudioClient for each server you want to join. See the sections on broadcasting to proceed.
Receiving¶
Receiving is not implemented in the latest version of Discord.Net
Events¶
Usage¶
Messages from the Discord server are exposed via events on the DiscordClient class and follow the standard EventHandler<EventArgs> C# pattern.
Warning
Note that all synchronous code in an event handler will run on the gateway socket’s thread and should be handled as quickly as possible. Using the async-await pattern to let the thread continue immediately is recommended and is demonstrated in the examples below.
Connection State¶
Connection Events will be raised when the Connection State of your client changes.
Warning
You should not use DiscordClient.Connected to run code when your client first connects to Discord. If you lose connection and automatically reconnect, this code will be ran again, which may lead to unexpected behavior.
Messages¶
- MessageReceived, MessageUpdated and MessageDeleted are raised when a new message arrives, an existing one has been updated (by the user, or by Discord itself), or deleted.
- MessageAcknowledged is only triggered in client mode, and occurs when a message is read on another device logged-in with your account.
Example of MessageReceived:
// (Preface: Echo Bots are discouraged, make sure your bot is not running in a public server if you use them)
// Hook into the MessageReceived event using a Lambda
_client.MessageReceived += async (s, e) => {
// Check to make sure that the bot is not the author
if (!e.Message.IsAuthor)
// Echo the message back to the channel
await e.Channel.SendMessage(e.Message);
};
Users¶
There are several user events:
- UserBanned: A user has been banned from a server.
- UserUnbanned: A user was unbanned.
- UserJoined: A user joins a server.
- UserLeft: A user left (or was kicked from) a server.
- UserIsTyping: A user in a channel starts typing.
- UserUpdated: A user object was updated (presence update, role/permission change, or a voice state update).
Note
UserUpdated Events include a User
object for Before and After the change.
When accessing the User, you should only use e.Before
if comparing changes, otherwise use e.After
Examples:
// Register a Hook into the UserBanned event using a Lambda
_client.UserBanned += async (s, e) => {
// Create a Channel object by searching for a channel named '#logs' on the server the ban occurred in.
var logChannel = e.Server.FindChannels("logs").FirstOrDefault();
// Send a message to the server's log channel, stating that a user was banned.
await logChannel.SendMessage($"User Banned: {e.User.Name}");
};
// Register a Hook into the UserUpdated event using a Lambda
_client.UserUpdated += async (s, e) => {
// Check that the user is in a Voice channel
if (e.After.VoiceChannel == null) return;
// See if they changed Voice channels
if (e.Before.VoiceChannel == e.After.VoiceChannel) return;
await logChannel.SendMessage($"User {e.After.Name} changed voice channels!");
};