テキストファイルからGoogleカレンダーに予定を追加するシステムの作り方
テキストファイルからGoogleカレンダーに予定を追加するシステムの作り方
日々のタスク管理において、Googleカレンダーは多くの方が活用しているツールの一つです。しかし、毎回ブラウザを開いて入力するのは手間がかかりますよね。今回は、テキストファイルからGoogleカレンダーに予定を追加する便利なシステムについて紹介します。このシステムを使えば、簡単なテキストファイルを作成するだけで、自動的にGoogleカレンダーにToDoタスクを追加できます!
このブログ記事では、システムの概要と使い方を中心に解説します。個人情報やプライベートな予定内容は含まれていません。
1. システム概要:テキストファイルからGoogleカレンダーへ
このシステムは、特定の命名規則に従ったテキストファイルをPythonスクリプトで読み込み、その内容をGoogleカレンダーのToDoタスクとして登録するものです。システムの大きな流れは以下の通りです:
システムの特徴
- ファイル名から日時を自動抽出:ファイル名のパターンから予定の日時を取得
- ファイル内容を説明文として登録:テキストファイルの内容がそのままカレンダーの説明欄に
- 場所情報の自動抽出:ファイル内容から場所情報を検出し設定
- Google認証の自動処理:初回のみブラウザ認証、以降は自動ログイン
2. 使用方法の解説
2.1 準備:必要なライブラリのインストール
このシステムを利用するには、まずPythonと必要なライブラリをインストールする必要があります。PowerShellを開いて以下のコマンドを実行します:
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
認証関連で問題が発生した場合は、認証モジュールを最新版に更新することをお勧めします:
pip install --upgrade google-auth-oauthlib
重要: Google APIを使用するには、Google Cloud Consoleでプロジェクトを作成し、認証情報(credentials.json
)を取得する必要があります。この認証情報ファイルは機密情報を含むため、第三者に共有したり、公開リポジトリにアップロードしないようご注意ください。
2.2 テキストファイルの作成ルール
テキストファイルは特定の命名規則に従って作成する必要があります。ファイル名のフォーマットは次のいずれかです:
yyyy-mmdd-hh_タイトル.txt
(例:2025-0503-14_美術展.txt
)yyyy-mm-dd-hh_タイトル.txt
(例:2025-05-03-14_美術展.txt
)
ファイル名の各部分の意味:
yyyy
:年(4桁)mm
:月(2桁)dd
:日(2桁)hh
:時間(2桁、24時間制)タイトル
:カレンダーに追加する予定のタイトル
2.3 テキストファイルの内容
テキストファイルの内容はカレンダーの説明欄に設定されます。また、「■場所:」や「■住所:」などのパターンがあると、それを自動的に場所情報として抽出します。
以下は、テキストファイルの内容例です:
【美術展 | スペシャル企画】 ■日時:2025年3月20日(木)~7月21日(月) ※休館日:月曜(月祝は営業) ■場所:アートギャラリー(都市公園内) 住所:〒100-0001 東京都千代田区1-1 ■開館時間: 平日・日曜:10:00~18:00 金・土・祝前日:10:00~20:00 ※入場は閉館の30分前まで ■料金: 一般 1,800円 大学生 1,500円(学生証要提示) 高校生 1,000円(学生証要提示)
2.4 スクリプトの実行方法
PowerShellを起動し、以下のコマンドでスクリプトを実行します:
python .\プロンプト文\GoogleカレンダーTD追加_サンプルコード.py -f ".\2025-0503-14_イベント.txt"
または、絶対パスを使用する場合:
python "<プロジェクトフォルダ>\プロンプト文\GoogleカレンダーTD追加_サンプルコード.py" -f "<プロジェクトフォルダ>\2025-0503-14_イベント.txt"
※ <プロジェクトフォルダ>
は、実際の環境に合わせたパスに置き換えてください。
2.5 初回認証の手順
初回実行時には、Googleアカウントでの認証が必要です。スクリプトは以下の2つの認証方法をサポートしています:
- ブラウザ認証(推奨):自動的にブラウザが開き、Googleアカウントでログイン後、権限を承認するだけです。
- コンソール認証(代替手段):ブラウザ認証が失敗した場合に自動的に切り替わります。コマンドラインにURLが表示され、手動でブラウザで開き、表示された認証コードをコンソールに入力します。
認証が完了すると、token.json
ファイルが保存され、次回からは自動的にログインされます。
3. システムの仕組み:テクニカル解説
3.1 ファイル名からの日時抽出
スクリプトは正規表現を使用してファイル名から日時情報とタイトルを抽出します。以下の2つのパターンをサポートしています:
pattern1 = r'(\d{4})-(\d{4})-(\d{2})_(.+)\.txt' pattern2 = r'(\d{4})-(\d{2})-(\d{2})-(\d{2})_(.+)\.txt'
この部分の実装コードは次のようになります:
def parse_filename(filename): """ファイル名から日時情報とタイトルを抽出する サポートするファイル名形式: - yyyy-mmdd-hh_タイトル.txt (2025-0503-14_美術展.txt) - yyyy-mm-dd-hh_タイトル.txt (2025-05-03-14_美術展.txt) Returns: tuple: (開始日時, タイトル) """ try: basename = Path(filename).name # パターン1: yyyy-mmdd-hh_タイトル.txt (例: 2025-0503-14_美術展.txt) pattern1 = r'(\d{4})-(\d{4})-(\d{2})_(.+)\.txt' # パターン2: yyyy-mm-dd-hh_タイトル.txt (例: 2025-05-03-14_美術展.txt) pattern2 = r'(\d{4})-(\d{2})-(\d{2})-(\d{2})_(.+)\.txt' match1 = re.match(pattern1, basename) match2 = re.match(pattern2, basename) if match1: year = int(match1.group(1)) # 最初の2桁が月、次の2桁が日 month_day = match1.group(2) month = int(month_day[:2]) day = int(month_day[2:]) hour = int(match1.group(3)) title = match1.group(4) # 開始日時を作成 start_time = datetime.datetime(year, month, day, hour, 0, 0) return start_time, title elif match2: year = int(match2.group(1)) month = int(match2.group(2)) day = int(match2.group(3)) hour = int(match2.group(4)) title = match2.group(5) # 開始日時を作成 start_time = datetime.datetime(year, month, day, hour, 0, 0) return start_time, title except Exception as e: print(f"ファイル名の解析中にエラーが発生しました: {e}") return None, None
3.2 ファイル内容からの場所情報抽出
ファイル内容から場所情報を抽出するために、複数のパターンを使用してマッチングを行います:
location_patterns = [ r'■場所[::]\s*(.+?)(?:\n|$)', r'■住所[::]\s*(.+?)(?:\n|$)', r'場所[::]\s*(.+?)(?:\n|$)', r'住所[::]\s*(.+?)(?:\n|$)', r'会場[::]\s*(.+?)(?:\n|$)', r'■会場[::]\s*(.+?)(?:\n|$)' ]
場所抽出の実装コードは次のようになります:
def extract_location(content): """ファイル内容から場所情報を抽出する""" location = "" # 場所や住所を示す一般的なパターン location_patterns = [ r'■場所[::]\s*(.+?)(?:\n|$)', r'■住所[::]\s*(.+?)(?:\n|$)', r'場所[::]\s*(.+?)(?:\n|$)', r'住所[::]\s*(.+?)(?:\n|$)', r'会場[::]\s*(.+?)(?:\n|$)', r'■会場[::]\s*(.+?)(?:\n|$)' ] for pattern in location_patterns: match = re.search(pattern, content) if match: location = match.group(1).strip() break return location
3.3 Google認証プロセス
スクリプトは次の順序で認証を試みます:
- 既存のトークンファイルがあれば読み込む
- トークンが期限切れの場合は更新を試みる
- 新規認証が必要な場合:
- まずブラウザ認証を試みる
- 失敗した場合はコンソール認証に切り替える
- 認証情報をトークンファイルに保存
Google認証処理の実装コード:
def authenticate_google_calendar(): """Googleカレンダーの認証を行い、サービスオブジェクトを返す""" creds = None # APIディレクトリとトークンファイルのパス API_DIR = Path(__file__).parent / 'API' TOKEN_FILE = API_DIR / 'token.json' CREDENTIALS_FILE = API_DIR / 'credentials.json' # トークンファイルが存在する場合は読み込む if TOKEN_FILE.exists(): try: with open(TOKEN_FILE, 'r') as token: creds = Credentials.from_authorized_user_info(json.loads(token.read()), SCOPES) except Exception as e: print(f"トークンファイルの読み込みに失敗しました: {e}") creds = None # 有効な認証情報がない場合は新規取得 if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: try: creds.refresh(Request()) except Exception as e: print(f"トークンの更新に失敗しました: {e}") creds = None # 新規認証が必要な場合 if not creds: if not CREDENTIALS_FILE.exists(): print(f"認証情報ファイルが見つかりません: {CREDENTIALS_FILE}") print("Google Cloud Consoleで認証情報を作成して、以下のパスに保存してください:") print(f" {CREDENTIALS_FILE}") sys.exit(1) try: flow = InstalledAppFlow.from_client_secrets_file(str(CREDENTIALS_FILE), SCOPES) # ローカルサーバーによる認証を試みる(推奨) try: print("ブラウザでの認証を開始します...") creds = flow.run_local_server(port=8080) print("認証が完了しました。") except Exception as e: print(f"ブラウザでの認証に失敗しました: {e}") print("コンソールでの認証を試みます...") # run_local_serverが失敗した場合はrun_consoleを試す try: creds = flow.run_console() print("コンソールでの認証が完了しました。") except AttributeError: print("認証モジュールが最新ではありません。以下のコマンドで更新してください:") print("pip install --upgrade google-auth-oauthlib") sys.exit(1) except Exception as e: print(f"認証処理中にエラーが発生しました: {e}") sys.exit(1) # トークンを保存 try: # トークンファイルの親ディレクトリが存在しない場合は作成 TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True) with open(TOKEN_FILE, 'w') as token: token.write(creds.to_json()) print(f"認証情報をトークンファイルに保存しました: {TOKEN_FILE}") except Exception as e: print(f"トークンファイルの保存に失敗しました: {e}") # Google Calendar APIサービスを作成 try: service = build('calendar', 'v3', credentials=creds) return service except Exception as e: print(f"Google Calendar APIへの接続に失敗しました: {e}") sys.exit(1)
3.4 カレンダーへの予定追加
カレンダーへの予定追加は以下の手順で行われます:
- 同名の予定が既に存在するか確認
- 存在する場合は更新、存在しない場合は新規作成
- 予定は「TD:タイトル」の形式で追加される
- 終了時間は開始時間の1時間後に自動設定
- 30分前にポップアップリマインダーを設定
カレンダーへの予定追加を行うコード例:
def add_to_google_calendar(service, start_time, title, description, location): """Googleカレンダーに予定を追加する""" # 終了時間は開始時間の1時間後 end_time = start_time + datetime.timedelta(hours=1) # カレンダーイベントの作成 event = { 'summary': f'TD:{title}', 'location': location, 'description': description, 'start': { 'dateTime': start_time.isoformat(), 'timeZone': 'Asia/Tokyo', }, 'end': { 'dateTime': end_time.isoformat(), 'timeZone': 'Asia/Tokyo', }, 'reminders': { 'useDefault': False, 'overrides': [ {'method': 'popup', 'minutes': 30}, ], }, } # 同名の予定が存在するか確認 check_time_min = start_time - datetime.timedelta(days=1) check_time_max = start_time + datetime.timedelta(days=1) try: # 既存の予定を検索 events_result = service.events().list( calendarId='primary', timeMin=check_time_min.isoformat() + 'Z', timeMax=check_time_max.isoformat() + 'Z', q=f'TD:{title}', singleEvents=True ).execute() events = events_result.get('items', []) existing_event = None for event_item in events: if event_item['summary'] == f'TD:{title}': existing_event = event_item break if existing_event: # 既存の予定を更新 event_id = existing_event['id'] updated_event = service.events().update( calendarId='primary', eventId=event_id, body=event ).execute() print(f"予定を更新しました: {updated_event['summary']}") else: # 新規予定を作成 event = service.events().insert( calendarId='primary', body=event ).execute() print(f"予定を追加しました: {event['summary']}") print("カレンダーへの登録が完了しました") return True except HttpError as error: print(f"Googleカレンダーへの追加に失敗しました: {error}") return False
4. 活用例とカスタマイズのアイデア
4.1 日常的なタスク管理
以下のような日常的なタスクをテキストファイルで管理できます:
- 定期的な家事:
2025-0427-20_調理.txt
- 買い物リスト:
2025-0427-18_買物:日用品.txt
- 問い合わせタスク:
2025-0427-06_家電の問い合わせ.txt
4.2 イベント情報の管理
イベントの詳細情報を含めてカレンダーに登録できます:
- 展示会:
2025-0503-14_美術展.txt
- 予約済みのレストラン:
2025-0510-18_レストラン予約.txt
- 映画鑑賞:
2025-0515-19_映画鑑賞.txt
4.3 システムのカスタマイズアイデア
- 複数のカレンダー対応:仕事用、プライベート用など複数のカレンダーに対応させる
- 終了時間の可変設定:ファイル内容から予定の所要時間を抽出して設定
- 自動タスク振り分け:キーワードに基づいて異なるカレンダーに振り分け
- 定期実行の自動化:Windows タスクスケジューラと連携して定期的に実行
- テキストファイルの自動生成:テンプレートからファイルを生成するスクリプトの追加
5. トラブルシューティング
5.1 認証関連の問題
認証エラーが発生した場合:
- token.jsonファイルを削除:
Remove-Item "<プロジェクトフォルダ>\プロンプト文\API\token.json"
- スクリプトを再実行し、認証手順をもう一度行う
5.2 ファイル名の解析エラー
ファイル名から日時情報を抽出できない場合は、命名規則を確認してください:
yyyy-mmdd-hh_タイトル.txt
yyyy-mm-dd-hh_タイトル.txt
5.3 ネットワーク関連の問題
接続エラーが発生した場合の対処法:
- インターネット接続を確認
- VPNを使用している場合は一時的に無効化
- ファイアウォールの設定でPythonを許可
6. メインスクリプトのサンプルコード
以下に、完全なメインスクリプトのサンプルコードを示します。これは、上記で解説した各コンポーネントを組み合わせた全体像です:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ GoogleカレンダーTD追加スクリプト 指定されたテキストファイルの内容をGoogleカレンダーに追加するスクリプト ファイル名から日付と時間を抽出し、ファイル内容を説明欄に設定します """ import os import re import sys import argparse import datetime import json from pathlib import Path # Google Calendar API関連のライブラリ try: from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError except ImportError: print("Google APIライブラリがインストールされていません。") print("以下のコマンドでインストールしてください:") print("pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib") sys.exit(1) # 権限スコープの設定 SCOPES = ['https://www.googleapis.com/auth/calendar'] # ファイルパスの設定(スクリプトが存在するディレクトリを基準に) BASE_DIR = Path(__file__).parent API_DIR = BASE_DIR / 'API' TOKEN_FILE = API_DIR / 'token.json' CREDENTIALS_FILE = API_DIR / 'credentials.json' def authenticate_google_calendar(): """Googleカレンダーの認証を行い、サービスオブジェクトを返す""" creds = None # トークンファイルが存在する場合は読み込む if TOKEN_FILE.exists(): try: with open(TOKEN_FILE, 'r') as token: creds = Credentials.from_authorized_user_info(json.loads(token.read()), SCOPES) except Exception as e: print(f"トークンファイルの読み込みに失敗しました: {e}") creds = None # 有効な認証情報がない場合は新規取得 if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: try: creds.refresh(Request()) except Exception as e: print(f"トークンの更新に失敗しました: {e}") creds = None # 新規認証が必要な場合 if not creds: if not CREDENTIALS_FILE.exists(): print(f"認証情報ファイルが見つかりません: {CREDENTIALS_FILE}") print("Google Cloud Consoleで認証情報を作成して、以下のパスに保存してください:") print(f" {CREDENTIALS_FILE}") sys.exit(1) try: flow = InstalledAppFlow.from_client_secrets_file(str(CREDENTIALS_FILE), SCOPES) # ローカルサーバーによる認証を試みる(推奨) try: print("ブラウザでの認証を開始します...") creds = flow.run_local_server(port=8080) print("認証が完了しました。") except Exception as e: print(f"ブラウザでの認証に失敗しました: {e}") print("コンソールでの認証を試みます...") # run_local_serverが失敗した場合はrun_consoleを試す try: creds = flow.run_console() print("コンソールでの認証が完了しました。") except AttributeError: print("認証モジュールが最新ではありません。以下のコマンドで更新してください:") print("pip install --upgrade google-auth-oauthlib") sys.exit(1) except Exception as e: print(f"認証処理中にエラーが発生しました: {e}") sys.exit(1) # トークンを保存 try: # トークンファイルの親ディレクトリが存在しない場合は作成 TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True) with open(TOKEN_FILE, 'w') as token: token.write(creds.to_json()) print(f"認証情報をトークンファイルに保存しました: {TOKEN_FILE}") except Exception as e: print(f"トークンファイルの保存に失敗しました: {e}") # Google Calendar APIサービスを作成 try: service = build('calendar', 'v3', credentials=creds) return service except Exception as e: print(f"Google Calendar APIへの接続に失敗しました: {e}") sys.exit(1) def parse_filename(filename): """ファイル名から日時情報とタイトルを抽出する サポートするファイル名形式: - yyyy-mmdd-hh_タイトル.txt (2025-0503-14_美術展.txt) - yyyy-mm-dd-hh_タイトル.txt (2025-05-03-14_美術展.txt) Returns: tuple: (開始日時, タイトル) """ try: basename = Path(filename).name # パターン1: yyyy-mmdd-hh_タイトル.txt (例: 2025-0503-14_美術展.txt) pattern1 = r'(\d{4})-(\d{4})-(\d{2})_(.+)\.txt' # パターン2: yyyy-mm-dd-hh_タイトル.txt (例: 2025-05-03-14_美術展.txt) pattern2 = r'(\d{4})-(\d{2})-(\d{2})-(\d{2})_(.+)\.txt' match1 = re.match(pattern1, basename) match2 = re.match(pattern2, basename) if match1: year = int(match1.group(1)) # 最初の2桁が月、次の2桁が日 month_day = match1.group(2) month = int(month_day[:2]) day = int(month_day[2:]) hour = int(match1.group(3)) title = match1.group(4) # 開始日時を作成 start_time = datetime.datetime(year, month, day, hour, 0, 0) return start_time, title elif match2: year = int(match2.group(1)) month = int(match2.group(2)) day = int(match2.group(3)) hour = int(match2.group(4)) title = match2.group(5) # 開始日時を作成 start_time = datetime.datetime(year, month, day, hour, 0, 0) return start_time, title except Exception as e: print(f"ファイル名の解析中にエラーが発生しました: {e}") return None, None def read_file_content(filepath): """ファイルの内容を読み込む""" try: with open(filepath, 'r', encoding='utf-8') as f: return f.read() except UnicodeDecodeError: # UTF-8でエラーが発生した場合はShift-JISで試行 try: with open(filepath, 'r', encoding='shift_jis') as f: return f.read() except Exception as e: print(f"ファイルの読み込みに失敗しました(Shift-JIS): {e}") return "" except Exception as e: print(f"ファイルの読み込みに失敗しました: {e}") return "" def extract_location(content): """ファイル内容から場所情報を抽出する""" location = "" # 場所や住所を示す一般的なパターン location_patterns = [ r'■場所[::]\s*(.+?)(?:\n|$)', r'■住所[::]\s*(.+?)(?:\n|$)', r'場所[::]\s*(.+?)(?:\n|$)', r'住所[::]\s*(.+?)(?:\n|$)', r'会場[::]\s*(.+?)(?:\n|$)', r'■会場[::]\s*(.+?)(?:\n|$)' ] for pattern in location_patterns: match = re.search(pattern, content) if match: location = match.group(1).strip() break return location def add_to_google_calendar(service, start_time, title, description, location): """Googleカレンダーに予定を追加する""" # 終了時間は開始時間の1時間後 end_time = start_time + datetime.timedelta(hours=1) # カレンダーイベントの作成 event = { 'summary': f'TD:{title}', 'location': location, 'description': description, 'start': { 'dateTime': start_time.isoformat(), 'timeZone': 'Asia/Tokyo', }, 'end': { 'dateTime': end_time.isoformat(), 'timeZone': 'Asia/Tokyo', }, 'reminders': { 'useDefault': False, 'overrides': [ {'method': 'popup', 'minutes': 30}, ], }, } # 同名の予定が存在するか確認 check_time_min = start_time - datetime.timedelta(days=1) check_time_max = start_time + datetime.timedelta(days=1) try: # 既存の予定を検索 events_result = service.events().list( calendarId='primary', timeMin=check_time_min.isoformat() + 'Z', timeMax=check_time_max.isoformat() + 'Z', q=f'TD:{title}', singleEvents=True ).execute() events = events_result.get('items', []) existing_event = None for event_item in events: if event_item['summary'] == f'TD:{title}': existing_event = event_item break if existing_event: # 既存の予定を更新 event_id = existing_event['id'] updated_event = service.events().update( calendarId='primary', eventId=event_id, body=event ).execute() print(f"予定を更新しました: {updated_event['summary']}") else: # 新規予定を作成 event = service.events().insert( calendarId='primary', body=event ).execute() print(f"予定を追加しました: {event['summary']}") print("カレンダーへの登録が完了しました") return True except HttpError as error: print(f"Googleカレンダーへの追加に失敗しました: {error}") return False def main(): """メイン関数""" parser = argparse.ArgumentParser(description='テキストファイルの内容をGoogleカレンダーに追加します') parser.add_argument('-f', '--file', required=True, help='追加するテキストファイルのパス') args = parser.parse_args() file_path = args.file if not os.path.exists(file_path): print(f"指定されたファイルが見つかりません: {file_path}") return # ファイル名から日時とタイトルを抽出 start_time, title = parse_filename(file_path) if not start_time or not title: print("ファイル名から日時情報またはタイトルを抽出できませんでした") print("ファイル名は以下のいずれかの形式である必要があります:") print(" - yyyy-mmdd-hh_タイトル.txt (例: 2025-0503-14_美術展.txt)") print(" - yyyy-mm-dd-hh_タイトル.txt (例: 2025-05-03-14_美術展.txt)") return # ファイル内容を読み込む description = read_file_content(file_path) # 場所情報を抽出 location = extract_location(description) # Google Calendar APIに接続 service = authenticate_google_calendar() # カレンダーに予定を追加 success = add_to_google_calendar(service, start_time, title, description, location) if success: print("処理が正常に完了しました") else: print("処理中にエラーが発生しました") if __name__ == '__main__': main()
7. まとめ
テキストファイルからGoogleカレンダーに予定を追加するこのシステムは、日々のタスク管理をシンプルかつ効率的にしてくれます。テキストファイルをベースにしているため、特別なアプリケーションがなくても予定の作成・編集が可能です。
システムの特徴は以下の通りです:
- シンプルな操作性:テキストファイルの作成とコマンド一つで予定追加
- 詳細情報の自動抽出:ファイル名から日時、内容から場所情報を抽出
- 拡張性:様々なカスタマイズが可能
今回紹介したシステムを活用することで、日々の予定管理がより簡単になります。ぜひ試してみてください!
※本記事で紹介したシステムはローカル環境での使用を前提としています。credentials.jsonには機密情報が含まれるため、第三者と共有したり、公開リポジトリにアップロードしないようご注意ください。