GoogleAppsScript
ifttt
GoogleHome

Google HomeからTogglを使う

背景

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

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

意味がない…

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

IFTTTで可能?→不可能

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

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

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

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

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

必要なAPI

Start a time entry

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

Get running time entry

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

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?のようになっている。

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

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

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

Bodyには以下を入力

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

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

d36e9a76-a41b-47cf-8c52-419cb2801ff6.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に分かれているのは自然なことである。