Tips:SpreadSheetにRememberTheMilkのタスクを取得する


# 何をするのか
- RememberTheMilkのタスクをスプレッドシートに取得する
 
# なぜするのか
- GoogleSpreadSheetから何かのAPIを使ってみたかった
- GrideAppなど連携すると楽しいかもと思った

# RememberTheMilk API keyの取得
- 以下のURLからNon-Commercial use のAPIkeyとSecretを取得
    - https://www.rememberthemilk.com/services/api/keys.rtm

# GAS
## 設定関連
```js:

// 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)
- 定期的にタスクを取得するためには、この関数を定期実行する
    - とりあえず１シートを更新するプログラムになっているが、日付毎にシートを作って履歴管理をするのもありかも

```js:
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」となる
  
### 取得したタスクをスプレッドシートに転記

```js:
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を使う
    - [参考](https://www.rememberthemilk.com/services/api/authentication.rtm)
    
```js:
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が切れているかどうかを取得する
```js:
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を取得する


```js:

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を押すと以降の処理が進む

```js:
//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) //認証する

```js:
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関数は下記サイトをそのままなので本家からご参照ください。
    - https://qiita.com/SogoK/items/cc0d514ffe74009e5fd5

```
//MD5を求める関数。下記サイトより拝借。
//https://qiita.com/SogoK/items/cc0d514ffe74009e5fd5
function MD5(input) {
...
}
```

# まとめ
- 結局AuthをGASだけではできなかった。
- token保持しておけば毎回認証する必要はないため、定期実行はできそう（GoogleDrive等々にアクセスする際に認証を求められるのと同じ形式かな）
- （tokenの保持をスプレッドシートにしちゃってるのはどうなのかと思う、、、、）
- 貼り付けてみたら変数名がキャメルケースとスネークケースがばらんばらんで慌てて直した。[jsではキャメルケースが一般的っぽいですかね。](https://qiita.com/codeship_tech/items/22a657a758af5f5ef067)
- もうGASからのAPIコワクナイ