AWS + Python + LINEbotでゴミ出しリマインドをする【前編】

## はじめに

いつでもゴミ出していいマンションから引っ越して、「ゴミの日覚えられん！」となりLINEbotを作成しました。
【前編】では、LINE Messaging API・AWS Lambda・Amazon EventBridgeを使って、ゴミの日の前日21時にリマインドが送られてくる機能を実装します。
【後編】では、LINE Messaging API・AWS Lambda・API Gatewayを使って、ユーザーが「燃えるゴミ」と送ったら「【収集日】毎週火曜日と金曜日」と返するような機能を実装します。

## 使用技術

・Python3.9
・LINE Messaging API
・AWS Lambda
・Amazon EventBridge


## 簡単な機能の説明

1.EventBridgeが定期的にLambda関数を実行する
2.Lambda関数が実行され、現在日時がゴミの日の前日の場合はLINEbotからメッセージが届く



## 手順

### LINE公式アカウントの作成とLINE Messaging APIの設定

こちらの記事を参考にしました。料金のことについても記載があります。
[【LINE Messaging API】を使ってブロードキャストメッセージを送信する](https://www.chuken-engineer.com/entry/2020/12/15/131236#%E3%83%96%E3%83%AD%E3%83%BC%E3%83%89%E3%82%AD%E3%83%A3%E3%82%B9%E3%83%88%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8)

今回は同居人にもメッセージを送りたかったので、上記記事と同じく、ブロードキャストメッセージ（作成したLINEアカウントと友だちになっているすべてのユーザーに送信するメッセージ）を送信します。
チャンネルアクセストークンは後程使いますので、発行の上控えておいてください。

### Lambda関数作成

実行したいことは以下の3つです。
・現在日時を取得して、「第〇週の〇曜日」と変換する
・明日がゴミの日である場合、「明日は〇〇ゴミの日です」というメッセージを定義する
　(例)毎週火曜日が燃えるゴミの日の場合、今日が月曜日であれば、「明日は燃えるゴミの日です」というメッセージを定義する
・定義された送信メッセージを送信する

1つずつ分けて説明します。

#### ①　現在日時を取得して、「第〇週の〇曜日」と変換する

```python:

from datetime import datetime, timedelta, timezone

# 「第n週」か取得
def get_nth_week(day):
  return (day - 1) // 7 + 1

# 「第n(週)X曜日」か取得
# datetime.weekdayは月曜が0、日曜が6で返す
def get_nth_dow(year,month,day):
  nth_week = get_nth_week(day)
  nth_day = datetime(year,month,day).weekday()
  return {"nth_week" : nth_week, "nth_day" : nth_day}

# 現在日時を取得する
JST = timezone(timedelta(hours=+9), 'JST')
current_at = datetime.now(JST)
year = current_at.year
month = current_at.month
day = current_at.day + 1   # 前日にLINEを送るため、+1をする
nth_week = get_nth_dow(year,month,day)["nth_week"]
nth_day = get_nth_dow(year,month,day)["nth_day"]

```

これで変数nth_weekに「第〇週」、変数nth_dayに「〇曜日」が代入されている状態になりました。
コードの書き方はこちらの記事を参考にしました。
[Pythonで任意の日付がその月の第何週目・何曜日かを取得](https://note.nkmk.me/python-calendar-datetime-nth-dow/)


#### ②　LINEbotからメッセージを送信するための設定をする

```python:

from linebot import (LineBotApi, WebhookHandler)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage)
from linebot.exceptions import (LineBotApiError, InvalidSignatureError)

# LINEbotの送信先設定をする
def notify_lastnight(body):
  line_bot_api =  LineBotApi(channel_access_token = "控えたチャンネルアクセストークン")
  line_bot_api.broadcast(TextSendMessage(text = body))

```

今回はline-bot-sdk-pythonを使ってみました。
公式リファレンスは[こちら](https://github.com/line/line-bot-sdk-python)

コードの書き方はこちらの記事を参考にしました。
[Python、LINE BOTで一斉送信](https://qiita.com/adgjmptw0/items/fa7bd0d039eb6b2e4bfe)


#### ③　明日がゴミの日である場合、「明日は〇〇ゴミの日です」というメッセージを定義する

条件式がちょっと多いですし、居住地域モロバレになってしまうので、フェイクを入れて一部だけコードを載せます。

```python:

  # ゴミ出しの曜日のパターンと送信メッセージの定義
  # 毎週火曜日と金曜日は燃えるゴミ
  if (nth_day == 1) or (nth_day == 4):
    body = "明日は燃えるゴミの日です。準備をしましょう！"
    notify_lastnight(body)

  # 第2・第4水曜日は空き缶・空きびん
  if ((nth_week == 2) and (nth_day == 2)) or ((nth_week == 4) and (nth_day == 2)):
    body = "明日は空き缶・空きびんの日です。準備をしましょう！"
    notify_lastnight(body)

```

①で取得した「第〇週」「〇曜日」という情報を条件式に組み込んで、現在日時に応じてメッセージを変えます。
条件式がtrueであった場合に②で作成した関数`notify_lastnight()`を呼び出しています。これによって、明日がゴミの日でない場合は、LINEメッセージを送らないようになります。

①～③のコードを組み合わせると、下記のようになります。

```python:line_bot_garbage.py

from datetime import datetime, timedelta, timezone
from linebot import (LineBotApi, WebhookHandler)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage)
from linebot.exceptions import (LineBotApiError, InvalidSignatureError)

# 「第n週」か取得
def get_nth_week(day):
  return (day - 1) // 7 + 1

# 「第n(週)X曜日」か取得
# datetime.weekdayは月曜が0、日曜が6で返す
def get_nth_dow(year,month,day):
  nth_week = get_nth_week(day)
  nth_day = datetime(year,month,day).weekday()
  return {"nth_week" : nth_week, "nth_day" : nth_day}

# LINEbotの送信先設定をする
def notify_lastnight(body):
  line_bot_api =  LineBotApi(channel_access_token = "控えたチャンネルアクセストークン")
  line_bot_api.broadcast(TextSendMessage(text = body))

def lambda_handler(event, context):
  # 現在の日時を取得する
  JST = timezone(timedelta(hours=+9), 'JST')
  current_at = datetime.now(JST)
  year = current_at.year
  month = current_at.month
  day = current_at.day + 1   # 前日にLINEを送るため、+1をする
  nth_week = get_nth_dow(year,month,day)["nth_week"]
  nth_day = get_nth_dow(year,month,day)["nth_day"]

  # ゴミ出しの曜日のパターンと送信メッセージの定義
  # 毎週火曜日と金曜日は燃えるゴミ
  if (nth_day == 1) or (nth_day == 4):
    body = "明日は燃えるゴミの日です。準備をしましょう！"
    notify_lastnight(body)

  # 第2・第4水曜日は空き缶・空きびん
  if ((nth_week == 2) and (nth_day == 2)) or ((nth_week == 4) and (nth_day == 2)):
    body = "明日は空き缶・空きびんの日です。準備をしましょう！"
    notify_lastnight(body)

  return {
    "statusCode" : 200,
    "body" : "ok"
  }

```

#### ④　Lambda Layersの設定をする

③までのステップでコードはすべて書けましたが、このままではline-bot-sdkは使えません。
line-bot-sdkのようにPythonの標準ライブラリでないものを使うには、`Lambda Layers`を利用します。
Lambda Layers用のパッケージを作る手順は以下のとおりです。
##### pythonフォルダを作成し、そのフォルダ下にline-bot-sdkをインストールする
　フォルダ名を`「python」`としなければ、Lambda関数がライブラリを参照できないので要注意です。
　[AWS公式ドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html)に詳しい説明があります。

##### 作成したpythonフォルダをzip化する
　ツールで圧縮しても、コードで圧縮しても、どちらでも大丈夫です。
　作成したzipファイルの容量が10MBを超える場合はS3にアップロードしてください。

##### zipファイルをLambda Layersに登録する
①　AWS Lambdaの「レイヤー」から「レイヤーの作成」を選択する
![lambda-layer01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/6576e3b5-6d45-e0f3-342c-1950d57e0173.png)
②　「レイヤー設定」
　「名前」を設定し、「.zipファイルをアップロード」を選択してアップロードを行い、「互換性のあるアーキテクチャ」は「x86_64」を選択し、「互換性のあるランタイム」はLambda関数のランタイムと同じものを選択して、「作成」を押すとLambda Layerができます。
![lambda-layer02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/d66ac18c-3900-3dba-e315-20381790c51c.png)
③　AWS Lambdaの「関数」から「Layers」を押す
![lambda-layer03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/8ec90966-f636-2a44-dfc6-db903dd15be4.png)
④　「レイヤーの追加」を押す
![lambda-layer04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/ae9b4760-b3a7-62b2-7e4d-ddff99fa6382.png)
⑤　「レイヤーを選択」
　「カスタムレイヤー」を選択し、「カスタムレイヤー」のプルダウンから先ほど作成したLambda Layerを選び、「追加」を押します。
![lambda-layer05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/ab33a2b8-2779-8865-bd71-de390cf296ab.png)

これでline-bot-sdkが使えるようになります！
　

### EventBridgeでLambda関数の定期実行を設定する

　実行するLambda関数の設定はできたので、それを定期実行させる設定をします。Amazon EventBridgeを使うと簡単です。

①　Amazon EventBridgeの画面を開き、「ルールを作成」を押す
![eventbridge01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/bf63cb84-4b69-0ed4-022a-3adf93efc716.png)
②　「ルールの詳細を定義」
　「名前」を設定し、「イベントパス」はデフォルトのまま、「ルールタイプ」は「スケジュール」を選択して、「次へ」を押します。
![eventbridge02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/a5241302-9d21-d177-0781-cb4d34d12fd3.png)
③　「スケジュールを定義」
　「特定の時刻に実行されるきめ細かいスケジュール」を選択し、Cron式を設定します。Cron式の書き方は[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-cloudwatchevents-expressions.html)を参考にしてください。
　また、スケジュールはUSTで設定されるので、JSTに合わせるためには時差を考慮してください。
　[【AWS】Amazon EventBridgeのcron式の時差について](https://pointsandlines.jp/server-infra/aws/amazon-eventbridge-cron-timezone)
　「以降10回のトリガー日」で「ローカルタイムゾーン」を選ぶと、日本時間でいつ実行されるかが出てきます。
![eventbridge03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/f0dae120-7e04-5952-e79c-6c2fbc0c135c.png)
④　「ターゲットを選択」
　「ターゲットタイプ」で「AWSのサービス」を選択し、「ターゲットを選択」で「Lambda関数」を選択し、「機能」で定期実行させたいLambda関数を選択し、「次へ」を押してください。
![eventbridge04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2735282/0eaab3a7-81ec-07ac-f192-2317c607a6fb.png)
⑤　タグの必要があれば設定し、「レビューと作成」でルールの内容を確認した後「作成」を押す。



## 感想

　LINEが生活に欠かせなくなっている人間なので、さくっと実装できる割に得られるメリットが大きく大満足です。
　AWS LambdaとLINE Messaging APIの組み合わせは、小規模利用の場合だとコストもかからないですし、初心者の学習にちょうど良いなと感じました。
　後編をなるべくはやく公開できるようにがんばります！