LoginSignup
1
0

More than 1 year has passed since last update.

Goを使ってNotionカレンダーからGoogleカレンダーへデータを移動させてみる

Posted at

はじめに

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を使ってざっくりとシーケンス図を作ってみました。
この図に従って実装を行いました。
NotionToCalender.png

作成コード

全体のコードは以下GitHubリポジトリ参照
https://github.com/aki537/notion-to-google-calender

main.go
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)
	}
}
controller/notion
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
}
controller/calendar
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でもやってみようかなと思ってます。

1
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
1
0