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なら、
<?php
header('content-type: application/json; charset=utf-8');
switch(rand(0, 2)){
case 0: echo '{"text": "daikichi"}'; break;
case 1: echo '{"text": "kichi"}'; break;
case 2: echo '{"text": "kyou"}'; break;
}
?>
- Outgoing webhookを作る。channelは#randomにしておく。trigger wordに”fortune”。そして、上記のphpが実行されるURLを設定。
動作例¶
できた。
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"]
に入れるべき文字列を実行時に指定しよう。
のように、リプライで返信される。
参考: デバッグ用コード(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で作成
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<IActionResult> 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 + "\"}");
}
}
}
など書いて、適切にテストして発行。
デプロイ終了後、https://omikujimXXXXXXXXXXXx.azurewebsites.net/api/Function1
のようなアドレスを得るので、それを、outgoingのendpointとして設定。
見栄えとしてはなにも変わらないが、Insightなどでさくっと利用率可視化できるのは便利だ。
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<IActionResult> 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<SlackAppMessage>(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をメモして上記に書く。
動作確認¶
できたー。意外と面倒。
f. Slack RTM APIを用いたインタラクティブなおみくじ(やらない)¶
Azure Function + RTMは賢くないので実装しない。
まとめ¶
今回はいくつかのおみくじbotの実装を行った。しかしながら、我々のSlackにはすでにおみくじbotが存在するため、今回の実装の活躍の場はない。