20190502日記: Slackおみくじ(3つの実装を行う) === ## モチベーション (裏の理由) ゴールデンウィークだし、少し時間ができたのでC#でAzureでなにかしたかった。 (表の理由) 円滑なコミュニケーションのために、ユーザからおみくじbotが求められることはIRC時代からきっと広く知られていた。昨今、Slackが広く浸透したため、車輪の再開発としておみくじbotを作る。 以下、通常よく例示される - a. Slack outgoing APIを用いた最もシンプルなおみくじ - [やらない]b. Slack event APIを用いたインタラクティブなおみくじ - c. Slack RT APIを用いたインタラクティブなおみくじ という例を示すと同時に、 Azure Functionで - d. Slack outgoing APIを用いた最もシンプルなおみくじ - e. Slack event APIを用いたインタラクティブなおみくじ - [やらない]f. Slack RT APIを用いたインタラクティブなおみくじ あたりを目指す。 Azure FunctionはAWS Lambdaのようなサーバレスの機能サービスであり、HTTPをトリガーにcallすることができる。 ## 共通仕様 - fourtuneという文字列を含むポストがされたら、等確率で大吉、吉、凶を返す ## a. シンプルおみくじ(PHP) ### どうするか? - Slackのoutgoing webhookは、Slack上で特定のキーワードが含まれるポストがあった際に、外部のwebhookをたたく機能である - 「特定のキーワードが含まれる」であるため、fortuneが含まれるかを判定する必要はない。 - hookされたURLへのpostへのresponseとして返信のJSONを投げることで返信のメッセージを送ることができる。これに必要な最低限のメッセージは以下の通り。 ``` { "text": "テキスト" } ``` ### 実装 - Internet上のアドレスからアクセス可能な場所に適当なプログラムを置く。例えば、phpなら、 ``` ``` - Outgoing webhookを作る。channelは#randomにしておく。trigger wordに"fortune"。そして、上記のphpが実行されるURLを設定。 ### 動作例 ![](Img/20190502_fortune_1.PNG) できた。 ## b: event APIを使う(やめた) - event APIを使うにはhttpsじゃないといけない。手持ちのサーバ系が諸般の理由でSSL Enableしていないのでやめた。 ## c: RTM APIを使う(Python) https://github.com/slackapi/python-slackclient#basic-usage-of-the-rtm-client を参考に進める。環境は適当に作る。 ``` 環境作成例(>=3.6らしい): apt-get install python3-venv python3 -m venv rtm . ./rtm/bin/activate pip3 install slackclient ``` - AppとしてBotsを作る。 ここで、"API Token"をめもる。bots APPはWebSocketを用い、 クライアントから発呼される。 この際に、Slack側が正しいユーザであるという認証のために (というか、所属するワークスペースなどもここで確認される) このトークンが必要になる。 ### コード ``` import slack import random @slack.RTMClient.run_on(event='message') def fortune(**payload): data = payload['data'] web_client = payload['web_client'] rtm_client = payload['rtm_client'] # textはreplyなどには含まれないので、subtypeがなし=親メッセージかを確認する if 'subtype' not in data and 'fortune' in data['text']: channel_id = data['channel'] thread_ts = data['ts'] user = data['user'] num = random.randint(0, 2) if num == 0: kichi = "daikichi" elif num == 1: kichi = "kichi" elif num == 2: kichi = "kyou" # thread_tsを設定することでスレッドでのリプライになる web_client.chat_postMessage( channel=channel_id, text=kichi, thread_ts=thread_ts ) slack_token = os.environ["SLACK_API_TOKEN"] rtm_client = slack.RTMClient(token=slack_token) rtm_client.start() ``` ### 動作例 ``` SLACK_API_TOKEN="xoxXXXXXXXXXXXXX" python3 bot.py ``` のように、プログラム上の`os.environ["SLACK_API_TOKEN"]`に入れるべき文字列を実行時に指定しよう。 ![](Img/20190502_fortune_2.PNG) のように、リプライで返信される。 ### 参考: デバッグ用コード(slackから送られてくるapiのpayload dump) ``` import os import slack from pprint import pprint @slack.RTMClient.run_on(event="message") def dump(**payload): pprint(payload) @slack.RTMClient.run_on(event="reaction_added") def dump2(**payload): pprint(payload) slack_token = os.environ["SLACK_API_TOKEN"] rtm_client = slack.RTMClient(token=slack_token) rtm_client.start() ``` ``` SLACK_API_TOKEN="xoxXXXXXXXXXXXXX" python3 aa.py ``` のように起動する。そして、slackで(eventに登録した動作を)すると、 ``` {'data': {'event_ts': '1556780058.004200', 'item': {'channel': 'C1HBXXXXXA', 'ts': '1556780053.004100', 'type': 'message'}, 'item_user': 'U1HBXXXXX2', 'reaction': 'hugging_face', 'ts': '1556780058.004200', 'user': 'U1HBXXXX2'}, ``` のように、イベントが見える。 尚、LISTENするイベントの一覧は、 https://api.slack.com/events を参照すること。 ## d. Azure Functionでシンプルなおみくじ とにかく簡単に作る。Visual Studio側での開発と発行を行う。 ### コード プロジェクトの作成 -> Azure Function -> HTTP Triggerで作成 ![](Img/20190502_fortune_3.PNG) ``` using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace omikuji_most_simple { public static class Function1 { [FunctionName("Function1")] public static async Task Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log) { String kichi = new String(""); Random rand = new Random(); switch (rand.Next(0, 3)) { case 0: kichi = "daikichi"; break; case 1: kichi = "kichi"; break; case 2: kichi = "kyou"; break; } log.LogInformation("Come: "); return (ActionResult)new OkObjectResult("{\"text\": \"" + kichi + "\"}"); } } } ``` など書いて、適切にテストして発行。 ![](Img/20190502_fortune_4.PNG) デプロイ終了後、`https://omikujimXXXXXXXXXXXx.azurewebsites.net/api/Function1`のようなアドレスを得るので、それを、outgoingのendpointとして設定。 ![](Img/20190502_fortune_6.PNG) 見栄えとしてはなにも変わらないが、Insightなどでさくっと利用率可視化できるのは便利だ。 ![](Img/20190502_fortune_5.PNG) ## e: Azure FunctionでSlack Event APIを使う https://api.slack.com/events-api を参考にする。 https://api.slack.com/apps からappを作成する。 ### コード - Visual Studioでfortune_event_apiなどというAzure Function APIを作成する。Anonymousスコープ。 - ソリューションエクスプローラかプロジェクトを右クリック > 追加 > Azure関数 > 名前をauth.csなどにして作成http trigger(anonymous) ``` using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System.Net; using System.Text; using System.Collections.Specialized; using System.Web; namespace fortune_event_api { public static class handler { // botTokenのID public static string botToken = "xoxb-XXXXXXXXXXXXXXXXXXXXXXXXX"; // chat.postMessageのUrl public static string urlChat = "https://slack.com/api/chat.postMessage"; [FunctionName("handler")] public static async Task Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log) { // ペイロードのオブジェクト化(含json deser) string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); SlackAppMessage data = JsonConvert.DeserializeObject(requestBody); // slackのbot登録時のverifyの場合、 if (data?.type == "url_verification") { // オウム返しする(challengeが含まれていればOKなので) return (ActionResult)new OkObjectResult(requestBody); } // 通常のメッセージの場合は else if (data?.type == "event_callback") { // まず、文頭がfortuneかを判定 if(!data.eventdict.text.StartsWith("fortune")) { // メッセージを受け取ったと返信 return (ActionResult)new OkObjectResult(""); } // do うらない String kichi = new String(""); Random rand = new Random(); switch (rand.Next(0, 3)) { case 0: kichi = "daikichi"; break; case 1: kichi = "kichi"; break; case 2: kichi = "kyou"; break; } // HTTP Handlerの作成 Uri uri = new Uri(urlChat); Encoding enc = new UTF8Encoding(); using (WebClient client = new WebClient()) { // HTTP送信するペイロードの作成 NameValueCollection sendmsg = new NameValueCollection(); sendmsg["channel"] = data.eventdict.channel; sendmsg["text"] = kichi; log.LogInformation("Channel:" + data.eventdict.channel); log.LogInformation("Text:" + data.eventdict.text); // ヘッダにBearerを追加 client.Headers["Authorization"] = "Bearer " + botToken; var res = client.UploadValues(uri, "POST", sendmsg); log.LogInformation("send to slack: "); string resText = enc.GetString(res); log.LogInformation("recv from slack "+ resText); } // メッセージを受け取ったと返信 return (ActionResult)new OkObjectResult(""); } else { // メッセージを受け取ったと返信 return (ActionResult)new BadRequestObjectResult("This function will be accept only url_verification and message."); } } public class SlackAppMessage { public string token { get; set; } public string challenge { get; set; } public string type { get; set; } // eventは予約名なので、JSON名との読み替えを行う [JsonProperty("event")] public SlackAppMessageEvent eventdict { get; set; } } public class SlackAppMessageEvent { public string channel { get; set; } public string text { get; set; } } public class SlackAppReplyMessage { public string token { get; set; } public string as_user { get; set; } public string channel { get; set; } public string text { get; set; } } } } ``` - Features > Bot User で `add bot User`する。 - Features > OAuth & Permissions で redirect URLsに指定されたアドレスを指定する - Features > OAuth & Permissions > Scopesで以下を追加してsave - channels:history(以下のeventを追加するのに必要) - chat:write:bot(botからのPOSTを受け取るのに必要) - Features > Event Subscriptions を有効にして - Request URLに作ったfunctionのURLを指定(これを有効にするとurl_verificationが送られる) - Subscribe to Workspace Events > addで`message.channels`を追加。 - Features > OAuth & PermissionsのBot User OAuth Access Tokenをメモして上記に書く。 ### 動作確認 ![](Img/20190502_fortune_7.PNG) できたー。意外と面倒。 ## f. Slack RTM APIを用いたインタラクティブなおみくじ(やらない) Azure Function + RTMは賢くないので実装しない。 ## まとめ 今回はいくつかのおみくじbotの実装を行った。しかしながら、我々のSlackにはすでにおみくじbotが存在するため、今回の実装の活躍の場はない。