Amazlet Tool for はてなダイアリー (2)

Bayside2006-05-11


前回は CGI でちまちま作りましたが、今回はそれを MVCオブジェクト指向なプログラムに書き直してみます。お手軽なウェブアプリなら Catalyst が一番です。しかし、CatalystSOAP::Lite は 相性が悪いらしいので、さらにお手軽に Net::Amazon を使ってみます。

モデルは DBD::Amazon を使うというディープな方法もありますが、今回はモデルは使いません。ビューは Template::Tookit を使います。コントローラは最近の Catalyst だと最初からついてくるルートコントローラを使います。

自分で動かしてみたい方用にソース一式を こちら においておきます。

アプリケーションの作成

catalyst.pl Amazlet
cd Amazlet/script
./amazlet_create.pl view TT TT
./amazlet_server -r

ファイル構成

./Amazlet/script/amazlet_server.pl
./Amazlet/script/amazlet_create.pl
./Amazlet/lib/Amazlet/View/TT.pm
./Amazlet/lib/Amazlet/Controller/Root.pm
./Amazlet/lib/Amazlet.pm
./Amazlet/root/template/view.html
./Amazlet/root/template/list.html
./Amazlet/root/template/search.html
./Amazlet/amazlet.yml

TT.pm

package Amazlet::View::TT;
use strict;
use base 'Catalyst::View::TT';
1;

Root.pm

package Amazlet::Controller::Root;
use strict;
use warnings;
use base 'Catalyst::Controller';
use Encode;
use Net::Amazon;
use Data::Dumper;
__PACKAGE__->config->{namespace} = '';
my $label_hash = {
    'books-jp'          => '和書',
    'books-us'          => '洋書',
    'music-jp'          => 'ポピュラー音楽',
    'classical-jp'      => 'クラシック',
    'dvd-jp'            => 'DVD',
    'vhs-jp'            => 'ビデオ',
    'electronics-jp'    => 'エレクトロニクス',
    'software-jp'       => 'ソフトウェア',
    'videogames-jp'     => 'ゲーム',
    'kitchen-jp'        => 'ホームキッチン',
    'toys-jp'           => 'おもちゃ&ホビー',
    'sporting-goods-jp' => 'スポーツ',
};
sub default : Private {
    my ( $self, $c ) = @_;
    $c->forward('list');
}
sub list : Local {
    my ( $self, $c ) = @_;
    if ( $c->req->param('keyword') && $c->req->param('search_type') ) {
        # 検索開始
        my $service = Net::Amazon->new(
            token  => $c->config->{developer_token},
            locale => 'jp',
        );
        my $keyword = $c->req->param('keyword');
        Encode::from_to( $keyword, 'euc-jp', 'utf8' );
        my $results = $service->search(
            keyword => $keyword,
            mode    => $c->req->param('search_type')
        );
        # 検索結果を stash に詰める
        foreach my $detail ( @{ $results->{xmlref}->{Details} } ) {
            push( @{ $c->stash->{items} }, $self->gen_hash( $c, $detail ) );
        }
    }
    $c->stash->{template} = 'template/list.html';
}
sub view : Local {
    my ( $self, $c ) = @_;
    if ( $c->req->param('asin') && $c->req->param('search_type') ) {
        # 検索開始
        my $service = Net::Amazon->new(
            token  => $c->config->{developer_token},
            locale => 'jp',
        );
        my $results = $service->search( asin => $c->req->param('asin') );
        # 検索結果を stash に詰める
        foreach my $detail ( @{ $results->{xmlref}->{Details} } ) {
            $c->stash->{item} = $self->gen_hash( $c, $detail );
        }
    }
    $c->stash->{template} = 'template/view.html';
}
sub end : Private {
    my ( $self, $c ) = @_;
    $c->stash->{template} = 'template/list.html'
      unless ( exists $c->stash->{template} );
    # 検索タイプを stash に詰める
    for my $item ( sort keys %{$label_hash} ) {
        push(
            @{ $c->stash->{search_types} },
            { id => $item, name => $label_hash->{$item} }
        );
    }
    $c->forward( $c->view('TT') ) unless $c->response->body;
}
sub gen_hash {
    my ( $self, $c, $detail ) = @_;
    my $hash;
    # UTF-8 から EUC-JP への変換
    for my $item (
        qw(ListPrice OurPrice UsedPrice ImageUrlSmall ImageUrlMedium ImageUrlLarge Availability Catalog Asin Url Manufacturer ProductName)
      ) {
        if ( exists $detail->{$item} ) {
            if ( $item eq 'Catalog' ) {
                $hash->{$item} = $label_hash->{ $c->req->param('search_type') };
            } else {
                $hash->{$item} = Encode::encode( 'euc-jp', $detail->{$item} );
            }
        } else {
            $hash->{$item} = '不明';
        }
    }
    return $hash;
}
1;

Amazlet.pm

package Amazlet;
use strict;
use warnings;
use Catalyst qw/-Debug StackTrace ConfigLoader Charsets::Japanese ConfigLoader Static::Simple/;
our $VERSION = '0.01';
__PACKAGE__->config( name => 'Amazlet' );
__PACKAGE__->setup;
1;

view.html

<html>
<head>
<title>Amazlet Tool for はてなダイアリー</title>
<link rel="stylesheet" type="text/css" href="http://d.hatena.ne.jp/theme/hatena/hatena.css" />
</head>
<body>
<form method="post" action="/list">
<!-- 検索フォーム -->
[% INCLUDE template/search.html -%]
<!-- 詳細モード -->
<div class="hatena-asin-detail"><a href="http://www.amazlet.com/browse/ASIN/[% c.stash.item.Asin %]/[% c.config.affiliate_id %]"><img src="[% c.stash.item.ImageUrlMedium %]" class="hatena-asin-detail-image" alt="[% c.stash.item.ProductName %]" title="[% c.stash.item.ProductName %]"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="http://www.amazlet.com/browse/ASIN/[% c.stash.item.Asin %]/[% c.config.affiliate_id %]">[% c.stash.item.ProductName %]</a></p><ul><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> [% c.stash.item.Manufacturer %]</li><li><span class="hatena-asin-detail-label">価格:</span> [% c.stash.item.ListPrice %]</li><li><span class="hatena-asin-detail-label">メディア:</span> [% c.stash.item.Catalog %]</li></ul></div><div class="hatena-asin-detail-foot"></div></div>
<!-- 詳細モード -->
<pre>
&lt;div class="hatena-asin-detail"&gt;&lt;a href="http://www.amazlet.com/browse/ASIN/[% c.stash.item.Asin %]/[% c.config.affiliate_id %]"&gt;&lt;img src="[% c.stash.item.ImageUrlMedium %]" class="hatena-asin-detail-image" alt="[% c.stash.item.ProductName %]" title="[% c.stash.item.ProductName %]"&gt;&lt;/a&gt;&lt;div class="hatena-asin-detail-info"&gt;&lt;p class="hatena-asin-detail-title"&gt;&lt;a href="http://www.amazlet.com/browse/ASIN/[% c.stash.item.Asin %]/[% c.config.affiliate_id %]"&gt;[% c.stash.item.ProductName %]&lt;/a&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="hatena-asin-detail-label"&gt;出版社/メーカー:&lt;/span&gt; [% c.stash.item.Manufacturer %]&lt;/li&gt;&lt;li&gt;&lt;span class="hatena-asin-detail-label"&gt;価格:&lt;/span&gt; [% c.stash.item.ListPrice %]&lt;/li&gt;&lt;li&gt;&lt;span class="hatena-asin-detail-label"&gt;メディア:&lt;/span&gt; [% c.stash.item.Catalog %]&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class="hatena-asin-detail-foot"&gt;&lt;/div&gt;&lt;/div&gt;
</pre>
</body>
</html>

list.html

<html>
<head>
<title>Amazlet Tool for はてなダイアリー</title>
<link rel="stylesheet" type="text/css" href="http://d.hatena.ne.jp/theme/hatena/hatena.css" />
</head>
<body>
<form method="post" action="/list">
<!-- 検索フォーム -->
[% INCLUDE template/search.html -%]
<!-- 一覧 -->
[% FOREACH item = c.stash.items -%]
<table border="1" width="80%">
  <tr>
    <td>
<div class="hatena-asin-detail"><a href="http://www.amazlet.com/browse/ASIN/[% item.Asin %]/[% c.config.affiliate_id %]"><img src="[% item.ImageUrlMedium %]" class="hatena-asin-detail-image" alt="[% item.ProductName %]" title="[% item.ProductName %]"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="http://www.amazlet.com/browse/ASIN/[% item.Asin %]/[% c.config.affiliate_id %]">[% item.ProductName %]</a></p><ul><li><span class="hatena-asin-detail-label">出版社/メーカー:</span> [% item.Manufacturer %]</li><li><span class="hatena-asin-detail-label">価格:</span> [% item.ListPrice %]</li><li><span class="hatena-asin-detail-label">メディア:</span> [% item.Catalog %]</li></ul></div><div class="hatena-asin-detail-foot"></div></div><div align="center"><a href="/view?asin=[% item.Asin %]&search_type=[% c.req.param('search_type') %]">リンクを作成</a></div>
    </td>
  </tr>
</table>
[% END -%]
</body>
</html>

search.html

<table border="1" width="80%">
  <tr>
    <th>キーワード</th>
    <td>
      <input type="text" name="keyword" size="40" value="[% c.req.param('keyword') %]" />
    </td>
  </tr>
  <tr>
    <th>検索種別</th>
    <td>
      <select name="search_type">
      [% FOREACH type = c.stash.search_types -%]
        [% IF c.req.param('search_type') == type.id -%]
        <option value="[% type.id %]" selected>[% type.name %]</option>
        [% ELSE -%]
        <option value="[% type.id %]">[% type.name %]</option>
        [% END -%]
      [% END -%]
      </select>
    </td>
  </tr>
  <tr>
    <th colspan="2">
      <input type="submit" name="submit" value="検索" />
      <input type="reset" value="クリア">
    </th>
  </tr>
</table>
</form>

amazlet.yml

---
name: Amazlet
charsets: EUC-JP
amazon_wdsl: http://soap.amazon.co.jp/schemas3/AmazonWebServices.wsdl
developer_token: xxxxxx
affiliate_id: xxxxxx