LINE NotifyとGoogleカレンダーAPIで実現！自動スケジュール通知botの作り方

# はじめに

こんにちは、やっしです。
この記事は[TechCommit AdventCalendar2023](https://adventar.org/calendars/8839)の20日目の記事です。

# 概要

本記事では、Googleカレンダーに登録されている1ヶ月先の特定の予定を、週１でLINEに通知するbotの作成方法ついて述べます。

# 目次
1. [必要なAPIとツールの概要](#必要なapiとツールの概要)
1. [LINE Notifyのアクセストークン取得](#line-notifyのアクセストークン取得)
1. [GoogleカレンダーIDの取得](#googleカレンダーidの取得)
1. [GASによるスクリプトの詳細](#gasによるスクリプトの詳細)
    1. [スクリプトプロパティの設定](#スクリプトプロパティの設定)
    1. [ソースコードの詳細：GoogleカレンダーとLINE Notifyの連携](#ソースコードの詳細googleカレンダーとline-notifyの連携)
    1. [定期実行のセットアップ](#定期実行のセットアップ)
1. [最後に：次の記事の紹介](#最後に次の記事の紹介)
1. [参考資料](#参考資料)

# 必要なAPIとツールの概要
- LINE Notify API
- GoogleカレンダーAPI
- GoogleAppsScript（エディタ&定期実行）

# LINE Notifyのアクセストークン取得
LINE Notifyのページにアクセスします。
https://notify-bot.line.me/my/

次に、ページ下部のアクセストークンの発行（開発者向け）の「トークンを発行する」をクリックします。

![スクリーンショット 2023-11-29 22.58.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552943/6edae612-f037-6560-bce6-5b8093c3eea8.png)

次に、トークン名の入力と、通知したいトークルームの選択を行います。

:::note warn
ここで指定したLINEのトークルームに公式のLINE Notifyを招待しておく必要があります。
:::

:::note info
トークン名はLINE Notifyから送信されるメッセージの冒頭に表示されますので、通知の目的に沿った名前を付けておくと良いでしょう。私は未確定のGoogleカレンダーの予定を通知させるので、「未確定ナビ」という名前にしました。
:::

画面の指示に従って入力を済ませたら、最後にアクセストークンが表示されるのでそれをメモしておきます。

:::note alert
アクセストークンは外部に漏らさないように注意して保管してください。
:::

# GoogleカレンダーIDの取得
Web版のGoogleカレンダーを開きます。通知を行いたいカレンダーの設定ページから、「カレンダーの統合」 > 「カレンダーID」の箇所を参照します。（※画像のIDは適当です）

![スクリーンショット 2023-11-29 0.14.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552943/65ca6313-0a5f-bf00-ecf8-cfb6072c86c3.png)

:::note warn
GoogleカレンダーIDは無闇に外部へ漏らさないようにしてください。
:::

# GASによるスクリプトの詳細

## スクリプトプロパティの設定
[Google Apps Script](https://script.google.com/home/start)を開きます。

まず、歯車のマークからスクリプトプロパティのところで先ほどメモしたGoogleカレンダーのIDとLINE Notify APIのアクセストークンをセットします。（後述のソースコードでこの値を参照します）

- CALENDAR_ID（GoogleカレンダーのID）
- LINE_NOTIFY_API_ACCESS_TOKEN（LINE Notify APIのアクセストークン）

![スクリーンショット 2023-11-29 22.34.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552943/219b9bf2-2aa7-3fae-b343-f70aaffd75f5.png)

## ソースコードの詳細：GoogleカレンダーとLINE Notifyの連携
エディタを開きます。
最終的には下記のようなファイル構成になります。（※UserName.gs、DateFormatter.gsはお好みで。）

![スクリーンショット 2023-11-29 22.37.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/552943/0e08e97d-b3ff-c5c6-0c9c-3e4098c54ca2.png)


### GoogleカレンダーAPI
サービスの右横の「+」をクリックして、「Calendar」を選択します。これによりGAS上でGoogleカレンダーAPIを扱えるようになります。

詳しい仕様については下記を参考にしてください。
https://developers.google.com/apps-script/advanced/calendar?authuser=0&hl=ja

さて、ここでは4週間先の予定を取得する処理を下記のように実装します。

```js:GetSchedulesService.gs
class GetSchedulesService
{
  /**
   * @param {string} calendarId
   */
  constructor(calendarId) {
    this.calendarId = calendarId;
  }

  /**
   * 直近4週間の予定を取得する
   * 
   * @return {Array}
   */
  getSchedules() {
    const now = new Date();
    const fourWeeksFromNow = new Date(now.getTime() + (4 * 7 * 24 * 60 * 60 * 1000));
    const events = Calendar.Events.list(this.calendarId, {
      timeMin: now.toISOString(),
      timeMax: fourWeeksFromNow.toISOString(),
      showDeleted: false,
      singleEvents: true,
      orderBy: 'startTime'
    });
    
    return events.items || [];
  }
}
```

### LINE Notify API

LINE Notify APIの公式ドキュメント
https://notify-bot.line.me/doc/ja/

ドキュメントによれば、notifyというエンドポイントを使えば良さそうです。
message以外にもリクエストパラメータがありますが、messageのみが必須なので今回はそれだけセットして下記のように実装しました。

```js:LineNotifyService.gs
class LineNotifyService
{
  /**
   * @param {string} token
   */
  constructor(token) {
    this.token = token;
  }

  /**
   * @param {string} message
   */
  send(message) {
    const options = {
      "method": "POST",
      "payload": {
        "message": message,
      },
      "headers": {
        "Content-Type": "application/x-www-form-urlencoded",
        "Authorization": "Bearer " + this.token,
      },
    };
    let response = UrlFetchApp.fetch('https://notify-api.line.me/api/notify', options);
  }
}

```

### mainの処理

GoogleカレンダーAPIとLINE Notify APIの処理を使ってmain関数を実装していきます。

```js:main.gs
function main() {
  // スクリプトプロパティからGoogleカレンダーIDとLINE Notifyのトークンを取得します。
  const scriptPropeties = PropertiesService.getScriptProperties();

  const calendarId = scriptPropeties.getProperty('CALENDAR_ID');
  const getSchedulesService = new GetSchedulesService(calendarId);
  const schedules = getSchedulesService.getSchedules();

  const lineToken = scriptPropeties.getProperty('LINE_NOTIFY_API_ACCESS_TOKEN');
  const lineNotifier = new LineNotifyService(lineToken);

  if (schedules.length > 0) {
    // ※メッセージはお好みで。
    lineNotifier.send(`\nお知らせ: 未確定の予約（${String(schedules.length)}件）が近づいています！`);
    schedules.forEach((event) => {
      const startDateObj = new Date(event.start.dateTime || event.start.date);
      const createDateObj = new Date(event.created);
      // UserNameは記事には載せてないですが、emailから名前をマッピングする処理を入れてます。ここはお好みに合わせてください。
      const userName = new UserName(event.creator.email);
      const message = buildMessage(
        event.summary, 
        userName.getValue(),
        startDateObj,
        createDateObj
      );

      lineNotifier.send(message);
    });

    lineNotifier.send("\n場所の予約と参加者の都合を確認してください。");
  } else {
    Logger.log('スケジュールはありません。');
  }
}

/**
 * @param {string} summary
 * @param {string} creatorName
 * @param {Date} startDateObj
 * @param {Date} createDateObj
 * @return {string}
 */
function buildMessage(summary, creatorName, startDateObj, createDateObj) {
  const startDateYmd = DateFormatter.toTsukiNichi(startDateObj);
  const createDateYmd = DateFormatter.toTsukiNichi(createDateObj);

  // 【未確定ナビ（トークン名）】、の文言のすぐ右隣にメッセージが表示されるため、改行を入れる。
  let message = "\n";
  message += `予定名：「${summary}」\n`;
  message += `開催日：${startDateYmd}\n`;
  message += `作成者：${creatorName}（${createDateYmd}に作成）`;

  return message;
}

```

lineNotifier.sendを1回実行するたびに、LINE Notifyから1通メッセージが送られます。
上記の場合、もし該当のスケジュールが2件あれば最初と最後のメッセージとスケジュール2件で合計4通のメッセージが送られます。

### DateFormmater（お好みでどうぞ）

```js:DateFormatter.gs
class DateFormatter
{
  /**
   * @param {Date} dateObj
   * @return {string} M月d日
   */
  static toTsukiNichi(dateObj) {
    const month = String(dateObj.getMonth() + 1).padStart(2, '0');
    const date = String(dateObj.getDate()).padStart(2, '0');

    return `${month}月${date}日`;
  }
}

```

## 定期実行のセットアップ
時間主導型、週ベースのタイマーにして、曜日と時刻を選択します。

これで実装は完了です。

:::note info
LINE Notifyから送られてくるメッセージは下記のような感じになります：

[未確定ナビ] 
お知らせ: 未確定の予約（1件）が近づいています！

[未確定ナビ] 
予定名：「この記事を投稿する」
開催日：12月20日
作成者：やっし（11月29日に作成）

[未確定ナビ] 
場所の予約と参加者の都合を確認してください。
:::

# 最後に：次の記事の紹介
21日目の記事はAYAKA TAKEMURAさんです。お楽しみに！

# 参考資料
- [LINE Notify](https://notify-bot.line.me/my/)
- [LINE Notify API Document](https://notify-bot.line.me/doc/ja/)
- [GoogleCalendarLibraly Document](https://developers.google.com/apps-script/advanced/calendar?authuser=0&hl=ja)