SwiftでTodoist API使ってみた

[TodoistとTodoistAPIで、毎朝のタスク報告をするヾ(‘ω’)ﾉ](http://mosowave.hatenablog.com/entry/2014/12/11/102432)を見たりしてSwiftでTodoist使って何かできないかなーと思い、とりあえずAPI使ってみました。

参考サイト
[Todoist API Documentation](https://developer.todoist.com)

([こちら](http://rayc5.hatenablog.com/entry/2015/06/01/060022)に書いたものです。)

## 事前準備
[こちら](https://developer.todoist.com/appconsole.html)にてClient IDとClient Secretを取得する

## 認証

今回はWebViewを使って、認証をおこなえるようにしました。

### STEP1

まず```https://todoist.com/oauth/authorize?client_id=<CLIENT_ID>&scope=<SCOPE>&state=<STATE>&redirect_uri=<REDIRECT_URI>```にリクエストを送ります。

パラメータは4つあります。

- client_id: 事前準備で取得したClient IDです
- scope: 権限の強さを指定します
    - task:add, data:read, data:read_write, data:delete, project:delete
- state: 好きな文字列。CSRFから守るためとありました。
- redirect_uri: リダイレクト先 (Optional, DeveloperサイトからDefaultを設定することができる)

### STEP2

リクエストを送信すると、```https://todoist.com/Users/showLogin?<PARAMETERS>``` が返ってきます。これは以下のようなGoogleかemailでログインすることができるビューです。
このビューをwebViewに表示しユーザーがログインできるようにします。
![auth.png](https://qiita-image-store.s3.amazonaws.com/0/42932/7a7b36b0-d644-7de4-c4d7-48101f9859dc.png)

### STEP3

ユーザーがログインしたら、以下の「同意する」のビューになります。
ここで同意するをクリックすると、STEP1で指定したリダイレクト先にリダイレクトされます。

![agree.png](https://qiita-image-store.s3.amazonaws.com/0/42932/271c6225-2c64-de21-73cf-c791a2c46f28.png)

このときに、パラメーターとしてstateとcodeの2つが添付されています。
このcodeが次に必要になるので、URLからcodeを取得します。

URLは```https://<リダイレクト先>?state=<STATE>&code=<CODE>```という形をしているので、urlからパラメータ部分を抽出し、stateの文字数がわかっているのでそれをもとにcodeの部分だけ抜き出しました。
(もうちょっとスマートにやりたいんですけど何かうまい方法無いのかな。。)

```swift:UIWebViewDelegate
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        let url = request.URL!
        if url.host == "<リダイレクト先のホスト>" {
            let query = url.query!
            // stateの長さに依存する. 12+(stateの長さ). stateをstateに設定したので今回は17.
            let code = query.substringFromIndex(advance(query.startIndex, 17))
            
            // codeを利用してaccess_tokenを取得する処理
            // -- <略> --
        }
        return true
    }
```

### STEP4

先ほど取得したcodeを利用してaccess_tokenを取得します。
```https://todoist.com/oauth/access_token?client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&code=<CODE>```にPOSTリクエストを送信します。

ここでのパラメータは3つです。

- client_id: 事前準備で取得したClient ID
- client_secret: 事前準備で取得したClient Secret
- code: STEP3で取得したcode

これでaccess_tokenを取得できるので、大事に保存しましょう。

### 認証で引っかかった問題

STEP2でGoogleでログインしようとしたときにうまくリダイレクトしてくれませんでした。
メールならログインした後に以下の画面が表示されるのですが、Googleのときはローディングで一切進みません。

以下の画面を表示するために、Googleでログインが完了したタイミングでもう一度STEP1に戻ってリクエストを送信しました。
STEP1のリクエストですが、すでにログインしている場合はメールアドレス/パスワードを入力する画面ではなく、同意するかどうかの画面に飛びます。
なので、もう一度リクエストを送信して同意するかどうかの画面を表示するようにしました。

## データ取得

### タスクの取得

タスクのリストを取得するためには```https://todoist.com/API/v6/sync?token=<TOKEN>&seq_no=0&seq_no_global=0&resource_types=[\"items\"]```にリクエストを送信します。

パラメータは以下の4つです。

- token: アクセストークン
- seq_no: Sequence number. 初めは0, 2回目以降は前回に受け取ったものを利用する.
- seq_no_global: Global sequence number. 初めは0, 2回目以降は前回に受け取ったものを利用する.
- resource_types: 取得したい情報を入力。リスト形式で複数渡すことが可能。
    - パラメータの種類は, projects, items, labels, notes, filters, reminders, locations, user, live_notifications, day_orders, allがある.

こんな感じで取得できました。

```swift
func getTasks() {
    let request = NSMutableURLRequest(URL: NSURL(string: getTasksURL)!)
    NSURLConnection.sendAsynchronousRequest(request,
        queue: NSOperationQueue.mainQueue(),
        completionHandler: {
            (res, data, error) in
            let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
            let jsonResult = NSJSONSerialization.JSONObjectWithData(data,
                options: NSJSONReadingOptions.MutableContainers,
                error: nil) as! NSDictionary
            
            let tasks = jsonResult["Items"] as! NSArray
            for task  in tasks {
                let task = task as! NSDictionary
                let id = task["id"] as! NSNumber
                let title = task["content"] as! String
                let date = task["due_date"] as! String
                println("id: \(id.stringValue), title: \(title), date: \(date)")
            }
    })
}
// 出力結果
id: 71504480, title: today's task 2, date: Mon 01 Jun 2015 04:59:59 +0000
id: 71504414, title: today's task 1, date: Mon 01 Jun 2015 04:59:59 +0000
id: 71504481, title: tomorrow's task 1, date: Tue 02 Jun 2015 04:59:59 +0000
```