かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

C# で Discord のボットを書いて Azure にデプロイしてみよう

ゲーム好きの人たちにはおなじみの Slack みたいなチャットサービスのボットを C# でも作れます。Python が一応公式っぽい??

C# で Discord のボットを作る場合には Discord.NET というライブラリを使うのが一般的みたいです。

github.com

簡単に検索しただけでもチラホラ日本語記事がヒットします。

qiita.com

atriasoft.work

kagasu.hatenablog.com

Discord 側でのアプリの作成やボットのアカウントの作り方とか、特定のチャンネルにボットを追加する方法は上記のブログなどにしっかり書いてあるので、ここでは Azure にデプロイしてみたいと思います。

今回は .NET Core にある汎用ホストで作っていきます。

docs.microsoft.com

.NET Core のコンソールアプリを作成して、Microsoft.Extensions.HostingDiscord.NetMicrosoft.Extensions.Logging.Console という NuGet パッケージを追加します。

あとは淡々と書いていきます。

using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace DiscordBot.HostApp
{
    class Program
    {
        static void Main(string[] args) => ConfigureHostBuider(args).Build().Run();

        public static IHostBuilder ConfigureHostBuider(string[] args) => Host.CreateDefaultBuilder(args)
            .ConfigureLogging(b =>
            {
                b.AddConsole();
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<DiscordBotService>();
            });
    }

    class DiscordBotService : BackgroundService
    {
        private DiscordSocketClient _client;
        private readonly IConfiguration _configuration;

        private string Token => _configuration.GetValue<string>("Token");
        public DiscordBotService(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _client = new DiscordSocketClient(new DiscordSocketConfig { LogLevel = Discord.LogSeverity.Info });
            _client.Log += x =>
            {
                Console.WriteLine($"{x.Message}, {x.Exception}");
                return Task.CompletedTask;
            };
            _client.MessageReceived += MessageReceived;
            await _client.LoginAsync(Discord.TokenType.Bot, Token);
            await _client.StartAsync();
        }

        public override async Task StopAsync(CancellationToken cancellationToken)
        {
            await _client.StopAsync();
        }

        private async Task MessageReceived(SocketMessage arg)
        {
            if (!(arg is SocketUserMessage m) || m.Author.IsBot)
            {
                return;
            }

            await m.Channel.SendMessageAsync($"{m.Content} と言いましたね。");
        }
    };
}

プロジェクトには appsettings.json と appsettings.Development.json を追加します。

f:id:okazuki:20191219192150p:plain

appsettings.json と appsettings.Development.json は ASP.NET Core の Web アプリケーションプロジェクトから持ってきました。Development の方には以下のように Token を足しています。ここに Discord でとってきたトークンを入れます。

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Grpc": "Information",
      "Microsoft": "Information"
    }
  },
  "Token": "<ここにトークンを入れる>"
}

これはプログラム中で以下のように Token という名前で設定を呼んでるから Token という名前で追加してるだけで、プログラムとあってれば任意の名前で大丈夫です。git を使う場合は、この appsettings.Development.json を .gitignore に追加しておきましょう。

次にプロジェクトのプロパティで環境変数 DOTNET_ENVIRONMENTDevelopment を追加します。これでローカルでのデバッグ実行時に appsettings.Development.json を見に行ってくれるようになります。

f:id:okazuki:20191219192513p:plain

実行するとなんか受付に対してレディになってますね。

f:id:okazuki:20191219192443p:plain

Discord を見てみるとボットがオンラインになってるので話しかけてみました。ちゃんと動いてます。

f:id:okazuki:20191219192716p:plain

Azure にデプロイ

では、今回はこの汎用ホストを WebJob にデプロイしてみようと思います。とりあえず Web App を作ります。Discord のボットは常駐してないと死んじゃうのでスタンダードプラン(S1)以上で作ります。

f:id:okazuki:20191219193139p:plain

そして、作成された App Service (Web App) の構成ページで新しいアプリケーション設定として Token を追加して Discord のボットのトークンを入れます。ローカルのデバッグ実行は appsettings.Development.json から読み込んで、本番はここから環境変数として読み込む感じです。ここら辺をいい感じに IConfiguration に隠してくれるの便利ですよね。

f:id:okazuki:20191219193830p:plain

本番で使うものは何かしらの CI/CD 系のサービスからデプロイするとして、今回のお試しはローカルの Visual Studio のプロジェクトの右クリックメニューから公開で WebJobs に公開していきます。

f:id:okazuki:20191219193352p:plain

先ほど作ったのが選択肢に出てくるので選びます。

f:id:okazuki:20191219193431p:plain

プロファイルが出来たら編集を押して Web ジョブの種類を Continuous にします。これで常時実行し続けます。

f:id:okazuki:20191219193539p:plain

では発行しましょう。発行が終わると、そのうち Discord のほうでボットがオンラインになります。

f:id:okazuki:20191219195308p:plain

ログぅ…

ダメ元で AddConsole メソッドを呼んでますが、WebJobs に置いただけでは Application Insights にはログはいてくれませんね。 なので Microsoft.Extensions.Logging.ApplicationInsights を NuGet で追加して。ConfigureHostBuilder メソッドを以下のように書き換えます。

public static IHostBuilder ConfigureHostBuider(string[] args) => Host.CreateDefaultBuilder(args)
    .ConfigureLogging((ctx, b) =>
    {
        b.ClearProviders();
        if (ctx.HostingEnvironment.IsProduction())
        {
            b.AddApplicationInsights(ctx.Configuration.GetValue<string>("APPINSIGHTS_INSTRUMENTATIONKEY"));
        }
        else
        {
            b.AddConsole();
        }
    })
    .ConfigureServices((hostContext, services) =>
    {
        services.AddHostedService<DiscordBotService>();
    });

こんな感じで変更して Azure に差異デプロイして、こんな風に話しかけると

f:id:okazuki:20191219200344p:plain

こんな感じに Application Insights にもログが出るようになりました。

f:id:okazuki:20191219200438p:plain

ソースコード

とりあえずそのまま GitHub に上げてます。

github.com