@shinotraさんが先日行われたHokkadio.pm #9で面白いことを発表してました。その一つがATNDのイベント参加者の名刺画像を自動生成するというモノ。神奈川にいながら北海道の熱気を感じつつ、昔同じようなことを「Facebookのイベントを対象に」やったなーと思って、もう一度最初から書き直してみました。イベント参加者のリストを取得し、名刺っぽい画像を出力するって代物です。Facebookでの認証処理から画像生成まで興味深いポイントを含んだWebアプリになったので、つくる行程から解説してみます。
ちなみに実装はPerl、WebフレームワークにはMojoliciousを使っていますが、他の言語でも流れは同じなので参考にしてください。
Facebook認証を行う
まず、Facebookから情報を取得するためにアプリケーション登録をします。 「facebook developers」のページからそれが出来るのでその後以下の文字列を取得します。
- アプリID
- アプリのシークレットキー
PerlでFacebookのAPIを扱うには、内部実装は置いておいてw Facebook::Graphというモジュールが便利です。使い方はこんな感じ。
アプリIDとシークレットキーを指定してインスタンスを作成。postback パラメータってのはFacebookの認証画面に行った後に戻ってくるアプリケーションのパスを指定する。
use Facebook::Graph; my $fb = Facebook::Graph->new( app_id => 'your_app_id', secret => 'your_secret', postback => 'http://localhost:3000/callback' );
Facebook側で認証するためのURLを作成する。基本的に「/login」とかそういうパスにアクセスされたら、この「$uri」へリダレイクトさせる。
my $uri = $fb->authorize->extend_permissions(qw/user_events/)->uri_as_string;
インスタンス作成時に postback で指定したパスへユーザーがリダイレクトされるので、その時にくっついていてくる code パラメータを食わせると access_token が取得可能になる。access_token の文字列などはセッションで覚えておくと吉。
$fb->request_access_token($self->req->param('code')); my $access_token = $fb->access_token;
次回以降Facebook APIへアクセスするにはセッションから access_token を取得し、access_token メソッドの引数に渡しておく。
$fb->access_token($access_token);
これを踏まえて、Mojolicious::Liteでの実装はこんな感じになります。helperでFacebook::Graphのインスタンスを取得する「fb」メソッドをはやしていますが、ここはもう少し工夫できるかもですね。
#!/usr/bin/env perl use Mojolicious::Lite; use Facebook::Graph; helper 'fb' => sub { my $c = shift; Facebook::Graph->new( app_id => 'your_app_id', secret => 'your_secret', postback => $c->req->url->base . '/callback', ); }; get '/' => sub { my $self = shift; my $user; if ( my $access_token = $self->session('access_token') ) { my $fb = $self->fb; $fb->access_token($access_token); $user = $fb->fetch('me'); } $self->stash->{user} = $user; $self->render('index'); }; get '/login' => sub { my $self = shift; my $fb = $self->fb; my $uri = $fb->authorize->extend_permissions(qw/user_events/)->uri_as_string; $self->redirect_to($uri); }; get '/callback' => sub { my $self = shift; my $fb = $self->fb; $fb->request_access_token( $self->req->param('code') ); $self->session( 'access_token', $fb->access_token ); $self->redirect_to('/'); }; get '/logout' => sub { my $self = shift; $self->session( 'access_token', undef ); $self->redirect_to('/'); }; app->start; __DATA__ @@ index.html.ep % if ($user) { Hi!, <%= $user->{name} %> <br /> <a href="/logout">Logout</a> % }else{ <a href="/login">Login</a> % }
APIから情報を取得する
Facebook APIはイベントのIDさえ分かれば「/{event_id}/invited」というパスにアクセスしてそのイベントに招待されているユーザーのリストを得る事が出来ます。IDをWebのフォームから入力してもらい、ユーザーリストを出力してみます。
get '/list' => sub { my $self = shift; my $access_token = $self->session('access_token') or $self->redirect_to('/'); my $fb = $self->fb; $fb->access_token($access_token); my $after_id = ''; my $users = []; while (1) { my $res = $fb->query->find( $self->req->param('id') . '/invited' ) ->where_until($after_id)->request->as_hashref; my ($id) = $res->{paging}{next} =~ m!__after_id=([0-9]+)!; last if $after_id eq $id; push @$users, @{ $res->{data} }; $after_id = $id; } $self->stash->{users} = $users; };
ページネーションを考慮している上、その処理を正規表現で行ってるのでややこしい感じですが、 これでユーザーのリストをテンプレートへ渡すことが出来ます。Facebook::Graph::Query で情報取得の条件を決めています。
画像を出力する
さて、いよいよユーザーごとに画像を出力します。サクッと実装するためにこのような仕様しました。
- 画像は名刺サイズのPNG形式で出力する
- 画像に載せる要素はFacebookに登録しているアイコンと名前のみ
- /card/{user_id} というパスにアクセスするとWebアプリからIDで指定されたユーザーの画像が出力される
画像を生成するのにImagerというライブラリを使ってみます。
get '/card/:id' => sub { my $self = shift; my $fb = $self->fb; my $user = $fb->fetch($self->stash->{id}); my $picture_url = $fb->picture($user->{id})->uri_as_string; my $ua = LWP::UserAgent->new; my $res = $ua->get($picture_url); my $content = $res->content; my $image = Imager->new( xsize => 345, ysize => 209 ); $image->box( filled => 1, color => Imager::Color->new('#3b5998') ); my $picture = Imager->new( data => $content ); $image->paste( left => 30, top => 30, src => $picture ); my $font = Imager::Font->new( file => 'ipagp.ttf' ); $image->string( x => 25, y => 140, string => $user->{name}, utf8 => 1, font => $font, size => 38, aa => 1, color => '#ffffff' ); my $data; $image->write( data => \$data, type => 'png' ); $self->render_data( $data, format => 'png' ); };
出来た画像はこちら。
背景色として「#3b5998」を指定してFacebookっぽく雰囲気を出していますw この場合直接Webアプリから画像を配信していますが、ローカルに画像を書き出してごにょごにょしてもよいですね。
まとめ
実際この「Facebookイベント参加者の名刺画像を自動生成する」というユースケースは以前行われたとある集まりでも使われたもので、似たようなコードを書いて非常に実用的なものでした。@shinotraさんのATND参加者から... というのもニーズは結構ありそうなので応援したいところです!こういう応用的、つまりアプリケーションよりのプログラミング、個人的にガンガンやりましょう!って思っています。