ゴミの日も記念日も何でも通知してくれるLINE BotをGASで作ってみよう

LINE Botをリマインダ的に活用して生活に役立てるのが流行っていますね。特に、LINE Botの[Messaging API](https://developers.line.biz/ja/services/messaging-api/)とGoogle Apps Script(通称GAS)との相性が良くて、とても簡単にBotが作れるみたいです。

本記事では、LINE Messaging APIとGASを使って、何でも通知してくれる便利なLINE Botを作ってみたいと思います。

### 追記

**2019/09/12**
プログラミングに馴染みのない人が通知設定シートを編集することをイメージし、以下の機能追加と変更を行いました。

- 新たに「年」を指定できるようにしました。
- 毎日、毎時といった指定をしたい場合、「*」を入力するのではなく、セルを空白にすれば良いように変更しました。
- 列の並びを、より日常生活に馴染みのある「年、月、日～」の並びに変更しました。

**2019/09/11**
思ったよりもいいねが伸びたので、「本文」が空欄だったり、「発言する場所」に不適切な文字が入っていてエラーとなった場合でも、止まらずに次の行の通知の処理へ進むよう微調整しました。

中身のロジックの理解は後回しに、とりあえず動くものを作ってみて欲しいという思いを込めて「新人プログラマ応援」のタグを追加しました。

# 関連する記事との比較

ゴミの種類を通知するLINE Botは、既にいくつか作ってみた記事があります。やはり分別は面倒ですね。

- [LINE Botでゴミ出し通知システムつくってみた](https://uyaroom.com/2019/01/23/linebot-trash/)
- [ゴミ出し日の前日にLINEで通知してくれるアプリを作る(Node.js / AWS Lambda)](https://qiita.com/so99ynoodles/items/1ef87440e4750e62e74a)

これらの記事では、ゴミの種類と曜日をコード内で定義しています。例えば、引っ越しをしてゴミの曜日や分別方法が変わった場合、コードを変更する必要があります。本記事では、通知する内容とコードを切り離し、**コードがわからない人でも、通知する日時と内容を編集できる**ことを目指します。

# 設計

以下の3つを利用して、何でも通知してくれるLINE Botを実装します。

- LINE Messaging API
- Google スプレッドシート
- Google Apps Script

# LINE Messaging API の準備

通知する内容を喋ってくれるLINE Botのアカウントを作成します。これに関しては、わかりやすく説明してくれる記事が沢山あるので、それに従ってください。

[LINE BOTの作り方を世界一わかりやすく解説（１）【アカウント準備編】](https://qiita.com/yoshizaki_kkgk/items/bd4277d3943200beab26)

アカウントが準備できたら、「チャネル基本設定」>「メッセージ送受信設定」から、「アクセストークン
（ロングターム）」を発行してください。これを後に利用します。

# スプレッドシートの準備

Google スプレッドシートを新規作成し、このような形式の表を作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/710ba63e-0273-e492-5ae1-4f76e0ace77f.png)


表の構成を見ていただければわかるかと思いますが、以下のことが実現できるようになります。

- 日時や曜日を指定して、狙ったタイミングで通知ができる
- 毎分、毎時、毎日のように、定期的な通知をしたい場合はセルを空白にすることで実現可能
- 第1月曜日、第2火曜日のように、何番目の何曜日という指定も可能（**ゴミ分別の通知に役立ちます！！**）
- 「発言する場所」を指定することで、通知内容ごとに別々の場所で発言できる

**※本記事では、「発言する場所」に入力するIDを調べるためのBotの機能も同時に実装します。**

[シートのサンプル](https://docs.google.com/spreadsheets/d/1L5PLbbFoHdf9atD6CU81vBSxOnZDsap_OEq-Hn53B2s/edit?usp=sharing)も用意しました。具体的な設定例をなるべく沢山載せておきますので、細かな通知の設定方法はサンプルをご確認ください。

**注意点**
- 年、月、日、第n週、曜日、時、分、本文、発言する場所という列の並びを変えないこと
- シート名（スプレッドシートの画面下部）を「alarm」とすること
- いずれも変更する場合は後述するGASのコードを変更する必要があります。

# Google Apps Scriptのコード

スプレッドシートの「ツール」>「スクリプトエディタ」で、Google Apps Scriptのエディタを開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/d190a37a-8eb2-90c4-aac8-ebbcc91ebf4d.png)

以下のコードをエディタで記入してください。コードは[Github](https://github.com/commojun/gasmi)にも一応公開しています。

ここで、`ACCESS_TOKEN`変数に、[LINE Messaging APIの準備](#line-messaging-api-%E3%81%AE%E6%BA%96%E5%82%99)で準備した「アクセストークン（ロングターム）」を記載してください。
`SHEET_KEY`は、スプレッドシートのURLに含まれている文字列です。 
`https://docs.google.com/spreadsheets/d/{この部分の文字列です！}/edit#gid=0` 

```Javascript:Code.gs

// 「メッセージ送受信設定」のアクセストークン（ロングターム）を記入してください。
var ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

// 発言させたい日時と内容が書かれたシートのKeyを記入してください。
// シート名には「alarm」としてください。
var SHEET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

function doPost(e) {
  var event = JSON.parse(e.postData.contents).events[0];
  var userMessage = event.message.text;

  var message = "";
  if ( userMessage === "ID" ) {
    message = tellID(event);
  }
  else {
    // 疎通確認が終わったらコメントアウトすると良いです。
    message = "メッセージを受け取ったわよ！";
  }

  replyMessage(event.replyToken, message);
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

function replyMessage(token, message) {
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'muteHttpExceptions': true,
    'payload': JSON.stringify({
      'replyToken': token,
      'messages': [{
        'type': 'text',
        'text': message,
      }],
    }),
  });
}

function pushMessage(to, message) {
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'muteHttpExceptions': true,
    'payload': JSON.stringify({
      'to': to,
      'messages': [{
        'type': 'text',
        'text': message,
      }],
    }),
  });
}

function tellID(event) {
  // ID
  var userID = event.source.userId;
  var talkID = "";
  if (event.source.type === "group") {
    talkID = event.source.groupId;
  } else if (event.source.type === "room") {
    talkID = event.source.roomId;
  }

  var message = "あなたのID: " + userID;
  if (talkID != "") {
    message += "\nこのチャットのID: " + talkID;
  }

  return message;
}

function notice() {
  var sheet = SpreadsheetApp.openById(SHEET_KEY).getSheetByName('alarm');
  var data  = sheet.getDataRange().getValues();

  var dayStr = ["日", "月", "火", "水", "木", "金", "土"];
  var now = new Date();
  for (var i=1; i<data.length; i++) {
    var [year, month, dayOfMonth, weekNum, dayOfWeek, hour, minute, message, to] = data[i];

    // 本文と発言する場所が空の場合はスキップ
    if (message === "" || to === "") { continue; }

    if ( (year       ==  now.getFullYear()               || year       === "")
      && (month      ==  now.getMonth() + 1              || month      === "")
      && (dayOfMonth ==  now.getDate()                   || dayOfMonth === "")
      && (weekNum    ==  parseInt(now.getDate() / 7) + 1 || weekNum    === "")
      && (dayOfWeek  === dayStr[now.getDay()]            || dayOfWeek  === "")
      && (hour       ==  now.getHours()                  || hour       === "")
      && (minute     ==  now.getMinutes()                || minute     === "")
      ) {
        pushMessage(to, message);
      }
  }
}
```

# 疎通確認

コードが準備できたら、「ウェブアプリケーションとして導入」をしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/6229ddbb-e654-8bc1-26a6-ba1ded05f93f.png)

**注意点**
- 「アプリケーションにアクセスできるユーザ」を、「全員（匿名ユーザーを含む）」にすること
- コードを修正した場合は、「プロジェクトバージョン」を「New」に指定し、更新すること


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/03f8caf9-80f6-a69d-ec6e-87395d839009.png)


完了したら、ウェブアプリケーションのURLを、LINE Developerの「チャンネル基本設定」>「メッセージ送受信設定」>「Webhook URL」に入力します。「Webhook送信」も有効化してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/db3a3671-3a0f-1c75-cbaf-d52d318a1cdb.png)

「接続確認」に成功すればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/3eede3ba-3a41-b768-0f70-1e3f64991634.png)

この状態でBotに何か話かけてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/f5263ebd-dd85-8c8a-39cf-bf625063619d.png)

メッセージが返ってくればOKです。

# 発言先のIDを調べて通知を設定する

通知内容の発言先「発言する場所」に入力するIDを調べてみましょう。通知をさせたいトークルームにBotを招待して、「ID」と発言すればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/36e294a0-bcc0-67e9-65ef-ca73932f4734.png)

頭文字がUのものが、個人のID、Rのものが、グループではないトークルームのID、Gのものが、グループのトークルームIDのようです。ここで調べたIDを、スプレッドシートの「発言する場所」に記入します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/e6716fc0-df1d-3c31-e18a-a1a49e520c2c.png)

# トリガーを設定する

時間になったら通知をするためのトリガーを設定します。Google Apps Scriptのエディタから時計マークをクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/6a10d267-96d1-29fd-4e76-f27a183321a9.png)

トリガーを追加

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/482e7a66-ba38-9659-cbde-40f955cdafd1.png)

実行する関数: notice
イベントのソース: 時間主導型
トリガーのタイプ: 分ベースのタイマー
時間の間隔: 1分おき
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/3e4583ff-eabe-10a3-32ed-f28504808e64.png)

これで設定は一通り完了です。
シートに設定した通りの通知が来れば成功です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70121/0b50238a-709c-8d59-335d-1dc090e2a0dc.png)


# 最後に

通知する内容、日時、通知先をGoogleスプレッドシートで指定可能なLINE Botを作成してみました。このBotを一度設定すれば、GASのコードがわからない家族や友達であっても、スプレッドシートの編集権限を与えて共有することで、各々が好きな内容の通知を好きなトークルームへ設定できるようになります。どこかで活用してくださる方が現れると嬉しいです。