要約
Discordボットのための良さげなC#ライブラリを触ってみたよ。
スラッシュコマンドの実装がシンプルでやりやすいね。
本文
初めまして、ゲームエンジニアのwrsmAです。Discordとは仲良くさせてもらってます。
突然ですが、私は「Techxenia」というDiscordサーバーの運営をやっています。
以前、運営のために簡単なBotを作った時はDiscrod.Net
というライブラリを利用しました。
今回はDSharpPlus
というライブラリがあると聞きつけたので使ってみた、という話です。
DSharpPlusの紹介
DSharpPlusはDiscordSharpからフォークされたプロジェクトです。
DiscordSharpが開発停止したため、C#でボット開発する際にDiscrod.Net
と並ぶ選択肢になりそうです。
実装
ここから、以下の流れで実装してみます。
- 導入
- ボットを立ち上げる
- メッセージを受け取る
- スラッシュコマンドを追加する
- ボタンを追加する
1. 導入
まずボットを作成します。作成経験のある方にはお馴染みの手順です。
DSharpPlusにも解説記事【Creating a Bot Account】があります。
テスト用のボットを追加しました。
次にプログラム環境を作成します。
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);
実行すると以下の通りログが出力され、ボットがオンラインになりました。
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!");
}
})
);
実行、お返事が返ってきました。
4. スラッシュコマンドを追加する
スラッシュコマンドも追加しましょう。
従来のプレフィックスコマンド(?や!などから始まるテキストベースのコマンド)も利用可能ですが、スラッシュコマンドを使いましょう(圧)。
スラッシュコマンドの使用には、ボットにUse Slash Commands
権限が付与されている必要があります。開発者ポータルで設定を確認してください。
スラッシュコマンドを実装するためにパッケージを追加します。
DSharpPlus.SlashCommands
というパッケージもありますが、5.1.0
で非推奨となるようです。
基本的な実装は公式の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);
});
実行、コマンドが呼び出せました。
オマケでコマンドにパラメータを持たせてみましょう。
パラメータを追加するには、コマンド関数に対応する型の引数を追加するだけです。
[Command("ping")]
public async Task Execute(CommandContext context, string name)
{
await context.RespondAsync($"Pong for {name}!");
}
実行、name
パラメータが追加されコマンドで利用できました。
【余談】コマンドのタイムアウトについて
スラッシュコマンドなどは実行後3秒以内になんらかの返答を返す必要があります。
そのため、長いタスクを実行する場合はDefer
とFollowup
を使用します。
[Command("ping")]
public async Task Run(CommandContext context)
{
await ctx.DeferResponseAsync();
await Task.Delay(1000 * 10);
await ctx.FollowupAsync("10秒待ってPong!");
}
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);
}
}
ButtonCommand
をextension
に追加するのを忘れないように気をつけてください。
extension.AddCommands([typeof(PingCommand), typeof(ButtomCommand)]);
実行、ボタンが作られました。
最後に、ボタンが押された時にメッセージを送信してみます。ボタンはId
で識別することができます。
InteractionResponseType
はDiscordInteractionResponseType
に名前が変わっているので注意しましょう。
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("ボタン押したね!"));
}
}));
実行、ボタンが押されたことが分かりました。
今回のコード全文
コード
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のため、安定板となる頃に改めて触ってみたいと思います。