AWS Lambdaで作るSlack bot (Incoming Webhook)

# 対象読者
- Slackを既に利用している (Workspaceに所属している)
- Slack botの作成経験がない
- Python3が書ける
- AWSを使用したことがない

# サービス説明
## Slack Incoming Webhookについて
[Incomming Webhook](https://get.slack.help/hc/ja/articles/115005265063-Slack-%E3%81%A7%E3%81%AEIncoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8)は、アプリケーション→Slackの単方向の通知が行える機能です。
Incoming WebhookからURLが提供され、アプリケーションがそのURLにPOSTリクエストを送るとSlackの指定したチャンネルに通知が飛びます。

Slackで何かしらの通知を行いたいとき、単純な内容 + 定期的な予定であれば[リマインダー](https://get.slack.help/hc/ja/articles/208423427-%E3%83%AA%E3%83%9E%E3%82%A4%E3%83%B3%E3%83%80%E3%83%BC%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)の使用が考えられますが、何かしらのイベントを受けて通知を出したり、少し複雑な内容を通知する場合はIncomming Webhookが便利です。

<img width="640" alt="無題.png" src="https://qiita-image-store.s3.amazonaws.com/0/58991/6dc093de-4d4f-7c36-f035-f0fcc6490f92.png">

## Amazon Web Service(AWS) Lambdaについて
[AWS Lambda](https://aws.amazon.com/jp/lambda/)とは、なにかしらのイベントによってプログラムを駆動させることができるサービスです。
このようなサービスはFunction as a Service(FaaS)と呼ばれたりします。
FaaSは様々なメリットがありますが、今回Slack botを作る上でのメリットは以下2つかと思われます。

- サーバの管理を意識しなくて良い
- 必要なときしかリソースが確保されない (コストが安く抑えらえる可能性)

また、AWS Lambdaは[無料枠](https://aws.amazon.com/jp/free/)があり、ひと月あたり一定回数or一定動作時間以内なら無料で使用することができます。
> AWS 無料利用枠 (無期限提供分):
> これらの AWS 無料利用枠は、12 か月間の AWS 無料利用枠の期間が終了しても自動的に期限切れになることはありません。AWS のすべてのお客様にご利用いただけます。
> AWS Lambda
> 1 か月当たり 1,000,000 件の無料リクエスト**
> 1 か月当たり最大 320 万秒のコンピューティング時間**
> ※ 2018-11-24現在

# 作業順序
以下の順序で作業を行っていきます。

1. SlackのWorkspaceにIncoming Webhookを登録
1. SlackにPOSTするプログラム作成 (Python)
1. AWSの設定

## SlackのWorkspaceにIncoming Webhookを登録
WorkspaceにIncoming Webhookを登録する方法は2種類あります。

- [Slackアプリを作成して登録](https://get.slack.help/hc/ja/articles/115005265063-Slack-%E3%81%A7%E3%81%AEIncoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8)
- **`Incoming Webhook` アプリから登録**

今回はIncoming Webhookしか使用する予定がないので、2つ目の「`Incoming Webhook` アプリから登録」で行います。
`Incoming Webhook`アプリを使用する場合の登録手順は以下になります。

1. `Incoming Webhook`アプリを登録 (設定を追加)
2. チャンネルを選択
3. Webhook URLを(手元に)記録
4. (名前、アイコンなどをカスタマイズ)

### 1.Incoming Webhookアプリを登録
まず[SlackAppディレクトリ](https://my.slack.com/apps)から`Incoming Webhook`アプリを検索します。
いかにも検索できそうなテキストボックスに`Incoming Webhook`と入力するとサジェストしてくれます。
サジェストされた`Incoming Webhook`アプリをクリックして、`設定を追加`ボタンを押しましょう。
<img width="497" alt="03.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/58991/b0b10a22-bd83-6660-3d6d-36e9de9ac96b.png">

### 2. チャンネルを選択
`設定を追加`を押すとチャンネル選択画面に移行します。
Botを働かせたいチャンネルを選択して`Incoming Webhookインテグレーションの追加`を押しましょう。

### 3. Webhook URLを(手元に)記録
これでIncoming Webhookを使用する準備ができました。


<img width="513" alt="04.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/58991/30ac4959-2e02-c942-4c17-ab4fd462a902.png">


表示されているWebhook URLを手元に記録しましょう。
このURLに指定されたフォーマットでPOSTを投げると、先程指定したチャンネルに投稿が出ます。
フォーマットを含む使用方法やカスタマイズ方法は、Webhook URLが表示されているページに全て記述されています。

また、Webhook URLが表示されているページには以下の手順でアクセスできます。

1. `Slackをカスタマイズ`
1. `(左メニューの)App管理`
1. `(左メニューの)カスタムインテグレーション`
1. `Incoming Webhook`
1. 設定の鉛筆アイコンクリック

<img width="629" alt="01.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/58991/4e03e722-1980-7b8f-2c7e-be139cf40235.png">


### 4. (名前、アイコンなどをカスタマイズ)
Webhook URLが表示されているページではBotのアイコンや名前、説明を変更できます。
必要があればここで設定できますが、Webhook URLに投げるPOSTにアイコンや名前のパラメータを入れることで投稿の度に変更することもできます。


## SlackにPOSTするプログラム作成 (Python)
単純な例を以下に挙げます。行っている作業は以下の通りです。

1. 表示するメッセージ作成
1. アイコンと名前指定(しなくても良い)
1. Webhook URLにPOST

後にAWS Lambdaから呼ぶことを考えるとメソッドにまとめておいたほうが良いでしょう。

```python
# 投稿内容は最初に挙げた例に揃えました
import json
import urllib.request

def post_slack():
    message = """
    今日のお昼ご飯はこの中から選んでみては？
    1. めし屋
    2. 洋食
    3. 焼き肉
    """

    send_data = {
        "username": "お昼ご飯推薦",
        "icon_emoji": ":bento:",
        "text": message,
    }
    send_text = "payload=" + json.dumps(send_data)
    # URLにはご自分のWebhook URLを入力してください
    request = urllib.request.Request(
        "https://hook.slack.com/~~~~~~~~", 
        data=send_text.encode("utf-8"), 
        method="POST"
    )
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")
```

## AWSの設定
### (AWSアカウント作成)
AWSのアカウントを作成しましょう。

以下が公式が出しているアカウント作成の流れです。これに従えば難なく登録できるかと思います。
https://aws.amazon.com/jp/register-flow/

**クレジットカードの登録**と**電話による認証**が必須となることに注意してください。
無料枠を超えた分は機能しないのではなく、**課金されながら機能し続けます**。

また事前にリージョン(地理的に離れたデータセンタの集合体)を東京に変更しておいたほうが良いでしょう。
<img width="569" alt="05.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/58991/f0cdbd61-7ed2-716c-61ce-3e45357ae150.png">

### AWS Lambdaにプログラム設置
サービス一覧からLambdaを選択し、`関数の作成`をクリックすると以下の画面に遷移します。
<img width="682" alt="06.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/58991/460b6d82-47c3-18fa-4d6a-590bb93f5370.png">

`一から作成`を選択し、必要事項を選択・記述します。

- `名前`は任意のものを入力してください。ランタイムは今回はPython3.7(3.6でも可)を選択します。
- `ランタイム`は今回は`Python 3.7`を選択します。(3.6でも可)
- `ロール`は、今回はAWS内のリソースにアクセスしないため、`カスタムロールの作成`を選択し、特に権限を持たないロールを一つ作成して割り当てます。
	- `ロール`というのはAWSリソースへのアクセスをコントロールする機能で、AWS Identity and Access Management (IAM) によって管理されます。
	- 作成するLambdaの関数がAWS内のデータベースなどにアクセスする場合は適切な権限を付与する必要があります。

入力後、`関数の作成`を押すと関数が作成され、関数の設定画面に遷移します。

### トリガー(起動タイミング)追加
関数の設定画面の`Designer`からトリガー(起動タイミング)を設定します。
今回は特定の時間になったら起動するようにしたいので、`Designer`の`トリガーの追加`から`CloudWatch Events`を追加します。

`CloudWatch Events`追加前
![01.PNG](https://qiita-image-store.s3.amazonaws.com/0/58991/ce3db469-43e4-fa63-90c8-c94e15d83d8f.png)


`CloudWatch Events`追加後
![02.PNG](https://qiita-image-store.s3.amazonaws.com/0/58991/53e35985-6920-4474-e9d2-a17299cf1cbf.png)

`CloudWatch Events`を追加すると、`Designer`上で追加された`CoudWatch Events`が選択された状態になり、`Designer`の下に`トリガーの設定`が現れます。
ルールから`新規ルールの作成`を選択しましょう。

![03.PNG](https://qiita-image-store.s3.amazonaws.com/0/58991/e1fe0b83-3981-5d10-1ed9-e6627943c65b.png)

すると`トリガーの設定`の項目が変化しますので、それぞれ記述していきます。

- `ルール名`は、任意の名前を入力してください。
- `ルールタイプ`は、今回のように時間を利用して起動する場合は`スケジュール式`を選択します。
- `スケジュール式`は、`cron`か`rate式`で記述できます。
    - 式についての詳細は[AWSドキュメント：ルールのスケジュール式](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)を確認してください。
    - `cron`は指定した時間に、`rate式`では一定時間ごとに関数を起動できます。
    - `cron`で記述する場合、**UTCで設定すること**に注意してください。
    - 例として、日本の月-金曜日の午前11時45分に起動する場合、 `cron(45 2 ? * MON-FRI *)`となります。
 
記述し終わったら`追加`をしましょう。
また、画面右上の`保存`ボタンを押すまで保存されませんので注意してください。

### コード設置
`Designer`の中央の関数を選択します。(例では`ForQiita`)
すると`CloudWatch Events`を選択していたときは`トリガーの設定`だった枠が`関数コード`となります。
![04.PNG](https://qiita-image-store.s3.amazonaws.com/0/58991/114457ef-916b-e42e-ac72-a7d0f07ced0c.png)

関数コードには予め`lambda_function.py`が入っており、以下のコードが記述されています。

```python
import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
```

`lambda_handler(event, context)`はトリガーによってLambda関数が呼び出されたときに、最初に呼び出されるメソッドです。(`関数コード`の`ハンドラ`から変更可能)

- 引数である`event`はdictであり、トリガーによって中身が変化します。
	- 例として、AWS API GatewayをトリガーとしてHTTPでアクセスによってLambdaが呼び出されたとき、`event`にはHTTPヘッダやボディが含まれます。
- `context`はオブジェクトであり、Lambdaのランタイム情報を得ることができます。

ブラウザ上で直接コードを編集し、`SlackにPOSTするプログラム作成 (Python)`で作成したコードを統合します。
以下例です。

```python
import json
import urllib.request

def lambda_handler(event, context):
    # TODO implement
    post_slack()
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

def post_slack():
    message = """
    今日のお昼ご飯はこの中から選んでみては？
    1. めし屋
    2. 洋食
    3. 焼き肉
    """

    send_data = {
        "username": "お昼ご飯推薦",
        "icon_emoji": ":bento:",
        "text": message,
    }
    send_text = "payload=" + json.dumps(send_data)
    # URLにはご自分のWebhook URLを入力してください
    request = urllib.request.Request(
        "https://hooks.slack.com/~~~~~~~~~~~~~~~~~~~", 
        data=send_text.encode('utf-8'), 
        method="POST"
    )
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode('utf-8')
```

`lambda_handler()`が呼び出されたときにSlackにPOSTするよう追記しました。
return文の値はSlackに対して影響を与えないので今回は未編集です。

最後に画面右上の`保存`ボタンを忘れずに押しましょう。

### テスト
画面右上の`テスト`ボタンからテストを行うことができます。
テストイベントを作成しイベントの中身を記述します。
ココに記述されたjsonが、引数`event`の内容となります。
今回は引数`event`を使用していないためどのような内容でも問題ありません。
![05.PNG](https://qiita-image-store.s3.amazonaws.com/0/58991/441ae964-cb8d-9e35-385a-cc274b13c057.png)

`イベント名`をつけて`作成`ボタンを押すと、画面左上の`テスト`ボタンから作成したテストイベントを利用してテストできます。

<img width="640" alt="無題.png" src="https://qiita-image-store.s3.amazonaws.com/0/58991/6dc093de-4d4f-7c36-f035-f0fcc6490f92.png">

# 最後に
今回AWS Lambdaを利用してアプリ→Slackの単方向のbotを作成しました。
追々Slash commandなどの使用を考える場合はHerokuなどのほうが楽だったかもしれません。

Webhook URLはお手軽で良いのですが、URL一個で使用できるのはセキュリティ的に大丈夫なのでしょうか。
URLバレたらすぐに再生成といった感じでしょうか。