ダイエットの進捗をTwitterにポストする


先日、[よく行くバー](http://onkai.on.omisenomikata.jp)のダイエット部に入部したため、体重計を買う事になりました(ちなみにダイエット部とは、７月末までにどのくらい痩せるかという目標を決めて、お互いに励ましあいながらがんばるという部活です)。

どうせ買うなら多機能で Internet of Things 的なヤーツが欲しかったので、まずは量販店に見に行きました。いろいろ出てはいるのですが、外部API連携が出来るかどうかとかが分からない(どこにも書いてないし、店員さんに聞いてももちろん分からない)ので、一旦諦めていろいろ調査をしてみました。

結局買ったのは、[Withings ネットワーク対応 体重計 Smart Body Analyzer WS-50(Amazon.co.jpリンク)](http://www.amazon.co.jp/dp/B00J8H59J8/)というヤツです。

![WS-50](https://www-assets-cdn.withings.com/front/products/smart-body-analyzer//media/scale-black.png)

これのいいところは、

- スマホをいじらなくても乗るだけで WiFi 経由でインターネットにデータを飛ばせる
- (一応)APIがある ※一応と書いた訳は後述
- IFTTTに対応している

といった点です。

## API について
公式ドキュメントは以下から。

- http://oauth.withings.com/api
- http://oauth.withings.com/api/doc

npm パッケージも[いくつか引っかかります](https://www.npmjs.com/browse/keyword/withings)ね。

ん？

> withings-request : The Withings OAuth implementation is not perfect. This lib hides that.

なんかキナ臭い。

[Top Six Things You Need To Know About The Withings API*](https://gist.github.com/katemonkeys/e17580777b57915f5068) によれば、Withings の API は

- It’s inconsistent about formatting
- They don’t understand OAuth.
- It is incomplete
- It disagrees with itself
- Account linking and device sharing is terrible.
- They don't listen

だいぶひどい言われようです。

## IFTTT での連携

というわけでAPI叩くのはやめて IFTTT を使う事にしました。
もしかして [IFTTT](http://ifttt.com) を知らない人のために補足すると、IFTTT は IF-This-Then-That の略で、トリガーとアクションを指定する事で、２つのデバイスやサービスをいい感じに連携させるサービスです。

例えば、体重計に乗ったら、Slack に通知する、なんていうのをポチポチ作るだけで、朝体重計に乗ると

![slack.png](https://qiita-image-store.s3.amazonaws.com/0/30051/c0137157-8bfa-ca52-8139-58c300de0622.png "slack.png")

なんていう通知が飛んできます。

同じように Twitter に流す事も可能ですが、さすがに自分の体重を全世界におっぴろげるのは憚られます。
また、目標に対してどの程度の進捗なのかも知りたいですよね。

という事で、クラウドを使ってそのあたりを解決したいと思います。

## 構成
![構成図](https://qiita-image-store.s3.amazonaws.com/0/30051/f962d576-1b08-d8fb-9fae-17387fe0702e.png "diet.png")

1. 体重計に乗ると IFTTT にプッシュされる(実際には一定のタイミングでポーリングっぽい動作)
2. IFTTT から Slack に通知
3. IFTTT から Maker Channel 経由で、API Gateway に HTTPS で POST リクエスト
4. API Gateway が Lambda をキック
5. 諸々の計算を行って、IFTTT の Maker Channel(incoming webhook) に HTTPS POST 
6. IFTTT から Twitter にポスト

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">開始体重からの差分: -7kg<br>目標体重までの差分: 2.2kg<br>予想目標到達日:5/18(目標の74日前)</p>&mdash; moto (@j3tm0t0) <a href="https://twitter.com/j3tm0t0/status/723302598711848960">2016年4月22日</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

## Lambda 詳細

inputには、下記のようなJSONを期待しています(Maker channelで設定する)

```javascript
{
  "WeightKg": {{WeightKg}}, // その日の体重計の読み(Kg)が入る
  "StartKg": xx.x,          // 開始日の体重
  "StartDate": "2016/1/29", // 開始日の日付
  "TargetKg": yy,           // 目標の体重
  "TargetDate": "2016/7/31" // 達成目標日
}
```

上記のパラメータを元に、諸々計算を行って、Maker channel の endpoint に HTTPS POST します。

```coffeescript
console.log 'Loading function'
request = require 'request'

exports.handler = (event, context) ->
  console.log JSON.stringify event
  current_kg = event.WeightKg
  start_kg = event.StartKg
  target_kg = event.TargetKg

  current_date = (new Date)/(24*60*60*1000)
  start_date = Date.parse(event.StartDate)/(24*60*60*1000)
  target_date = Date.parse(event.TargetDate)/(24*60*60*1000)

  diff_kg = Math.round(100 * (event.WeightKg - event.StartKg) ) / 100
  togo_kg = Math.round(100 * (event.WeightKg - event.TargetKg) ) / 100

  due_pace = (target_kg - start_kg)/(target_date - start_date)
  due_kg = start_kg + due_pace * (current_date - start_date)

  progress_kg = Math.round(100*(current_kg - due_kg))/100
  progress_days = Math.round(progress_kg / due_pace)

  actual_pace = (current_kg - start_kg)/(current_date - start_date)
  estimated_date = new Date( (new Date) - (togo_kg / actual_pace)*24*60*60*1000 )
  margin_days = Math.round(target_date - estimated_date/(24*60*60*1000))

  message = """
  開始体重からの差分: #{diff_kg}kg
  目標体重までの差分: #{togo_kg}kg
  予想目標到達日:#{estimated_date.getMonth()+1}/#{estimated_date.getDate()}(目標の#{margin_days}日前)
  """

  console.log message

  request.post
    url: 'https://maker.ifttt.com/trigger/twitter/with/key/XXXXXXXXXXXXXXXXX'
    form:
      value1: message
  , (err, response, body) ->
    context.fail err if err
    context.succeed body
  return
```

## 効果
もともと Slack に投稿していた時も結構減ってたんですが一旦停滞してて、Twitter でより広範囲に晒すことでそれまで以上に順調に減ってきています。

![体重推移](https://qiita-image-store.s3.amazonaws.com/0/30051/21d4ddb9-21af-9fdd-ba06-0a46d10fbaa5.png "IMG_5283.PNG")


## おわりに
俺、このダイエットが終わったら、美味しいお好み焼きを食べに行くんだ！