技術系

Google Calenderに登録する

技術系

こんにちは、なかにしです。

最近、「Google Calendarに登録」というボタンを含んだアプリをよく見かけます。

便利だなーと思ったので、作成してみようと思います!

今回は、CRUDを実装していきます。
1. Google Calendarから予定を取得
2. Google Calendarに予定を登録
3. Google Calendarの予定を更新
4. Google Calendarの予定を削除

準備

Google Calendarを操作するには、
「Googleへのログイン」と「Google Calendarの使用許可」が必要です。

基本的な流れとして、ユーザー自身でGoogleへログインし、Google Calendarの使用許可をしてもらいます。

すると「アクセストークン」が取得できるので、そのアクセストークンを使用し、Google Calendar apiを叩いてGoogle Calendarを操作する、といった流れです。

準備事項がいくつかあるので、まずはそこからやっていきます。

プロジェクト作成

calendar Google Cloud でプロジェクトを作成します。

Google Cloud Console へアクセスし、プロジェクトを作成します。

プロジェクトの作成は、以下をご参照ください。
Google Cloudのプロジェクト作成方法とアクセス権の付与方法をご紹介

Google Calendar API を有効化

Google Cloud Console から、Google Calendar APIを有効にします。

以下が参考になります。(手順の4まで)
Google Calendar APIを取得する方法

認証情報の作成(OAuth同意画面)

「OAuth同意画面」を押下

いくつか必須項目があるので、設定していきます。

▽ まずは基本情報

「次へ」ボタンを押下します。

▽ 続いてスコープ設定

「スコープを追加または削除」ボタンを押下します。

▽ 右側の一覧から、許可したいGoogle Calenderの権限を選びます。

「追加や削除」や、「ReadOnly」などから、やりたいことに合ったものを選びます。
今回はCRUDをやりたいので、全て許可する「…/auth/calendar」を範囲とするものを選びました。

▽ 「次へ」を押すと、テストユーザーを選ぶ画面へ遷移

先ほど登録したホームページの情報などをGoogle側が審査し、OKだったら「公開」というステータスになります。

それまでは「テストユーザー」しかアクセスができない為、テストする人のメールアドレスをここで登録しておきます。

今回は自分のみがテストするので、自分のメールアドレスを登録しました。

認証情報の作成(認証情報)

▽ 続いて、認証情報を作成します。

▽ 認証情報を作成 ボタンを押下

▽ OAuthクライアントID を押下

▽ 承認済みの JavaScript生成元 と承認済みのリダイレクトURIを設定

今回はローカルホストから叩いて検証するので、上記のように設定しました。
※ http://localhost:5173 のみだとリクエストが拒否されるので注意!!

ローカルホスト以外で検証する際には、URLを変更してください。

保存して、終了です。
生成された クライアントID は後ほど使用します。

実装

Reactで実装していきます。

共通部分

▽ ライブラリの読み込み

  useEffect(() => {
    const script = document.createElement('script');
    script.src = "https://accounts.google.com/gsi/client";
    script.onload = initClient;
    script.async = true;
    script.defer = true;
    document.body.appendChild(script);
  }, []);

ライブラリは scriptタグでのみ提供されています。

その為、
・index.htmlに記載
・JSのDOM操作でindex.htmlに入れるか
のどちらかで読み込ませます。

上記の例はJSのDOM操作をしています。

onloadで、読み込みの際に初期化処理を指定します。
処理内容は以下です。

▽ 初期化

  const CLIENT_ID = import.meta.env.VITE_CLIENT_ID  // ここは「準備」フェーズで取得したクライアントID
  const SCOPE = 'https://www.googleapis.com/auth/calendar' // ここは「OAuth同意画面」の作成時に設定したスコープ

  /** 初期化 */
  const initClient = () => {
    const initTokenClient = window.google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPE,
      callback: (tokenResponse: any) => {
        setAccessToken(tokenResponse.access_token);
      },
    });
    setClient(initTokenClient);
  };

GoogleのAPIを使用する為にはトークンが必要です。
それを取得する処理も実装します。

▽ アクセストークン取得

  /** アクセストークン取得 */
  const getToken = () => {
    client.requestAccessToken();
  };

▽ アクセストークン削除

  /** アクセストークン削除 */
  const revokeToken = () => {
    window.google.accounts.oauth2.revoke(accessToken, () => {
      console.log('access token revoked');
    });
  };

▽ 上記をまとめると、以下になります。

import { useEffect, useState } from 'react';
import './App.css';

const App = () => {
  const CLIENT_ID = import.meta.env.VITE_CLIENT_ID
  const SCOPE = 'https://www.googleapis.com/auth/calendar';

  const [client, setClient] = useState<any>(null);
  const [accessToken, setAccessToken] = useState('');

  useEffect(() => {
    const script = document.createElement('script');
    script.src = "https://accounts.google.com/gsi/client";
    script.onload = initClient;
    script.async = true;
    script.defer = true;
    document.body.appendChild(script);
  }, []);

  /** 初期化 */
  const initClient = () => {
    const initTokenClient = window.google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPE,
      callback: (tokenResponse: any) => {
        console.log(tokenResponse)
        setAccessToken(tokenResponse.access_token);
      },
    });
    setClient(initTokenClient);
  };

  /** アクセストークン取得 */
  const getToken = () => {
    client.requestAccessToken();
  };

  /** アクセストークン削除 */
  const revokeToken = () => {
    window.google.accounts.oauth2.revoke(accessToken, () => {
      console.log('access token revoked');
    });
  };

  return (
    <div>
      <h1>カレンダーアプリ</h1>
      <button onClick={getToken}>アクセストークン取得</button><br /><br />
    </div>
  );
};

export default App;

カレンダー取得

ドキュメントは こちら です。
headerに取得したトークン情報を含めて google calendar apiを叩きます。

▽ カレンダー取得の処理

  /** カレンダーの取得 */
  const loadCalendar = () => {
    fetch("https://www.googleapis.com/calendar/v3/calendars/primary/events", {
      method: "GET",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }).then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.json();
    }).then((data: any) => {
      console.log("今後の予定: ", data.items)
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  };

▽ 共通処理 + 一覧取得 を併せたコード

import { useEffect, useState } from 'react';
import './App.css';

const App = () => {
  const CLIENT_ID = import.meta.env.VITE_CLIENT_ID
  const SCOPE = 'https://www.googleapis.com/auth/calendar';

  const [client, setClient] = useState<any>(null);
  const [accessToken, setAccessToken] = useState('');

  useEffect(() => {
    const script = document.createElement('script');
    script.src = "https://accounts.google.com/gsi/client";
    script.onload = initClient;
    script.async = true;
    script.defer = true;
    document.body.appendChild(script);
  }, []);

  /** 初期化 */
  const initClient = () => {
    const initTokenClient = window.google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPE,
      callback: (tokenResponse: any) => {
        console.log(tokenResponse)
        setAccessToken(tokenResponse.access_token);
      },
    });
    setClient(initTokenClient);
  };

  /** アクセストークン取得 */
  const getToken = () => {
    client.requestAccessToken();
  };

  /** アクセストークン削除 */
  const revokeToken = () => {
    window.google.accounts.oauth2.revoke(accessToken, () => {
      console.log('access token revoked');
    });
  };

  /** カレンダーの取得 */
  const loadCalendar = () => {
    fetch("https://www.googleapis.com/calendar/v3/calendars/primary/events", {
      method: "GET",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }).then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.json();
    }).then((data: any) => {
      console.log("今後の予定: ", data.items)
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  };

  return (
    <div>
      <h1>カレンダーアプリ</h1>
      <button onClick={getToken}>アクセストークン取得</button><br /><br />
      <button onClick={loadCalendar}>カレンダーの読み込み</button><br /><br />
      <button onClick={revokeToken}>トークン破棄</button>
    </div>
  );
};

export default App;

予定の登録

ドキュメントは こちら です。
headerに取得したトークン情報を含めて google calendar apiを叩きます。

▽ 予定の登録の処理

  // 登録する内容
  const registerResource =
  {
    kind: "calendar#event",
    etag: "イータグ",
    summary: "今作ったイベント",
    description: "No Limit!",
    location: "東京",
    start: {
      dateTime: '2024-01-01T09:00:00',
      timeZone: 'Asia/Tokyo'
    },
    end: {
      dateTime: '2024-01-01T17:00:00',
      timeZone: 'Asia/Tokyo'
    },
  }
  
  /** カレンダーの登録 */
  const registerCalendar = () => {
    fetch("https://www.googleapis.com/calendar/v3/calendars/primary/events", {
      method: "POST",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      },
      body: JSON.stringify(registerResource)
    }).then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.json();
    }).then((data: any) => {
      console.log("登録した予定: ", data)
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  };

▽ 共通処理 + 登録処理 を併せたコード

import { useEffect, useState } from 'react';
import './App.css';

const App = () => {
  const CLIENT_ID = import.meta.env.VITE_CLIENT_ID
  const SCOPE = 'https://www.googleapis.com/auth/calendar';

  const [client, setClient] = useState<any>(null);
  const [accessToken, setAccessToken] = useState('');

  useEffect(() => {
    const script = document.createElement('script');
    script.src = "https://accounts.google.com/gsi/client";
    script.onload = initClient;
    script.async = true;
    script.defer = true;
    document.body.appendChild(script);
  }, []);

  /** 初期化 */
  const initClient = () => {
    const initTokenClient = window.google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPE,
      callback: (tokenResponse: any) => {
        console.log(tokenResponse)
        setAccessToken(tokenResponse.access_token);
      },
    });
    setClient(initTokenClient);
  };

  /** アクセストークン取得 */
  const getToken = () => {
    if (!client) {
      console.log("clientがないです。")
      return;
    }

    client.requestAccessToken();
  };

  /** アクセストークン削除 */
  const revokeToken = () => {
    window.google.accounts.oauth2.revoke(accessToken, () => {
      console.log('access token revoked');
    });
  };

  // 登録する内容
  const registerResource =
  {
    kind: "calendar#event",
    etag: "イータグ",
    summary: "今作ったイベント",
    description: "No Limit!",
    location: "東京",
    start: {
      dateTime: '2024-01-01T09:00:00',
      timeZone: 'Asia/Tokyo'
    },
    end: {
      dateTime: '2024-01-01T17:00:00',
      timeZone: 'Asia/Tokyo'
    },
  }

  /** カレンダーの登録 */
  const registerCalendar = () => {
    fetch("https://www.googleapis.com/calendar/v3/calendars/primary/events", {
      method: "POST",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      },
      body: JSON.stringify(registerResource)
    }).then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.json();
    }).then((data: any) => {
      console.log("登録した予定: ", data)
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  };

  return (
    <div>
      <h1>カレンダーアプリ</h1>
      <button onClick={getToken}>アクセストークン取得</button><br /><br />
      <button onClick={registerCalendar}>カレンダーの登録</button><br /><br />
      <button onClick={revokeToken}>トークン破棄</button>
    </div>
  );
};

export default App;

予定の更新

ドキュメントは こちら です。
headerに取得したトークン情報を含めて google calendar apiを叩きます。

▽ カレンダーの更新処理

/** カレンダーの更新 */
  const updateCalendar = () => {

    // 更新する内容
    const registerResource =
    {
      kind: "calendar#event",
      etag: "イータグ",
      summary: "今作ったイベント",
      description: "No Way!!",
      location: "東京",
      start: {
        dateTime: '2024-01-01T09:00:00',
        timeZone: 'Asia/Tokyo'
      },
      end: {
        dateTime: '2024-01-01T18:00:00',
        timeZone: 'Asia/Tokyo'
      },
    }

    fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`, {
      method: "PUT",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      },
      body: JSON.stringify(registerResource)
    }).then((response) => {
      console.log(response);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.json();
    }).then((data: any) => {
      console.log("登録した予定: ", data)
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  };

${eventId} には、更新したいイベント(予定)のIDが入ります。
予定一覧を取得した際にも手に入りますし、登録した際のレスポンスでも手に入ります。

▽ 共通処理 + 更新処理 を併せたコード

import { useEffect, useState } from 'react';
import './App.css';

const App = () => {
  const CLIENT_ID = import.meta.env.VITE_CLIENT_ID
  const SCOPE = 'https://www.googleapis.com/auth/calendar';

  const [client, setClient] = useState<any>(null);
  const [accessToken, setAccessToken] = useState('');

  useEffect(() => {
    const script = document.createElement('script');
    script.src = "https://accounts.google.com/gsi/client";
    script.onload = initClient;
    script.async = true;
    script.defer = true;
    document.body.appendChild(script);
  }, []);

  /** 初期化 */
  const initClient = () => {
    const initTokenClient = window.google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPE,
      callback: (tokenResponse: any) => {
        console.log(tokenResponse)
        setAccessToken(tokenResponse.access_token);
      },
    });
    setClient(initTokenClient);
  };

  /** アクセストークン取得 */
  const getToken = () => {
    if (!client) {
      console.log("clientがないです。")
      return;
    }

    client.requestAccessToken();
  };

  /** アクセストークン削除 */
  const revokeToken = () => {
    window.google.accounts.oauth2.revoke(accessToken, () => {
      console.log('access token revoked');
    });
  };

  /** カレンダーの更新 */
  const updateCalendar = () => {

    // 登録する内容
    const registerResource =
    {
      kind: "calendar#event",
      etag: "イータグ",
      summary: "今作ったイベント",
      description: "No Way!!",
      location: "東京",
      start: {
        dateTime: '2024-01-01T09:00:00',
        timeZone: 'Asia/Tokyo'
      },
      end: {
        dateTime: '2024-01-01T18:00:00',
        timeZone: 'Asia/Tokyo'
      },
    }

    // ここの ${eventId} はお好きなイベントのIDを入れてください。
    fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`, {
      method: "PUT",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      },
      body: JSON.stringify(registerResource)
    }).then((response) => {
      console.log(response);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.json();
    }).then((data: any) => {
      console.log("登録した予定: ", data)
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  };

  return (
    <div>
      <h1>カレンダーアプリ</h1>
      <button onClick={getToken}>アクセストークン取得</button><br /><br />
      <button onClick={updateCalendar}>カレンダーの更新</button><br /><br />
      <button onClick={revokeToken}>トークン破棄</button>
    </div>
  );
};

export default App;

予定の削除

ドキュメントは こちら です。
headerに取得したトークン情報を含めて google calendar apiを叩きます。

▽ 予定の削除の処理

  /** 予定の削除 */
  const deleteEvent = () => {
    fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`, {
      method: "DELETE",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }).then((response) => {
      console.log(response);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.text();
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  }

${eventId} には、削除したいイベント(予定)のIDが入ります。
予定一覧を取得した際にも手に入りますし、登録した際のレスポンスでも手に入ります。

▽ 共通処理 + 削除処理 を併せたコード

import { useEffect, useState } from 'react';
import './App.css';

const App = () => {
  const CLIENT_ID = import.meta.env.VITE_CLIENT_ID
  const SCOPE = 'https://www.googleapis.com/auth/calendar';

  const [client, setClient] = useState<any>(null);
  const [accessToken, setAccessToken] = useState('');

  useEffect(() => {
    const script = document.createElement('script');
    script.src = "https://accounts.google.com/gsi/client";
    script.onload = initClient;
    script.async = true;
    script.defer = true;
    document.body.appendChild(script);
  }, []);

  /** 初期化 */
  const initClient = () => {
    const initTokenClient = window.google.accounts.oauth2.initTokenClient({
      client_id: CLIENT_ID,
      scope: SCOPE,
      callback: (tokenResponse: any) => {
        console.log(tokenResponse)
        setAccessToken(tokenResponse.access_token);
      },
    });
    setClient(initTokenClient);
  };

  /** アクセストークン取得 */
  const getToken = () => {
    if (!client) {
      console.log("clientがないです。")
      return;
    }

    client.requestAccessToken();
  };

  /** アクセストークン削除 */
  const revokeToken = () => {
    window.google.accounts.oauth2.revoke(accessToken, () => {
      console.log('access token revoked');
    });
  };

  /** 予定の削除 */
  const deleteEvent = () => {
    
    // ここの ${eventId} はお好きなイベントのIDを入れてください。
    fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events/${eventId}`, {
      method: "DELETE",
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    }).then((response) => {
      console.log(response);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      return response.text();
    }).catch((error) => {
      console.log("リクエスト中にエラーが発生しました: ", error)
    })
  }

  return (
    <div>
      <h1>カレンダーアプリ</h1>
      <button onClick={getToken}>アクセストークン取得</button><br /><br />
      <button onClick={deleteEvent}>予定の削除</button><br /><br />
      <button onClick={revokeToken}>トークン破棄</button>
    </div>
  );
};

export default App;

さいごに

これにて、Google Calender APIを使用したCRUDの処理は完了です。

意外とReactを使用したCRUD処理の例が見つからなくて苦労しました。

「サービスアカウント」という第三者的なものを介してカレンダー操作をしている例がほとんどで、カレンダー操作するだけなのにそれ必要か?と思ってましたが、無くても操作できましたね。

ちなみに、今回はテストユーザーとして操作しましたが、一般の方が操作できる為には審査を通す必要があります。

審査は2週間ほどかかるようなので、一般公開するなら早めに申請を出しておかないとですね。

Googleカレンダーは使用率が高い & APIが無料なので、
自分でアプリを作成する際はぜひ組み込んでおきたい実装ですね!

今回はここまで!
Enjoy Hacking!!

タイトルとURLをコピーしました