Claude CodeにGoogleカレンダーから参加ステータスの予定を取得するツールを作ってもらう

Claude CodeにGoogleカレンダーから参加ステータスの予定を取得するツールを作ってもらう

2025.12.01

はじめに

こんにちは。
クラウド事業本部コンサルティング部の渡邉です。

私は、日々のスケジュール管理やタスク管理を行うために、Googleカレンダーを利用しています。
最近、Claude Codeに日報の作成をお願いするカスタムスラッシュコマンドを作成しました。カスタムスラッシュコマンド内で、作業日当日の予定を表形式にまとめてもらったり、タスクの進捗状況の確認や進め方のアドバイスをもらいたいと思ったので、Googleカレンダーから作業日当日の予定抜き出してClaude Codeに渡す必要がありました。

せっかくなら、Claude CodeにGoogle Calendar APIを利用して、自分が参加予定(承諾済み)の予定のみを取得するPythonツールを作成してもらおうと思い今回のブログ執筆に至ります。

概要

作成するツールの機能

今回作成するツールは、以下の機能を持つようにClaude Codeに依頼しました。
ざっくりとした依頼ですが、Claude Codeにひな型を作成してもらい、あとは適宜対話してチューニングしてもらいました。

機能 説明
参加予定のみ取得 自分が「承諾(accepted)」したイベントのみを抽出
終日イベント除外 終日イベントは除外し、時間指定の予定のみ取得
メモ予定除外 私がメモとしてカレンダー登録している開始時刻と終了時刻が同じ予定は除外
複数出力形式 コンソール表示とJSON形式の両方に対応

Google Calendar API とは

Googleカレンダーからプログラムを用いて予定を抽出するためには、Google Calendar APIを利用する必要があります。
Google Calendar APIを利用することで、予定の取得、作成、更新、削除などの操作が可能です。

https://developers.google.com/calendar/api/guides/overview

APIを使用するには、以下の認証方式から選択できます。

認証方式 用途 説明
OAuth 2.0 クライアント認証 ユーザーのカレンダーにアクセス ユーザーの同意を得てアクセストークンを取得
サービスアカウント サーバー間通信 Google Workspaceのドメイン全体の委任が必要

今回は、私個人のカレンダーにアクセスするためにOAuth 2.0 クライアント認証を使用しました。

プロジェクト構造

今回Claude Codeに作成してもらったツールは以下のディレクトリ構造で構成されています。

google-calendar-search/
├── .venv/                    # Python仮想環境
├── pyproject.toml            # プロジェクト設定
├── auth.py                   # 認証モジュール
├── calendar_client.py        # カレンダーAPI操作モジュール
├── formatter.py              # 出力フォーマットモジュール
├── main.py                   # メインエントリーポイント
├── client_secret.json        # OAuthクライアント認証情報(※要作成)
└── token.json                # 認証トークン(自動生成)

事前準備

前提条件

  • Python 3.10以上
  • Googleアカウント
  • Google Cloudプロジェクト

Google Cloud プロジェクトの設定

1. Google Calendar APIの有効化

Google Calendar APIを利用するためには、Google Cloudプロジェクトが必要になります。
自身が管理しているGoogle CloudプロジェクトのConsoleから Google Calendar API を有効化します。

【APIとサービス】 → 【有効なAPIとサービス】をクリックします。

alt text

【APIとサービスを有効にする】をクリックします。

alt text

検索欄から【google calendar api】を検索し、【Google Calendar API】をクリックします。

alt text

【Google Calendar API】の【有効にする】をクリックします。

alt text

これで【Google Calendar API】が有効になりました。

alt text

2. OAuth 同意画面の作成

認可にOAuth 2.0を使用する場合は、ユーザーに同意画面を表示する必要があります。
Google Cloud Consoleから【Google Auth platform】 → 【ブランディング】 をクリックします。

alt text

【開始】ボタンを押下し、OAuth同意画面の設定を構成します。

alt text

アプリ情報としてアプリ名ユーザーサポートメールを入力します。

  • アプリ名:GoogleCalendarEventExtractor
  • ユーザーサポートメール:自身のメールアドレス

alt text

プロジェクト構成として対象を入力します。今回は私個人しか利用しないスクリプトなので内部を選択します。

  • 対象:内部

alt text

連絡先情報としてメールアドレスを入力します。

  • メールアドレス:自身のメールアドレス

alt text

終了するためには、Google APIサービス:ユーザーデータに関するポリシーに同意しますにチェックを入れて、【続行】→【作成】をクリックします。

alt text

ここまででOAuth 同意画面の作成の設定は完了です。

alt text

3. クライアントの作成

OAuth2.0を利用して、Google Calendar APIにアクセスするには、OAuth2.0クライアントIDを作成する必要があります。
Google Cloud Consoleで、【Google Auth platform】 → 【クライアント】 に移動します。

OAuthクライアントIDの作成において、必要情報を入力していき【作成】をクリックします。

  • アプリケーションの種類:デスクトップアプリ
  • 名前:GoogleCalendarEventExtractor

alt text

OAuthクライアントが作成されるので、クライアントシークレットが記載されているJsonファイルをローカルにダウンロードしておきます。

alt text

新しく作成した認証情報は、[OAuth 2.0 クライアント ID] に表示されます。
alt text

環境構築手順

1. Python仮想環境のセットアップ(uvを使用)

今回はPythonのパッケージマネージャーとしてuvを使用します。

# uvのインストール(未インストールの場合)
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env

# 仮想環境の作成
uv venv

2. 依存パッケージのインストール

pyproject.tomlを作成し、依存パッケージを定義します。

pyproject.toml
[project]
name = "google-calendar-search"
version = "0.1.0"
description = "Google Calendar から当日の参加予定イベントを取得するCLIツール"
requires-python = ">=3.10"
dependencies = [
    "google-api-python-client>=2.0.0",
    "google-auth-httplib2>=0.1.0",
    "google-auth-oauthlib>=1.0.0",
]

[project.scripts]
gcal-today = "main:main"

[tool.setuptools]
py-modules = ["main", "auth", "calendar_client", "formatter"]

依存パッケージをインストールします。

uv pip install -e .

3. OAuthクライアント認証情報の配置

Google Cloud Consoleでダウンロードしたclient_secret.jsonをプロジェクトディレクトリに配置します。

実装

認証モジュール(auth.py)

OAuth認証を行うモジュールです。初回実行時はブラウザでGoogleアカウントの認証を行い、取得したトークンをtoken.jsonに保存します。2回目以降はトークンを再利用し、期限切れの場合は自動的にリフレッシュします。

auth.py
"""認証モジュール(OAuth クライアントシークレット方式)"""

from pathlib import Path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

# Calendar API の読み取り専用スコープ
SCOPES: list[str] = ["https://www.googleapis.com/auth/calendar.readonly"]

# 認証関連ファイルのパス
_BASE_DIR = Path(__file__).parent
CLIENT_SECRET_FILE = _BASE_DIR / "client_secret.json"
TOKEN_FILE = _BASE_DIR / "token.json"

def get_credentials() -> Credentials:
    """
    OAuth クライアントシークレットを使用して認証情報を取得する

    認証フロー:
    1. token.json が存在し有効な場合: そのまま使用
    2. token.json が存在し期限切れの場合: リフレッシュトークンで更新
    3. token.json が存在しない場合: ブラウザで認証フローを実行

    Returns:
        Credentials: 有効な認証情報

    Raises:
        FileNotFoundError: client_secret.json が見つからない場合
    """
    creds = None

    # 既存のトークンファイルがあれば読み込む
    if TOKEN_FILE.exists():
        creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)

    # 有効な認証情報がない場合
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            # リフレッシュトークンで更新
            creds.refresh(Request())
        else:
            # 新規認証フローを実行
            if not CLIENT_SECRET_FILE.exists():
                raise FileNotFoundError(
                    f"クライアントシークレットファイルが見つかりません: {CLIENT_SECRET_FILE}\n\n"
                    "以下の手順で設定してください:\n"
                    "1. Google Cloud Console (https://console.cloud.google.com/) を開く\n"
                    "2. 「APIとサービス」→「認証情報」を選択\n"
                    "3. 「認証情報を作成」→「OAuthクライアントID」を選択\n"
                    "4. アプリケーションの種類: 「デスクトップアプリ」を選択\n"
                    "5. 作成後、JSONをダウンロードして client_secret.json として保存"
                )

            flow = InstalledAppFlow.from_client_secrets_file(
                str(CLIENT_SECRET_FILE), SCOPES
            )
            creds = flow.run_local_server(port=0)

        # トークンを保存
        with open(TOKEN_FILE, "w") as token:
            token.write(creds.to_json())

    return creds

カレンダーAPI操作モジュール(calendar_client.py)

カレンダーAPIとのやり取りを行うモジュールです。参加ステータスのフィルタリングがこのモジュールのコア機能になります。

calendar_client.py
"""カレンダーAPI操作モジュール"""

from dataclasses import dataclass
from datetime import datetime, time
from zoneinfo import ZoneInfo

from google.oauth2.credentials import Credentials
from googleapiclient.discovery import Resource, build

@dataclass
class CalendarEvent:
    """カレンダーイベントを表すデータクラス"""

    summary: str
    start: datetime
    end: datetime
    location: str | None = None
    description: str | None = None

def get_calendar_service(credentials: Credentials) -> Resource:
    """
    Calendar API サービスオブジェクトを生成する

    Args:
        credentials: OAuth認証情報

    Returns:
        Resource: Calendar API サービス
    """
    return build("calendar", "v3", credentials=credentials)

def get_today_time_range() -> tuple[str, str]:
    """
    当日の開始・終了時刻をISO 8601形式で取得

    Returns:
        tuple[str, str]: (開始時刻, 終了時刻) ISO 8601形式
    """
    local_tz = ZoneInfo("Asia/Tokyo")
    today = datetime.now(local_tz).date()

    start = datetime.combine(today, time.min, tzinfo=local_tz)
    end = datetime.combine(today, time(23, 59, 59), tzinfo=local_tz)

    return start.isoformat(), end.isoformat()

def is_accepted_event(event: dict) -> bool:
    """
    自分が参加予定のイベントかどうかを判定

    Args:
        event: Google Calendar API から取得したイベント辞書

    Returns:
        bool: 参加予定の場合 True
    """
    attendees = event.get("attendees", [])

    if not attendees:
        return True

    for attendee in attendees:
        if attendee.get("self", False):
            return attendee.get("responseStatus") == "accepted"

    return True

def is_all_day_event(event: dict) -> bool:
    """
    終日イベントかどうかを判定

    Args:
        event: Google Calendar API から取得したイベント辞書

    Returns:
        bool: 終日イベントの場合 True
    """
    return "date" in event.get("start", {}) and "dateTime" not in event.get("start", {})

def parse_event_datetime(event_time: dict) -> datetime:
    """
    イベントの日時情報をパースする

    Args:
        event_time: start または end の辞書

    Returns:
        datetime: パースされた日時
    """
    local_tz = ZoneInfo("Asia/Tokyo")

    if "dateTime" in event_time:
        dt = datetime.fromisoformat(event_time["dateTime"])
        return dt.astimezone(local_tz)
    else:
        date_str = event_time["date"]
        return datetime.strptime(date_str, "%Y-%m-%d").replace(tzinfo=local_tz)

def get_today_events(service: Resource) -> list[CalendarEvent]:
    """
    当日の参加予定イベントを取得する

    Args:
        service: Calendar API サービス

    Returns:
        list[CalendarEvent]: 当日の参加予定イベントリスト
    """
    time_min, time_max = get_today_time_range()

    events_result = (
        service.events()
        .list(
            calendarId="primary",
            timeMin=time_min,
            timeMax=time_max,
            singleEvents=True,
            orderBy="startTime",
        )
        .execute()
    )

    events = events_result.get("items", [])
    calendar_events: list[CalendarEvent] = []

    for event in events:
        if not is_accepted_event(event):
            continue

        if is_all_day_event(event):
            continue

        start = parse_event_datetime(event["start"])
        end = parse_event_datetime(event["end"])

        # 開始時刻と終了時刻が同じ予定(メモ)はスキップ
        if start == end:
            continue

        calendar_event = CalendarEvent(
            summary=event.get("summary", "(タイトルなし)"),
            start=start,
            end=end,
            location=event.get("location"),
            description=event.get("description"),
        )
        calendar_events.append(calendar_event)

    return calendar_events

出力フォーマットモジュール(formatter.py)

取得したイベントをコンソール表示用とJSON形式に整形するモジュールです。

formatter.py
"""出力フォーマットモジュール"""

import json
from datetime import datetime
from zoneinfo import ZoneInfo

from calendar_client import CalendarEvent

def format_events_for_console(events: list[CalendarEvent]) -> str:
    """
    イベントリストをコンソール表示用に整形する

    Args:
        events: イベントリスト

    Returns:
        str: 整形された文字列
    """
    local_tz = ZoneInfo("Asia/Tokyo")
    today = datetime.now(local_tz).strftime("%Y-%m-%d")

    lines: list[str] = []
    lines.append("=" * 40)
    lines.append(f"本日の予定 ({today})")
    lines.append("=" * 40)
    lines.append("")

    if not events:
        lines.append("予定はありません")
        lines.append("")
    else:
        for i, event in enumerate(events, 1):
            start_time = event.start.strftime("%H:%M")
            end_time = event.end.strftime("%H:%M")

            lines.append(f"[{i}] {start_time} - {end_time}")
            lines.append(f"    タイトル: {event.summary}")

            if event.location:
                lines.append(f"    場所: {event.location}")

            if event.description:
                desc = event.description.replace("\n", " ")[:50]
                if len(event.description) > 50:
                    desc += "..."
                lines.append(f"    説明: {desc}")

            lines.append("")

    lines.append("-" * 40)
    lines.append(f"合計: {len(events)} 件の予定")
    lines.append("=" * 40)

    return "\n".join(lines)

def format_events_as_json(events: list[CalendarEvent]) -> str:
    """
    イベントリストをJSON形式で出力する

    Args:
        events: イベントリスト

    Returns:
        str: JSON文字列
    """
    data = []
    for event in events:
        data.append(
            {
                "summary": event.summary,
                "start": event.start.isoformat(),
                "end": event.end.isoformat(),
                "location": event.location,
                "description": event.description,
            }
        )

    return json.dumps(data, ensure_ascii=False, indent=2)

メインモジュール(main.py)

CLIのエントリーポイントです。--jsonオプションでJSON形式の出力に切り替えられます。

main.py
"""Google カレンダー予定取得ツール メインモジュール"""

import argparse
import sys

from google.auth.exceptions import RefreshError
from googleapiclient.errors import HttpError

from auth import TOKEN_FILE, get_credentials
from calendar_client import get_calendar_service, get_today_events
from formatter import format_events_as_json, format_events_for_console

def main() -> None:
    """メインエントリーポイント"""
    parser = argparse.ArgumentParser(
        description="Google カレンダーから当日の参加予定イベントを取得します"
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="JSON形式で出力する",
    )
    args = parser.parse_args()

    try:
        credentials = get_credentials()
        service = get_calendar_service(credentials)
        events = get_today_events(service)

        if args.json:
            print(format_events_as_json(events))
        else:
            print(format_events_for_console(events))

    except FileNotFoundError as e:
        print(f"[ERROR] {e}", file=sys.stderr)
        sys.exit(1)

    except RefreshError:
        print(
            "[ERROR] 認証トークンの更新に失敗しました。\n"
            f"token.json を削除して再認証してください:\n"
            f"rm {TOKEN_FILE}",
            file=sys.stderr,
        )
        sys.exit(1)

    except HttpError as e:
        if e.resp.status == 401:
            print(
                "[ERROR] 認証エラーが発生しました。\n"
                f"token.json を削除して再認証してください:\n"
                f"rm {TOKEN_FILE}",
                file=sys.stderr,
            )
        elif e.resp.status == 403:
            print(
                "[ERROR] APIへのアクセス権限がありません。\n"
                "Google Cloud Console で Calendar API が有効になっているか確認してください。",
                file=sys.stderr,
            )
        elif e.resp.status == 429:
            print(
                "[ERROR] APIリクエスト制限に達しました。しばらく待ってから再実行してください。",
                file=sys.stderr,
            )
        else:
            print(f"[ERROR] API エラー: {e}", file=sys.stderr)
        sys.exit(1)

    except Exception as e:
        print(f"[ERROR] 予期しないエラーが発生しました: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

動作確認

初回実行

以下のコマンドを実行して作成したプログラムの動作確認を行っていきます。

# 実行
uv run python main.py

初回実行時はブラウザが開き、Googleアカウントの認証が求められるので自身のGoogleアカウントを選択します。
alt text

Google Cloud側のOAuth同意画面に設定したアプリ名(GoogleCalendarEventExtractor)の表示でアクセスを許可するかどうか聞かれるので、【許可】をクリックします。
alt text

認証が完了するとブラウザ上に以下の文章が表示されますので、そのままブラウザを閉じます。

The authentication flow has completed. You may close this window.

認証が完了すると、以下のようなフォーマットで当日の参加予定が表示されます。
以下は、サンプルの予定出力情報になります。

========================================
本日の予定 (2025-12-01)
========================================

[1] 10:00 - 11:00
    タイトル: チームミーティング
    場所: 会議室A

[2] 14:00 - 15:00
    タイトル: 1on1
    説明: 週次の進捗確認

----------------------------------------
合計: 2 件の予定
========================================

JSON形式での出力

プログラム実行時のオプションに--jsonを付与することで、出力形式をjson形式にすることができます。

uv run python main.py --json
[
  {
    "summary": "チームミーティング",
    "start": "2025-12-01T10:00:00+09:00",
    "end": "2025-12-01T11:00:00+09:00",
    "location": "会議室A",
    "description": null
  },
  {
    "summary": "1on1",
    "start": "2025-12-01T14:00:00+09:00",
    "end": "2025-12-01T15:00:00+09:00",
    "location": null,
    "description": "週次の進捗確認"
  }
]

最後に

今回は、Google Calendar APIを使って、自分が参加予定(承諾済み)の予定のみを取得するPythonツールをClaude Codeに作成してもらいました。
Claude Codeのような生成AIを利用することで、自然言語で自分が実現したいことを伝えることができプログラムを開発することができるのは非常に便利ですよね。
プログラムを書くことが苦手でも、生成AIと会話を行い解説してもらうことで理解も深めることができます。

作成したツールはClaude Codeの日報作成カスタムスラッシュコマンド内で、実行してもらい日報作成に役立てようと思います。

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部コンサルティング部の渡邉でした!

この記事をシェアする

FacebookHatena blogX

関連記事