Mattermostの「褒めbot」をLambdaとPythonで作ってみた

## 「褒めbot」を作ってみた経緯

- [Mattermost（マッターモスト）](https://www.mattermost.org/)は、Slack互換を謳ったオープンソースのチャット型コミュニケーションツールです。「Slackだと、チャットの内容が社外サーバへ飛んでしまうため、セキュリティ的にNG。チャットは導入したいけど、自分で管理できるプライベートな環境で構築したい。」などの理由からMattermostを使い始めている人も多いのではないでしょうか。まさに私もそうでした。
- 私はMattermostをEC2で立てたので、「せっかくAWSだしLambdaでも使ってサクッとbotでも作るか！作るなら[褒めbot](https://cnxt.jp/topics/homechannel/)でコミュニケーションしやすい環境にしておきたいなぁ。[心理的安全性](https://qiita.com/hirokidaichi/items/5d8c4294083d85654a04)、大切。まずはチームにポジティブな雰囲気を！」と思い、作ってみることにしました。
- 実際、作ってみると、少ない手順でサクッと作ることができるし、ほどよくいろんな要素を使って作るので、「これはQiitaにちょうどいいかも？」ってことで、作り方を共有します！

## 「褒めbot」の概要

- 「褒チャンネル」に投稿があったら、褒め言葉を返すbotを作ります。完成形はこんなイメージです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/77cc5faa-0984-3754-f4bb-26354e49d364.png)

- AWSの構成は以下の通りです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/b906420a-65e7-7aa7-ab59-89261865f427.png)

- 動作検証バージョンは以下の通りです。
 - Mattermost 5.3.1
 - Python 3.7 (Lambda)

## 「褒めbot」を作る4つの手順

作る手順は以下の4STEPです。

1. Mattermostで、内向きのウェブフック(Incoming Webhook)を作成する
2. Lambda関数を作成する
3. API Gatewayを設定する
4. Mattermostで、外向きのウェブフック(Outgoing Webhook)を作成する

Mattermostのインストールやセットアップについては、この記事では取り扱いません。以下の@terukizmさんの記事が参考になるので、参照してください。

- [SlackクローンのMattermostを使ってみる - 導入、初期設定編-](https://qiita.com/terukizm/items/4a4016d8ec5a21856e4f)
- [SlackクローンのMattermostを使ってみる - 外部連携編 -（WebHooks、Hubot） # 0. 事前準備](https://qiita.com/terukizm/items/4524249dd7f1298fdc06#0-%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99)

それでは、ひとつずつ説明していきます！

### 1. Mattermostで、内向きのウェブフック(Incoming Webhook)を作成する

- まずは、botが投稿に使うURLを払い出します。Mattermostの統合機能の画面を開き、内向きのウェブフックを作成します。（投稿先の「褒チャンネル」は先に作成しておきます。）内向きのウェブフックを作成するとURLが払い出されるので、次の手順でLambda関数に組み込みます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/321e842e-54c5-8977-be57-34e53f18ef6d.png)

### 2. Lambda関数を作成する

- 次に「褒めbot」の本体となるLambda関数を作成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/fe6a07df-2723-d5a7-e6d1-5bb9991d6cf4.png)

- コードは以下の通りです。`WEBHOOK_URL`に前の手順で払い出したURLを指定します。（オレオレ証明書を使っている場合は、最後のコメントアウトしてある方のコードを使います。）

```py:lambda_function.py
import json
import urllib.request
import ssl

# 内向きのウェブフックのURL
WEBHOOK_URL = "https://example.com/hooks/abcdefghij" # ここに内向きのウェブフックで払い出したURLを指定する

def lambda_handler(event, context):
    # リクエストヘッダをJSONにする。
    req_headers = {
        "Content-Type": "application/json",
    }
    # JSONにメッセージをつめる。
    req_json = {
        "text": "いいね！ :+1:",
    }

    # リクエストを生成してMattermostへ投げる。
    req = urllib.request.Request(WEBHOOK_URL, json.dumps(req_json).encode(), req_headers)
    urllib.request.urlopen(req)

    # オレオレ証明書を回避する必要がある場合は、こちらのコードを使う。
    # （contextを設定した上で、リクエストを生成してMattermostへ投げる。）
#    req = urllib.request.Request(WEBHOOK_URL, json.dumps(req_json).encode(), req_headers)
#    context = ssl._create_unverified_context()
#    urllib.request.urlopen(req, context=context)

```

- いったん作成したLambda関数をテストしてみます。（最初のテストの前にテストイベントを作成する必要がありますが、デフォルトで作成すれば大丈夫です。）以下のようにMattermostへの投稿ができればここまでの手順はOKです！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/6ee92dc6-23b5-4753-582f-a89bbf16f02c.png)

### 3. API Gatewayを設定する

- 今度は、「褒チャンネル」に投稿があったときに呼び出す先のAPIを作成します。API Gatewayの設定は、「API→リソース→メソッド」の順に設定し、最後に作成したAPIをデプロイするという流れです。まず、APIを作成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/aaf3eff2-4566-7607-c472-68004006f8c1.png)

- 次にリソースを作成します。ここのリソース名がAPIのURLに含まれます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/30821994-89b0-eed2-94e0-d9e9ab29ac06.png)

- 作成したリソースの中に、メソッド（POST）を作成して、前の手順のLambda関数が呼び出されるように設定します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/d836dd02-1c67-4426-d520-676905d5dfc6.png)

- 作成したAPIをデプロイします。ここのステージ名がAPIのURLに含まれます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/1beac152-b23d-4624-888e-9f7c78417489.png)

- ステージのURLと、リソース名をつなげたものが、Mattermostから呼び出すAPIとなるので、次の手順でMattermostに設定します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/cb9ae15f-18d9-f1ab-c82f-819ed6ef0f06.png)

### 4. Mattermostで、外向きのウェブフック(Outgoing Webhook)を作成する

- 最後の手順として、「褒チャンネル」に投稿があったときにbotのAPIを呼び出す設定をします。Mattermostの統合機能の画面を開き、外向きのウェブフックを作成します。コールバックURLに、前の手順の`[ステージのURL]/[リソース名]`を指定します。また、トリガーワードは指定せず、すべての投稿でbotが呼び出されるようにします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/4e6c08c6-0030-f288-3a7b-cda977457982.png)

- これで手順は完了です。「褒チャンネル」に投稿してみて、以下のようにbotから褒め言葉が返ってくればOKです！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/1f242309-7f61-7f55-994f-ef6df3dbee50.png)

## 「褒めbot」をもう少しブラッシュアップ

これだけだとちょっと味気ないので、もう少しブラッシュアップして、以下の機能を作ります。

1. アイコンを設定する
2. いろんな褒め言葉をランダムに投稿する

### 1. アイコンを設定する

- アイコンは、内向きのウェブフックのプロフィール画像で指定します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/036d6589-c14f-405b-70eb-b9ffb4067d25.png)

- 私は「褒めbot」の作成にあわせて「褒めスタンプ」のカスタム絵文字も作ったので、カスタム絵文字が保存されているURLをブラウザのデベロッパーツールで調べて指定しました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/b2130e04-482b-2428-7a86-60c4ebf178a2.png)

### 2. いろんな褒め言葉をランダムに投稿する

- 今のままだと「いいね！」しか褒め言葉がないので、いろんな褒め言葉をランダムに投稿するように修正します。Lambda関数を修正しますが、直接修正すると「褒チャンネル」に即反映されてしまい、おかしな動作になる可能性があります。そこで、Lambda関数を修正する前にエイリアスを使って、「褒チャンネル」からのリクエストを受け付けるバージョンを切り離します。Lambda関数の新しいバージョンを発行した上で、以下のようにエイリアスを作成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/638709ed-f1b9-a640-41f1-d5c0d830579f.png)

- API Gateway側の設定も`prod`を呼び出すように修正します。修正した後、APIをデプロイすると「褒チャンネル」の投稿に対して、Lambda関数の最新バージョンではなく、`prod`のバージョンでbotが動くようになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/528590aa-131a-430a-04b4-b45ade5c2b17.png)

- 次に、Lambda関数を修正します。修正後のコードは以下の通りです。

```py:lambda_function.py
import json
import urllib.request
import ssl
import random

# 内向きのウェブフックのURL
WEBHOOK_URL = "https://example.com/hooks/abcdefghij" # ここに内向きのウェブフックで払い出したURLを指定する

def lambda_handler(event, context):
    # リクエストヘッダをJSONにする。
    req_headers = {
        "Content-Type": "application/json",
    }
    # メッセージの候補リストを作成する。
    msg_list = [
        "いいね！ :+1:",
        "# :homeru:",
        "ありがとー！ :thankyou:",
        "えらい！ :erai:",
        "すごい！ :sugoi:",
        "# :kansha:",
        "さすがです :tada:",
    ]
    # JSONにランダムにメッセージをつめる。
    req_json = {
        "text": random.choice(msg_list),
        # チャンネルを指定して投稿もできるので、
        # 修正後にLambdaのテストをしておきたいときは
        # 自分へのダイレクトメッセージとするとよい。
#        "channel": "@hoge.hoge",
    }

    # リクエストを生成してMattermostへ投げる。
    req = urllib.request.Request(WEBHOOK_URL, json.dumps(req_json).encode(), req_headers)
    urllib.request.urlopen(req)

    # オレオレ証明書を回避する必要がある場合は、こちらのコードを使う。
    # （contextを設定した上で、リクエストを生成してMattermostへ投げる。）
#    req = urllib.request.Request(WEBHOOK_URL, json.dumps(req_json).encode(), req_headers)
#    context = ssl._create_unverified_context()
#    urllib.request.urlopen(req, context=context)

```

- Lambda関数の修正が完了したら、エイリアス`prod`が指すバージョンを最新版である`$LATEST`に切り替えます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/89547012-64fa-f9d0-5a73-c4bf8aece5d7.png)

- 「褒チャンネル」に投稿してみて、以下のように新しく増やした褒め言葉がランダムに返ってくればOKです！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/1791f2f2-605a-c854-c5d7-627131df6963.png)

- この記事では、シンプルなbot作成までを書きました。ブラッシュアップはしたものの、まだまだ他にも機能を追加できます（公式ドキュメント [Outgoing Webhooks — Mattermost documentation # Create an Outgoing Webhook](https://docs.mattermost.com/developer/webhooks-outgoing.html#create-an-outgoing-webhook)）。例えば、投稿された内容から`user_name`や`text`を拾って、発言したユーザや発言内容で処理を振り分けてみたり…など、色々と考えられるので、また試してみたいと思います！

## 「褒めbot」を作ってみた結果

- 「褒めbot」を実際に、1ヶ月くらい運用しているのですが、どんな雰囲気かというと…
![image.png](https://qiita-image-store.s3.amazonaws.com/0/126976/87488b42-0980-8a2a-5221-d23420d1e010.png)

　① 最初は面白がって使ってくれた！
　② 機能が物足りなくて、すぐ飽きたので改善（記事のブラッシュアップ）。チームの雰囲気もイイ感じ！
　③ でも盛り上がりは一時的で、すぐ特定の人しか使わなくなる…。それでもこの段階では、個々の進捗や学びが共有できていた。
　④ がスケジュールに切羽詰まり始め、投稿ゼロの日が発生…過疎化してしまう。チームの雰囲気もちょっと微妙に…。
　⑤ いま、また盛り上がるように自作自演から再開中！

- …と、思いっきり作り方の記事を書いておいてー、って感じですが、あくまでツールはツールでしかないです。「褒めbot」は、チームの雰囲気作りや情報共有を助けてはくれますが、人間系での布教活動は忘れてはいけません。④のような時期にこそ、自分から「褒チャンネル」を盛り上げることが大切なのだと思いました。私も引き続きがんばります！