Google HomeからTogglを使う

# 背景

タイムトラッキングサービスの[Toggl](https://toggl.com/)
大まかに言えば、「ストップウォッチで測った時間がログとして残ることで、時間の使い方を振り返ることができる」サービス。Webアプリやスマホアプリ、Windowsデスクトップアプリ等多くのプラットフォームで利用できる。
遊びと勉強のバランス取りたい的な思惑で、プライベートで利用していた。
（※本来想定されているユースケースは「デスクワーカーが仕事中に利用する」っぽい）

が…。
**ボタンを押してストップウォッチを開始するのが面倒臭い。**
**何をしていた時間かをキーボード入力するのも面倒臭い。**
→測らなくなる。

意味がない…

→Google Homeを使って、音声コマンドでストップウォッチを開始・停止できれば計測のハードルが下がるかも？

# IFTTTで可能？→不可能

IFTTTのGoogle Assistantトリガーは引数を取ることができるし、Togglは[Web API](https://github.com/toggl/toggl_api_docs)を持っているので、トリガーで聞き取った言葉を含めてアクションでAPIを叩けばすぐに実現できる、と初めは考えた。

* IFTTTアクションで**送れるリクエストは1つ**のみ
* リクエストは**送り捨て**であり、レスポンスは扱えない 
* IFTTT **アプレット同士は独立**で情報の受け渡しは不可能
* タイムエントリ**停止APIを叩くにはエントリIDが必要**
  * エントリIDは開始APIを叩いた際のレスポンスに含まれるが、前述の通り扱う術がない

なんの情報もないところからエントリ停止を行いたい場合、

* 計測中のエントリ情報取得API
* タイムエントリ停止API

の2つを叩く必要がある。

## 必要なAPI

### [Start a time entry](https://github.com/toggl/toggl_api_docs/blob/master/chapters/time_entries.md#start-a-time-entry)

`https://www.toggl.com/api/v8/time_entries/start`にPOSTする。

### [Get running time entry](https://github.com/toggl/toggl_api_docs/blob/master/chapters/time_entries.md#get-running-time-entry)

`https://www.toggl.com/api/v8/time_entries/current`にGETする。

### [Stop a time entry](https://github.com/toggl/toggl_api_docs/blob/master/chapters/time_entries.md#stop-a-time-entry)

`https://www.toggl.com/api/v8/time_entries/{time_entry_id}/stop`にPUTする。

# Google Apps Scriptでラッパー作成

* タイムエントリ開始
* 計測中のタイムエントリ停止

ができるラッパーを作成。
※例外処理もなにもない
※おおよそコピペ可能だが、`startEntry`関数の`options`オブジェクトの`payload`プロパティのwidの部分は自分のワークスペースの値に置き換える必要あり。https://toggl.com/app/workspaces で特定のワークスペースの"settings"を選択してワークスペース設定画面に遷移後、URLから確認できる。`https://toggl.com/app/workspaces/[ワークスペースID]/settings?`のようになっている。

```javascript:toggl.gs
function doPost(e){
  var jsonStr = e.postData.getDataAsString();
  var data = JSON.parse(jsonStr);
  Logger.log(data);

  var authData = Utilities.base64Encode(data.user + ':api_token');
  
  if(data.word != "")
  {
    var response = startEntry(data.word, authData);
  }
  else
  {
    var response = stopEntry(authData);
  }
  return ContentService.createTextOutput(response);
}

function startEntry(entryName, authData){
  var startUrl = "https://www.toggl.com/api/v8/time_entries/start";
  var options = {
    'method' : 'post',
    'headers' : {"Authorization" : "Basic " + authData},
    'contentType' : 'application/json',
    'payload' : "{\"time_entry\":{\"description\":\"" + entryName + "\",\"wid\":自分のワークスペースID,\"created_with\":\"Google Apps Script\"}}"
  }
  var response = UrlFetchApp.fetch(startUrl, options);
  return response;
}

function stopEntry(authData){
  var entry_id = getEntryId(authData);
  Logger.log(entry_id);
  var response = stopEntryById(entry_id, authData);
  return response;
}

function getEntryId(authData){
  var currentUrl = "https://www.toggl.com/api/v8/time_entries/current";
  var options = {
    'headers' : {"Authorization" : "Basic " + authData}
  }
  var response = UrlFetchApp.fetch(currentUrl, options);
  var json = JSON.parse(response.getContentText());
  return json.data.id;
}

function stopEntryById(id, authData){
  var stopUrl = "https://www.toggl.com/api/v8/time_entries/" + id + "/stop";
  var options = {
    'method' : 'put',
    'headers' : {"Authorization" : "Basic " + authData},
    'contentType' : 'application/json'
  }
  var response = UrlFetchApp.fetch(stopUrl, options);
  return response;
}
```

## アプリの公開

GoogleドライブでGoogle Apps Scriptプロジェクトを開き、

* 「公開」→「ウェブアプリケーションとして導入」
* 次のユーザーとしてアプリケーションを実行:自分
* アプリケーションにアクセスできるユーザー:全員（匿名ユーザーを含む）

ここで表示されるURLはIFTTTアプレット作成時に利用する。

## ポイント？

Toggl APIの認証はBasic認証であり、

* 認証におけるユーザー名：APIトークン
* 認証におけるパスワード：api_token（固定文字列）

であるという点。
https://www.toggl.com/app/profile
で自分のAPIトークンを確認できる。

# IFTTTアプレット作成

## 開始アプレットThis部 Google Assistantを選択

トリガーを引くためのフレーズ設定等はお好みで

![e4127a57-c41d-46ac-98df-7ac167fbf160.png](https://qiita-image-store.s3.amazonaws.com/0/195900/0207332f-6e2b-de7a-9bb3-a0990d61ff2b.png)

## 開始アプレットThen部 Webhookを選択

URL欄にはGASを「Webアプリケーションとして導入」したときのURLをコピペして入力

Bodyには以下を入力

```
{ "user": "自分のAPIトークン", "word": "{{TextField}}" }
```

`{{TextField}}`にはThis部で`$`に相当する言葉が入り、設定したフレーズに続けた言葉がタイムエントリのタイトルになる。

![d36e9a76-a41b-47cf-8c52-419cb2801ff6.png](https://qiita-image-store.s3.amazonaws.com/0/195900/589b86b4-87e7-f544-0903-89df0b3faf8d.png)

## 停止アプレット

* This部は引数なしの"Say a simple phrase"を選択しお好みで設定
* Then部はBody以外開始アプレットと同様に設定

Bodyには以下を入力

```
{ "user": "自分のAPIトークン", "word": "" }
```

※上記のラッパーは**JSONのword属性が空かどうかで開始か停止かを判断**している。IFTTTアプレットのThis部の設定がまずいと、開始したいのに停止のJSONが送られることがある点には注意。

開始と停止のアプレットを保存すれば、呪文を唱えて開始・停止できるようになる。

# ちょっとした問題

タイムエントリはタグやプロジェクト情報を持つことができるが、今回作った仕組みではそこに触れないので、エントリを分別したい場合あとでタグ付け等しないといけない。

# 余談：なぜ「計測中のエントリを停止」APIが公式にないのか？

「計測中のエントリを停止」APIを用意してくれていれば今回の目的はIFTTTを使うだけで済ませられた。
なぜないのか？
→あるとステートフルになってしまう

「計測中のエントリを停止」APIがあるとすると、クライアント側は今計測中かどうか気にせず叩ける代わりに、サーバー側では状態に応じて振る舞いを変えなければならない（ステートフル）。

Toggl APIの隅々まで検証したわけではないが、REST APIとして設計されているのだとすれば、ステートレスであることが必要になるため「計測中のエントリ取得」「IDを指定して計測停止」の2つのAPIに分かれているのは自然なことである。