• トップ
  • ブログ一覧
  • Google Calendar for Team Events for Slack を GAS で自作してみる ~即時通知編~
  • Google Calendar for Team Events for Slack を GAS で自作してみる ~即時通知編~

    こー(エンジニア)こー(エンジニア)
    2024.03.07

    IT技術

    はじめに

    こんにちは!株式会社ライトコードの福岡本社でモバイルエンジニアやってる こー です!

    featureImg2021.11.05YOUは何しにライトコードへ?〜こーくん編〜プロジェクト内で安心感を与えられる存在になりたい!今回は、弊社のエンジニアである高さんにフィーチャー!技術力に定評のあ...

    前編では Google Calendar for Team Events for Slack を GAS で実装した話 の一環として定時通知編(決まった時間に当日の予定を通知)を紹介しましたが、今回の後編は 即時通知(予定が作られたら即通知) の実装について書きたいと思います。

    • Google Calendar for Team Eventsって何?
    • 何で自作しようと思ったの?
    • GASって何?何で使おうと思ったの?
    • GAS アプリケーションのデプロイ
    • Slack 通知用Botの準備

    などや、1の定時通知の実装については前編の記事で詳しく書いていますので、後編では前編と異なる部分・特筆すべき部分に絞って書いていこうと思います。

    前編の記事はこちら!↓

    featureImg2023.05.26Google Calendar for Team Events for Slack を GAS で自作してみる ~定時通知編~はじめにこんにちは!株式会社ライトコードの福岡本社でモバイルエンジニアやってる こー です!今回は Google Ca...

    何を作ったの?

    弊社では、社員の休暇予定を「みんなの休暇予定」というカレンダーで共有しており、

    1. 平日朝8時にその日の休暇予定(定時通知)
    2. 休暇予定の作成(即時通知)

    の2つをSlackに通知しています。

    前編の記事では、1の定時通知の作成について書いたので、今回は2の即時通知になります。

    それでは早速、実際のコードを早速見ていきましょう!

    メインコード

    通知処理を行うメインコードはこちらです。

    こちらはカレンダーに予定が作成された際にフックされるメソッドですが、そのフック方法はまた後述します。

    1
    2function notifyCreatedHolidayEvents() {
    3  // 1. 新規作成された予定を抽出
    4  const calendarId = PropertiesService.getScriptProperties().getProperty("CALENDAR_ID")
    5  const nextSyncToken = PropertiesService.getScriptProperties().getProperty("SYNC_TOKEN")
    6  const optionalArgs = {
    7    syncToken: nextSyncToken
    8  }
    9  const calendarEvent = Calendar.Events.list(calendarId, optionalArgs)
    10  PropertiesService.getScriptProperties().setProperty("SYNC_TOKEN", calendarEvent.nextSyncToken)
    11  const events = calendarEvent.items.filter((item) => item.status == EventStatus.confirmed && item.start.date)
    12
    13  // 2. 通知メッセージの組み立て
    14  let message = ""
    15  switch (events.length) {
    16    case 0:
    17      return  // 新規作成された予定がなければ通知しない
    18    case 1:
    19      message = "*1* event was created"
    20      break
    21    default:
    22      message = `*${events.length}* events were created`
    23  }
    24
    25  const attachments = makeAttachmentsFromEvents_(events)
    26
    27  // 3. Slackに通知
    28  if (!attachments.length) return
    29  postSlack_(message, attachments)
    30}

    この3つのナンバリングの内、前編と差分があるのは 1 のみになるので、その部分の解説をしていきます。

    新規作成された予定を抽出

    1  // 1. 新規作成された予定を抽出
    2  const calendarId = PropertiesService.getScriptProperties().getProperty("CALENDAR_ID")
    3  const nextSyncToken = PropertiesService.getScriptProperties().getProperty("SYNC_TOKEN")
    4  const optionalArgs = {
    5    syncToken: nextSyncToken
    6  }
    7  const calendarEvent = Calendar.Events.list(calendarId, optionalArgs)
    8  PropertiesService.getScriptProperties().setProperty("SYNC_TOKEN", calendarEvent.nextSyncToken)
    9  const events = calendarEvent.items.filter((item) => item.status == EventStatus.confirmed && item.start.date)

    スクリプトプロパティ を用いてカレンダーIDを取得しているところまでは前編と同じです。

    スクリプトプロパティとは、GASで利用できる環境変数のようなもので、外部に公開されないため ID やトークン、パスワードなど秘匿情報を保持するのに適しているものでしたね。

    sycn_token とは?

    これに加えて、新規作成された予定のみを抽出するためには sync_token という値を利用します。

    sync_token は簡単に言うと「最終同期(=取得)をどこまで行ったか」を示すトークンです。

    これをオプションに添えることで、前回取得時との差分の予定のみを取得することができるわけですね。

    1  const nextSyncToken = PropertiesService.getScriptProperties().getProperty("SYNC_TOKEN")
    2  const optionalArgs = {
    3    syncToken: nextSyncToken
    4  }
    5  const calendarEvent = Calendar.Events.list(calendarId, optionalArgs)
    6  PropertiesService.getScriptProperties().setProperty("SYNC_TOKEN", calendarEvent.nextSyncToken)

    そして、取得された予定には新たな sync_token が付与されています。

    これをスクリプトプロパティに保存して次回の予定取得の際に利用されることで、今回との差分の予定を取得することができるんですね。

    sync_token を利用して差分の予定を取得 -> 予定に付与されている sync_token を保存 -> 次回その sync_token を利用して予定の差分を取得 -> ...

    というようにして、予定の新規作成の度に前回フック時との差分を取得して、毎回新規作成された予定のみを取得することができます。

    初回のsync_token は?

    「初回のsync_tokenはどこから取ってくるの?」

    気づいた方、鋭いですね。

    そうです、このままだと初回は sync_token なしで実行してしまうため、カレンダー上全ての予定を取得してしまいます。

    そのため、初回のみ手動で sycn_token 取得する専用のメソッドを用意して実行します。

    1function initialSync_() {
    2  let calendarId = PropertiesService.getScriptProperties().getProperty("CALENDAR_ID")
    3  let optionalArgs = {
    4    timeMin: new Date().toISOString(),
    5    singleEvents: true
    6  }
    7  let items = Calendar.Events.list(calendarId, optionalArgs)
    8  let nextSyncToken = items.nextSyncToken
    9  PropertiesService.getScriptProperties().setProperty("SYNC_TOKEN", nextSyncToken)
    10}

    やっていることは、「予定を取得して、それに付与されている sync_token を取得し、それをスクリプトプロパティに保存する」という単純なものです。

    これを、GAS 上から実行してあげます。

    これで初回の sync_token 保存処理は完了です。

    ちなみにこのメソッドはここで御役御免なので削除しても問題ないですが、自分は何かトラブルが発生し sync_token の再取得が必要になった場合などに備えて残しています。

    誤って実行してしまわないように、メソッド名の最後に _ を付与することでメソッドを private 化しています。

    これで新規作成された予定を通知する処理の実装は完成です!

    あとは、前編と同様に GAS アプリケーションをデプロイしましょう。

    カレンダーの更新イベントを通知用メソッドにフックする

    それではいよいよ、この作成した通知用のメソッドをカレンダーに予定作成があった際に実行されるようにトリガーを設定していきましょう。

    トリガーの詳しい作成方法は前編に譲り、ここでは前編と異なる部分にフォーカスします。

    「実行する関数を選択」では、先程作成したメソッドを選択した上で、

    1. イベントのソースを選択: 「カレンダーから」を選択
    2. カレンダーの詳細を入力: 「カレンダー更新済み」を選択
    3. カレンダーのオーナーのメールアドレス: CALENDAR_IDを入力

    を設定することで、カレンダーに更新があった際にメソッドを実行することができます。

    更新の度にメソッドは実行されますが、差分に新規作成の予定があった場合のみ通知を行うような実装になっているので問題はありません。

    これで、全工程完了です!お疲れさまでした!

    送信された通知

    無事、作成した際の通知を受け取ることができましたね。

    前編の定時通知とは、少々メッセージや attachment の色を変えているのみで、大きな違いはありません。

    attachment は色々カスタマイズ可能なので、皆さんのお好みに仕上げてくださいね。

    さいごに

    後編いかがだったでしょうか?

    前回実行時との差分を取得するために利用した sync_token 周りが少々複雑だったかな、と思います。

    GAS のトリガーには「カレンダーの更新」のみしか設定できないところがあるなど、少しむず痒いところがあるなぁ、というのが正直な感想ですね笑

    「予定作成時」や「削除時」など、フックできるイベントが増えてくれるとありがたいのですが、昔からずっとこのままなので期待しない方がいいですね笑

    GAS を使うと Google サービス関連を簡単に自動化できるので非常におすすめです!

    この記事が皆様のお役に立てれば幸いです。

    最後までお読みいただきありがとうございました!

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    こー(エンジニア)

    こー(エンジニア)

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background