どっかの元高専生の技術備忘録

10割自分の備忘録用のブログ。

Googleカレンダーで予定が変更されたら、そのことをグループLINEで通知するBotの作成withGAS

やりたいこと

Googleカレンダーで家族の予定を共有しているけど、頻繁にアプリを開いて確認するのが億劫
だったら新しく予定が追加された時と、予定の開始時刻1日前に通知するBotをLINEのMessaging APIとGASを使って実現できないかやってみる。

更新履歴

2023/11/14 繰り返しの予定を作成した際、同じメッセージを何回も送信するバグを直した。

目次

LINE公式アカウントの作成

LINE Developersにアクセスしてアカウントを作成します。

ログインできたら、Create New Providerをクリックして新しいプロバイダーを作成します。

そうしたら、Create New ChannelからMessaging APIを選択して新しいチャンネルを作っていきます。
ここで、Botの名前やアイコン、アカウントのカテゴリーなどを設定していきます。

作成できたら、Bot informationのところにある友達追加用のQRコードから追加しておきましょう。

GASの方でスクリプトを書くときに以下の2点が必要になるので、控えておきましょう。 * Your user ID * Channel access token

Channel access tokenは初期状態では発行されていないので、Messaging APIタブから発行しておきましょう。

グループLINEにBotを追加する場合

作成したBotをグループLINEに追加する場合は、LINE Official Account featuresのところにある、Allow bot to join group chatsを有効化しておいてください。

なお、グループに追加するのは後述するグループIDを取得する準備ができてからするようにしてください。

Calendar IDの取得

予定の変更を通知したいカレンダーのIDを控えておきます。
xxxxxxxx@group.calendar.google.comこんな感じの形式になってます。

GASの作成

ここから、メッセージを送るためのスクリプトを作成していきます。
Google Apps Scriptにアクセスして以下のコード貼り付てください。

const TOKEN = "Token";
const DESTID = "User ID or Group ID";
const CALENDARID = "CalendarID";
const URL = "https://api.line.me/v2/bot/message/push";

// 初回のイベント一覧を取得、次回から差分を取得するためのnextSyncTokenを保存する関数
function initSync() {
  var items = Calendar.Events.list(CALENDARID);
  var nextSyncToken = items.nextSyncToken;
  var properties = PropertiesService.getScriptProperties();
  properties.setProperty("syncToken", nextSyncToken);
  console.log("Initial Sync Done");
  properties.setProperty("prevtitle", "") // 前回のイベントタイトルを格納するようのproperties
  console.log("Initial property Done");
}

// LINEにメッセージを送信する関数
function sendLINE(body){
  UrlFetchApp.fetch(URL, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + TOKEN,
    },
    'method': 'POST',
    'payload': JSON.stringify({
      'to': DESTID,
      'messages':[{
        'type': 'text',
        'text': body ,
      }]
     })
   })
}

// カレンダーが編集されたときに実行される関数
function onCalendarEdit() {
  // nextSyncTokenを使って差分を取得
  var properties = PropertiesService.getScriptProperties();
  var nextSyncToken = properties.getProperty("syncToken");
  var optionalArgs = {syncToken: nextSyncToken};
  const events = Calendar.Events.list(CALENDARID, optionalArgs);
  console.log(events); // 変更された予定をプリント

  var nextSyncToken = events.nextSyncToken;
  properties.setProperty("syncToken", nextSyncToken); // nextSyncTokenの保存

  var prevevent = properties.getProperty("prevtitle"); // 前回のイベントタイトルを取得

  // 繰り返しの予定で2回目以降はスクリプト停止
  if ((events.items[0].hasOwnProperty("recurrence")) && (prevevent == events.items[0].summary)){
    console.log("This event is recurrenced");
    return ;
  }
  // ステータスが削除の場合、property初期化
  else if (events.items[0].status == "cancelled"){
    properties.setProperty("prevtitle", "");
  }
  // それ以外(通常のイベント、繰り返し1回目の場合)は今回処理したイベント名をpropに格納
  else{
    properties.setProperty("prevtitle", events.items[0].summary);
  }

  // メッセージの作成
  switch (events.items[0].status) {
    case "confirmed":
      var msg = ("【予定の追加・変更】\n"+
      "\n"+
      "タイトル: "+events.items[0].summary+"\n"+
      "開始時刻: "+Utilities.formatDate(new Date(events.items[0].start["dateTime"]), events.items[0].start["timeZone"], "yyyy/MM/dd (E) HH時mm分")+"\n"+
      "終了時刻: "+Utilities.formatDate(new Date(events.items[0].end["dateTime"]), events.items[0].end["timeZone"], "yyyy/MM/dd (E) HH時mm分")+"\n"+
      "作成者: "+events.items[0].creator.email+"\n\n"+
      "詳細はアプリを開いて確認してください。"
      );
      break;
    case "cancelled":
      var msg = ("【予定の削除】\n"+
      "\n\n"+
      "詳細はアプリを開いて確認してください。"
      );
      break;
    case "tentative":
       var msg = ("【予定の追加・変更】\n"+
      "\n"+
      "タイトル: "+events.items[0].summary+"\n"+
      "開始時刻: "+Utilities.formatDate(new Date(events.items[0].start["dateTime"]), events.items[0].start["timeZone"], "yyyy/MM/dd (E) HH時mm分")+"\n"+
      "終了時刻: "+Utilities.formatDate(new Date(events.items[0].end["dateTime"]), events.items[0].end["timeZone"], "yyyy/MM/dd (E) HH時mm分")+"\n"+
      "作成者: "+events.items[0].creator+"\n\n"+
      "詳細はアプリを開いて確認してください。"
      );
      break;
    case "draft":
      var msg = ("【下書きの予定があります】\n"+
      "\n"+
      "タイトル: "+events.items[0].summary+"\n\n"+
      "詳細はアプリを開いて確認してください。"
      );
    default:
      return ;
  }
  console.log(msg);
  sendLINE(msg)
}

なお、グループLINEにメッセージを送りたい人はDESTIDにUser IDではなくGroup IDを入力する必要があります。
この取得方法がめんどくさいので、後述します。
貼り付ける事ができたら、初めて使う際はinitSync関数を実行する必要があります。
下の画像のように、Debugの右にある関数を選ぶところからinitSyncを選択し、Runをクリックします。

エラー無く実行できたら成功です。

グループLINEにメッセージを送る方法

グループIDを取得するには、Webhookを受信するサーバーが必要になります。
自前で用意するのもありですが、めんどくさいのでGASとスプレッドシートを使ってグループIDを取得します。
まず、スプレッドシートを新規作成しExtensionからApps Scriptを選択してGASの画面に行きます。

そうしたら、以下のコードを貼り付けます。
なお、コードは三十路街道 ボンバィエ様のサイトにあるものを使わせていただきました。

cyuraharuto.com

function doPost(e){
 var json = JSON.parse(e.postData.contents);
 var UID = json.events[0].source.userId;
 var GID = json.events[0].source.groupId;
  
 var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
 sheet.getRange(1,1).setValue(GID);

}

貼り付けたら、これをWEBアプリとしてデプロイしていきます。
上の方にあるDeployからNew Deployを選択して、以下のように設定します。

デプロイできたら、URLが表示されるのでそれをコピーしてLINE DevelopersのWebhook URLに貼り付けます。

そうしたら、下にあるUse webhookを有効化します。ここまで出来たらBotをグループLINEに招待してください。
そうすると、先程のスプレッドシートのA1にグループIDが表示されています。これをDESTIDに入力してください。 完了したら、セキュリティのためにUse webhookを無効化し、スクリプトを削除しておいてください。

トリガーの設定

最後に、Googleカレンダーに変更があった際にスクリプトが実行されるよう、トリガーを設定していきます。

Choose which function to runにはonCalendarEdit
Enter calendar detailsにはCalendar Updated Owner emailにはカレンダーのIDをそれぞれ設定してください。
あとは、放置でOKです。

参考

cyuraharuto.com

cyuraharuto.com

for-dummies.net

qiita.com