Building a Discord voice bot with Discord.NET

Getting started with Discord.NET to create a bot that uses voice on Discord.

Posted by on

C# Discord Open Source

I've been playing around with the Discord api recently, I wanted to see what could actually be done with it. I set a goal to make a discord bot that slightly annoys people by joining voice channels and randomly say Zapp Brannigan quotes.

For those who don't know Discord is an:

All-in-one voice and text chat for gamers that's free, secure, and works on both your desktop and phone. - https://discordapp.com/

I got started with Discord.NET and was able to get the bot connected very quickly. Discord.NET is an unofficial .NET API wrapper for discord which seem to be the most popular and widely used library for it so I went with it. Be sure to check out the documentation for details. For this to work with voice connections there are a few native dlls that you need to download and put with your application. These are simple enough to get from the voice documentation (or check out my full source link at the end).

First thing you need to do is get an instance of DiscordSocketClient for my project I am doing a .NET Core 2.2 console app with the built in dependency injection.

_discord = _services.GetRequiredService<DiscordSocketClient>();
await _discord.LoginAsync(TokenType.Bot, _botToken);
await _discord.StartAsync();

That is all the code you need to have your bot connect to discord!

I wanted my bot to detect when a user joined a voice channel and connect to that channel (if it wasn't already). To do that I used the UserVoiceStateUpdated event on the DiscordSocketClient which can tell us when a users voice state updates (join/leave/move voice channels).

// Add this before calling StartAsync
_discord.UserVoiceStateUpdated += OnVoiceStateUpdated;

The code to handle the event then looks like this:

private async Task OnVoiceStateUpdated(SocketUser user, SocketVoiceState state1, SocketVoiceState state2)
{
    // Check if this was a non-bot user joining a voice channel
    if (user.IsBot)
        return;

    if (state1.VoiceChannel == null && state2.VoiceChannel != null)
    {
        ConnectToVoice(state2.VoiceChannel).Start();
    }
}

private async Task ConnectToVoice(SocketVoiceChannel voiceChannel)
{
    if (voiceChannel == null)
        return;

    Console.WriteLine($"Connecting to channel {voiceChannel.Id}");
    var connection = await voiceChannel.ConnectAsync();
    Console.WriteLine($"Connected to channel {voiceChannel.Id}");
}
Important to note here is that we don't await ConnectToVoice, this is because if we await that call from inside the UserVoiceStateUpdated event it will cause a deadlock and never return from connecting to the voice channel.

Now we are connecting to a voice channel when we detect a user joins it so lets look at having the bot actually send audio through. To do this the documentation for the library has an example using ffmpeg to read the sound files and pipes that into a stream that then gets sent to Discord.

var psi = new ProcessStartInfo
{
    FileName = "ffmpeg",
    Arguments = $@"-i ""{sound.Filename}"" -ac 2 -f s16le -ar 48000 pipe:1",
    RedirectStandardOutput = true,
    UseShellExecute = false
};
var ffmpeg = Process.Start(psi);

var output = ffmpeg.StandardOutput.BaseStream;
var discord = connection.CreatePCMStream(AudioApplication.Voice);
await output.CopyToAsync(discord);
await discord.FlushAsync();

Here we start a new process passing in start info that opens up ffmpeg.exe (also included in the project) passing in a relative path to out sound file and a few other command line args to play the sound and pipe the output which we then copy into a stream into discord.

This is the basics of creating a voice capable discord bot with Discord.NET, for a more advanced implementation take a look at the full source for this project at https://github.com/dkarzon/DiscordZapBot.