コネヒト開発者ブログ

コネヒト開発者ブログ

物覚えが悪いのでSlackに色々と覚えてもらうappを作った話

こんにちは!「M-1グランプリやるなら教えてほしかったんだけど?」と思っている金城(@o0h_)です。
ちょっと寂しいので、最近読みました「ここは今から倫理です。」のリンクを貼ります。

ここは今から倫理です。 1 (ヤングジャンプコミックスDIGITAL)

ここは今から倫理です。 1 (ヤングジャンプコミックスDIGITAL)

さて昨日は、英語が聴ける・話せるようになりたい・・・・と思っているところにガツンとくるトミーさんの記事でした。
引き続いてのAdvent Calendar Day16、わたくしからはSlackのbotの話でもいたします。

https://cdn.mamari.jp/authorized/5a34f7a1-bc6c-46ad-b6f5-0019ac120002.jpg

標準の「自動レスポンス」を再発明してリッチにする

皆さんも、自動レスポンス(という名前でいいのかな・・)は利用しているのではないかと思います。
設定画面で「キーワード」と「反応」を入れると反応してくれるアレですね。 f:id:o0h:20171216185417g:plain

コネヒトではSlackにいろいろを集約していく風潮がありますので、こういう機能は「よく使うけど忘れがちなコマンド」や「使いたくなったときにすぐ引き出したいURL」を放り込むなどして活用しています。

しかし、標準の機能に対して「もっとこうできたらいいのにな!」という課題が段々と浮かび上がってきておりました。
具体的には以下の3点です

  1. Slackでの権限が強い人しか設定を行えず、「私の作業効率を上げたい」と願う全ての人の要望に応えにくい
  2. 設定画面を開いて登録!という手間がある
  3. 「登録済みキーワード」の検索や分類もできればいいのに・・・

これらの問題に対して過剰に対応を行いましたので、今回はそれについて技術的観点からの話題を中心にお話したいと思います。

全体概要

実現したいこと

ざっと整理して、こんな要求があるものと設定しました。

  1. 登録・編集・削除を全てSlack上で完結させるようにする
    • Slackアカウントの権限を問わず利用が可能になる
    • 手軽にアップデートを行える状態にすることによって、「少し便利にする」のハードルが下がる
  2. 登録済みキーワードないし本文から、「検索」をできるようにする
    • トリガーとなるキーワードを覚えてないといけない!という問題が解消
  3. 登録したキーワードを「分類」できるようにして、関連語を一覧できるようにする
    • 2と同様に「探したいものがわからない」問題に対するアプローチ

実現すること

これらに対して、以下の方法をとって実現をしていくものとします。 普通に作ってもつまらない ので、 折角だからSlackの色んな機能を使っちゃれ というエゴを丸出しにしています!*1

機能面

  1. チャットによる簡易なコンテンツ登録
  2. チャットによる登録コンテンツの呼び出し
  3. 同じくチャットで「検索」「カテゴリ呼び出し」を行い、直感的かつ邪魔にならない形でのコンテンツの呼び出し
  4. 専用UIを用いた、フォーム形式で詳細なコンテンツ登録

技術面

  1. Events APIを用いて、RTMクライアントを伴わない形でチャットへのリアクションを行う(機能面の1,2,3)*2
  2. Interactive Messageを用いて、検索やカテゴリ指定でのコンテンツ群の呼び出しを行う(機能面の3)
  3. Slash Commands + Dialogを用いた、コンテンツの登録・編集(機能面の4)

完成イメージ

ここからゴタゴタと述べていくので、その前にまずは絵面をごらんください・・・!

コンテンツの登録

登録用フォーマットを決めて、それに従う形でトリガーワード・コンテンツを放り込みます。
社内ミームとして「いでよ!○○」が以前から存在し、標準の自動レスポンス機能の利用にも散見されていたので、これを利用します。

機能-1です。

新しくいでよ!{{キーワード}}
{{コンテンツ}}
{{複数行OK}}

f:id:o0h:20171216202229g:plain

コンテンツの表示

機能-2です。

いでよ!{{キーワード}}

f:id:o0h:20171216202622g:plain

コンテンツの検索

機能-3(1)です。

探して!{{検索語}}

f:id:o0h:20171216203420g:plain

カテゴリの表示

機能-3(2)です。

{{カテゴリ}}!ください

f:id:o0h:20171216203742g:plain

コンテンツの登録(詳細)

機能-4です。 チャットベースでの登録は手軽なのですが、長い文章を打ちにくかったりするので、「詳細にやりたいぜ」って時です。

/ideyo-add

f:id:o0h:20171216204739g:plain

コンテンツの編集

「詳細登録」では、既存コンテンツの編集もできます。
さっき登録した 英語で1-5 にtypoがあります。これを編集したい!といった場合に。

/ideyo-add {{キーワード}}

f:id:o0h:20171216205122g:plain 便利ですね。

実装ポイント

ここから先は技術です。

  1. Slack Appの準備
  2. アプリケーションロジックの実装
  3. Events API
  4. Interactive Message / Components
  5. Slash Commands
  6. Dialog

といった流れで紹介します

appの準備

Slack API: Applications | Slackから独自appを作成します。
自チームへのinstallは、OAuthの準備などをすっ飛ばして Settings > Install App から行えますので、手軽にコレを利用しましょう。*3

Ideyo-appの実装

「バックエンド」です。 各種Request URLにこいつを指定して、Slackへの返答を行わせます。

Events APIからポストされたメッセージの内容を読み取り、「データをDBに永続化する」「DBから読み出す」といった処理をもたせます。
この記事ではSlackの機能や利用法の紹介を目的としているので、詳細を割愛します。
このあと、いくつかの「Slackから呼び出されるエンドポイント」が必要になってくるので、予め何かしらの用意をしておいた方が良い気がします。 開発中は ngrock あたりを利用しました。また、一般公開等を考えていないのであれば、https環境でなくても利用できます。

Events API

ざっくり言うと、「特定のイベントが起きた時に、指定したURLをSlackが叩いてくれる」というものです。
「Slackが」というのが重要です。これにより、RTMクライアントのデーモン等を持つ必要がなく、Web Serverの実装のみで完結することができます。

api.slack.com

「どのイベントを購読するか」を選択しておくことで、該当のイベントが来たときに「予め指定されたendpointにpostでメッセージがくる」状態を実現できます。*4
今回は「チャットメッセージがチャンネルに投稿されたとき」に知らせてほしいので、 message.channels を有効にしましょう。

(作成したアプリの設定画面で) > Basic Information > Add features and functionality > Event Subscriptions > Enable Events > Add Workspace Event f:id:o0h:20171216211839p:plain f:id:o0h:20171216211545p:plain

※ 購読するイベントのタイプに応じてスコープを開いておく必要があるので、 Required Scope を参照してください api.slack.com

チャレンジ!Events API

先に述べたように「イベントが発生したら通知する先」をアプリごとに1つ指定する必要があります。
その設定を行うのに、 検証が必要です。

api.slack.com

  1. Event Subscriptionsの画面で、リクエスト URLに登録したいエンドポイントを入力する
  2. challenge というフィールドを持つリクエストが飛んできます
  3. これに対して、 200 OKで challenge の値を返すように応答してください
  4. うまくいくと、「検証済み ✓」と表示されるようになります(先のキャプチャを参照してください)

1度検証を通しても、このエンドポイントがエラーを頻発するようだと自動的にイベントの購読が停止されます。
実際、私のもとにこんなメールがきました*5。可愛いですね f:id:o0h:20171217233307p:plain

Interactive Message / Components

ざっくりいうと、

  1. chat.postMessage に「メニュー」を添付して
  2. 添付されたメニューに対するリアクションは、「予め指定された、Interactive Components用エンドポイント」に送られ
  3. そこから、slackに対して応答を行う

という流れになります。

f:id:o0h:20171216220604p:plain

interactive messageのフォーマットは、Slackが提供しているプレビューツールを利用して遊んでみるのがおすすめです。 message builder

interactive component用エンドポイントはappの設定メニューから登録してください。
ユーザーが(Slack上で)選択したメニューや押したボタンの内容とともに、こちらのエンドポイントにPOSTリクエストが飛んで来るようになります。 f:id:o0h:20171216221234p:plain

あとは、必要に応じて返答その他の処理をアプリケーションから行ってください。
なお、指定できるinteractive componentのrequest urlはappに対して1つとなるため、複数の機能を実装する場合には「どの内容への反応か」を callback_id で見分ける事になります。*6

Slash Commands

さっくりいうと、

  1. 予め登録しておいたコマンドをユーザーが入力すると
  2. 予め登録されていたURLに対して、リクエスト内容が送信され
  3. そこから、slackに対して応答を行う

という流れになります。

Slash Commands用エンドポイントはappの設定メニューから登録してください。
f:id:o0h:20171216221052p:plain f:id:o0h:20171217135326p:plain

今回は /ideyo-add コマンドを設けます。

Dialog

Dialog機能は比較的新しいものになります。
Dialogはこちらの文書によると interactive frameworkの一部 として位置づけられています。メニュー、ボタン、ダイアログ・・となるわけですね。
従来のコンポーネントと異なるのは、メニュー/ボタンは chat.postMessage に attachmentsとして渡すのに対して、ダイアログは独自のメソッド dialog.open を利用するという点です。

こちらのエントリーにて紹介されているので、一読されるのをおすすめします。夢が広がる! medium.com

ざっくりいうと、

  1. Slash Commands(or Interactive Components)からの送信内容に含まれる trigger_id を取り出して
  2. open.Dialog へ、trigger_id と供に展開内容を送信すると
  3. ユーザーに対してダイアログが展開される
  4. 選択内容が、「予め指定された、interactive component用エンドポイント」に送られ
  5. (以下、interactive messagesのときと同様)

という流れになります。

f:id:o0h:20171216222724p:plain

これで「ダイアログ」機能が展開されます。
2で投げる内容は、こんな感じですね

{
    "callback_id": "#{callack_id}",
    "title": "なにをいだしてくれるって!?!?",
    "submit_label": "いでよ!",
    "elements": [
        {
            "type": "text",
            "label": "キーワード",
            "name": "title"
        },
        {
            "type": "textarea",
            "label": "内容",
            "name": "body"
        },
        {
            "type": "text",
            "label": "カテゴリ(option)",
            "name": "category",
            "optional": true
        }
    ]
}

まとめ

チャットのみをUIとした機能はかなり前から社内で利用していたのですが、特にコンテンツ編集や検索のユーザービリティの低さがネックでした。
今回、Slackの機能をリッチに利用することで、これらの問題を解消できたと思っています。 特にナレッジ領域の課題を解消するサービスのおいては、「いかにユーザー自身の「引き出し」にたよらず機能を引き出せるか」というのが、UXに関してかなり大きな価値をもつと思います。その点で言えば、「かんたんに検索できる」「グループに属する一覧を引き出せる」というのは、個人的にもお気に入り機能です。

今後は、細かいチューニングや社内のコミュニケーションをより円滑にする方面にも活用できたら面白いかな?と妄想しています。

次回予告

さて、明日はついに!!我らがCTO @tatsushimの登場です・・・!
全く内容を聞いていないので、私も楽しみ・・・w

qiita.com


【追記】

素で間違えていたので、1箇所修正しました・・(ご指摘いただきありがとうございます) f:id:o0h:20171217134843g:plain


*1:開発合宿でベースを開発したため、力技も用いて「自分でやりたいことをやる!」というふうに振っています。ので、実装方式としては筋が良いわけではなさそう。遊び心・・・!

*2:本来的には、リアルタイム性を要するアプリケーションはRTMを利用すべきです。実際、botkit等で作られた「自動返信システム」に比べて今回の実装には応答遅延等が見られやすいです

*3:あわせて読みたい: https://api.slack.com/slack-apps-preview

*4:rebild.fmで話題になったReacji Channelerも、Events APIのreaction_addedを利用したら実現できそうですね

*5:通常のWebアプリケーションの感覚で作っていたため、当初はトリガーキーワード(いでよ!)を含まない場合に404や500を返す、といった実装になっていました。今は204で空コンテンツを返しています

*6:Interactive Messageについては、過去記事でも少し触れていますので、御覧ください。コネヒト開発合宿(秋)に行ってきました! - コネヒト開発者ブログ4. Slack Interactive Message Buttons Oasobi の部分