AWSLambda + Python3 でLINEに通知してくれるToDoリストを作った話

#作り始めた経緯
一か月前ほどからJavaを使ってandroidアプリ開発の勉強を始めたのですが、ToDoリストを作っている途中で
「よく考えたら普段使いは**PC**と**iPhone**だった:innocent:」
ということに気づき、このまま使いもしないandroidアプリを作るくらいだったらとりあえずPCからタスク管理ができるものを作ろうということで作り始めました。
#AWSを使う
AWS(Amazon Web Service)はクラウドで様々なことを行えるサービスです
登録後12か月は無料枠がありますし、個人で使う分には利用料金もそこまでしないので安心です。

AWS登録はこちら:point_right: https://aws.amazon.com/jp/

今回は様々なサービスがある中で***Lambda***と***S3***と***CloudWatchEvents***を使っていきます。

***Lambda***:関数を作って何かのトリガーで実行する機能
***S3***:ファイルを保存するストレージ
***CloudWatchEvents***:指定した時間に関数を叩いてくれる

くらいの認識で大丈夫だと思います。
#システム概要
![AWSflow1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/e82716b4-69fe-c78a-cd8d-2ba90481cd6b.png)
分かりにくいですがこんな感じに作っていきます。

#Lambda関数を作る
まずはLambdaの関数を作らなければ何も始まりませんので作っていきます。

まず関数の作成を押します。
<img width="729" alt="AWS1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/837d4022-7e98-32c7-d17d-aad3ecdc804a.png">
一から作成を選択します。

<img width="874" alt="AWS2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/195cbf2b-d5c4-b3f5-cce5-20c4ea986208.png">

すぐ下にこのような画面が出るので関数名と使う言語を入力し関数の作成を押します。
<img width="874" alt="AWS3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/8cc3cc78-8db4-a3dc-18a8-b270f1e8fe61.png">

なんかこんな画面が出てきたらOKです。
<img width="865" alt="AWS4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/d1adbee1-aeba-e1dd-b041-ab48785c1fc6.png">

#ロールの設定
ロールにポリシーをアタッチしておかないと許可がなく実行できなかったりするのでロールの設定をしていきます。

先ほどのページを下がっていくと実行ロールの欄に既存のロールというというところがあります。そこの**hogehogeロールを表示します**というところをクリックしてください。
<img width="586" alt="AWS5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/5e429067-b679-55f2-4058-9d10540bb609.png">
すると、このようなページが出てくると思うので**ポリシーをアタッチします**をクリックしてください。
<img width="718" alt="AWS6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/4fb15498-fd91-2eea-9bd4-dc1de30d79b8.png">
その先のページで下記3つのポリシーをアタッチしてください。
<img width="691" alt="AWS7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/1980d454-b0bd-54e1-55a9-da9d1b1056e7.png">
これでポリシーの設定は完了です。
#PythonでAWSを扱う
PythonでAWSを扱うためにはboto3というライブラリを使用します。
初期設定などは少し難しいので偉大なる先人様の記事を参考にしました。
:point_down:ココミテ:eye:
[boto3を使ってS3をごにょごにょする]
(https://qiita.com/is_ryo/items/e16527db5800854cd95f)
#S3にタスクをアップロードする
まずはクライアント側のプログラムを作ります。
細かいプログラムは人に見せられるような代物ではないので、ここでは僕が作っていて詰まった点とその解決法を書いていきます。
###受け渡しデータ
CSVファイルに渡すデータはタスク名、日付、時間の三つのデータです。
年のデータを渡していないので一年以内のタスクしか扱えません。
###ClientErrorが出る
これはtry-exceptで例外処理できるのですが、素の状態だとエラー名が見つからないので

```py
from botocore.exceptions import ClientError
```

してから

```py
try:
    hogehoge
except(ClientError):
    hugahuga
```

してください。

###データの受取型
with open した後に

```py
reader = csv.reader(f)
```
するのですが、readerにはreaderオブジェクトとして帰るのでそのままは使えません。
僕はfor文を使って二重ループで内容を回収しました。

#通知時間に到達しているかチェックする関数
ここからはLambda側で作っていきます。
なおLambdaにライブラリとコードをまとめたZIP形式でアップロードするためコーディングは自分側で行います。

###プログラム概要
![task_check_flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/1700fc1d-f5f4-d8c7-3f11-120ad8846777.png)

###データを保存するディレクトリ
Lambdaにデータを受け渡すためにS3にいったんCSV形式でタスクのデータを送っているため、それを受け取らなければいけません。
その際ダウンロードをする場所を/tmp/hogehoge.csvとしなければなりません。

```py
s3 = boto3.resource('s3')
bucket = s3.Bucket('hoge')
bucket.download_file('hogehoge.csv','/tmp/hogehoge.csv')
```
のようにします。
###handleについて
Lambdaは呼び出されたときhandle設定した関数にeventとcontextを渡して実行します。
今回はS3からデータを持ってきて利用するのでこの受け取った内容は使用しませんが、受け取らないとエラーを吐くのでしっかりと

```py
def handle(event, context):
```
と記述する必要があります。
また、handle名は自由につけることができ
<img width="804" alt="AWS8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/a4b95f3e-52a8-94d8-854f-7968abe41730.png">
上記のようにハンドラの欄から　プログラム名.ハンドル名　の形式で設定します。
###Lambda上でのタイムゾーン
Lambda上では(多分)リージョンにかかわらずtimeモジュールで持ってくる時間がグリニッジ標準時(GMT)になっています。当然僕は日本でこのプログラムを利用するため日本時間(JST)になおす必要があります。
<img width="826" alt="AWS9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/7e7f97af-6a92-bfa5-6483-9a24fc9b779a.png">
そのために環境変数の欄にキーをTZ、値をAsia/Tokyoと設定します。
するとtimeモジュールで持ってくる値が日本時間に変更されます。
###LambdaからLambdaを叩く
Lambda関数から別のLambda関数を叩くには

```py
import boto3
import json

clientLambda = boto3.client("lambda")
clientLambda.invoke(
    FunctionName="send_line", #送る先の関数名
    InvocationType="Event", #EventかRequestResponseどちらを受け取るか選ぶ
    Payload=json.dumps(event) 
)
```
ここも少し難しかったので下記の先人様の記事を参考にさせていただきました。
[AWS LambdaからLambda呼んでハマった話。](https://qiita.com/shinichi-takahashi/items/04287757c5944818ffe5)
[AWSのLambdaからLambdaを呼んで、Slackにメッセージを送信する](https://qiita.com/seisyu1985/items/76a7a10543ea38dd07de)
###LambdaにZIP形式でアップロードする
ライブラリをインポートして使用している場合Lambdaで扱うためにはライブラリとプログラム本体をまとめたZIP形式のファイルでアップロードする必要があります。

```console
C:\Users\user\programfile> pip install hogehoge -t ./
```
とかでプログラムと同階層に利用したライブラリを入れてください。

その後ZIP圧縮するのですが、この際ファイルの置いてあるディレクトリを圧縮すると階層が一つ深くなってhandleを掴めなくなってしまうようなので、ファイル自体を全選択して圧縮します。
<img width="821" alt="AWS10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/2d12cbc4-5546-7bb3-465e-3ce8a171853a.png">
あとはここに投げて保存して終了です。
一応、ハンドルがつかめているか確認するためにテストイベントを実行します。
<img width="637" alt="AWS11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/155b34f1-eb31-74fd-f0a6-b4404761ccd0.png">
テストイベントの設定を押して
<img width="658" alt="AWS12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/269896/8b575111-c073-433a-5dfa-d4a09f177083.png">
空のテストイベントを作成します。
その後テストから実行します。handleを掴めていてほかのエラーが出ていればとりあえずOKです。

###CloudWatchEventsのcron式
cron式の記法は[分 時間 日 月 曜日 年]の形式で書きます。
cron式の日フィールドと曜日フィールドは同時に指定することができないとCloudWatchEventsのガイドに書いてあるので、どちらか一方でワイルドカード？(疑問符)を使用する必要があります。

[ルールのスケジュール式 - Amazon CloudWatch Events](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)

このプログラムは一時間に一回、00分に叩かれる必要があるので

```[0 * * * ? *]```
のように記述しました。

#LINE Notifyの設定をする
設定はこちらから:point_right:[LINE Notify] (https://notify-bot.line.me/ja/)
ラインに通知を行うためにLINEのAPIサービスのLINE Notifyの登録を行います。
ページを見ればわかるとは思いますが詰まった場合は
[[超簡単]LINE notify を使ってみる](https://qiita.com/iitenkida7/items/576a8226ba6584864d95)
のページを参考にすることをお勧めします。
#LINE送信部
アクセストークンが発行出来たら、先ほどのLambda関数でメッセージが生成されたときに叩かれるsend_line関数を作成していきます。
###権限
ロールにアタッチする権限はS3のreadonly権限のみでOKです。
###初期設定

```py
url = "https://notify-api.line.me/api/notify"
token ="accesstoken" #各個取得したアクセストークン
headers = {"Authorization" : "Bearer "+ token}
```

これをスクリプト内に記述します。
###プログラム概要
メッセージのCSVファイルを受け取って読み取って各メッセージごとにラインに送るだけ。
###メッセージ複数送信

```py
for mess in mess_lis:
    message =  mess
    payload = {"message" :  message}
    r = requests.post(url ,headers = headers ,params=payload)
```
こうしておけば複数送信も可能です。

#最後に
今回はAWSのさわりとしてこのようなToDoリストを作ってみました。
初めての投稿で読みずらい箇所多々あったとは思いますが、また何か作った際にはもっと改善して読みやすい記事を書けるようになってきますのでよろしくお願いします。:bow_tone1:

余談なのですが、最近「ほんとに使える「ユーザービリティ」（エリック・L.ライス著）」という本を読みました。振り返ってみるとこの記事は写真が多すぎた気がします。:pensive: