1
1

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 1 year has passed since last update.

LIFFとGASで家計簿を作ってみる

Posted at

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
  • SHEET_ID
  • USER_LIST
    • このアプリを使う人の登録
      1. サンプルのまま,xxxで実行する.
      2. CHANNEL_ACCESS_TOKENリンク先の続きのやり方などからwebhookとしてこのプログラムをwebhookとして登録する.
      3. 適当に作ったbotに話しかける.
      4. discordの通知をみて自分のuserIDを確認する
        - ここでポイントとして,console.logしても,ログの出力は取れないことに注意して下さい
        - Discordがない場合は,GSS等に出力してください
      5. properties.gsから環境変数として保存する.

また, 家計簿に登録する項目を決めるために,kinds.gsにある関数setQuickRepItemsから項目を登録します.
これは,後で行う,このアプリをwebhook化したあとからでも,項目の順番を変えることができるようにということが目的です.
自分達がわかりやすくするための日本語と,システムに認識させやすくするための英単語をセットで登録しています.
日本語の方に,,.を含むとおかしくなるので,注意が必要です.

あとは,作ったbotに話しかけるだけです.
LINE_capture_704935112.512195.jpg

{品目}
{価格}

で送ると,クイックリプレイとして,先ほど登録した種類が返ってくるので,それを後は押せば,登録されたタイムスタンプと種類,品目,価格が返ってきます.

プログラムの解説

備忘録としても,かいてるプログラムの解説も書いていこうと思います

webhookの入り口部分

main.gs
// 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に送られるようにしてます.

メッセージの場合の返信

message.gs
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から送ってしまったデータの編集ができること
  • 月一くらいで一月の結果が返ってくること
  • 使い過ぎたらアラートが飛んでくること
  • 種別に関してはサジェスチョンされ,あっていれば動作ひとつでおわること

このくらいできればとりあえず満足いくものと言えるのではないでしょうか?
ここまで自作で無料でできるのはなかなか面白いですね

おわりに

ドキュメントを書く練習をせねばということで書き始めましたが飽きました.
また気が向いたら加筆修正しようと思います.
文章嫌いです...
もしここまで読んでくださり,意味わからないことがあれば是非聞いてください
次回または修正版に乞うご期待を

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?