Net::TwitterでlistのAPIを使えるようにしようとして挫折

Twitterにlist機能がついて、そのAPIも使えるようになっているらしい。
Google グループ
ということで、これらを簡単に扱えるようにNet::Twitterモジュールを拡張できないか、と試行錯誤してみた。


Net::Twitter::Liteの方は、

my $api_def = [
    .....
];

while ( @$api_def ) {
    .....
    no strict 'refs';
    *{__PACKAGE__ . "::$_"} = $code for $name, @{$options{aliases}};
}

という具合にAPIメソッドが定義されてしまっていて、手が出しにくい、と思って早々に挫折。


Net::Twitterの方は。
ソースコードを追いかけてみたけど、Moose素人な自分には仕組みが理解しにくい。


とりあえず、多くのTwitter APIメソッドが定義されているのはNet::Twitter::Role::API::RESTらしい。
ここでtwitter_api_methodというメソッドにAPIメソッド名とパラメータを渡してNet::TwitterインスタンスからそれらのAPIメソッドを呼び出せるようにしているようだ。このtwitter_api_methodというのはNet::Twitter::APIで定義されている。


で、実際に上記のメソッド定義処理がどこから呼び出されるかというと、Net::Twitterのnew時、ここで$traitsを判定し、この$traitsを元にcreate_anon_classというのを呼び出したりしている。例えばnewに何も引数を渡さなかった場合は$traitsは'Legacy'が指定され、色々処理をした結果Net::Twitter::Role::Legacyというrolesを持つ匿名クラス(?)が作られることになる、のかな。
そしてNet::Twitter::Role::Legacyでは、Moose::Role、

with map "Net::Twitter::Role::$_", qw/
    API::REST
    API::Search
    API::TwitterVision
    WrapError
/;

という呼び出しがなされている。ここでNet::Twitter::Role::API::RESTが呼ばれてtwitter_api_methodが定義されることになるのだろう、たぶん。


さて、Twitter APIメソッドの定義はすべてnew時に自動でロードされる仕組みのようなので、インスタンスを作ってから後付け的にメソッドを追加するのは結構ムリがあるようだ。
となると方法は、Roleを自分で定義して、new時にtraitsを明示指定してやる、という方法?
Net::Twitter::APIのドキュメントに定義の仕方が書いてある。これを参考に、例えば

#!/opt/local/bin/perl
{
    package Net::Twitter::Role::API::REST::List;
    use Net::Twitter::API;
    use Moose::Role;
    with 'Net::Twitter::Role::API::REST';

    twitter_api_method sugyan_lists => (
        description => '',
        path     => 'sugyan/lists',
        method   => 'GET',
        params   => [],
        required => [],
    );
    twitter_api_method sugyan_memberships => (
        description => '',
        path     => 'sugyan/lists/memberships',
        method   => 'GET',
        params   => [],
        required => [],
    );
}
use strict;
use warnings;

use Config::Pit;
use Net::Twitter;

my $config = pit_get('twitter.com', require => {
    'username' => 'your username on twitter',
    'password' => 'your password on twitter'
});
my $twitter = Net::Twitter->new(
    traits => [qw/API::REST::List/],
    %$config,
);

print "lists:\n";
print "$_->{full_name}\n" for @{$twitter->sugyan_lists->{lists}};
print "\n";
print "memberships:\n";
print "$_->{full_name}\n" for @{$twitter->sugyan_memberships->{lists}};

というのを書いてやると、自分で定義したNet::Twitter::Role::API::REST::Listが、Net::Twitterのnew時にtraitsで指定することにより使われるようになる。

$ perl hoge.pl 
lists:
@sugyan/hoge
@sugyan/zenra

memberships:
@mayahjp/who-knows-me
@anonymizer/who-i-met
@keireki/early-members
@ysakaki/mitaka-rb
@kaede_a_a3/programmers-heaven
@sakurako_s/perl-ja
@masapong/met
@Hasegawaa/celebrity
@noblejasper/perl-casual
@yamashiro/zenra
@yamashiro/test
@amachang/geek-ja
@ooooooooo/mojicon
@pantherhead/zenra
@mattn_jp/subtech
@sumiek/engineers
@Harraboo/it
@ats/python
@tsuyoshikawa/oopjog
@Ewigkeit/java-ja

こうして一応、自分でメソッドを定義して指定したpathに対応するAPIメソッドを叩けるようにはなった。


…が、上記のものはuser名を直打ちしているので汎用性が無い。できることなら

$twitter->lists({ user => 'sugyan' });
$twitter->menberships({ user => 'sugyan' });

とか

$twitter->list_statuses({
    user => 'sugyan',
    list => 'zenra',
});

とかいう使い方ができるようにしたい。
…けど、現状のtwitter_api_methodの仕組み上、パラメータに渡すのが基本になっていて、pathでuser名やlist名を指定してAPIを叩けるようにはなっていない。このtwitter_api_methodそのものも書き換える必要がありそう…。


と思ってメソッド書き換えようと思ったけど上手くいかず。うーん、Net::Twitterがlist対応updateするのを待った方が良いかな…?