Git Product home page Git Product logo

nefarius.dsharpplus.extensions.hosting's Introduction

DSharpPlus hosting extensions

An extension for DSharpPlus to make hosting a Discord bot in .NET Core Worker Services easier.

Build status GitHub Nuget Discord

About


This is not an official DSharpPlus extension, use it at your own risk!


This set of libraries abstracts away a lot of the plumbing code required to get DSharpPlus up and running in a .NET Core Worker Service (or simply a plain old Console Application) and provides Dependency-Injection-friendly integration.

It also offers a new feature/concept on top: event subscribers! Changes happening in the Discord universe are represented in DSharpPlus by a (rather large) number of events that can be subscribed to. The library offers an interface for every event of the Discord Client your "event subscriber" class can implement. These interface methods will be called when the corresponding event occurs. But there's a convenience extra: within the callback method you will have access to scoped services, like database contexts!

And if that wasn't enough, here's another one: intents will be automatically registered if you're using an interface/event that requires them! Yay automation!

To-Do

  • Documentation
  • Support the sharded client

Package overview

Nefarius.DSharpPlus.Extensions.Hosting

NuGet

The core library for DSharpPlus, required to set up a Discord client as a hosted service.

Nefarius.DSharpPlus.CommandsNext.Extensions.Hosting

NuGet

Optional. Adds support for DSharpPlus.CommandsNext extension.

Nefarius.DSharpPlus.Interactivity.Extensions.Hosting

NuGet

Optional. Adds support for DSharpPlus.Interactivity extension.

Nefarius.DSharpPlus.VoiceNext.Extensions.Hosting

NuGet

Optional. Adds support for DSharpPlus.VoiceNext extension.

Nefarius.DSharpPlus.SlashCommands.Extensions.Hosting

NuGet

Optional. Adds support for DSharpPlus.SlashCommands extension.

Documentation

If you're already familiar with .NET Core Workers or ASP.NET Core you'll have your bot up and running in seconds ๐Ÿ‘Œ

You can also take a look at the reference example of this repository.

Setup

Create a new .NET Core Worker project either via Visual Studio templates or using the command dotnet new worker in a fresh directory.

The current version of the library depends on the DSharpPlus nightly version. If you're using the stable nuget version, update to the nightly version.

Add the core hosting package (and optional extensions, if you need them) via NuGet package manager.

Implementation

Most of the heavy lifting is done in the ConfigureServices method, so we will focus on that. To get a bare basic Discord bot running, all you need to do is register the client service and the hosted background service:

//
// Adds DiscordClient singleton service you can use everywhere
// 
services.AddDiscord(options =>
{
 //
 // Minimum required configuration
 // 
 options.Token = "recommended to read bot token from configuration file";
});

//
// Automatically host service and connect to gateway on boot
// 
services.AddDiscordHostedService();

That's pretty much it! When you launch your worker with a valid bot token you should see your bot come online in an instant, congratulations! โœจ

OpenTracing (Optional)

You probably wonder what's the deal with the tracing dependency. I've taken liberty to implement OpenTracing within all event subscribers, so if your bot struggles with performance, you can easily analyse it with the addition of e.g. Jaeger Tracing. If you don't know what that means or don't care about tracing at all, just skip this section. To utilise tracing, you can use this snippet to get you up and running (although I highly recommend you check out the Jaeger docs beforehand anyway):

// Adds the Jaeger Tracer.
services.AddSingleton<ITracer>(serviceProvider =>
{
 var serviceName = "MyBot";
 var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();

 // This is necessary to pick the correct sender, otherwise a NoopSender is used!
 Configuration.SenderConfiguration.DefaultSenderResolver = new SenderResolver(loggerFactory)
  .RegisterSenderFactory<ThriftSenderFactory>();

 // This will log to a default localhost installation of Jaeger.
 var tracer = new Tracer.Builder(serviceName)
  .WithLoggerFactory(loggerFactory)
  .WithSampler(new ConstSampler(true))
  .Build();

 // Allows code that can't use DI to also access the tracer.
 GlobalTracer.Register(tracer);

 return tracer;
});

Make sure you call this before AddDiscord or the Mock Tracer will be used by default which will not collect or publish any metrics.

Handling Discord Events

Now to the actual convenience feature of this library! Creating one (or more) class(es) that handle events, like when a guild came online or a message got created. Let's wire one up that gets general guild and member change events:

// this does the same as calling
//   services.AddDiscordGuildAvailableEventSubscriber<BotModuleForGuildAndMemberEvents>();
[DiscordGuildAvailableEventSubscriber]
// this does the same as calling
//   services.AddDiscordGuildMemberAddedEventSubscriber<BotModuleForGuildAndMemberEvents>();
[DiscordGuildMemberAddedEventSubscriber]
internal class BotModuleForGuildAndMemberEvents :
    // you can implement one or many interfaces for event handlers in one class 
    // or split it however you like. Your choice!
    IDiscordGuildAvailableEventSubscriber,
    IDiscordGuildMemberAddedEventSubscriber
{
    private readonly ILogger<BotModuleForGuildAndMemberEvents> _logger;

    private readonly ITracer _tracer;

    /// <summary>
    ///     Optional constructor for Dependency Injection.
    ///     Parameters get populated automatically with your services.
    /// </summary>
    /// <param name="logger">The logger service instance.</param>
    /// <param name="tracer">The tracer service instance.</param>
    public BotModuleForGuildAndMemberEvents(
        ILogger<BotModuleForGuildAndMemberEvents> logger,
        ITracer tracer
    )
    {
        //
        // Do whatever you like with these. It's recommended to not do heavy tasks in 
        // constructors, just store your service references for later use!
        // 
        // You can inject scoped services like database contexts as well!
        // 
        _logger = logger;
        _tracer = tracer;
    }

    public Task DiscordOnGuildAvailable(DiscordClient sender, GuildCreateEventArgs args)
    {
        //
        // To see some action, output the guild name
        // 
        Console.WriteLine(args.Guild.Name);

        //
        // Usage of injected logger service
        // 
        _logger.LogInformation("Guild {Guild} came online", args.Guild);

        //
        // Return successful execution
        // 
        return Task.CompletedTask;
    }

    public Task DiscordOnGuildMemberAdded(DiscordClient sender, GuildMemberAddEventArgs args)
    {
        //
        // Fired when a new member has joined, exciting!
        // 
        _logger.LogInformation("New member {Member} joined!", args.Member);

        //
        // Return successful execution
        // 
        return Task.CompletedTask;
    }
}

Now let's dissect what is happening here. The class gets decorated by the attributes DiscordGuildAvailableEventSubscriber and DiscordGuildMemberAddedEventSubscriber (hint: you can use only one attribute for the event group you're interested in, you can use many more on the same class, doesn't matter, your choice) which causes it to get automatically registered as subscribers for these events.

An alternative approach to registration is manually calling the extension methods, like

services.AddDiscordGuildAvailableEventSubscriber<BotModuleForGuildAndMemberEvents>();
services.AddDiscordGuildMemberAddedEventSubscriber<BotModuleForGuildAndMemberEvents>();

from within ConfigureServices. Using the attributes instead ensures you don't forget to register your subscribers while coding vigorously!

Implementing the interfaces IDiscordGuildAvailableEventSubscriber and IDiscordGuildMemberEventsSubscriber ensures your subscriber class is actually callable by the Discord Client Service. You must complete every event callback you're not interested in with return Task.CompletedTask; as demonstrated or it will result in errors. In the example above we are only interested in DiscordOnGuildAvailable and print the guild name to the console. I'm sure you can think of more exciting tasks!

And last but not least; your subscriber classes are fully dependency injection aware! You can access services via classic constructor injection:

private readonly ILogger<BotModuleForGuildAndMemberEvents> _logger;

private readonly ITracer _tracer;

public BotModuleForGuildAndMemberEvents(
 ILogger<BotModuleForGuildAndMemberEvents> logger,
 ITracer tracer
)
{
 _logger = logger;
 _tracer = tracer;
}

You can even inject scoped services, the subscriber objects get invoked in their own scope by default. This allows for easy access for e.g. database contexts within each subscriber. Neat!

nefarius.dsharpplus.extensions.hosting's People

Contributors

nefarius avatar dependabot[bot] avatar

Forkers

azdub

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.