はじめに
NotionのカレンダーとGoogleカレンダーを両方つかっていましたが、
2つとも管理するのが煩わしくなってきたので、Googleカレンダーに統一しようと思いました。
そこでNotionAPIとGoogleCalendarAPIを使って、Notionカレンダー→Googleカレンダーへ
データを移動するプログラムをGoで作ってみました。
それぞれのAPIの始め方
ひとまず両方ともAPIを触ったことがなかったので、公式のスタートガイドから概要を学びました。
スタートガイドとリファレンスについては下のリンクになります。
詳しいガイドの内容については割愛させて頂きます。
NotionAPI
NotioAPIスタートガイド
NotionAPIリファレンス
GoogleCalendarAPI
GoogleCalendarAPIクイックスタート
GoogleCalendarAPIリファレンス
Goドキュメント
GoogleCalendarAPIを使う際、OAuth認証のトークン取得に少し手間取ったので以下のqita記事を参考にしました、
Google API OAuth2.0のアクセストークン&リフレッシュトークン取得手順 2017年2月版
実装
やりたい事
- 開始日と終了日を決めて範囲内のデータを移動させる
- すでにGoogleカレンダーに同名の項目が登録済みならば更新する
簡単な設計
最初にPlantUMLを使ってざっくりとシーケンス図を作ってみました。
この図に従って実装を行いました。
作成コード
全体のコードは以下GitHubリポジトリ参照
https://github.com/aki537/notion-to-google-calender
package main
import (
"NotionToGoogleCalender/controller"
"log"
)
const (
// startとendに指定された間のカレンダーを登録する
Start = "2021-10-01"
End = "2022-03-1"
)
func main() {
// notionの情報を取得
notionList, err := controller.GetNotionCalender(Start, End)
if err != nil {
log.Fatalf("Failed GetNotionCalender: %v", err)
}
// 取得した情報をgoogleカレンダーに登録
err = controller.PutGoogleCalender(notionList)
if err != nil {
log.Fatalf("Failed PutGoogleCalender: %v", err)
}
}
package controller
import (
"NotionToGoogleCalender/api"
"fmt"
"log"
"time"
)
const (
timeFormat = "2006-01-02"
)
// GetNotionCalender は、notionカレンダーから情報を取得して構造体を返します。
func GetNotionCalender(start, end string) ([]*AddCalender, error) {
addCalenderList := []*AddCalender{}
notionList := []*api.Result{}
notion, err := api.NewNotion()
if err != nil {
return nil, err
}
rangeList := getSplitRange(start, end, []*Range{})
// 2ヶ月ごとにページのリストを取得
for _, item := range rangeList {
reqBody, err := api.NewCalenderReqBody(item.Start, item.End)
if err != nil {
return nil, err
}
res, err := notion.FetchPageList(reqBody)
if err != nil {
return nil, err
}
notionList = append(notionList, res.Results...)
}
// ページの子ブロックを取得して構造体に追加
for _, item := range notionList {
childblock, err := notion.FetchChildrenBlock(item.ID)
if err != nil {
return nil, err
}
// 子ブロックのリストを変換して追加
addCalender := toAddCalender(childblock.Results, item.Properties.Date.Date.Start)
addCalenderList = append(addCalenderList, addCalender)
}
log.Printf("追加用構造体に変換完了 全%d件", len(addCalenderList))
return addCalenderList, nil
}
// getSplitRangeは、取得したい日付範囲を2ヶ月ごとに分割して再帰的に取得します
func getSplitRange(start, end string, list []*Range) []*Range {
startDate, _ := time.Parse(timeFormat, start)
endDate, _ := time.Parse(timeFormat, end)
// 開始日より2ヶ月プラスした日時
addStartDate := startDate.AddDate(0, 2, 0)
// 開始日より2ヶ月プラスした日時 < 終了日となっているか
ok := addStartDate.Before(endDate)
if ok {
addStart := addStartDate.Format(timeFormat)
list = append(list, &Range{Start: start, End: addStart})
list = getSplitRange(addStart, end, list)
} else {
list = append(list, &Range{Start: start, End: end})
}
return list
}
// toAddCalender は、取得した子ブロックをgoogleカレンダー登録用の構造体に変換します。
func toAddCalender(list []*api.Result, date string) *AddCalender {
result := &AddCalender{}
// 追加する日付
result.Date = date
// タイトル
dateTime, _ := time.Parse(timeFormat, date)
result.Title = fmt.Sprintf("Diary %d/%d/%d", dateTime.Year(), dateTime.Month(), dateTime.Day())
// 本文
body := ""
for _, item := range list {
for _, text := range item.Paragraph.RichText {
body += text.PlainText + "\n"
}
}
result.Body = body
return result
}
package controller
import (
"NotionToGoogleCalender/api"
"log"
"google.golang.org/api/calendar/v3"
)
// PutGoogleCalender は、notionのカレンダーをgooglecalenderに登録します
// 登録済みの場合は更新します。
func PutGoogleCalender(addCalenderList []*AddCalender) error {
calender, err := api.NewGoogleCalendar()
if err != nil {
return err
}
// notionのカレンダーをgooglecalenderに登録
for _, item := range addCalenderList {
log.Printf("カレンダー登録日:%s", item.Date)
event := &calendar.Event{
Summary: item.Title,
Description: item.Body,
Start: &calendar.EventDateTime{
Date: item.Date,
},
End: &calendar.EventDateTime{
Date: item.Date,
},
ColorId: "2",
Reminders: &calendar.EventReminders{
UseDefault: false,
},
}
// その日のイベントを取得し、同じタイトルがある場合は更新、無い場合は新しく挿入する
dateList, err := calender.FetchDateEvents(item.Date)
if err != nil {
return err
}
eventId, ok := getEventID(dateList.Items, item.Title)
if ok {
log.Printf("カレンダー挿入 %s", event.Summary)
err = calender.UpdateEvent(eventId, event)
if err != nil {
return err
}
} else {
log.Printf("カレンダー更新 %s", event.Summary)
err = calender.InsertEvent(event)
if err != nil {
return err
}
}
}
return nil
}
// getEventID は、引数に渡したタイトルがイベント内にある場合にeventIDを返却します
func getEventID(eventItems []*calendar.Event, addTitle string) (string, bool) {
for _, item := range eventItems {
if item.Summary == addTitle {
return item.Id, true
}
}
return "", false
}
さいごに
それぞれのAPIの仕様の把握や使いはじめに時間がかかりましたが、実装自体は
思っていたより簡単にできました。
今度GASを触ってみたいなあと思っているので、今回の実装をGASでもやってみようかなと思ってます。