タスク管理ツール WunderlistのAPIで遊んでみた

こんにちは、最近は寒すぎて電気カーペットの上で直接寝てしまう木佐貫([@nori4k](https://twitter.com/nori4k))です。

非エンジニアとのタスク共有にWunderlistを使っています。
機能もUIもシンプルで分かりやすいので普段はスマホくらいしか触らない人でも負担なく導入することができとても重宝しています。
WunderlistからAPIも提供されているのでどんな風につかうのか早速試してみました。


## Wunderlist とは


>[Wunderlist](https://www.wunderlist.com/ja/) は、タスク管理を支援するシンプルな ToDo リスト アプリです。家族との買い物リストの共有からプロジェクト管理、休暇の計画まで、Wunderlist を使えば、ToDo の入力、共有、そして実行を簡単に行うことができます。Wunderlist はスマートフォン、タブレット、PC の間で瞬時に同期するAため、あらゆるタスクにどこからでもアクセスできます。   
（iPhoneアプリの説明欄から引用）


Wunderlistではリスト - タスク - サブタスク の３階層構造でタスクを管理する仕組みになっています。
例えば、「買い物」というリストがあり、その中に「ティッシュ」「ミルク」「醤油」「夕飯の買い出し」などのタスクを作って管理します。場合によっては「夕飯の買い出し」に更にサブタスクを作ることができます。

また、リストをカテゴリ分けするためのフォルダという機能もあります。
これは文字通りリストをフォルダ分けすることができる機能です。


## APIを使うための準備

APIを利用するためにはClientIDとAccessTokenが必要です。

1. 以下にアクセス
[https://developer.wunderlist.com/](https://developer.wunderlist.com/)

2. register your app をクリック
2. Create NewAppという画面が開くので必要情報を入力
今回は次のように入力しsaveをクリック
Name: hoge
Description: hogehoge
App icon: なし 
App url: http://myapp.net
Auth Callback url: http://myapp.net

3. My Appという画面が開く
ClientIDをメモする。
Create Access TokenをクリックしてAccessTokenを生成しメモしておく。

これでAPIを使用する準備が整いました！






## APIの動作確認

今回はcurlを使ってAPIの動作を見てみることにします。
詳しくは[公式ドキュメント](https://developer.wunderlist.com/documentation)をご覧ください。


### リスト一覧の取得

リスト一覧を取得してみます。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/lists
```

レスポンス

```json
[
    {
        "created_at": "2017-02-03T16:00:54.570Z", 
        "id": 289113886, 
        "list_type": "inbox", 
        "owner_id": 48481118, 
        "owner_type": "user", 
        "public": false, 
        "revision": 1, 
        "title": "inbox", 
        "type": "list"
    }, 
    {
        "created_at": "2017-02-03T16:01:05.807Z", 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lwf70ac84aecf208b6e54cb227755032", 
        "id": 289113923, 
        "list_type": "list", 
        "owner_id": 48481118, 
        "owner_type": "user", 
        "public": false, 
        "revision": 4, 
        "title": "食料品", 
        "type": "list"
    }
]
```

`inbox`と`食料品`という2つのリストが取得できました。


### フォルダ一覧の取得

フォルダ一覧を取得してみます。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/folders
```

レスポンス

```json
[
    {
        "created_at": "2017-02-03T16:12:24.161Z", 
        "created_by_id": 48481118, 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw0e0b4864b5ea8e8300b002d7f656fb", 
        "id": 6754585, 
        "list_ids": [
            289113923
        ], 
        "revision": 2, 
        "title": "Folder01", 
        "type": "folder", 
        "updated_at": "2017-02-03T16:12:29.360Z", 
        "user_id": 48481118
    }
]
```


### タスク一覧の取得

タスク一覧を取得してみます。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/tasks?list_id=289113923
```

レスポンス

```json
[
    {
        "completed": false, 
        "created_at": "2017-02-03T16:06:02.482Z", 
        "created_by_id": 48481118, 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw9716f473956e5aa3d4e5459e3211e5", 
        "id": 2501790935, 
        "list_id": 289113923, 
        "revision": 2, 
        "starred": false, 
        "title": "タスク", 
        "type": "task"
    }
]
```

タスク一覧が取得できました。この例ではタスクが１つしかありませんでしたが、複数あれば配列の形で一括取得できそうです。
`revision`はタスクに対して何かしらの修正が行われたときにインクリメントされるみたいです。

### サブタスク一覧の取得

サブタスク一覧を取得してみます。



```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/subtasks?list_id=289113923
```

レスポンス

```json
[
    {
        "completed": true, 
        "completed_at": "2017-02-03T16:16:49.162Z", 
        "created_at": "2017-02-03T16:16:39.874Z", 
        "created_by_id": 48481118, 
        "created_by_request_id": "subtask:498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw332052f52407696b86632c27d0c9f1", 
        "id": 1363942139, 
        "revision": 2, 
        "task_id": 2501790935, 
        "title": "サブタスク１", 
        "type": "subtask"
    }, 
    {
        "completed": false, 
        "created_at": "2017-02-03T16:16:46.296Z", 
        "created_by_id": 48481118, 
        "created_by_request_id": "subtask:498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lwe9ed06158ac87dcd846b060d671630", 
        "id": 1363942171, 
        "revision": 1, 
        "task_id": 2501790935, 
        "title": "サブタスク３", 
        "type": "subtask"
    }, 
    {
        "completed": false, 
        "created_at": "2017-02-03T16:16:42.900Z", 
        "created_by_id": 48481118, 
        "created_by_request_id": "subtask:498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw34349df41802ba2c13f4faecfa5d18", 
        "id": 1363942150, 
        "revision": 1, 
        "task_id": 2501790935, 
        "title": "サブタスク２", 
        "type": "subtask"
    }
]
```

サブタスク一覧が取得できました。
list_idを指定することで、リスト内のサブタスクを一括取得できるのが便利です。
もちろんtask_idをしていすることで特定のタスク配下のサブタスクのみを取得することも可能です。


### メモ一覧の取得

メモ一覧を取得してみます。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/notes?list_id=289113923
```

レスポンス

```json
[
    {
        "content": "こちらがノート", 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lwef206de86e88ab1d34ad3e39f08f7d", 
        "id": 172985213, 
        "revision": 1, 
        "task_id": 2501790935, 
        "type": "note"
    }
]
```

### コメント一覧の取得

コメント一覧を取得してみます。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/task_comments?list_id=289113923
```

レスポンス

```json
[
    {
        "author": {
            "avatar": "https://a.wunderlist.com/api/v1/avatar?user_id=48481118", 
            "id": 48481118, 
            "name": "your name"
        }, 
        "created_at": "2017-02-03T16:13:17.000Z", 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw242a3dc76707a21b3661383e388407", 
        "id": 54718254, 
        "local_created_at": "2017-02-03T16:13:21.711Z", 
        "revision": 1, 
        "task_id": 2501790935, 
        "text": "コメントだよ", 
        "type": "task_comment"
    }
]
```


### 添付ファイル一覧の取得

添付ファイル一覧を取得してみます。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/files?list_id=289113923
```

レスポンス

```json
[
    {
        "content_type": "image/png", 
        "created_at": "2017-02-03T16:15:04.303Z", 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw7f6545b24ab8d9e4cd8bff539f903f", 
        "file_name": "SnapCrab_My Apps  Wunderlist Developer - Google Chrome_2017-2-4_1-4-55_No-00.png", 
        "file_size": 147744, 
        "id": 22489318, 
        "local_created_at": "2017-02-03T16:15:04.000Z", 
        "revision": 1, 
        "task_id": 2501790935, 
        "type": "file", 
        "updated_at": "2017-02-03T16:15:04.646Z", 
        "url": "https://download.wunderlist.io/d519bf30-cc59-0134-9abe-22000a0a0772-1486138498-834452?AWSAccessKeyId=AKIAJEN6W4AO3LJODOAA&Expires=1488557704&Signature=vr%2BPL9WLuhP8wF4gY%2FOS3uf%2BdyE%3D", 
        "user_id": 48481118
    }
]
```


### webhookの作成

Webhookも試してみます。
Webhookを登録しておくとWunderlistに変更があった場合に変更内容の通知を受け取ることができます。
ポーリングする必要がなくなるのでWunderlistと連携したアプリ開発をするときには必須ですね。
webhookはリスト毎に設定でき、一つのリストに複数のwebhookを設定できます。

`url=xxxxxx`の部分にcallback URLを設定します。

```bash
curl -H "X-Access-Token: xxxxxx" \
-H "X-Client-ID: yyyyyy" \
-X POST \
-F "list_id=289113923" \
-F "url=http://myapp.net/callback.php" \
-F "processor_type=generic" \
-F "configuration=" \
a.wunderlist.com/api/v1/webhooks
```

callback.phpも用意しておきます。

```php
<?php

$json_string = file_get_contents('php://input'); 
echo $json_string;
$obj = json_decode($json_string);
var_dump($obj);
```

レスポンス

```json
{
    "id": 2502681, 
    "list_id": 289113923, 
    "url": "http://myapp.net/callback.php"
}
```

最初はコールバックURLを`https`で指定していたのですが、wunderlist側からのアクセスがあるもののjsonを受け取ることができせんでした。SSLを諦めて`http`で指定したら無事jsonを受け取れるようになりました。
SSL化に無料で使える"Let's Encrypt"の証明書を使っていたのが悪かったのかもしれません。


### webhookの取得

設定されたwebhookの情報を取得してみます。
リスト毎にしか取得できないので、全リストのwebhookを一括取得できるAPIもほしい。。。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" a.wunderlist.com/api/v1/webhooks?list_id=289113923
```

レスポンス

```json
[
    {
        "id": 2502681, 
        "list_id": 289113923, 
        "url": "http://myapp.net/callback.php"
    }
]
```



### webhookの削除

webhookを削除してみます。

```bash
curl -H "X-Access-Token: xxxxxx" -H "X-Client-ID: yyyyyy" -X DELETE a.wunderlist.com/api/v1/webhooks/2502681
```

レスポンス

```json
なし
```

webhookを削除するには「webhookの取得」で取得したwebhookのidを指定します。
思い込みでlist_idを指定していてエラーの原因がわかるのに小一時間くらいかかりました。。。

### callbackされるjson

通知はタスク / サブタスク / メモ / ファイル / コメント などの作成・更新・削除のタイミングで行われます。
リストの共有なぜか通知されなかったです。

タスクを完了させたときのjsonを参考に掲載しておきます。

```json
{
    "after": {
        "completed": true, 
        "completed_at": "2017-02-03T22:17:06.268Z", 
        "completed_by_id": 48481118, 
        "created_at": "2017-02-03T16:06:02.482Z", 
        "created_by_id": 48481118, 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c291:f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw9716f473956e5aa3d4e5459e3211e5", 
        "id": 2501790935, 
        "is_recurrence_child": false, 
        "list_id": 289113923, 
        "revision": 41, 
        "starred": false, 
        "title": "タスク", 
        "updated_at": "2017-02-03T22:17:06.271Z", 
        "updated_by_id": 48481118
    }, 
    "before": {
        "completed": false, 
        "created_at": "2017-02-03T16:06:02.482Z", 
        "created_by_id": 48481118, 
        "created_by_request_id": "498d3ffc44ddfa2f275b:92840a96-1d01-463c-956b-dff9594c2941:f43d7f5-5be5-45b1-a70c-b3a9-7dc9583d:48481118:lw9716f473956e5aa3d4e5459e3211e5", 
        "id": 2501790935, 
        "is_recurrence_child": false, 
        "list_id": 289113923, 
        "revision": 40, 
        "starred": false, 
        "title": "タスク", 
        "updated_at": "2017-02-03T22:17:05.319Z", 
        "updated_by_id": 48481118
    }, 
    "cause": null, 
    "client": {
        "device_id": "92840a96-1d01-463c-956b-dff9594c2941", 
        "id": "498d3ffc44ddfa2f275b", 
        "instance_id": "f43da7f5-5be5-45b1-a70c-b3a9-7dc9583d", 
        "request_id": "15b8ced2-99b8-4798-8785-3827-91ff2fd9", 
        "user_id": "48481118"
    }, 
    "data": {
        "completed": true, 
        "completed_at": "2017-02-03T22:17:06.268Z", 
        "completed_by_id": 48481118, 
        "revision": 41, 
        "updated_at": "2017-02-03T22:17:06.271Z"
    }, 
    "enqueued_at": 1486160226.2807422, 
    "operation": "update", 
    "subject": {
        "id": 2501790935, 
        "parents": [
            {
                "id": 289113923, 
                "type": "list"
            }
        ], 
        "previous_revision": 40, 
        "revision": 41, 
        "type": "task"
    }, 
    "type": "mutation", 
    "user_id": 48481118, 
    "version": 1
}
```

## URL Scheme

APIとは直接関係ない話になってしまいますが、
iPhoneにはURL Schemeという機構があり、URL形式のリンクをクリックすることでアプリを起動させることができます。（Androidでもあるんでしたっけ？）
WunderlistもURL Schemeに対応しているのですが公式ドキュメントにも説明がなかったので私が見つけたURL Schemeをメモしておきます。


アプリの起動

```js
wunderlist://
```

特定のリスト

```js
wunderlist://lists/:list_id
```

特定のタスク

```js
wunderlist://tasks/:task_id
```

スター付きのタスク

```js
wunderlist://lists/starred
```

期限が今日までのタスク

```js
wunderlist://lists/today
```

期限が今週までのタスク

```js
wunderlist://lists/week
```

すべてのタスク

```js
wunderlist://lists/all
```

完了済みのタスク

```js
wunderlist://lists/completed
```

自分に割り当てられたタスク

```js
wunderlist://lists/assigned
```

## 感想

apiの構想がシンプルなので学習コストが低くすぐに使えるようになりました。
calbackの受取にすこしハマったのでそこくらいですね。