EventBridge + Lambdaを使ってスプレッドシートから読書メモをpushするLineBot作成

tech article

せっかく読書をしても、しかも丁寧にメモまで取っていても、内容をすぐに忘れてしまうのがもったいないなと思っていました。そこで毎日1回メモの中からランダムに1つを通知するLambda関数を作ってみました。

AWSのアカウントを作成するという関門(この記事ではアカウントを持っているという前提で話を進めます)さえ乗り越えれば、ITの知識0でも作れるはずです。即席麺が完成するのを待っている間にできちゃうくらい簡単な内容です!

LINEボットを作成する

まずは下の記事に従ってMessagingAPIチャネルを作ります。

Messaging APIを始めよう
LINE Developersサイトは開発者向けのポータルサイトです。LINEプラットフォームのさまざまな開発者向けプロダクトを利用するための、管理ツールやドキュメントを利用できます。LINEログインやMessaging APIを活用して、アプリやサービスをもっと便利に。

上の記事の最後のほうにも書いてあるように、続けて「ボットを作成する」の記事に従ってボットを作成します。

いくつか注意点があります。

チャンネルトークンは「長期のチャネルアクセストークン」を取得します。
今回はユーザーからのメッセージは受け付けないので「Webhook URLを設定する」は飛ばします。

ボットを作成する
LINE Developersサイトは開発者向けのポータルサイトです。LINEプラットフォームのさまざまな開発者向けプロダクトを利用するための、管理ツールやドキュメントを利用できます。LINEログインやMessaging APIを活用して、アプリやサービスをもっと便利に。

ここまで出来たらLINEボット編は終了です。
この段階で作ったボットを自分のLINEのともだちに追加しておきましょう。

スプレッドシートを作成する

以下のようにシートを準備します。今回のボットで実際に使用するスプレッドシートです。

Book's_passages
Sheet1 書名,一節,著者 Z世代化する社会 お客様になっていく若者たち,昇進が「どうでもいい」という割合が増えたことは概ね批判的に受け取られ、若者叩きの格好のネタでもあった。しかし、意外な事実として見えてきたのは、けっこう中身のある「どうでもいい」があるのだということだ。好意的に見れば、これこそ「キャリア観の多様...

A列には書名を、B列には一節(メモの内容)を、C列には著者を記入します。
一行目は凡例を入力します。
(これ凡例って言わん気がするな……ラベルが正解?どんぴしゃの言葉持ってる人こっそり教えてください。)

スプレッドシートが準備できたら、APIでスプレッドシートの読み取りをできるようにします。
以下のサイトを参考に進めます。

「手順3、スプレッドシートの作成」において、シート名は「Sheet1」にしてください。
「手順5、エンドポイントを使ってJSONを取得」はできる方はやってみてください。できない方は無視して大丈夫です。

【Google Sheets API】 スプレッドシートのデータをJSONで取得する|notes by SHARESL
Google Sheets APIを使ってスプレッドシートのデータをJSONで取得する方法について。APIの利用手順について5つの手順にまとめています。サクッと手元のデータをWeb API化したい時に便利な方法です。

ここまで出来たらスプレッドシート編は終了です。

Lambdaを作成する

いよいよ本丸です。

AWSアカウントにログインし、Lambdaを作成します。
多分この記事での最大の難所はAWSアカウントを作成するところな気がします……
頑張って作ってみて!アカウント作れたらマジで誰でもできるように記事を書いてるから!!

すでに関数を作成してから画面をキャプチャしているのでエラーが出てしまっていますが、無視してください。

この設定にして他はデフォルトのままで、右下にある「関数の作成」をクリックしてください。すると以下のような画面が出てくると思います。

このimport jsonで始まるコードの中身をすべて削除して、以下のコードソースに変更してください。
コードの解説はおまけとしてこの記事の最後に載せます。

from urllib import request
import json
import random
import os

def req(url):
    with request.urlopen(url) as response:
        body = response.read().decode('utf-8')
        return body

# スプレッドシートIDとAPIキーとアクセストークン
SPREADSHEET_ID = os.environ['SPREADSHEET_ID']
API_KEY = os.environ['API_KEY']
access_token = os.environ['access_token']

# Google Sheets APIのURL
url = f"https://sheets.googleapis.com/v4/spreadsheets/{SPREADSHEET_ID}/values/Sheet1!A2:Z1000?key={API_KEY}"

# APIリクエストを送信し、レスポンスを取得
response = req(url)
data = json.loads(response)

# 'values' 配列にアクセス
passage_info_array = data['values']

# 'values' 配列が空でないことを確認
if passage_info_array:
    # ランダムに一つの要素を選択
    random_element = random.choice(passage_info_array)
    # 選択された要素を出力
    print(random_element)
else:
    random_element = "Passage doesn't exist"
    print(random_element)

def format_list_as_text(items):
    # 各要素を改行で連結し、'[', ']' を削除したテキストを返す
    return '\n\n'.join(item.strip("[]") for item in items)

# LINEに送信するメッセージ
formatted_text = format_list_as_text(random_element)
message = str(formatted_text)

def send_line_message(token, message):
    url = "https://api.line.me/v2/bot/message/broadcast"
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {token}"
    }
    data = {
        "messages": [
            {
                "type": "text",
                "text": message
            }
        ]
    }
    
    # POSTリクエストを作成して送信
    req = request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers)
    with request.urlopen(req) as response:
        result = response.read().decode('utf-8')
        return result

# メッセージをLINEに送信
def lambda_handler(event, context):
    result = send_line_message(access_token, message)
    print(result)

すると以下のように画面の表示が変わるので、「Deploy」をクリックしましょう。

つづいて環境変数の設定を行います。

環境変数の設定

「コード」の3つ隣の「設定」をクリックすると以下のような画面になります。
左のリストから「環境変数」を選択し、右側の「編集」をクリックします。

すると以下のような画面が出てきます。

「環境変数の追加」をクリックして、環境変数を追加します。
access_tokenにはLINEボットでつくった長期のチャネルアクセストークンを、
API_KEYにはスプレッドシートを作成するときに作ったAPIキーを、
SPREADSHEET_IDにはスプレッドシートのIDを、
それぞれ入力してください。

入力が完了したら保存しましょう。

保存したら、またコードに戻って、青色のTestをクリックしましょう。

すると以下のような画面が出てくるので、イベント名に「test」と入力し、他はデフォルトのままにしておいて右下の「保存」をクリックしましょう。

これでLINEに通知が来ていたら完成はもうすぐそこです!頑張りましょう!

トリガーを追加する

毎日1回午前9時に、再生装置が発動するように設定します。

画面を上にスクロールして「トリガーを追加」をクリックしてください。
(関数の名前が違いますが無視してください。)

「ソースを選択」と書かれている入力欄をクリックし、EventBridgeを見つけたらクリックしてください。

適当にルール名を入力し、スケジュール式には「cron(0 0 * * ? *)」と入力しましょう

右下の「追加」を押したら作業完了です!お疲れさまでした。
今後はスプレッドシートにメモを追加していけば、毎回ランダムに選ばれたメモを再生装置が蘇らせてくれます。

さらばだ!

おまけ(コードソース解説)

from urllib import request
import json
import random
import os

ここではライブラリのインポートをしています。
はじめはimport requestsしたかったのですが、これをするにはローカルでLayerファイルを作る必要があります。誰でも簡単にボットを作れるようにしたかったので、標準ライブラリだけでなんとかなるようにしました。

def req(url):
    with request.urlopen(url) as response:
        body = response.read().decode('utf-8')
        return body

この記事を参考にしました。

SPREADSHEET_ID = os.environ['SPREADSHEET_ID']
API_KEY = os.environ['API_KEY']
access_token = os.environ['access_token']

os.environっていうのはPythonにおける環境変数へのアクセス方法なんですね。Lambdaではどの言語を使っていてもos.environで環境変数にアクセスすると思ってました。Node.jsではprocess.envでアクセスするみたい。

# Google Sheets APIのURL
url = f"https://sheets.googleapis.com/v4/spreadsheets/{SPREADSHEET_ID}/values/Sheet1!A2:Z1000?key={API_KEY}"

# APIリクエストを送信し、レスポンスを取得
response = req(url)
data = json.loads(response)

# 'values' 配列にアクセス
passage_info_array = data['values']

json.loads(response)があんまり意味わかってない。もともとJSONで受け取るからいらない気もする……

# 'values' 配列が空でないことを確認
if passage_info_array:
    # ランダムに一つの要素を選択
    random_element = random.choice(passage_info_array)
    # 選択された要素を出力
    print(random_element)
else:
    random_element = "Passage doesn't exist"
    print(random_element)

スプレッドシートの中身があるかどうかチェックします。

def format_list_as_text(items):
    # 各要素を改行で連結し、'[', ']' を削除したテキストを返す
    return '\n\n'.join(item.strip("[]") for item in items)

# LINEに送信するメッセージ
formatted_text = format_list_as_text(random_element)
message = str(formatted_text)

‘\n\n’.join(array)で、配列の値と値の間に’\n\n’をいれて結合し、一つの文字列を生成します。

def send_line_message(token, message):
    url = "https://api.line.me/v2/bot/message/broadcast"
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {token}"
    }
    data = {
        "messages": [
            {
                "type": "text",
                "text": message
            }
        ]
    }
    
    # POSTリクエストを作成して送信
    req = request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers)
    with request.urlopen(req) as response:
        result = response.read().decode('utf-8')
        return result

いくつかメッセージ送信方法がありますが、今回はブロードキャストメッセ―ジを採用しました。
ボットがメッセージ送るときってURL全部一緒なんですね~びっくり。
一日当たりこのURLってどれくらいアクセスされてるんですかね。

メッセージを送信する
LINE Developersサイトは開発者向けのポータルサイトです。LINEプラットフォームのさまざまな開発者向けプロダクトを利用するための、管理ツールやドキュメントを利用できます。LINEログインやMessaging APIを活用して、アプリやサービスをもっと便利に。

POSTタイプのリクエストはあんまり理解してません……

# メッセージをLINEに送信
def lambda_handler(event, context):
    result = send_line_message(access_token, message)
    print(result)

Lambda関数の本体です。
Pythonの場合はeventとcontextを引数に取りますが、シグネチャはプログラミング言語によって変わるらしいです。

AWS Lambda handler はどのように呼ばれるのか

eventはイベントから渡される任意の値、contextは実行環境の情報らしいです。

Lambdaの機能あれこれメモ

ユーザーが記述するコードソース上にこいつらについて詳しく書いている場所がなくてよくわかってなかったのですが、今回記事を書くにあたって調べたことで理解が深まりました。
defで定義してる関数なのでeventとcontextが仮引数なのかなと思ってましたが、そうじゃなくて普通に引数なんですかね?

ユーザーが記述するコードソースには見えない裏側でAWSが

event=[...]
context=[...]

みたいに変数に値を代入してくれてるのかな?という理解です。

あれ?でもdefで定義してるからやっぱりこいつが関数として動いてるわけじゃなくて、どこかでlambda_handlerっていう関数が実行されていて、その時に参照されているだけですよね……

ちょっとむずかしいので今回はこの程度で諦めます。

タイトルとURLをコピーしました