ToodledoのログをGASを使ってGoogleスプレッドシートに保存する方法

TODOの管理にはToodledoを利用していまして、実績をIFTTTに連携してEvernoteで、1日1ノートになるように記録していました。 そうする中で2点困っていることがありました。

  • Evernoteに連携できる項目が少なく、特に予実管理するために必要な項目(Length:予定作業時間、Timer:実績作業時間)が無いため、Evernote側の記録だけを見ても、各タスクの作業時間がわからなかった。
  • 週に1〜2回の頻度で1日複数ノートに別れて記録されてしまい、手でメンテナンスする必要があった。

そこで、ToodledoのAPIを使って、実績を、Googleスプレッドシートに記録することにしました。

実際に行なった事

Toodledo

ToodledoのデータにアクセスするためにAPIを利用します。

APIを利用するためにDeveloper's API Documentation : Register & Stats ✓ Toodledo APIよりアプリケーションを登録し、Client ID、Secretを発行します。

Redirect URLは以下の形式で指定する。 「https://script.google.com/macros/d/{PROJECT KEY}/usercallback」

{PROJECT KEY}はhttps://script.google.com/から確認が出来ます。

Googleスプレッドシート

  1. ツール->スクリプトエディタよりスクリプトエディタを開く
  2. OAuth2 ライブラリをインポートする
    1. エディタのリソース>ライブラリよりライブラリを追加する
    2. プロジェクトキー:MswhXl8fVhTFUH_Q3UOJbXvxhMjh3Sh48
  3. コードを書く
var CLIENT_ID = 'XXXXX', // 発行したClient IDを指定
    CLIENT_SECRET = 'XXXXX'; // 発行したSecretを指定

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Toodledo')
    .addItem('Authorize', 'openAuthDialog')
    .addToUi();
}

function openAuthDialog() {
  var toodledoService = getToodledoService();
  if (toodledoService.hasAccess()) {
    Browser.msgBox('Already autherized');
  } else {
    var authorizationUrl = toodledoService.getAuthorizationUrl();
    var template = HtmlService.createTemplate(
        '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>');
    template.authorizationUrl = authorizationUrl;
    var page = template.evaluate();

    SpreadsheetApp.getUi()
      .showModalDialog(page, 'Authorize');
  }
}

//////////////////////////////////////////////////////////////

function getToodledoService() {
  // Create a new service with the given name. The name will be used when
  // persisting the authorized token, so ensure it is unique within the
  // scope of the property store.
  return OAuth2.createService('Toodledo')
      // Set the endpoint URLs, which are the same for all Toodledo services.
      .setAuthorizationBaseUrl('https://api.toodledo.com/3/account/authorize.php')
      .setTokenUrl('https://api.toodledo.com/3/account/token.php')

      // Set the client ID and secret, from Toodledo
      //   https://api.toodledo.com/3/account/doc_register.php
      .setClientId(CLIENT_ID)
      .setClientSecret(CLIENT_SECRET)
      .setScope('basic tasks')

      // Set the name of the callback function in the script referenced
      // above that should be invoked to complete the OAuth flow.
      .setCallbackFunction('authCallback')

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getUserProperties())
}

function authCallback(request) {
  var toodledoService = getToodledoService();
  var isAuthorized = toodledoService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}

function clearService() {
  OAuth2.createService('Toodledo')
  .setPropertyStore(PropertiesService.getUserProperties())
  .reset();
}

// Reusable function to generate a callback URL, assuming the script has been published as a
// web app (necessary to obtain the URL programmatically). If the script has not been published
// as a web app, set `var url` in the first line to the URL of your script project (which
// cannot be obtained programmatically).
function getCallbackURL(callbackFunction) {
  var url = ScriptApp.getService().getUrl();      // Ends in /exec (for a web app)
  url = url.slice(0, -4) + 'usercallback?state='; // Change /exec to /usercallback
  var stateToken = ScriptApp.newStateToken()
    .withMethod(callbackFunction)
    .withTimeout(120)
    .createToken();
  Logger.log(url + stateToken);
}

////////////////////////////////////////////////////

function getCompletedTasks() {
  var folders = getFolders();
  var contexts = getContexts();
  
  var toodledoService = getToodledoService();
  var response = UrlFetchApp.fetch('http://api.toodledo.com/3/tasks/get.php?access_token=' + toodledoService.getAccessToken() + '&comp=1&after=' + getAfter() + '&fields=folder,context,goal,location,tag,startdate,duedate,duedatemod,starttime,duetime,remind,repeat,status,star,priority,length,timer,added,note,parent,children,order,meta,previous,attachment,shared,addedby,via,attachments');
  var tasks = JSON.parse(response);

  var values = [];
  if (tasks.length > 1) {
    for (var i = 1; i < tasks.length; i++) {
      var task = tasks[i];
      var value = [];
      value.push(task.id);
      value.push(task.title);
      value.push(typeof folders[task.folder] === "undefined" ? "" : folders[task.folder]);
      value.push(task.tag);
      value.push(typeof contexts[task.context] === "undefined" ? "" : contexts[task.context]);
      var startdate = (task.starttime === 0 ? task.startdate : task.starttime);
      value.push(startdate === 0 ? "" : new Date((startdate - 60 * 60 * 9) * 1000)); // GMT UNIXタイムスタンプ->日付に変換
      var duedate = (task.duetime === 0 ? task.duedate : task.duetime);
      value.push(duedate === 0 ? "" : new Date((duedate - 60 * 60 * 9) * 1000)); // GMT UNIXタイムスタンプ->日付に変換    
      value.push(new Date((task.completed - 60 * 60 * 9) * 1000)); // GMT UNIXタイムスタンプ->日付に変換
      value.push(task.length);
      value.push(task.timer / 60); // 秒->分に変換
      value.push(task.note);
      values.push(value);
    }
    
    // 最終行に追記
    var sheet = SpreadsheetApp.getActiveSheet();
    var nextRow = sheet.getLastRow() + 1;
    sheet.getRange(nextRow, 1, values.length, values[0].length).setValues(values);
  }
}

function getFolders() {
  var toodledoService = getToodledoService();
  var response = UrlFetchApp.fetch('http://api.toodledo.com/3/folders/get.php?access_token=' + toodledoService.getAccessToken());
  var folders = JSON.parse(response);

  var map = new Object();
  for (var i = 0; i < folders.length; i++) {
    var folder = folders[i];
    map[folder.id] = folder.name;
  }
  
  return map;
}

function getContexts() {
  var toodledoService = getToodledoService();
  var response = UrlFetchApp.fetch('http://api.toodledo.com/3/contexts/get.php?access_token=' + toodledoService.getAccessToken());
  var contexts = JSON.parse(response);

  var map = new Object();
  for (var i = 0; i < contexts.length; i++) {
    var context = contexts[i];
    map[context.id] = context.name;
  }
  
  return map;
}

// 前日のUNIXタイムスタンプを取得する(翌日深夜にトリガーを起動させることを考慮して)
function getAfter() {
  var now = new Date();
  var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
  return Math.round( yesterday.getTime() / 1000 );
}

設定

OAuthの認可

スプレッドシートを開くとToodledoというメニューが表示されるので、Toodledo->Authorizeから認可を行う(初回のみ)。

トリガーの設定

プログラムで前日分を取得するようにしているので、トリガーも合わせて設定します。

私の場合は、時間主導型で日次で深夜に1回実行させるようにしました。

参考

Google Apps ScriptでOAuth2を使う(GitHubのissueを出力) - dackdive's blog

Developer's API Documentation : Version 3.0 ✓ Toodledo API