asin 0.4.0 → 0.5.0

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.
@@ -1,4 +1,10 @@
1
- == 0.4.0.beta2
1
+ == 0.5.0
2
+
3
+ * move client to own file
4
+ * use autoload
5
+ * support for Hashie::Rash
6
+
7
+ == 0.4.0
2
8
 
3
9
  * add configuration option for item/cart class
4
10
  * add more functionality to item class
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- asin (0.4.0)
4
+ asin (0.5.0)
5
5
  crack (~> 0.1.8)
6
6
  hashie (~> 1.0.0)
7
7
  httpi (~> 0.9.2)
@@ -9,29 +9,31 @@ PATH
9
9
  GEM
10
10
  remote: http://rubygems.org/
11
11
  specs:
12
- chalofa_ruby-progressbar (0.0.9.1)
13
12
  crack (0.1.8)
14
13
  diff-lcs (1.1.2)
15
- fuubar (0.0.4)
16
- chalofa_ruby-progressbar (~> 0.0.9)
14
+ fuubar (0.0.5)
17
15
  rspec (~> 2.0)
18
16
  rspec-instafail (~> 0.1.4)
17
+ ruby-progressbar (~> 0.0.10)
19
18
  hashie (1.0.0)
20
19
  httpclient (2.2.0.2)
21
20
  httpi (0.9.4)
22
21
  pyu-ntlm-http (>= 0.1.3.1)
23
22
  rack
24
23
  pyu-ntlm-http (0.1.3.1)
25
- rack (1.2.2)
24
+ rack (1.3.0)
25
+ rash (0.3.0)
26
+ hashie (~> 1.0.0)
26
27
  rspec (2.6.0)
27
28
  rspec-core (~> 2.6.0)
28
29
  rspec-expectations (~> 2.6.0)
29
30
  rspec-mocks (~> 2.6.0)
30
- rspec-core (2.6.1)
31
+ rspec-core (2.6.4)
31
32
  rspec-expectations (2.6.0)
32
33
  diff-lcs (~> 1.1.2)
33
- rspec-instafail (0.1.7)
34
+ rspec-instafail (0.1.8)
34
35
  rspec-mocks (2.6.0)
36
+ ruby-progressbar (0.0.10)
35
37
 
36
38
  PLATFORMS
37
39
  ruby
@@ -40,4 +42,5 @@ DEPENDENCIES
40
42
  asin!
41
43
  fuubar (~> 0.0.4)
42
44
  httpclient (~> 2.2.0.2)
45
+ rash (~> 0.3.0)
43
46
  rspec (~> 2.6.0)
@@ -39,13 +39,13 @@ ASIN is designed as a module, so you can include it into any object you like:
39
39
  # lookup an ASIN
40
40
  lookup '1430218150'
41
41
 
42
- But you can also use the +client+ method to get a client-object:
42
+ But you can also use the +instance+ method to get a proxy-object:
43
43
 
44
44
  # just require
45
45
  require 'asin'
46
46
 
47
47
  # create an ASIN client
48
- client = ASIN.client
48
+ client = ASIN::Client.instance
49
49
 
50
50
  # lookup an item with the amazon standard identification number (asin)
51
51
  item = client.lookup '1430218150'
@@ -74,7 +74,7 @@ There is an additional set of methods to support AWS cart operations:
74
74
  require 'asin'
75
75
 
76
76
  # create an ASIN client
77
- client = ASIN.client
77
+ client = ASIN::Client.instance
78
78
 
79
79
  # create a cart with an item
80
80
  cart = client.create_cart({:asin => '1430218150', :quantity => 1})
@@ -101,6 +101,20 @@ There is an additional set of methods to support AWS cart operations:
101
101
  cart.saved_items
102
102
  => [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
103
103
 
104
+ == Item / Cart Response
105
+
106
+ ASIN is customizable in the way it returns Responses from Amazon.
107
+ By default it will return +SimpleItem+ or +SimpleCart+ instances,
108
+ but you can override this behavior for using your custom Classes:
109
+
110
+ client.configure :item_type => YourItemClass
111
+ client.configure :cart_type => YourCartClass
112
+
113
+ You can also use built-in +:raw+, +:mash+ or +:rash+ types.
114
+ Since +rash+ is an additional library, you need to add it to your gemfile if you want to use it:
115
+
116
+ gem 'rash'
117
+
104
118
  == HTTPI
105
119
 
106
120
  ASIN uses HTTPI[https://github.com/rubiii/httpi] as a HTTP-Client adapter.
@@ -113,4 +127,4 @@ As a default HTTPI uses _httpclient_ so you should add that dependency to your p
113
127
 
114
128
  Have a look at the RDOC[http://rdoc.info/projects/phoet/asin] for this project, if you want further information.
115
129
 
116
- For more information on the REST calls, have a look at the whole Amazon E-Commerce-API[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/].
130
+ For more information on the REST calls, have a look at the whole Amazon E-Commerce-API[http://docs.amazonwebservices.com/AWSECommerceService/2010-11-01/DG/].
@@ -24,6 +24,7 @@ Gem::Specification.new do |s|
24
24
  s.add_dependency('httpi', '~> 0.9.2')
25
25
 
26
26
  s.add_development_dependency('httpclient', '~> 2.2.0.2')
27
+ s.add_development_dependency('rash', '~> 0.3.0')
27
28
  s.add_development_dependency('rspec', '~> 2.6.0')
28
29
  s.add_development_dependency('fuubar', '~> 0.0.4')
29
30
  end
@@ -1,332 +1,7 @@
1
- # -*- coding: utf-8 -*-
2
- require 'httpi'
3
- require 'crack/xml'
4
- require 'cgi'
5
- require 'base64'
6
- require 'logger'
7
-
8
- require 'asin/item'
9
- require 'asin/cart'
10
- require 'asin/version'
11
- require 'asin/configuration'
12
-
13
- # ASIN (Amazon Simple INterface) is a gem for easy access of the Amazon E-Commerce-API.
14
- # It is simple to configure and use. Since it's very small and flexible, it is easy to extend it to your needs.
15
- #
16
- # Author:: Peter Schröder (mailto:phoetmail@googlemail.com)
17
- #
18
- # == Usage
19
- #
20
- # The ASIN module is designed as a mixin.
21
- #
22
- # require 'asin'
23
- # include ASIN
24
- #
25
- # In order to use the Amazon API properly, you need to be a registered user (http://aws.amazon.com).
26
- #
27
- # The registration process will give you a +secret-key+ and an +access-key+ (AWSAccessKeyId).
28
- #
29
- # Both are needed to use ASIN (see Configuration for more details):
30
- #
31
- # configure :secret => 'your-secret', :key => 'your-key'
32
- #
33
- # == Search
34
- #
35
- # After configuring your environment you can call the +lookup+ method to retrieve an +SimpleItem+ via the
36
- # Amazon Standard Identification Number (ASIN):
37
- #
38
- # item = lookup '1430218150'
39
- # item.title
40
- # => "Learn Objective-C on the Mac (Learn Series)"
41
- #
42
- # OR search with fulltext/ASIN/ISBN
43
- #
44
- # items = search 'Learn Objective-C'
45
- # items.first.title
46
- # => "Learn Objective-C on the Mac (Learn Series)"
47
- #
48
- # The +SimpleItem+ uses a Hashie::Mash as its internal data representation and you can get fetched data from it:
49
- #
50
- # item.raw.ItemAttributes.ListPrice.FormattedPrice
51
- # => "$39.99"
52
- #
53
- # == Further Configuration
54
- #
55
- # If you need more controll over the request that is sent to the
56
- # Amazon API (http://docs.amazonwebservices.com/AWSEcommerceService/4-0/),
57
- # you can override some defaults or add additional query-parameters to the REST calls:
58
- #
59
- # configure :host => 'webservices.amazon.de'
60
- # lookup(asin, :ResponseGroup => :Medium)
61
- #
62
- # == Cart
63
- #
64
- # ASIN helps with AWS cart-operations.
65
- # It currently supports the CartCreate, CartGet, CartAdd, CartModify and CartClear operations:
66
- #
67
- # cart = create_cart({:asin => '1430218150', :quantity => 1})
68
- # cart.valid?
69
- # cart.items
70
- # => true
71
- # => [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
72
- #
73
- # cart = get_cart('176-9182855-2326919', 'KgeVCA0YJTbuN/7Ibakrk/KnHWA=')
74
- # cart.empty?
75
- # => false
76
- #
77
- # cart = clear_cart(cart)
78
- # cart.empty?
79
- # => true
80
- #
81
- # cart = add_items(cart, {:asin => '1430216263', :quantity => 2})
82
- # cart.empty?
83
- # => false
84
- #
85
- # cart = update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :action => :SaveForLater}, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
86
- # cart.valid?
87
- # cart.saved_items
88
- # => true
89
- # => [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
90
- #
91
1
  module ASIN
92
-
93
- DIGEST = OpenSSL::Digest::Digest.new('sha256')
94
- PATH = '/onca/xml'
95
-
96
- # Convenience method to create an ASIN client.
97
- #
98
- # A client is not necessary though, you can simply include the ASIN module otherwise.
99
- #
100
- def self.client
101
- client = Object.new
102
- client.extend ASIN
103
- client
104
- end
105
-
106
- # Configures the basic request parameters for ASIN.
107
- #
108
- # Expects at least +secret+ and +key+ for the API call:
109
- #
110
- # configure :secret => 'your-secret', :key => 'your-key'
111
- #
112
- # See ASIN::Configuration for more infos.
113
- #
114
- def configure(options={})
115
- Configuration.configure(options)
116
- end
117
-
118
- # Performs an +ItemLookup+ REST call against the Amazon API.
119
- #
120
- # Expects an ASIN (Amazon Standard Identification Number) and returns an +SimpleItem+:
121
- #
122
- # item = lookup '1430218150'
123
- # item.title
124
- # => "Learn Objective-C on the Mac (Learn Series)"
125
- #
126
- # ==== Options:
127
- #
128
- # Additional parameters for the API call like this:
129
- #
130
- # lookup(asin, :ResponseGroup => :Medium)
131
- #
132
- def lookup(asin, params={:ResponseGroup => :Medium})
133
- response = call(params.merge(:Operation => :ItemLookup, :ItemId => asin))
134
- handle_item(response['ItemLookupResponse']['Items']['Item'])
135
- end
136
-
137
- # Performs an +ItemSearch+ REST call against the Amazon API.
138
- #
139
- # Expects a search-string which can be an arbitrary array of strings (ASINs f.e.) and returns a list of +SimpleItem+s:
140
- #
141
- # items = search_keywords 'Learn', 'Objective-C'
142
- # items.first.title
143
- # => "Learn Objective-C on the Mac (Learn Series)"
144
- #
145
- # ==== Options:
146
- #
147
- # Additional parameters for the API call like this:
148
- #
149
- # search_keywords('nirvana', 'never mind', :SearchIndex => :Music)
150
- #
151
- # Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
152
- #
153
- def search_keywords(*keywords)
154
- params = keywords.last.is_a?(Hash) ? keywords.pop : {:SearchIndex => :Books, :ResponseGroup => :Medium}
155
- response = call(params.merge(:Operation => :ItemSearch, :Keywords => keywords.join(' ')))
156
- (response['ItemSearchResponse']['Items']['Item'] || []).map {|item| handle_item(item)}
157
- end
158
-
159
- # Performs an +ItemSearch+ REST call against the Amazon API.
160
- #
161
- # Expects a Hash of search params where and returns a list of +SimpleItem+s:
162
- #
163
- # items = search :SearchIndex => :Music
164
- #
165
- # ==== Options:
166
- #
167
- # Additional parameters for the API call like this:
168
- #
169
- # search(:Keywords => 'nirvana', :SearchIndex => :Music)
170
- #
171
- # Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
172
- #
173
- def search(params={:SearchIndex => :Books, :ResponseGroup => :Medium})
174
- response = call(params.merge(:Operation => :ItemSearch))
175
- (response['ItemSearchResponse']['Items']['Item'] || []).map {|item| handle_item(item)}
176
- end
177
-
178
- # Performs an +CartCreate+ REST call against the Amazon API.
179
- #
180
- # Expects one ore more item-hashes and returns a +SimpleCart+:
181
- #
182
- # cart = create_cart({:asin => '1430218150', :quantity => 1})
183
- #
184
- # ==== Options:
185
- #
186
- # Additional parameters for the API call like this:
187
- #
188
- # create_cart({:asin => '1430218150', :quantity => 1}, {:asin => '1430216263', :quantity => 1, :action => :SaveForLater})
189
- #
190
- # Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
191
- #
192
- def create_cart(*items)
193
- cart(:CartCreate, create_item_params(items))
194
- end
195
-
196
- # Performs an +CartGet+ REST call against the Amazon API.
197
- #
198
- # Expects the CartId and the HMAC to identify the returning +SimpleCart+:
199
- #
200
- # cart = get_cart('176-9182855-2326919', 'KgeVCA0YJTbuN/7Ibakrk/KnHWA=')
201
- #
202
- def get_cart(cart_id, hmac)
203
- cart(:CartGet, {:CartId => cart_id, :HMAC => hmac})
204
- end
205
-
206
- # Performs an +CartAdd+ REST call against the Amazon API.
207
- #
208
- # Expects a +SimpleCart+ created with +create_cart+ and one ore more Item-Hashes and returns an updated +SimpleCart+:
209
- #
210
- # cart = add_items(cart, {:asin => '1430216263', :quantity => 2})
211
- #
212
- # ==== Options:
213
- #
214
- # Additional parameters for the API call like this:
215
- #
216
- # add_items(cart, {:asin => '1430218150', :quantity => 1}, {:asin => '1430216263', :quantity => 1, :action => :SaveForLater})
217
- #
218
- # Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
219
- #
220
- def add_items(cart, *items)
221
- cart(:CartAdd, create_item_params(items).merge({:CartId => cart.cart_id, :HMAC => cart.hmac}))
222
- end
223
-
224
- # Performs an +CartModify+ REST call against the Amazon API.
225
- #
226
- # Expects a +SimpleCart+ created with +create_cart+ and one ore more Item-Hashes to modify and returns an updated +SimpleCart+:
227
- #
228
- # cart = update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
229
- #
230
- # ==== Options:
231
- #
232
- # Additional parameters for the API call like this:
233
- #
234
- # update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :action => :SaveForLater}, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
235
- #
236
- # Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
237
- #
238
- def update_items(cart, *items)
239
- cart(:CartModify, create_item_params(items).merge({:CartId => cart.cart_id, :HMAC => cart.hmac}))
240
- end
241
-
242
- # Performs an +CartClear+ REST call against the Amazon API.
243
- #
244
- # Expects a +SimpleCart+ created with +create_cart+ and returns an empty +SimpleCart+:
245
- #
246
- # cart = clear_cart(cart)
247
- #
248
- def clear_cart(cart)
249
- cart(:CartClear, {:CartId => cart.cart_id, :HMAC => cart.hmac})
250
- end
251
-
252
- private
253
-
254
- def handle_item(item)
255
- Configuration.item_type.is_a?(Class) ? Configuration.item_type.new(item) : item
256
- end
257
-
258
- def create_item_params(items)
259
- keyword_mappings = {
260
- :asin => 'ASIN',
261
- :quantity => 'Quantity',
262
- :cart_item_id => 'CartItemId',
263
- :offer_listing_id => 'OfferListingId',
264
- :action => 'Action'
265
- }
266
- params = {}
267
- items.each_with_index do |item, i|
268
- item.each do |key, value|
269
- next unless keyword = keyword_mappings[key]
270
- params["Item.#{i}.#{keyword}"] = value.to_s
271
- end
272
- end
273
- params
274
- end
275
-
276
- def cart(operation, params={})
277
- response = call(params.merge(:Operation => operation))
278
- cart = response["#{operation}Response"]['Cart']
279
- Configuration.cart_type.is_a?(Class) ? Configuration.cart_type.new(cart) : cart
280
- end
281
-
282
-
283
- def credentials_valid?
284
- Configuration.secret && Configuration.key
285
- end
286
-
287
- def call(params)
288
- raise "you have to configure ASIN: 'configure :secret => 'your-secret', :key => 'your-key''" unless credentials_valid?
289
-
290
- log(:debug, "calling with params=#{params}")
291
- signed = create_signed_query_string(params)
292
-
293
- url = "http://#{Configuration.host}#{PATH}?#{signed}"
294
- log(:info, "performing rest call to url='#{url}'")
295
-
296
- response = HTTPI.get(url)
297
- if response.code == 200
298
- # force utf-8 chars, works only on 1.9 string
299
- resp = response.body
300
- resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
301
- log(:debug, "got response='#{resp}'")
302
- Crack::XML.parse(resp)
303
- else
304
- log(:error, "got response='#{response.body}'")
305
- raise "request failed with response-code='#{response.code}'"
306
- end
307
- end
308
-
309
- def create_signed_query_string(params)
310
- # nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
311
- params[:Service] = :AWSECommerceService
312
- params[:AWSAccessKeyId] = Configuration.key
313
- # utc timestamp needed for signing
314
- params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
315
-
316
- # signing needs to order the query alphabetically
317
- query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
318
-
319
- # yeah, you really need to sign the get-request not the query
320
- request_to_sign = "GET\n#{Configuration.host}\n#{PATH}\n#{query}"
321
- hmac = OpenSSL::HMAC.digest(DIGEST, Configuration.secret, request_to_sign)
322
-
323
- # don't forget to remove the newline from base64
324
- signature = CGI.escape(Base64.encode64(hmac).chomp)
325
- "#{query}&Signature=#{signature}"
326
- end
327
-
328
- def log(severity, message)
329
- Configuration.logger.send severity, message if Configuration.logger
330
- end
331
-
332
2
  end
3
+ ASIN.autoload :Client, 'asin/client'
4
+ ASIN.autoload :SimpleItem, 'asin/simple_item'
5
+ ASIN.autoload :SimpleCart, 'asin/simple_cart'
6
+ ASIN.autoload :Version, 'asin/version'
7
+ ASIN.autoload :Configuration, 'asin/configuration'
@@ -0,0 +1,341 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'httpi'
3
+ require 'crack/xml'
4
+ require 'cgi'
5
+ require 'base64'
6
+
7
+ # ASIN (Amazon Simple INterface) is a gem for easy access of the Amazon E-Commerce-API.
8
+ # It is simple to configure and use. Since it's very small and flexible, it is easy to extend it to your needs.
9
+ #
10
+ # Author:: Peter Schröder (mailto:phoetmail@googlemail.com)
11
+ #
12
+ # == Usage
13
+ #
14
+ # The ASIN module is designed as a mixin.
15
+ #
16
+ # require 'asin'
17
+ # include ASIN::Client
18
+ #
19
+ # In order to use the Amazon API properly, you need to be a registered user (http://aws.amazon.com).
20
+ #
21
+ # The registration process will give you a +secret-key+ and an +access-key+ (AWSAccessKeyId).
22
+ #
23
+ # Both are needed to use ASIN (see Configuration for more details):
24
+ #
25
+ # configure :secret => 'your-secret', :key => 'your-key'
26
+ #
27
+ # == Search
28
+ #
29
+ # After configuring your environment you can call the +lookup+ method to retrieve an +SimpleItem+ via the
30
+ # Amazon Standard Identification Number (ASIN):
31
+ #
32
+ # item = lookup '1430218150'
33
+ # item.title
34
+ # => "Learn Objective-C on the Mac (Learn Series)"
35
+ #
36
+ # OR search with fulltext/ASIN/ISBN
37
+ #
38
+ # items = search 'Learn Objective-C'
39
+ # items.first.title
40
+ # => "Learn Objective-C on the Mac (Learn Series)"
41
+ #
42
+ # The +SimpleItem+ uses a Hashie::Mash as its internal data representation and you can get fetched data from it:
43
+ #
44
+ # item.raw.ItemAttributes.ListPrice.FormattedPrice
45
+ # => "$39.99"
46
+ #
47
+ # == Further Configuration
48
+ #
49
+ # If you need more controll over the request that is sent to the
50
+ # Amazon API (http://docs.amazonwebservices.com/AWSEcommerceService/4-0/),
51
+ # you can override some defaults or add additional query-parameters to the REST calls:
52
+ #
53
+ # configure :host => 'webservices.amazon.de'
54
+ # lookup(asin, :ResponseGroup => :Medium)
55
+ #
56
+ # == Cart
57
+ #
58
+ # ASIN helps with AWS cart-operations.
59
+ # It currently supports the CartCreate, CartGet, CartAdd, CartModify and CartClear operations:
60
+ #
61
+ # cart = create_cart({:asin => '1430218150', :quantity => 1})
62
+ # cart.valid?
63
+ # cart.items
64
+ # => true
65
+ # => [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
66
+ #
67
+ # cart = get_cart('176-9182855-2326919', 'KgeVCA0YJTbuN/7Ibakrk/KnHWA=')
68
+ # cart.empty?
69
+ # => false
70
+ #
71
+ # cart = clear_cart(cart)
72
+ # cart.empty?
73
+ # => true
74
+ #
75
+ # cart = add_items(cart, {:asin => '1430216263', :quantity => 2})
76
+ # cart.empty?
77
+ # => false
78
+ #
79
+ # cart = update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :action => :SaveForLater}, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
80
+ # cart.valid?
81
+ # cart.saved_items
82
+ # => true
83
+ # => [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
84
+ #
85
+ module ASIN
86
+ module Client
87
+
88
+ DIGEST = OpenSSL::Digest::Digest.new('sha256')
89
+ PATH = '/onca/xml'
90
+
91
+ # Convenience method to create an ASIN client.
92
+ #
93
+ # An instance is not necessary though, you can simply include the ASIN module otherwise.
94
+ #
95
+ def self.instance
96
+ ins = Object.new
97
+ ins.extend ASIN::Client
98
+ ins
99
+ end
100
+
101
+ # Configures the basic request parameters for ASIN.
102
+ #
103
+ # Expects at least +secret+ and +key+ for the API call:
104
+ #
105
+ # configure :secret => 'your-secret', :key => 'your-key'
106
+ #
107
+ # See ASIN::Configuration for more infos.
108
+ #
109
+ def configure(options={})
110
+ Configuration.configure(options)
111
+ end
112
+
113
+ # Performs an +ItemLookup+ REST call against the Amazon API.
114
+ #
115
+ # Expects an ASIN (Amazon Standard Identification Number) and returns an +SimpleItem+:
116
+ #
117
+ # item = lookup '1430218150'
118
+ # item.title
119
+ # => "Learn Objective-C on the Mac (Learn Series)"
120
+ #
121
+ # ==== Options:
122
+ #
123
+ # Additional parameters for the API call like this:
124
+ #
125
+ # lookup(asin, :ResponseGroup => :Medium)
126
+ #
127
+ def lookup(asin, params={:ResponseGroup => :Medium})
128
+ response = call(params.merge(:Operation => :ItemLookup, :ItemId => asin))
129
+ handle_item(response['ItemLookupResponse']['Items']['Item'])
130
+ end
131
+
132
+ # Performs an +ItemSearch+ REST call against the Amazon API.
133
+ #
134
+ # Expects a search-string which can be an arbitrary array of strings (ASINs f.e.) and returns a list of +SimpleItem+s:
135
+ #
136
+ # items = search_keywords 'Learn', 'Objective-C'
137
+ # items.first.title
138
+ # => "Learn Objective-C on the Mac (Learn Series)"
139
+ #
140
+ # ==== Options:
141
+ #
142
+ # Additional parameters for the API call like this:
143
+ #
144
+ # search_keywords('nirvana', 'never mind', :SearchIndex => :Music)
145
+ #
146
+ # Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
147
+ #
148
+ def search_keywords(*keywords)
149
+ params = keywords.last.is_a?(Hash) ? keywords.pop : {:SearchIndex => :Books, :ResponseGroup => :Medium}
150
+ response = call(params.merge(:Operation => :ItemSearch, :Keywords => keywords.join(' ')))
151
+ (response['ItemSearchResponse']['Items']['Item'] || []).map {|item| handle_item(item)}
152
+ end
153
+
154
+ # Performs an +ItemSearch+ REST call against the Amazon API.
155
+ #
156
+ # Expects a Hash of search params where and returns a list of +SimpleItem+s:
157
+ #
158
+ # items = search :SearchIndex => :Music
159
+ #
160
+ # ==== Options:
161
+ #
162
+ # Additional parameters for the API call like this:
163
+ #
164
+ # search(:Keywords => 'nirvana', :SearchIndex => :Music)
165
+ #
166
+ # Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
167
+ #
168
+ def search(params={:SearchIndex => :Books, :ResponseGroup => :Medium})
169
+ response = call(params.merge(:Operation => :ItemSearch))
170
+ (response['ItemSearchResponse']['Items']['Item'] || []).map {|item| handle_item(item)}
171
+ end
172
+
173
+ # Performs an +CartCreate+ REST call against the Amazon API.
174
+ #
175
+ # Expects one ore more item-hashes and returns a +SimpleCart+:
176
+ #
177
+ # cart = create_cart({:asin => '1430218150', :quantity => 1})
178
+ #
179
+ # ==== Options:
180
+ #
181
+ # Additional parameters for the API call like this:
182
+ #
183
+ # create_cart({:asin => '1430218150', :quantity => 1}, {:asin => '1430216263', :quantity => 1, :action => :SaveForLater})
184
+ #
185
+ # Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
186
+ #
187
+ def create_cart(*items)
188
+ cart(:CartCreate, create_item_params(items))
189
+ end
190
+
191
+ # Performs an +CartGet+ REST call against the Amazon API.
192
+ #
193
+ # Expects the CartId and the HMAC to identify the returning +SimpleCart+:
194
+ #
195
+ # cart = get_cart('176-9182855-2326919', 'KgeVCA0YJTbuN/7Ibakrk/KnHWA=')
196
+ #
197
+ def get_cart(cart_id, hmac)
198
+ cart(:CartGet, {:CartId => cart_id, :HMAC => hmac})
199
+ end
200
+
201
+ # Performs an +CartAdd+ REST call against the Amazon API.
202
+ #
203
+ # Expects a +SimpleCart+ created with +create_cart+ and one ore more Item-Hashes and returns an updated +SimpleCart+:
204
+ #
205
+ # cart = add_items(cart, {:asin => '1430216263', :quantity => 2})
206
+ #
207
+ # ==== Options:
208
+ #
209
+ # Additional parameters for the API call like this:
210
+ #
211
+ # add_items(cart, {:asin => '1430218150', :quantity => 1}, {:asin => '1430216263', :quantity => 1, :action => :SaveForLater})
212
+ #
213
+ # Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
214
+ #
215
+ def add_items(cart, *items)
216
+ cart(:CartAdd, create_item_params(items).merge({:CartId => cart.cart_id, :HMAC => cart.hmac}))
217
+ end
218
+
219
+ # Performs an +CartModify+ REST call against the Amazon API.
220
+ #
221
+ # Expects a +SimpleCart+ created with +create_cart+ and one ore more Item-Hashes to modify and returns an updated +SimpleCart+:
222
+ #
223
+ # cart = update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
224
+ #
225
+ # ==== Options:
226
+ #
227
+ # Additional parameters for the API call like this:
228
+ #
229
+ # update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :action => :SaveForLater}, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
230
+ #
231
+ # Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
232
+ #
233
+ def update_items(cart, *items)
234
+ cart(:CartModify, create_item_params(items).merge({:CartId => cart.cart_id, :HMAC => cart.hmac}))
235
+ end
236
+
237
+ # Performs an +CartClear+ REST call against the Amazon API.
238
+ #
239
+ # Expects a +SimpleCart+ created with +create_cart+ and returns an empty +SimpleCart+:
240
+ #
241
+ # cart = clear_cart(cart)
242
+ #
243
+ def clear_cart(cart)
244
+ cart(:CartClear, {:CartId => cart.cart_id, :HMAC => cart.hmac})
245
+ end
246
+
247
+ private()
248
+
249
+ def handle_item(item)
250
+ handle_type(item, Configuration.item_type)
251
+ end
252
+
253
+ def handle_type(data, type)
254
+ if type.is_a?(Class)
255
+ type.new(data)
256
+ elsif type == :mash
257
+ require 'hashie'
258
+ Hashie::Mash.new(data)
259
+ elsif type == :rash
260
+ require 'rash'
261
+ Hashie::Rash.new(data)
262
+ else
263
+ data
264
+ end
265
+ end
266
+
267
+ def create_item_params(items)
268
+ keyword_mappings = {
269
+ :asin => 'ASIN',
270
+ :quantity => 'Quantity',
271
+ :cart_item_id => 'CartItemId',
272
+ :offer_listing_id => 'OfferListingId',
273
+ :action => 'Action'
274
+ }
275
+ params = {}
276
+ items.each_with_index do |item, i|
277
+ item.each do |key, value|
278
+ next unless keyword = keyword_mappings[key]
279
+ params["Item.#{i}.#{keyword}"] = value.to_s
280
+ end
281
+ end
282
+ params
283
+ end
284
+
285
+ def cart(operation, params={})
286
+ response = call(params.merge(:Operation => operation))
287
+ cart = response["#{operation}Response"]['Cart']
288
+ handle_type(cart, Configuration.cart_type)
289
+ end
290
+
291
+
292
+ def credentials_valid?
293
+ Configuration.secret && Configuration.key
294
+ end
295
+
296
+ def call(params)
297
+ raise "you have to configure ASIN: 'configure :secret => 'your-secret', :key => 'your-key''" unless credentials_valid?
298
+
299
+ log(:debug, "calling with params=#{params}")
300
+ signed = create_signed_query_string(params)
301
+
302
+ url = "http://#{Configuration.host}#{PATH}?#{signed}"
303
+ log(:info, "performing rest call to url='#{url}'")
304
+
305
+ response = HTTPI.get(url)
306
+ if response.code == 200
307
+ # force utf-8 chars, works only on 1.9 string
308
+ resp = response.body
309
+ resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
310
+ log(:debug, "got response='#{resp}'")
311
+ Crack::XML.parse(resp)
312
+ else
313
+ log(:error, "got response='#{response.body}'")
314
+ raise "request failed with response-code='#{response.code}'"
315
+ end
316
+ end
317
+
318
+ def create_signed_query_string(params)
319
+ # nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
320
+ params[:Service] = :AWSECommerceService
321
+ params[:AWSAccessKeyId] = Configuration.key
322
+ # utc timestamp needed for signing
323
+ params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
324
+
325
+ # signing needs to order the query alphabetically
326
+ query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
327
+
328
+ # yeah, you really need to sign the get-request not the query
329
+ request_to_sign = "GET\n#{Configuration.host}\n#{PATH}\n#{query}"
330
+ hmac = OpenSSL::HMAC.digest(DIGEST, Configuration.secret, request_to_sign)
331
+
332
+ # don't forget to remove the newline from base64
333
+ signature = CGI.escape(Base64.encode64(hmac).chomp)
334
+ "#{query}&Signature=#{signature}"
335
+ end
336
+
337
+ def log(severity, message)
338
+ Configuration.logger.send severity, message if Configuration.logger
339
+ end
340
+ end
341
+ end
@@ -1,4 +1,5 @@
1
1
  require "yaml"
2
+ require 'logger'
2
3
 
3
4
  module ASIN
4
5
  class Configuration
@@ -34,8 +35,8 @@ module ASIN
34
35
  # [key] the API access key
35
36
  # [host] the host, which defaults to 'webservices.amazon.com'
36
37
  # [logger] a different logger than logging to STDERR (nil for no logging)
37
- # [item_type] a different class for SimpleItem, use :raw for a plain mash
38
- # [cart_type] a different class for SimpleCart, use :raw for a plain mash
38
+ # [item_type] a different class for SimpleItem, use :mash / :rash for Hashie::Mash / Hashie::Rash or :raw for a plain hash
39
+ # [cart_type] a different class for SimpleCart, use :mash / :rash for Hashie::Mash / Hashie::Rash or :raw for a plain hash
39
40
  #
40
41
  def configure(options={})
41
42
  init_config
File without changes
File without changes
@@ -1,3 +1,3 @@
1
1
  module ASIN
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -11,7 +11,7 @@ end
11
11
 
12
12
  Rake::RDocTask.new(:rdoc_dev) do |rd|
13
13
  rd.rdoc_files.include(File.readlines('.document').map(&:strip))
14
- rd.options + ['-a', '--inline-source', '--charset=UTF-8']
14
+ rd.options + ['-a', '--line-numbers', '--charset=UTF-8']
15
15
  end
16
16
 
17
17
  task :default=>:spec
@@ -5,7 +5,7 @@ module ASIN
5
5
 
6
6
  before do
7
7
  ASIN::Configuration.reset
8
- @helper = ASIN.client
8
+ @helper = ASIN::Client.instance
9
9
  @helper.configure :logger => nil
10
10
 
11
11
  @secret = ENV['ASIN_SECRET']
@@ -4,7 +4,7 @@ module ASIN
4
4
  describe ASIN do
5
5
  before do
6
6
  ASIN::Configuration.reset
7
- @helper = ASIN.client
7
+ @helper = ASIN::Client.instance
8
8
  @helper.configure :logger => nil
9
9
 
10
10
  @secret = ENV['ASIN_SECRET']
@@ -93,6 +93,16 @@ module ASIN
93
93
  @helper.lookup(ANY_ASIN)['ItemAttributes']['Title'].should_not be_nil
94
94
  end
95
95
 
96
+ it "should return a mash value" do
97
+ @helper.configure :item_type => :mash
98
+ @helper.lookup(ANY_ASIN).ItemAttributes.Title.should_not be_nil
99
+ end
100
+
101
+ it "should return a rash value" do
102
+ @helper.configure :item_type => :rash
103
+ @helper.lookup(ANY_ASIN).item_attributes.title.should_not be_nil
104
+ end
105
+
96
106
  it "should search_keywords a book with fulltext" do
97
107
  items = @helper.search_keywords 'Learn', 'Objective-C'
98
108
  items.should have(10).things
@@ -102,7 +112,7 @@ module ASIN
102
112
 
103
113
  it "should search_keywords never mind music" do
104
114
  items = @helper.search_keywords 'nirvana', 'never mind', :SearchIndex => :Music
105
- items.should have(8).things
115
+ items.should have(9).things
106
116
 
107
117
  items.first.title.should =~ /Nevermind/
108
118
  end
metadata CHANGED
@@ -1,94 +1,101 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: asin
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
4
5
  prerelease:
5
- version: 0.4.0
6
6
  platform: ruby
7
- authors:
8
- - "Peter Schr\xC3\xB6der"
7
+ authors:
8
+ - Peter Schröder
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-05-20 00:00:00 +02:00
12
+ date: 2011-07-07 00:00:00.000000000 +02:00
14
13
  default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
17
16
  name: crack
18
- prerelease: false
19
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: &2153571760 !ruby/object:Gem::Requirement
20
18
  none: false
21
- requirements:
19
+ requirements:
22
20
  - - ~>
23
- - !ruby/object:Gem::Version
21
+ - !ruby/object:Gem::Version
24
22
  version: 0.1.8
25
23
  type: :runtime
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
28
- name: hashie
29
24
  prerelease: false
30
- requirement: &id002 !ruby/object:Gem::Requirement
25
+ version_requirements: *2153571760
26
+ - !ruby/object:Gem::Dependency
27
+ name: hashie
28
+ requirement: &2153571260 !ruby/object:Gem::Requirement
31
29
  none: false
32
- requirements:
30
+ requirements:
33
31
  - - ~>
34
- - !ruby/object:Gem::Version
32
+ - !ruby/object:Gem::Version
35
33
  version: 1.0.0
36
34
  type: :runtime
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
39
- name: httpi
40
35
  prerelease: false
41
- requirement: &id003 !ruby/object:Gem::Requirement
36
+ version_requirements: *2153571260
37
+ - !ruby/object:Gem::Dependency
38
+ name: httpi
39
+ requirement: &2153570800 !ruby/object:Gem::Requirement
42
40
  none: false
43
- requirements:
41
+ requirements:
44
42
  - - ~>
45
- - !ruby/object:Gem::Version
43
+ - !ruby/object:Gem::Version
46
44
  version: 0.9.2
47
45
  type: :runtime
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
50
- name: httpclient
51
46
  prerelease: false
52
- requirement: &id004 !ruby/object:Gem::Requirement
47
+ version_requirements: *2153570800
48
+ - !ruby/object:Gem::Dependency
49
+ name: httpclient
50
+ requirement: &2153570340 !ruby/object:Gem::Requirement
53
51
  none: false
54
- requirements:
52
+ requirements:
55
53
  - - ~>
56
- - !ruby/object:Gem::Version
54
+ - !ruby/object:Gem::Version
57
55
  version: 2.2.0.2
58
56
  type: :development
59
- version_requirements: *id004
60
- - !ruby/object:Gem::Dependency
61
- name: rspec
62
57
  prerelease: false
63
- requirement: &id005 !ruby/object:Gem::Requirement
58
+ version_requirements: *2153570340
59
+ - !ruby/object:Gem::Dependency
60
+ name: rash
61
+ requirement: &2168479800 !ruby/object:Gem::Requirement
64
62
  none: false
65
- requirements:
63
+ requirements:
66
64
  - - ~>
67
- - !ruby/object:Gem::Version
65
+ - !ruby/object:Gem::Version
66
+ version: 0.3.0
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *2168479800
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: &2168479340 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
68
77
  version: 2.6.0
69
78
  type: :development
70
- version_requirements: *id005
71
- - !ruby/object:Gem::Dependency
72
- name: fuubar
73
79
  prerelease: false
74
- requirement: &id006 !ruby/object:Gem::Requirement
80
+ version_requirements: *2168479340
81
+ - !ruby/object:Gem::Dependency
82
+ name: fuubar
83
+ requirement: &2168478880 !ruby/object:Gem::Requirement
75
84
  none: false
76
- requirements:
85
+ requirements:
77
86
  - - ~>
78
- - !ruby/object:Gem::Version
87
+ - !ruby/object:Gem::Version
79
88
  version: 0.0.4
80
89
  type: :development
81
- version_requirements: *id006
90
+ prerelease: false
91
+ version_requirements: *2168478880
82
92
  description: Amazon Simple INterface.
83
- email:
93
+ email:
84
94
  - phoetmail@googlemail.com
85
95
  executables: []
86
-
87
96
  extensions: []
88
-
89
97
  extra_rdoc_files: []
90
-
91
- files:
98
+ files:
92
99
  - .document
93
100
  - .gitignore
94
101
  - .rvmrc
@@ -98,9 +105,10 @@ files:
98
105
  - README.rdoc
99
106
  - asin.gemspec
100
107
  - lib/asin.rb
101
- - lib/asin/cart.rb
108
+ - lib/asin/client.rb
102
109
  - lib/asin/configuration.rb
103
- - lib/asin/item.rb
110
+ - lib/asin/simple_cart.rb
111
+ - lib/asin/simple_item.rb
104
112
  - lib/asin/version.rb
105
113
  - rakefile.rb
106
114
  - spec/asin.yml
@@ -110,32 +118,29 @@ files:
110
118
  has_rdoc: true
111
119
  homepage: http://github.com/phoet/asin
112
120
  licenses: []
113
-
114
121
  post_install_message:
115
122
  rdoc_options: []
116
-
117
- require_paths:
123
+ require_paths:
118
124
  - lib
119
- required_ruby_version: !ruby/object:Gem::Requirement
125
+ required_ruby_version: !ruby/object:Gem::Requirement
120
126
  none: false
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: "0"
125
- required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
132
  none: false
127
- requirements:
128
- - - ">="
129
- - !ruby/object:Gem::Version
130
- version: "0"
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
131
137
  requirements: []
132
-
133
138
  rubyforge_project: asin
134
- rubygems_version: 1.5.2
139
+ rubygems_version: 1.6.2
135
140
  signing_key:
136
141
  specification_version: 3
137
142
  summary: Simple interface to AWS Lookup, Search and Cart operations.
138
- test_files:
143
+ test_files:
139
144
  - spec/asin.yml
140
145
  - spec/cart_spec.rb
141
146
  - spec/search_spec.rb