大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【Slack】テキトーなテキストメッセージへの返信方法

はじめに

こんにちは!大ちゃんの駆け出し技術ブログです。

今回もまた!Slack APIです笑

もうどんだけ書くのよという感じですが、今はこれに奮闘しすぎてSlackのネタがたくさん出てきてしまいます笑

今回はテキトーなメッセージを送信した時にヘルプメッセージを送信する方法を紹介します。基本的にはヘルプメッセージはslashコマンドの一覧を返します。

https://i.gyazo.com/08691868040f2d705457bbe5b8eddb8e.png

読者対象

  • Slack APIをある程度知っている方
  • RubyでSlack アプリを作成している方
  • Slack Appを既に開発している方

メッセージタブでのやりとりのフロー

この記事を深く理解するための事前知識として、「メッセージタブ」の存在に触れておきます。メッセージタブとは、Slack Appのボットとメッセージのやりとりができるスペースのことです。下の図で言うところの「Home」、「Messages」、「About」のMessagesにあたる箇所です。

https://i.gyazo.com/42cf36c25eafade6784ab7b42eda3ddf.png

Slackアプリはこのメッセージタブでイベントを発生させることでユーザとのやりとりを可能にします。メッセージタブでのイベントの種類は下記のような感じです。

  • ユーザがメッセージタブを開いた時
  • ユーザがメッセージタブにメッセージを送信した時

今回は後者のメッセージタブにメッセージを送信した時にアプリ側にリクエストが送られ、それに対してヘルプメッセージを送信するフローになります。


  • ユーザが「hogehoge」とメッセージタブに打ち込む

  • Slackが指定のURLにリクエストを送信

  • アプリ側でヘルプメッセージを送信する処理

  • メッセージタブにヘルプメッセージが送られる

方法がわからなかったのはSlackが指定のURLにリクエストを送信する方法です。「hogehoge」とメッセージタブに送信を行った場合、Slack側ではどのようにそれをアプリ側に送信するのでしょうか。

Event Subscriptions

SlackではEvent SubscriptionsというSlack上でのユーザの動作に対してアプリにリクエストを送ることができる機能があります。

Using the Slack Events API

どこで設定するのかというと、Slack App開発画面のFeatures > Event Subscriptionsで設定できます。

https://i.gyazo.com/4d73b7818aa9e2632cb0334ccca0c231.png

ここで「ユーザがメッセージタブにメッセージを送った時にアプリにリクエストを送る」と言う設定を行っていきます。ユーザがメッセージタブにメッセージを送るイベントをアプリ側に送るためには、message.im eventをEvent Subscriptionsで設定します。

Event reference: message.im event

少し大雑把に説明しましたが、ある程度の理解で構いません。今はEvent Subscriptionsの設定でmessage.im eventを使用することでメッセージタブのイベントをリクエストとして送信することができるんだなと思っておいてください。

実装手順

それでは実際に設定していきます。

まず、先ほどの設定画面で右上にあるボタンをONにしましょう。

https://i.gyazo.com/87848a945f705d9ee17b188cb3087514.gif

すると、いろいろな設定項目が出てきます。

まずRequest URLから設定していきます。これはEvent Subscriptionsで有効にしたイベントが発生した時に送信するURLのことです。Railsアプリ側でそのURLを受け取るためのルーティングを設定しましょう。下記の例ですとアプリURL/slack/eventsのリクエストを受け取るようにします。

# routes.rb
namespace :slack do
  post 'events', to: 'events#respond'
end

よってRequest URLで設定するURLもこれに合わせて設定します。今回はngrokを使用してHTTPS側のURLを設定します。

すると、下記のようにエラーメッセージ が表示されたはずです。

https://i.gyazo.com/8093306bb9984aa251b45cbe008442a3.png

**URLがchallengeパラメータの値で応答しませんでした。**

Event SubscriptionsのURLを受け取るためにはアプリ側で一度challengeパラメータの値に応答しておく必要があります。これはurl_verificationというイベントが発生していて、Slack側で発行されるchallengeパラメータをRequest URLのレスポンスでJSONでレンダーすることで、アプリのリクエストURLが本人のものということを認証します。

Event reference: url_verification event

このRequest URLはどのようなHTTPSのURLも設定できてしまいます。例えばYouTubeのURLも設定できます。

https://www.youtube.com/

しかし、当然ですがYouTubeは自分が開発したものではないですよね。何の認証もなしにRequest URLの値に好きなURLを入れられたら、たくさんのリクエストが知らないところから飛んできてしまいます。

そのため、Request URLに最初にchallengeパラーメーターを送り、同じ値をレンダーすることでそのアプリが本人のものということを認証するのです。よってchallengeパラメータ をレンダーするようにします。

def respond
  render json: params[:challenge], status: 200
end

これで同じリクエストURLに認証を行うと、下記画面のように認証が通ったというメッセージが表示されます。

https://i.gyazo.com/e29f4217eb28e27ab65f0cd57b3a1c79.png

ちなみにですが、このchallengeパラメーターへの処理は認証が通ればそれ以上は必要ないので削除します。もしこれを残したまま本番環境にデプロイすると、他のslackアプリからのRequest URLにも同じURLが指定された時にchallengeパラメーターを返却してしまいます。あくまで自分のアプリであることを証明するためのフローです。

それでは次にmessage.im eventを追加します。Subscribe to bot eventsの「Add Bot User Event」ボタンからmessage.imを検索しましょう。見つけたら追加して右下にある「Save Changes」ボタンをクリックします。

https://i.gyazo.com/1a59b2eb5d4b1d40571faf19f44a4e2b.gif

補足すると、im:historyというscopeがアプリ側に自動で追加されているかと思います。これはこのイベントを追加するためにはこのim:historyのscopeが必要になるということです。

これでGUI画面での設定は終わりです。実際にパラメータを受け取れるようになったはずです。binding.pryで実際にパラメータが飛んでいるか確認します。

def respond
  binding.pry

アプリ側で一度「hogehoge」というメッセージを送ってみましょう。すると処理が止まるはずですのでそこでparamsを確認します。するとすごく長いパラメーターを取得できたと思います。

params
=> <ActionController::Parameters {"token"=>"xxxxxxx", "team_id"=>"xxxxxx", "api_app_id"=>"xxxxx", "event"=><ActionController::Parameters {"client_msg_id"=>"xxxxx", "type"=>"message", "text"=>"hogehoge", "user"=>"xxxxx",

あとはユーザに対してヘルプメッセージを送信する処理のみです。

自分が作成した処理を貼っておきます。

def respond
  if params[:event][:type] == 'message' && params[:event][:text].present?
    send_help_msg
  end
end

def send_help_msg
  team = Team.find_by(workspace_id: params[:team_id])
  user = User.find_by(uid: params[:event][:user])
  return if team.nil? || user.nil? || team.workspace_id != user.team.workspace_id
  access_token = set_access_token(user.authentication.access_token)
  encoded_text = get_encoded_help_text
  encoded_msg = get_encoded_help_block_msg
  access_token.post("api/chat.postMessage?channel=#{user.uid}&blocks=#{encoded_msg}&text=#{encoded_text}&pretty=1").parsed
end

def get_encoded_help_text
  text = "ヘルプメッセージを送信しました"
  encoded_text = ERB::Util.url_encode(text)
  return encoded_text
end

def get_encoded_help_block_msg
  msg = "[ { 'type': 'divider' }, { 'type': 'section', 'text': { 'type': 'mrkdwn', 'text': '本アプリでは以下のコマンドがSlack内で利用できるよ:hamster:' } }, { 'type': 'section', 'fields': [ { 'type': 'mrkdwn', 'text': ':information_source: `/prof_help` \nDMでヘルプメッセージを送るよ' }, { 'type': 'mrkdwn', 'text': ':postbox: `/prof_random_block` \n DMでランダムにブロックを1つ送るよ' } ] }, { 'type': 'section', 'fields': [ { 'type': 'mrkdwn', 'text': ':ok_hand: `/prof_activate_share` \n毎日18時の投稿を有効するよ' }, { 'type': 'mrkdwn', 'text': ':raised_back_of_hand: `/prof_inactivate_share` \n 毎日18時の投稿を止めるよ' } ] }, { 'type': 'divider' } ]"
  encoded_msg = ERB::Util.url_encode(msg)
  return encoded_msg
end

まずパラメータの種類がmessageであること、textが含まれていることでユーザからのDMだということがわかるので条件分岐を張り付けます。

if params[:event][:type] == 'message' && params[:event][:text].present?

DMであることがわかれば、そのユーザからアクセストークンと取り出します。その手前でuserとteamを取り出している処理ですが、これはアプリにユーザが登録しているかどうかを検証するためです。ユーザでなければ基本的にはヘルプメッセージを送信する必要がないので。

team = Team.find_by(workspace_id: params[:team_id])
user = User.find_by(uid: params[:event][:user])
return if team.nil? || user.nil? || team.workspace_id != user.team.workspace_id
access_token = set_access_token(user.authentication.access_token)

あとはchat.postMessageメソッドでエンコードしたメッセージを送信しています。ここれへんの説明はSlack APIをよく知っていればわかるはずです。

Image from Gyazo