Amazlet Tool for はてなダイアリー (2)
前回は CGI でちまちま作りましたが、今回はそれを MVC でオブジェクト指向なプログラムに書き直してみます。お手軽なウェブアプリなら Catalyst が一番です。しかし、Catalyst と SOAP::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> <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> </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