amazon-product-advertising-api-prezjordan 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/EXAMPLE.txt +128 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +99 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/amazon-product-advertising-api.gemspec +53 -0
- data/lib/amazon_product_advertising_api.rb +18 -0
- data/lib/amazon_product_advertising_api/base.rb +24 -0
- data/lib/amazon_product_advertising_api/operations/base.rb +184 -0
- data/lib/amazon_product_advertising_api/operations/browse_node.rb +70 -0
- data/lib/amazon_product_advertising_api/operations/item.rb +133 -0
- data/lib/amazon_product_advertising_api/response_elements.rb +560 -0
- data/lib/amazon_product_advertising_api/support.rb +67 -0
- metadata +96 -0
data/.gitignore
ADDED
data/EXAMPLE.txt
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
Basic usage
|
2
|
+
###########
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'amazon_product_advertising_api'
|
6
|
+
|
7
|
+
# Setup your API keys - in an initializer or something like that
|
8
|
+
AmazonProductAdvertisingApi::Base.access_key_id = "<insert api key here>"
|
9
|
+
AmazonProductAdvertisingApi::Base.secret_access_key = "<insert secret access key here>"
|
10
|
+
|
11
|
+
# Setup Associates IDs for whichever regions you're selling to
|
12
|
+
AmazonProductAdvertisingApi::Base.associate_ids.uk = "<insert UK Associate ID here>"
|
13
|
+
AmazonProductAdvertisingApi::Base.associate_ids.us = "<insert US Associate ID here>"
|
14
|
+
|
15
|
+
lookup = AmazonProductAdvertisingApi::Operations::Item::ItemLookup.new("0201485419")
|
16
|
+
lookup.run
|
17
|
+
|
18
|
+
# Returned XML, tidied
|
19
|
+
# <?xml version="1.0"?>
|
20
|
+
# <ItemLookupResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2005-10-05">
|
21
|
+
# <OperationRequest>
|
22
|
+
# <RequestId>d6c19392-7710-4b05-ad9c-1637755170d8</RequestId>
|
23
|
+
# <Arguments>
|
24
|
+
# <Argument Name="Operation" Value="ItemLookup"/>
|
25
|
+
# <Argument Name="Service" Value="AWSECommerceService"/>
|
26
|
+
# <Argument Name="ItemId" Value="0201485419"/>
|
27
|
+
# <Argument Name="AWSAccessKeyId" Value="<your api key>"/>
|
28
|
+
# </Arguments>
|
29
|
+
# <RequestProcessingTime>0.0376290000000000</RequestProcessingTime>
|
30
|
+
# </OperationRequest>
|
31
|
+
# <Items>
|
32
|
+
# <Request>
|
33
|
+
# <IsValid>True</IsValid>
|
34
|
+
# <ItemLookupRequest>
|
35
|
+
# <Condition>New</Condition>
|
36
|
+
# <DeliveryMethod>Ship</DeliveryMethod>
|
37
|
+
# <IdType>ASIN</IdType>
|
38
|
+
# <MerchantId>Amazon</MerchantId>
|
39
|
+
# <OfferPage>1</OfferPage>
|
40
|
+
# <ItemId>0201485419</ItemId>
|
41
|
+
# <ResponseGroup>Small</ResponseGroup>
|
42
|
+
# <ReviewPage>1</ReviewPage>
|
43
|
+
# </ItemLookupRequest>
|
44
|
+
# </Request>
|
45
|
+
# <Item>
|
46
|
+
# <ASIN>0201485419</ASIN>
|
47
|
+
# <DetailPageURL>http://www.amazon.co.uk/Art-Computer-Programming-Information-Processing/dp/0201485419%3FSubscriptionId%3D<your api key>%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0201485419</DetailPageURL>
|
48
|
+
# <ItemAttributes>git
|
49
|
+
# <Author>Donald E. Knuth</Author>
|
50
|
+
# <Manufacturer>Addison Wesley</Manufacturer>
|
51
|
+
# <ProductGroup>Book</ProductGroup>
|
52
|
+
# <Title>The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science & Information Processing)</Title>
|
53
|
+
# </ItemAttributes>
|
54
|
+
# </Item>
|
55
|
+
# </Items>
|
56
|
+
# </ItemLookupResponse>
|
57
|
+
|
58
|
+
# is_valid is part of the api and says whether the request was valid
|
59
|
+
lookup.is_valid
|
60
|
+
=> true
|
61
|
+
|
62
|
+
lookup.response.items.size
|
63
|
+
=> 1
|
64
|
+
|
65
|
+
lookup.response.items.first
|
66
|
+
=> #<AmazonProductAdvertisingApi::Item:0x139154c @detail_page_url="http://www.amazon.co.uk/Art-Computer-Programming-Information-Processing/dp/0201485419%3FSubscriptionId%3D<your api key>%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0201485419", @item_attributes=#<AmazonProductAdvertisingApi::Container:0x1391448 @manufacturer="Addison Wesley", @product_group="Book", @title="The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science & Information Processing)", @author="Donald E. Knuth">, @asin="0201485419">
|
67
|
+
|
68
|
+
lookup.response.items.first.asin
|
69
|
+
=> "0201485419"
|
70
|
+
|
71
|
+
# ItemAttributes is a container element, doesn't return a string
|
72
|
+
lookup.response.items.first.item_attributes
|
73
|
+
=> #<AmazonProductAdvertisingApi::Container:0x1391448 @manufacturer="Addison Wesley", @product_group="Book", @title="The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science & Information Processing)", @author="Donald E. Knuth">
|
74
|
+
|
75
|
+
lookup.response.items.first.item_attributes.title
|
76
|
+
=> "The Art of Computer Programming: v. 1-3: Vol 1-3 (Series in Computer Science & Information Processing)"
|
77
|
+
|
78
|
+
lookup.response.items.first.item_attributes.author
|
79
|
+
=> "Donald E. Knuth"
|
80
|
+
|
81
|
+
# If you need to debug, you can access all the xml data easily
|
82
|
+
lookup.raw_data
|
83
|
+
=> <XML string, straight from the request body>
|
84
|
+
|
85
|
+
lookup.hpricot_data
|
86
|
+
=> <the xml data, having been fed into Hpricot>
|
87
|
+
|
88
|
+
# And also the request uri
|
89
|
+
lookup.request_uri
|
90
|
+
=> #<URI::HTTP:0x1c7021c URL:http://ecs.amazonaws.co.uk/onca/xml?Service=AWSECommerceService&Operation=ItemLookup&ItemId=0201485419&AWSAccessKeyId=%3Cinsert%20api%20key%20here%3E>
|
91
|
+
|
92
|
+
|
93
|
+
Error handling - invalid request credentials, url form, etc
|
94
|
+
###########################################################
|
95
|
+
AmazonProductAdvertisingApi::Base.access_key_id = "<an invalid id>"
|
96
|
+
lookup = AmazonProductAdvertisingApi::Operations::Item::ItemLookup.new("0201485419")
|
97
|
+
lookup.run
|
98
|
+
=> false
|
99
|
+
|
100
|
+
lookup.is_valid
|
101
|
+
=> false
|
102
|
+
|
103
|
+
lookup.errors
|
104
|
+
=> <an array of error objects>
|
105
|
+
|
106
|
+
lookup.errors.first.code
|
107
|
+
=> AWS.InvalidParameterValue
|
108
|
+
|
109
|
+
lookup.errors.first.message
|
110
|
+
=> <an invalid id> is not a valid value for AWSAccessKeyId. Please change this value and retry your request.
|
111
|
+
|
112
|
+
|
113
|
+
Error handling - invalid request credentials, url form, etc
|
114
|
+
###########################################################
|
115
|
+
AmazonProductAdvertisingApi::Base.access_key_id = "<valid api key>"
|
116
|
+
lookup = AmazonProductAdvertisingApi::Operations::Item::ItemLookup.new("<invalid asin>")
|
117
|
+
lookup.run
|
118
|
+
=> false
|
119
|
+
|
120
|
+
# is_valid returns true as the request was well formed, it just doesn't have any valid data to return
|
121
|
+
lookup.is_valid
|
122
|
+
=> true
|
123
|
+
|
124
|
+
lookup.errors.first.code
|
125
|
+
=> AWS.InvalidParameterValue
|
126
|
+
|
127
|
+
lookup.errors.first.message
|
128
|
+
=> <an invalid asin> is not a valid value for ItemId. Please change this value and retry your request.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Jon Gilbraith, CompletelyNovel Ltd
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
= amazon-product-advertising-api
|
2
|
+
|
3
|
+
== Introduction
|
4
|
+
A nice rubyish interface to the Amazon Product Advertising API, formerly
|
5
|
+
known as the Associates Web Service and before that the Amazon E-Commerce
|
6
|
+
Service.
|
7
|
+
|
8
|
+
The basic design philosophy is the 'Principle of least surprise', so it's:
|
9
|
+
- a dsl that should feel familiar to ruby folk.
|
10
|
+
- structured generally like the API.
|
11
|
+
|
12
|
+
== Prerequisites
|
13
|
+
It's assumed you're reasonably familiar with Ruby as a language, and the
|
14
|
+
typical coding patterns and styles employed by the community.
|
15
|
+
|
16
|
+
It's also assumed that you're familiar with the API. If not go RTFM for a bit,
|
17
|
+
it's all pretty straight forward.
|
18
|
+
|
19
|
+
== Overview
|
20
|
+
As mentioned before, the idea behind this is to model the API so that once
|
21
|
+
you're familiar with this library and the API, you'll find this an easy way to
|
22
|
+
navigate around it. You should then be able to just refer to the lists of
|
23
|
+
Operations, Response Groups and Response Elements in the appendices for all
|
24
|
+
you need to know.
|
25
|
+
|
26
|
+
You initiate requests by instantiating classes representing api Operations and
|
27
|
+
named the same way, i.e. ItemSearch, ItemLookup, etc. The request parameters
|
28
|
+
are represented by attributes on those objects and can be manipulated as you
|
29
|
+
wish. If a parameter is required it'll be required or defaulted on
|
30
|
+
initialization, but everywhere else we defer to the defaults provided by the
|
31
|
+
api.
|
32
|
+
|
33
|
+
Once setup, you call 'run' on the operation which will send the request and
|
34
|
+
return a response object (and after that also available as operation.response).
|
35
|
+
|
36
|
+
The response has a bit less structure than the request operations. There is no
|
37
|
+
representation for Response Groups as beyond one level deep there is no clear
|
38
|
+
pattern for how returned XML is structured (at least not without going down the
|
39
|
+
route trying to define custom handling for each of them). So, instead we group
|
40
|
+
the Operations into broad groups which return the same collections of data.
|
41
|
+
ItemSearch, ItemLookup and SimilarityLookup all return a Items container
|
42
|
+
element with one or more Item tag in it, etc.
|
43
|
+
|
44
|
+
Everything beyond one level deep is done with a recursive sweep instantiating
|
45
|
+
Element objects as we go.
|
46
|
+
|
47
|
+
== Things to know
|
48
|
+
- Amazon uses a lot of Camel case in their XML, but to suit Ruby / Rails
|
49
|
+
conventions attributes, etc are converted to underscore style.
|
50
|
+
- One gotcha - Response Elements can either be a simple value or a container
|
51
|
+
with more elements inside. We recognise this situation by simply comparing
|
52
|
+
the parent and child names (plural parent, singular child) though that isn't
|
53
|
+
always the pattern so we also look for multiple children of the same name.
|
54
|
+
|
55
|
+
That means that some situations might creep through and not work as expected.
|
56
|
+
If the parent and child don't follow the same naming convention and there is
|
57
|
+
also only one child element you'd find the child would be defined as
|
58
|
+
parent.child rather than parent.children.first.
|
59
|
+
|
60
|
+
This is a bug and will have to be addressed (probably by making a list of
|
61
|
+
container elements to check against).
|
62
|
+
|
63
|
+
== Having problems?
|
64
|
+
After running the request uri, returned raw xml and hpricot data will be
|
65
|
+
available in operation.raw_data and operation.hpricot_data which should tell
|
66
|
+
you all you need to know about the response.
|
67
|
+
|
68
|
+
== Test coverage
|
69
|
+
There aren't any tests at the moment but I'll try to add some.
|
70
|
+
|
71
|
+
For the most part there isn't really all that much to test though as it's all
|
72
|
+
largely just making http requests, however the XML parsing is quite convoluted
|
73
|
+
so some tests would be good there to make sure the parsing is following the
|
74
|
+
intended rules.
|
75
|
+
|
76
|
+
|
77
|
+
= TODO
|
78
|
+
- Implement the rest of the Operations (Cart*, Customer*, Help, List*, Seller*,
|
79
|
+
Tag*, Transaction* and Vehicle*).
|
80
|
+
- Implement batch and multiple operation requests, abstracted away from the
|
81
|
+
user within the dsl.
|
82
|
+
- Some sort of internal caching mechanism.
|
83
|
+
- Ability to bind to different IP addresses, in order to circumvent API rate
|
84
|
+
limiting.
|
85
|
+
|
86
|
+
|
87
|
+
= Obtaining
|
88
|
+
The main repository is at github but there is also a rubyforge project.
|
89
|
+
|
90
|
+
- https://github.com/completelynovel/amazon-product-advertising-api/tree
|
91
|
+
- http://amazon-pa-api.rubyforge.org/
|
92
|
+
|
93
|
+
|
94
|
+
= Credits
|
95
|
+
Created by Jon Gilbraith, jon@completelynovel.com, while working with
|
96
|
+
CompletelyNovel.
|
97
|
+
|
98
|
+
Contents of support.rb are basically taken and adapted from Rails'
|
99
|
+
ActiveSupport.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Generate documentation for the amazon-product-advertising-api gem.'
|
6
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
7
|
+
rdoc.rdoc_dir = 'rdoc'
|
8
|
+
rdoc.title = 'AmazonProductAdvertisingApi'
|
9
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
10
|
+
rdoc.rdoc_files.include('README.rdoc')
|
11
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'jeweler'
|
16
|
+
Jeweler::Tasks.new do |gemspec|
|
17
|
+
gemspec.name = "amazon-product-advertising-api"
|
18
|
+
gemspec.summary = "A nice rubyish interface to the Amazon Product Advertising API."
|
19
|
+
gemspec.email = "jon@completelynovel.com"
|
20
|
+
gemspec.homepage = "http://github.com/completelynovel/amazon-product-advertising-api"
|
21
|
+
gemspec.description = "A nice rubyish interface to the Amazon Product Advertising API, formerly known as the Associates Web Service and before that the Amazon E-Commerce Service."
|
22
|
+
gemspec.authors = ["Jon Gilbraith"]
|
23
|
+
gemspec.add_dependency("hpricot")
|
24
|
+
gemspec.add_dependency("ruby-hmac")
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
28
|
+
end
|
29
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.1
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{amazon-product-advertising-api-prezjordan}
|
5
|
+
s.version = "0.2.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Jon Gilbraith", "Jordan Scales"]
|
9
|
+
s.date = %q{2009-07-30}
|
10
|
+
s.description = %q{A nice rubyish interface to the Amazon Product Advertising API (MODIFIED to handle ResponseGroup), formerly known as the Associates Web Service and before that the Amazon E-Commerce Service.}
|
11
|
+
s.email = %q{jon@completelynovel.com}
|
12
|
+
s.extra_rdoc_files = [
|
13
|
+
"README.rdoc"
|
14
|
+
]
|
15
|
+
s.files = [
|
16
|
+
".gitignore",
|
17
|
+
"EXAMPLE.txt",
|
18
|
+
"MIT-LICENSE",
|
19
|
+
"README.rdoc",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION",
|
22
|
+
"amazon-product-advertising-api.gemspec",
|
23
|
+
"lib/amazon_product_advertising_api.rb",
|
24
|
+
"lib/amazon_product_advertising_api/base.rb",
|
25
|
+
"lib/amazon_product_advertising_api/operations/base.rb",
|
26
|
+
"lib/amazon_product_advertising_api/operations/browse_node.rb",
|
27
|
+
"lib/amazon_product_advertising_api/operations/item.rb",
|
28
|
+
"lib/amazon_product_advertising_api/response_elements.rb",
|
29
|
+
"lib/amazon_product_advertising_api/support.rb"
|
30
|
+
]
|
31
|
+
s.has_rdoc = true
|
32
|
+
s.homepage = %q{http://github.com/prezjordan/amazon-product-advertising-api}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.1}
|
36
|
+
s.summary = %q{A nice rubyish interface to the Amazon Product Advertising API. (MODIFIED to handle ResponseGroup)}
|
37
|
+
|
38
|
+
if s.respond_to? :specification_version then
|
39
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
40
|
+
s.specification_version = 2
|
41
|
+
|
42
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
43
|
+
s.add_runtime_dependency(%q<hpricot>, [">= 0"])
|
44
|
+
s.add_runtime_dependency(%q<ruby-hmac>, [">= 0"])
|
45
|
+
else
|
46
|
+
s.add_dependency(%q<hpricot>, [">= 0"])
|
47
|
+
s.add_dependency(%q<ruby-hmac>, [">= 0"])
|
48
|
+
end
|
49
|
+
else
|
50
|
+
s.add_dependency(%q<hpricot>, [">= 0"])
|
51
|
+
s.add_dependency(%q<ruby-hmac>, [">= 0"])
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'cgi'
|
3
|
+
require 'hpricot'
|
4
|
+
require 'time'
|
5
|
+
require 'hmac'
|
6
|
+
require 'hmac-sha2'
|
7
|
+
require 'base64'
|
8
|
+
|
9
|
+
require 'amazon_product_advertising_api/support'
|
10
|
+
|
11
|
+
Class.send(:include, AmazonProductAdvertisingApi::CoreExtensions::Class)
|
12
|
+
String.send(:include, AmazonProductAdvertisingApi::CoreExtensions::String)
|
13
|
+
|
14
|
+
require 'amazon_product_advertising_api/base'
|
15
|
+
require 'amazon_product_advertising_api/response_elements'
|
16
|
+
require 'amazon_product_advertising_api/operations/base'
|
17
|
+
require 'amazon_product_advertising_api/operations/browse_node'
|
18
|
+
require 'amazon_product_advertising_api/operations/item'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AmazonProductAdvertisingApi #:nodoc:
|
2
|
+
|
3
|
+
# This is the main base class where you define your config data. Setup like so:
|
4
|
+
# AmazonProductAdvertisingApi.base.access_key_id = <your Amazon AccessKeyId>
|
5
|
+
# AmazonProductAdvertisingApi.base.secret_access_key = <your Amazon SecretAccessKey>
|
6
|
+
#
|
7
|
+
# You also setup your Associates codes for different regions here (the right one is supplied)
|
8
|
+
# based on what region you are requesting for.
|
9
|
+
# AmazonProductAdvertisingApi.base.associate_ids.ca = <your associates CA Affiliate key>
|
10
|
+
# AmazonProductAdvertisingApi.base.associate_ids.uk = <your associates UK Affiliate key>
|
11
|
+
class Base
|
12
|
+
|
13
|
+
cattr_accessor :access_key_id
|
14
|
+
|
15
|
+
cattr_accessor :secret_access_key
|
16
|
+
|
17
|
+
cattr_accessor :response_group
|
18
|
+
|
19
|
+
cattr_accessor :associate_ids
|
20
|
+
@@associate_ids = Struct.new(:ca, :de, :fr, :jp, :uk, :us).new
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module AmazonProductAdvertisingApi #:nodoc:
|
2
|
+
module Operations #:nodoc:
|
3
|
+
module Base #:nodoc:
|
4
|
+
|
5
|
+
# This is the parent class of any Operations performed.
|
6
|
+
#
|
7
|
+
# Each class should have a constant defined called REQUEST_PARAMETERS which contains the available parameters
|
8
|
+
# as defined in the API docs. Each class should also override the parse method with a custom version to suit the
|
9
|
+
# particular pattern of XML returned for it.
|
10
|
+
#
|
11
|
+
# Subclasses should also override initialize to take any parameters the API defines as required, and follow the pattern
|
12
|
+
# of the region being the last parameter.
|
13
|
+
#
|
14
|
+
# Before doing that though it should call super back to this one.
|
15
|
+
class Request
|
16
|
+
|
17
|
+
# String - Amazon API calls can be sent to any of 6 regions, so this defines which one.
|
18
|
+
# It'll also use this data to pick the right Associates key to use.
|
19
|
+
attr_accessor :region
|
20
|
+
|
21
|
+
# String - The name of the Operation you want to perform, i.e. ItemSearch, ItemLookup, etc.
|
22
|
+
attr_accessor :operation
|
23
|
+
|
24
|
+
# String - This stores the request that gets sent to Amazon (for investigation if you want to look under the covers).
|
25
|
+
attr_accessor :request_uri
|
26
|
+
|
27
|
+
# String - This stores the raw data of the request response (again, for investigation if you want to look under the covers).
|
28
|
+
attr_accessor :raw_data
|
29
|
+
|
30
|
+
# Hpricot - This stores the raw data of the request response should you feel the need / desire to do some parsing yourself.
|
31
|
+
attr_accessor :hpricot_data
|
32
|
+
|
33
|
+
# Element - This is the root of the structure that the lib assembles from the data when parsed.
|
34
|
+
attr_accessor :response
|
35
|
+
|
36
|
+
# Boolean - All responses have a field saying whether the request was valid. Note that this refers to the format of the request, etc
|
37
|
+
# and not errors with the request parameters, etc. I.e. a lookup for an item that doesn't exsist is still valid.
|
38
|
+
attr_accessor :is_valid
|
39
|
+
|
40
|
+
# Array of Struct - Any errors will be added to this attribute and each has an attribute of code or message.
|
41
|
+
attr_accessor :errors
|
42
|
+
|
43
|
+
SERVICE_URLS = {
|
44
|
+
:us => 'http://ecs.amazonaws.com/onca/xml',
|
45
|
+
:uk => 'http://ecs.amazonaws.co.uk/onca/xml',
|
46
|
+
:ca => 'http://ecs.amazonaws.ca/onca/xml',
|
47
|
+
:de => 'http://ecs.amazonaws.de/onca/xml',
|
48
|
+
:jp => 'http://webservices.amazon.co.jp/onca/xml',
|
49
|
+
:fr => 'http://ecs.amazonaws.fr/onca/xml'
|
50
|
+
}
|
51
|
+
|
52
|
+
API_VERSION = "2009-03-31"
|
53
|
+
|
54
|
+
# hacky :(
|
55
|
+
def initialize(arg1, arg2)
|
56
|
+
self.response = AmazonProductAdvertisingApi::Operations::Base::Element.new
|
57
|
+
self.errors = []
|
58
|
+
end
|
59
|
+
|
60
|
+
# This takes care of building request, performing it, storing the results, checking for errors then parsing the data (if the request was valid).
|
61
|
+
def query_amazon(params)
|
62
|
+
request_params = {}
|
63
|
+
request_params["Service"] = "AWSECommerceService"
|
64
|
+
request_params["SignatureVersion"] = 2
|
65
|
+
request_params["SignatureMethod"] = "HmacSHA256"
|
66
|
+
request_params["Timestamp"] = Time.now.gmtime.iso8601
|
67
|
+
request_params["AWSAccessKeyId"] = AmazonProductAdvertisingApi::Base.access_key_id
|
68
|
+
request_params["Operation"] = self.operation
|
69
|
+
request_params["AssociateTag"] = AmazonProductAdvertisingApi::Base.associate_ids.send(self.region) unless AmazonProductAdvertisingApi::Base.associate_ids.send(self.region).nil?
|
70
|
+
request_params["Version"] = API_VERSION
|
71
|
+
request_params["ResponseGroup"] = AmazonProductAdvertisingApi::Base.response_group.try(:join, ',')
|
72
|
+
|
73
|
+
request_params.merge!(params)
|
74
|
+
|
75
|
+
# Process all params - make sure they're all strings, camelize and escape (where appropriate)
|
76
|
+
request_params = request_params.collect { |var, val| [var.to_s.camelize, val.to_s] }
|
77
|
+
request_params = request_params.collect { |var, val| [var, CGI::escape(val).gsub('+', '%20')] }
|
78
|
+
|
79
|
+
# Assemble into a full request string
|
80
|
+
unsigned_uri = URI.parse("#{SERVICE_URLS[self.region]}?#{request_params.sort { |a, b| a[0] <=> b[0] }.collect { |var, val| var + "=" + val }.join("&")}")
|
81
|
+
|
82
|
+
# Generate hmac
|
83
|
+
hmac = HMAC::SHA256.new(AmazonProductAdvertisingApi::Base.secret_access_key)
|
84
|
+
hmac.update("GET\n#{unsigned_uri.host}\n#{unsigned_uri.path}\n#{unsigned_uri.query}")
|
85
|
+
|
86
|
+
self.request_uri = URI.parse("#{unsigned_uri}&Signature=#{CGI::escape(Base64.encode64(hmac.digest).chomp)}")
|
87
|
+
|
88
|
+
result = Net::HTTP::get_response(self.request_uri)
|
89
|
+
raise("Error connecting to Amazon - #{result.to_s}") if !result.kind_of?(Net::HTTPSuccess)
|
90
|
+
|
91
|
+
# Store away the raw data for debugging or if more direct access is required
|
92
|
+
self.raw_data = result.body
|
93
|
+
self.hpricot_data = Hpricot.XML(self.raw_data)
|
94
|
+
|
95
|
+
# Now parse the xml and build out the reponse elements
|
96
|
+
self.is_valid = self.hpricot_data.at(:IsValid).inner_html == "True"
|
97
|
+
|
98
|
+
self.parse
|
99
|
+
|
100
|
+
# is_valid only refers to the request, so we could still have errors - check and parse if present
|
101
|
+
if !self.hpricot_data.at(:Errors).nil?
|
102
|
+
self.hpricot_data.at(:Errors).search(:Error).each do |error|
|
103
|
+
self.errors << Struct.new(:code, :message).new(error.at(:Code).inner_html, error.at(:Message).inner_html)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return false if it's not a valid request, otherwise return the response
|
108
|
+
self.is_valid ? self.response : false
|
109
|
+
end
|
110
|
+
|
111
|
+
# The parse method of a request should be overwritted by any subclasses to account for different patterns in the XML.
|
112
|
+
def parse
|
113
|
+
raise "This should be being overridden by it's subclass to provide custom parsing for the particular operation concerned."
|
114
|
+
end
|
115
|
+
|
116
|
+
# Launches the request's query to Amazon (via query_amazon).
|
117
|
+
def run
|
118
|
+
self.query_amazon(params)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
# When passed an hpricot element it returns true or false based on whether this item is thought to be inside a Container Element.
|
123
|
+
# It does this in the rather crude way of seeing if the parent's name is the pluralized form of it's own, or it is one of
|
124
|
+
# several with the same name. This isn't 100% fool proof so I think at some point using a definitive list of the container elements
|
125
|
+
# would be a better way to go.
|
126
|
+
#
|
127
|
+
# The pluralisation could also do with something a bit more sophisticated.
|
128
|
+
def parent_a_container?(hpricot_element)
|
129
|
+
hpricot_element.parent.name == hpricot_element.name + "s" || hpricot_element.parent.search("> #{hpricot_element.name}").size > 1
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
# XML data that is returned by Amazon gets built into a tree of nodes, which are made up of instances of this class.
|
135
|
+
# They represent the 'response element' entity within the API docs.
|
136
|
+
#
|
137
|
+
# As well as various having attributes it can also contain a collection and behave like an array.
|
138
|
+
class Element < Array
|
139
|
+
|
140
|
+
attr_reader :attributes
|
141
|
+
|
142
|
+
def initialize
|
143
|
+
@attributes = {}
|
144
|
+
end
|
145
|
+
|
146
|
+
# Defines a new accessor on the element and if supplied assigns that attribute a value.
|
147
|
+
def add_element(name, value = nil)
|
148
|
+
name = name.underscore
|
149
|
+
|
150
|
+
@attributes[name.to_s] = value
|
151
|
+
|
152
|
+
self.instance_eval %{
|
153
|
+
def self.#{name}
|
154
|
+
@#{name}
|
155
|
+
end
|
156
|
+
def self.#{name}=(value)
|
157
|
+
@#{name} ||= value
|
158
|
+
end
|
159
|
+
}
|
160
|
+
|
161
|
+
if !value.nil?
|
162
|
+
value = value.to_s if value.is_a?(Symbol)
|
163
|
+
self.send("#{name}=", value)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Return the element
|
167
|
+
self.instance_eval("self.#{name}")
|
168
|
+
end
|
169
|
+
|
170
|
+
def method_missing(method, *args)
|
171
|
+
return super unless RESPONSE_ELEMENTS.include?(method.to_sym)
|
172
|
+
|
173
|
+
if CONTAINER_RESPONSE_ELEMENTS.include?(method.to_sym)
|
174
|
+
self.class.new
|
175
|
+
else
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|