GASでタニタのHealth Planet APIを叩いてスプレッドシートに出力した
友人から、タニタの体組成計をプレゼントしていただいた。
iPhoneやAndroidのアプリにデータを連携できるのだが、どうもアプリのグラフだけだと見にくいし、Googleスプレッドシートにとりあえず出力しておけば、後々、いろいろできそうだしってので、ちょっとやってみた。
Health Planet APIの仕様については、 ↓
以下、書いたGoogle App Scriptについて、解説する。
Health PlanetでClientを登録する
Health Planet APIは、OAuth2.0に準拠しているとのことで、最初にHealth Planet側にクライアントを登録する必要がある。
以下の画面にアクセスして、
APIの設定をする。
すると、 Client ID
と Client secret
が払出される。
これ、後で使う。
Googleスプレッドシートをつくる
こんな感じにした。
1行目に、認証用に Auth
と書いた図形と、AccessTokenを取得する用の Token
と書いた図形と、体組成データを取得する用の InnerScan
という図形を用意した。
普段は、 InnerScan
をクリックすると、データをサーバから取得してくるのだが、AccessTokenの有効期限が、30日っぽいので、また、認証してTokenを払出してっていうのをcurlとかでやるの面倒なので、スプレッドシートから実行できるようにしてる。
以下、各ボタンに対応するGASを書いたので、1個ずつ動きもふまえて解説する。
Authボタン
こんな感じで、モーダルが開く。
ここでは、Health Planet APIの /oauth/auth
にアクセスして、返ってくるHTMLをモーダルに表示している。
function openOauthAuth() { const text = _getOauthAuthText(); _openDialog(text); }
GASに、こんなfunctionを作って、このfunctionをクリック時に呼び出すように指定している。
処理としては、 /oauth/auth
にアクセスして、返ってきたHTMLをダイアログに渡して描画している。
/oauth/auth
にアクセスする処理は、以下。
function _getOauthAuthText() { const url = "https://www.healthplanet.jp/oauth/auth"; const params = { 'method': "post", 'payload': { 'client_id': _getClientId(), 'redirect_uri': "https://www.healthplanet.jp/success.html", 'scope': "innerscan", 'response_type': "code" } }; const res = UrlFetchApp.fetch(url, params); const text = res.getContentText("shift_jis"); return text; }
GASの Class UrlFetchApp | Apps Script | Google Developers を使って、Health Planet APIを叩いている。
_getClientId()
は、
function _getClientId() { return PropertiesService.getScriptProperties().getProperty("ClientId"); }
GASの Class PropertiesService | Apps Script | Google Developers を使っている。
ClientIdとかをコードに埋め込まないで、Script内で有効なPropertiesとして設定して使うようにした。 後程、ClientSecretも出てくるが、同じようにPropertiesに設定して使っている。
ダイアログを開くところは、
function _openDialog(text) { const html = HtmlService.createHtmlOutput(text); SpreadsheetApp.getUi().showModalDialog(html, "OAuth auth"); }
GASの Class Ui | Apps Script | Google Developers を使って開いている。
ダイアログを開いたら、IDとPWを入力してログインする。
アクセスを許可するか、確認するボタンが表示されるので、許可する。
コードが表示されるので、このコードをコピーする。
x
ボタンでモーダルを閉じて、コピーしたコードを、スプレッドシートのセルB2に貼り付ける。
セルB2を、後程、Tokenボタンをクリックした際に取得して、Tokenの取得処理に使うようにしている。
Tokenボタン
Tokenボタンをクリックすると、セルB2のコードを使って、Health Plane APIの /oauth/token
を呼び出して、Access Tokenを取得する。
function getOauthToken() { const sheet = SpreadsheetApp.getActiveSheet(); const code = sheet.getRange(1,2).getValue(); const tokenJson = _getOauthToken(code); sheet.getRange(1,4).setValue(tokenJson['access_token']); }
Access Tokenを取得したら、それをセルD2に書き込むようにした。
さらに次のInnerScanボタンをクリックしたときに使うようにしている。
/oauth/token
の呼び出し処理は、以下。
function _getOauthToken(code) { const url = "https://www.healthplanet.jp/oauth/token"; const params = { 'method': "post", 'payload': { 'client_id': _getClientId(), 'client_secret': _getClientSecret(), 'redirect_uri': "https://localhost", 'code': code, 'grant_type': "authorization_code" } }; return _fetchToJson(url, params); }
ちゃんと取れたら、 JSON.parse
する感じにして、取れなかったらErrorをthrowするみたいなことにしている。
InnerScanボタン
最後に、Access Tokenを使って、体組成計データを取得し、それをスプレッドシートに出力する。
function getInnerScan() { const sheet = SpreadsheetApp.getActiveSheet(); const accessToken = sheet.getRange(1,4).getValue(); const lastRow = sheet.getLastRow(); const lastRowDate = _getRowDate(sheet, lastRow); const from = lastRowDate + "00"; const json = _getInnerScanJson(accessToken, from); const dateKeyDataValueMap = _convertDateKeyDataValueMap(json); const tagColumnList = _getTagColumnList(sheet); var row = lastRow; const dateList = Object.keys(dateKeyDataValueMap) .sort() .filter(function(date) { return date !== lastRowDate; }) .map(function(date) { row++; sheet.getRange(row, 1).setValue(date); const dict = dateKeyDataValueMap[date]; tagColumnList.forEach(function(tag) { sheet.getRange(row, tag.col).setValue(dict[tag.value]); }); return date; }); if (dateList.length === 0) { SpreadsheetApp.getUi().alert("No data"); return; } }
セルD4のAccess Tokenを取得して使う。
また、シートの最終行数を取得し、A列の最終行の値を取得している。
これは、最後に読み込んだ日付を取得して、そこから後のデータをAPIで取得するようにしたいので、そのような処理にした。
Health Planet APIの /status/innerscan.format
が返すデータ構造は、日付が降順だったり、日付ごとではなく、フラットにデータとして返ってくるので、これをスプレッドシートの1行データに整形する必要がある。
function _convertDateKeyDataValueMap(json) { return json.data.reduce(function(acc, d) { const dict = (d.date in acc) ? acc[d.date] : {}; dict[d.tag] = d.keydata; acc[d.date] = dict; return acc; }, {}); }
reduce
で畳み込んで、日付をキーとしたObjectに変換した。
あと、フラットなデータ構造の中に入っているTagに対応する値を取得して、対応する列に表示していくためにもキーとなるTag情報とかがいる。
このあたりは、シートから情報を取得して、良い感じに列に収まって表示されるようにする必要があった。
function _getTagColumnList(sheet) { // B2:I2までを読み込む var startCol = 2; const range = sheet.getRange(2, startCol, 1, 8); return range.getValues()[0].map(function(v) { return { 'col': startCol++, 'value': v }; }); }
畳み込んだObjectの日付キーのリストを昇順にしてループし上から取り出していくようにした。
1日ずつ取り出しつつ、Tagもループして、対応するTagの値を取得して、セルに値を入れていく感じ。
まとめ
これで、毎日、体組成計に乗って、アプリでデータ取得して、それをスプレッドシートに取得する生活ができた。
スクリプト全文は、Gistに貼り付けておいた。
HealthPlanetScript.js · GitHub
以上です。