diff options
| author | Andreas Brachold <vdr07@deltab.de> | 2007-08-13 18:41:27 +0000 |
|---|---|---|
| committer | Andreas Brachold <vdr07@deltab.de> | 2007-08-13 18:41:27 +0000 |
| commit | bcbf441e09fb502cf64924ff2530fa144bdf52c5 (patch) | |
| tree | f377707a2dac078db8cd0c7d7abfe69ac1006d71 /lib/Net/Amazon.pm | |
| download | xxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.gz xxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.bz2 | |
* Move files to trunk
Diffstat (limited to 'lib/Net/Amazon.pm')
| -rw-r--r-- | lib/Net/Amazon.pm | 1255 |
1 files changed, 1255 insertions, 0 deletions
diff --git a/lib/Net/Amazon.pm b/lib/Net/Amazon.pm new file mode 100644 index 0000000..4630c11 --- /dev/null +++ b/lib/Net/Amazon.pm @@ -0,0 +1,1255 @@ +##################################################################### +package Net::Amazon; +###################################################################### +# Mike Schilli <m@perlmeister.com>, 2003 +###################################################################### + +use 5.006; +use strict; +use warnings; + +our $VERSION = '0.34'; +our @CANNED_RESPONSES = (); + +use LWP::UserAgent; +use HTTP::Request::Common; +use XML::Simple; +use Data::Dumper; +use URI; +use Log::Log4perl qw(:easy get_logger); +use Time::HiRes qw(usleep gettimeofday tv_interval); + +# Each key represents a search() type, and each value indicates which +# Net::Amazon::Request:: class to use to handle it. +use constant SEARCH_TYPE_CLASS_MAP => { + artist => 'Artist', + asin => 'ASIN', + blended => 'Blended', + browsenode => 'BrowseNode', + exchange => 'Exchange', + keyword => 'Keyword', + manufacturer => 'Manufacturer', + power => 'Power', + seller => 'Seller', + similar => 'Similar', + textstream => 'TextStream', + upc => 'UPC', + wishlist => 'Wishlist', +}; + + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + if(! exists $options{token}) { + die "Mandatory paramter 'token' not defined"; + } + + if(! exists $options{affiliate_id}) { + $options{affiliate_id} = "webservices-20"; + } + + my $self = { + strict => 1, + response_dump => 0, + rate_limit => 1.0, # 1 req/sec + max_pages => 5, + ua => LWP::UserAgent->new(), + %options, + }; + + help_xml_simple_choose_a_parser(); + + bless $self, $class; +} + +################################################## +sub search { +################################################## + my($self, %params) = @_; + + foreach my $key ( keys %params ) { + next unless ( my $class = SEARCH_TYPE_CLASS_MAP->{$key} ); + + return $self->_make_request($class, \%params); + } + + # FIX? + # This seems like it really should be a die() instead...this is + # indicative of a programming problem. Generally speaking, it's + # best to issue warnings from a module--you can't be sure that the + # client has a stderr to begin with, or that he wants errors + # spewed to it. + warn "No Net::Amazon::Request type could be determined"; + + return undef; +} + +################################################## +sub intl_url { +################################################## + my($self, $url) = @_; + + # Every time Amazon is adding a new country to the web service, + # they're rolling a dice on what the new URL is going to be. + # This method will try to keep up with their crazy mappings. + + if(! exists $self->{locale}) { + return $url; + } + + if ($self->{locale} eq "jp") { + $url =~ s/\.com/.co.jp/; + return $url; + } + + if($self->{locale} eq "uk" or + $self->{locale} eq "de") { + $url =~ s/xml/xml-eu/; + return $url; + } + + return $url; +} + +################################################## +sub request { +################################################## + my($self, $request) = @_; + + my $AMZN_WISHLIST_BUG_ENCOUNTERED = 0; + + my $resp_class = $request->response_class(); + + eval "require $resp_class;" or + die "Cannot find '$resp_class'"; + + my $res = $resp_class->new(); + + my $url = URI->new($self->intl_url($request->amzn_xml_url())); + my $page = $request->{page}; + my $ref; + + { + my %params = $request->params(); + $params{page} = $page; + $params{locale} = $self->{locale} if exists $self->{locale}; + + $url->query_form( + 'dev-t' => $self->{token}, + 't' => $self->{affiliate_id}, + map { $_, $params{$_} } sort keys %params, + ); + + my $urlstr = $url->as_string; + my $xml = fetch_url($self, $urlstr, $res); + + if(!defined $xml) { + return $res; + } + + DEBUG(sub { "Received [ " . $xml . "]" }); + + # Let the response class parse the XML + $ref = $res->xml_parse($xml); + + # DEBUG(sub { Data::Dumper::Dumper($ref) }); + + if(! defined $ref) { + ERROR("Invalid XML"); + $res->messages( [ "Invalid XML" ]); + $res->status(""); + return $res; + } + + if(exists $ref->{TotalPages}) { + INFO("Page $page/$ref->{TotalPages}"); + } + + if(exists $ref->{TotalResults}) { + $res->total_results( $ref->{TotalResults} ); + } + + if(exists $ref->{ErrorMsg}) { + + if($AMZN_WISHLIST_BUG_ENCOUNTERED && + $ref->{ErrorMsg} =~ /no exact matches/) { + DEBUG("End of buggy wishlist detected"); + last; + } + + if (ref($ref->{ErrorMsg}) eq "ARRAY") { + # multiple errors, set arrary ref + $res->messages( $ref->{ErrorMsg} ); + } else { + # single error, create array + $res->messages( [ $ref->{ErrorMsg} ] ); + } + + ERROR("Fetch Error: " . $res->message ); + $res->status(""); + return $res; + } + + my $new_items = $res->xmlref_add($ref); + DEBUG("Received valid XML ($new_items items)"); + + # Stop if we've fetched max_pages already + if($self->{max_pages} <= $page) { + DEBUG("Fetched max_pages ($self->{max_pages}) -- stopping"); + last; + } + + # Work around the Amazon bug not setting TotalPages properly + # for wishlists + if(ref($res) =~ /Wishlist/ and + !exists $ref->{TotalPages} and + $new_items == 10 + ) { + $AMZN_WISHLIST_BUG_ENCOUNTERED = 1; + DEBUG("Trying to fetch additional wishlist page (AMZN bug)"); + $page++; + redo; + } + + if(exists $ref->{TotalPages} and + $ref->{TotalPages} > $page) { + DEBUG("Page $page of $ref->{TotalPages} fetched - continuing"); + $page++; + redo; + } + + # We're gonna fall out of this loop here. + } + + $res->status(1); + # We have a valid response, so if TotalResults isn't set, + # we most likely have a single response + $res->total_results(1) unless defined $res->total_results(); + return $res; +} + +################################################## +sub fetch_url { +################################################## + my($self, $url, $res) = @_; + + my $max_retries = 2; + + INFO("Fetching $url"); + + if(@CANNED_RESPONSES) { + INFO("Serving canned response (testing)"); + return shift @CANNED_RESPONSES; + } + + if(exists $self->{cache}) { + my $resp = $self->{cache}->get($url); + if(defined $resp) { + INFO("Serving from cache"); + return $resp; + } + + INFO("Cache miss"); + } + + my $ua = $self->{ua}; + $ua->env_proxy(); + + my $resp; + + { + # wait up to a second before the next request so + # as to not violate Amazon's 1 query per second + # rule (or the configured rate_limit). + $self->pause() if $self->{strict}; + + $resp = $ua->request(GET $url); + + $self->reset_timer() if $self->{strict}; + + if($resp->is_error) { + # retry on 503 Service Unavailable errors + if ($resp->code == 503) { + if ($max_retries-- >= 0) { + INFO("Temporary Amazon error 503, retrying"); + redo; + } else { + INFO("Out of retries, giving up"); + $res->status(""); + $res->messages( [ "Too many temporary Amazon errors" ] ); + return undef; + } + } else { + $res->status(""); + $res->messages( [ $resp->message ] ); + return undef; + } + } + + if($self->{response_dump}) { + my $dumpfile = "response-$self->{response_dump}.txt"; + open FILE, ">$dumpfile" or die "Cannot open $dumpfile"; + print FILE $resp->content(); + close FILE; + $self->{response_dump}++; + } + + if($resp->content =~ /<ErrorMsg>/ && + $resp->content =~ /Please retry/i) { + if($max_retries-- >= 0) { + INFO("Temporary Amazon error, retrying"); + redo; + } else { + INFO("Out of retries, giving up"); + $res->status(""); + $res->messages( [ "Too many temporary Amazon errors" ] ); + return undef; + } + } + } + + if(exists $self->{cache}) { + $self->{cache}->set($url, $resp->content()); + } + + return $resp->content(); +} + +################################################## +# Poor man's Class::Struct +################################################## +sub make_accessor { +################################################## + my($package, $name) = @_; + + no strict qw(refs); + + my $code = <<EOT; + *{"$package\\::$name"} = sub { + my(\$self, \$value) = \@_; + + if(defined \$value) { + \$self->{$name} = \$value; + } + if(exists \$self->{$name}) { + return (\$self->{$name}); + } else { + return ""; + } + } +EOT + if(! defined *{"$package\::$name"}) { + eval $code or die "$@"; + } +} + +################################################## +# Make accessors for arrays +################################################## +sub make_array_accessor { +################################################## + my($package, $name) = @_; + + no strict qw(refs); + + my $code = <<EOT; + *{"$package\\::$name"} = sub { + my(\$self, \$nameref) = \@_; + if(defined \$nameref) { + if(ref \$nameref eq "ARRAY") { + \$self->{$name} = \$nameref; + } else { + \$self->{$name} = [\$nameref]; + } + } + # Return a list + if(exists \$self->{$name} and + ref \$self->{$name} eq "ARRAY") { + return \@{\$self->{$name}}; + } + + return undef; + } +EOT + + if(! defined *{"$package\::$name"}) { + eval $code or die "$@"; + } +} + +################################################## +sub artist { +################################################## + my($self, $nameref) = @_; + + # Only return the first artist + return ($self->artists($nameref))[0]; +} + + +################################################## +sub xmlref_add { +################################################## + my($self, $xmlref) = @_; + + my $nof_items_added = 0; + + # Push a nested hash structure, retrieved via XMLSimple, onto the + # object's internal 'xmlref' entry, which holds a ref to an array, + # whichs elements are refs to hashes holding an item's attributes + # (like OurPrice etc.) + + #DEBUG("xmlref_add ", Data::Dumper::Dumper($xmlref)); + + unless(ref($self->{xmlref}) eq "HASH" && + ref($self->{xmlref}->{Details}) eq "ARRAY") { + $self->{xmlref}->{Details} = []; + } + + if(ref($xmlref->{Details}) eq "ARRAY") { + # Is it an array of items? + push @{$self->{xmlref}->{Details}}, @{$xmlref->{Details}}; + $nof_items_added = scalar @{$xmlref->{Details}}; + } else { + # It is a single item + push @{$self->{xmlref}->{Details}}, $xmlref->{Details}; + $nof_items_added = 1; + } + + #DEBUG("xmlref_add (after):", Data::Dumper::Dumper($self)); + return $nof_items_added; +} + +################################################## +sub help_xml_simple_choose_a_parser { +################################################## + + eval "require XML::Parser"; + unless($@) { + $XML::Simple::PREFERRED_PARSER = "XML::Parser"; + return; + } + + eval "require XML::SAX::PurePerl"; + unless($@) { + $XML::Simple::PREFERRED_PARSER = "XML::SAX::PurePerl"; + return; + } +} + +################################################## +# This timer makes sure we don't query Amazon more +# than once a second. +################################################## +sub reset_timer { +################################################## + + my $self = shift; + $self->{t0} = [gettimeofday]; +} + +################################################## +# Pause for up to a second if necessary. +################################################## +sub pause { +################################################## + + my $self = shift; + return unless ($self->{t0}); + + my $t1 = [gettimeofday]; + my $dur = (1.0/$self->{rate_limit} - + tv_interval($self->{t0}, $t1)) * 1000000; + if($dur > 0) { + # Use a pseudo subclass for the logger, since the app + # might not want to log that as 'ERROR'. Log4perl's + # inheritance mechanism makes sure it does the right + # thing for the current class. + my $logger = get_logger(__PACKAGE__ . "::RateLimit"); + $logger->error("Ratelimiting: Sleeping $dur microseconds"); + usleep($dur); + } +} + +## +## 'PRIVATE' METHODS +## + +# $self->_make_request( TYPE, PARAMS ) +# +# Takes a TYPE that corresponds to a Net::Amazon::Request +# class, require()s that class, instantiates it, and returns +# the result of that instance's request() method. +# +sub _make_request { + my ($self, $type, $params) = @_; + + my $class = "Net::Amazon::Request::$type"; + + eval "require $class"; + + my $req = $class->new(%{$params}); + + return $self->request($req); +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon - Framework for accessing amazon.com via SOAP and XML/HTTP + +=head1 SYNOPSIS + + use Net::Amazon; + + my $ua = Net::Amazon->new(token => 'YOUR_AMZN_TOKEN'); + + # Get a request object + my $response = $ua->search(asin => '0201360683'); + + if($response->is_success()) { + print $response->as_string(), "\n"; + } else { + print "Error: ", $response->message(), "\n"; + } + +=head1 ABSTRACT + + Net::Amazon provides an object-oriented interface to amazon.com's + SOAP and XML/HTTP interfaces. This way it's possible to create applications + using Amazon's vast amount of data via a functional interface, without + having to worry about the underlying communication mechanism. + +=head1 DESCRIPTION + +C<Net::Amazon> works very much like C<LWP>: First you define a useragent +like + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN', + max_pages => 3, + ); + +which you pass your personal amazon developer's token (can be obtained +from L<http://amazon.com/soap>) and (optionally) the maximum number of +result pages the agent is going to request from Amazon in case all +results don't fit on a single page (typically holding 20 items). Note that +each new page requires a minimum delay of 1 second to comply with Amazon's +one-query-per-second policy. + +According to the different search methods on Amazon, there's a bunch +of different request types in C<Net::Amazon>. The user agent's +convenience method C<search()> triggers different request objects, +depending on which parameters you pass to it: + +=over 4 + +=item C<< $ua->search(asin => "0201360683") >> + +The C<asin> parameter has Net::Amazon search for an item with the +specified ASIN. If the specified value is an arrayref instead of a single +scalar, like in + + $ua->search(asin => ["0201360683", "0596005083"]) + +then a search for multiple ASINs is performed, returning a list of +results. + +=item C<< $ua->search(artist => "Rolling Stones") >> + +The C<artist> parameter has the user agent search for items created by +the specified artist. Can return many results. + +=item C<< $ua->search(browsenode=>"4025", mode=>"books" [, keywords=>"perl"]) >> + +Returns a list of items by category ID (node). For example node "4025" +is the CGI books category. You can add a keywords parameter to filter +the results by that keyword. + +=item C<< $ua->search(exchange => 'Y04Y3424291Y2398445') >> + +Returns an item offered by a third-party seller. The item is referenced +by the so-called I<exchange ID>. + +=item C<< $ua->search(keyword => "perl xml", mode => "books") >> + +Search by keyword, mandatory parameters C<keyword> and C<mode>. +Can return many results. + +=item C<< $ua->search(wishlist => "1XL5DWOUFMFVJ") >> + +Search for all items in a specified wishlist. +Can return many results. + +=item C<< $ua->search(upc => "075596278324", mode => "music") >> + +Music search by UPC (product barcode), mandatory parameter C<upc>. +C<mode> has to be set to C<music>. Returns at most one result. + +=item C<< $ua->search(similar => "0201360683") >> + +Search for all items similar to the one represented by the ASIN provided. +Can return many results. + +=item C<< $ua->search(power => "subject: perl and author: schwartz", mode => "books") >> + +Initiate a power search for all books matching the power query. +Can return many results. See L<Net::Amazon::Request::Power> for details. + +=item C<< $ua->search(manufacturer => "o'reilly", mode => "books") >> + +Initiate a search for all items made by a given manufacturrer. +Can return many results. See L<Net::Amazon::Request::Manufacturer> +for details. + +=item C<< $ua->search(blended => "Perl") >> + +Initiate a search for items in all categories. + +=item C<< $ua->search(seller => "A2GXAGU54VOP7") >> + +Start a search on items sold by a specific third-party seller, referenced +by its ID (not seller name). + +=item C<< $ua->search(textstream => "Blah blah Rolling Stones blah blah") >> + +Find items related to keywords within a text stream. + +=back + +The user agent's C<search> method returns a response object, which can be +checked for success or failure: + + if($resp->is_success()) { + print $resp->as_string(); + } else { + print "Error: ", $resp->message(), "\n"; + } + +In case the request for an item search +succeeds, the response contains one or more +Amazon 'properties', as it calls the products found. +All matches can be retrieved from the Response +object using it's C<properties()> method. + +In case the request fails, the response contains one or more +error messages. The response object's C<message()> method will +return it (or them) as a single string, while C<messages()> (notice +the plural) will +return a reference to an array of message strings. + +Response objects always have the methods +C<is_success()>, +C<is_error()>, +C<message()>, +C<total_results()>, +C<as_string()> and +C<properties()> available. + +C<total_results()> returns the total number of results the search +yielded. +C<properties()> returns one or more C<Net::Amazon::Property> objects of type +C<Net::Amazon::Property> (or one of its subclasses like +C<Net::Amazon::Property::Book>, C<Net::Amazon::Property::Music> +or Net::Amazon::Property::DVD), each +of which features accessors named after the attributes of the product found +in Amazon's database: + + for ($resp->properties) { + print $_->Asin(), " ", + $_->OurPrice(), "\n"; + } + +In scalar context, C<properties()> just returns the I<first> +C<Net::Amazon::Property> object found. +Commonly available accessors to C<Net::Amazon::Property> objects are +C<OurPrice()>, +C<ImageUrlLarge()>, +C<ImageUrlMedium()>, +C<ImageUrlSmall()>, +C<ReleaseDate()>, +C<Catalog()>, +C<Asin()>, +C<url()>, +C<Manufacturer()>, +C<UsedPrice()>, +C<ListPrice()>, +C<ProductName()>, +C<Availability()>, +C<SalesRank()>, +C<CollectiblePrice()>, +C<CollectibleCount()>, +C<NumberOfOfferings()>, +C<UsedCount()>, +C<ThirdPartyNewPrice()>, +C<ThirdPartyNewCount()>, +C<similar_asins()>. +For details, check L<Net::Amazon::Property>. + +Also, the specialized classes C<Net::Amazon::Property::Book> and +C<Net::Amazon::Property::Music> feature convenience methods like +C<authors()> (returning the list of authors of a book) or +C<album()> for CDs, returning the album title. + +Customer reviews: +Every property features a C<review_set()> method which returns a +C<Net::Amazon::Attribute::ReviewSet> object, which in turn offers +a list of C<Net::Amazon::Attribute::Review> objects. Check the respective +man pages for details on what's available. + +=head2 Requests behind the scenes + +C<Net::Amazon>'s C<search()> method is just a convenient way to +create different kinds of request objects behind the scenes and +trigger them to send requests to Amazon. + +Depending on the parameters fed to the C<search> method, C<Net::Amazon> will +determine the kind of search requested and create one of the following +request objects: + +=over 4 + +=item Net::Amazon::Request::ASIN + +Search by ASIN, mandatory parameter C<asin>. +Returns at most one result. + +=item Net::Amazon::Request::Artist + +Music search by Artist, mandatory parameter C<artist>. +Can return many results. + +=item Net::Amazon::Request::BrowseNode + +Returns category (node) listing. Mandatory parameters C<browsenode> +(must be numeric) and C<mode>. Can return many results. + +=item Net::Amazon::Request::Keyword + +Keyword search, mandatory parameters C<keyword> and C<mode>. +Can return many results. + +=item Net::Amazon::Request::UPC + +Music search by UPC (product barcode), mandatory parameter C<upc>. +C<mode> has to be set to C<music>. Returns at most one result. + +=item Net::Amazon::Request::Blended + +'Blended' search on a keyword, resulting in matches across the board. +No 'mode' parameter is allowed. According to Amazon's developer's kit, +this will result in up to three matches per category and can yield +a total of 45 matches. + +=item Net::Amazon::Request::Power + +Understands power search strings. See L<Net::Amazon::Request::Power> +for details. Mandatory parameter C<power>. + +=item Net::Amazon::Request::Manufacturer + +Searches for all items made by a given manufacturer. Mandatory parameter +C<manufacturer>. + +=item Net::Amazon::Request::Similar + +Finds items similar to a given one. + +=item Net::Amazon::Request::Wishlist + +Find item on someone's wish list. + +=item Net::Amazon::Request::Seller + +Searches for a third-party seller on Amazon by seller ID. This search +is different than the previous ones, since it doesn't return Amazon +items, but a single seller record. Don't use the C<properties()> method +on the response, use C<result()> instead, which returns a +L<Net::Amazon::Result::Seller> object. Check the manpage for details. + +=item Net::Amazon::Request::Exchange + +Searches for items offered by third-party sellers. Items are referenced +by their so-called I<Exchange ID>. +Similar to L<Net::Amazon::Request::Seller>, +this request doesn't return a list of Amazon properties, so please use +C<result()> instead, which will return a I<single> +L<Net::Amazon::Result::Seller::Listing> item. +Check the manpage for details on what attributes are available there. + +=back + +Check the respective man pages for details on these request objects. +Request objects are typically created like this (with a Keyword query +as an example): + + my $req = Net::Amazon::Request::Keyword->new( + keyword => 'perl', + mode => 'books', + ); + +and are handed over to the user agent like that: + + # Response is of type Net::Amazon::Response::ASIN + my $resp = $ua->request($req); + +The convenient C<search()> method just does these two steps in one. + +=head2 METHODS + +=over 4 + +=item $ua = Net::Amazon->new(token => $token, ...) + +Create a new Net::Amazon useragent. C<$token> is the value of +the mandatory Amazon developer's token, which can be obtained from +L<http://amazon.com/soap>. + +Additional optional parameters: + +=over 4 + +=item C<< max_pages => $max_pages >> + +Sets how many +result pages the module is supposed to fetch back from Amazon, which +only sends back 10 results per page. +Since each page requires a new query to Amazon, at most one query +per second will be made in C<strict> mode to comply with Amazon's terms +of service. This will impact performance if you perform a search +returning many pages of results. + +=item C<< affiliate_id => $affiliate_id >> + +your Amazon affiliate ID, if you have one. It defaults to +C<webservices-20> which is currently (as of 06/2003) +required by Amazon. + +=item C<< strict => 1 >> + +Makes sure that C<Net::Amazon> complies with Amazon's terms of service +by limiting the number of outgoing requests to 1 per second. Defaults +to C<1>, enabling rate limiting as defined via C<rate_limit>. + +=item C<< rate_limit => $reqs_per_sec >> + +Sets the rate limit to C<$reqs_per_sec> requests per second if +rate limiting has been enabled with C<strict> (see above). +Defaults to C<1>, limiting the number of outgoing requests to +1 per second. + +=item C<< $resp = $ua->request($request) >> + +Sends a request to the Amazon web service. C<$request> is of a +C<Net::Amazon::Request::*> type and C<$response> will be of the +corresponding C<Net::Amazon::Response::*> type. + +=back + +=head2 Accessing foreign Amazon Catalogs + +As of this writing (07/2003), Amazon also offers its web service for +the UK, Germany, and Japan. Just pass in + + locale => 'uk' + locale => 'de' + locale => 'jp' + +respectively to C<Net::Amazon>'s constructor C<new()> and instead of returning +results sent by the US mothership, it will query the particular country's +catalog and show prices in (gack!) local currencies. + +=head2 EXAMPLE + +Here's a full-fledged example doing a artist search: + + use Net::Amazon; + use Net::Amazon::Request::Artist; + use Data::Dumper; + + die "usage: $0 artist\n(use Zwan as an example)\n" + unless defined $ARGV[0]; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN', + ); + + my $req = Net::Amazon::Request::Artist->new( + artist => $ARGV[0], + ); + + # Response is of type Net::Amazon::Artist::Response + my $resp = $ua->request($req); + + if($resp->is_success()) { + print $resp->as_string, "\n"; + } else { + print $resp->message(), "\n"; + } + +And here's one displaying someone's wishlist: + + use Net::Amazon; + use Net::Amazon::Request::Wishlist; + + die "usage: $0 wishlist_id\n" . + "(use 1XL5DWOUFMFVJ as an example)\n" unless $ARGV[0]; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN', + ); + + my $req = Net::Amazon::Request::Wishlist->new( + id => $ARGV[0] + ); + + # Response is of type Net::Amazon::ASIN::Response + my $resp = $ua->request($req); + + if($resp->is_success()) { + print $resp->as_string, "\n"; + } else { + print $resp->message(), "\n"; + } + +=head1 CACHING + +Responses returned by Amazon's web service can be cached locally. +C<Net::Amazon>'s C<new> method accepts a reference to a C<Cache> +object. C<Cache> (or one of its companions like C<Cache::Memory>, +C<Cache::File>, etc.) can be downloaded from CPAN, please check their +documentation for details. In fact, any other type of cache +implementation will do as well, see the requirements below. + +Here's an example utilizing a file cache which causes C<Net::Amazon> to +cache responses for 30 minutes: + + use Cache::File; + + my $cache = Cache::File->new( + cache_root => '/tmp/mycache', + default_expires => '30 min', + ); + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN', + cache => $cache, + ); + +C<Net::Amazon> uses I<positive> caching only, errors won't be cached. +Erroneous requests will be sent to Amazon every time. Positive cache +entries are keyed by the full URL used internally by requests submitted +to Amazon. + +Caching isn't limited to the C<Cache> class. Any cache object which +adheres to the following interface can be used: + + # Set a cache value + $cache->set($key, $value); + + # Return a cached value, 'undef' if it doesn't exist + $cache->get($key); + +=head1 PROXY SETTINGS + +C<Net::Amazon> uses C<LWP::UserAgent> under the hood to send +web requests to Amazon's web site. If you're in an environment where +all Web traffic goes through a proxy, there's two ways to configure that. + +First, C<Net::Amazon> picks up proxy settings from environment variables: + + export http_proxy=http://proxy.my.place:8080 + +in the surrounding shell or setting + + $ENV{http_proxy} = "http://proxy.my.place:8080"; + +in your Perl script +will route all requests through the specified proxy. + +Secondly, you can +pass a user agent instance to Net::Amazon's constructor: + + use Net::Amazon; + use LWP::UserAgent; + + my $ua = LWP::UserAgent->new(); + my $na = Net::Amazon->new(ua => $ua, token => 'YOUR_AMZN_TOKEN'); + # ... + +This way, you can configure C<$ua> up front before Net::Amazon will use it. + +=head1 DEBUGGING + +If something's going wrong and you want more verbosity, just bump up +C<Net::Amazon>'s logging level. C<Net::Amazon> comes with C<Log::Log4perl> +statements embedded, which are disabled by default. However, if you initialize +C<Log::Log4perl>, e.g. like + + use Net::Amazon; + use Log::Log4perl qw(:easy); + + Log::Log4perl->easy_init($DEBUG); + my Net::Amazon->new(); + # ... + +you'll see what's going on behind the scenes, what URLs the module +is requesting from Amazon and so forth. Log::Log4perl allows all kinds +of fancy stuff, like writing to a file or enabling verbosity in certain +parts only -- check http://log4perl.sourceforge.net for details. + +=head1 LIVE TESTING + +Results returned by Amazon can be incomplete or simply wrong at times, +due to their "best effort" design of the service. This is why the test +suite that comes with this module has been changed to perform its test +cases against canned data. If you want to perform the tests against +the live Amazon servers instead, just set the environment variable + + NET_AMAZON_LIVE_TESTS=1 + +=head1 WHY ISN'T THERE SUPPORT FOR METHOD XYZ? + +Because nobody wrote it yet. If Net::Amazon doesn't yet support a method +advertised on Amazon's web service, you could help us out. Net::Amazon +has been designed to be expanded over time, usually it only takes a couple +of lines to support a new method, the rest is done via inheritance within +Net::Amazon. + +Here's the basic plot: + +=over 4 + +=item * + +Get Net::Amazon from CVS. Use + + # (Just hit enter when prompted for a password) + cvs -d:pserver:anonymous@cvs.net-amazon.sourceforge.net:/cvsroot/net-amazon login + cvs -z3 -d:pserver:anonymous@cvs.net-amazon.sourceforge.net:/cvsroot/net-amazon co Net-Amazon + +If this doesn't work, just use the latest distribution from +net-amazon.sourceforge.net. + +=item * + +Write a new Net::Amazon::Request::XYZ package, start with this template + + ###################################### + package Net::Amazon::Request::XYZ; + ###################################### + use base qw(Net::Amazon::Request); + + ###################################### + sub new { + ###################################### + my($class, %options) = @_; + + if(!exists $options{XYZ_option}) { + die "Mandatory parameter 'XYZ_option' not defined"; + } + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate + } + +and add documentation. Then, create a new Net::Amazon::Response::XYZ module: + + ############################## + package Net::Amazon::Response; + ############################## + use base qw(Net::Amazon::Response); + + use Net::Amazon::Property; + + ############################## + sub new { + ############################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate + } + +and also add documentation to it. Then, add the line + + use Net::Amazon::Request::XYZ; + +to Net/Amazon.pm. + +=back + +And that's it! Again, don't forget the I<add documentation> part. Modules +without documentation are of no use to anybody but yourself. + +Check out the different Net::Amazon::Request::* +and Net::Amazon::Response modules in the distribution if you need to adapt +your new module to fulfil any special needs, like a different Amazon URL +or a different way to handle the as_string() method. Also, post +and problems you might encounter to the mailing list, we're gonna help you +out. + +If possible, provide a test case for your extension. When finished, send +a patch to the mailing list at + + net-amazon-devel@lists.sourceforge.net + +and if it works, I'll accept it and will work it into the main distribution. +Your name will show up in the contributor's list below (unless you tell +me otherwise). + +=head2 SAMPLE SCRIPTS + +There's a number of useful scripts in the distribution's eg/ directory. +Take C<power> for example, written by Martin Streicher +E<lt>martin.streicher@apress.comE<gt>: I lets you perform +a I<power search> using Amazon's query language. To search for all books +written by Randal Schwartz about Perl, call this from the command line: + + power 'author: schwartz subject: perl' + +Note that you need to quote the query string to pass it as one argument +to C<power>. If a power search returns more results than you want to +process at a time, just limit the number of pages, telling C<power> +which page to start at (C<-s>) and which one to finish with (C<-f>). +Here's a search for all books on the subject C<computer>, limited +to the first 10 pages: + + power -s 1 -f 10 'subject: computer' + +Check out the script C<power> in eg/ for more options. + +=head2 HOW TO SEND ME PATCHES + +If you want me to include your modification or enhancement +in the distribution of Net::Amazon, please do the following: + +=over 4 + +=item * + +Work off the latest CVS version. Here's the steps to get it: + + CVSROOT=:pserver:anonymous@cvs.net-amazon.sourceforge.net:/cvsroot/net-amazon + export CVSROOT + cvs login (just hit Enter) + cvs co Net-Amazon + +This will create a new C<Net-Amazon> directory with the latest +development version of C<Net::Amazon> on your local machine. + +=item * + +Apply your changes to this development tree. + +=item * + +Run a diff between the tree and your changes it in this way: + + cd Net-Amazon + cvs diff -Nau >patch_to_mike.txt + +=item * + +Email me C<patch_to_mike.txt>. If your patch works (and you've included +test cases and documentation), I'll apply it on the spot. + +=back + +=head1 INSTALLATION + +C<Net::Amazon> depends on Log::Log4perl, which can be pulled from CPAN by +simply saying + + perl -MCPAN -eshell 'install Log::Log4perl' + +Also, it needs LWP::UserAgent and XML::Simple 2.x, which can be obtained +in a similar way. + +Once all dependencies have been resolved, C<Net::Amazon> installs with +the typical sequence + + perl Makefile.PL + make + make test + make install + +Make sure you're connected to the Internet while running C<make test> +because it will actually contact amazon.com and run a couple of live tests. + +The module's distribution tarball and documentation are available at + + http://perlmeister.com/devel/#amzn + +and on CPAN. + +=head1 SEE ALSO + +The following modules play well within the C<Net::Amazon> framework: + +=over 4 + +=item C<Net::Amazon::RemoteCart> + +by David Emery E<lt>dave@skiddlydee.comE<gt> provides a complete API for +creating Amazon shopping carts on a local site, managing them and finally +submitting them to Amazon for checkout. It is available on CPAN. + +=back + +=head1 CONTACT + +The C<Net::Amazon> project's home page is hosted on + + http://net-amazon.sourceforge.net + +where you can find documentation, news and the latest development and +stable releases for download. If you have questions about how to +use C<Net::Amazon>, want to report a bug or just participate in its +development, please send a message to the mailing +list net-amazon-devel@lists.sourceforge.net + +=head1 AUTHOR + +Mike Schilli, E<lt>na@perlmeister.comE<gt> (Please contact me via the mailing list: net-amazon-devel@lists.sourceforge.net ) + +Contributors (thanks y'all!): + + Andy Grundman <andy@hybridized.org> + Barnaby Claydon <bclaydon@perseus.com> + Batara Kesuma <bkesuma@gaijinweb.com> + Bill Fitzpatrick + Brian <brianbrian@gmail.com> + Brian Hirt <bhirt@mobygames.com> + Dan Kreft <dan@kreft.net> + Dan Sully <daniel@electricrain.com> + Jackie Hamilton <kira@cgi101.com> + Konstantin Gredeskoul <kig@get.topica.com> + Lance Cleveland <lancec@proactivewm.com> + Martha Greenberg <marthag@mit.edu> + Martin Streicher <martin.streicher@apress.com> + Mike Evron <evronm@dtcinc.net> + Padraic Renaghan <padraic@renaghan.com> + rayg <rayg@varchars.com> + Robert Graff <rgraff@workingdemo.com> + Robert Rothenberg <wlkngowl@i-2000.com> + Steve Rushe <steve@deeden.co.uk> + Tatsuhiko Miyagawa <miyagawa@livedoor.jp> + Tony Bowden <tony@kasei.com> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003, 2004 by Mike Schilli E<lt>na@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut |
