asin 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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