作成したタスクマネージャーbot
使用した技術
1.Node.js
2.firebase
3.Messaging API
4.express
事前準備
firebaseのプロジェクトを作成する
↓↓ここ大事
プロジェクトのプランをBlazeプラン(従量制)に変更
無料のsparkプランでは外部APIを実行できないってさ、、、
Blazeプランは使った分だけ支払うという形で、無料枠があるので
個人で動かすぐらいなら無料枠で十分なのでほぼお金はかからないと思って大丈夫です!!
開発環境構築
こちらを参考に進めました。
ここでは上記サイトを参考に簡単に書いていきます。
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に登録してみる
'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の環境変数で設定しています。方法はこちら
送信されたテキストと送信してきたユーザーのIDを引数として、
下記addTodoメソッドを呼び出しています。
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で用意されているメッセージタイプの一種で、
画像、タイトル、テキストに加えてアクションボタンが含まれたメッセージのこと。
こんな感じ↓↓↓
LINE Developers
https://developers.line.biz/ja/docs/messaging-api/message-types/#template-messages(参照2021-09-05)
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メソッドが走ります。
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で応答メッセージとして送信する感じです!
完了ボタンが押された
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 + "が完了しました。",
},);
}
module.exports.deleteTodo = async (id) => {
await db.collection("todos").doc(id).delete();
}
ボタンテンプレートのアクションボタンが押されるとポストバックで情報を返すようにしているので(api.jsのjoinメソッドを見てください)、早期リターンでevent.typeがpostbackの時の処理を記述しています。
postbackデータをjavascriptで扱えるようにJSON.parseメソッドでJSONオブジェクトに変換しています。
postbackイベントの中身
そして、api.jsで記述したfirestoreに登録したタスクを消去する処理を呼び出しています!
正規表現
締め切り日を設定する際に
mm/dd か mm月dd日 (mm、ddは数字)
の形で送信された時のみ登録できるようにしています。
バリデーションチェックに正規表現を用いています。
参考にさせてもらったサイト
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