GitHubのWebhookでプルリクエストをマージした際にツイートできるようしてみた

# はじめに
こんにちは。なおとです。
今回はGitHubのプルリクをマージした際に自動でツイートできるようにしたのでその解説となります。

プルリクをマージするとこんな感じでTweetされます。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">tsutomuさんがなおとさんのプルリクをマージしました😊<br>【analysis_resultsテーブルにカラムを追加👨‍🔧】となります😎️<a href="https://t.co/EWfJsIlGyx">https://t.co/EWfJsIlGyx</a></p>&mdash; a6s-cloud (@CloudA6s) <a href="https://twitter.com/CloudA6s/status/1116323525025914880?ref_src=twsrc%5Etfw">2019年4月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

図で表すとこんな感じです。
<img width="1023" alt="スクリーンショット 2019-05-19 22.09.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/bb6e6510-c01d-cefc-67ed-f72ad68e3900.png">


#### なぜやろうと思ったのか？
プライベートでチーム開発をしていますが、時間の制約もあり、かつ非同期のコミュニケーションのため、モチベーションを保つことは難しいです。

普段はGitHub↔︎slack連携をしているので、イシュー作成やプルリクをマージした際に通知がきます。
せっかくプロジェクトもpublicにしているので、ツイッターのオープンな場に通知できたらモチベーションも上がるのでは？と考えたのがきっかけです。

また、今の個人開発ではTwitter APIを使用しています。
以前アカウントを新規取得したのですが、ツイートをしなかった影響なのかアカウントをロックされてしまいました。
その為、CT（ケイゾクテキ ツイート）をして、アカウントがロックされないようにしよう！！と思ったのもあります笑

今回のソースコードはこちらにあります。
https://github.com/nsuzuki7713/github-webhook

# 構成
<img width="1070" alt="スクリーンショット 2019-05-19 22.03.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/4e20c44d-315c-b15d-3cf3-cc915936edd4.png">


こちらがシーケンス図となります。

![IMG_4582.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/5fe45009-dead-a8dd-3725-a3961fd1ea95.png)

シンプルな処理形式だと思います。

1. ユーザーがプルリクを作成する
2. GitHubのWebhookでAPI Gatewayにリクエストをする
3. API Gatewayはlambdaを実行する
4. lambdaでは最初に認証チェック、妥当性チェックを行う
5. lambdaからtwitter apiを叩いてツイートする

# 開発
## ツイッターアカウント作成とapiキーの作成
APIからTweetするためには、Twitter APIが必要です。
下記記事を参考にして申請しました。
https://www.torikun.com/entry/twitter-developer-api/

特に詰まることなく、承認も一瞬でした。数日待たされるという記事も見かけましたが僕は申請したら、数分で承認メールがきました。

英語で使用用途を記載する必要がありますが、僕はGoogle翻訳をフル活用しました。

## lambdaとAPI Gatewayの設定
下記記事を参考にして、進めました。
スクショもあり、丁寧に手順が書いてあります。
[【API Gateway】AWS Lambda統合のPythonでHello, world](https://qiita.com/baikichiz/items/2de7c4c0dcf9b051037a)

## GitHub webhookの設定
下記記事を参考にして、進めました。
[Github serviceをwebhookに変更した](https://tech.actindi.net/2019/01/15/083222)


こちらが設定した内容です。
webhookのtriggerを設定できますが、今回はPull requestsにチェックを入れます。

<img width="984" alt="スクリーンショット 2019-04-13 14.53.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/f1671c82-1ee1-580b-6d53-309b1e513fd7.png">

<img width="728" alt="スクリーンショット 2019-04-13 14.54.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/3202276d-2f40-dda9-c822-c2274bb6b863.png">

## lambda側の実装
今回はpythonを使用しています。選定理由はtwitter apiを使用したサンプル記事が多かったからです。

コードの詳細はGitHubをご確認お願いします。
https://github.com/nsuzuki7713/github-webhook

### フォルダ構成
最低限必要なファイル構成となります。

最初、使用するモジュールをmodulフォルダの中に入れていましたが、Lambdaはデフォルトでプロジェクト直下にモジュールを置く必要があるとのことです。
[プログラム初心者がAWS Lambda(Python)でハマった7個のこと](http://blog.serverworks.co.jp/tech/2016/08/31/lambda-point/)

```bash
├── function.py
├── settings.example.py
├── (以下、使用するモジュール)
```

### ライブラリのインストール

```
# プロジェクトの直下に移動

# ライブラリのインストール
$ pip3 install requests requests_oauthlib -t .
```

僕は`pip3 install requests requests_oauthlib -t`で下記エラーがでました。

`distutils.errors.DistutilsOptionError: must supply either home or prefix/exec-prefix -- not both`

下記記事を参考にして、設定を変更しました。
http://www.sysop.jp/entry/2017/02/05/231821

### APIキー等の設定

```settings.py
###############Twitter API######################
CONSUMER_KEY = "Twitter APIのキー"
CONSUMER_SECRET = "Twitter APIのキー"
ACCESS_TOKEN = "Twitter APIのキー"
ACCESS_TOKEN_SECRET = "Twitter APIのキー"

###############GitHub Webhook######################
SECRET = "GitHubのWebhookで記載してsecretキー"
```

### メイン処理の設定

Webhook payloadのexampleは公式ドキュメントが分かりやすいです。
https://developer.github.com/v3/activity/events/types/#pullrequestevent

```python:function.py
#coding: UTF-8
import json,hashlib,hmac,requests
from requests_oauthlib import OAuth1Session
from datetime import datetime
import settings

def lambda_handler(event, context):
    # HMAC値による簡易認証処理
    signature = event['headers']['X-Hub-Signature']
    signedBody = "sha1=" + hmac.new(bytes(settings.SECRET, 'utf-8'), bytes(event['body'], 'utf-8'), hashlib.sha1).hexdigest()
    if(signature != signedBody):
        return {"statusCode": 401, "body": "Unauthorized" }
    
    # プルリクの情報を抽出
    body = json.loads(event['body'])
    
    # actionのキーがなければ終了
    if "action" not in body:
        return {"statusCode": 200, "body": "exit" }
    
    # プルリクのクローズでなければ終了
    if body['action'] != "closed":
        return {"statusCode": 200, "body": "exit2" }
    
    # ツイートで必要な情報を取得
    title = body['pull_request']['title'] # プルリクのタイトル
    html_url = body['pull_request']['html_url'] # プルリクのURL
    user = body['pull_request']['user']['login'] # プルリク作成者
    merged_by = body['pull_request']['merged_by']['login'] # マージ者
    merge_commit_sha = body['pull_request']['merge_commit_sha'] # マージハッシュ
    repo_name = body['pull_request']['head']['repo']['full_name'] # レポジトリ名(nsuzuki7713/a6s-cloud-backend, nsuzuki7713/a6s-cloud-front, nsuzuki7713/a6s-cloud-batch)
    
    # GitHubのアカウントとツイートする際の名前の対応表
    user_list = {
        "nsuzuki7713": "なおと",
    }

    # コミットハッシュからコミットメッセージを表示
    url = "https://api.github.com/repos/" + repo_name + "/git/commits/" + merge_commit_sha
    res = requests.get(url)
    lists = json.loads(res.text)
    message = lists['message'].split("\n\n", 1)[1]
    
    # tweet文章作成
    if title == message:
        # コミットメッセージとtitleが同じ場合は今までの文言を反映
        msg = user_list[merged_by] + "さんが"\
          + user_list[user] + "さんのプルリクをマージしました😊" + "\n"\
          + "【" + title + "】となります😎️" + "\n" +html_url
    else:
        msg = "【" + title + "】の対応です!!" + "\n" + message + " by " + user_list[merged_by] + "\n" + html_url

    # tweet処理
    twitter = OAuth1Session(settings.CONSUMER_KEY, settings.CONSUMER_SECRET, settings.ACCESS_TOKEN, settings.ACCESS_TOKEN_SECRET)
    params = {"status": msg }
    req = twitter.post("https://api.twitter.com/1.1/statuses/update.json",params = params)
    
    return {"statusCode": 200, "body": msg}
```

## デプロイ
外部モジュールを使用する場合はlambdaのインライン上からはできなく、zip等にする必要があります。
今回はzip形式でデプロイしました。

```bash
# プロジェクトの直下に移動

$ zip -r toLambda.zip ./*
```

作成したzipファイルをlambdaのコンソールからアップロードします。

<img width="1284" alt="スクリーンショット 2019-04-13 21.17.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/fa5e2b19-2bf7-a316-64df-61fab00d158e.png">


## 確認
GitHubのWebHooks設定から実行して、確認することが可能です。

<img width="753" alt="スクリーンショット 2019-04-13 15.13.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/f2cbbab7-63a7-f1fd-03e3-4b46362da457.png">

<img width="743" alt="スクリーンショット 2019-04-13 15.14.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226008/384fdf73-a90a-7821-f04b-8cb3694de785.png">

# おわりに
API GatewayやWebhook、pythonなど初めて使うものが多く、まだ1つ1つ理解できていませんが完成はできました。

issue駆動開発をしたので、issueのクローズした順を追っていただければ僕の進め方も分かります。
https://github.com/nsuzuki7713/github-webhook/issues?q=is%3Aissue+is%3Aclosed

個人開発やGitHub駆動で学習している人は、CT(ケイゾクテキ ツイート)も作成してみませんか笑

ただ、今のままだと固定文言ですぐに飽きてしまうので、コミット数や変更行数のデータを見ながら、もっと文言のバリエーションとかを増やしたいなーと思っています。

# 更新
## 2019/05/19
- 動作イメージ図を追加
- 構成図の追加
- 最新版のコードを反映