Googleカレンダーを読み書きするAPIをGASで作ってGPTsのCustom Actionsから呼び出してみる

## はじめに
こんにちは、ほたるです。

Google Apps ScriptでAPIをつくり、GPTsから呼び出す方法について投稿しました。

https://qiita.com/iconss/items/3db0761bb4cfe1422a6e


この記事では、さらに複雑な処理にトライします。

- Googleカレンダーに予定を追加
- Googleカレンダーから1日分の予約を取得
- 上記の２つの処理を一つのGPTsで会話文から判断して呼び分ける

今回は趣向を変えて、GoogleスプレッドシートではなくGoogleカレンダーを扱ってみました。

### この記事の対象者
- GPTsのCustom ActionsのAPIを自作したい方
- Google Apps Scriptで簡単なスクリプトを作ったことがある方
- Zapierの連携よりも複雑な処理を求めている方

## APIの概要
二つのGoogle Apps Scriptを用意します。
- カレンダーに予定を追加するスクリプト
- カレンダーから1日分の予定を取得するスクリプト

![スクリーンショット 2023-11-16 14.42.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/e27d76cf-c1cf-94f7-9efa-83e4f8082646.png)

GPTsが会話文を解析して、二つの異なるスクリプトを呼び分けします。

## Google Apps Scriptを作成
Google Apps Scriptにアクセスします。

https://script.google.com/home

![スクリーンショット 2023-11-16 8.04.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/472280f0-1b76-a8f8-6e4b-da02f9e37523.png)

Apps Scriptのエディタが開きます。
![スクリーンショット 2023-11-13 9.17.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/e3deeed0-47e4-fddd-405a-85fc2dbe6546.png)

- カレンダーに予定を追加するスクリプト
- カレンダーから1日分の予定を取得するスクリプト
の2つの別々のプロジェクト（スクリプト）を作成します。

### カレンダーに予定を追加するスクリプト
~~~Javascript:AddSchedule.gs
// Googleカレンダーに予定を追加
function doPost(e) {
  try {
    console.log("start GPTs Custom Actions API Google Calendar");

    // リクエストからパラメータを取得
    console.log("e: " + JSON.stringify(e));
    var startDate = e.parameter.Date;
    var durationHours = parseInt(e.parameter.Hours, 10);
    var title = e.parameter.Title;
    console.log("e.parameter.Date: " + e.parameter.Date);
    console.log("e.parameter.Hours: " + e.parameter.Hours);
    console.log("e.parameter.Title: " + e.parameter.Title);

    // 開始日時の解析
    var year = startDate.substring(0, 4);
    var month = startDate.substring(4, 6) - 1; // JavaScriptの月は0から始まるため
    var day = startDate.substring(6, 8);
    var hour = startDate.substring(9, 11);
    var minute = startDate.substring(11, 13);
    console.log(`Year: ${year}, Month: ${month + 1}, Day: ${day}, Hour: ${hour}, Minute: ${minute}`);

    // Dateオブジェクトの作成
    var start = new Date(year, month, day, hour, minute);
    var end = new Date(start.getTime());
    end.setHours(start.getHours() + durationHours);

    // イベントの作成
    var calendar = CalendarApp.getDefaultCalendar();
    calendar.createEvent(title, start, end);

    // 成功メッセージの返送
    console.error("success");
    return ContentService.createTextOutput("success");

  } catch (error) {
    // エラー発生時の処理
    console.error("Error adding event: " + error.message);
    return ContentService.createTextOutput("Error: " + error.message);
  }
}

~~~

### カレンダーから1日分の予定を取得するスクリプト
~~~Javascript:GetSchedule.gs
// Googleカレンダーから指定した日付の予定を取得
function doGet(e) {
  try {
    console.log("start Custom Actions GetSchedule");

    // リクエストからパラメータを取得
    console.log("e: " + JSON.stringify(e));
    var dateString = e.parameter.Day;
    console.log("e.parameter.Day: " + e.parameter.Day);

    // 日付の文字列から年、月、日を取得
    var year = parseInt(dateString.substring(0, 4), 10);
    var month = parseInt(dateString.substring(4, 6), 10) - 1; // JavaScriptの月は0から始まる
    var day = parseInt(dateString.substring(6, 8), 10);

    // 指定された日付の始まりと終わりを示すDateオブジェクトを作成
    var startTime = new Date(year, month, day);
    var endTime = new Date(year, month, day + 1);

    // Googleカレンダーからイベントを取得
    var calendar = CalendarApp.getDefaultCalendar();
    var events = calendar.getEvents(startTime, endTime);

    // イベントの情報を含むオブジェクトの配列を作成
    var eventsData = events.map(function(event) {
      return {
        'title': event.getTitle(),
        'startTime': event.getStartTime().toISOString() // ISO 8601形式の日付文字列
      };
    });

    // JSON形式で返す
    console.log("eventsData: " + eventsData);
    return ContentService.createTextOutput(
      JSON.stringify(eventsData)
    ).setMimeType(ContentService.MimeType.JSON);
  } catch (error) {
    // エラー発生時の処理
    console.error("Error adding event: " + error.message);
    return ContentService.createTextOutput("Error: " + error.message);
  }
}
~~~

## Google Calendar APIを有効にする
Google Apps ScriptからGoogleカレンダーを読み書きするには、スクリプトでGoogle Calendar APIを有効にする必要があります。
2つのスクリプト両方に対して設定します。
![スクリーンショット 2023-11-16 8.32.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/9071aae3-c4e4-59f0-06d2-75c801a3d845.png)
![スクリーンショット 2023-11-16 8.32.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/51c82b9c-1dc7-fc8a-42cd-745242b4fa57.png)

## Google Apps Scriptをデプロイ
2つのスクリプト両方に対してデプロイします。
スクリプトに対して初めてデプロイする方法はこちらを参照。

https://qiita.com/iconss/items/3db0761bb4cfe1422a6e#google-apps-script%E3%82%92%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4

デプロイ完了した時のデプロイのURLを2つ分ひかえておきます。
→この後で使用します。

スクリプトを編集した場合は、こちらの手順でデプロイします。

https://qiita.com/iconss/items/3db0761bb4cfe1422a6e#%E8%A3%9C%E8%B6%B3

## GPTsを作成
特に設定はしません。
Instructionsはお好みで。
![スクリーンショット 2023-11-16 10.21.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/7d600cf5-a4ae-8091-270b-056010af9535.png)

## GPTsのCustom Actionsを設定
![スクリーンショット 2023-11-13 7.13.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/1072893e-8bcc-61aa-b843-2b5d7e3c5996.png)

Schemaにこちらをコピペします。
コピペしたら、前の章でコピーしておいた、デプロイしたURLの一部（/macros以下）を貼り付けます。
今回は、関数2つ分、貼り付けますよ。

例えばデプロイしたURLが

>https://script.google.com/macros/s/AKfycbzR2GGFg_9Ilnv55jzZDBQ3J_miH_OIQUn3qDJpPDBWSlnw-OT6H6rRZhkJ57EIijUYEQ/exec

の場合

>/macros/s/AKfycbzR2GGFg_9Ilnv55jzZDBQ3J_miH_OIQUn3qDJpPDBWSlnw-OT6H6rRZhkJ57EIijUYEQ/exec

の部分を貼り付けます。
~~~JSON:Schema
{
  "openapi": "3.1.0",
  "info": {
    "title": "Google Calendar Operations",
    "description": "Operations for Google Calendar",
    "version": "v1.0.0"
  },
  "servers": [
    {
      "url": "https://script.google.com"
    }
  ],
  "paths": {
    "ここにカレンダーに予定を追加するスクリプトのデプロイしたURLの /macros以下 のアドレスを設定": {
      "post": {
        "description": "Googleカレンダーに予定を追加します",
        "operationId": "AddSchedule",
        "parameters": [
          {
            "name": "Date",
            "in": "query",
            "description": "日付と時刻を指定 時刻は24時間制 例)2023年8月15日13時5分 : 20230815-1305",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Hours",
            "in": "query",
            "description": "時間数を指定 例)1時間 : 1、30分 : 0.5",
            "required": true,
            "schema": {
              "type": "number",
              "format": "float"
            }
          },
          {
            "name": "Title",
            "in": "query",
            "description": "予定に追加するタイトルを指定",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        },
        "deprecated": false
      }
    },
    "ここにカレンダーから1日分の予定を取得するスクリプトのデプロイしたURLの /macros以下 のアドレスを設定": {
      "get": {
        "description": "Googleカレンダーから1日の予定を取得します",
        "operationId": "GetSchedule",
        "parameters": [
          {
            "name": "Day",
            "in": "query",
            "description": "日付を指定 例)2023年4月15日 : 20230415",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          }
        },
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {}
  }
}

~~~
Available actionsのところに、二つの関数が表示されていることを確認します。
![スクリーンショット 2023-11-16 12.26.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/6d83128a-3d4b-3740-1739-12505cb54c13.png)

2023.11.16現在、Available actionsのTestボタンを押すと、その関数の通信テストができるようになっています。
右側のPrevewに、実際にAPIと通信したログが表示されます。
Schemaの設定に基づいていい感じにテストデータを生成して、APIと通信してテストしてくれるので助かる！

## ためしてみる
![スクリーンショット 2023-11-16 10.13.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/62cfdb2b-a680-93c5-19f5-175cfad145c4.png)

11月16日午後4時に「移動時間」が追加されました。
![スクリーンショット 2023-11-16 10.14.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/8393cbcc-1acd-04ab-fa3b-5de414f55fc0.png)

複数の予定も一度に取得してくれます。
![スクリーンショット 2023-11-16 12.45.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/cf58a42a-a424-1ca4-e2a5-3458b543c904.png)

![スクリーンショット 2023-11-16 12.24.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/a96c29a0-9f5e-e9bf-e042-60126e9646e2.png)
日本時間でなく世界標準時で回答してくることもあるので、要注意。
ここはInstructionsにプロンプトを設定すれば、なんとかなるかな。
![スクリーンショット 2023-11-16 12.24.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496109/4f8fc380-66b1-ea3c-d8cd-858e7c30b0dc.png)


## Custom ActionsがあるGPTsの公開について
ご参考までに、一般公開されることを考えている方は、こちらを参照願います。

https://qiita.com/iconss/items/3db0761bb4cfe1422a6e#custom-actions%E3%81%8C%E3%81%82%E3%82%8Bgpts%E3%81%AE%E5%85%AC%E9%96%8B%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

## 考察
### スクリプトについて
今回のスクリプトは、GoogleアカウントのデフォルトのGoogleカレンダーに予定を追加したり、予定を取得したりしました。
もし任意のカレンダーを操作したい場合は、適当にスクリプトを改造してみて下さい。
Javascriptがわからない場合でも、ChatGPT先生がスクリプトを作ってくれますよ。

### Custom Actionsの設定について
Schemaで指定するAPIのエンドポイントのURLは処理ごとに別々に指定する必要があります。
なのでスクリプトが2つに分かれています。
1つのスクリプトにカレンダーに追加する処理とカレンダーを取得する処理を入れてみたりしましたが、うまく動きませんでした。
処理をかけて別のスクリプトにした方が手間は増えるかもしれませんが、わかりやすいかなと考えます。

### テストボタンについて
このところはGPTsの画面や機能がどんどん改善されていくのを体感していく毎日です。
OpenAIの開発スピードに驚くばかりですね。
この機能は本当に便利で、APIの開発がはかどりました。助かりました。

### Actionsについて
会話文を判断して、うまく2種類の処理を行ってもらうことに成功しました。
Scemaの設定次第では、3つ4つとかいけるでしょうね。
とは言え、処理の種類が多くなると、意地悪な会話文の内容だとうまく判断してくれない場合もあるかと考えられます。
例えば、お互いの処理自体の定義が意味的にかぶったりすると、GPTsがうまく会話文を判断できなくなるでしょう。
なので、どういう処理を行ってもらうかの仕様の検討から考えることがミソですね。

## おわりに
Custom ActionsのSchema


この記事を参考にご自身でCustom ActionsのAPIを開発する際の一助になれば、幸いです。

最後までご精読いただき、ありがとうございました。

## 参考資料

https://qiita.com/iconss/items/3db0761bb4cfe1422a6e