2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINEで予定送るだけで自動でカレンダー登録!!GeminiAPIを利用して自動カレンダー登録してくれるLINEbotを開発してみた

Last updated at Posted at 2025-03-23

📅 カレンダー自動とうろくん

カレンダーに予定を登録するのがめんどくさい。LINEで予定の連絡をもらってもいちいちカレンダーに手動で入力するのは面倒。予定のタイトル、日時を別々に入力していくのはかったるい。そんな悩みを解決するために、予定の内容をLINEに送信するだけで自動でカレンダーに登録してくれるLINE Botを開発しました。

その名も、「カレンダー自動とうろくん」!

「カレンダー自動とうろくん」とは、LINEで送信された自然言語のメッセージから予定情報を抽出し、Googleカレンダーに自動で登録するLINE Botです。
AWS Lambdaを活用したサーバーレスアーキテクチャで構築されており、ユーザーは複雑な操作なしに、日常会話のような文章でカレンダー登録が可能となっています!

カレンダー自動とうろくん イメージ図1 カレンダー自動とうろくん イメージ図2

ソースコードは

全体像

システム構成図

  1. ユーザーがLINEで予定の内容を送信
  2. LINE Messaging APIからLambda関数URL経由でLambda関数が起動
  3. Lambda関数がGemini APIを使用して自然言語から予定情報を抽出
  4. 抽出された情報をGoogle Calendar APIを使ってカレンダーに登録
  5. 処理結果をLINE経由でユーザーに返信

1. Googleカレンダー連携のための下準備

Googleカレンダーに自動で登録するために、GCPでサービスアカウントを作成し、Google Calendar APIを使用してカレンダー登録を行います。

1-1. Google Calendar APIの有効化

  1. Google Cloud Platformに移動します
  2. 「APIとサービス」→「ライブラリ」を選択
  3. 検索バーで「Google Calendar API」を検索
  4. Google Calendar APIを選択して「有効にする」をクリック

1-2. サービスアカウントを作成

  1. Google Cloud Platformに移動します
  2. 「APIとサービス」を選択
  3. 認証情報を選択し、「+認証情報を作成」からサービスアカウントを選択
    サービスアカウント作成画面
  4. ロールをオーナーに設定してサービスアカウントを作成

1-3. 鍵の作成

  1. 作成したサービスアカウントの一覧で、サービスアカウント名をクリック
  2. 「鍵」タブを選択し、「キーを追加」→「新しい鍵を作成」をクリック
    鍵の作成画面
  3. キーのタイプは「JSON」を選択し、「作成」をクリック
  4. 作成されたJSONファイルをcredentials.jsonとして保存しましょう(後で使います)

1-4. Googleカレンダーの設定

  1. Googleカレンダーの設定を開きます
  2. 予定を管理するマイカレンダーを選択→「特定のユーザーとの共有」の「ユーザーやグループを追加」をクリック
    カレンダー共有設定画面
  3. 1-2で作成したサービスアカウントのメールアドレスを追加し、権限を「予定の変更」に設定
  4. 同画面下部にあるカレンダーIDをメモしておきましょう(後で環境変数として使用します)

2. LINE Messaging APIのチャンネル作成

LINE Messaging APIの仕様が2024年9月に変更されたようで、他の記事を見ても最新の手順が見つからず苦労しました。ここでは、できるだけ詳しく手順を説明します。

まずは下記リンクから始めましょう:

2-1. LINE Developersコンソールにログイン

LINE Developersコンソールにログインします。
(初めての方は新規登録を完了させてください)
LINE Developers ログイン画面

2-2. プロバイダーの作成

プロバイダーを作成します。
プロバイダー作成画面

2-3. Messaging APIの設定

Messaging APIを選択します。
Messaging API選択画面

LINE DevelopersコンソールからMessaging APIチャンネルを直接作成することができなくなったようです(2024年9月頃から変更)。指示に従って公式アカウントを作成します。
公式アカウント作成画面

2-4. LINE Official Account Managerでの設定

必要情報を入力して作成すると、LINE Official Account Managerコンソールが開きます。Messaging APIチャンネルを開設するために、画面右上部の設定を開きます。

LINE Official Account Manager設定画面

左側のメニューバーにある歯車マークにカーソルを合わせると「Messaging API」が表示されます。
(このメニューを見つけるのに筆者は30分もかかりました。非常にわかりにくいインターフェースです)
Messaging APIメニュー

「Messaging APIを利用する」ボタンをクリックし、指示に従って進めていくと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

レイヤーの作成手順:

  1. ローカル環境でPython 3.9をインストール(Dockerでやったほうがいいかも)
  2. 以下のコマンドを実行してライブラリをインストール:
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
  1. 作成したlayer.zipをLambdaコンソールからレイヤーとしてアップロード
  2. Lambda関数の画面から「レイヤー」>「レイヤーの追加」で作成したレイヤーを選択

4. LINE Webhook URLの設定

LINE Official Account Managerに戻り、「Messaging API設定」から「Webhook URL」を設定します。URLはLambda関数の関数URLを入力します。

また、「Webhook送信」を「オン」に設定してください。

5. 動作確認

設定が完了したら、LINEアプリから作成したBotに友達登録し、以下のような予定メッセージを送信してみましょう:

明日の13時から15時まで会議があります

Botが予定を解析してGoogleカレンダーに登録し、結果を返信してくれるはずです。

参考にしたサイト

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?