C#コードを評価して結果を返すSlackBotをAzureFunctionsで実装する

  • 5
    いいね
  • 0
    コメント

C#コードを評価して結果を返すbotを実装してみようと思います。
結果はこのような感じです。
botsample.png

Slack の Integration 設定

Slack で bot を作るには Integration を利用すると良さそうです。

Outgoing Webhooks

何か発言に対して反応したい場合はOutgoingWebhooksを利用できそうです。
"@C#:"と先頭についている場合に反応するようにTriggerWordを設定します。
反応する発言があった場合にデータを送信するURLは後述するAzureFunctionsで実装したAPIのURLをセットします。
outgoing.png

Incoming Webhooks

何か発言をする場合はIncomingWebhooksを利用できそうです。
設定画面に発言データ(payload)を送りつけるURLが書いてあります。
発言するチャンネルはpayloadで上書きできるので
疎通確認用のチャンネル等にしておくと良いのでは無いでしょうか。
incoming.png

AzureFunctions で bot の実装

OutgoingWebhook で送られてきたデータを処理して IncomingWebhook に返す処理を AzureFunctions に実装します。
AzureFunctions に関してはぎたぱそ先生のブログが参考になります。

Integrate タブの WebHook type を Not a WebHook にする

OutgoingWebhook を受け取る為に設定します。
じんぐる先生のブログに記述されていました。

Nuget のインストール

Nugetパッケージを利用できるようにコンソールからインストールします。
この記事読めばできますね。

C#コードの評価

Roslyn の CSharpScripting を使えば簡単です。

今回実装したコード

長々と書きなぐっていて美しさには欠けますがこのようになりました。

project.json
{
 "frameworks": {
   "net46":{
     "dependencies": {
       "Microsoft.CodeAnalysis.Scripting": "1.2.1",
       "Newtonsoft.Json" : "8.0.3"
     }
   }
 }
}
run.csx
#r "System.Collections"
#r "System.Configuration"
#r "System.Runtime"
#r "System.Reflection"
#r "System.Threading.Tasks"
#r "System.Web"

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting;

private static readonly string[] DefaultImports =
{
    "System",
    "System.Collections.Generic",
    "System.IO",
    "System.Linq",
    "System.Text",
};

private static readonly Assembly[] DefaultReferences =
{
    typeof(Enumerable).Assembly,
    typeof(List<string>).Assembly,
};

private const string INCOMING_URL_KEY = "IncomingWebhookUrl";

private const string TRIGGER_WORD = "@C#:";

public static async Task<object> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Verbose("Webhook was triggered!");

    var content = await req.Content.ReadAsStringAsync();
    log.Verbose(content);

    var data = content
        .Split('&')
        .Select(x => x.Split('='))
        .ToDictionary(x => x[0], x => HttpUtility.HtmlDecode(HttpUtility.UrlDecode(x[1])));

    if (data["user_name"] == "slackbot")
    {
        return req.CreateResponse(HttpStatusCode.OK, new {
            body = "Cannot Support Messages From SlackBot.",
        });
    }

    var text = data["text"] as string ?? "";
    log.Verbose(text);

    var code = text.Replace(TRIGGER_WORD, "");

    object result = null;
    try
    {
        result = await CSharpScript.EvaluateAsync(code ?? "コードが空ニャ", 
            ScriptOptions.Default
                .WithImports(DefaultImports)
                .WithReferences(DefaultReferences));
    }
    catch (Exception ex)
    {
        result = ex.Message;
    }

    var resultText = result?.ToString() ?? "";
    if (resultText.StartsWith(TRIGGER_WORD))
    {
        resultText = resultText.Replace(TRIGGER_WORD, "");
    }

    log.Verbose(resultText);

    var payload = new
    {
        channel = "#" + data["channel_name"],
        username = "C#と和解せよ",
        text = string.IsNullOrWhiteSpace(resultText) ? "空だニャ" : resultText,
        icon_emoji = ":cat:",
    };
    var jsonString = JsonConvert.SerializeObject(payload);
    using (var client = new HttpClient())
    {
    var incomingUrl = ConfigurationManager.AppSettings[INCOMING_URL_KEY];
        var res = await client.PostAsync(incomingUrl, new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("payload", jsonString)
        }));
        return req.CreateResponse(res.StatusCode, new {
            body = resultText,
        });
    }
}

IncomingWebhookのUrlはAppSettingsに格納しています。
こちらを見ればできます。

手軽に bot が作れて良いですね。