3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

linebot + cloud functions + firestore で家計簿みたいなbotを作る

Posted at

できたもの

firestoreに保存されている
linebot_test.png

Botの内容について

  • 一緒に住んでるような人と、費用を割り勘するためのbot
  • 食費、電気代など、二人で割り勘する費用を保存していき、どっちが多く負担しているかを常に知ることができる
  • 今回できたのは、botと1対1で、金額を入力すると、データベースに保存してくれるとこまで

ソースコード

環境・前提知識

  • nodejs v12.18.1
  • firestoreを触ったことがある

仕組み

  1. botにメッセージを送ると、linebotのwebhookイベントが呼ばれる
  2. cloud functionsでそのイベントを受けとる(linebotの設定画面からwebhookのurlを指定できる)
  3. cloud functionsでfirestoreのapiを呼び出し、保存する
  4. 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,
    };
    

functionの作成

  • linebotのnodejs referenceを参考にとりあえず返事するbotを作る
    既に自動で作られているfunctions/index.tsに追加していく
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を修正する

import * as admin from "firebase-admin";

admin.initializeApp({
  credential: admin.credential.applicationDefault(),
});
  • handleEventにfirestoreに保存する処理を追加する
    その前にいつもお世話になっているmomentjsを追加しておく
    npm i moment
index.ts
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

firebase functions のローカルで環境変数を使う
https://qiita.com/kenny_J_7/items/f75cc26f6265a019673d#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%83%88%E6%99%82%E3%81%AB%E3%81%AF%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%82%92%E5%88%A5%E9%80%94%E5%90%90%E3%81%8D%E5%87%BA%E3%81%99%E5%BF%85%E8%A6%81%E3%81%8C%E3%81%82%E3%82%8B

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

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?