GASとLINE Bot(Messaging API)で家計管理してみる

## 概要
- LINE Developersに登録してBotを作成
- Google spread sheetを作ってGASでコーディング

→毎月計算していた共用分の家計計算を楽にしてみた

最後にGitHub Gistのリンクを添付しています。

## はじめに
もともと共用の食費や日用品の支払いを、~~家計管理~~せめて残しておこうという意図で、それ用に作ったLINEグループにアルバムとして追加し、毎月月末に締めて精算を行っていました。

ある日仕事終わりに上司と話しているときにGASの話になり、そういえばGAS使ってなんかできないかなあと思って調べてみたところ、お手軽にできそうだったのでやってみたメモになります。(Qiita投稿していなかったのでそれも兼ねて...)


## 実現したいこと
毎月の共用支払いを以下のようにアナログ管理していました。
①支払いのレシートや明細ページを、LINEアルバムを毎月作成して追加する
②月末に電卓を使って合計金額を算出して、多い方から少ない方へ振込

...アナログです

実際のお金のやり取りはしょうがないとして、精算を毎月時間をとってやることをなくせれば、という感じです。

そこで

- 現在使っているLINEグループをそのまま使う
- 金額を自動で計算したい

を主軸に進めました。
レシートをアルバムに添付するのは、証拠を残すために継続するようにし、ミニマムな機能が一旦作れればよいかなという考えです。

### 機能
- 金額の登録
- 合計金額の表示

## LINE BOTの準備
LINE Developersに登録します。
登録からBot準備は非常に簡単でした。参考にさせていただいたサイトは1番最後にまとめて載せさせていただいています。

今回ポイントとしてはLINEグループに追加したかったので、下記部分の変更が必要でした。
Messaging API設定からBotの「グループ・複数人チャットへの参加を許可する」という項目があるので、右側の編集をクリック。
LINE Official Account Managerのアカウント設定が開くので、
機能の利用>チャットへの参加の部分で「グループ・複数人チャットへの参加を許可する」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427650/02c4b5f5-37d7-431c-edf7-b73de383d6d8.png)

これでグループ追加が可能になります。

## GASで実装
メインの処理: `doPost()`
spreadsheetへの登録: `record()`
メッセージ返却: `doReply()`

の3つで分けました。
(分岐部分とかきれいに分けたいところでしたが、GASのあるべき書き方が分かっておらず...)

#### メインの処理
```javascript
const ACCESS_TOKEN = ''; // LINE botのチャネルアクセストークンを設定
const URL = 'https://api.line.me/v2/bot/message/reply'; // 応答メッセージ用のAPI URL
const DATA_SHEET = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('foo'); // 保存sheetのID
const SUM_SHEET = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('bar'); // サマリsheetのID
const USER = {'id1': 'なまえ1', 'id2': 'なまえ2'};
function doPost(e) {
  const json = JSON.parse(e.postData.contents);

  const replyToken = json.events[0].replyToken;
  if (typeof replyToken === 'undefined') {
    return;
  }

  // ユーザID取得
  const userId = json.events[0].source.userId;
  // ここで特定ユーザでなければ登録せずに返却する
  let postMessage = '';
  if (USER[userId] === undefined) {
    postMessage = 'このユーザからの登録はできません。';
    return doReply(postMessage, replyToken);
  }

  // メッセージ取得
  const userMessage = json.events[0].message.text;
  // 内容を各変数に保持
  const messages = userMessage.split(/\r\n|\n/);
  const param = messages[0];

  /**
   * 以下パラメータによって各種処理に分岐
   * ・へるぷ || help || ヘルプ
   * ・yyyy/MM
   * ・金額(+備考)
   */
  if (param.match(/[help|へるぷ|ヘルプ]/)) {
    // ヘルプ表示
    postMessage = '以下が実行できます。\n==========\n';
    postMessage += '①登録:1行目に金額を入力します。改行して備考を併せて登録可能です。\n{金額}\n{備考}\n----------\n';
    postMessage += '②金額確認:1行目に金額を確認したい年月をyyyy/MMで入力します。\n(例)2021/04\n==========';
  } else if (param.match(/[0-9]{4}\/[0-2]{2}/)) {
    // 合計額の取得
    postMessage = getAmount(param);
  } else if (param.match(/[0-9]+/)) {
    // 登録
    const cost = parseInt(param);
    const etc = messages[1];
    const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd');

    if (Number.isNaN(cost) === false) {
      postMessage = record(userId, cost, etc, date);
    } else {
      postMessage = `金額\n備考(任意)\nで入力してください。`;
    }
  } else {
    postMessage = '入力が正しくありません。\n「help」や「へるぷ」、「ヘルプ」を送ることで、何ができるか確認できます。'
  }

  return doReply(postMessage, replyToken);
}
```
メッセージ送信者を判断するために、`json.events[0].source.userId`でpostDataからuserIdを取得しています。
予めこれを持っておいて、spreadsheet入力の名前として利用しています。

#### spreadsheetへの登録
```javascript
function record(userId, cost, etc, date)
{
  const newRow = DATA_SHEET.getLastRow() + 1;

  let etcMessage = !etc === false ? etc : 'なし';
  let replyMessage = `日付: ${date}\n金額: ${cost}円\n備考: ${etcMessage}\nを登録しました`;
  let targetUser = USER[userId];
  if (targetUser === undefined) {
    targetUser = userId;
  }
  DATA_SHEET.getRange(`A${newRow}`).setValue(targetUser);
  DATA_SHEET.getRange(`B${newRow}`).setValue(date);
  DATA_SHEET.getRange(`C${newRow}`).setValue(cost);
  DATA_SHEET.getRange(`D${newRow}`).setValue(etc);

  return replyMessage;
}
```
データの最下行から次に入力する行を取得し、決めておいた列に受け取った入力データを書き込んでいきます。
DB的にspreadsheetを使っている感じです。

保存sheet
![保存sheet](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427650/dcee22a5-0c8a-92c3-8282-d65ce954df52.png)

#### メッセージ返却
```javascript
function doReply(postMessage, replyToken)
{
  UrlFetchApp.fetch(URL, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': postMessage,
      }],
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({ 'content': 'post ok' })).setMimeType(ContentService.MimeType.JSON);
}
```

## デプロイ
コードを書き終えたらデプロイします。
右上にあるデプロイボタンから新しいデプロイを選択し、説明文を入力します。
![デプロイ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427650/9c16ea06-417f-97dc-5915-740d06a2b972.png)
アクセスできるユーザを「全員」に設定。

変更して更新する場合は、デプロイボタンからデプロイ管理を選択し、編集ボタンを押した後にバージョンから「新しいバージョン」を選択すればOKです。
![version.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427650/da1d9bdd-3c08-5212-5538-26e6c2264219.png)

デプロイ後に表示されるウェブアプリのURLをコピーし、Messaging APIのWebhook URLに設定すれば完了です。
![webhook.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427650/537125ed-af56-b6e6-8e7d-244c56db2cf3.png)

## 使ってみた
試しにhelpを表示させてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427650/2082ed90-b956-86b3-a3fc-8a171489fb45.png)


## おわりに
今回思い立ってやってみましたが、簡易的にやりたいことが実現できました。
コードの中身とかを良い書き方にするとかは飛ばしてしまいましたが、普段使うツールの延長として簡単なアプリケーション的に作るには非常によかったです。(何よりお金がかからない範囲でできる)

受け取ったメッセージを分岐するような形である意味CLIベースのような形ですが、今度はリッチメニューを使う形にしたり、
必要な機能を追加していけたらよいかなと思います。

## ソースコード
https://gist.github.com/regista333/c3858bd3be03bedabc785ebd23631f64


## 参考
[LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)
[[LINE BOT]GASで家計簿BOT作ってみた](https://techblog.istyle.co.jp/archives/6135)
[グループでも動作するLINE BOTを作る！](https://www.ultra-noob.com/blog/2020/92/)