天然パーマです。

Facebookイベント参加者の名刺画像を自動生成!

@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' );
};

出来た画像はこちら。

631255029.png

背景色として「#3b5998」を指定してFacebookっぽく雰囲気を出していますw この場合直接Webアプリから画像を配信していますが、ローカルに画像を書き出してごにょごにょしてもよいですね。

まとめ

実際この「Facebookイベント参加者の名刺画像を自動生成する」というユースケースは以前行われたとある集まりでも使われたもので、似たようなコードを書いて非常に実用的なものでした。@shinotraさんのATND参加者から... というのもニーズは結構ありそうなので応援したいところです!こういう応用的、つまりアプリケーションよりのプログラミング、個人的にガンガンやりましょう!って思っています。