Github上のアクティブなPullRequestがあるときにSlackに通知するBotを作った


# 経緯
会社のソースコード管理にGithubを使い開発を行なっていると、プルリクエストを作成してレビューを依頼する　という流れがよくあると思います。

しかし、**土日を跨いでしまったり、普段使わないリポジトリでのPRを出した際など**に
うっかりレビューすることを忘れられてしまい、放置されるという事がまぁよくあります。

その度に毎回「レビューお願いします！」というコミュニケーションをとる必要が出てしまい、
正直無駄なコミュニケーションだなと思ったので
Graffityではレビュー待ちのPRをリポジトリ単位で通知するBotを作ってSlackで運用しています

動作する様子↓
![github.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159111/2b698c4a-1e88-9f4a-1bf3-102107e41cd5.png)

こんな感じで毎朝（**１１時始業なので１１時は朝です**）各リポジトリのアクティブなPRの個数を通知してくれます。

全部マージ済み or ドラフトPRしかない場合はこんな感じで誉めてくれます。

![githubPR1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159111/1c6c3bd4-dac8-1f5c-fb54-d15f6fe96bdf.png)

このBotの実装について今回は紹介します。


# 技術選定

言語：Python3 (Pythonを使用している理由は会社での共通言語的な使い方をしているからです）
ライブラリ： PyGithub 1.55(実装時) https://github.com/PyGithub/PyGithub 
Slackへの投稿はみんな大好きIncomingWebHookを使用

# 事前準備

まずはPyGithubを使うためにGithubのPersonalAccessTokenを取得する必要があります。
https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token

会社で使う場合は会社のGithubアカウント的なアカウントで作るのが良いでしょう。
管理しやすい名前と期限を設定しておきましょう。
念の為無期限はやめといたほうが良いかなと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159111/46a0baed-23d3-fa11-d504-ef15d63c52e7.png)

権限に関しては今回はアクティブなPRの個数が知りたいだけなのでrepoにチェックを入れておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159111/7820de05-b9c5-3949-e3d7-0567e73f03f4.png)

生成ボタンを押すとトークンが表示されます。
これはページを離れると二度と見れなくなるのでメモしておきましょう（1敗
![Personal_Access_Tokens__Classic_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159111/0855fffa-a62d-f936-3919-de5f5a71e149.png)


続いて、SlackでポストするためのIncomingWebhookのURLを発行しておきます。
マニュアルの手順に従い作成し、通知させたいSlackチャンネルも設定しておきましょう。
URLが作成されたらそれも控えておきます
https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8


最後にpythonで使用するライブラリをインストールしておきましょう
```
pip install PyGithub
```

# 実装

とりあえずコード全文です
``` main.py
import sys
from github import Github
import requests, json

WEB_HOOK_URL = "取得したWebhookURL"

# 監視対象リポジトリ
# ここに追加してね
TargetRepositories = ["https://github.com/HOGEHOGE/FUGAFUGA",
                      "https://github.com/HOGEHOGE/PIYOPIYO"]


def main():
    args = sys.argv
    if len(args) != 2:
        print("引数がおかしいよ: github トークン入れてる？")
        return -1
    try:
        github_instance = Github(args[1])
        result_text = ["プルリクレビューの時間だよ！"]
        for repo in github_instance.get_user().get_repos():
            if repo.html_url in TargetRepositories:
                active_pull_requests = [pr for pr in repo.get_pulls() if not pr.draft]
                if len(active_pull_requests) > 0:
                    result_text.append(repo.html_url + " にアクティブなPRが" + str(len(active_pull_requests)) + "個あり:masu:")

        output = "\n".join(result_text)

        send_text = ''
        if len(result_text) >= 2:
            send_text = output
        else:
            send_text = "PR全部レビューしてるわ。偉い"

        requests.post(WEB_HOOK_URL, data=json.dumps({
            'text': send_text
        }))
    except Exception as e:
        print("Error" + e)
        return 1

    return 0

statuscode = main()
sys.exit(statuscode)
```

使う際はこんな感じです
```
python main.py Githubのアクセストークン
```

## 各所解説

まずWebHookのURLと監視対象のリポジトリのリストを用意しています。
現状はコードにベタ書きにしています。（監視対象のリポジトリの増える頻度って新規プロジェクト発足時くらいなので）
```
WEB_HOOK_URL = "取得したWebhookURL"

# 監視対象リポジトリ
# ここに追加してね
TargetRepositories = ["https://github.com/HOGEHOGE/FUGAFUGA",
                      "https://github.com/HOGEHOGE/PIYOPIYO"]
```
ただし、GithubのアクセストークンはGit管理したくなかったので（それはそう）引数として受け取るようにしています

```
args = sys.argv
    if len(args) != 2:
        print("引数がおかしいよ: github トークン入れてる？")
        return -1
```

PyGithubの初期化を行います。
引数にはアクセストークンを入れます
```
github_instance = Github(args[1])
```

ここが今回で一番キモとなる部分です。順を追って説明します
``` main.py
for repo in github_instance.get_user().get_repos():
            if repo.html_url in TargetRepositories:
                active_pull_requests = [pr for pr in repo.get_pulls() if not pr.draft]
                if len(active_pull_requests) > 0:
                    result_text.append(repo.html_url + " にアクティブなPRが" + str(len(active_pull_requests)) + "個あり:masu:")
```

まずgithub_intance.get_user()で自分自身の情報を取得します。
そこからさらにget_reposをすることで「自分が見ることのできるリポジトリの一覧」を取得できます（ここで言う”自分”とはアクセストークンを発行したユーザーです）
それをforで回すことで、自分が見れるリポジトリを全てチェックしていく事ができます
``` main.py
for repo in github_instance.get_user().get_repos():
```

repoの中にhtml_urlというのがあるので、前述のTargetRepositoriesと一致している場合のみ処理を続行します
``` main.py
if repo.html_url in TargetRepositories:
```

get_pullsでPullRequestの情報が取得できます。
その中からdraftじゃないものを抽出し。１個以上あった場合は通知対象としてテキストをリストに追加します
``` main.py
active_pull_requests = [pr for pr in repo.get_pulls() if not pr.draft]
    if len(active_pull_requests) > 0:
        result_text.append(repo.html_url + " にアクティブなPRが" + str(len(active_pull_requests)) + "個あり:masu:")
```


最後に、配列の長さでPRがあるかをチェックし、ない場合は誉めてあげるテキストを入れた上でWebhookURLに対してPostを行えば完了です


``` main.py
output = "\n".join(result_text)

send_text = ''
if len(result_text) >= 2:
    send_text = output
else:
    send_text = "PR全部レビューしてるわ。偉い"

requests.post(WEB_HOOK_URL, data=json.dumps({'text': send_text}))

```


# 運用してみて

実装自体は１時間もかからずにできたBotなのですが大変便利です。
レビュー漏れが無くなるしいちいち「レビューお願いします！」といったコミュニケーションもしなくて済むので快適です。

Graffityではこのpythonコードをビルドマシンのcronで平日朝に定期実行しています。
朝にレビューBotの投稿を見てそこからレビューするというルーチンにもなっており、レビュー促進もされており良い感じです。

ぜひ興味があれば導入を検討してみてください〜