LoginSignup
2
5

More than 3 years have passed since last update.

PythonとGoogle APIを使ってCSVの予定でGoogleカレンダーを更新してみた

Posted at

目標:Googleカレンダー⇔CSVファイル⇔別アプリのようにカレンダーと他アプリを連携すること。
以下の続編。

Pythonを使ってCSVの予定をGoogleカレンダーに載っけてみた
https://qiita.com/Octpascal/items/07e53bd89dfbca93bf3e

CSVのファイルに記述した予定でGoogleカレンダーを更新するプログラムを作りました。
現状、CSV⇒Googleカレンダーの一方向です。

CSVファイルは前回と変わらず、以下の記述

id,cid,summary,location,description,start,end
  • id : CSV内で識別用ID(str)
  • cid : カレンダーに登録したときにGoogleカレンダー上で決定されるID(str)
  • summary : イベント名(str)
  • location : 開催場所(str)
  • description : 詳細(str)
  • start : 開始時間(str 例:2020/08/22 09:00 もしくは 2020/08/22)
  • end : 終了時間(str 開始時刻と同じ記述。ただし、揃えなければならない)

Googleカレンダー操作用コード

from __future__ import print_function
import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/calendar']
CID = 'Calendar ID'

def check_token():
    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    service = build('calendar', 'v3', credentials=creds)
    return service

def insert(event):
    service = check_token()
    # Call the Calendar API
    create_event = service.events().insert(calendarId=CID, body=event).execute()
    return create_event['id']

def get_lists():
    service = check_token()

    page_token = None
    events = []
    while True:
        events_list = service.events().list(calendarId=CID, pageToken=page_token).execute()
        page_token = events_list.get('nextPageToken')
        events.extend(events_list['items'])
        if not page_token:
            break
    return events

def update(eid, event):
    service = check_token()
    updated_event = service.events().update(calendarId=CID, eventId=eid, body=event).execute()
    return (updated_event['updated'])

def delete(eid):
    service = check_token()
    service.events().delete(calendarId=CID, eventId=eid).execute()

CIDには自身のカレンダーIDを入れてください。

check_token

GoogleカレンダーAPIのサンプルコードから。APIの認証用。

insert

イベント登録。辞書型を受け取り投げる。

get_list

既に登録されているイベントの取得。一度に取得できるリスト数に制限があるが、nextPageTokenを送ることで続きのリストを取得できる。

update, delete

そのままの用途

CSVをAPI用に変換するプログラム

import pandas as pd
import datetime
import gg_calendar
import sys
TIMEZONE = 'Japan'

def TransTime(t):
    if len(t) > 11:
        strtime = datetime.datetime.strptime(t, '%Y/%m/%d %H:%M').isoformat()
        dictime = {
            'dateTime': strtime,
            'timeZone': TIMEZONE
        }
    else :
        strtime = datetime.datetime.strptime(t, '%Y/%m/%d').isoformat()
        dictime = {
            'date': strtime[0:10],
            'timeZone': TIMEZONE
        }
    return dictime

def get_list():
    lists = gg_calendar.get_lists()
    new_lists = []
    for event in lists :
        new_lists.append(
            {
                'id': event['id'],
                'summary': event['summary'],
                'location': event['location'],
                'description': event['description'],
                'start': event['start'],
                'end': event['end']
            }
        )
    return new_lists

def search_id(dics, skey, svalue):
    df = pd.DataFrame(dics)
    return (df[skey] == svalue).any()

csv = pd.read_csv('csv.csv')
csv_d = csv.to_dict(orient='index')

ex_lists = get_list()

# delete
for ex_list in ex_lists:
    if not search_id(csv, 'cid', ex_list['id']):
        print("delete from calendar id={}".format(ex_list['id']))
        gg_calendar.delete(ex_list['id'])

# update, insert
for h, num in zip(csv_d.values(), csv_d.keys()):
    h['start'] = TransTime(h['start'])
    h['end']   = TransTime(h['end'])

    if (search_id(ex_lists, 'id', h['cid'])):
        del h['id']
        cid = h['cid']
        del h['cid']
        print('updata')
        gg_calendar.update(cid, h)
    else:
        del h['id']
        del h['cid']
        try:
            event_id = gg_calendar.insert(h)
        except:
            print("Error: Can't put id={} on your calendar".format(csv.loc[num, 'id']))
        else:
            print("Put id={} on your calendar as calendar_id={}".format(csv.loc[num, 'id'], event_id))
            csv.loc[num, 'cid'] = event_id

csv.to_csv('csv.csv',index=False)

TransTime

上記CSVの書式をGoogleカレンダーAPIの書式へ変換。詳細は前回記事参照。

get_lsit

リストを取得し必要な情報だけの辞書型にする。

search_id

辞書に値があるかどうか判定する
ある辞書型(dics)の辞書キー(skey)にある値(svalue)があるかどうか判定する。
辞書型をpandas DataFrameに変換する。
df[skey] == svalueでBooleanのpandas Seriesが返ってくる。(例:True, False, False, False)
any()関数でひとつでもTrueがあればTrueとなることで判定できる。

最初は辞書型のままfor文を回していたが、多分これが一番簡単に記述できます。

delete

CSVから消えたものはカレンダーから消します。

update, insert

既にカレンダーに登録されているものは更新。ないものは登録し、返ってきたイベントIDをCSVに追記します。

現状バグ

  • カレンダーにひとつも登録がないとエラーが発生する。
  • カレンダー内の登録がget_list関数で作成する辞書型の十分条件。例えば、カレンダーにlocationのないイベントがあるとエラーが発生する。⇒現状、id以外利用してないので取得しないことにすると解決する。

改善すべき点

  • 既存の登録されているカレンダーをすべてupdateに投げている。⇒終了時刻が一定以上前のデータは更新しないなど対策する。
  • カレンダーの既存イベントを全取得している、⇒ こちらもある程度期限を決めて取得することにする。

さいごに

次回は上記点を改善しアップデートしていきたい。

2
5
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
5