1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Discord】DSharpPlus使ってみた

Last updated at Posted at 2025-03-09

要約

Discordボットのための良さげなC#ライブラリを触ってみたよ。
スラッシュコマンドの実装がシンプルでやりやすいね。

本文

初めまして、ゲームエンジニアのwrsmAです。Discordとは仲良くさせてもらってます。
突然ですが、私は「Techxenia」というDiscordサーバーの運営をやっています。
以前、運営のために簡単なBotを作った時はDiscrod.Netというライブラリを利用しました。
今回はDSharpPlusというライブラリがあると聞きつけたので使ってみた、という話です。

DSharpPlusの紹介

DSharpPlusはDiscordSharpからフォークされたプロジェクトです。
DiscordSharpが開発停止したため、C#でボット開発する際にDiscrod.Netと並ぶ選択肢になりそうです。

実装

ここから、以下の流れで実装してみます。

  1. 導入
  2. ボットを立ち上げる
  3. メッセージを受け取る
  4. スラッシュコマンドを追加する
  5. ボタンを追加する

1. 導入

まずボットを作成します。作成経験のある方にはお馴染みの手順です。
DSharpPlusにも解説記事【Creating a Bot Account】があります。

テスト用のボットを追加しました。

image.png

次にプログラム環境を作成します。
DSharpPlus公式ドキュメント【Your First Bot】のページ通り進めましょう。
VisualStudioでなくても大丈夫です。

今回は5.0.0のパッケージを使用します。

2. ボットを立ち上げる

ここからボットを立ち上げていきます。 接続にはDiscordClientクラスを使用します。
DiscordClientBuilderにTokenやIntentのパラメータを渡してclientを取得します。

// v5.0.0以降
const string Token = "なんからToken";

var builder = DiscordClientBuilder.CreateDefault(Token, DiscordIntents.AllUnprivileged);
var client = builder.Build();

なお、最新安定板である4.5.0にはDiscordClientBuilderは存在しません。
DiscordClientのコンストラクタを使用しましょう。

// v4.5.0
var client = new DiscordClient(new DiscordConfiguration
{
    Token = "なんからToken",
    Intents = DiscordIntents.AllUnprivileged
});

Tokenの取り扱いには十分注意してください。
誰にでも見える形で公開しないでください。

最後にclientを接続し、コンソールが閉じないようにします。

await client.ConnectAsync();
await Task.Delay(-1);

実行すると以下の通りログが出力され、ボットがオンラインになりました。

image.png

3. メッセージを受け取る

次にメンバーのメッセージを受け取って返事を返してみます。
Spicing Up Your Botの内容通りです。

builderに渡すIntentを追加して、メッセージをハンドルする処理を追加します。

// DiscordIntents.MessageContentsを追加
var builder = DiscordClientBuilder.CreateDefault(Token, DiscordIntents.AllUnprivileged | DiscordIntents.MessageContents);
builder.ConfigureEventHandlers
(
    b => b.HandleMessageCreated(async (s, e) =>
    {
        if (e.Message.Content.ToLower().StartsWith("ping"))
        {
            await e.Message.RespondAsync("pong!");
        }
    })
);

実行、お返事が返ってきました。

image.png

4. スラッシュコマンドを追加する

スラッシュコマンドも追加しましょう。
従来のプレフィックスコマンド(?や!などから始まるテキストベースのコマンド)も利用可能ですが、スラッシュコマンドを使いましょう(圧)。

スラッシュコマンドの使用には、ボットにUse Slash Commands権限が付与されている必要があります。開発者ポータルで設定を確認してください。

スラッシュコマンドを実装するためにパッケージを追加します。

DSharpPlus.SlashCommandsというパッケージもありますが、5.1.0で非推奨となるようです。

DSharpPlus.SlashCommands
image.png

基本的な実装は公式のCommands Introductionを参照します。
コマンドの設定にはbuilderのUseCommands関数を使用します。

builder.UseCommands((IServiceProvider serviceProvider, CommandsExtension extension) =>
{
    // ここでコマンドの登録などを行う
});

ドキュメントで使用されているTextCommandProcessorクラスは、プレフィックスコマンドを登録するためのクラスになります。
スラッシュコマンドを登録するために、SlashCommandProcessorクラスを使用します。

builder.UseCommands((IServiceProvider serviceProvider, CommandsExtension extension) =>
{
    // ここでコマンドの登録などを行う

+   var slashCommandProcessor = new SlashCommandProcessor(new SlashCommandConfiguration());
+   extension.AddProcessor(slashCommandProcessor);
});

次にコマンド本体を実装します。ドキュメントに倣ってpingコマンドにしましょう。
コマンドにはCommandアトリビュートを付けます。
context.Client.Pingは無くなっているのでとりあえず「Pong!」とだけ返します。

public class PingCommand
{
    [Command("ping")]
    public async Task Run(CommandContext context)
    {
        await context.RespondAsync("Pong!");
    }
}

最後にPingCommandを登録します。

builder.UseCommands((IServiceProvider serviceProvider, CommandsExtension extension) =>
{
    // ここでコマンドの登録などを行う
+   extension.AddCommands([typeof(PingCommand)]);    

    var slashCommandProcessor = new SlashCommandProcessor(new SlashCommandConfiguration());
    extension.AddProcessor(slashCommandProcessor);
});

実行、コマンドが呼び出せました。

image.png

オマケでコマンドにパラメータを持たせてみましょう。
パラメータを追加するには、コマンド関数に対応する型の引数を追加するだけです。

[Command("ping")]
public async Task Execute(CommandContext context, string name)
{
    await context.RespondAsync($"Pong for {name}!");
}

実行、nameパラメータが追加されコマンドで利用できました。

image.png

image.png

【余談】コマンドのタイムアウトについて

スラッシュコマンドなどは実行後3秒以内になんらかの返答を返す必要があります。
そのため、長いタスクを実行する場合はDeferFollowupを使用します。

[Command("ping")]
public async Task Run(CommandContext context)
{
    await ctx.DeferResponseAsync();
    await Task.Delay(1000 * 10);
    await ctx.FollowupAsync("10秒待ってPong!");
}

DeferRespondeAsyncで待機中
image.png

FollowupAsyncで完了
image.png

5. ボタンを追加する

少し踏み込んで、ボタンも追加してみます。
ボタンとはメッセージに埋め込めるコンポーネントの1種で、ユーザーが押すことでさらにイベントを実行できるものです。
投票機能などに利用されていたりします。

では、新しいコマンドを作って実装してみましょう。
公式ドキュメントはButtonsです。

ボタンの作成にはDiscordButtonComponentクラスを使用します。
DiscordButtonStyleの種類などは公式ドキュメントに記載されています。
その後、DiscordMessageBuilderクラスを使ってメッセージにまとめ、Respondします。

public class ButtomCommand
{
    [Command("button")]
    public async Task Execute(CommandContext context)
    {
        var button = new DiscordButtonComponent(DiscordButtonStyle.Primary, "customButtonId", "ぼたん");
        var builder = new DiscordMessageBuilder();
        builder.AddComponents(button);
        await context.RespondAsync(builder);
    }
}

ButtonCommandextensionに追加するのを忘れないように気をつけてください。

extension.AddCommands([typeof(PingCommand), typeof(ButtomCommand)]);

実行、ボタンが作られました。

image.png

最後に、ボタンが押された時にメッセージを送信してみます。ボタンはIdで識別することができます。
InteractionResponseTypeDiscordInteractionResponseTypeに名前が変わっているので注意しましょう。

builder.ConfigureEventHandlers(b => b.HandleComponentInteractionCreated(async (s, e) =>
{
    await e.Interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);
    if (e.Id == "customButtonId")
    {
        await e.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().WithContent("ボタン押したね!"));
    }
}));

実行、ボタンが押されたことが分かりました。

image.png

今回のコード全文

コード
using DSharpPlus;
using DSharpPlus.Commands;
using DSharpPlus.Commands.Processors.SlashCommands;
using DSharpPlus.Commands.Trees;

public class Program
{
    static async Task Main(string[] args)
    {
        // 適宜、Environmentなどに逃がしてください。
        const string Token = "なんらかToken";

        var builder = DiscordClientBuilder.CreateDefault(Token, DiscordIntents.AllUnprivileged | DiscordIntents.MessageContents);

        // 3. メッセージを受け取る
        builder.ConfigureEventHandlers
        (
            b => b.HandleMessageCreated(async (s, e) =>
            {
                if (e.Message.Content.ToLower().StartsWith("ping"))
                {
                    await e.Message.RespondAsync("pong!");
                }
            })
        );

        // 4. スラッシュコマンドを追加する
        builder.UseCommands((IServiceProvider _, CommandsExtension extension) =>
        {
            extension.AddCommands([typeof(PingCommand)]);

            var slashCommandProcessor = new SlashCommandProcessor(new SlashCommandConfiguration());
            extension.AddProcessor(slashCommandProcessor);
        });
        
        // 5. ボタンを追加する
        builder.ConfigureEventHandlers(b => b.HandleComponentInteractionCreated(async (s, e) =>
        {
            await e.Interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);
            if (e.Id == "customButtonId")
            {
                await e.Interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().WithContent("ボタン押したね!"));
            }
        }));

        var client = builder.Build();

        await client.ConnectAsync();
        await Task.Delay(-1);
    }
}

public class PingCommand
{
    [Command("ping")]
    public async Task Execute(CommandContext context, string name)
    {
        await context.RespondAsync($"Pong for {name}!");
    }
}

public class ButtomCommand
{
    [Command("button")]
    public async Task Execute(CommandContext context)
    {
        var button = new DiscordButtonComponent(DiscordButtonStyle.Primary, "customButtonId", "ぼたん");
        var builder = new DiscordMessageBuilder();
        builder.AddComponents(button);
        await context.RespondAsync(builder);
    }
}

あとがき

今回はDiscord.Netと比較する意味も込めて基本的な機能を実装してみました。
特にスラッシュコマンドの実装がシンプルで分かりやすいと思いました。
現時点では5.0.0はnightlyのため、安定板となる頃に改めて触ってみたいと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?