ないんのブログ

気になったネタをつぶやきます

家計管理のためのLINEボットを作った話

はじめに

我が家は共働きですので、僕と奥さんがそれぞれ毎月末に一定額ずつ家に入れるという運用をしています。 家に入れる際にその月に支払った家計費分を差し引いて共有の銀行口座に入金。 家計費予算以外の部分は各自好きに貯金したり買い物したりしています。

例えば手取りが20万で家計費枠が15万とすると、毎月5万が各自好きに使える金額、その月に8万の家計費支出があったとすると7万を銀行に入れるという感じです。

共働きだと似たような運用をしているご家庭も多いのではないでしょうか?
この運用を確立するまで色々と試行錯誤があったので共有したいと思います。

前半部分はつまらないので飛ばして貰って大丈夫です。

月毎の精算はキャッシュレスと相性が悪い

最近は様々なキャッシュレス施策やポイント施策があるため、複数枚のクレカや色々なPay系サービスを使っています。僕はメインはANAカード、携帯用にdカード、キャッシュレスにLINE Pay、また最近にはJCBやVISAの還元キャンペーンがあったのでさらに数枚のカードを使っています。奥さんはAmazonユーザーなのでAmazonカードのヘビーユーザーです。

これだけたくさんの支払い手段があると家計費の計算も一苦労です。
家計簿アプリを使えばほとんどの支出管理を集約できるので良いのですが、この時に問題になるのがクレジットカードです。クレジットカードは実際に使ってから明細への反映や引き落としまで結構なタイムラグがあるので「毎月の家計費」をどのように定義するのか考える必要があります。

f:id:osanine:20200521190217p:plain
支払い方法と反映タイミング

大きく分けて下記3パターンぐらい検討しました。

パターンA(利用日に計上)

上図の一番左の利用日を基準に計上する方法です。一番直感的です。一般的なご家庭の家計簿や家計簿アプリでも普通は利用日で計上していますよね。ただクレジットカードだとカード明細や家計簿アプリの反映までに月をまたいでしまう可能性があるので、月毎に家計費精算をする場合は別途管理する必要が出てきてしまいます。 そこまで細かく管理出来るほどマメでも無いのでNG。

パターンB(引き落とし日に計上)

上図の一番右の引き落とし日を基準に計上する方法です。一番簡単です。現金・カード含めて自分の手元からお金が減るタイミングで計上すれば、クレカの締め日や反映のタイミング等の問題が無くなります。 しかし問題点は、利用してから引き落としまでのタイムラグです。上図の通り利用タイミングによっては実際の引き落としが約2か月後になります。特にネットショッピングのヘビーユーザーはAmazon.co.jpが並ぶ明細を見ても2か月前に何に使ったか思い出せないのではないでしょうか。あと最近は支払い手段が多すぎて全ての確認も労力がかかります。 しかもこの方法では家計簿アプリの支援を得られないという問題点もあります。家計簿アプリの管理単位とクレカの明細単位がリンクしていないため、家計簿上のどの支払いがいつ引き落としになるかが分かりません。

なのでこちらもNG。

パターンC(Web明細への反映時に計上)

A・Bの問題点を踏まえて個人的に着目したのがこの方法です。上図の反映日を基準に計上します。 Web明細への反映タイミングならパターンAの月またぎ問題はありませんし、パターンBのように時間が経ちすぎて忘れているという心配も少ないです。 ただし最大の問題点が、明細反映のタイミングをリアルタイムで把握する方法が無い事です。手作業で明細のアップデート部分だけ抽出するなんて困難ですし、家計簿アプリでもこの部分にフォーカスしているアプリは無さそうでした。

そこで新着情報をうまく管理できるよう自分でプログラムを書いて技術的ハードルをクリア出来れば最高のエクスペリエンスを得られるのではないかと思い立ったのが3年前です。試作品を作って3年ほど運用してきましたが奥さんからの評判も良く、色々と改良を重ねて良い感じになったので情報共有したいと思います。

(本題) 家計管理のためのLINEボットを作ってみた

どうでも良い考察は終わってここからLINE Botのお話です。 LINE Botそのものの作り方は割愛します。

今回作ったボットの全体構想はこんな感じです。

f:id:osanine:20200521190336p:plain
全体構想

大きく分けて下記4パートに分かれています。

  • 支出情報の取得
  • 家計費の分類
  • 月末の精算
  • 家計支出の可視化

それぞれのパートについて説明します。

支出情報の取得

今回は家計簿サービスの僕と奥さんそれぞれのアカウントを定期的にスクレイピングしてレコードの差分を抽出するという方法を用いました。これなら支払い手段が増えても家計簿サービスに口座を登録するだけなので楽です。本当はAPIで抽出出来れば良いのですが個人向けにAPIを提供しているサービスはZaimだけで、ZaimもAPI経由だとクレジットカードの履歴は取得出来ないようでした(手動で登録したもののみしか取れない)。せめてプレミアムだけでもAPI経由の取得に対応してくれたらいいのに。。

スクレイピングはFirebase Functions (以前はAWS Lambda)の上で動かしています。Firebase FunctionsならPuppeteerが使えるので、割と複雑めなスクレイピングでも対応出来ます。詳細は割愛。

この辺の仕組みせいでソースコード公開しづらいのがつらいところです。(クローラー部分を取り除いたバージョンはいずれ公開しようかと思っています)

家計費の分類

スクレイピングして新規に登録された支出情報は、LINE Messaging APIを使って家計費として算出するかどうか各自に確認します。LINEでポチポチするだけで分類出来るので奥さんにも好評です(bot名は不評)。

そもそも僕自身がかなりズボラなのでこのぐらい簡単な仕組みじゃないと続かないんですよね。 家計簿アプリも最初こそ頑張って分類したりしていましたが3日で開かなくなりました。 目につくところに少量の情報をプッシュしていくというのは割と色々な所で使えるテクニックだと思います。

f:id:osanine:20200521192914p:plain:w300
確認画面
(最近Flex Messageに対応して良い感じの見た目になりました)

分類カテゴリは試行錯誤の結果下記4パターンに落ち着きました。あんまり多くても面倒なので家計費として管理するだけならこれで十分。食費とかのカテゴリ情報も一応家計簿サービスから取ってきてはいるので、必要になればそちらで分類すれば良いかなという感じです。

  • 生活経費 … 家賃・光熱費・食費等定常的に発生するもの
  • 特別費 … 旅行や家電等定常的ではない大型出費
  • 個人支出 … 家計費に算出しないもの全般
  • その他 … 立て替えとかその他全般

月末の精算

月末に月締め処理が走って集計結果が表示されます。
生活費枠から家計費支出を差し引いた分を各人が共用口座に入金します。本当は入金も自動化したいんですけどね。 月締め処理の時点で分類していなかったレコードは翌月分として精算されます。精算漏れの無い安心設計です。

f:id:osanine:20200521190641p:plain:w300
集計結果
(これは月の途中結果なので少なめです)

ちなみに僕はメインバンクがUFJ銀行(旧三和銀行)で共用口座(自分名義)がSBIネット銀行、奥さんのメインバンクはSBIネット銀行です。SBIネット銀行同士だとMoneyTapというアプリで手数料無料で簡単に送金出来て便利です。UFJからSBIへの入金も証券口座を経由すれば3日ほどかかるものの手数料がかからず良い感じです。

家計支出の可視化

最初は家計費レコードはZaimにAPIで登録していたんですけどズボラすぎて3年間一回も見に行っていない事に気付いてLINEアプリ内で確認出来るようにしてみました。リッチメニューとLIFF (LINE Front-end Framework)を使って実現しています。

f:id:osanine:20200521190813p:plain:w300
メニュー(一部は実装途中)

リッチメニューで家計簿を開くを選択するとLINE内でブラウザが立ち上がって内容を確認出来ます。LIFFを使うとログイン無しでユーザー情報を取得出来るので便利。サイト部分は普通のReactです。

f:id:osanine:20200521190921p:plain:w300f:id:osanine:20200521190947p:plain:w300
家計簿(内容はフィクションです)

個人支出は本人分のみ、家計費は家族分が確認出来る仕組みです。プリコネやエッチなサイトに課金したとしても家計費にしなければバレません。

共有する内容が選べるような家計簿アプリってありそうで無い。まあ↑の家計費選択のフローが無ければ分類難しそうだしなあ。

終わりに

気合で全部自分で作ったけど、共働きとかズボラーにフォーカスした家計簿アプリあると良いなあと思いました。もし良いサービスとかアイデアとかあればこっそり教えてく下さい!

botソースコードは少し整理して公開しようと思います。

おまけ

f:id:osanine:20200521204402j:plain:w300
かしこい幼女

GAS×LINE Notifyで宅配野菜(らっでぃっしゅぼーや)のお届け内容通知botを作ってみた

我が家で進めているおうちハックの紹介です。 今回は宅配野菜(らでぃっしゅぼーや)のお届け内容を自動で通知してくれるbotを作ってみました。

我が家では1年ほど前から宅配野菜(らでぃっしゅぼーや)の契約をしています。 自分で食材を買って料理していると献立が偏りがちなので、 毎週旬の野菜をバランス良く届けてくれるサービスというのはとても便利です。

ただ、毎週届く野菜が決まっているわけではないので、献立計画や買い物計画を立てるのが不便です。 1〜2日前になるとWebサイト上にお届け内容が公開されるのですが、いちいち見に行くのも面倒です。

そこで今回はGAS×LINE Notifyを使ってお届け内容をスクレイピングして、自動で通知してくれるbotを作成してみました。 f:id:osanine:20190506201111j:plain:w480

LINE Notifyのアクセストークンを取得する

LINE Notifyで通知するためにはアクセストークンを取得する必要があります。 LINEアカウントで下記サイトにログインしてマイページ上でアクセストークンを発行出来ます。

参考: qiita.com

ぱれっとお届け内容のURLを取得する

次に定期宅配の野菜の取得URLを取得します。 お届け内容や曜日は人によって異なると思うので、下記サイトにアクセスして地域・曜日・コースを選択して、 検索結果に一つだけ結果が表示されている状態にします。 そのページのURLを取得します。

www.radishbo-ya.co.jp

f:id:osanine:20190506111217p:plain
お届け内容のURLを取得

GAS作成

  • LINE Notifyトーク
  • お届け内容のURL

が取得出来たらGASの作成を行います。

SpreadSheetの作成

まずは下記サイトから新規SpreadSheetの作成をします。 www.google.com

次にGASのエディタを開きます。

f:id:osanine:20190506112310p:plain
SpreadSheet

スクレイピングの際に外部ライブラリ(Parser)を利用するので、リソース→ライブラリ...選択して、 ライブラリを追加の部分に下記キーをコピペして追加します。

M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV

f:id:osanine:20190506181345p:plain

スクリプト

らでぃっしゅぼーやのお届け内容を取得するスクリプトとLINE Notify経由でユーザーに通知するスクリプトです。 ついでに後から過去のお届け内容を確認出来るようにスプレッドシートに結果を追記しています。 エディタ上でコードの実行してみてLINE Notifyで通知されれば成功です。

var LINE_TOKEN = '<LINE Notify トークン>';
var URL = '<お届け内容のURL>';

// 宅配日を取得
function get_date(html) {
  var date_html = Parser.data(html).from('palette_delivery_info_heading').to('お届け分').build();
  var date_str = Parser.data(date_html).from('&nbsp;').to(')', offset=1).build();
  return date_str;
}

// お届け内容を取得
function get_items(html) {
  var palettes = Parser.data(html).from('<ul class="palette_delivery_item_list ">').to('</ul>').iterate();
  var next_palette = palettes[0];
  var items = Parser.data(next_palette).from('target="_blank">').to('</a>').iterate();
  return items;
}

// LINE Notifyで通知
function notify_line(date, items) {
  message = date + '\n' + items.join('、');
  Logger.log(message);
  var options = {
        "method"  : "post",
        "payload" : "message=" + message,
        "headers" : {"Authorization" : "Bearer "+ LINE_TOKEN}
  };

  UrlFetchApp.fetch("https://notify-api.line.me/api/notify",options);
}

// 結果をスプレッドシートに保存
function save_to_spreadsheet(date, items) {
 var sheet = SpreadsheetApp.getActiveSheet();
 sheet.appendRow([date].concat(items));
}


function main() {
  var html = UrlFetchApp.fetch(URL).getContentText();
  
  var date = get_date(html);
  var items = get_items(html);
  save_to_spreadsheet(date, items);
  notify_line(date, items);
  
  Logger.log(items);
}

定期実行の設定

最後に定期実行の設定です。 大抵はお届け予定日の2日前には更新されているようです。 うちでは前日の朝に通知する設定にしています。

編集→現在のスクリプトのトリガーからトリガー設定を行います。

f:id:osanine:20190506183436p:plain

まとめ

今回は宅配野菜の内容を自動で通知してくれるbotを作ってみました。 我が家では他にも色々と自動化を進めているのでまた紹介したいと思います。 面倒臭い作業は全部自動化していまいましょう!

スキー旅行で一人ハッカソンをした話2019

昨年のスキー旅行に続いて今年も一人ハッカソンをしてきたのでその紹介です。

 

今年は忙しかった事もありうやむやにしようと思っていたのですが、直前になって「今年も何か作ってくるよな??」という圧力を受けたので準備期間2週間のハッカソンが幕を開けたのでした。

 

続きを読む

スキー旅行で一人ハッカソンをした話

先日、毎年恒例の大学時代の友達とのスキー旅行に行ってきました。早いもので7歳の僕を除いては参加者の平均年齢も30にさしかかり、既婚者も増え始めてきました。そして今回からはついに家族参加がOKとなり、1歳の子供が参加するようになりました。ふとしたきっかけから一人ハッカソンをする運びになって、お披露目したらまあまあウケたので紹介してみようと思います。

 

一人ハッカソンのきっかけ

時は遡り年末の忘年会。

僕「今回から家族参加OKにしよう」

A氏「奥さんと子供(1歳)連れていくわ。でも、教育に悪いから下品なネタ禁止な」(スキー旅行はとにかく下品な会話が多い)

B氏「下品な話をするたびに、罰金1000円にしよう」

僕「じゃあ、罰金管理システムが要るね」

  という会話がきっかけに、スキー旅行の罰金管理システム(+α)作りハッカソンが始まったのでした。

仕様

単に罰金がカウントされるだけだと面白くないので、色々楽しめそうな要素を入れようと考えていました。 主に以下の要素を盛り込むように意識して設計しました。

  1. リアルタイムに罰金額が分かる
  2. 誰でも簡単に罰金を登録出来るようにする
  3. 罰金が発生すると何か演出が走る
  4. その他なんか面白そうな機能を入れる(+α要素)

実装期間は1ヶ月弱でした。

設計

その結果の全体構成(概略)は以下のようになりました。色々詰め込んだのでなかなかカオスな構成です…。

f:id:osanine:20180304212559p:plain

Webベースのアプリです。フロントエンドは流行りのReact+ReduxをAmazon S3 + Cloudfront(独自ドメイン用)でホスティングしました。バックエンドはAPI Gateway+Lambda+Dynamo DBです。その他イベント同期にPubSubサービスのPubNub、画像ホスティング&加工が可能なCloudinaryを利用しました。運用費ほぼ0円なサーバーレスな構成です。

システムの大まかなイメージとしては、タブレットにランキングボードが常時表示されおり、スマホから罰金を登録したら、タブレット側で演出が流れるようなイメージです。

各機能の詳細について紹介します。

モバイル画面

各人のユーザーの罰金登録画面です。罰金ボタンを押すと1000円罰金が発生します。

f:id:osanine:20180304164921j:plain:w300

ロゴは流行りのゆるキャン風で作ってみました。ゆるキャンと同じフォント(漢字タイポス415 R)を使うためだけに、AdobeのComplete Pack(月額4000円)に登録しました(金欠)。

誰かが罰金ボタンを押すとこのように通知が表示されます。 みんながボタンを押しまくると、ついったーのふぁぼ爆撃みたいに通知で画面が埋まります。

f:id:osanine:20180304165328j:plain:w300

ダッシュボード画面

ダッシュボードを宴会部屋に置いておき、罰金額をランキング形式でいつでも確認出来るようにしました。

また、スマホで罰金ボタンが押されると、罰金サウンド(笑ってはいけないのパクr…オマージュです)が流れるようにしてみました(下記動画)。罰金ボタンが押されると、バックエンドAPIからPubNubのWebSocketでダッシュボードに罰金更新の通知を送っています。ちなみに、罰金額の同期もPubNubのイベントを利用して、APIコール数を減らしています。

動画音声中の名前の部分はgoogle-ttsで動的に生成しています。ブラウザだとCORSの関係でうまく動かなかったので、Lambdaで一度S3に保存してそれを再生するようにしています。

ちなみにタブレットは自立出来て音もそこそこ鳴るものが良かったので、YOGA TAB 3 PLUSを買いました(4万くらい)(金欠)。 モバイル端末だとタッチイベント等のユーザーイベントが無いと音が再生出来ない制約がありますが、今回はChromeのflagを編集するワークアラウンドで対処しました。

異議申し立て機能

かねてから罰金の濫用が懸念されていたので、異議申し立てを出来る機能を実装しました。 Google Home に「OK Google, 異議あり!」としゃべったら審判機能が開始します。

流れは以下のような感じです。

審判(ジャッジメント)の開始

LINE botを使ってアウトかセーフの集計

f:id:osanine:20180304195647p:plain:w300

1分後に審判結果をしゃべってくれる(本番では上手く動かなかった…)

Webアプリに投票機能を実装すると、アプリを開いていない人が投票出来ず、一票の格差が生まれる可能性があったため、みんなが居るLINE部屋に投稿するようにしました。 ちなみに、動画再生にはreact-playerを利用しました。ジャッジメントですの!の動画は、Youtube動画を埋め込みで再生しています(検索して適当に見付けた)。

裏側では、LambdaでPubNubに動画再生命令を送る→LINE Messaging APIでスキーのグループにTemplate Messageを送信(結果はwebhook経由でDynamoDBに保存)→1分スリープ→結果をDBから読み出す→LINEに投稿する→PubNubにテキスト再生命令を送る、という流れになっています。この部分はスキー旅行前日に実装したのでハードコーディングでゴリゴリ書きましたが、こういうシナリオ的なのを簡単に扱えるような仕組みってないんですかね。

画像・動画表示機能

もはや罰金とは全く関係ないですが、罰金機能を実装する際にLINE botや動画再生機能など色々実装したので、機能を流用してLINE部屋に投稿した画像や動画が自動で再生されるようにしてみました。

LINE botに画像や動画を送るとこんな感じでダッシュボードに表示してくれます。

最初はS3で画像と動画のホスティングをしようと思っていましたが、似たようなアイデアCloudinaryを使っているのを見かけて自分も使ってみました。APIが充実していてとても使いやすかったです。 tomoima525.hatenablog.com

スキー中の写真や動画を表示させたら面白いかなと思いましたが、画面が小さくてイマイチでした。宿泊先の大画面テレビが故障してたのが一番の敗因(Chromecast持っていったのに…)。

まとめ

後からAWSのログを見たら1晩で1万回ぐらい罰金ボタンが押されていました。1回1000円なので合計1000万円ですね、やばい。

アウトの所で名前入りのサウンドを鳴らすようにしたのがなかなか好評でした。

面倒臭さ排除のためにログインの仕組みは設けず、URLにアクセス出来る人なら誰でも罰金ボタンを押せるような仕様にしていたのですが、突然アウト〜の音声が流れてきたりして、誰が押したんだろうという探り合いが始まるのも面白かったです。

糞みたいなアプリでしたが、意外な所が盛り上がり要素に繋がるんだなととても勉強になりました。

最後の方はデスマーチ状態でかなり汚いコードになってしまったので、ソースコードはまた後程整理して公開しようと思っています。

おまけ

他の参加者にAPIがハックされた

f:id:osanine:20180304202428j:plain:w300

f:id:osanine:20180304202617j:plain:w300