たった30分でできる！？サンタから毎日メッセージが届くLINE Botを作るチャレンジ

本記事は、下記アドベントカレンダーの初日の記事でもあります！
初日に相応しいかどうかは別として・・


https://qiita.com/advent-calendar/2022/linedc


12月に何か LINE Bot をネタにしたハンズオンができないかな？と考えていたところ、この Bot が浮かびました。毎日サンタさんから LINE にメッセージが届きます。

ハンズオンは、LINEDC(LINE Developer Community)イベントのもので、この記事はイベント前に書いてます。

（追記)当日の動画も最後にリンク貼ってありますので、よろしければお楽しみください！
イベント概要は以下より。

https://linedevelopercommunity.connpass.com/event/265838/



### 日常にほんのちょっとの彩りを

私が想像する利用シーンは以下です。 `family script` 言語です。

```family script
子：「ねぇ。今日ってサンタさんから LINE きた？？」
親：「まだかな。もう少しでくるんじゃない？」
（数分後）
親：「お！きたぞきたぞ！」
子：「見せて見せて！」
```

といった感じに、クリスマスまで毎日 Bot にメッセージが届き、親子のコミュニケーションのひと時をお届けできたら素敵です。日常にほんのちょっとの彩りを。

![IMG_9196.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/e4ee34c6-dd32-0ed6-1ac6-725aadbff4be.png)

### 構成はいたってシンプルです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/5cce3164-1f16-57c2-ca4c-95931fedb089.png)




### ざっくりやることは

1. LINE Botの準備（Developers Console)
1. スプレッドシートでカレンダー作成
1. GAS実装
1. GASトリガー設定

# 1. LINE Botの準備（Developers Console)

https://developers.line.biz/ja/

LINE Bot （Messaging API） の設定をしていきます。以下の記事に手順がありますので、こちらを参考に。

- プロバイダーとチャネルを作成
- ユーザーIDをメモ
- チャネルアクセストークンを発行しメモ

https://qiita.com/h2m_kinoko/items/d55623425415ed2b7f9c

今回、Botに対してユーザーがメッセージを送信してBotが応答を返すようなことはしないため、Webhook の設定や応答メッセージの設定などは不要です。


# 2. スプレッドシートでカレンダー作成

こんな感じでカレンダーっぽく作り、画像では省略してますが Bot に通知したいメッセージ（サンタさんからのメッセージ）を日毎に仕込みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/b7e40803-1ebf-68f5-4379-b25f252b34bc.png)

GASで操作しやすいように、カレンダーの範囲に「カレンダー」と名前を付けています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/895c9486-12d1-a64b-d49a-c8e2ea7991ac.png)


# 3. GAS実装

愚直に実装です。
イベント内で30分で全て作り切れるか？的なチャレンジでしたのですが、結果はどうだったのか・・（この記事はイベント前に書いてますので・・笑）。イベントの様子は、イベント終了後に動画のリンクを貼っておきます。

### カレンダーのセル（データ）を取得

以下の記述で「カレンダー」と名前をつけた範囲の値を全て取得してます。 `getValues` というメソッドがありますが、日時を表示通りに取得したかったので、 `getDisplayValues` を使ってます。

```
const calendar = SpreadsheetApp.getActiveSheet().getRange("カレンダー").getDisplayValues();
```

以下のような形で２次元配列で取得されます。スプレッドシートの行ごとに配列で取得され、さらに各行のセルの値が取得されています。

```
console.log(calrendar);

[ 
  [ '',
    '',
    '',
    '2022/12/01',
    '2022/12/02',
    '2022/12/03',
    '2022/12/04' ],
  [ '', '', '', '木曜日', '金曜日', '土曜日', '日曜日' ],
  [ '',
    '',
    '',
    'いよいよ12月じゃ。サンタさんにプレゼントはお願いしたかの？',
    '今日は何か楽しいことはあったかい？',
    '今日は何をして遊んだんじゃ？',
    '' ],
  [ '2022/12/05',
    '2022/12/06',
    '2022/12/07',
    '2022/12/08',
    '2022/12/09',
    '2022/12/10',
    '2022/12/11' ],
  [ '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日' ],
  [ '', '', '', '', '', '', '' ],
  [ '2022/12/12',
    '2022/12/13',
    '2022/12/14',
    '2022/12/15',
    '2022/12/16',
    '2022/12/17',
    '2022/12/18' ],
  [ '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日' ],
  [ '', '', '', '', '', '', '' ],
  [ '2022/12/19',
    '2022/12/20',
    '2022/12/21',
    '2022/12/22',
    '2022/12/23',
    '2022/12/24',
    '2022/12/25' ],
  [ '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日' ],
  [ '', '', '', '', '', '今日ははやく寝るんじゃぞ。', 'メリークリスマス！' ] 
]
```

### 該当する日のメッセージを取得

一致する日付から、その日に通知するメッセージを取得。
力技この上ない実装ですが、上記２次元配列をまわして、一致する日付の列方向のインデックス (`clmIndex`) を取得します。一致したら、そのセルの２行下のセルに通知するメッセージが記載されているので、 `calendar[rowIndex + 2][clmIndex]` で取得しています。 

```
let santaMessage = "";
calendar.forEach((rowData, rowIndex) => {
    const clmIndex = rowData.findIndex(cell => cell === today);
    if (clmIndex === -1) {
      return;
    }
    
    santaMessage = calendar[rowIndex + 2][clmIndex]
})
```

`today` には、プログラムが動作した日付が `yyyy/MM/dd` の形式で格納されているので、スプレッドシート記載の日付と比較可能です。

```
const today = Utilities.formatDate(new Date(), "GMT+9", "yyyy/MM/dd");
```

### LINE Bot にメッセージをプッシュ通知

```
const sendMessageToLine = (santaMessage) => {
  const pushMessage = {
    to: USER_ID,
    messages: [
      {
        type: "image",
        originalContentUrl: IMAGE_URL,
        previewImageUrl: IMAGE_URL
      },
      {
        type: "text",
        text: santaMessage
      }
    ]
  }

  const params = {
    method: "POST",
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}`
    },
    payload: JSON.stringify(pushMessage)
  }

  UrlFetchApp.fetch(LINE_API, params)
}
```

プッシュメッセージのリクエスト方法などは以下のリファレンスに記載がありますので、こちらを参考に画像とメッセージを通知しています。

https://developers.line.biz/ja/reference/messaging-api/#send-push-message

# 4. GASトリガー設定

以下のようにGASにて時間主導型のトリガーを作成します。
下記サンプルでは午後7時〜8時の間くらいにトリガーが動作し、結果Line Botにプッシュ通知が飛んできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/4fd40a17-5636-48ff-e78a-febd593fd023.png)

![スクリーンショット 2022-11-27 14.45.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/e87b39d6-c706-5a07-0343-64d03a75e070.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119864/f177457c-f77e-34f9-ab7b-7d8e279f601c.png)


# イベント当日の動画はこちらから

https://www.youtube.com/watch?v=rz5lw8pAGDI

### 作ったスプレッドシートは以下に共有してますので、ご自由にお使いください。

https://docs.google.com/spreadsheets/d/13u3kcG6bI-kMzLFek6KWRIXi2-PI0VEhty9CKInUsk0/edit?usp=sharing


### GAS全体

```
const CHANNEL_ACCESS_TOKEN = "[アクセストークン]"
const USER_ID = "[ユーザーID]"

const IMAGE_URL = "https://drive.google.com/uc?id=[画像ファイルID]"

const LINE_API = "https://api.line.me/v2/bot/message/push"

const sendMessage = () => {

  const calendar = SpreadsheetApp.getActiveSheet().getRange("カレンダー").getDisplayValues();
  const today = Utilities.formatDate(new Date(), "GMT+9", "yyyy/MM/dd");

  let santaMessage = "";
  calendar.forEach((rowData, rowIndex) => {
    const clmIndex = rowData.findIndex(cell => cell === today);
    if (clmIndex === -1) {
      return;
    }

    santaMessage = calendar[rowIndex + 2][clmIndex]
  })

  sendMessageToLine(santaMessage)
}

const sendMessageToLine = (santaMessage) => {
  const pushMessage = {
    to: USER_ID,
    messages: [
      {
        type": "image",
        originalContentUrl: IMAGE_URL,
        previewImageUrl: IMAGE_URL
      },
      {
        type: "text",
        text: santaMessage
      }
    ]
  }

  const params = {
    method: "POST",
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}`
    },
    payload: JSON.stringify(pushMessage)
  }

  UrlFetchApp.fetch(LINE_API, params)
}
```

# イベント後

なんとか30分でライブコーディング？できましたでしょうか笑
素敵なクリスマスになりますように。メリークリスマス！