mitsu's techlog

Customer Reliability Engineerになりました。備忘録も兼ねて技術ネタを適当に。技術以外はこっち→http://mitsu9.hatenablog.com/

SwiftからTodoistのTodoを取得する

TodoistとTodoistAPIで、毎朝のタスク報告をするヾ(‘ω’)ノを見てSwiftでTodoist使って何かできないかなーと思い、とりあえずAPI使ってみました。

参考サイト Todoist API Documentation

事前準備

こちらにて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に表示しユーザーがログインできるようにします。 f:id:rayc5:20150601055724p:plain

STEP3

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

f:id:rayc5:20150601055719p:plain

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

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

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がある.

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

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

まとめ

よくわからないところで引っかかり、ググってもイマイチ情報が出てこないので時間とられました。 Documentのサンプルがうまく動かないとかは止めて欲しいですね。

サンプルとして全体像を載せておきます。 適当すぎですが、とりあえずは動くはず。。

Todoist_API_sample