CREATIVE X 第2弾
CREATIVE X 第2弾
2018.10.12

Google Homeで体重を記録してSlackへグラフを通知する仕組みを作りましたが、ダイエットには失敗しました。

まさくに

こんにちは。

仕事中ただひたすらにクロノ・クロスのサントラを聞いています。いま世界で一番クロノ・クロスの音楽を聞いている人間は僕なんじゃないか、そんなちっぽけな自信を胸に今日も生きているのです。バックエンドエンジニアのまさくにです。

いや、なんですか、太りましたね。僕。2018年初頭では20キロ痩せるみたいなことをのたまっていましたが、2018年も残り3ヶ月を迎えるにあたって悔恨の念に堪えないです。全然痩せていないですね。僕。

そこで今回はGoogle Homeを使って、IFTTT経由でスプレッドシートに体重を書き込み、週次でSlackにグラフを通知するという、2018年初頭に僕が構築して、全くの無駄になった仕組みを公開します。

あらかじめ言っておきますが、この方法を使っても痩せませんし、「プログラムレスでカンタン構築」ってわけでもありません。無念です。

全体像

端的に表すと上記の通りです。

まずIFTTTでGoogle Assistant、およびスプレッドシートにアクセスできるように設定し、Google Home(Google Assistant)を通して、スプレッドシートへ体重を記録します。

さらにスプレッドシートの体重グラフの画像をSlackへ週次で通知するようにしておきます。Slackは画像のリンクを自動的にプレビューする機能が付いていますが、そのプレビューを働かせるために一度サーバーでバッファリングをしています。この理由は後述します。

必要なものの準備

事前の準備として下記のアカウント、データをそろえておいてください。

Slackのアカウントとトークン

最終的に体重のグラフが通知されるSlackと、それに紐づくbotのトークンが必要になります。トークンの取得の仕方は長くなるので割愛します。また本稿ではnewsというSlackのチャンネルへ体重のグラフがpostされることを目指します。適宜、チャンネル名は読み替えてください。

Slackについては過去にも記事を書きました。

IFTTTのアカウント

IFTTTを経由してアクションのトリガーやデータの受け渡しを行います。アカウントがなければ作成しておいてください。基本的に無料です。

IFTTTはAppletと呼ばれる「もしAが起きたら、Zをする」というような設定をあらかじめ決めておくことによって、主にWebサービスとWebサービスを連結します。今回の場合は、「もしGoogle Homeへ特定のキーワード(体重)が話しかけられたら、その値をスプレッドシートに保存する」という条件を作成することになります。

このため、Google AssistantとGoogle Sheetについて、IFTTTがアクセスできるよう、アクセス権限を与えておいてください。

体重を管理するスプレッドシート

Google Driveの中で適当なスプレッドシートを作成しておいてください。シートとカラムは下記のような設定が必要ですが、後述するスクリプトやグラフをご自身で変更していただければ、どのようなフォーマットでも構いません。

▼1枚目のシート:buffer

A列、B列のみを使います。

IFTTTによって自動的に入力されるシートなので、この時点では何かを入力する必要はありません。ここでIFTTTからの情報を受け取り、targetシートに入力していくことになります。

▼2枚目のシート:target

A列から日付、目標、体重、備考の4つのカラムを用意します。

日付を便りにbufferからデータが受け渡され、体重が入力されます。このため日付には2018/01/01から2018/12/31までを入力します。目標のカラムには最終的に達したい体重を記入してください。僕の場合は、2018/12/31の時点で、75kgになっている予定でした(無理です)。

▼3枚目のシート:graph

targetのシートをグラフに直し、シート化したものです。僕の場合は目標を赤線、実績値を青線で表示するようになっており、バーンダウンチャートのように体重の現象と目標値との差分が分かるようになっています。

このグラフを画像化されたものが最終的にSlackに通知されるようになります。

botを動かすためのサーバー

週次でグラフをSlackに投げるbotの稼働するサーバーが必要になります。常駐する必要はないので、頑張れば何かのサービスの無料枠だけで行ける気がしますが、僕はVPSを一つ借りているのでそこで動かしています。

そしてGoogle Home

ご購入ください。

ちなみに、試してはいませんがGoogle Homeの中に入っているGoogle Assistantを経由してデータの受け渡しを行うので、Androidでも同様の動作はできるはずです。たぶん。

Google HomeからIFTTTへ

IFTTTでAppletを作成します。

thisに「Google Assistant」の「Say a phrase with a number」を選んで、下記のように設定してください。

「What do you want to say?」でキーとなる言葉を登録します。ここでは「体重」と数値ですね。「What’s another way to say it?(他の言い方は?)」で、ひらがなも指定しておきます。「体重」が漢字として認識されないときもある、とのことなので保険です。

IFTTTからスプレッドシートへ

先ほどの続きで、IFTTTのthatを選びます。

「Google Sheet」から「Add row to spreadsheet」を選んで、下記のように設定してください。

「Spredsheet name」に作成済みのスプレッドシートのファイル名、「Drive folder path」にそのファイルの存在するDrive内のパスを指定します。

この設定で指定されたスプレッドシートの1シート目、最終行に、日時と体重が追加されることになります。少し前までこのCreatedAtが入力されないというバグがあったのですが、現在は直っているようです。

スプレッドシート内でグラフの更新

GASを使ってbufferシートに登録された体重を整形し、グラフがきれいに出力されるようにします。スプレッドシートのメニュー、「ツール>スクリプトエディタ」からGASの編集画面を開いてください。

ここに下記のスクリプトを書きます。

function main() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

  var targetSheet = spreadsheet.getSheetByName('target');
  var bufferSheet = spreadsheet.getSheetByName('buffer');

  var targetData = targetSheet.getRange("A2:C" + targetSheet.getLastRow()).getValues();
  var bufferData = bufferSheet.getRange("A1:B" + bufferSheet.getLastRow()).getValues();

  for(targetIndex = 0; targetIndex < targetData.length; targetIndex++) {
    var targetRow = targetData[targetIndex];

    if (targetRow[2] != '') continue;

    var targetDate = Utilities.formatDate(new Date(targetRow[0]), 'Asia/Tokyo', 'yyyyMMdd')

    for(bufferIndex = 0; bufferIndex < bufferData.length; bufferIndex++) {   
      var bufferRow = bufferData[bufferIndex];
      var weight = bufferRow[1];

      if (isNaN(weight)) continue;

      var onlyDate = bufferRow[0].toString().replace(/ at.*$/, '');
      var bufferDate = Utilities.formatDate(new Date(onlyDate), 'Asia/Tokyo', 'yyyyMMdd');

      if (targetDate == bufferDate) {
        targetSheet.getRange('C' + (targetIndex + 2).toString()).setValue(weight);
      }
    }
  }
}

もうこの辺で自分が何を目指しているのか分からなくなってきますね。でも今が一番大切なところなので頑張ってください。このスクリプトを動かすとGoogle Homeから受け取った数値をtargetシートに書き込んで、グラフが更新されます。

一度実行してみましょう。自分の作ったアプリケーションとなるので、このアプリケーションがDriveのファイルを操作できるよう、認可をしなければなりません。初回は下記のようなダイアログが表示されるかもしれないのですが、何しろ作ったのは自分なので認可をしてみてください。まだbufferシートも空だと思うので何も起きませんが、スクリプトが実行されます。

次にこのスクリプトをいつ実行するかを決めておきます。「編集>現在のプロジェクトのトリガー」を開いて、下記のように設定してください。

これで朝の5時から6時にかけてtargetシートとグラフが最新の情報に更新されます。

次にそのグラフの画像URLを取得します。スプレッドシートの方に戻り、「ファイル>ウェブに公開」を選んでください。ここで下記のように公開設定をして、グラフの画像URLを取得します。

これでGoogleにログインしなくても、このグラフの画像を表示させることができるようになりました。この画像URLは後で使います。

スプレッドシートからSlackへ

SlackはURLを自動的にプレビューしてくれるのだし、もうグラフの画像URLがあるんだから、そのURLを何らかの方法でSlackに通知すればいいだけじゃないのか。そんなことを考えていた時期が僕にもありました。

先ほどの画像URLをSlackに貼り付けてみてください。プレビューが展開されないのではないでしょうか。絶望を覚えます。詳細な条件を見つけることはできませんでしたが、おそらくMIME Typeや拡張子などのいくつかの条件をクリアしたURLが画像として認識され、Slackにより取得され、プレビューされているのだと思います。

スプレッドシートで先ほど公開されたURLはおそらく動的に出力されているため、Slackが画像として処理してくれず、プレビューが表示されません。

なので、泣く泣くサーバーで一度画像をダウンロードして、Slackにファイルアップデートを行います。詳細は大胆に割愛しますが、Railsで下記のようなタスクを書きました。slack-ruby-client を使用しています。

require 'bundler'
Bundler.require

namespace :my_weight do

  desc "グラフのアップロード"
  task :upload do

    # ダウンロード
    open('/tmp/my_weight.png', 'wb') do |output|
      open(ENV['SLACK_MY_WEIGHT_URL']) do |data|
        output.write(data.read)
      end
    end

    # アップロード
    Slack.configure do |config|
      config.token = ENV['SLACK_API_TOKEN']
    end

    client = Slack::Web::Client.new
    client.files_upload(
      file: UploadIO::new('/tmp/my_weight.png', 'image/png'),
      filename: 'my_weight.png',
      channels: '#news',
    )
  end

end

環境変数SLACK_MY_WEIGHT_URLにグラフの画像URL、SLACK_API_TOKENにSlackのbot用APIのトークンを渡し、土曜朝の八時ころ、cronで実行するようにしておきます。

この設定ではnewsチャンネルにグラフをpostするようになっているので、適宜調整してください。

この仕組みを9ヶ月運用してみた

「オッケー、Google。体重86.0」

「登録しました」

ここまでの設定を終えると、上記のようなやりとりでIFTTTを経由してスプレッドシートのbufferシートへ体重が入力されるようになったかと思います。このあとGASが動き、Railsが動き、Slackにグラフが通知されるようになったら成功です。長かった。長すぎた。ピタゴラスイッチ感がすごい。何やってんだ僕は。

ただ、時間をかけたぶん、かなり便利になった実感があります。風呂上がり、体重を測って声に出すだけで体重をレコーディングできるようになります。また、IFTTT、GAS、Rails、Slack、その他の周辺技術を工夫して使うことで、Google Homeにはまだまだ可能性を感じています。

一部、Google Homeの誤認識によって70kg台まで落ち込んでいる日がありますね。スプレッドシートを修正すればグラフも直りますが、これはGoogle Homeの優しい嘘だと思って残しています。なので、同じような仕組みを作ったとしても、厳密さを求めるシステムにはまだ使えないでしょう。

ここまで頑張って作ったのだから普通、心機一転、目標に向かって減量という下り坂を面白いように転げ落ちていくのではないでしょうか。本当にそうだったら素敵だったのに、と思いながら年の瀬が近づいてきます。何かもっとスマートな方法がある気がしますし、誰かがアプリを作っているような気もします。こんなもん作る前に運動をした方がいいのは確実です。

それでは。まさくにでした。