論文をサボっているとリプライで叱ってくれるbotを作った(AWS Lambda + CloudWatch Events)

# やったこと：投稿論文の進捗管理用TwitterBotを作る

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/236071/86ec0a04-f1fc-0af7-bd1c-2734d30b6a95.png)
https://twitter.com/naoki_maejima/status/1061196481762222080

- 論文を書き上げるためには、論文を書くためだけの時間を作ることが必要らしい。
- そこで、論文を書く予定の日に論文ドラフト管理用GitHubリモートリポジトリにコミットがなかった時にリプライで叱ってくるTwitterのBotを作った。
    - 毎週月・火・木(金曜はやっぱり遊びたかった)を絶対論文書く日とする。この日にコミットがないと怒られる仕様。
    - 公共の目に触れるTwitterでbotを作るほうが、Slackbotなどより効果的だと考えた。
- 開発に使ったもの：Python3.6, AWS Lambda, AWS CloudWatch Events
- TwitterDevelopersAccountやGitHubのアクセストークンなどは取得した前提で書いています。

# ファイル構成

```
twitter_bot
├── config.yml
├── lambda_function.py
└── requirements.txt
```
この他に後ほどPythonのパッケージがインストールされる。

# requirements.txt

- 必要なPythonパッケージは以下。
    - git_lambdaはLambdaからgitを動かす用。gitのバイナリがバインドされていて、PATHも通してくれるスグレモノ。スクラッチで実装するのは面倒だったので…(感謝)。

```{requirements.txt}
requests_oauthlib==1.0.0
GitPython==2.1.11
git_lambda==1.1.3
PyYAML==3.13
```

# 各種設定用のyamlファイル(config.yml)

- 認証情報やリプライ先の指定を外部化している。
- 叱るだけではあんまりなので褒める文章も用意

```{config.yml}
keys:
    twitter: 
        consumer_key: "XXXXXXXXXXXXXXXXXXX"
        consumer_secret: "XXXXXXXXXXXXXXXXXXX"
        access_token: "XXXXXXXXXXXXXXXXXXX"
        access_secret: "XXXXXXXXXXXXXXXXXXX"
    github:
        repository: "https://username:token@github.com/username/repository.git"
    
config:
    twitter:
        user: "@twitter_user"
        alert_text: #サボった時のお叱り
            - "論文を書け、まだ間に合う。"
            - "論文を書きなさ～い!!"
            - "論文を進めれば、不安は消える。"
            - "明日やろうはバカ野郎だ。"
        praise_text: #サボってなかった時の褒め
            - "論文を書いて、えらーい!!"
            - "論文を書いたので救われた。"
            - "いいね、この調子!!明日はもっと書ける!!"
            - "がんばった!!投稿まであと少し!!"
```

#スクリプト(lambda_function.py)

```{lambda_function.py}
import git_lambda
git_lambda.setup() #lambdaからgitを使えるように設定しておく

from git import Repo, RemoteProgress
import yaml
import os
import random
from datetime import datetime, timedelta, timezone 
from requests_oauthlib import OAuth1Session

def lambda_handler(event, context):
    
   　#各種パラメータの設定
    TEMP_DIR = '/tmp/git_test_' + str(datetime.now().timestamp()) #git init用の一時フォルダ。lambdaでは/tmp以下でしか書き込みができない
    JST = timezone(timedelta(hours=+9),'JST') #日本時間に設定
    TODAY_DATE = datetime.now(JST).date() #現在の月日を取得
    UPDATE_URL = 'https://api.twitter.com/1.1/statuses/update.json' #Twitter投稿用
    
    #設定ファイルを読み込む
    with open('./config.yml','r') as f:
        keys = yaml.load(f)
    
    #Twitterの認証情報
    consumer_key = keys['keys']['twitter']['consumer_key']
    consumer_secret = keys['keys']['twitter']['consumer_secret']
    access_token = keys['keys']['twitter']['access_token']
    access_secret = keys['keys']['twitter']['access_secret']

    #gitリモートリポジトリからfetchする
    os.makedirs(TEMP_DIR, exist_ok=True)
    repo = Repo.init(TEMP_DIR)
    origin = repo.create_remote('origin',keys['keys']['github']['repository'])
    origin.fetch()
    commits = repo.iter_commits('origin/master')
    
    #最新のコミットから順に読まれていくので、今日のコミットでなくなるまで詳細を取得していく
    today_insertions = 0
    today_deletions = 0
    today_commit_count = 0
    for commit in commits:
        commit_date = datetime.fromtimestamp(commit.committed_date,JST).date()
        if(commit_date == TODAY_DATE):
            commit_stats = commit.stats.total
            today_insertions += commit_stats['insertions']
            today_deletions += commit_stats['deletions']
            today_commit_count += 1
        else:
            break
    
    #ツイートを部品ごとに生成していく
    header_text = "\n".join([keys['config']['twitter']['user'], #リプライ先の指定
    TODAY_DATE.strftime("%Y/%m/%d")]) #日付
    
    result_text = "\n".join(["コミット数:" + str(today_commit_count),
    "追加した行:" + str(today_insertions),
    "削除した行:" + str(today_deletions)])
    
    if(today_commit_count == 0):
        #サボってた場合のテキスト
        comment_text = random.choice(keys['config']['twitter']['alert_text'])
    else:
        #頑張って論文を書いた場合のテキスト
        comment_text = random.choice(keys['config']['twitter']['praise_text'])
    
    text = "\n".join([header_text,result_text,comment_text])
    
    #TwitterAPI経由で実際にツイートを行う
    params = {"status": text}
    twitter = OAuth1Session(consumer_key, consumer_secret, access_token, access_secret)
    req = twitter.post(UPDATE_URL, params = params)
    return "Tweet OK"
```

# パッケージごとzipで固める

```{bash}
pip install -r requirements.txt -t .
zip -r twitter_bot.zip ./*
```
- こうしてできたzipファイルをLambdaにアップロードします。
    - 15MBちょいくらいのzipファイルができるはずです

# Lambdaコンソールからテスト
- 適当にテストイベントを設定して、Lambdaコンソール上からテストします。
<img width="220" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/236071/0af0bcc1-1acf-4e38-ec8b-ca1fd3991994.png">
実際にツイートされました。

- 開発環境からは以下で動かせるはず

```{main.py}
from lambda_function import lambda_handler
lambda_handler(None,None)
```

# CloudWatch Eventsから定時実行の設定

- ターゲットを先程のLambda関数に設定。
- 日本時間の毎週月火木の23:59(UTC 14:59)に起動するように、CloudWatchEventsのスケジューラに以下のcron式を記述。

```
59 14 ? * MON,TUE,THU *
```

# 参考
- https://github.com/bcongdon/git_lambda
- https://www.ketancho.net/entry/2017/12/13/003000
- https://gitpython.readthedocs.io/en/stable/
- https://qiita.com/mattsu6/items/5511c5631e7a54550f7f