WebService::Simple::Flickrを作りました

前回,Flickr::API を使って Flickr の認証のやり方がわかりましたが,なんか Flickr だけの モジュールを使うのもしゃくなので,ゆーすけべーさんが公開している WebSerivice::Simple を継承して認証できる形のモジュールを作ったので公開します.

WebService::Simple::Flickr

Flickr の認証はちょっと面倒な形をしています.最初に触る API にすべきじゃないです w 何が面倒かというと,パラメータを送るとき,それを並べてハッシュをとったものを 署名として付けないといけない点です.この部分を実装しておきます.

get()関数をオーバーライド

sub get {
    my ($self, $args) = @_;
    $args->{api_key} = $self->{api_key};

    if(defined $self->{api_secret} && length $self->{api_secret}){
        if(defined $self->{auth_token} && length $self->{auth_token}){
            $args->{auth_token} = $self->{auth_token};
        }
        $args->{api_sig} = $self->sign_args($args);
    }
    return $self->SUPER::get($args);
}

まぁコード見てもらえば分かる通りで,Flickr::API からほぼそのまま持って来た sign_args()関数を使ってハッシュを作り,それを api_sig というパラメータで 追加しています.api_key と api_secret はあらかじめ与えておき,auth_token は 後に示す手順で取得しておいて与えておきます.

使い方は,WebService::Simple の get()と同じで,

my $flickr = WebService::Simple::Flickr->new(
    api_key => $api_key, api_secret => $secret_key, auth_token => $token);
my $res = $flickr->get({method => "flickr.photosets.getList"});

なんて感じでハッシュにつっこんで get すれば OK.この例の場合,(token が取得して あれば)自分の Set のリストがプライベート設定のやつも含めて取得できているはずです.

コンストラクタもオーバーライド

sub new {
    my $class    = shift;
    my %args     = @_;
    my $api_key = delete $args{api_key} || "";
    my $api_secret = delete $args{api_secret} || "";
    my $auth_token = delete $args{auth_token} || "";

    my $self = $class->SUPER::new(%args);
    $self->{api_key} = $api_key;
    $self->{api_secret} = $api_secret;
    $self->{auth_token} = $auth_token;
    return $self;
}

さっきの例で分かる通り,api_key とかをコンストラクタに渡しちゃってます. よくわかんないですが,とりあえず WebService::Simple のコンストラクタのやってることを パクって,api_key, api_secret, auth_token を持っておくようにします. ちなみに,インスタンス作ったあとに,$flickr->{auth_token}とかで与えても 大丈夫みたいなので,この実装は要らないかも.

upload_post()メソッドを追加

use base qw(WebService::Simple);
__PACKAGE__->config(
    base_url   => "http://api.flickr.com/services/rest/",
    upload_url   => "http://api.flickr.com/services/upload/",
    );

....

sub upload_post {
    my ($self, $args) = @_;
    $args->{api_key} = $self->{api_key};
    local $self->{base_url} = $self->config->{upload_url};

    my $photo = delete $args->{photo};

    if(defined $self->{api_secret} && length $self->{api_secret}){
        if(defined $self->{auth_token} && length $self->{auth_token}){
            $args->{auth_token} = $self->{auth_token};
        }
        $args->{api_sig} = $self->sign_args($args);
    }
    $args->{photo} = [$photo] if ref $photo ne "ARRAY";
    return $self->post("", Content_Type => "form-data", Content => $args);
}

Flickr のアップロード用の API は URL が違ったりするし,POST メソッドだったり するので,別に用意します.WebService::Simple の POD の中に例が書いてありますが そのままだと extra_url まわりで失敗するので,post メソッドの最初の変数に空文字列を 与えています.呼び出し方は後の例を参考に.

request_auth_url メソッドを追加

sub request_auth_url {
    my ($self, $perms, $frob) = @_;

    return undef unless defined $self->{api_secret} && length $self->{api_secret};

    my %args = (
        'api_key' => $self->basic_params->{api_key},
        'perms'   => $perms
        );

    if ($frob) {
        $args{frob} = $frob;
    }

    my $sig = $self->sign_args(\%args);
    $args{api_sig} = $sig;

    my $uri = URI->new('http://flickr.com/services/auth');
    $uri->query_form(%args);

    return $uri;
}

これは Flickr::API からまるパクリ.呼び出し方も当然同じ.後で使います.

とりあえずこれを使ってトークンを取得してみよう

前回 Flickr::API でやったことを WebService::Simple::Flickr を使ってやると こんな感じ.

use utf8;
use WebService::Simple::Flickr;
use Data::Dumper;

my $api_key = "*********************************";
my $secret_key = "**********************";

my $flickr = WebService::Simple::Flickr->new(
    api_key => $api_key, api_secret => $secret_key);

my $ref;
$ref = $flickr->get({method => "flickr.auth.getFrob"});
my $frob = $ref->parse_response->{frob};
print $frob . "\n";
print $flickr->request_auth_url('write', $frob);

my $line =<stdin>; #表示されるURLにブラウザからアクセスしてOKしたらEnter
$ref = $flickr->get({method => "flickr.auth.getToken", frob =>$frob});
my $token = $ref->parse_response->{auth}->{token};
print $token . "\n";
$flickr->{auth_token} = $token;

$ref = $flickr->upload({photo => "/home/user/image.jpg"});
print Dumper $ref->parse_response;

こんな感じで写真がアップロードできました.やりましたね!

いつも通り github に置いておきます

というわけで,モジュールのソースは github に上げておきます.Plagger とは別の レポジトリを作りました.

perllib/WebService/Simple/Flickr.pm

package WebService::Simple::Flickr;

use Digest::MD5 qw(md5_hex);

use base qw(WebService::Simple);
__PACKAGE__->config(
    base_url => "http://api.flickr.com/services/rest/",
    upload_url => "http://api.flickr.com/services/upload/",
    );

sub new {
    my $class = shift;
    my %args = @_;
    my $api_key = delete $args{api_key} || "";
    my $api_secret = delete $args{api_secret} || "";
    my $auth_token = delete $args{auth_token} || "";

    my $self = $class->SUPER::new(%args);
    $self->{api_key} = $api_key;
    $self->{api_secret} = $api_secret;
    $self->{auth_token} = $auth_token;
    return $self;
}

sub api_key { $_[0]->{api_key} }
sub api_secret { $_[0]->{api_secret} }
sub auth_token { $_[0]->{auth_token} }

sub sign_args {
    my ($self, $args) = @_;

    my $sig = $self->api_secret;
    foreach my $key (sort {$a cmp $b} keys %{$args}) {
        my $value = (defined($args->{$key})) ? $args->{$key} : "";
        $sig .= $key . $value;
    }
    print "sig=" . $sig . "\n";
    return md5_hex($sig);
}

sub get {
    my ($self, $args) = @_;
    $args->{api_key} = $self->{api_key};

    if(defined $self->{api_secret} && length $self->{api_secret}){
        if(defined $self->{auth_token} && length $self->{auth_token}){
            $args->{auth_token} = $self->{auth_token};
        }
        $args->{api_sig} = $self->sign_args($args);
    }
    return $self->SUPER::get($args);
}

sub upload_post {
    my ($self, $args) = @_;
    $args->{api_key} = $self->{api_key};
    local $self->{base_url} = $self->config->{upload_url};

    my $photo = delete $args->{photo};

    if(defined $self->{api_secret} && length $self->{api_secret}){
        if(defined $self->{auth_token} && length $self->{auth_token}){
            $args->{auth_token} = $self->{auth_token};
        }
        $args->{api_sig} = $self->sign_args($args);
    }
    $args->{photo} = [$photo] if ref $photo ne "ARRAY";
    return $self->post("", Content_Type => "form-data", Content => $args);
}

sub request_auth_url {
    my ($self, $perms, $frob) = @_;

    return undef unless defined $self->{api_secret} && length $self->{api_secret};

    my %args = (
        'api_key' => $self->basic_params->{api_key},
        'perms' => $perms
        );

    if ($frob) {
        $args{frob} = $frob;
    }

    my $sig = $self->sign_args(\%args);
    $args{api_sig} = $sig;

    my $uri = URI->new('http://flickr.com/services/auth');
    $uri->query_form(%args);

    return $uri;
}