Googleカレンダーの予定をLINEbotで毎日通知する！

## はじめに
朝起きてスマホのカレンダーアプリを開き，その日の予定を確認する．
たった数ステップですけど寝起きでこのフローをこなすのって意外と面倒ですよね．
自分はそれが面倒だと思うタイプの人なので，朝起きた瞬間に通知が飛んできていてリスト的にその日の予定を確認できたらいいな~と思ったのがこのシステムを作ったきっかけです．

## システムの概要
1. まずGoogleカレンダーの予定が以下のようにあります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3888054/ed42b950-1198-e7fc-f46b-3af95a7b734f.png)

2. この予定を毎朝定刻になったらLINEbotに通知してもらいます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3888054/f0571a6b-81d8-51e1-5427-baa1f58f7925.png)

システム自体は単純です．これを使うことで毎朝ちょっとカレンダーアプリ開くのが億劫な時でも一目で予定を確認することでできるようになりました．皆さんもぜひ使ってみてください．

## 技術選定について
### ・LINE Messagaing API
LINEbotから自動送信するにはこいつが必要です．

### ・Googlecalendar API
Googleカレンダーの予定の取得や削除をするのに必要です．

### ・Google Apps Script
このシステムを作るのに他の言語も試しましたが，Google関係のアプリのAPIを叩くにはこれが一番楽です．

# 作成手順について
## 1.LINEbotの作成
https://note.com/climate_culture/n/ne288dc168069

まずは上記の記事を参考にLINEbotを作成してください．
この時に取得したChannel access tokenとUser IDはソースコードの一番上の設定欄にコピペでokです．

## 2.GoogleカレンダーID取得
Googleカレンダー【設定】 → 【マイカレンダーの設定】 → 【カレンダーの統合】
黄色で囲われているIDを取得してください．
人によっては用途ごとにマイカレンダーを複数持っている方もいると思います．例えば，授業，サークル，バイト，イベントのように分けている場合，LINEbotで通知したいカレンダーのIDを複数取得してもらっても構いません．ソースコードには3つまでカレンダーIDを登録できるようにしています．
こちらも同様にソースコードの設定欄にIDをコピペしてください．
![スクリーンショット 2024-09-18 012327.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3888054/866d2700-4d9c-9abb-72f8-5e598ebe7e4f.png)

## 3.スクリプト保存先の設定と実行
Googleドライブ【新規】 → 【その他】 → 【Google Apps Script】
ページが開けたらソースコードをコピペしてください．
あとは実行すれば完了です．もしかしたら初回実行でユーザー認証に遷移するかもしれませんが，指示に従えば問題ないと思います．

## 4.トリガーの設定
毎朝通知してもらうために定期実行するトリガーを設定します．ソースコードにもトリガーを作成するコードを書いていたのですが，そのコードで実際にトリガーを設定していたか，コード関係なくGASのトリガーを使っていたか忘れてしまいました．どちらか上手くいく方を採用してください．念のためコード関係なしのトリガー設定も記述しておきます．

スクリプト保存先【サイドバー】 → 【トリガー】 → 【トリガーを追加】
で下の画面に辿り着きます．時間主導型の日付ベース，時間は好きな時間に設定すれば問題ないと思います．

このトリガーの仕様だと思いますが本当に7時～8時の間で割とランダムに通知が来ます．定刻キッチリに朝7:00に通知が来てほしいと思う方は他に工夫する必要があるかもしれません．自分は朝起きたときに確認できれば問題なかったので起きる時間より早く実行できればいいやと雑に時間設定しています．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3888054/c5c2c0bf-b593-7d74-cc6e-1b1e646d0158.png)

## 5.実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3888054/604ca00e-fd24-14e8-d16b-ca0f54bf80fd.png)
上記のような通知が得られました．
工夫点としては〇件予定があります，1件目，2件目となるようにしたところです．
朝の通知でリスト的に予定が確認したいと思っていた希望を叶えています．
また時間，予定内容，詳細と項目を見れるようにしています．実際にカレンダーに登録した内容をそのままこの通知に出てくるようにしているので分かりやすいかなと思います．
他にも項目を増やしたければGoogleカレンダーに登録してある内容であれば反映させれます．ソースコードをいじってみてください．

# ソースコード
簡単に処理の概要を説明します．
まず上からGoogleカレンダー×LINEbotの連携に必要なIDをまとめて書いています．
あとは実行された日の日付を取得しGoogleカレンダーから予定引っ張ってくる．Googleカレンダーの予定は1次元配列で取得できるようなので，頭から順番にこちらの体裁に書き換えます．予定が0件の場合とそうでないときで分岐させて文章を送信すれば完了という形です．
言語としてGASを選定しましたが，このカレンダーの予定に関するデータの扱いが圧倒的に楽でした．

```
//Google Calendarと連携する設定
const LINE_ACCESS_TOKEN = '';// LINE Messaging API: Channel access token
const LINE_USER_ID = '';
const GoogleCalender_ID_1 = '';
const GoogleCalendar_ID_2 = '';
const GoogleCalendar_ID_3 = '';
const URL = 'https://api.line.me/v2/bot/message/broadcast';


//Googleカレンダーから当日分の予定を全て取得し、予定の配列を作成する関数
function getGoogleCalendar() {
  //B,カレンダーから取得する時間の設定
  const today = new Date();
  today.setHours(00);//当日の午前12時の「時」
  today.setMinutes(00);//「分」
  today.setSeconds(00); //「秒」
  const tomorrow = new Date(Date.parse(today) + (24 * 60 * 60 * 1000)); //翌日の午前12時を設定
  
  //予定を取得する日付の取得
  const monthNum = (today.getMonth())+1; //月
  const dateNum = today.getDate(); //日
  const day = today.getDay(); //曜日
  const dayArray = ['日','月','火','水','木','金','土'];
  const thisDate = monthNum + "/" + dateNum; //当日の日付を文字列に変換
  let sendMessage = "おはようございます。\n" + thisDate + "(" + dayArray[day] + ")"; 
  
  //カレンダーから予定を取得
  let myCalendar1 = CalendarApp.getCalendarById(GoogleCalender_ID_1);
  let myCalendar2 = CalendarApp.getCalendarById(GoogleCalendar_ID_2);
  let myCalendar3 = CalendarApp.getCalendarById(GoogleCalendar_ID_3);
  let events1 = myCalendar1.getEvents(today, tomorrow); //取得する当日のカレンダーの予定をすべて取得
  let events2 = myCalendar2.getEvents(today, tomorrow);
  let events3 = myCalendar3.getEvents(today, tomorrow);
  let events = [];
  events = events1.concat(events2,events3);
  let schedule = ""; //配列から文字列に変換した予定の文字列
  let messageArray = []; //取得した予定を格納する配列 

  //取得した予定を配列に格納
  for (var i in events) {
    const number = "\n" + (Number(i) + 1) + "件目"; //予定の件数
    const startHours = "0" + events[i].getStartTime().getHours();
    const startMinutes = "0" + events[i].getStartTime().getMinutes();
    const startTime = startHours.slice(-2) +":"+ startMinutes.slice(-2); //開始時間
    const endHours = "0" + events[i].getEndTime().getHours();
    const endMinutes = "0" + events[i].getEndTime().getMinutes();
    const endTime = endHours.slice(-2) +":"+ endMinutes.slice(-2); //終了時間
    const time = "\n【時間】" + startTime +" ~ "+ endTime; //予定を行う日時
    const title = "\n【予定】" + events[i].getTitle(); //予定のタイトル
    let location = ""; //場所
    let description = ""; //詳細

    //空白ではないときの処理
    if(!events[i].getLocation() == null || !events[i].getLocation() == ""){
      location = "\n【場所】" + events[i].getLocation();
    }    
    if(!events[i].getDescription() == null || !events[i].getDescription() == ""){
      description = "\n【詳細】" + events[i].getDescription();
    }

    //カレンダーから得たデータを文にまとめて配列に格納
    const message = number + time + title + location + description + "\n";
    messageArray.push(message);
 }

 //送信する文章を作成
 for(var j=0; j<=messageArray.length-1; j++){
   schedule += messageArray[j];
 }
 if(schedule == "" || schedule == null){
   sendMessage += "の予定はありません。\n";
 }else{
   sendMessage += "は" + events.length + "件予定があります。\n" + schedule + "\n https://calendar.google.com";
 }

 var postData = {
    to: LINE_USER_ID,
    messages: [
      {
        type: 'text',
        text: sendMessage,
      },
    ],
  };

 //LINEに送信
 const options =
  {
    "method"  : "post",
    "contentType": "application/json",
    "payload" : JSON.stringify(postData),
    "headers" : {"Authorization" : "Bearer "+ LINE_ACCESS_TOKEN}
  };
 UrlFetchApp.fetch(URL, options);

 //翌日に実行するトリガーを作成
 //today.setDate(today.getDate() +1);
 //today.setHours(7);
 //today.setMinutes(0);
 //today.setSeconds(0);
 //ScriptApp.newTrigger('getGoogleCalendar').timeBased().at(today).create();
}
```

## 雑感
作ってみて半年近く使い続けていますが意外と便利です．
作成する中で他の方のブログを参考にしましたが，APIが更新されていてソースコードの使い回しができなかったりと苦労した記憶があります．どの方のサイトを参考にしたか覚えていないのでリンクは貼れませんでした，すみません．
改善点としては予定が上から時間順にならないケースがあることです．
カレンダー1，カレンダー2，カレンダー3とあったとして，それらの予定を各々取得し，ただconcatで配列結合しているだけなのでカレンダー1の予定の時間よりも2の予定が早かったとしても順番は1，2の順に通知されます．時間順に並び替えて通知できるように書き換えてみても面白いかもしれないですね．自分は今のところこれで困っていることはないのでそのまま使い続けます(笑)

## 参考サイト
https://developers.google.com/apps-script/reference/calendar?hl=ja

https://note.com/climate_culture/n/ne288dc168069