LIFFとGASで家計簿を作ってみる
背景
やはり家計簿はほしい
どれだけお金をつかったか月が終わるとあれってなる.きっと見直せばとてもいいことになるだろう.
ただ,ズボラな私にはいちいち紙に書いて計算してではやらなくなる.家計簿アプリもダメだった.
ならば自分でできるだけ楽な形でやりたい.お金も掛けたくない.
ということでLIFFをつかってGASで対話形式の家計簿アプリをつくることとした.
DBとしてGSSをつかう. まずは,最低限の構築をする.
制作物
大体ここに入ってます
出来上がりイメージ
ID | タイムスタンプ | カテゴリ | 内容 | 金額 |
---|---|---|---|---|
1682980392508 | 23-05-02 7:33:12 | 食費 | お菓子 | 100 |
1683113113802 | 23-05-03 20:25:13 | 食費 | お菓子 | 150 |
※データの管理用にIDとしてUNIX時間をつかっています.
使い方
1. 環境変数を登録する
正確には環境変数ではないのですが,tokenなど,それぞれが発行すべきものは,ScriptProperty
として登録することにしています.
まず,それぞれの定数をproperties.gs
からScriptProperty
として登録します.
"xxx"の部分をそれぞれ適当なものに変換してください.
適当にリンク貼っておきました.
-
CHANNEL_ACCESS_TOKEN
-
DISCORD_WEBWOOK
- Discordに通知が欲しい場合はここにアクセストークンを入れる
- エラーハンドリングなど,GASではログの管理が難しいというかめんどくさかったので,自分用の鯖を作り通知が来るようにしてます
-
SHEET_ID
-
USER_LIST
- このアプリを使う人の登録
- サンプルのまま,
xxx
で実行する. -
CHANNEL_ACCESS_TOKEN
のリンク先の続きのやり方などからwebhookとしてこのプログラムをwebhookとして登録する. - 適当に作ったbotに話しかける.
- discordの通知をみて自分の
userID
を確認する
- ここでポイントとして,console.log
しても,ログの出力は取れないことに注意して下さい
- Discordがない場合は,GSS等に出力してください -
properties.gs
から環境変数として保存する.
- サンプルのまま,
- このアプリを使う人の登録
また, 家計簿に登録する項目を決めるために,kinds.gs
にある関数setQuickRepItems
から項目を登録します.
これは,後で行う,このアプリをwebhook化したあとからでも,項目の順番を変えることができるようにということが目的です.
自分達がわかりやすくするための日本語と,システムに認識させやすくするための英単語をセットで登録しています.
日本語の方に,,
や.
を含むとおかしくなるので,注意が必要です.
{品目}
{価格}
で送ると,クイックリプレイとして,先ほど登録した種類が返ってくるので,それを後は押せば,登録されたタイムスタンプと種類,品目,価格が返ってきます.
プログラムの解説
備忘録としても,かいてるプログラムの解説も書いていこうと思います
webhookの入り口部分
// lineにメッセージがあったらとりあえず呼ばれる部分
function doPost(e) {
try{
var post_json = JSON.parse(e.postData.getDataAsString());
const events = post_json.events;
for(let event of events){
// sendDisco(`type : ${event.type}`+"\nuser:"+event.source.userId+"\nugroup:"+event.source.groupId+"\nuroom:"+event.source.roomId)
console.log(getProfile(event.source.userId));
switch (event.type) {
case "message":
if(event.message.type=="sticker"){
// スタンプがそうしんされた時
// sendSelect(event);
}else{
messageRep(event);
}
break;
case "postback":
messageRepPost(event);
break
default:
sendDisco("false"+event.type+ +event.source.roomId + +event.source.type);
}
}
}catch(e){
sendDisco(printError(e));
}
}
GASにおいて,webhookを作った時,POSTされると関数doPost(e)
が呼ばれます.
tryで例外処理を行いつつ,event(メッセージを送る,画像を送るなどの各動作,特にpostbackはmessageとpostbackどちらもが送られてきます)がまとめて送られてくる場合があるので,それぞれで処理するようにループを行い,eventごとにそれぞれの関数に送られるようにします.
今回は,メッセージが送られてきた時と,postbackメッセージが送られてきた時だけを考えています.
全体を,tryで囲むようにして何かしらエラーが起きた場合は,discordに送られるようにしてます.
メッセージの場合の返信
function messageRep(event){
var reply_token = event.replyToken;
if (typeof reply_token === 'undefined') {
sendDisco("messageRep\nnoreptoken");
return;
}else if(!(userlist.includes(event.source.userId))){
sendDisco(`messageRep\nnotVaridUser`)
return;
}
const message = event.message.text.split("\n");
if(message.length==2){
const d = new Date(event.timestamp);
var sheetName = Utilities.formatDate(d, 'JST', 'yy-');
var month = d.getMonth();
if(month%2==0){
sheetName+=( '00' + (month+1) ).slice( -2 )+( '00' + (month+2) ).slice( -2 );
}else{
sheetName+=( '00' + (month+0) ).slice( -2 )+( '00' + (month+1) ).slice( -2 );
}
const sheet = getSheet(sheetName);
var data = [event.timestamp,Utilities.formatDate(d, 'JST', "yy-MM-dd HH:mm:ss"),,...message]
sheet.appendRow(data);
quickRepItems = getQuickRepItems(sheet.getLastRow(), sheetName);
eTitle="2"
lastRow=2
UrlFetchApp.fetch( 'https://api.line.me/v2/bot/message/reply', {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
},
'method': 'post',
'muteHttpExceptions':true,
'payload': JSON.stringify({
'replyToken': reply_token,
'messages': [{
'type': 'text',
'text': '種類は?',
"quickReply": {
"items":quickRepItems
}
}],
}),
});
}else if(message.length==3){
const d = new Date(event.timestamp);
const sheet = getSheet("収入");
var data = [event.timestamp,Utilities.formatDate(d, 'JST', "yy-MM-dd HH:mm:ss"),,...message.slice(1,3)]
sheet.appendRow(data);
quickRepItems = getQuickRepItems(sheet.getLastRow(), sheetName);
eTitle="2"
lastRow=2
UrlFetchApp.fetch( 'https://api.line.me/v2/bot/message/reply', {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN,
},
'method': 'post',
'muteHttpExceptions':true,
'payload': JSON.stringify({
'replyToken': reply_token,
'messages': [{
'type': 'text',
'text': `登録しました\n収入\n`+
`品目 : ${message[1]}\n`+
`価格 : ${message[2]}円`
}],
}),
});
}else{errorRep(reply_token, event.message.text+"入力形式を間違えています");return;}
ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}
ここでは,メッセージが送られてきた時の返事を作っています.
今回は入力形式として,
支出 | 収入 |
---|---|
{品目} {値段} |
収入 {品目} {値段} |
といった感じで,改行でおくられてきたテキストを分けた場合,2行だと支出,3行だと収入ということにしています.
また,記入するシートに関しても,収入はあまりないと見越したので,支出は二ヶ月ごとに一枚のシート,一枚のシートにずっと追加されていく方式としました.
あとはクイックリプレイに返信された時どのデータの種類かわかるようにIDとしてUNIX時間を渡してやれば終わりです
今後の展望
- 計算を含めた値段の送信ができること
- lineから送ってしまったデータの編集ができること
- 月一くらいで一月の結果が返ってくること
- 使い過ぎたらアラートが飛んでくること
- 種別に関してはサジェスチョンされ,あっていれば動作ひとつでおわること
このくらいできればとりあえず満足いくものと言えるのではないでしょうか?
ここまで自作で無料でできるのはなかなか面白いですね
おわりに
ドキュメントを書く練習をせねばということで書き始めましたが飽きました.
また気が向いたら加筆修正しようと思います.
文章嫌いです...
もしここまで読んでくださり,意味わからないことがあれば是非聞いてください
次回または修正版に乞うご期待を