Wunderlist APIを使ってみた

Wunderlistの完了済みタスクをコピーしてEvernoteに保存してタスクを削除、というのを毎週するようにしたのだけど、どうにも面倒くさい。

Documentation | Wunderlist Developer

APIが提供されているから自動化できそう。

検索するとPerlではAPI::Wunderlistというモジュールが公開されているのだけど、インストールがどうも上手く行かなくて、強制インストールするとコードがエラーになる。

http://deps.cpantesters.org/?module=API%3A%3AWunderlist;perl=latest

どうやらType::Tinyのテストが失敗するようなのだけど、コードが抽象的すぎてよく分からんし諦めた。

そもそもX-Client-IDとX-Access-Tokenを付けたHTTPを投げるだけなんだから、curlを使ったシェルスクリプトでもいいんだよね。PerlならLWPをそのまま使ってもいいし。何でインストールに四苦八苦してまでモジュールを入れんといかんねん。

こういうマイクロな実装ってライブラリの導入とか依存関係とかで困るから結局モノリシックなものが便利なんじゃないかという気になってきた(老化)

余談はともかく。完了したタスク+ノートをリストアップするスクリプトを作ってみた。

LWPでもいいけど、REST::ClientといのがLWPベースのシンプルな実装っぽいので使うことにした。

use strict;
use warnings;
use Carp qw/croak/;

use REST::Client;
use HTTP::Response;
use JSON::XS;
use YAML::Syck;

my $JSON   = JSON::XS->new->utf8;
my $API    = 'a.wunderlist.com/api/v1';
my $client = REST::Client->new();
my $res;

$client->addHeader('X-Client-ID', $CLIENT_ID);
$client->addHeader('X-Access-Token', $ACCESS_TOKEN);

# 1. get lists
$client->GET("$API/lists");
$res = res_with_check($client);
### $res

# 2. get completed tasks and notes
my @ctasks;
foreach my $l (@$res){
  ### $l
  $client->GET(sprintf("$API/tasks?completed=true&list_id=%s", $l->{id}));
  $res = res_with_check($client);
  ### $res
  foreach my $t (@$res){
    ### $t
    $client->GET(sprintf("$API/notes?task_id=%s", $t->{id}));
    $res = res_with_check($client);
    ### $res
    push(@ctasks, {
      id           => $t->{id},
      list         => $l->{title},
      title        => $t->{title},
      created_at   => $t->{created_at},
      completed_at => $t->{completed_at},
      note         => ($$res[0]->{content} || ''),
    });
  }
}
### @ctasks

DumpFile("wl_completed.yml", \@ctasks);

sub res_with_check{
  my $client = shift;
  my $code = $client->responseCode();
  if($code ne '200'){
    my $err = sprintf("Server Error: %s\n", HTTP::Response->new($code)->status_line);
    $err .= $client->responseContent() . "\n" if $client->responseContent();
    croak $err;
  }
  my $data = $client->responseContent();
  my $tree = $JSON->decode($data);
  return $tree;
}
  • ドキュメントによるとnotesにはlist_idも指定できるんだけど、未完了タスクだけで完了済みタスクのnoteを取ってこないから、個別のtaskごとにリクエストしないとダメっぽい
    • そもそもWLのタスクに付けてるメモは "Note" ではなく "Task comment" だと思ってて「なんで空のコンテンツが返ってくるんや〜」って数時間悩んでいた。説明が足りない。Documentation | Wunderlist Developer
  • REST::Client->GETがいい感じにエラーハンドリングしてくれて直接contentを返してくれたらコードがスッキリするかなあ
  • JSONで受け取ったデータをYAMLで保存するのもアレだなあと思うんだけど、見やすいし手動で加工しやすいので
  • REST APIをいい感じに扱えるライブラリってなんだろ。rubyとかpythonの方がいいのかなあ。

追記 2018-11-07

http - Perl: Programatically set POST param using REST::Client module - Stack Overflow

POSTでJSONデータを渡すときは明示的に "Content-type: application/json" を付けないといけないらしい。気づかなくて小一時間悩んでしまった・・・。

あと、JSONのencode/decode周りの日本語処理がクソ訳わからなくて心が折れそうになった。

こんな感じでモジュール化したので、今後使う機会があればもっと簡単にできそう。

sub new{
  my $this  = shift;
  my $class = ref($this) || $this;
  my $self  = {};
  bless $self, $class;

  $self->{api}    = 'a.wunderlist.com/api/v1';
  $self->{client} = REST::Client->new;
  $self->{client}->addHeader('X-Client-ID', $client_id);
  $self->{client}->addHeader('X-Access-Token', $access_token);
  return $self;
}

sub post{
  my $self = shift;
  my ($op, $data) = @_;
  my $uri = sprintf("%s/%s", $self->{api}, $op);
  $self->{client}->POST($uri, JSON::XS->new->ascii->encode($data), {"Content-type" => 'application/json'});
  return $self->res_with_check;
}