📅 カレンダー自動とうろくん
カレンダーに予定を登録するのがめんどくさい。LINEで予定の連絡をもらってもいちいちカレンダーに手動で入力するのは面倒。予定のタイトル、日時を別々に入力していくのはかったるい。そんな悩みを解決するために、予定の内容をLINEに送信するだけで自動でカレンダーに登録してくれるLINE Botを開発しました。
その名も、「カレンダー自動とうろくん」!
「カレンダー自動とうろくん」とは、LINEで送信された自然言語のメッセージから予定情報を抽出し、Googleカレンダーに自動で登録するLINE Botです。
AWS Lambdaを活用したサーバーレスアーキテクチャで構築されており、ユーザーは複雑な操作なしに、日常会話のような文章でカレンダー登録が可能となっています!
ソースコードは
全体像
- ユーザーがLINEで予定の内容を送信
- LINE Messaging APIからLambda関数URL経由でLambda関数が起動
- Lambda関数がGemini APIを使用して自然言語から予定情報を抽出
- 抽出された情報をGoogle Calendar APIを使ってカレンダーに登録
- 処理結果をLINE経由でユーザーに返信
1. Googleカレンダー連携のための下準備
Googleカレンダーに自動で登録するために、GCPでサービスアカウントを作成し、Google Calendar APIを使用してカレンダー登録を行います。
1-1. Google Calendar APIの有効化
- Google Cloud Platformに移動します
- 「APIとサービス」→「ライブラリ」を選択
- 検索バーで「Google Calendar API」を検索
- Google Calendar APIを選択して「有効にする」をクリック
1-2. サービスアカウントを作成
- Google Cloud Platformに移動します
- 「APIとサービス」を選択
- 認証情報を選択し、「+認証情報を作成」からサービスアカウントを選択
- ロールをオーナーに設定してサービスアカウントを作成
1-3. 鍵の作成
- 作成したサービスアカウントの一覧で、サービスアカウント名をクリック
- 「鍵」タブを選択し、「キーを追加」→「新しい鍵を作成」をクリック
- キーのタイプは「JSON」を選択し、「作成」をクリック
- 作成されたJSONファイルを
credentials.json
として保存しましょう(後で使います)
1-4. Googleカレンダーの設定
- Googleカレンダーの設定を開きます
- 予定を管理するマイカレンダーを選択→「特定のユーザーとの共有」の「ユーザーやグループを追加」をクリック
- 1-2で作成したサービスアカウントのメールアドレスを追加し、権限を「予定の変更」に設定
- 同画面下部にあるカレンダーIDをメモしておきましょう(後で環境変数として使用します)
2. LINE Messaging APIのチャンネル作成
LINE Messaging APIの仕様が2024年9月に変更されたようで、他の記事を見ても最新の手順が見つからず苦労しました。ここでは、できるだけ詳しく手順を説明します。
まずは下記リンクから始めましょう:
2-1. LINE Developersコンソールにログイン
LINE Developersコンソールにログインします。
(初めての方は新規登録を完了させてください)
2-2. プロバイダーの作成
2-3. Messaging APIの設定
LINE DevelopersコンソールからMessaging APIチャンネルを直接作成することができなくなったようです(2024年9月頃から変更)。指示に従って公式アカウントを作成します。
2-4. LINE Official Account Managerでの設定
必要情報を入力して作成すると、LINE Official Account Managerコンソールが開きます。Messaging APIチャンネルを開設するために、画面右上部の設定を開きます。
左側のメニューバーにある歯車マークにカーソルを合わせると「Messaging API」が表示されます。
(このメニューを見つけるのに筆者は30分もかかりました。非常にわかりにくいインターフェースです)
「Messaging APIを利用する」ボタンをクリックし、指示に従って進めていくとMessaging APIの開設が完了します。
2-5. チャンネルアクセストークンの取得
LINE Developersコンソールに戻り、作成したプロバイダーを選択すると、Messaging APIチャネルが表示されています。
(本記事ではプロバイダー名を「kozin_kaihatsu」、公式アカウント名を「カレンダー自動とうろくん」としています)
Messaging APIチャネルに移動し、画面上部のメニューバーから「Messaging API設定」に移動します。ページ下部にスクロールすると「チャネルアクセストークン」があります。これを発行しておきましょう。
(このトークンはLambda関数でLINE Messaging APIを呼び出すときに使用します)
3. Lambdaの作成
今回はPython 3.9を使用しました。
(後述するライブラリのレイヤー作成時に、Python 3.12では問題が発生したため)
LINEからのWebhookイベントを直接Lambdaで受け取るため、「関数URL」を有効にし、「認証タイプ」はNONE
を指定します。
3-1. ソースコード
lambda_function.py
import os
import json
import requests
import datetime
import google.auth
from googleapiclient.discovery import build
def lambda_handler(event, context):
CHANNEL_ACCESS_TOKEN = os.environ["CHANNEL_ACCESS_TOKEN"]
CALENDAR_ID = os.environ["CALENDAR_ID"] # 環境変数からカレンダーIDを取得
print(event)
body = json.loads(event["body"])
replyToken = body["events"][0]["replyToken"]
receivedText = body["events"][0]["message"]["text"]
json_text = get_json_from_gemini(receivedText)
# 予定追加処理
success, message = add_google_calendar_event(json_text, CALENDAR_ID)
# 返信メッセージを設定
reply_message = message
# LINE Messaging APIを使用してユーザーからのメッセージに対してリプライを送信する
# HTTPリクエストでLINEのエンドポイントにJSONデータを送信
url = "https://api.line.me/v2/bot/message/reply"
header = {
"Content-Type": "application/json",
"Authorization": f"Bearer {CHANNEL_ACCESS_TOKEN}"
}
body = json.dumps({
"replyToken": replyToken,
"messages": [
{
"type": "text",
"text": reply_message
}
]
})
res = requests.post(url=url, headers=header, data=body)
print(res.text)
return "OK"
def get_json_from_gemini(received_message):
"""Gemini APIを使用してJSONを取得する関数"""
try:
# 環境変数からAPIキーを取得
api_key = os.environ["GEMINI_API_KEY"]
# 現在の日時を取得
JST = datetime.timezone(datetime.timedelta(hours=+9), 'JST')
today = datetime.datetime.now(JST).strftime("%Y-%m-%d %H:%M")
# プロンプトテンプレート
system_prompt = (
"あなたの役割は、ユーザーが入力した日本語のテキストからイベント情報を抽出し、\n"
"Googleカレンダーに登録できる形式のJSONを生成することです。\n\n"
)
user_prompt = (
"入力から抽出すべき情報:\n"
"1. 予定の概要(summary)\n"
"2. 開始日時(start)\n"
"3. 終了日時(end)\n"
"4. タイムゾーン(\"Asia/Tokyo\" で固定)\n\n"
"- JSONは以下の形式で出力してください。出力は**JSONのみ**とし、それ以外の文字は含めないでください。\n\n"
"```json"
"{"
" \"summary\": \"予定の概要\","
" \"start\": {"
" \"dateTime\": \"YYYY-MM-DD HH:MM\","
" },"
" \"end\": {"
" \"dateTime\": \"YYYY-MM-DD HH:MM\","
" }"
"}"
"```\n\n"
"- 日付と時間は24時間表記で「YYYY-MM-DD HH:MM」形式にしてください。\n"
f"- 現在の日付は{today}です。\n"
"- 明らかにカレンダー登録とかけ離れる内容の場合は、\"no event found\" と出力してください。\n\n"
"### 入力テキスト\n"
f"{received_message}\n\n"
"### 出力JSON\n"
)
# Gemini APIを直接呼び出す
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}"
headers = {
"Content-Type": "application/json"
}
# リクエストデータ
data = {
"system_instruction": {
"parts": {
"text": system_prompt
}
},
"contents": {
"parts": {
"text": user_prompt
}
}
}
response = requests.post(url, headers=headers, json=data)
response_json = response.json()
print("Gemini API response:", response_json) # デバッグ用ログ
# レスポンスからテキスト部分を抽出
content = response_json.get("candidates", [{}])[0].get("content", {})
parts = content.get("parts", [{}])
result_text = parts[0].get("text", "") if parts else ""
# マークダウン記法(```json と ```),改行を削除
result_text = result_text.replace("```json", "").replace("```", "").replace("\n", "").strip()
print("result_text",result_text) # デバッグ用ログ
return result_text
except Exception as e:
error_message = f"予定の追加に失敗しました。エラー:{e}"
print(error_message)
return error_message
def add_google_calendar_event(json_text, calendar_id):
"""Googleカレンダーに予定を追加する関数 (OAuth 2.0 サービスアカウント認証を使用)"""
try:
# サービスアカウント認証情報を使用してGoogle Calendar APIクライアントを構築
credentials, project_id = google.auth.default() # タプルを分解して取り出す
service = build('calendar', 'v3', credentials=credentials) # credentials のみを渡す
# json_textが"no event found"の場合はエラーを発生させる
if json_text == '"no event found"' or json_text == 'no event found':
raise Exception("カレンダーに追加したい予定を送ってね!!")
# json_textが文字列の場合、パース
if isinstance(json_text, str):
json_text = json.loads(json_text)
JST = datetime.timezone(datetime.timedelta(hours=+9), 'JST')
summary = json_text["summary"]
start_time = json_text["start"]["dateTime"]
end_time = json_text["end"]["dateTime"]
event_details = {
'summary': summary,
'start': {
'dateTime': datetime.datetime.strptime(start_time, "%Y-%m-%d %H:%M").replace(tzinfo=JST).isoformat(),
'timeZone': 'Asia/Tokyo',
},
'end': {
'dateTime': datetime.datetime.strptime(end_time, "%Y-%m-%d %H:%M").replace(tzinfo=JST).isoformat(),
'timeZone': 'Asia/Tokyo',
},
}
event = service.events().insert(calendarId=calendar_id, body=event_details).execute() # APIリクエストを実行
print("Google Calendar event created successfully")
return True, f"予定を追加しました!!\n 概要:「{summary}」\n時刻:({start_time}~{end_time})"
except Exception as e: # エラー処理を修正
error_message = f"予定の追加に失敗しました。\nエラー:{e}" # エラーメッセージに例外オブジェクトeを含める
print(error_message)
return False, error_message
credentials.json(1-3で作成したものをアップロードしてください。値は空白にしています)
{
"type": ,
"project_id": ,
"private_key_id": ,
"private_key": ,
"client_email":,
"client_id": ,
"auth_uri": ,
"token_uri": ,
"auth_provider_x509_cert_url": ,
"client_x509_cert_url": ,
"universe_domain":
}
3-2. 環境変数の追加
Lambda関数の画面から「設定」>「環境変数」にアクセスし、以下の環境変数を登録します:
-
CHANNEL_ACCESS_TOKEN
:LINEのチャンネルアクセストークン -
CALENDAR_ID
:GoogleカレンダーのID -
GOOGLE_APPLICATION_CREDENTIALS
:/var/task/credentials.json
(credentials.jsonファイルのパス) -
GEMINI_API_KEY
:GeminiのAPIキー(Google AI Studioから取得可能)
3-3. Lambda関数のレイヤーを追加
Lambda関数で外部ライブラリを使用するには、レイヤーを追加する必要があります。今回必要なライブラリは以下の通りです:
- google-auth
- google-api-python-client
- requests
レイヤーの作成手順:
- ローカル環境でPython 3.9をインストール(Dockerでやったほうがいいかも)
- 以下のコマンドを実行してライブラリをインストール:
mkdir -p python/lib/python3.9/site-packages
pip install google-auth google-api-python-client requests -t python/lib/python3.9/site-packages
zip -r layer.zip python
- 作成した
layer.zip
をLambdaコンソールからレイヤーとしてアップロード - Lambda関数の画面から「レイヤー」>「レイヤーの追加」で作成したレイヤーを選択
4. LINE Webhook URLの設定
LINE Official Account Managerに戻り、「Messaging API設定」から「Webhook URL」を設定します。URLはLambda関数の関数URLを入力します。
また、「Webhook送信」を「オン」に設定してください。
5. 動作確認
設定が完了したら、LINEアプリから作成したBotに友達登録し、以下のような予定メッセージを送信してみましょう:
明日の13時から15時まで会議があります
Botが予定を解析してGoogleカレンダーに登録し、結果を返信してくれるはずです。
参考にしたサイト