LINEのチャットボットでタスクマネージャーを作ってみた！

#作成したタスクマネージャーbot
<img width="200px" alt="chatbotのQRコード" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973408/82ec170b-303c-3da3-763e-42db27efbf2f.png">



#使用した技術
1.Node.js
2.firebase
3.Messaging API
4.express

##事前準備
[firebase](https://console.firebase.google.com/u/0/?pli=1)のプロジェクトを作成する

<font color=red>↓↓ここ大事</font>
プロジェクトのプランをBlazeプラン（従量制）に変更

無料のsparkプランでは外部APIを実行できないってさ、、、
Blazeプランは使った分だけ支払うという形で、無料枠があるので
個人で動かすぐらいなら無料枠で十分なのでほぼお金はかからないと思って大丈夫です！！

#開発環境構築
[こちら](https://qiita.com/n0bisuke/items/909881c8866e3f2ca642)を参考に進めました。
ここでは上記サイトを参考に簡単に書いていきます。

##1. Firebase CLIのインストール
```
npm i -g firebase-tools
```


##2. Firebase CLIでfirebaseにログイン
```
firebase login
```

##3. Firebaseの初期化（プロジェクトの作成）
```
firebase init functions
```

##4. LINEbotSDKとExpressをインストール
```
cd functions
```
でfunctionsディレクトリに移動して

```
npm i --save @line/bot-sdk express
```

#作成開始！
ここからは自分が作成したチャットボットで、重要な部分を切り抜いて書いていきます！

#送信された内容をFirestoreに登録してみる

```javascript:index.js
'use strict';

const functions = require('firebase-functions');
const express = require('express');
const line = require('@line/bot-sdk');
const API = require('./api');



// LINE bot
const config = {
 channelSecret: functions.config().line.secret ,
 channelAccessToken: functions.config().line.token,
};

const client = new line.Client(config);
const app = express();

app.post('/', line.middleware(config), (req, res) => {
    res.sendStatus(200);
 Promise.all(req.body.events.map(post))
     .then(() => res.status(200).end())
     .catch((err) => {
      console.error(err);
      res.status(500).end()
     })
});

async function handleEvent(event) {
    API.addTodo(event.message.text, event.source.userId);
    if (event.type === "message" && event.message.type === "text") {
        return client.replyMessage(
          event.replyToken, {
            type: "text", text: 
            event.message.text
          })
    }
}

async function post(event) {
    return API(event.message.text, event.source.userId);
}

exports.app = functions.region("asia-northeast1").https.onRequest(app);
```
※channelSecretとchannelAccessTokenはfirebaseの環境変数で設定しています。方法は[こちら](https://hyodoblog.com/firebase-functions-env/)

送信されたテキストと送信してきたユーザーのIDを引数として、
下記addTodoメソッドを呼び出しています。

```javascript:api.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();
const db = admin.firestore();

exports.addTodo = function(content, uid){
  db.collection("todos").add({
    todoName: content,
    uid: uid, 
  })
}
```
新たにapi.jsを作成し、firestoreにデータを登録する処理を記述します。
todoNameとuidの2つのフィールドを登録している処理です。

##登録したデータをボタンテンプレートで表示する
**ボタンテンプレート**・・・
MessagingAPIで用意されているメッセージタイプの一種で、
画像、タイトル、テキストに加えてアクションボタンが含まれたメッセージのこと。
こんな感じ↓↓↓
<img width="200px" alt="ボタンテンプレートの画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1973408/6c31b242-331a-5418-aa4d-09a18d4cb0c6.png">
LINE Developers
<span style="font-size:10px">https://developers.line.biz/ja/docs/messaging-api/message-types/#template-messages</span>（参照2021-09-05）

```javascript:index.js/一部抜粋
      if(event.message.text == "@show"){
        let todos = await API.getTodo(event.source.userId);
        let list = await API.join(todos);
        return client.replyMessage(event.replyToken, list);
```
＠showと送信されるとgetTodoとjoinメソッドが走ります。

```javaScript：api.js/getTodoメソッド,joinメソッド
module.exports.getTodo = async (uid) => {
  const todo = await db.collection("todos")
  .where("userId", "==", uid); //uidが一致するもの指定

  return todo.get().then((snapshot) => {
    let todos = [];
    snapshot.forEach((doc)=>{ 
      todos.push({
        id: doc.id,
        todoName: doc.data().todoName,
        deadLine: doc.data().deadLine, 
        isComplete: doc.data().isComplete,
      });
    });

    return todos;
  });
}


//データ整形
module.exports.join = (datas) => {
  let data = [];
  for(var i =0; i< datas.length; i++){
    data.push(
      {
        "type": "template",
        "altText": "this is a buttons template",
        "template": {
            "type": "buttons",
            "actions": [
                {//ポストバックでtodoの情報を返す
                    "type": "postback",
                    "label": "完了",
                    "data" : JSON.stringify(datas[i]), //文字列
                },
            ],
        "title" : datas[i].todoName,
        "text" : "期日 : " + datas[i].deadLine,
        }
      },
    )
  }
  return data;
}
```
getTodoメソッドではuserIdが一致するドキュメントのみを指定してデータを取り出しています。
joinメソッドでは**ボタンテンプレート**として送信するために一つ一つのタスクのデータを整形しています。
それらの整形されたタスクデータを配列に格納し、index.jsで応答メッセージとして送信する感じです！

#完了ボタンが押された
```javaScript：index.js/タスク完了の処理
 if(event.type === "postback"){  //タスクが完了した
    const data = JSON.parse(event.postback.data); //オブジェクトデータに変換
    await API.deleteTodo(data.id); //todo削除
    return client.replyMessage(event.replyToken , {
      type :"text",
      text : "お疲れさまです！\n" + data.todoName + "が完了しました。",
    },);
  }
```
```javaScript:api.js/deleteTodoメソッド
module.exports.deleteTodo = async (id) => {
  await db.collection("todos").doc(id).delete();
}
```

ボタンテンプレートのアクションボタンが押されるとポストバックで情報を返すようにしているので（api.jsのjoinメソッドを見てください）、早期リターンでevent.typeがpostbackの時の処理を記述しています。
postbackデータをjavascriptで扱えるようにJSON.parseメソッドでJSONオブジェクトに変換しています。
[postbackイベントの中身](https://developers.line.biz/ja/reference/messaging-api/#postback-event)
そして、api.jsで記述したfirestoreに登録したタスクを消去する処理を呼び出しています！

#正規表現
締め切り日を設定する際に
mm/dd　か　mm月dd日　（mm、ddは数字）
の形で送信された時のみ登録できるようにしています。
バリデーションチェックに正規表現を用いています。
参考にさせてもらった[サイト](https://murashun.jp/article/programming/regular-expression.html)

```javascript:api.js/checkDateメソッド
exports.checkDate = (date) => {
  if(!date.match(/^\d{1,2}(\/|月)\d{1,2}($|日)/)){  // 〇〇/〇〇か〇〇月〇〇日の形のみ通過
    return "error";
  }    
  let str = date.split(/\/|月|日/); // "/"か"月"か"日"で区切る
  let month = parseInt(str[0], 10);
  let day = parseInt(str[1], 10);
  if( month < 1 || 12 < month || day < 1 || 31 < day ){
    return "error";
  }
  let due = str[0] + "月" + str[1] + "日";
  return due;
}
```

#まとめ
ざっと実装した内容について説明しましたが、間違ってたり、こーいう風に書いた方がいいんじゃない？みたいなことあれば是非ともコメントおねがいします！！
また、こんな機能あったら便利だね！とかあればドシドシコメントお願いします！！！

#実装予定機能
~~現在インスタを使って友達に使ってもらい、フィードバックをもらっています！~~
通知機能実装中←もうすぐできそう！

#参考文献
https://developers.line.biz/ja/docs/messaging-api/
https://qiita.com/n0bisuke/items/909881c8866e3f2ca642
https://murashun.jp/article/programming/regular-expression.html