diff options
Diffstat (limited to 'lib/Net')
39 files changed, 5298 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 diff --git a/lib/Net/Amazon/Attribute/Review.pm b/lib/Net/Amazon/Attribute/Review.pm new file mode 100644 index 0000000..5a5d094 --- /dev/null +++ b/lib/Net/Amazon/Attribute/Review.pm @@ -0,0 +1,104 @@ +###################################################################### +package Net::Amazon::Attribute::Review; +###################################################################### +use warnings; +use strict; +use Log::Log4perl qw(:easy); +use base qw(Net::Amazon); + +__PACKAGE__->make_accessor($_) for qw(rating summary comment); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = { + rating => "", + summary => "", + comment => "", + %options, + }; + + bless $self, $class; +} + +################################################## +sub init_via_xmlref { +################################################## + my($self, $xmlref) = @_; + + for(qw(Rating Summary Comment)) { + my $method = lc($_); + if($xmlref->{$_}) { + $self->$method($xmlref->{$_}); + } else { + #LOGWARN "No '$_'"; + return undef; + } + } +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Attribute::Review - Customer Review Class + +=head1 SYNOPSIS + + use Net::Amazon::Attribute::Review; + my $rev = Net::Amazon::Attribute::Review->new( + 'rating' => $rating, + 'summary' => $summary, + 'comment' => $comment, + ); + +=head1 DESCRIPTION + +C<Net::Amazon::Attribute::Review> holds customer reviews. + +=head2 METHODS + +=over 4 + +=item rating() + +Accessor for the numeric value of the rating. + +=item summary() + +Accessor for the string value of the summary. + +=item comment() + +Accessor for the string value of the customer comment. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +__END__ + <Reviews> + <AvgCustomerRating>4.33</AvgCustomerRating> + <TotalCustomerReviews>6</TotalCustomerReviews> + <CustomerReview> + <Rating>4</Rating> + <Summary>Good introduction to Perl, and great reference</Summary> + <Comment>From its corny title you might expect another one of those + diff --git a/lib/Net/Amazon/Attribute/ReviewSet.pm b/lib/Net/Amazon/Attribute/ReviewSet.pm new file mode 100644 index 0000000..29a34e6 --- /dev/null +++ b/lib/Net/Amazon/Attribute/ReviewSet.pm @@ -0,0 +1,137 @@ +###################################################################### +package Net::Amazon::Attribute::ReviewSet; +###################################################################### +use warnings; +use strict; +use Log::Log4perl qw(:easy); +use Net::Amazon::Attribute::Review; +use base qw(Net::Amazon); + +__PACKAGE__->make_accessor($_) for qw(average_customer_rating total_reviews); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = { + reviews => [], # list of reviews + }; + + bless $self, $class; +} + +########################################### +sub add_review { +########################################### + my($self, $review) = @_; + + if(ref $review ne "Net::Amazon::Attribute::Review") { + warn "add_review called with type ", ref $review; + return undef; + } + + push @{$self->{reviews}}, $review; +} + +########################################### +sub reviews { +########################################### + my($self) = @_; + + return @{$self->{reviews}}; +} + +################################################## +sub init_via_xmlref { +################################################## + my($self, $xmlref) = @_; + + my @pairs = qw(AvgCustomerRating average_customer_rating + TotalCustomerReviews total_reviews); + + while(my($field, $method) = splice @pairs, 0, 2) { + + if(defined $xmlref->{$field}) { + DEBUG "Setting $field via $method to $xmlref->{$field}"; + $self->$method($xmlref->{$field}); + } else { + LOGWARN "No '$field'"; + return undef; + } + } + + if(ref $xmlref->{CustomerReview} ne "ARRAY") { + $xmlref->{CustomerReview} = [$xmlref->{CustomerReview}]; + } + + for my $review_xmlref (@{$xmlref->{CustomerReview}}) { + my $review = Net::Amazon::Attribute::Review->new(); + $review->init_via_xmlref($review_xmlref); + DEBUG "Adding review ", $review->summary(); + $self->add_review($review); + } +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Attribute::ReviewSet - A set of customer reviews + +=head1 SYNOPSIS + + use Net::Amazon::Attribute::ReviewSet; + my $rev = Net::Amazon::Attribute::Review->new( + average_customer_rating => $avg, + total_reviews => $total, + ); + +=head1 DESCRIPTION + +C<Net::Amazon::Attribute::ReviewSet> holds a list of customer +reviews, each of type C<Net::Amazon::Attribute::Review>. + +=head2 METHODS + +=over 4 + +=item C<< $self->reviews() >> + +Returns a list of C<Net::Amazon::Attribute::Review> objects. + +=item C<< $self->average_customer_rating() >> + +Accessor for the average customer rating, a numeric value. + +=item C<< $self->total_reviews() >> + +Accessor for the total number of reviews. Please note that this +might not be equal to the number of reviews held in the list, since +there might be less customer reviews than total reviews (reviews +can also be non-customer-reviews, but they're not available by +the web service as of Aug 2003). + +=item C<< $self->add_review($rev) >> + +Add a C<Net::Amazon::Attribute::Review> object to the list. +(Used internally only). + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Property.pm b/lib/Net/Amazon/Property.pm new file mode 100644 index 0000000..6c06957 --- /dev/null +++ b/lib/Net/Amazon/Property.pm @@ -0,0 +1,320 @@ +###################################################################### +package Net::Amazon::Property; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon); + +use Net::Amazon::Property::DVD; +use Net::Amazon::Property::Book; +use Net::Amazon::Property::Music; +use Net::Amazon::Attribute::ReviewSet; +use Data::Dumper; +use Log::Log4perl qw(:easy); + +use warnings; +use strict; + +our @DEFAULT_ATTRIBUTES = qw( + OurPrice ImageUrlLarge ImageUrlMedium ImageUrlSmall + ReleaseDate Catalog Asin url Manufacturer UsedPrice + ListPrice ProductName Availability SalesRank + Media NumMedia ProductDescription + CollectiblePrice CollectibleCount NumberOfOfferings + UsedCount ThirdPartyNewPrice ThirdPartyNewCount + ThirdPartyProductInfo +); + +__PACKAGE__->make_accessor($_) for @DEFAULT_ATTRIBUTES; +__PACKAGE__->make_accessor($_) for qw(year review_set); +__PACKAGE__->make_array_accessor($_) for qw(browse_nodes similar_asins); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + if(!$options{xmlref}) { + die "Mandatory param xmlref missing"; + } + + my $self = { + %options, + }; + + bless $self, $class; + + # Set default attributes + for my $attr (@DEFAULT_ATTRIBUTES) { + $self->$attr($options{xmlref}->{$attr}); + } + + # The release date is sometimes missing + if($options{xmlref}->{ReleaseDate}) { + my ($year) = ($options{xmlref}->{ReleaseDate} =~ /(\d{4})/); + $self->year($year); + } else { + $self->year(""); + } + + my $browse_nodes = $options{xmlref}->{BrowseList}->{BrowseNode}; + if(ref($browse_nodes) eq "ARRAY") { + my @nodes = map { + $_->{BrowseName} + } @{ $browse_nodes }; + $self->browse_nodes(\@nodes); + } elsif (ref($browse_nodes) eq "HASH") { + $self->browse_nodes([ $browse_nodes->{BrowseName} ]); + } else { + $self->browse_nodes([ ]); + } + + my $similar = $options{xmlref}->{SimilarProducts}; + if(ref($similar) eq "HASH") { + $self->similar_asins($similar->{Product}); + } else { + $self->similar_asins([ ]); + } + + return $self; +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + my $result = "\"$self->{xmlref}->{ProductName}\", "; + + if($self->{xmlref}->{Manufacturer}) { + $result .= "$self->{xmlref}->{Manufacturer}, "; + } + + $result .= $self->year() . ", " if $self->year(); + + $result .= $self->OurPrice() . ", "; + $result .= $self->Asin(); + return $result; +} + +################################################## +sub factory { +################################################## + my(%options) = @_; + + my $xmlref = $options{xmlref}; + die "Called factory without xmlref" unless $xmlref; + + # DEBUG(sub {"factory xmlref=" . Data::Dumper::Dumper($xmlref)}); + + my $catalog = $xmlref->{Catalog}; + my $obj; + + if(0) { + } elsif($catalog eq "Book") { + DEBUG("Creating new Book Property"); + $obj = Net::Amazon::Property::Book->new(xmlref => $xmlref); + } elsif($catalog eq "Music") { + DEBUG("Creating new Music Property"); + $obj = Net::Amazon::Property::Music->new(xmlref => $xmlref); + } elsif($catalog eq "DVD") { + DEBUG("Creating new DVD Property"); + $obj = Net::Amazon::Property::DVD->new(xmlref => $xmlref); + } else { + DEBUG("Creating new Default Property ($catalog)"); + $obj = Net::Amazon::Property->new(xmlref => $xmlref); + } + + return $obj; +} + +################################################## +sub init_via_xmlref { +################################################## + my($self, $xmlref) = @_; + + my $reviewset = Net::Amazon::Attribute::ReviewSet->new(); + + if(exists $xmlref->{Reviews}) { + $reviewset->init_via_xmlref($xmlref->{Reviews}); + } + + $self->review_set($reviewset); +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Property - Baseclass for products on amazon.com + +=head1 SYNOPSIS + + use Net::Amazon; + + # ... + + if($resp->is_success()) { + for my $prop ($resp->properties) { + print $_->ProductName(), " ", + $_->Manufacturer(), " ", + $_->OurPrice(), "\n"; + +=head1 DESCRIPTION + +C<Net::Amazon::Property> is the baseclass for results returned +from Amazon web service queries. The term 'properties' is used as +a generic description for an item on amazon.com. + +Typically, the C<properties()> method of a C<Net::Amazon::Response::*> object +will return one or more objects of class C<Net::Amazon::Property> or +one of its subclasses, e.g. C<Net::Amazon::Property::Book> or +C<Net::Amazon::Property::CD>. + +While C<Net::Amazon::Property> objects expose accessors for all +fields returned in the XML response (like C<OurPrice()>, C<ListPrice()>, +C<Manufacturer()>, C<Asin()>, C<Catalog()>, C<ProductName()>, subclasses +might define their own accessors to more class-specific fields +(like the iC<Net::Amazon::Property::Book>'s C<authors()> method returning +a list of authors, while C<Net::Amazon::Property>'s C<Authors()> method +will return a reference to a sub-hash containing a C<Author> field, just like +the response's XML contained it). + +=head2 METHODS + +Methods vary, depending on the item returned from a query. Here's the most +common ones. They're all accessors, meaning they can be used like C<Method()> +to retrieve the value or like C<Method($value)> to set the value of the +field. + +=over 4 + +=item Asin() + +The item's ASIN number. + +=item ProductName() + +Book title, CD album name or item name + +=item Availability() + +Text string describing if the item is available. Examples: +C<"Usually ships within 24 hours"> or +C<"Out of Print--Limited Availability">. + +=item Catalog() + +Shows the catalog the item was found in: C<Book>, C<Music>, C<Classical>, +C<Electronics> etc. + +=item Authors() + +Returns a sub-hash with a C<Author> key, which points to either a single +$scalar or to a reference of an array containing author names as scalars. + +=item ReleaseDate() + +Item's release date, format is "NN Monthname, Year". + +=item Manufacturer() + +Music label, publishing company or manufacturer + +=item ImageUrlSmall() + +URL to a small (thumbnail) image of the item + +=item ImageUrlMedium() + +URL to a medium-size image of the item + +=item ImageUrlLarge() + +URL to a large image of the item + +=item ListPrice() + +List price of the item + +=item OurPrice() + +Amazon price of the item + +=item UsedPrice() + +Used price of the item + +=item SalesRank() + +Sales rank of the item (contains digits and commas, like 1,000,001) + +=item Media() + +Type of media (Paperback, etc.). + +=item NumMedia() + +Number of media the item carries (1,2 CDs etc.). + +=item ProductDescription() + +Lengthy textual description of the product. + +=item CollectiblePrice() + +Lowest price in "Collectible" category. + +=item CollectibleCount() + +Number of offerings in "Collectible" category. + +=item NumberOfOfferings() + +Total number of offerings in all categories. + +=item UsedCount() + +Number of offerings in "Used" category. + +=item ThirdPartyNewPrice() + +Lowest price in "Third Party New" category. + +=item ThirdPartyNewCount() + +Number of offerings in "Third Party New" category. + +=item year() + +The release year extracted from ReleaseDate(). + +=item browse_nodes() + +Returns a list of browse nodes (text string categories) for this item. + +=item similar_asins() + +Returns a list of ASINs of similar items for this item. + +=back + +Please check the subclasses of C<Net::Amazon::Property> for specialized +methods. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Property/Book.pm b/lib/Net/Amazon/Property/Book.pm new file mode 100644 index 0000000..8a01756 --- /dev/null +++ b/lib/Net/Amazon/Property/Book.pm @@ -0,0 +1,152 @@ +###################################################################### +package Net::Amazon::Property::Book; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Property); + +__PACKAGE__->make_accessor($_) for qw(title publisher binding isbn); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + bless $self, $class; # Bless into this class + + if(exists $options{xmlref}) { + $self->init_via_xmlref($options{xmlref}); + } + + return $self; +} + +################################################## +sub init_via_xmlref { +################################################## + my($self, $xmlref) = @_; + + $self->SUPER::init_via_xmlref($xmlref); + + $self->authors($xmlref->{Authors}->{Author}); + $self->title($xmlref->{ProductName}); + $self->publisher($xmlref->{Manufacturer}); + $self->binding($xmlref->{Media}); + $self->isbn($xmlref->{Isbn}); +} + +################################################## +sub author { +################################################## + my($self, $nameref) = @_; + + # Only return the first author + return ($self->authors($nameref))[0]; +} + +################################################## +sub authors { +################################################## + my($self, $nameref) = @_; + + if(defined $nameref) { + if(ref $nameref eq "ARRAY") { + $self->{authors} = $nameref; + } else { + $self->{authors} = [$nameref]; + } + } + + # Return a list + return $self->{authors} ? @{$self->{authors}} : (); +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return join('/', $self->authors) . ", " . + '"' . $self->title . '"' . ", " . + $self->year . ", " . + $self->OurPrice . ", " . + $self->Asin; +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Property::Book - Class for books on amazon.com + +=head1 SYNOPSIS + + use Net::Amazon; + + # ... + + if($resp->is_success()) { + for my $prop ($resp->properties) { + print join("/", $prop->authors(), " ", + $prop->title(), " ", + $prop->publisher(), " ", + $prop->year(), "\n"; + } + +=head1 DESCRIPTION + +C<Net::Amazon::Property::Book> is derived from +C<Net::Amazon::Property> and on top of the all-purpose +methods the base class provides, it offers specialized accessors for +book parameters. + +=head2 METHODS + +=over 4 + +=item authors() + +Returns a list of the book's authors. There's also a C<author()> method +which just returns the I<first> author. + +=item publisher() + +Returns the book's publishing company as a string. + +=item title() + +Returns the book's title as a string. + +=item isbn() + +Returns the book's ISBN number. + +=item new(xmlref => $xmlref) + +Initializes an object by passing a hash of hashes structure containing +the XML data returned from the service. Usually, this is just used by +C<Net::Amazon> internally to initialize objects for on backcoming +data. + +=back + +Check out L<Net::Amazon::Property> for all-purpose accessors, like +C<year>, C<OurPrice>, C<ListPrice>, etc. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Property/DVD.pm b/lib/Net/Amazon/Property/DVD.pm new file mode 100644 index 0000000..dc7fd5a --- /dev/null +++ b/lib/Net/Amazon/Property/DVD.pm @@ -0,0 +1,156 @@ +###################################################################### +package Net::Amazon::Property::DVD; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Property); + +__PACKAGE__->make_accessor($_) for qw(title studio theatrical_release_date + media nummedia upc mpaa_rating); +__PACKAGE__->make_array_accessor($_) for qw(directors starring features); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + bless $self, $class; # Bless into this class + + if(exists $options{xmlref}) { + $self->init_via_xmlref($options{xmlref}); + } + + return $self; +} + +################################################## +sub director { +################################################## + my($self, $nameref) = @_; + + # Only return the first director + return ($self->directors($nameref))[0]; +} + +################################################## +sub init_via_xmlref { +################################################## + my($self, $xmlref) = @_; + + $self->SUPER::init_via_xmlref($xmlref); + + $self->title($xmlref->{ProductName}); + $self->studio($xmlref->{Manufacturer}); + $self->directors($xmlref->{Directors}->{Director}); + $self->starring($xmlref->{Starring}->{Actor}); + $self->media($xmlref->{Media}); + $self->nummedia($xmlref->{NumMedia}); + $self->upc($xmlref->{Upc}); + $self->theatrical_release_date($xmlref->{TheatricalReleaseDate}); + $self->mpaa_rating($xmlref->{MpaaRating}); + $self->features($xmlref->{Features}->{Feature}); +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Property::DVD - Class for DVDs on amazon.com + +=head1 SYNOPSIS + + use Net::Amazon; + + # ... + + if($resp->is_success()) { + for my $prop ($resp->properties) { + print $_->title(), " ", + $_->studio(), " ", + $_->year(), "\n"; + } + +=head1 DESCRIPTION + +C<Net::Amazon::Property::DVD> is derived from +C<Net::Amazon::Property> and on top of the all-purpose +methods the base class provides, it offers specialized accessors for +DVD parameters. + +=head2 METHODS + +=over 4 + +=item title() + +Returns the title of the DVD. + +=item studio() + +Returns the studio. + +=item directors() + +Returns a list of directors. Note that there's also a director() method +only returning the first director. + +=item starring() + +Returns a list of actors starring in the movie. + +=item upc() + +Returns the DVD's UPC as a string. + +=item media() + +Returns the DVD's media type as a string. + +=item nummedia() + +Returns the DVD's number of media (number of discs) as a string. +Amazon doesn't always send this back, so if you get undef assume it +is 1. + +=item theatrical_release_date() + +Returns the DVD's theatrical release date. + +=item mpaa_rating() + +Returns the DVD's MPAA rating. + +=item features() + +Returns the DVD's features as a list of strings. Examples: +"Color", "Closed-captioned", "Widescreen". + +=item new(xmlref => $xmlref) + +Initializes an object by passing a hash of hashes structure containing +the XML data returned from the service. Usually, this is just used by +C<Net::Amazon> internally to initialize objects for on backcoming +data. + +=back + +Check out L<Net::Amazon::Property> for all-purpose accessors, like +C<year>, C<OurPrice>, C<ListPrice>, etc. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Property/Music.pm b/lib/Net/Amazon/Property/Music.pm new file mode 100644 index 0000000..59807be --- /dev/null +++ b/lib/Net/Amazon/Property/Music.pm @@ -0,0 +1,161 @@ +###################################################################### +package Net::Amazon::Property::Music; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Property); + +__PACKAGE__->make_accessor($_) for qw(album label media nummedia upc); +__PACKAGE__->make_array_accessor($_) for qw(artists tracks); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + bless $self, $class; # Bless into this class + + if(exists $options{xmlref}) { + $self->init_via_xmlref($options{xmlref}); + } + + return $self; +} + +################################################## +sub artist { +################################################## + my($self, $nameref) = @_; + + # Only return the first artist + return ($self->artists($nameref))[0]; +} + +################################################## +sub init_via_xmlref { +################################################## + my($self, $xmlref) = @_; + + $self->SUPER::init_via_xmlref($xmlref); + + $self->artists($xmlref->{Artists}->{Artist}); + $self->tracks($xmlref->{Tracks}->{Track}); + $self->album($xmlref->{ProductName}); + $self->label($xmlref->{Manufacturer}); + $self->upc($xmlref->{Upc}); + $self->media($xmlref->{Media}); + $self->nummedia($xmlref->{NumMedia}); +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return join('/', $self->artists) . ", " . + '"' . $self->album . '"' . ", " . + $self->year . ", " . + $self->OurPrice . ", " . + $self->Asin; +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Property::Music - Class for pop CDs on amazon.com + +=head1 SYNOPSIS + + use Net::Amazon; + + # ... + + if($resp->is_success()) { + for my $prop ($resp->properties) { + print join("/", $_->artists(), " ", + $_->album(), " ", + $_->label(), " ", + $_->year(), " "; + $_->upc(), " "; + $_->media(), " "; + $_->nummedia(), "\n"; + } + +=head1 DESCRIPTION + +C<Net::Amazon::Property::Music> is derived from +C<Net::Amazon::Property> and on top of the all-purpose +methods the base class provides, it offers specialized accessors for +popular music CD parameters. + +=head2 METHODS + +=over 4 + +=item artists() + +Returns a list of the CD's artists. There's also a C<artist()> method +which just returns the first artist. + +=item tracks() + +Returns a list of the CD's track titles. + +=item label() + +Returns the music label as a string. + +=item album() + +Returns the CD's title as a string. + +=item upc() + +Returns the CD's UPC as a string. + +=item media() + +Returns the CD's media type as a string. + +=item nummedia() + +Returns the CD's number of media (number of discs) as a string. +Amazon doesn't always send this back, so if you get undef assume it +is 1. + +=item new(xmlref => $xmlref) + +Initializes an object by passing a hash of hashes structure containing +the XML data returned from the service. Usually, this is just used by +C<Net::Amazon> internally to initialize objects for on backcoming +data. + +=back + +Check out L<Net::Amazon::Property> for all-purpose accessors, like +C<year>, C<OurPrice>, C<ListPrice>, etc. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 THANKS + +Thanks to Padraic Renaghan E<lt>padraic@renaghan.com<gt> for adding +the upc/media/nummedia fields. + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + diff --git a/lib/Net/Amazon/Request.pm b/lib/Net/Amazon/Request.pm new file mode 100644 index 0000000..962d7bf --- /dev/null +++ b/lib/Net/Amazon/Request.pm @@ -0,0 +1,221 @@ +###################################################################### +package Net::Amazon::Request; +###################################################################### + +use warnings; +use strict; + +use constant DEFAULT_MODE => 'books'; +use constant DEFAULT_TYPE => 'heavy'; +use constant DEFAULT_PAGE_COUNT => 1; +use constant DEFAULT_FORMAT => 'xml'; +use constant DEFAULT_SORT_CRITERIA => '+salesrank'; + +use constant VALID_TYPES => { map { $_ => 1 } qw(heavy lite) }; + +our $AMZN_XML_URL = "http://xml.amazon.com/onca/xml3"; + +################################################## +sub amzn_xml_url { +################################################## + return $AMZN_XML_URL; +} + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = { + mode => DEFAULT_MODE, + type => DEFAULT_TYPE, + page => DEFAULT_PAGE_COUNT, + f => DEFAULT_FORMAT, + sort => DEFAULT_SORT_CRITERIA, + %options, + }; + + die "Unknown type in ", __PACKAGE__, " constructor: ", + $self->{type} unless exists VALID_TYPES->{$self->{type}}; + + bless $self, $class; +} + +################################################## +sub params { +################################################## + my($self) = @_; + + return(%$self); +} + +################################################## +# Figure out the Response class to a given Request +# class. To be used by sub classes. +################################################## +sub response_class { +################################################## + my($self) = @_; + + my $response_class = ref($self); + $response_class =~ s/Request/Response/; + return $response_class; +} + +## +## 'PRIVATE' METHODS +## + +# CLASS->_convert_option( OPTIONS, ORIGINAL, TARGET [, CALLBACK] ) +# +# Takes a reference to a hash of OPTIONS and renames the +# ORIGINAL key name to the TARGET key name. If the optional +# CALLBACK subroutine reference is defined, that subroutine +# is invoked with two arguments: +# +# CALLBACK->( OPTIONS, TARGET ) +# +# The result of the CALLBACK's execution is then returned to +# the caller. No assumptions are made about what the CALLBACK +# should return (or even *if* is should return)--that's the +# caller's responsibility. +# +# Returns 1 in the absensence of a CALLBACK. +# +sub _convert_option { + my ($class, $options, $original, $target, $callback) = @_; + + if ( exists $options->{$original} ) { + $options->{$target} = $options->{$original}; + delete $options->{$original}; + } + + return 1 unless ( $callback ); + + # The key name is explicitly passed-in so that the caller doesn't + # have think "Hrmm.. now which key am I working on, the original + # or the target key?" Confusion is bad. + return $callback->($options, $target); +} + +# CLASS->_assert_options_defined( OPTIONS, KEYS ) +# +# Takes a reference to a hash of OPTIONS and a list of +# one or more KEYS. Tests to see if each key in KEYS +# has a defined value. Calls die() upon the first +# missing key. Otherwise, returns undef. +# +sub _assert_options_defined { + my ($class, $options, @keys) = @_; + + foreach my $key ( @keys ) { + die "Mandatory parameter '$key' not defined" + unless ( defined $options->{$key} ); + } +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request - Baseclass for requests to Amazon's web service + +=head1 SYNOPSIS + + my $req = Net::Amazon::Request::XXX->new( + [ type => 'heavy', ] + [ page => $start_page, ] + [ mode => $mode, ] + [ offer => 'All', ] + [ sort => $sort_type, ] + ); + +=head1 DESCRIPTION + +Don't use this class directly, use derived classes +(like C<Net::Amazon::Request::ASIN>, C<Net::Amazon::Request::Wishlist> +etc.) instead to specify the type of request and its parameters. + +However, there's a bunch of parameters to the constructor +that all request types have in common, here they are: + +=over 4 + +=item type + +Defaults to C<heavy>, but can be set to C<lite> if no reviews etc. +on a product are wanted. Some fields (e.g. C<isbn>) are not going to be +available in C<lite> mode, though. + +=item mode + +Defaults to C<books>, but can be set to other catalog values. + +=item page + +Defaults to C<1>, but can be set to a different number to +start with a different result page. Used in conjunction with the +C<max_pages> parameter of the C<Net::Amazon> object. C<page> is the +offset, C<max_pages> is the maximum number of pages pulled in starting +at C<page>. + +=item sort + +Defaults to C<+salesrank>, but search results can be sorted in various +ways, depending on the type of product returned by the search. Search +results may be sorted by the following criteria: + +=over 8 + +=item * +Featured Items + +=item * +Bestselling + +=item * +Alphabetical (A-Z and Z-A) + +=item * +Price (High to Low and Low to High) + +=item * +Publication or Release Date + +=item * +Manufacturer + +=item * +Average Customer Review + +=item * +Artist Name + +=back + +Consult L<Net::Amazon::Request::Sort> for details. + +=item offer + +To receive values for the fields +C<CollectibleCount>, C<NumberOfOfferings>, C<UsedCount>, +specify C<offer =E<gt> "All">. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/ASIN.pm b/lib/Net/Amazon/Request/ASIN.pm new file mode 100644 index 0000000..4d8f752 --- /dev/null +++ b/lib/Net/Amazon/Request/ASIN.pm @@ -0,0 +1,139 @@ +###################################################################### +package Net::Amazon::Request::ASIN; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +# These values are defined in the AWS SDK +# (http://amazon.com/webservices) under +# "Product and Catalog Data" / "ASIN and ISBN Searches" +use constant MAX_ASINS_PER_TYPE => { + heavy => 10, + lite => 30, +}; + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + $class->_assert_options_defined(\%options, 'asin'); + + $class->_convert_option(\%options, + 'asin', + 'AsinSearch', + \&_process_asin_option); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +## +## PRIVATE FUNCTIONS +## + +# _process_asin_option( OPTIONS, KEY ) +# +# Takes a reference to a hash of OPTIONS and checks the value keyed by +# KEY to make sure it looks legitimate. If the value associated with +# KEY is an array, we check to make sure that we're not asking for +# too many asins at once. +# +# Returns true if all goes well. If any problems are encountered, +# die() will be called. +# +sub _process_asin_option { + my ($options, $key) = @_; + + # If the asins are supplied in the form of an array, we have to + # make sure that the caller isn't trying to ask for too many at a + # time. If we don't make this test, those excessive asins will be + # silently ignored by the AWS servers...resulting in potentially + # confusing results for the user. + if ( ref $options->{$key} eq 'ARRAY' ) { + my $type = $options->{'type'} || __PACKAGE__->SUPER::DEFAULT_TYPE; + my $max_asins = MAX_ASINS_PER_TYPE->{$type}; + + # Dying is the right thing to do here because this is + # indicative of a programming error. + die "Only $max_asins may be requested at a time using type '$type'" + if ( @{$options->{$key}} > $max_asins ); + + $options->{$key} = join ',', @{$options->{$key}}; + } elsif ( ref $options->{$key} ) { + die "The 'asin' parameter must either be a scalar or an array"; + } + + return 1; +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::ASIN - Class for submitting ASIN requests + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::ASIN; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::ASIN->new( + asin => '0201360683' + ); + + # Response is of type Net::Amazon::Response::ASIN + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::ASIN> is a class used to submit ASIN requests +to the Amazon web service. + +The ASIN of the item to look is specified in the C<asin> parameter. + +Upon success, the responses' C<properties()> method will return one +single C<Net::Amazon::Property::*> object. + +=head2 METHODS + +=over 4 + +=item new( asin => $asin ) + +Constructs a new C<Net::Amazon::Request::ASIN> object, used to query +the Amazon web service for an item with the specified ASIN number. + +C<$asin> can also be a reference to an array of ASINs, like in + + $ua->search(asin => ["0201360683", "0596005083"]) + +in which case a search for multiple ASINs is performed, returning a list of +results. + +=back + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Artist.pm b/lib/Net/Amazon/Request/Artist.pm new file mode 100644 index 0000000..4c111f8 --- /dev/null +++ b/lib/Net/Amazon/Request/Artist.pm @@ -0,0 +1,86 @@ +###################################################################### +package Net::Amazon::Request::Artist; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + if(exists $options{artist}) { + $options{ArtistSearch} = $options{artist}; + delete $options{artist}; + $options{mode} = "music"; + } else { + die "Mandatory parameter 'artist' not defined"; + } + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Artist - Class for submitting Artist requests + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Artist; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Artist->new( + artist => 'Zwan' + ); + + # Response is of type Net::Amazon::Response::Artist + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Artist> is a class used to submit Artist search +requests to the Amazon web service. + +The artist to search for is specified in the C<artist> parameter. + +Upon success, the responses' C<properties()> method will return a list of +C<Net::Amazon::Property::Music> objects. + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head2 METHODS + +=over 4 + +=item new( artist => $artist ) + +Constructs a new C<Net::Amazon::Request::Artist> object, used to query +the Amazon web service for items of a given artist. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Blended.pm b/lib/Net/Amazon/Request/Blended.pm new file mode 100644 index 0000000..1000eec --- /dev/null +++ b/lib/Net/Amazon/Request/Blended.pm @@ -0,0 +1,80 @@ +###################################################################### +package Net::Amazon::Request::Blended; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + $class->_assert_options_defined(\%options, 'blended'); + + $class->_convert_option(\%options, + 'blended', + 'BlendedSearch'); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Blended - request class for 'Blended Search' + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Blended; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Blended->new( + blended => 'perl' + ); + + # Response is of type Net::Amazon::Response::Blended + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Blended> is a class used to request +I<Blended Searches> from the Amazon web service. + +The C<blended> parameter specifies the keyword search string +for the blended query. C<mode> is not applicable to blended +searches which returns books, music, etc all at once. + +Upon success, the response's C<properties()> method will return a list +of C<Net::Amazon::Property::*> objects. + +=head2 METHODS + +=over 4 + +=item new(keyword => $search_string) + +Constructs a new C<Net::Amazon::Request::Blended> object. + +See the "Amazon Web Services 2.1 API and Integration Guide" for details. + +=back + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head1 SEE ALSO + +=head1 AUTHORS + +Robert Graff, E<lt>rgraff@workingdemo.comE<gt> + +=cut diff --git a/lib/Net/Amazon/Request/BrowseNode.pm b/lib/Net/Amazon/Request/BrowseNode.pm new file mode 100644 index 0000000..2f8d92f --- /dev/null +++ b/lib/Net/Amazon/Request/BrowseNode.pm @@ -0,0 +1,137 @@ +###################################################################### +package Net::Amazon::Request::BrowseNode; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + $class->_assert_options_defined(\%options, + qw(browsenode mode)); + + $class->_convert_option(\%options, + 'browsenode', + 'BrowseNodeSearch', + \&_assert_node_is_numeric); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +## +## 'PRIVATE' FUNCTIONS +## + +# _assert_node_is_numeric( OPTIONS, KEY ) +# +# Takes a reference to a hash of OPTIONS and makes sure +# that the browse node id keyed by KEY is numeric. +# +# Returns if all is well, dies otherwise. +# +sub _assert_node_is_numeric { + my ($options, $key) = @_; + + die "Browse Node ID must be numeric." + if ( $options->{$key} =~ /\D/ ); +} + + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::BrowseNode - request class for browse node search + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::BrowseNode; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::BrowseNode->new( + browsenode => 30, + mode => 'books' + ); + + # Response is of type Net::Amazon::Response::BrowseNode + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::BrowseNode> is a class used to submit node search +requests to the Amazon web service. + +The node to search for is specified in the C<browsenode> parameter. The +browse node ID is a number that corresponds to a general subject area +of Amazon.com. + +To find browse node IDs, the best way is to visit the "browse" area +of the various product lines at Amazon.com. When you find a subject area +that you would like to generate XML for, look at the web page URL. The +browse ID should appear after the string "/tg/browse/-/". Here are some +examples of URLs that contain browse IDs: + +=over 8 + +http://www.amazon.com/exec/obidos/tg/browse/-/30 +(In this example, the browse ID = 30) + +http://www.amazon.com/exec/obidos/tg/browse/-/467970 +(In this example, the browse ID = 467970) + +http://www.amazon.com/exec/obidos/tg/browse/-/602314 +(In this example, the browse ID = 60231 + +=back + +Please be aware that some nodes cannot be used with a +BrowseNodeSearch. (The vast majority of them can, but you +may run across a few that simply will not work). It is also +important to point out that from time to time, some browse +nodes are deprecated or are changed without notice. + +The catalog to search in is specified in the C<mode> parameter, +typical values are C<books>, C<music>, C<classical> or C<electronics>. + +An optional C<keywords> parameter may be added to filter the results by that keyword. + +Upon success, the responses' C<properties()> method will return a list of +C<Net::Amazon::Properties::*> objects. + +=head2 METHODS + +=over 4 + +=item new( browsenode => $nodeID, mode => $mode [, keywords => $keywords] ) + +Constructs a new C<Net::Amazon::Request::BrowseNode> object, used to query +the Amazon web service for items in a particular category (node) in the +mode (catalog) specified. + +=back + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head1 AUTHOR + +Net::Amazon framework by Mike Schilli, E<lt>m@perlmeister.comE<gt> + +BrowseNode.pm by Jackie Hamilton, E<lt>kira@cgi101.comE<gt> + +=head1 LICENSE + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Exchange.pm b/lib/Net/Amazon/Request/Exchange.pm new file mode 100644 index 0000000..df75549 --- /dev/null +++ b/lib/Net/Amazon/Request/Exchange.pm @@ -0,0 +1,91 @@ +###################################################################### +package Net::Amazon::Request::Exchange; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + if(exists $options{exchange}) { + $options{ExchangeSearch} = $options{exchange}; + delete $options{exchange}; + $options{type} = "lite"; + $options{mode} = ""; + } else { + die "Mandatory parameter 'exchange' not defined"; + } + +# if(!exists $options{exchange}) { +# die "Mandatory parameter 'exchange' not defined"; +# } + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Exchange - Class for submitting Exchange requests + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Exchange; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Exchange->new( + exchange => 'Y04Y3424291Y2398445' + ); + + # Response is of type Net::Amazon::Response::Seller + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Exchange> is a class used to submit Exchange search +requests to the Amazon web service. Exchange requests send an item's +exchange ID and retrieve a description of the item, offered by a third +party seller on Amazon. + +Upon success, the responses' C<result()> method will return a single +C<Net::Amazon::Result::Seller::Listing> object. + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head2 METHODS + +=over 4 + +=item new( exchange => $exchange_id ) + +Constructs a new C<Net::Amazon::Request::Exchange> object, used to query +the Amazon web service with the given seller id. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2005 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Keyword.pm b/lib/Net/Amazon/Request/Keyword.pm new file mode 100644 index 0000000..0b15d1d --- /dev/null +++ b/lib/Net/Amazon/Request/Keyword.pm @@ -0,0 +1,90 @@ +###################################################################### +package Net::Amazon::Request::Keyword; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + $class->_assert_options_defined(\%options, + qw(keyword mode)); + + $class->_convert_option(\%options, + 'keyword', + 'KeywordSearch'); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Keyword - request class for keyword search + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Keyword; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Keyword->new( + keyword => 'Zwan', + mode => 'books' + ); + + # Response is of type Net::Amazon::Response::Keyword + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Keyword> is a class used to submit keyword search +requests to the Amazon web service. + +The keyword to search for is specified in the C<keyword> parameter. + +The catalog to search in is specified in the C<mode> parameter, +typical values are C<books>, C<music>, C<classical> or C<electronics>. + +Upon success, the responses' C<properties()> method will return a list of +C<Net::Amazon::Property::*> objects. + +=head2 METHODS + +=over 4 + +=item new( keyword => $keyword, mode => $mode ) + +Constructs a new C<Net::Amazon::Request::Keyword> object, used to query +the Amazon web service for items matching a given keyword in the +mode (catalog) specified. + +=back + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Manufacturer.pm b/lib/Net/Amazon/Request/Manufacturer.pm new file mode 100644 index 0000000..0e8adb8 --- /dev/null +++ b/lib/Net/Amazon/Request/Manufacturer.pm @@ -0,0 +1,78 @@ +######################################
+package Net::Amazon::Request::Manufacturer;
+######################################
+use warnings;
+use strict;
+use base qw(Net::Amazon::Request);
+
+######################################
+sub new {
+######################################
+ my($class, %options) = @_;
+
+ $class->_assert_options_defined(\%options,
+ 'manufacturer');
+
+ $class->_convert_option(\%options,
+ 'manufacturer',
+ 'ManufacturerSearch');
+
+ my $self = $class->SUPER::new(%options);
+
+ bless $self, $class; # reconsecrate
+}
+1;
+
+__END__
+
+=head1 NAME
+
+Net::Amazon::Request::Manufacturer - Class for submitting Manufacturer requests
+
+=head1 SYNOPSIS
+
+ use Net::Amazon;
+ use Net::Amazon::Request::Manufacturer;
+
+ my $ua = Net::Amazon->new(
+ token => 'YOUR_AMZN_TOKEN'
+ );
+
+ my $req = Net::Amazon::Request::Manufacturer->new(
+ manufacturer => 'Disney'
+ );
+
+ # Response is of type Net::Amazon::Response::Manufacturer
+ my $resp = $ua->request($req);
+
+=head1 DESCRIPTION
+
+C<Net::Amazon::Request::Manufacturer> is a class used to submit
+searches for items made by a given manufacturer.
+
+The manufacturer to search for is specified in the C<manufacturer> parameter.
+
+Upon success, the responses' C<properties()> method will return one
+or more C<Net::Amazon::Property::*> objects.
+
+=head2 METHODS
+
+=over 4
+
+=item new( manufacturer => $manufacturer )
+
+Constructs a new C<Net::Amazon::Request::Manufacturer> object, used to query
+the Amazon web service for an item with the specified manufacturer name.
+
+=back
+
+Check L<Net::Amazon::Request> for common request parameters not listed here.
+
+=head1 SEE ALSO
+
+=head1 AUTHORS
+
+Bill Fitzpatrick
+Mike Schilli, E<lt>m@perlmeister.comE<gt>
+
+=cut
diff --git a/lib/Net/Amazon/Request/Power.pm b/lib/Net/Amazon/Request/Power.pm new file mode 100644 index 0000000..fdfd74e --- /dev/null +++ b/lib/Net/Amazon/Request/Power.pm @@ -0,0 +1,95 @@ +###################################################################### +package Net::Amazon::Request::Power; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + $class->_assert_options_defined(\%options, + qw(power mode)); + + $class->_convert_option(\%options, + 'power', + 'PowerSearch'); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Power - request class for 'Power Search' + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Power; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Power->new( + power => 'subject: perl and author: schwartz', + mode => 'books', + ); + + # Response is of type Net::Amazon::Response::Power + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Power> is a class used to request +so-called I<Power Searches> from the Amazon web service. + +The C<power> parameter specifies the power search string, C<mode> +defines which properties to look for. + +Upon success, the response's C<properties()> method will return a list +of C<Net::Amazon::Property::*> objects. + +=head2 METHODS + +=over 4 + +=item new(power => $search_string, mode => $property) + +Constructs a new C<Net::Amazon::Request::Power> object. C<$property> +is typically C<"books">. Examples for C<$search_string> are: + + author: schwartz + + author: schwartz and pubdate: after 10-2002 + + subject: perl and (objects or object-oriented) + + keywords: "high tech*" and not fiction and pubdate: during 1999 + + power "author: randal schwartz and publisher: Addison Wesley" + + author: randal schwartz and title: object books + +See the "Amazon Web Services 2.1 API and Integration Guide" for details. + +=back + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head1 SEE ALSO + +=head1 AUTHORS + +Martin Streicher, E<lt>martin.streicher@apress.comE<gt> +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=cut diff --git a/lib/Net/Amazon/Request/Seller.pm b/lib/Net/Amazon/Request/Seller.pm new file mode 100644 index 0000000..54e1930 --- /dev/null +++ b/lib/Net/Amazon/Request/Seller.pm @@ -0,0 +1,84 @@ +###################################################################### +package Net::Amazon::Request::Seller; +###################################################################### +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + if(exists $options{seller}) { + $options{SellerSearch} = $options{seller}; + delete $options{seller}; + } else { + die "Mandatory parameter 'seller' not defined"; + } + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Seller - Class for submitting Seller requests + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Seller; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Seller->new( + seller => 'A2GXAGU54VOP7' + ); + + # Response is of type Net::Amazon::Response::Seller + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Seller> is a class used to submit Seller search +requests to the Amazon web service. + +The seller to search for is specified in the C<seller> parameter, which +contains the seller's ID (not the seller's nickname!). + +Upon success, the responses' C<result()> method will return a single +C<Net::Amazon::Result::Seller> object. + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head2 METHODS + +=over 4 + +=item new( seller => $seller_id ) + +Constructs a new C<Net::Amazon::Request::Seller> object, used to query +the Amazon web service with the given seller id. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2004 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Similar.pm b/lib/Net/Amazon/Request/Similar.pm new file mode 100644 index 0000000..c5b5a53 --- /dev/null +++ b/lib/Net/Amazon/Request/Similar.pm @@ -0,0 +1,95 @@ +###################################################################### +package Net::Amazon::Request::Similar; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +our $AMZN_XML_URL = "http://xml.amazon.com/onca/xml3"; + +################################################## +sub amzn_xml_url { +################################################## + return $AMZN_XML_URL; +} + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + # For backwards compatibility + $class->_convert_option(\%options, 'asin', 'similar'); + + $class->_assert_options_defined(\%options, 'similar'); + + $class->_convert_option(\%options, + 'similar', + 'SimilaritySearch'); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Similar - request class for 'Similarities Search' + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Similar; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Similar->new( + similar => 'B00005B6TL', + ); + + # Response is of type Net::Amazon::Response::Similar + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Similar> is a class used to request +so-called I<Similarities Searches> from the Amazon web service. + +The C<asin> parameter specifies the ASIN of the item which you want +to get similar items for. + +Upon success, the response's C<properties()> method will return a list +of C<Net::Amazon::Property::*> objects. + +=head2 METHODS + +=over 4 + +=item new(similar => $asin) + +Constructs a new C<Net::Amazon::Request::Similar> object. + +=back + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Sort.pm b/lib/Net/Amazon/Request/Sort.pm new file mode 100644 index 0000000..059845d --- /dev/null +++ b/lib/Net/Amazon/Request/Sort.pm @@ -0,0 +1,246 @@ +=pod
+
+=head1 SORT TYPES
+
+Search results can be sorted in various ways, depending on the type of
+product returned by the search. If a sort parameter isn't specified in
+the request, the results will be ordered as if the +salesrank value is
+specified.
+
+=head2 Sorting Books Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Average Customer Review | +reviewrank |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | +pricerank |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | +inverse-pricerank |
+ |-------------------------+-------------------------+
+ | Publication Date | +daterank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (Z-A) | -titlerank |
+ +---------------------------------------------------+
+
+=head2 Sorting Software Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical | +titlerank |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | +price |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | -price |
+ +---------------------------------------------------+
+
+=head2 Sorting Outdoor Living Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +psrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (Z-A) | -titlerank |
+ |-------------------------+-------------------------+
+ | Manufacturer (A-Z) | +manufactrank |
+ |-------------------------+-------------------------+
+ | Manufacturer (Z-A) | -manufactrank |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | +price |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | -price |
+ +---------------------------------------------------+
+
+=head2 Sorting Tools Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +psrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (Z-A) | -titlerank |
+ |-------------------------+-------------------------+
+ | Manufacturer (A-Z) | +manufactrank |
+ |-------------------------+-------------------------+
+ | Manufacturer (Z-A) | -manufactrank |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | +price |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | -price |
+ +---------------------------------------------------+
+
+=head2 Sorting Camera and Photo Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (Z-A) | -titlerank |
+ +---------------------------------------------------+
+
+=head2 Sorting Computers Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +psrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (Z-A) | -titlerank |
+ +---------------------------------------------------+
+
+=head2 Sorting Video Games Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical | +titlerank |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | +price |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | -price |
+ +---------------------------------------------------+
+
+=head2 Sorting Music Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +psrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Artist Name | +artistrank |
+ |-------------------------+-------------------------+
+ | Original Release Date | +orig-rel-date |
+ |-------------------------+-------------------------+
+ | Alphabetical | +titlerank |
+ +---------------------------------------------------+
+
+=head2 Sorting Office Products Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (Z-A) | -titlerank |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | +price |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | -price |
+ |-------------------------+-------------------------+
+ | Review | +reviewrank |
+ +---------------------------------------------------+
+
+=head2 Sorting Video Results
+
+ +---------------------------------------------------+
+ | Sort Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +psrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical | +titlerank |
+ +---------------------------------------------------+
+
+=head2 Sorting Electronics Results
+
+ +---------------------------------------------------+
+ | Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical | +titlerank |
+ |-------------------------+-------------------------+
+ | Review | +reviewrank |
+ +---------------------------------------------------+
+
+=head2 Sorting DVDs Results
+
+ +---------------------------------------------------+
+ | Type | Value |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical | +titlerank |
+ +---------------------------------------------------+
+
+=head2 Sorting Kitchen Results
+
+ +---------------------------------------------------+
+ | Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (Z-A) | -titlerank |
+ |-------------------------+-------------------------+
+ | Manufacturer (A-Z) | +manufactrank |
+ |-------------------------+-------------------------+
+ | Manufacturer (Z-A) | -manufactrank |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | +price |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | -price |
+ +---------------------------------------------------+
+
+=head2 Sorting Toys Results
+
+ +---------------------------------------------------+
+ | Type | Value |
+ |-------------------------+-------------------------+
+ | Featured Items | +pmrank |
+ |-------------------------+-------------------------+
+ | Bestselling | +salesrank |
+ |-------------------------+-------------------------+
+ | Price (Low to High) | +pricerank |
+ |-------------------------+-------------------------+
+ | Price (High to Low) | +inverse-pricerank |
+ |-------------------------+-------------------------+
+ | Alphabetical (A-Z) | +titlerank |
+ +---------------------------------------------------+
+
+=cut
diff --git a/lib/Net/Amazon/Request/TextStream.pm b/lib/Net/Amazon/Request/TextStream.pm new file mode 100644 index 0000000..62a0717 --- /dev/null +++ b/lib/Net/Amazon/Request/TextStream.pm @@ -0,0 +1,76 @@ +######################################################################
+package Net::Amazon::Request::TextStream;
+######################################################################
+use warnings;
+use strict;
+use base qw(Net::Amazon::Request);
+
+##################################################
+sub new {
+##################################################
+ my($class, %options) = @_;
+
+ $class->_assert_options_defined(\%options,
+ 'textstream');
+
+ $class->_convert_option(\%options,
+ 'textstream',
+ 'TextStreamSearch');
+
+ my $self = $class->SUPER::new(%options);
+
+ bless $self, $class; # reconsecrate
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Net::Amazon::Request::TextStream - request class for text stream search
+
+=head1 SYNOPSIS
+
+ use Net::Amazon;
+ use Net::Amazon::Request::TextStream;
+
+ my $ua = Net::Amazon->new(
+ token => 'YOUR_AMZN_TOKEN'
+ );
+
+ my $req = Net::Amazon::Request::TextStream->new(
+ textstream => 'Here is some text that mentions the Rolling Stones.',
+ );
+
+ # Response is of type Net::Amazon::Response::TextStream
+ my $resp = $ua->request($req);
+
+=head1 DESCRIPTION
+
+C<Net::Amazon::Request::TextStream> is a class used to perform a search on
+a block of text. Amazon extracts keywords from the given block of text,
+but note that conjunctions and helper words, such as "and", "or", "the",
+etc. are not excluded, so strip them out yourself if need be.
+
+TextStream searching is only available for the US service.
+
+Upon success, the response's C<properties()> method will return a list
+of C<Net::Amazon::Property::*> objects.
+
+=head2 METHODS
+
+=over 4
+
+=item new(textstream => $text)
+
+Constructs a new C<Net::Amazon::Request::TextStream> object, used to query
+the Amazon web service with a block of text.
+
+=back
+
+Check L<Net::Amazon::Request> for common request parameters not listed here.
+
+=head1 SEE ALSO
+
+=cut
diff --git a/lib/Net/Amazon/Request/UPC.pm b/lib/Net/Amazon/Request/UPC.pm new file mode 100644 index 0000000..ae77645 --- /dev/null +++ b/lib/Net/Amazon/Request/UPC.pm @@ -0,0 +1,94 @@ +###################################################################### +package Net::Amazon::Request::UPC; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + $class->_assert_options_defined(\%options, 'upc'); + + $class->_convert_option(\%options, + 'upc', + 'UpcSearch'); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::UPC - request class for UPC search + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::UPC; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::UPC->new( + upc => '724381198421', + mode => 'music', + + ); + + # Response is of type Net::Amazon::Response::UPC + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::UPC> is a class used to submit UPC (product barcode) +search requests to the Amazon web service. + +The UPC number to search for is specified in the C<upc> parameter. +It currently works with the following values of the C<mode> parameter: +C<music>, +C<classical>, +C<software>, +C<dvd>, +C<video>, +C<vhs>, +C<electronics>, +C<pc-hardware>, and +C<photo>. + +Upon success, the response's C<properties()> method will return a single +C<Net::Amazon::Property::Music> object. + +=head2 METHODS + +=over 4 + +=item new(upc => $upc) + +Constructs a new C<Net::Amazon::Request::UPC> object, used to query +the Amazon web service for an item with the given UPC number. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Request/Wishlist.pm b/lib/Net/Amazon/Request/Wishlist.pm new file mode 100644 index 0000000..824d5d4 --- /dev/null +++ b/lib/Net/Amazon/Request/Wishlist.pm @@ -0,0 +1,89 @@ +###################################################################### +package Net::Amazon::Request::Wishlist; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Request); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + # For backwards compatibility: + $class->_convert_option(\%options, 'id', 'wishlist'); + + $class->_assert_options_defined(\%options, 'wishlist'); + + $class->_convert_option(\%options, + 'wishlist', + 'WishlistSearch'); + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Request::Wishlist - request class for wishlist search + +=head1 SYNOPSIS + + use Net::Amazon; + use Net::Amazon::Request::Wishlist; + + my $ua = Net::Amazon->new( + token => 'YOUR_AMZN_TOKEN' + ); + + my $req = Net::Amazon::Request::Wishlist->new( + wishlist => '1XL5DWOUFMFVJ', + ); + + # Response is of type Net::Amazon::Response::Wishlist + my $resp = $ua->request($req); + +=head1 DESCRIPTION + +C<Net::Amazon::Request::Wishlist> is a class used to request +a specified wishlist. + +The wishlist ID (can be found as parameters in URLs when a customer's +Amazon wishlist is being pulled up) is specified in the C<id> parameter. + +Upon success, the response's C<properties()> method will return a list +of C<Net::Amazon::Property::*> objects. + +=head2 METHODS + +=over 4 + +=item new(wishlist => $id) + +Constructs a new C<Net::Amazon::Request::Wishlist> object, used to query +the Amazon web service for a specific wishlist, identified by the wishlist +ID. + +=back + +Check L<Net::Amazon::Request> for common request parameters not listed here. + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Response.pm b/lib/Net/Amazon/Response.pm new file mode 100644 index 0000000..6147e0e --- /dev/null +++ b/lib/Net/Amazon/Response.pm @@ -0,0 +1,214 @@ +###################################################################### +package Net::Amazon::Response; +###################################################################### +use warnings; +use strict; + +use base qw(Net::Amazon); + +use Text::Wrap qw($columns wrap); +use XML::Simple; + +our @FORCE_ARRAY_FIELDS = (); + +__PACKAGE__->make_accessor($_) for qw( + status messages items xmlref total_results); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = { + status => "", + messages => [], + items => [], + xmlref => {}, + total_results => undef, + }; + + bless $self, $class; +} + +sub message { + my($self) = @_; + + return join(";",@{$self->{messages}}); +} + +########################################### +sub is_success { +########################################### + my($self) = @_; + + return $self->{status} ? 1 : ""; +} + +########################################### +sub is_error { +########################################### + my($self) = @_; + + return !$self->is_success(); +} + +########################################### +sub push_item { +########################################### + my($self, $item) = @_; + + push @{$self->{items}}, $item; +} + +########################################### +sub as_string { +########################################### + my($self) = @_; + + return Data::Dumper::Dumper($self); +} + +########################################### +sub list_as_string { +########################################### + my($self, @properties) = @_; + + my $full = ""; + + # Column with + $columns = 60; + my $bullet = 1; + + foreach my $property (@properties) { + $full .= "\n" if $full; + my $bullet_string = sprintf("[%d]%s", + $bullet, (" " x (3-length($bullet)))); + $full .= wrap("", " ", $bullet_string . $property->as_string()); + $bullet++; + } + + return $full; +} + +################################################## +sub properties { +################################################## + my($self) = @_; + + my @properties = (); + + if($self->is_success && ref($self->{xmlref}->{Details}) eq 'ARRAY') { + foreach my $xmlref (@{$self->{xmlref}->{Details}}) { + my $property = Net::Amazon::Property::factory(xmlref => $xmlref); + push @properties, $property; + } + } + + if(wantarray) { + return (@properties); + } + + if(@properties) { + # Scalar context and we've got results. Return the first one. + return $properties[0]; + } + + # Scalar context and we don't have anything. + return undef; +} + +################################################## +sub xml_parse { +################################################## + my($self, $xml) = @_; + + my $xs = XML::Simple->new(); + return $xs->XMLin($xml, ForceArray => [ @FORCE_ARRAY_FIELDS ]); +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Response - Baseclass for responses from Amazon's web service + +=head1 SYNOPSIS + + $resp = $ua->request($request); + + if($resp->is_success()) { + print $resp->as_string(); + } + + if($resp->is_error()) { + print $resp->message(); + } + + if($resp->is_success()) { + for my $property ($resp->properties) { + print $property->as_string(), "\n"; + } + } + +=head1 DESCRIPTION + +C<Net::Amazon::Response> is the baseclass for responses coming back +from the useragent's C<request> method. Responses are typically +not of type C<Net::Amazon::Response> but one of its subclasses +C<Net::Amazon::Response::*>. However, for basic error handling and +dumping content, C<Net::Amazon::Response>'s methods are typically used, +because we typically don't know what type of object we're +actually dealing with. + +=head2 METHODS + +=over 4 + +=item is_success() + +Returns true if the request was successful. This doesn't mean any objects +have been found, it just indicates a successful roundtrip. + +=item is_error() + +Returns true if an error occurred. Use C<message()> to determine what +kind of error. + +=item properties() + +Returns the list of C<Net::Amazon::Property> objects which were found +by the query. + +=item as_string() + +Dumps the content of the response. + +=item message() + +Returns the error message as a string in case an error occurred. In case +several errors occurred, they're stringed together. Look up C<messages()> +if you need them separated. + +=item messages() + +Returns all error messages for a response as a reference to an array +of string messages. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2003 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Response/ASIN.pm b/lib/Net/Amazon/Response/ASIN.pm new file mode 100644 index 0000000..32bad76 --- /dev/null +++ b/lib/Net/Amazon/Response/ASIN.pm @@ -0,0 +1,28 @@ +###################################################################### +package Net::Amazon::Response::ASIN; +###################################################################### +use warnings; +use strict; +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 +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +1; diff --git a/lib/Net/Amazon/Response/Artist.pm b/lib/Net/Amazon/Response/Artist.pm new file mode 100644 index 0000000..0ee653b --- /dev/null +++ b/lib/Net/Amazon/Response/Artist.pm @@ -0,0 +1,29 @@ +###################################################################### +package Net::Amazon::Response::Artist; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Response); + +use Net::Amazon::Property; +use Data::Dumper; + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +1; diff --git a/lib/Net/Amazon/Response/Blended.pm b/lib/Net/Amazon/Response/Blended.pm new file mode 100644 index 0000000..3c75ded --- /dev/null +++ b/lib/Net/Amazon/Response/Blended.pm @@ -0,0 +1,73 @@ +###################################################################### +package Net::Amazon::Response::Blended; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Response); + +use Net::Amazon::Property; +use XML::Simple; + +our @FORCE_ARRAY_FIELDS = qw(ProductLine); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +################################################## +sub xmlref_add { +################################################## + my($self, $xmlref) = @_; + + my $nof_items_added = 0; + + unless(ref($self->{xmlref}) eq "HASH" && + ref($self->{xmlref}->{Details}) eq "ARRAY") { + $self->{xmlref}->{Details} = []; + } + + if ($xmlref->{ProductLine} && (ref($xmlref->{ProductLine}) eq "ARRAY")) { + my @lines = @{$xmlref->{ProductLine}}; # Copy the lines + # sort the copies by relevance + @lines = sort { $a->{RelevanceRank} <=> $b->{RelevanceRank} } @lines; + + foreach (@lines) { + next unless $_->{ProductInfo}->{Details}; + my $details = $_->{ProductInfo}->{Details}; + if (ref($details) eq "ARRAY") { + push @{$self->{xmlref}->{Details}}, @$details; + $nof_items_added += scalar @$details; + } else { + push @{$self->{xmlref}->{Details}}, $details; + $nof_items_added++; + } + } + } + + return $nof_items_added; +} + +################################################## +sub xml_parse { +################################################## + my($self, $xml) = @_; + + my $xs = XML::Simple->new(); + return $xs->XMLin($xml, ForceArray => [ @FORCE_ARRAY_FIELDS ]); +} + +1; diff --git a/lib/Net/Amazon/Response/BrowseNode.pm b/lib/Net/Amazon/Response/BrowseNode.pm new file mode 100644 index 0000000..8f40a63 --- /dev/null +++ b/lib/Net/Amazon/Response/BrowseNode.pm @@ -0,0 +1,29 @@ +###################################################################### +package Net::Amazon::Response::BrowseNode; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Response); + +use Net::Amazon::Property; +use Data::Dumper; + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +1; diff --git a/lib/Net/Amazon/Response/Exchange.pm b/lib/Net/Amazon/Response/Exchange.pm new file mode 100644 index 0000000..56852fe --- /dev/null +++ b/lib/Net/Amazon/Response/Exchange.pm @@ -0,0 +1,81 @@ +###################################################################### +package Net::Amazon::Response::Exchange; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Response); + +use Net::Amazon::Property; +use Net::Amazon::Result::Seller::Listing; +use Log::Log4perl qw(:easy); + +__PACKAGE__->make_array_accessor($_) for qw(listings); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +################################################## +sub result { +################################################## + my($self) = @_; + + if($self->is_success()) { + return Net::Amazon::Result::Seller::Listing->new( + xmlref => $self->{xmlref}->{ListingProductDetails}->[0], + ); + } + + return undef; +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return "TODO: as_string not defined yet in ", __PACKAGE__; +} + +################################################## +sub xmlref_add { +################################################## + my($self, $xmlref) = @_; + + my $nof_items_added = 0; + + unless(ref($self->{xmlref}) eq "HASH" && + ref($self->{xmlref}->{ListingProductDetails}) eq "ARRAY") { + $self->{xmlref}->{Details} = []; + } + + if(ref($xmlref->{ListingProductDetails}) eq "ARRAY") { + # Is it an array of items? + push @{$self->{xmlref}->{ListingProductDetails}}, + @{$xmlref->{ListingProductDetails}}; + $nof_items_added = scalar @{$xmlref->{ListingProductDetails}}; + } else { + # It is a single item + push @{$self->{xmlref}->{ListingProductDetails}}, + $xmlref->{ListingProductDetails}; + $nof_items_added = 1; + } + + return $nof_items_added; +} + +################################################## +sub properties { +################################################## + my($self) = @_; + + die "properties() not defined in ", __PACKAGE__, ". Use result() instead"; +} + +1; diff --git a/lib/Net/Amazon/Response/Keyword.pm b/lib/Net/Amazon/Response/Keyword.pm new file mode 100644 index 0000000..f6277d6 --- /dev/null +++ b/lib/Net/Amazon/Response/Keyword.pm @@ -0,0 +1,29 @@ +###################################################################### +package Net::Amazon::Response::Keyword; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Response); + +use Net::Amazon::Property; +use Data::Dumper; + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +1; diff --git a/lib/Net/Amazon/Response/Manufacturer.pm b/lib/Net/Amazon/Response/Manufacturer.pm new file mode 100644 index 0000000..97ceebf --- /dev/null +++ b/lib/Net/Amazon/Response/Manufacturer.pm @@ -0,0 +1,29 @@ +######################################################################
+package Net::Amazon::Response::Manufacturer;
+######################################################################
+use warnings;
+use strict;
+use base qw(Net::Amazon::Response);
+
+use Net::Amazon::Property;
+use Data::Dumper;
+
+##################################################
+sub new {
+##################################################
+ my($class, %options) = @_;
+
+ my $self = $class->SUPER::new(%options);
+
+ bless $self, $class; # reconsecrate
+}
+
+##################################################
+sub as_string {
+##################################################
+ my($self) = @_;
+
+ return $self->SUPER::list_as_string($self->properties);
+}
+
+1;
diff --git a/lib/Net/Amazon/Response/Power.pm b/lib/Net/Amazon/Response/Power.pm new file mode 100644 index 0000000..88cdd2d --- /dev/null +++ b/lib/Net/Amazon/Response/Power.pm @@ -0,0 +1,28 @@ +###################################################################### +package Net::Amazon::Response::Power; +###################################################################### +use warnings; +use strict; +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 +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +1; diff --git a/lib/Net/Amazon/Response/Seller.pm b/lib/Net/Amazon/Response/Seller.pm new file mode 100644 index 0000000..f33ed50 --- /dev/null +++ b/lib/Net/Amazon/Response/Seller.pm @@ -0,0 +1,78 @@ +############################################# +package Net::Amazon::Response::Seller; +############################################# +use base qw(Net::Amazon::Response); + +use Net::Amazon::Result::Seller; +use Data::Dumper; +use Log::Log4perl qw(:easy); + +############################## +sub new { +############################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + bless $self, $class; # reconsecrate +} + +################################################## +sub result { +################################################## + my($self) = @_; + + if($self->is_success()) { + DEBUG "Calling Seller constructor with ", Dumper($self); + return Net::Amazon::Result::Seller->new( + xmlref => $self->{xmlref}->{SellerSearchDetails}->[0], + ); + } + + return undef; +} + +################################################## +sub properties { +################################################## + my($self) = @_; + + die "properties() not defined in ", __PACKAGE__, ". Use result() instead"; +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->result()->as_string(); +} + +################################################## +sub xmlref_add { +################################################## + my($self, $xmlref) = @_; + + my $nof_items_added = 0; + + unless(ref($self->{xmlref}) eq "HASH" && + ref($self->{xmlref}->{SellerSearchDetails}) eq "ARRAY") { + $self->{xmlref}->{Details} = []; + } + + if(ref($xmlref->{SellerSearchDetails}) eq "ARRAY") { + # Is it an array of items? + push @{$self->{xmlref}->{SellerSearchDetails}}, + @{$xmlref->{SellerSearchDetails}}; + $nof_items_added = scalar @{$xmlref->{SellerSearchDetails}}; + } else { + # It is a single item + push @{$self->{xmlref}->{SellerSearchDetails}}, + $xmlref->{SellerSearchDetails}; + $nof_items_added = 1; + } + + #DEBUG("xmlref_add (after):", Data::Dumper::Dumper($self)); + return $nof_items_added; +} + +1; diff --git a/lib/Net/Amazon/Response/Similar.pm b/lib/Net/Amazon/Response/Similar.pm new file mode 100644 index 0000000..27add88 --- /dev/null +++ b/lib/Net/Amazon/Response/Similar.pm @@ -0,0 +1,28 @@ +###################################################################### +package Net::Amazon::Response::Similar; +###################################################################### +use warnings; +use strict; +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 +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +1; diff --git a/lib/Net/Amazon/Response/TextStream.pm b/lib/Net/Amazon/Response/TextStream.pm new file mode 100644 index 0000000..0cee872 --- /dev/null +++ b/lib/Net/Amazon/Response/TextStream.pm @@ -0,0 +1,28 @@ +######################################################################
+package Net::Amazon::Response::TextStream;
+######################################################################
+use warnings;
+use strict;
+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
+}
+
+##################################################
+sub as_string {
+##################################################
+ my($self) = @_;
+
+ return $self->SUPER::list_as_string($self->properties);
+}
+
+1;
diff --git a/lib/Net/Amazon/Response/UPC.pm b/lib/Net/Amazon/Response/UPC.pm new file mode 100644 index 0000000..4fba4ad --- /dev/null +++ b/lib/Net/Amazon/Response/UPC.pm @@ -0,0 +1,40 @@ +###################################################################### +package Net::Amazon::Response::UPC; +###################################################################### +use warnings; +use strict; +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 +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + my($property) = $self->properties; + return $property->as_string(); +} + +################################################## +sub properties { +################################################## + my($self) = @_; + + my $property = Net::Amazon::Property::factory( + xmlref => $self->{xmlref}->{Details}->[0]); + + return ($property); +} + +1; diff --git a/lib/Net/Amazon/Response/Wishlist.pm b/lib/Net/Amazon/Response/Wishlist.pm new file mode 100644 index 0000000..15fd999 --- /dev/null +++ b/lib/Net/Amazon/Response/Wishlist.pm @@ -0,0 +1,29 @@ +###################################################################### +package Net::Amazon::Response::Wishlist; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon::Response); + +use Net::Amazon::Property; +use Data::Dumper; + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + my $self = $class->SUPER::new(%options); + + bless $self, $class; # reconsecrate +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + return $self->SUPER::list_as_string($self->properties); +} + +1; diff --git a/lib/Net/Amazon/Result/Seller.pm b/lib/Net/Amazon/Result/Seller.pm new file mode 100644 index 0000000..cb876a6 --- /dev/null +++ b/lib/Net/Amazon/Result/Seller.pm @@ -0,0 +1,130 @@ +###################################################################### +package Net::Amazon::Result::Seller; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon); + +use Data::Dumper; +use Log::Log4perl qw(:easy); +use Net::Amazon::Result::Seller::Listing; + +our @DEFAULT_ATTRIBUTES = qw(StoreName SellerNickname + NumberOfOpenListings StoreId); +__PACKAGE__->make_accessor($_) for @DEFAULT_ATTRIBUTES; +__PACKAGE__->make_array_accessor($_) for qw(listings); + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + if(!$options{xmlref}) { + die "Mandatory param xmlref missing"; + } + + my @listings = (); + + my $self = { + %options, + }; + + bless $self, $class; + + # Set default attributes + for my $attr (@DEFAULT_ATTRIBUTES) { + DEBUG "Setting attribute $attr to $options{xmlref}->{$attr}"; + $self->$attr($options{xmlref}->{$attr}); + } + + for my $listing (@{$options{xmlref}->{ListingProductInfo}->{ListingProductDetails}}) { + push @listings, + Net::Amazon::Result::Seller::Listing->new( + xmlref => $listing); + } + + $self->listings(\@listings); + + return $self; +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + my $result = $self->StoreName() . + " (" . + $self->SellerNickname() . + "): " . + $self->NumberOfOpenListings() . + ""; + + return $result; +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Result::Seller - Class for Seller info + +=head1 SYNOPSIS + + use Net::Amazon; + + # ... + + if($resp->is_success()) { + print $resp->result()->as_string(); + } + +=head1 DESCRIPTION + +C<Net::Amazon::Result::Seller> is a container for results on a seller +search. It contains data on one particular seller (the one turned up by +the previous search) and the listings this seller is currently running. + +=head2 METHODS + +=over 4 + +=item StoreName() + +Name of the seller's store. + +=item SellerNickname() + +Seller's nickname. + +=item StoreId() + +ID of seller's store. + +=item NumberOfOpenListings() + +Number of listings the seller has currently open. + +=item listings() + +Returns an array of C<Net::Amazon::Result::Seller::Listing> objects. +See the documentation of this class for details. + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2004 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/Amazon/Result/Seller/Listing.pm b/lib/Net/Amazon/Result/Seller/Listing.pm new file mode 100644 index 0000000..c09ec08 --- /dev/null +++ b/lib/Net/Amazon/Result/Seller/Listing.pm @@ -0,0 +1,146 @@ +###################################################################### +package Net::Amazon::Result::Seller::Listing; +###################################################################### +use warnings; +use strict; +use base qw(Net::Amazon); + +use Data::Dumper; +use Log::Log4perl qw(:easy); + +our @DEFAULT_ATTRIBUTES = qw( + ExchangeStartDate ExchangeConditionType + ExchangeAsin ExchangeSellerId ExchangeEndDate ExchangePrice + ExchangeSellerRating ExchangeStatus ExchangeId ExchangeTitle + ExchangeQuantityAllocated ExchangeQuantity ExchangeSellerCountry + ExchangeSellerState ExchangeSellerNickname ExchangeFeaturedCategory + ExchangeAvailability ExchangeOfferingType ListingId ExchangeCondition + ExchangeDescription +); + +__PACKAGE__->make_accessor($_) for @DEFAULT_ATTRIBUTES; + +################################################## +sub new { +################################################## + my($class, %options) = @_; + + if(!$options{xmlref}) { + die "Mandatory param xmlref missing"; + } + + my $self = { + %options, + }; + + bless $self, $class; + + DEBUG "Calling Listing with xmlref=", Dumper($options{xmlref}); + + # Set default attributes + for my $attr (@DEFAULT_ATTRIBUTES) { + $self->$attr($options{xmlref}->{$attr}); + } + + return $self; +} + +################################################## +sub as_string { +################################################## + my($self) = @_; + + my $result = + $self->ExchangeTitle() . + " (" . + $self->ExchangeAsin() . + "): " . + $self->ExchangePrice() . + ""; + + return $result; +} + +1; + +__END__ + +=head1 NAME + +Net::Amazon::Result::Seller::Listing - Class for a single Listing of a Seller + +=head1 SYNOPSIS + + for($seller_search_resp->result()->seller()->listings()) { + print $_->as_string(), "\n"; + } + +=head1 DESCRIPTION + +C<Net::Amazon::Result::Seller::Listing> is a container for a single listing +owned by a third-party seller, who is represented by a +C<Net::Amazon::Result::Seller> object. + +An object of this class is also returned by an C<Exchange> request, using +C<Net::Amazon::Response::Exchange>'s C<result> method. + +=head2 METHODS + +=over 4 + +=item ExchangeStartDate() + +=item ExchangeConditionType() + +=item ExchangeAsin() + +=item ExchangeSellerId() + +=item ExchangeEndDate() + +=item ExchangePrice() + +=item ExchangeSellerRating() + +=item ExchangeStatus() + +=item ExchangeId() + +=item ExchangeTitle() + +=item ExchangeQuantityAllocated() + +=item ExchangeQuantity() + +=item ExchangeSellerCountry() + +=item ExchangeSellerState() + +=item ExchangeSellerNickname() + +=item ExchangeFeaturedCategory() + +=item ExchangeAvailability() + +=item ExchangeOfferingType() + +=item ListingId() + +=item ExchangeCondition() + +=back + +=head1 SEE ALSO + +=head1 AUTHOR + +Mike Schilli, E<lt>m@perlmeister.comE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright 2004 by Mike Schilli E<lt>m@perlmeister.comE<gt> + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Net/IP/Match/Regexp.pm b/lib/Net/IP/Match/Regexp.pm new file mode 100644 index 0000000..9d89837 --- /dev/null +++ b/lib/Net/IP/Match/Regexp.pm @@ -0,0 +1,293 @@ +package Net::IP::Match::Regexp; + +require 5.006; +use strict; +use warnings; + +use base 'Exporter'; +our @EXPORT = qw(); +our @EXPORT_OK = qw( create_iprange_regexp match_ip ); +our $VERSION = '0.91'; + +=head1 NAME + +Net::IP::Match::Regexp - Efficiently match IPv4 addresses against IPv4 ranges via regexp + +=head1 LICENSE + +Copyright 2005 Clotho Advanced Media, Inc., <cpan@clotho.com> + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 SYNOPSIS + + use Net::IP::Match::Regexp qw( create_iprange_regexp match_ip ); + + my $regexp = create_iprange_regexp( + qw( 10.0.0.0/8 87.134.66.128 87.134.87.0/24 145.97.0.0/16 ) + ); + if (match_ip("209.249.163.62", $regexp)) { + ... + } + +=head1 DESCRIPTION + +This module allows you to check an IP address against one or more IP +ranges. It employs Perl's highly optimized regular expression engine +to do the hard work, so it is very fast. It is optimized for speed by +doing the match against a pre-computed regexp, which implicitly checks +the broadest IP ranges first. An advantage is that the regexp can be +comuted and stored in advance (in source code, in a database table, +etc) and reused, saving much time if the IP ranges don't change too +often. The match can optionally report a value instead of just a +boolean, which makes module useful for mapping IP ranges to names or +codes or anything else. + +=head1 SEE ALSO + +There are several other CPAN modules that perform a similar function. +This one is faster than the other ones that I've found and tried. +Here is a synopsis of those others: + +=head2 Net::IP::Match + +Optimized for speed by taking a "source filter" approach. That is, it +modifies your source code at run time, kind of like a C preprocessor. +A huge limitation is that the IP ranges must be hard-coded into your +program. + +=head2 Net::IP::Match::XS + +(Also released as Net::IP::CMatch) + +Optimized for speed by doing the match in C instead of in Perl. This +module loses efficiency, however, because the IP ranges must be +re-parsed every invokation. + +=head2 Net::IP::Match::Resolver + +Uses Net::IP::Match::XS to implement a range-to-name map. + + +=head1 PERFORMANCE + +I ran a test on my Mac G5 to compare this module to +Net::IP::Match::XS. The test was intended to be a realistic net +filter case: 100,000 random IPs tested against 300 semi-random IP +ranges. Times are in seconds. + + Module | Setup time | Run time + -----------------------+------------+---------- + Net::IP::Match::Regexp | 0.057 | 1.663 + Net::IP::Match::XS | n/a | 4.238 + +=head1 IMPLEMENTATION + +The speed of this module comes from the short-circuit nature of +regular expressions. The setup function turns all of the IP ranges +into binary strings, and mixes them into a regexp with C<|> choices +between ones and zeros. This regexp can then be passed to the match +function. When an unambiguous match is found, the regexp sets a +variable (via the regexp $^R feature) and terminates. That variable +becomes the return value for the match, typically a true value. + +Here's an example of the regexp for a single range, that of my company's subnet: + + print create_iprange_regexp("209.249.163.0/25")' + # ^1101000111111001101000110(?{'1'}) + +If I add another range, say a NAT LAN, I get: + + print create_iprange_regexp("209.249.163.0/25", "192.168.0.0/16")' + # ^110(?:0000010101000(?{'1'})|1000111111001101000110(?{'1'})) + +Note that for a 192.168.x.x address, the regexp checks at most the +first 16 bits (1100000010101000) whereas for a 209.249.163.x address, +it goes out to 15 bits (1101000111111001101000110). The cool part is +that for an IP address that starts in the lower half (say 127.0.0.1) +only needs to check the first bit (0) to see that the regexp won't +match. + +If mapped return values are specified for the ranges, they get embedded +into the regexp like so: + + print create_iprange_regexp({"209.249.163.0/25" => "clotho.com", + "192.168.0.0/16" => "localhost"})' + # ^110(?:0000010101000(?{'localhost'})|1000111111001101000110(?{'clotho.com'})) + +This could be implemented in C to be even faster. In C, it would +probably be better to use a binary tree instead of a regexp. However, +a goal of this module is to create a serializable representation of +the range data, and the regexp is perfect for that. So, we'll +probably never do a C version. + +=head1 COMPATIBILITY + +Because this module relies on the C<(?{ code })> feature of regexps, +it won't work on old Perl versions. I've successfully tested this +module on Perl 5.6.0, 5.8.1 and 5.8.6. In theory, the code regexp +feature should work in 5.005, but I've used "our" and the like, so it +won't work there. I don't have a 5.005 to test anyway... + +=head1 FUNCTIONS + +=over + +=cut + + +=item create_iprange_regexp IPRANGE | MAP, ... + +This function digests IP ranges into a regular expression that can +subsequently be used to efficiently test single IP addresses. It +returns a regular expression string that can be passed to match_ip(). + +The simple way to use this is to pass a list of IP ranges as +C<aaa.bbb.ccc.ddd/n>. When used this way, the return value of the +match_ip() function will be simply C<1> or C<undef>. + +The more complex way is to pass a hash reference of IP range => return +value pairs. When used this way, the return value of the match_ip() +function will be the specified return value or C<undef> for no match. + +For example: + + my $re1 = create_iprange_regexp("209.249.163.0/25", "127.0.0.1/32"); + print match_ip("209.249.163.62", $re1); # prints "1" + + my $re2 = create_iprange_regexp({"209.249.163.0/25" => "clotho.com", + "127.0.0.1/32" => "localhost"}); + print match_ip("209.249.163.62", $re2); # prints "clotho.com" + +Note that these two styles can be mixed (a rarely used feature). +These two examples are equivalent: + + create_iprange_regexp("127.0.0.1/32", + {"209.249.163.0/25" => "clotho.com"}, + "10.0.0.0/8", + {"192.168.0.0/16" => "LAN"}); + + create_iprange_regexp({"127.0.0.1/32" => 1, + "209.249.163.0/25" => "clotho.com", + "10.0.0.0/8" => 1, + "192.168.0.0/16" => "LAN"}); + +Special note: the value string will be wrapped in single-quotes in the +regexp. Therefore, you must double-escape any single quotes in that value. +For example: + + create_iprange_regexp({"208.201.239.36/31" => "O\\'Reilly publishing"}); + +Warning: This function does no checking for validity of IP ranges. It +happily accepts C<1000.0.0.0/-38>. Hopefully a future version will +validate the ranges, perhaps via Net::CIDR or Net::IP. + +=cut + +sub create_iprange_regexp +{ + # If an argument is a hash ref, flatten it + # If an argument is a scalar, make it a key and give it a value of 1 + my %map = map {ref $_ ? %$_ : ($_ => 1)} @_; + + # The tree is a temporary construct. It has three possible + # properties: 0, 1, and code. The code is the return value for a + # match. + my %tree; + + for my $range (keys %map) + { + my ($ip,$mask) = split /\//, $range; + + my $tree = \%tree; + my @bits = split //, unpack("B32", pack("C4", split(/\./, $ip))); + for my $val (@bits[0..$mask-1]) + { + # If this case is hit, it means that our IP range is a subset + # of some other range. + last if ($tree->{code}); + + $tree->{$val} ||= {}; + $tree = $tree->{$val}; + } + # If the code is already set, it's a non-fatal error (bad data) + $tree->{code} ||= $map{$range}; + + # prune redundant branches + # this is only important if the range data is poor + delete $tree->{0}; + delete $tree->{1}; + } + + # Recurse into the tree making it into a regexp + my $re = "^".tree2re(\%tree); + return $re; +} + +=item match_ip IP_ADDR, REGEXP + +Given a single IP address as a string of the form C<aaa.bbb.ccc.ddd> +and a regular expression string (typically the output of +create_iprange_regexp()), this function returns a pre-specified value +(typically C<1>) if the IP is in one of the ranges, or C<undef> if no +ranges match. + +See create_ipranges_regexp() for more details about the return value +of this function. + +Warning: This function does no checking for validity of the IP address. + +=cut + +sub match_ip +{ + my ($ip,$re) = @_; + + local $^R; + use re 'eval'; + unpack("B32", pack("C4", split(/\./, $ip))) =~ /$re/; + return $^R; +} + +# Helper function. This recurses to build the regular expression +# string from a tree of IP ranges constructed by +# create_iprange_regexp(). + +sub tree2re +{ + my $tree = shift; + + if ($tree->{code}) + { + return "(?{'$$tree{code}'})"; + } + elsif ($tree->{0} && $tree->{1}) + { + return "(?:0".tree2re($tree->{0})."|1".tree2re($tree->{1}).")"; + } + elsif ($tree->{0}) + { + return "0".tree2re($tree->{0}); + } + elsif ($tree->{1}) + { + return "1".tree2re($tree->{1}); + } + else + { + die "Internal error"; + } +} + +1; + +__END__ + +=back + +=head1 AUTHOR + +Clotho Advanced Media, Inc. I<cpan@clotho.com> + +Primary developer: Chris Dolan |
