複数のGoogleカレンダーから1つのNotionのカレンダーにスケジュールを同期するバッチ（追加・削除）

:::note info
削除機能に対応しました。Googleカレンダーから削除された予定、また会議の予定を「不参加」にした場合は、Notionからも予定が削除されます。（2025/02/21 修正）
:::

:::note info
処理方法を見直したので全体的に修正しました。（2022/04/26 修正）
:::

https://github.com/BiaHoi-BaChien/SyncMultiGoogleCalendarToOneNotionDatabase

# 環境
- Laravel8.69.0
- PHP8

# 前準備
## 1. Laravelのインストール
## 2. 以下のパッケージをインストール<br>
~~laravel-notion-api（ https://github.com/5am-code/laravel-notion-api ）~~

:::note info
laravel-notion-apiの利用を停止しました。 （2025/02/21 修正）
:::
google-api-php-cient（ https://github.com/googleapis/google-api-php-client ）

## 3. 同期したいGoogleカレンダーのカレンダーIDを調べる
![20211119132243.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2272731/88af54d6-aafa-c11c-7366-d99e1cef8bb1.png)
カレンダー＞カレンダーの設定＞カレンダーID
## 4. Google Calendar APIの準備<br>
https://liginc.co.jp/472637 を参考にして Google Calendar APIからGoogleカレンダーが参照できるようにする。
## 5. NotionAPIの準備<br>
https://tektektech.com/notion-api/ を参考にして同期したいNotionのカレンダーがAPIから操作できるようにする。
## 6. 同期させたいNotionカレンダーのデータベースIDを調べる<br>
https://zenn.dev/5t111111/articles/785fec8d21d82b を参考にしました。
## 7. Notion側のカレンダーに「googleCalendarId」というテキストのプロパティを追加
![20211119142251.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2272731/c497fb22-5be9-9aa0-eb71-51bf0df2f647.png)


# Laravelでバッチを作成
https://codelikes.com/laravel-batch-command/ を参考にしました。

## 1. .envに設定を追加<br>

```shell:.env
NOTION_API_TOKEN=secret_xxxxxxxxxxxxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxx

TIMEZONE=Asia/Tokyo
```
NotionのAPIトークンとカレンダーのデータベースIDを設定。
またGoogleカレンダーのタイムゾーンを設定しておく。

## 2. config/app.php

```php:config/app.php
     /*
    |--------------------------------------------------------------------------
    | アプリ独自の定義
    |--------------------------------------------------------------------------
    |
    | デフォルトではない、後付けの定義
    |
    */
    'notion_database_id_of_calendar' => env('NOTION_DATABASE_ID', null),
```
.envファイルよりNotionカレンダーのデータベースIDを利用できるする。

## 3.構造体の準備

```php:Console/Commands/GoogleCalendarSyncNotion.php
        $this->google_calendar_list = array (
            'personal' => array (
                'calendar_id' => (string)config('app.google_calendar_id_personal'),
                'notion_label' => '生活',
            ),
            'business' => array(
                'calendar_id' => (string)config('app.google_calendar_id_business'),
                'notion_label' => '仕事',
            ),
            'school' => array(
                'calendar_id' => (string)config('app.google_calendar_id_school'),
                'notion_label' => '学校',
            )
        );

        $this->google_holiday_calendar_list = array (
            'holiday' => array (
                'calendar_id' => (string)config('app.google_calendar_id_holiday'),
                'notion_label' => '祝日',
            )
        );
```
今回は個人用、仕事用、子供の学校用、日本の祝日の３つのカレンダーを同期させるため上記定数を準備する。
「notion_label」はNotionカレンダーに同期した際にラベル付けしたい文字列。

:::note info
holidayモードを追加し、休日カレンダーは別スケジュールで実行されるようにする。
:::
```php:Console/Commands/GoogleCalendarSyncNotion.php
    public function handle()
    {
        // Mode
        if( $this->argument('mode') === 'holiday'){
            $calendar_list = $this->google_holiday_calendar_list;
        }else{
            $calendar_list = $this->google_calendar_list;
        }
```


## 4. メインロジック

```php:Console/Commands/GoogleCalendarSyncNotion.php

        // 設定値(sync_max_days)に従い日数分のデータをSyncさせる
        $targetDateStart = (string)date("Y-m-d");
        $targetDateEnd = (string)date("Y-m-d", strtotime('+'. config('app.sync_max_days') .'day'));
```
設定値(SYNC_MAX_DAYS)に従って本日より何日分の予定を同期させるかを決定。

```php:Console/Commands/GoogleCalendarSyncNotion.php
        // Google Calenterから指定日のデータを取得
        foreach (array_keys($calendar_list) as $key){
            if(is_null($calendar_list[$key]['calendar_id'])){
                continue;
            }                

            $events = array();  
            $googlecal = new GoogleCalendarModel;

            try{
                $events = $googlecal->getGoogleCalendarEventList( $targetDateStart, $targetDateEnd, $calendar_list[$key]['calendar_id']);
            }catch (\Exception $e){
                report($e);
                return Command::FAILURE;
            };
```
カレンダー毎に予定されているイベントを取得。
        

```php:Console/Commands/GoogleCalendarSyncNotion.php

            foreach ($events as $event) {

                // このイベントがNotionに登録されているか検索
                try{
                    $collections = $notions->getCollectionsFromNotion($event->id);
                }catch(\Exception $e){
                    report($e);
                    return Command::FAILURE;
                }

                // 既にNotionに登録されているのでスルー
                if (count($collections)) {
                    continue;
                }

                // Notionに登録
                try{
                    $notions->registNotion($event, $calendar_list[$key]['notion_label']); // insert
                }catch(\Exception $e){
                    report($e);
                    return Command::FAILURE;
                }
            } 
```
イベントが既にNotionに登録されているかどうかを判断。Notion側に設定したテキストプロパティにGoogle Calendar Idを入れておいてその値で判断します。
未登録だったらNotionデータベースに追加。

## 5.Googleカレンダー用のロジック

```php:Models/GoogleCalendarModle.php
    public function getGoogleCalendarEventList(string $targetDateStart, string $targetDateEnd, string $google_calendar_id)
    {
        // Googleカレンダーに登録されているデータを取得
        $client = $this->getClient();
        $service = new Google_Service_Calendar($client);

        $optParams = array(
            'maxResults' => 50,
            'orderBy' => 'startTime',
            'singleEvents' => true,
            'timeMin' => date('c', strtotime($targetDateStart ." 00:00:00")),
            'timeMax' => date('c', strtotime($targetDateEnd ." 23:59:59")), 
            'timeZone' => config('app.timezone'),
        );

        $results = $service->events->listEvents($google_calendar_id, $optParams);
        
        return $results->getItems();
    }
```
Googleカレンダーに登録されているデータを取得


## 6.Notionカレンダー用のロジック

```php:Models/NotionModel.php
    public function getCollectionsFromNotion(string $googleCalendarId)
    {
        $notion = new Notion((string)config('app.notion_api_token'));

        // 現在Notionに登録されているデータを取得
        $sortings = new Collection();
        $filters = new Collection();
        $filters->add(
            Filter::textFilter("googleCalendarId", Operators::EQUALS, $googleCalendarId), 
        );
        $collections = $notion->database(config('app.notion_database_id_of_calendar'))
            ->filterBy($filters) // filters are optional
            ->query()
            ->asCollection();

        return $collections;
    }
```
テキストプロパティのGoogle Calendar Idをキーにしてデータを取得する。


## 7. バッチ登録

```php:Console/Kernel.php
        $schedule->command('command:gcal-sync-notion')
            ->everyThirtyMinutes()
            ->timezone('Asia/Ho_Chi_Minh')
            ->between('6:00', '23:00');

        $schedule->command('command:gcal-sync-notion holiday')
            ->monthly()
            ->timezone('Asia/Ho_Chi_Minh');
```
とりあえず30分に一回動かす。夜中は動かなくていいやと思ったので6時～23時まで。
スケジュールは複数登録できることを知ったので 「holiday」モードを追加し休日カレンダーは1カ月に1回起動するように変更。



```shell:crontab
* * * * *	/usr/bin/php artisan schedule:run
```

![20211119152247.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2272731/bf7ba131-bd33-2638-b983-c430e7c28d74.png)
こんな感じでそれぞれのカレンダーからイベントがラベル付きで追加されるようになりました。（アイコンはあとから手作業で追加してます）


## 8. 終わり
他のサービスを利用すれば同じようなことできそうですが、Laravelを勉強したかったので苦労しながら作ってみました。
Notionにデータを登録する際に（特に日本の祝日の場合は）アイコンを指定したかったけど今回使用したパッケージではまだできなかったようなので、Notionにデータ同期された後に手作業でアイコン付けてます。

~~https://github.com/BiaHoi-BaChien/multi-googlecalendar-sync-to-one-notioncalendar~~

~~GitHubでソースコードを公開しました。（2021/12/03 修正）~~