メールでGitHubに新規issueを追加する

*メールの受け口にAmazon SESを使うようリファクタリングしました [2020/04/26] → 記事は[こちら](https://qiita.com/nikotan/items/840bf8eef167d4f1ed4f)*

# はじめに
メールしか使えない環境から[GitHub](https://github.com/)に新規issueを追加できるようになると便利、というニッチな個人的ニーズがあり、やってみました。

# 方針
まず、メールで何らかのプログラムをキックする方法を考えます。自由に使えるドメインとメールサーバがあれば何でもできそうですが、そうでない場合、メール受信を起点とするWEBサービスを作る選択肢ってそうたくさんはなさそうです。
というわけで、今回は、[IFTTT](https://ifttt.com/)に用意されている[「Email」サービス](https://ifttt.com/email)を使ってみます。IFTTTのトリガーとして使うことができて、IFTTTアカウントに紐付けられたメールアドレスからIFTTTの所定のメールアドレス宛にメールを送ることで、IFTTTのアクションをキックできます。

### IFTTTの「GitHub」サービスを使ったissue追加の課題
IFTTTには[「GitHub」サービス](https://ifttt.com/github)も用意されているので、トリガーを「Email」、アクションを「GitHub」にすれば確かに目的のレシピは作れます。この場合、IFTTTに送りつけたメールのタイトルと本文で新規issueを追加できます。
<img width="330" alt="01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/95dbb21e-38a1-e949-0a3d-c607b5b33bb2.png">
が、、、これを実際に使ってみると、追加された新規issueにはメール本文の改行がなぜか反映されません。
GitHubのissueは[Markdown](https://ja.wikipedia.org/wiki/Markdown)が使えるところが便利ポイントなので、改行が消えてしまうこのレシピでは使い物になりません。

### というわけで「Webhooks」サービス
IFTTTのサービスの中にニーズを満たすものがないときは[「Webhooks」サービス](https://ifttt.com/maker_webhooks)の出番です。新規issueの追加は[GitHub REST API v3](https://developer.github.com/v3/)を使えばできるので、トリガーを「Email」、アクションを「Webhooks」にして、自作のWEBサービスでGitHubへissueを追加することにします。
IFTTTのレシピにするとこんな感じ。
<img width="326" alt="02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/b07eed32-2e33-ee47-8c0b-7d3ca5ead351.png">

# 実装
小さなWEBサービスなので、[Amazon API Gateway](https://aws.amazon.com/jp/api-gateway/)＋[AWS Lambda](https://aws.amazon.com/jp/lambda/)と、この環境向けのPythonフレームワークである[chalice](https://github.com/aws/chalice)を使って簡単に作ってしまいます。

説明をすっ飛ばして、コードは以下だけ。
中で使っているいくつかの環境変数(`os.environ['hoge']`)をchaliceの設定ファイル(`.chalice/config.json`)で設定する必要はありますが、`chalice deploy`のコマンド一発でWEBサービスが動き出します。API GatewayやLambdaの管理コンソールに触る必要は全くありません。

```python:app.py
from chalice import Chalice
import logging, os, json
import urllib.request

# setup logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# setup chalice
app = Chalice(app_name='hook2issue')


@app.route('/', methods=['POST'])
def index():
    json_body = app.current_request.json_body
    logger.info("request body: {}".format(json_body))

    key = json_body['key']
    subject = json_body['subject']
    body = json_body['body']

    if key != os.environ['IFTTT_WEBHOOKS_KEY']:
        logger.warning("wrong key: {}".format(key))

    else:
        url = 'https://api.github.com/repos/{}/{}/issues?access_token={}'.format(
            os.environ['GITHUB_OWNER'],
            os.environ['GITHUB_REPOSITORY'],
            os.environ['GITHUB_ACCESS_TOKEN']
        )
        headers = {
            'Content-Type': 'application/json'
        }
        body = {
            'title': subject,
            'body': body
        }
        req = urllib.request.Request(
            url,
            data=json.dumps(body).encode('utf-8'),
            method='POST',
            headers=headers
        )
        try:
            with urllib.request.urlopen(req) as res:
                logger.info(res.read().decode("utf-8"))
        except Exception as e:
            logger.exception("Error in request: %s", e)

    # 200OK
    return {}
```

ちなみに、上記の自作WEBサービスに合わせて、IFTTTの「Webhooks」は以下のような感じで設定します。
<img width="430" alt="03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336603/eca6a721-2832-fbaf-41b8-856c7890ec8c.png">
これで所望の動きをしてくれるのですが、ハマリポイントが一点だけ。
「Body (optional)」の注意書きに、`Surround any text with "<<>>" to escape the content`とあるのですが、きちんと文字列をエスケープするには`"<<<>>>"`で囲ってあげる必要があります。運良くこのことが書かれた[WEB記事](https://qiita.com/n-noguchi/items/cc1c53e59e652cd0b93e)を見つけられたので良かったですが、もう少しで諦めるところでした。。。

# おわりに
メールはもはやレガシーな手段になってしまった感がありますが、それでもなお、メールしかまともに使えない環境もあるにはあります。メールを起点にしたWEBサービス連携の一つのパターンとして、他にも使えるものが何か作れそうです。

## 参考
- [【Lambda】めんどくさいGitHubのIssue作成はBotにやらせよう！ - Qiita](https://qiita.com/odrum428/items/1d402cc8ccc9b1ecabec)
- [IFTTTでif `Google Assistant` then `Webhook`でBad Requestが発生したときの対応メモ - Qiita](https://qiita.com/n-noguchi/items/cc1c53e59e652cd0b93e)
- [Issues | GitHub Developer Guide](https://developer.github.com/v3/issues/#create-an-issue)