【Rails+Slack API】Slack APIのmember_joined_channelイベントを使ったバッチの実装

# 環境
* Ruby
* Rails
* （DB）MySQL
* （Webサーバ）Nginx+Unicorn
* Docker

# 概要
今回は､Slack APIの[member_joined_channel](https://api.slack.com/events/member_joined_channel)イベントを使って､ワークスペースのチャンネルにユーザーが参加したというイベントを受け取って､[users.list](https://api.slack.com/methods/users.list)メソッドを使って､参加したユーザーのメールアドレスを取得して､メールアドレスをもとにユーザー情報をDBに登録するバッチを実装した｡

member_joined_channelイベントは､Subscribe to Bot EventsとSubscribe to Workspace Eventsの2種類あり､Subscribe to Bot EventsではBotが参加しているチャンネルのイベントを取得し､Subscribe to Workspace Eventsではワークスペース全体のチャンネルのイベントを取得するという違いがある｡

Subscribe to Bot Eventsでも良かったが､指定のチャンネル以外にBotを参加させた場合に予期せぬ動きをしそうだったので､Subscribe to Workspace Eventsを使って､ワークスペース全体のチャンネルのイベントを取得し､チャンネルの切り分けはバッチのロジックに入れることにした｡

# 準備
## Slackアプリを作成して､tokenを取得
### Slackアプリを作成
まず､Slackアプリを作成する｡
https://api.slack.com/ で「Start Building」をクリック｡
以下の画面で､アプリ名とワークスペースを選択して作成｡
<img width="417.75" alt="slack-api.png (31.0 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/d55df678-4a6e-4ade-a6de-38d54ef446ba.png">

### ScopesでSlackアプリの権限を設定
tokenを取得するには､アプリをワークスペースに追加する必要があるが､何か設定後でないとできないみたいなので､先にScopesを設定する｡

Basic Information Pageで､
「Permissons」をクリック｡
<img width="588.75" alt="slack-api.png (79.3 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/93c7720e-3c1a-4562-a6aa-b666eb2bfadd.png">



今回､使う[users.list](https://api.slack.com/methods/users.list)メソッドは､最低限「bot」と「users:read」の権限があれば動作する｡
<img width="435.75" alt="slack-api.png (30.9 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/97e6f896-e064-4252-83e9-2841f6b30ee4.png">

[member_joined_channel](https://api.slack.com/events/member_joined_channel)イベントは､「channels:read」と「groups:read」の権限が必要｡
<img width="438.75" alt="slack-api.png (14.7 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/564aad84-4223-4b53-a080-88d2788851e6.png">



ちなみにメソッドがどんな感じで動くか試したい場合は､`https://slack.com/api/users.identity?token=xxx`とURLを開くとどんな結果が返ってくるかブラウザですぐに確認できたりする｡
使う場合は､user.identitがメソッド名､tokenがtokenになっているので適宜変更する｡

<img width="317.25" alt="slack-api.png (4.3 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/7541466a-72cd-4878-a0dd-60e94dc64d97.png">

### Botを作成
そして､Scopesを保存したら､Botも作成して保存しておく｡
<img width="437.25" alt="slack-api.png (40.4 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/1d6ce392-bb6a-417c-a713-72f8c32df5a6.png">

### tokenを取得
そしたら､Install App to Workspaceができるようになっている
<img width="450.75" alt="slack-api.png (19.5 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/061c1636-ac7a-4c17-bb28-8c4d7a7f415b.png">

ワークスペースにアプリをインストールすると､tokenが取得できるようになる｡
<img width="435.75" alt="slack-api.png (35.0 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/bb6ab77a-66a0-44a7-8201-e34a3a94b4d7.png">

ここでは､Bot User OAuth Access Tokenを使う｡

## slack-ruby-clientのgemを使って､tokenの設定
Railsでは､[slack-ruby-client](https://github.com/slack-ruby/slack-ruby-client)という便利なgemがあるのでそれを使った｡

まず､トークンの設定｡

```ruby:config\initializers\slack.rb
Slack.configure do |config|
  config.token = ENV['SLACK_BOT_USER_TOKEN']
  raise 'Missing ENV[SLACK_BOT_USER_TOKEN]!' unless config.token
end
```

ここの`ENV['SLACK_BOT_USER_TOKEN']`はdocker-compose.ymlの環境変数を読み込んでいる｡セキュリティ的に問題ないなら直書きでも問題ない｡

```docker-compose.yml
.
.
environment:
      SLACK_BOT_USER_TOKEN:
.
.
```
docker-compose.ymlはこんな感じ｡
環境変数のキーだけ書くと､マシンの中の同じ名前の環境変数を取ってきてくれる｡
tokenをファイルに直書きしたくないためこのやり方を採用｡

使い方としては､`@client = Slack::Web::Client.new`でインスタンスを生成して､`@client.users_info(user: slack_id)`などとする｡
インスタンスを生成した際に､configで設定したtokenをもったインスタンスが生成されている｡

## ngrokを使って､localhostを外部公開
後ほど出てくるが､Event SubscriptionsでSlackでイベント発火したら､それを受け取るRequest URLを設定する必要がある｡

その際､localhostのままだとSlackからのリクエストを受け取れないので､https接続できるようにする｡

ngrokがかなり便利なので､それを使う｡
[ngrokを使用してローカル環境を外部に公開する \- Qiita](https://qiita.com/kitaro729/items/44214f9f81d3ebda58bd)

## Event SubscriptionsのRequest URLをverifyする
Slack APIのイベントを使う際に､verifyしたRequest URLが必要になる｡

verifyするには､Slackからパラメータ付きのリクエストが飛んでくるのでそれをそのまま返す必要がある｡

下記とまったく同じやり方でいけた｡
[オウム返し slack bot をぱっとつくる \- Qiita](https://qiita.com/takose/items/bc2b63426b9921604599)

## verifyしたら､Subscribe to Workspace Eventsを登録する
verifyしたら､Subscribe to Workspace Eventsのmember_joined_channelを登録する｡

これで､チャンネルにメンバーが参加したら､RequestURLに毎回リクエストが飛ぶようになった｡
<img width="411.75" alt="slack-api.png (22.0 kB)" src="https://img.esa.io/uploads/production/attachments/12444/2019/10/07/57955/900a8102-6b12-4ccb-bb10-20fc0a8d6e4f.png">


# 実装
```config\routes.rb
post '/member_joined_channel' => 'member_joined_channel#callback', as: :member_joined_channel
```

```app\controllers\member_joined_channel_controller.rb
class MemberJoinedChannelController < ApplicationController
  # callbackアクションのCSRFトークン認証を無効
  protect_from_forgery :except => [:callback]
  # callbackアクションのログイン認証を無効
  before_action :authenticate_user!, except: [:callback]

  def callback
    @body = JSON.parse(request.body.read)
    case @body['type']
      when 'url_verification'
        render json: @body
      when 'event_callback'
        # ここにイベントを受け取った際のバッチ処理
    end
  end
end
```

# はまったポイント
## マシンの環境変数がDocker側になかなか反映されずにはまった
途中､環境変数を変更する必要があったのでマシンの環境変数を変更したが､なかなか変更されなかった｡

原因1：ユーザーとシステムのどちらも環境変数を登録していたが､ユーザー側しか変更していなかった｡ ⇒ ユーザーとシステムどちらも環境変数を変更した
原因2：マシンの環境変数を変更したが､dokcerの環境に反映されていない｡ ⇒ マシン側でgit bashでenvと打つと環境変数が反映されているか確認したところ､反映されていなかったので再起動したところ反映された｡

ちなみにdocker-compose.ymlのenvironmentはマシン側が反映されればbuildせずとも､docker-compose upで反映された｡


## ログイン認証にdeviseを使っていて､verifyできなかった｡
 Event SubscriptionsのRequest URLをverifyするのにとてもはまった｡
debuggerも動かず､原因調査がなかなか進まなかったが単純な原因だった｡

ログイン認証にdeviseを使っており､application_controller.rbで`before_action :authenticate_user!`でログインしていないユーザーを弾く設定にしていたため､Slackからのリクエスト時にログインしていないユーザーとして弾かれていた｡

Controllerに`before_action :authenticate_user!, except: [:callback]`することで解決できた｡

## 謎のmissing scope
必要な権限を設定しているにも関わらず､権限がないと怒られる事象があった｡
古いtokenを使っていることが原因だった｡
どこかのタイミングで一番初めにOAuth tokenを使用しており､あとからBotを作成してBot tokenを使用できるようになったので､ずっとOAuth tokenを使っていて権限がないと怒られていた｡
全ての権限を追加しても改善されなかったため､気づくことが出来た｡

# まとめ
実装よりもSlack側の設定が大変だった｡｡
次からは使い回すことができそう｡
肝心のバッチの実装部分は､大したことはしていないので省略しました｡
イベントを受け取って実行するという仕組みができたので､あとは各々で好きなように実装してください｡