Discord.Net 備忘録

# 前置き
　作りながら得た知識の備忘録なので、一部不正確な情報があるかもしれません。本記事の内容をうのみにするのではなく、API開発者のドキュメントを確認するようお願いします。なお本記事は1から作ってみよう！といった内容ではなく、APIの解説寄りになっています。
　感想、指摘等コメントお待ちしております。

# Discord Botとは
　Discordを使う上でロールの変更など、手動でやるのは面倒な操作がありますよね。そんな時はプログラムに働いてもらいましょう！
　[こちら](https://discord.com/developers/applications)のDeveloper PortalからBotを簡単に作成できます。詳細の手順が気になる場合は、他の方の記事を確認してみてください。

# Discord.Netについて
　Discord Botを作るためのAPIは様々なものが公開されています。Discord.pyやDiscord.jsがとくに有名なようです。
　今回はMicrosoftの.NETで動く[Discord.Net](https://docs.stillu.cc/index.html)を採用することにします。理由は私が.NET好きだというのと、Linqなど扱いやすいライブラリが多い(個人差あり)からです。
　ちなみに同じく.NET用APIでDSharpPlusというものも公開されています。

## 0. サンプル
https://github.com/Ulma-S/DiscordBotSample/blob/master/DiscordBotSample/Program.cs

## 1. 初期設定
　Visual Studio 2019で.NET Frameworkもしくは.NET Coreのコンソールアプリケーション用プロジェクトを作成します。本記事執筆時点でフレームワークのバージョンは.NET Core 3.1を採用しています。
　Discord.NETのライブラリはNuGetから落とせます。NuGetパッケージマネージャーを開き、`Discord.Net`と`Microsoft.Extensions.DependencyInjection`をプロジェクトにインポートします。

## 2. 初期化
　Botを動かすために必要なものは大きく分けて3種類あります。順を追って説明していきます。

### 1. Bot本体
　`Discord.WebSocket.DiscordSocketClient`クラスはユーザーとしての基本機能を提供します。基本的にこのクラスのインスタンスを通してBot自身への操作をしていくことになります。

### 2. コマンド
　`Discord.Commands.CommandService`クラスは後述するコマンド群を管理するために使用されます。コマンドの登録はDependency Injection(依存性注入)を利用して自動で行われます。

### 3. サービスプロバイダー
　Dependency Injectionによってコマンドを登録、取得するために使用されます。

　これらをコードにすると以下のようになります。

```C#
private DiscordSocketClient client;
private CommandService commands;
private IServiceProvider serviceProvider;

public async Task MainAsync(){
    serviceProvider = new ServiceCollection().BuildServiceProvider();
    commands = new CommandService();
    await commands.AddModulesAsync(Assembly.GetEntryAssembly(), serviceProvider);
    client = new DiscordSocketClient();
}
```

## 3. 処理の登録
　クライアントにはメッセージ受信時をはじめ、様々なタイミングで呼ばれるイベントが実装されています。ここに処理を登録することでBotに固有の処理をさせることができます。
 
```c#
public async MainAsync(){
    client.Log += Log;
    client.MessageReceived += CommandReceived;
}

private async Task Log(LogMessage message){
    Console.WriteLine(message.ToString());
}

private async Task CommandReceived(SocketMessage message){
    var msg = msg as SocketUserMessage;
    if(message == null) return;
    if(message.Author.IsBot) return;

    int argPos = 0;
    var command = new CommandContext(client, msg);
    await commands.ExecuteAsync(command, argPos, serviceProvider);
}
```

## 4. コマンドの作成
　`Discord.Commands.ModuleBase`を継承したクラスにメソッドを追加することで、独自のコマンドを実装することができます。

```c#
public class CommandModule : ModuleBase{
    /// <summary>
    /// Helloと返す
    /// </summary>
    [Commands("hello")]    //コマンドとして使用する文字列をアトリビュートで設定
    public async Task SayHelloAsync(){
        await ReplyAsync("Hello!");
    }
}
```

　引数付きのコマンドを作成する場合は以下のようにします。

```c#
public class CommandModule : ModuleBase{
    /// <summary>
    /// 入力したロールが付与されたユーザー名を列挙する。
    /// </summary>
    [Commands("show")]
    public async Task EchoAsync([Summary("対象のロール名")] string rollName){
        var role = Context.Guild.Roles.FirstOrDefault(rl => rl.ToString() == rollName);
        var users = Context.Guild.GetUsersAsync();

        foreach(var _user in users){
            var user = _user as SocketGuildUser;
            if(user.Roles.Contains(role)){
                await ReplyAsync(user.Username);
            }
        }
    }
}
```

# 詳細設定
　一部API機能の使用にはDiscord Gateway Intentを有効にする必要があります。例えば上記のコマンド例で使用した、ユーザーリストを取得するメソッドを有効化するには、これらのインテントを有効化が必要です。
　Discord Developer Portalの個別アプリケーションのページから設定を操作できます。

# その他
　コマンドの登録のために使用しているDependency Injection(依存性注入)は、クラス間の依存関係を疎結合にし、単体テストをしやすくするためにしばしば使われます。詳細はDependency Injectionについてまとめている記事をご確認ください。