何をするのか
- RememberTheMilkのタスクをスプレッドシートに取得する
 
なぜするのか
- GoogleSpreadSheetから何かのAPIを使ってみたかった
 - GrideAppなど連携すると楽しいかもと思った
 
RememberTheMilk API keyの取得
- 以下のURLからNon-Commercial use のAPIkeyとSecretを取得
 
GAS
設定関連
// AuthParams
var apiKey = "apikey_xxxxxxxxxxxx"
var secret = "secret_xxxxxxxxx"
// SpreadSheetSettings
var spreadsheetId = "spreadsheetid_xxxxxxxxxxxxxx"
var spreadsheetSettingSheetname = "Settings"
var spreassheetOutputSheetname = "Output"  
var spreadsheet = SpreadsheetApp.openById(spreadsheetId)
var sheet = spreadsheet.getSheetByName(spreadsheetSettingSheetname)
var tokenRange = sheet.getRange(1,2)
//requestUrls
var restBaseUrl = "https://api.rememberthemilk.com/services/rest/"
var authBaseUrl = "https://www.rememberthemilk.com/services/auth/"
//Parameters
var listId = xxxxxxx;//取得したいTaskListのId
Mainとなるフロー
- Taskの取得のフローは下記
- tokenが切れていたら再度tokenを取得、切れていなければそのままtokenを使う
- token期限が切れているかを確認 function checkToken(token)
 - frobを取得 function getFlob()
 - frobを使って認証する function auth(flob)
 - frobをtokenに交換(この時点でfrobが破棄される) function getToken(frob)
 
 - 以降はtokenを使ってapiを呼び出す function getTaskList(token, listId)
 
 - tokenが切れていたら再度tokenを取得、切れていなければそのままtokenを使う
 - 定期的にタスクを取得するためには、この関数を定期実行する
- とりあえず1シートを更新するプログラムになっているが、日付毎にシートを作って履歴管理をするのもありかも
 
 
function getTasksFromRtm() {
  
  token = tokenRange.getValue()
  if (checkToken(token) == false){
    //tokenの期限が切れている場合、新しく認証する(URLが表示されるのでブラウザ上でアクセスしたあと、OKボタン)
    var frob = getFrob()
    var authResult = auth(frob)
    var token = getToken(frob)
    tokenRange.setValue(token) //tokenをシートに保持
    }
  var taskList = getTaskList(token, listId)
  //Logger.log(tasks)
  var tasks = taskList[0]["taskseries"]
  toSpreadSheet(spreassheetOutputSheetname, tasks)
  //Logger.log(tasks[0]["taskseries"][0])
}
Module
- 全体として、API呼び出しをするときのルールがある
- リクエストパラメータをmd5でハッシュ化したauth_sigを付与する必要がある
 - 例えば、「?method=aaa&list_id=bbb&auth_key=ccc」の場合、下記(※パラメータ名のアルファベット順に並び替えてkeyvalueを連結したもの)をmd5でハッシュ化する。
- 「auth_keyccclist_idbbbmethodaaa」
 
 - ハッシュ化したものを「zzzzzz」とすると、最終的なリクエストパラメータは下記となる
- 「?method=aaa&list_id=bbb&auth_key=ccc&auth_sig=zzzzzz」となる
 
 
 
取得したタスクをスプレッドシートに転記
function toSpreadSheet(outputSheetname, tasks){
  var outputSheet = spreadsheet.getSheetByName(outputSheetname)
  outputSheet.clear()
  
  //転記するサイズ
  var rowSize = tasks.length
  //Header
  outputSheet.getRange(1, 1).setValue("id")
  outputSheet.getRange(1, 2).setValue("title")
  outputSheet.getRange(1, 3).setValue("completed")
  
  for(var i = 1; i < rowSize + 1 ; i++){
    outputSheet.getRange(i+1,1).setValue(tasks[i-1].id);
    outputSheet.getRange(i+1,2).setValue(tasks[i-1].name);
    outputSheet.getRange(i+1,3).setValue(tasks[i-1]["task"][0].completed); //完了の日付
  }  
}
function getFrob() //frobを取得する
- 今回、taskの取得だけを目指すのでparams=readとしているが、更新や削除したいときはwrite/deleteを使う
 
function getFrob(){
  //frobをjson形式で取得する
  var methodToGetfrob = "rtm.auth.getFrob"
  var getFrobUrl = restBaseUrl + "?method=" + methodToGetfrob + "&api_key=" + apiKey + "&format=json&perms=read" //読み取りのみ
  var frobSign = secret + "api_key" + apiKey + "formatjson" + "method"+  methodToGetfrob  + "permsread" 
  var encoded = MD5(frobSign)
  getFrobUrl = getFrobUrl + "&api_sig=" + encoded
  
  Logger.log(getFrobUrl);
  var frobJson = UrlFetchApp.fetch(getFrobUrl).getContentText()
  var json = JSON.parse(frobJson);
  if (json["rsp"]["stat"] == "ok") {
    frob = json["rsp"]["frob"]
  }
  Logger.log(frob)
  return frob
}
function checkToken() //tokenが切れているかどうかを取得する
function checkToken(token){
  var methodToCheckToken = "rtm.auth.checkToken"
  var getCheckTokenUrl = restBaseUrl + "?method=" + methodToCheckToken + "&api_key=" + apiKey  + "&auth_token=" + token  +"&format=json" //読み取りのみ
  var checkTokenSign = secret + "api_key" + apiKey+ "auth_token"+ token + "formatjson" + "method"+  methodToCheckToken  
  Logger.log(checkTokenSign)
  encoded = MD5(checkTokenSign)
  getCheckTokenUrl = getCheckTokenUrl + "&api_sig=" + encoded
  
  Logger.log(getCheckTokenUrl)
  var checktokenJson = UrlFetchApp.fetch(getCheckTokenUrl).getContentText()
  var json = JSON.parse(checktokenJson);
  if (json["rsp"]["stat"] == "ok") {
    //tasks = json["rsp"]["tasks"]["list"]
    Logger.log(checktokenJson);
    return true
  }
  Logger.log(checktokenJson);
  return false
}
function getTaskList(token, tasklistId) //taskを取得する
function getTaskList(token, tasklistId){
 
  var methodToGetTask = "rtm.tasks.getList"
  var getTaskUrl = restBaseUrl + "?method=" + methodToGetTask + "&api_key=" + apiKey  + "&auth_token=" + token  + "&list_id=" + tasklistId +"&format=json" //読み取りのみ
  var taskSign = secret + "api_key" + apiKey+ "auth_token"+ token + "formatjson" + "list_id" + tasklist_id + "method"+  methodToGetTask  
  Logger.log(taskSign)
  encoded = MD5(taskSign)
  getTaskUrl = getTaskUrl + "&api_sig=" + encoded
  
  Logger.log(getTaskUrl)
  var taskJson = UrlFetchApp.fetch(getTaskUrl).getContentText()
  var json = JSON.parse(taskJson);
  if (json["rsp"]["stat"] == "ok") {
    tasks = json["rsp"]["tasks"]["list"]
    return tasks
  }
  Logger.log(taskJson);
  return false
}
function auth(frob) //認証する
- GASだけではうまく認証できなかったので、次のような手順で手動認証
- tokenが無効の時、認証用のURLをDialogに表示する
 - (手動)認証用のURLをブラウザに貼り付け、アクセス許可をする
 - DialogでOKを押すと以降の処理が進む
 
 
//Auth Miyuki Kondo 2020/12/28
function auth(frob){
    
  var authUrl = authBaseUrl + "?api_key=" + apiKey + "&frob=" + frob + "&format=json&perms=read" //読み取りのみ
  var authSign = secret + "api_key" + apiKey + "formatjson" + "frob" + frob + "permsread" 
  encoded = MD5(authSign)
  authUrl = authUrl + "&api_sig=" + encoded
  Logger.log(authUrl);
  
  //下記ではAuthできなかったので、Msgに出るURLを開く形式に。
  //var auth = UrlFetchApp.fetch(authUrl, {method:"post"}).getContentText()
  //Logger.log(auth)
  
  //Authしていない場合は、ブラウザでアクセスして許可、DialogでOKが出るまでWaitする。
  var result = Browser.msgBox(authUrl, Browser.Buttons.OK_CANCEL)
  return true
}
function getToken(frob) //認証する
function getToken(frob){
  //tokenをjson形式で取得する
  var methodToGetToken = "rtm.auth.getToken"
  var getTokenUrl = restBaseUrl + "?method=" + methodToGetToken + "&api_key=" + apiKey + "&frob=" + frob +"&format=json&perms=read" //読み取りのみ
  var tokenSign = secret + "api_key" + apiKey + "formatjson" + "frob" + frob + "method"+  methodToGetToken  + "permsread" 
  encoded = MD5(tokenSign)
  getTokenUrl = getTokenUrl + "&api_sig=" + encoded
  Logger.log(getTokenUrl)
  
  var tokenJson = UrlFetchApp.fetch(getTokenUrl).getContentText()
  var json = JSON.parse(tokenJson);
  if (json["rsp"]["stat"] == "ok") {
    token = json["rsp"]["auth"]["token"]
    Logger.log(tokenJson)
    Logger.log(token)
    return token
  }
  Logger.log(tokenJson)
  return false
}
- MD5関数は下記サイトをそのままなので本家からご参照ください。
 
//MD5を求める関数。下記サイトより拝借。
//https://qiita.com/SogoK/items/cc0d514ffe74009e5fd5
function MD5(input) {
...
}
まとめ
- 結局AuthをGASだけではできなかった。
 - token保持しておけば毎回認証する必要はないため、定期実行はできそう(GoogleDrive等々にアクセスする際に認証を求められるのと同じ形式かな)
 - (tokenの保持をスプレッドシートにしちゃってるのはどうなのかと思う、、、、)
 - 貼り付けてみたら変数名がキャメルケースとスネークケースがばらんばらんで慌てて直した。jsではキャメルケースが一般的っぽいですかね。
 - もうGASからのAPIコワクナイ