できたもの
Botの内容について
- 一緒に住んでるような人と、費用を割り勘するためのbot
- 食費、電気代など、二人で割り勘する費用を保存していき、どっちが多く負担しているかを常に知ることができる
- 今回できたのは、botと1対1で、金額を入力すると、データベースに保存してくれるとこまで
ソースコード
環境・前提知識
- nodejs v12.18.1
- firestoreを触ったことがある
仕組み
- botにメッセージを送ると、linebotのwebhookイベントが呼ばれる
- cloud functionsでそのイベントを受けとる(linebotの設定画面からwebhookのurlを指定できる)
- cloud functionsでfirestoreのapiを呼び出し、保存する
- cloud functionsでlinebot message apiを呼び出し、「記録しました!」と返信
※firebaseは完全無料のsparkプランではなく、従量課金制のblazeプランじゃないとmessage apiの呼び出しができない
といっても百万リクエストで$0.4とかだし、無料枠もあるのでほとんどかからないと思われるが、自己責任で。
手順
Line Bot の作成
-
Line Developerに登録、ログインする
-
チャネルを作る
ここから作れる。先にプロバイダーというものを作る必要がある。チャネル作るとこまででok
cloud functionsの設定
-
firebase-toolsをグローバルインストール
npm install -g firebase-tools
-
firebaseにログイン
firebase login
-
ディレクトリを作成してfirebaseプロジェクトとの紐づけ
プロジェクトを作ってない人は以下で作る(Blazeプランに変更が必要)
https://console.firebase.google.com/u/0/
mkdir warikan_functions && cd warikan_functions
firebase init functions
最初の質問で既存のプロジェクトを指定する
あとは適当に選ぶ
※今回はtypescriptを選択してみました -
functionsフォルダに移動して必要なライブラリのインストール
cd functions && npm i express @line/bot-sdk
基本はfunctions
配下で作業することになる。 -
(必要であれば)デフォルトだとtslintを使用していたので、下記を参考にeslintを使うよう変更する
https://qiita.com/madono/items/a134e904e891c5cb1d20
そのほかにpackage.json
のコマンドも変更する必要あり。ソースコード参照。 -
cloud functions で linebot message api を使うための準備
以下のコマンドで、message api のtokenをcloud functionsの環境変数に設定する
firebase functions:config:set linebot.token="アクセストークン" linebot.secret="シークレット"
- アクセストークン: LineBot設定画面のMessaging API設定タブの最下部の「チャネルアクセストークン」(発行していない場合は発行する)
- シークレット: LineBot設定画面のチャンネル基本設定タブの「チャンネルシークレット」
以下のように環境変数を呼び出せる
const config = { channelAccessToken: functions.config().linebot.token, channelSecret: functions.config().linebot.secret, };
- アクセストークン: LineBot設定画面のMessaging API設定タブの最下部の「チャネルアクセストークン」(発行していない場合は発行する)
functionの作成
-
linebotのnodejs referenceを参考にとりあえず返事するbotを作る
既に自動で作られているfunctions/index.ts
に追加していく
import * as functions from "firebase-functions";
import * as line from "@line/bot-sdk";
import * as express from "express";
const config = {
channelAccessToken: functions.config().linebot.token,
channelSecret: functions.config().linebot.secret,
};
const app = express();
app.post("/costs", line.middleware(config), (req, res) => {
return Promise.all(req.body.events.map(handleEvent)).then((result) =>
res.json(result)
);
});
const client = new line.Client(config);
async function handleEvent(event: line.WebhookEvent) {
if (event.type !== "message" || event.message.type !== "text") {
return Promise.resolve(null);
}
return replyWithText("記録しました!", event);
}
async function replyWithText(message: string, event: line.MessageEvent) {
return client.replyMessage(event.replyToken, {
type: "text",
text: message,
});
}
export const lineBot = functions.https.onRequest(app);
-
functionをdeployする
warikan_functions/functions/
で
npm run deploy
を実行
成功するとエンドポイントのurlが表示されるので、コピーしておく -
linebot設定画面でwebhook urlを設定する
-
message api設定タブのwebhook urlに上でコピーしたurlを設定する
今回の場合、{エンドポイント}/costs
までしないとダメ(ここで結構時間使った・・・)
そもそもapp.post("/", ....
で設定しておけばよかった。どうせ1つしか使わなそうだし。 -
すぐ下のwebhookの利用をONにする
※検証押すとエラーがでるが、リファレンス見る限り200ok返していないのが原因かも。未検証。
-
-
linebot設定画面のQRコードからBotを追加してメッセージ送る
テキストを送信して、「記録しました!」が返ってくればOK
firestoreに費用を保存するようfunctionを修正する
-
firebase admin sdkの設定を追加する
以下を参考に環境変数を設定してfirebase adminの設定を初期化できるようにする
https://firebase.google.com/docs/admin/setup?hl=ja#initialize_the_sdk以下のコードでfirestoreのapiが呼び出せるようになる
import * as admin from "firebase-admin";
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
-
handleEvent
にfirestoreに保存する処理を追加する
その前にいつもお世話になっているmomentjsを追加しておく
npm i moment
import * as functions from "firebase-functions";
import * as line from "@line/bot-sdk";
import * as express from "express";
import * as admin from "firebase-admin";
import * as moment from "moment";
const config = {
channelAccessToken: functions.config().linebot.token,
channelSecret: functions.config().linebot.secret,
};
const app = express();
app.post("/costs", line.middleware(config), (req, res) => {
return Promise.all(req.body.events.map(handleEvent)).then((result) =>
res.json(result)
);
});
admin.initializeApp({
credential: admin.credential.applicationDefault(),
});
async function handleEvent(event: line.WebhookEvent) {
if (event.type !== "message" || event.message.type !== "text") {
return Promise.resolve(null);
}
// ここから
const userId = event.source.userId;
if (userId == null) return Promise.resolve(null);
const userRef = admin.firestore().collection("users").doc(userId);
const user = await userRef.get();
if (!user.exists) {
await admin.firestore().collection("users").doc(userId).set({});
}
const amount = Number(event.message.text);
if (!amount) return replyWithText("半角数字で入力してください", event);
await userRef
.collection("costs")
.doc(moment().utcOffset(0).format("YYYY-MM-DDTHH:mm:ss.SSSSSS"))
.set({
amount: amount,
burdenRate: 0.5,
paymentDate: admin.firestore.Timestamp.fromDate(moment().toDate()),
});
// ここまで
return replyWithText("記録しました!", event);
}
-
functionをdeployする
warikan_functions/functions/
で
npm run deploy
を実行 -
linebotにメッセージを送ってみる
半角数字で送れば「記録しました!」という返事が返ってきて、firebaseのコンソールにあるfirestoreのデータを見て、{userId}/costs/{日付}
という構造でデータが入っていれば成功
次にやることリスト
今回で下地が揃ったので、どんどん改良していきたい。
- 一覧取得、差額取得などのアクションの追加
- groupに招待して、二人で使えるようにする
- ラインボットのリッチメニューの作成
- functionのテストを導入する
本文のリンク以外で参考にさせてもらったリンク
Line Message Api Reference
https://developers.line.biz/ja/reference/messaging-api/
firebase functions で環境変数を使う
https://firebase.google.com/docs/functions/config-env?hl=ja
getaddrinfo EAI_AGAIN api.line.me:443
というエラーがcloud functionsのログで出たら
https://qiita.com/vedokoy/items/e996d7e2d5e8baa93dac#-%E3%83%97%E3%83%A9%E3%83%B3%E3%82%92%E5%A4%89%E6%9B%B4