mitsu's techlog

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

SwiftでWunderlistのAPI使ってみた

Todoistに続き、WunderlistからもAPIを使ってデータを取ってみました。 これからはGitHubにサンプルも上げていこうと思うので、もしコードをみたい方がいればそちらも見てみてください。

GitHubのサンプルはこちら

全体的な流れはTodoistの時と基本は全く同じなのですが、Wunderlistの方がドキュメントはしっかりしており、パラメータも単にURLにつけるだけではなくHTTPのbodyやheaderにつけないといけなかったりしました。 しかし、パラメータのつけ方だけ気をつければ基本的には大丈夫だと思います!

参考サイト

Wunderlist API Documentation

事前準備

毎回のことですが、こちらからClient_IDとClient_Secretを取得します。

認証

認証もOAuthなので基本的にやることは同じです。

STEP1

リクエストを送信します。エンドポイントは以下のとおりです。 https://www.wunderlist.com/oauth/authorize?client_id=ID&redirect_uri=URL&state=RANDOM

パラメータは3つ

  • client_id : クライアントID
  • redirect_uri : リダイレクトURL
  • state : ランダムな文字列

Todoistのときはredirect_uriがoptionalでwebで設定できたのですが、Wunderlistではrequiredになっていました。

こちらは前回同様WebViewを利用してリクエストを送信しています。 以下のように認証用にWebViewを持つだけのシンプルなVCを作って実装しました。

class Wunderlist {
    private struct Info {
        static let client_id = "your_client_id"
        static let client_secret = "your_client_secret"
        static let redirect_uri = "https://localhost/"
        static let state = "state"
        
        static var code: String? = nil
        static var access_token: String? = nil {
            didSet {
                let ud = NSUserDefaults.standardUserDefaults()
                ud.setValue(access_token, forKey: Wunderlist.accessTokenPath)
            }
        }
    }

    private let authRequestURL = "https://www.wunderlist.com/oauth/authorize?client_id=\(Info.client_id)&redirect_uri=\(Info.redirect_uri)&state=\(Info.state)"

    // --<略>--
}

class WunderlistAuthController: UIViewController, UIWebViewDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
    
        webView = UIWebView(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height))
        webView!.delegate = self
        view.addSubview(webView!)
        if let url = NSURL(string: Wunderlist.sharedInstance.authRequestURL) {
            webView!.loadRequest(NSURLRequest(URL: url))
        }
    }
}

STEP2

ユーザーが認証・許可をするとリダイレクトしてくれるので、WebViewでそれを拾って、access_tokenを取得するためのリクエストを送信します。

まず、URLからcodeを取得します。 shouldStartLoadWithRequestの中でリダイレクト先と一致していた場合、codeを取得するようにしました。

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
    if let url = request.URL {
        if url.host == Wunderlist.sharedInstance.redirectHost {
            let query = url.query!
            // query forms "state=state&code=<code>"
            let code = query.substringFromIndex(advance(query.startIndex, 17))
            println("query: \(query),code: \(code)")
            Wunderlist.sharedInstance.getAccessToken(code)
            return true
        }
    }
    return true
}

次にリクエストをPOSTします。

パラメータは3つ

  • client_id: クライアントID
  • client_secret: クライアントsecret
  • code: 先ほど取得したcode

このパラメータなのですが、Todoistの時とは違い、HTTPBodyに記述します。 なのでコードは以下のような感じになります。

private let accessTokenRequestURL = "https://www.wunderlist.com/oauth/access_token"
private var accessTokenHTTPBody: String? {
    get {
        if let code = Info.code {
            return "client_id=\(Info.client_id)&client_secret=\(Info.client_secret)&code=\(code)"
        }
        return nil
    }
}

private func getAccessToken(code: String) {
    Info.code = code
    let request = NSMutableURLRequest(URL: NSURL(string: accessTokenRequestURL)!)
    request.HTTPMethod = "POST"
    var bodyData = accessTokenHTTPBody!
    request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
    NSURLConnection.sendAsynchronousRequest(request,
        queue: NSOperationQueue.mainQueue(),
        completionHandler: {
            (_, data, _) in
            let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
            let jsonResult = NSJSONSerialization.JSONObjectWithData(data,
                options: NSJSONReadingOptions.MutableContainers,
                error: nil) as! NSDictionary
            Info.access_token = jsonResult["access_token"] as? String
            self.delegate?.didAuthrization(true)
    })
}

これでリクエストを送信すると、無事access_tokenが取得できます。

データの取得

データ取得に関しての注意点

データを取得するためのリクエストにはaccess_tokenを添付する必要がありますが、wunderlistはHTTPのヘッダーの中に情報を格納するように要求しています。 また、X-Access-TokenとX-Client-IDというフィールドを作成する必要があります。 なので、リクエストを作成する際に、以下のようにしてヘッダーを追加します。

今回はエンドポイントを受け取りNSMutableURLRequestにして返す関数を作成しました。 その中でヘッダーにclient_idとaccess_tokenの情報を追加しています。

private func getRequest(endpoint: String) -> NSMutableURLRequest? {
    if let url = NSURL(string: endpoint) {
        let request = NSMutableURLRequest(URL: url)
   request.addValue(Info.access_token, forHTTPHeaderField: "X-Access-Token")
        request.addValue(Info.client_id, forHTTPHeaderField: "X-Client-ID")
        return request
    }
    return nil
}

Listの取得

taskを取得するためには、まずlistを取得してlist_idを知る必要があります。

listを取得するためのエンドポイントはhttps://a.wunderlist.com/api/v1/listsです。 パラメータは特に必要ないので、とりあえずリクエストをおくります。

WunderlistのAPIに関するクラスにgetメソッドを作り、引数としてenumで宣言しておいた何が欲しいかの情報を渡します。 返ってきたjsonからidやtitleなどの情報を取得します。

サンプルの中では、listを取得した際にidを利用してtaskを取得するようにしています。

Wunderlist.sharedInstance.get(.List) {
    if let array = $0 {
        for listObj in array {
            let list = listObj as! NSDictionary
            let id = list["id"] as! NSNumber
            let title = list["title"] as! String
            println("list: id=\(id), title=\(title)")
            self.getTasks(id.stringValue)
            self.lists.append(list)
        }
    }
}

Taskの取得

listを取得した後には、list_idを利用してtaskを取得します。

エンドポイントはhttps://a.wunderlist.com/api/v1/tasksで、必要なパラメータはlist_idです。 また、パラメータにcompletedを追加すると完了済み/未完了のタスクを指定して取得することもできます。

func getTasks(listID: String) {
    Wunderlist.sharedInstance.get(.Task, parameters: ["list_id":listID]) {
        if let array = $0 {
            for taskObj in array {
                let task = taskObj as! NSDictionary
                let id = task["id"] as! NSNumber
                let title = task["title"] as! String
                let completed = task["completed"] as! NSNumber
                let due = task["due_date"] as? String
                println("    task: id=\(id), title=\(title), due=\(due), completed=\(completed.boolValue)")
                self.tasks.append(task)
            }
        }
    }
}