asin 0.3.0 → 0.4.0.beta1
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/Gemfile.lock +20 -18
- data/README.rdoc +57 -12
- data/asin.gemspec +6 -6
- data/lib/asin.rb +221 -91
- data/lib/asin/cart.rb +54 -0
- data/lib/asin/configuration.rb +40 -16
- data/lib/asin/item.rb +5 -5
- data/lib/asin/version.rb +1 -1
- data/rakefile.rb +3 -28
- data/spec/asin.yml +4 -0
- data/spec/cart_spec.rb +159 -0
- data/spec/search_spec.rb +95 -0
- data/spec/spec_helper.rb +4 -0
- metadata +17 -13
- data/.travis.yml +0 -1
- data/spec/asin_spec.rb +0 -74
data/Gemfile.lock
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
asin (0.
|
|
4
|
+
asin (0.4.0.beta1)
|
|
5
5
|
crack (~> 0.1.8)
|
|
6
6
|
hashie (~> 1.0.0)
|
|
7
|
-
httpi (~> 0.
|
|
7
|
+
httpi (~> 0.9.2)
|
|
8
8
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: http://rubygems.org/
|
|
11
11
|
specs:
|
|
12
|
+
chalofa_ruby-progressbar (0.0.9.1)
|
|
12
13
|
crack (0.1.8)
|
|
13
14
|
diff-lcs (1.1.2)
|
|
14
|
-
fuubar (0.0.
|
|
15
|
+
fuubar (0.0.4)
|
|
16
|
+
chalofa_ruby-progressbar (~> 0.0.9)
|
|
15
17
|
rspec (~> 2.0)
|
|
16
18
|
rspec-instafail (~> 0.1.4)
|
|
17
|
-
ruby-progressbar (~> 0.0.9)
|
|
18
19
|
hashie (1.0.0)
|
|
19
|
-
httpclient (2.1.
|
|
20
|
-
httpi (0.
|
|
20
|
+
httpclient (2.1.7.2)
|
|
21
|
+
httpi (0.9.2)
|
|
22
|
+
ntlm-http (>= 0.1.1)
|
|
21
23
|
rack
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rspec-
|
|
26
|
-
rspec-
|
|
27
|
-
|
|
28
|
-
rspec-
|
|
24
|
+
ntlm-http (0.1.1)
|
|
25
|
+
rack (1.2.2)
|
|
26
|
+
rspec (2.5.0)
|
|
27
|
+
rspec-core (~> 2.5.0)
|
|
28
|
+
rspec-expectations (~> 2.5.0)
|
|
29
|
+
rspec-mocks (~> 2.5.0)
|
|
30
|
+
rspec-core (2.5.1)
|
|
31
|
+
rspec-expectations (2.5.0)
|
|
29
32
|
diff-lcs (~> 1.1.2)
|
|
30
|
-
rspec-instafail (0.1.
|
|
31
|
-
rspec-mocks (2.
|
|
32
|
-
ruby-progressbar (0.0.9)
|
|
33
|
+
rspec-instafail (0.1.7)
|
|
34
|
+
rspec-mocks (2.5.0)
|
|
33
35
|
|
|
34
36
|
PLATFORMS
|
|
35
37
|
ruby
|
|
@@ -37,5 +39,5 @@ PLATFORMS
|
|
|
37
39
|
DEPENDENCIES
|
|
38
40
|
asin!
|
|
39
41
|
fuubar (~> 0.0.3)
|
|
40
|
-
httpclient (~> 2.1.
|
|
41
|
-
rspec (~> 2.
|
|
42
|
+
httpclient (~> 2.1.7.2)
|
|
43
|
+
rspec (~> 2.5.0)
|
data/README.rdoc
CHANGED
|
@@ -2,34 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
Status: http://stillmaintained.com/phoet/asin.png
|
|
4
4
|
|
|
5
|
-
The gem is tested against 1.9.2
|
|
5
|
+
The gem is tested against 1.9.2 and 1.8.7 (compatibility with Heroku-Bamboo-Stack[http://docs.heroku.com/stack]) and runs smoothly with Rails 3.
|
|
6
6
|
|
|
7
7
|
gem install asin
|
|
8
|
+
gem install httpclient # optional, see HTTPI
|
|
8
9
|
|
|
9
10
|
== Configuration
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
You can put this in a initializer (config/initializers/asin.rb):
|
|
12
|
+
Rails style initializer (config/initializers/asin.rb):
|
|
14
13
|
|
|
15
14
|
ASIN::Configuration.configure do |config|
|
|
16
15
|
config.secret = 'your-secret'
|
|
17
16
|
config.key = 'your-key'
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
YAML style configuration:
|
|
20
|
+
|
|
21
|
+
ASIN::Configuration.configure :yaml => 'config/asin.yml'
|
|
22
|
+
|
|
23
|
+
Inline style configuration:
|
|
24
|
+
|
|
25
|
+
ASIN::Configuration.configure :secret => 'your-secret', :key => 'your-key'
|
|
26
|
+
# or
|
|
27
|
+
client.configure :secret => 'your-secret', :key => 'your-key'
|
|
28
|
+
|
|
29
|
+
== Usage
|
|
30
|
+
|
|
31
|
+
ASIN is designed as a module, so you can include it into any object you like:
|
|
21
32
|
|
|
33
|
+
# require and include
|
|
22
34
|
require 'asin'
|
|
23
35
|
include ASIN
|
|
24
36
|
|
|
25
|
-
#
|
|
26
|
-
|
|
37
|
+
# lookup an ASIN
|
|
38
|
+
lookup '1430218150'
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
But you can also use the +client+ method to get a client-object:
|
|
29
41
|
|
|
42
|
+
# just require
|
|
43
|
+
require 'asin'
|
|
44
|
+
|
|
30
45
|
# create an ASIN client
|
|
31
46
|
client = ASIN.client
|
|
32
|
-
|
|
47
|
+
|
|
33
48
|
# lookup an item with the amazon standard identification number (asin)
|
|
34
49
|
item = client.lookup '1430218150'
|
|
35
50
|
|
|
@@ -51,6 +66,39 @@ The old configuration syntax is still valid:
|
|
|
51
66
|
item.raw.ItemAttributes.ListPrice.FormattedPrice
|
|
52
67
|
=> $39.99
|
|
53
68
|
|
|
69
|
+
There is an additional set of methods to support AWS cart operations:
|
|
70
|
+
|
|
71
|
+
#just require
|
|
72
|
+
require 'asin'
|
|
73
|
+
|
|
74
|
+
# create an ASIN client
|
|
75
|
+
client = ASIN.client
|
|
76
|
+
|
|
77
|
+
# create a cart with an item
|
|
78
|
+
cart = client.create_cart({:asin => '1430218150', :quantity => 1})
|
|
79
|
+
cart.items
|
|
80
|
+
=> [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
|
|
81
|
+
|
|
82
|
+
# get an already existing cart from a CartId and HMAC
|
|
83
|
+
cart = client.get_cart('176-9182855-2326919', 'KgeVCA0YJTbuN/7Ibakrk/KnHWA=')
|
|
84
|
+
cart.empty?
|
|
85
|
+
=> false
|
|
86
|
+
|
|
87
|
+
# clear everything from the cart
|
|
88
|
+
cart = client.clear_cart(cart)
|
|
89
|
+
cart.empty?
|
|
90
|
+
=> true
|
|
91
|
+
|
|
92
|
+
# add items to the cart
|
|
93
|
+
cart = client.add_items(cart, {:asin => '1430216263', :quantity => 2})
|
|
94
|
+
cart.empty?
|
|
95
|
+
=> false
|
|
96
|
+
|
|
97
|
+
# update items in the cart
|
|
98
|
+
cart = client.update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :action => :SaveForLater}, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
|
|
99
|
+
cart.saved_items
|
|
100
|
+
=> [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
|
|
101
|
+
|
|
54
102
|
== HTTPI
|
|
55
103
|
|
|
56
104
|
ASIN uses HTTPI[https://github.com/rubiii/httpi] as a HTTP-Client adapter.
|
|
@@ -64,6 +112,3 @@ As a default HTTPI uses _httpclient_ so you should add that dependency to your p
|
|
|
64
112
|
Have a look at the RDOC[http://rdoc.info/projects/phoet/asin] for this project, if you want further information.
|
|
65
113
|
|
|
66
114
|
For more information on the REST calls, have a look at the whole Amazon E-Commerce-API[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/].
|
|
67
|
-
|
|
68
|
-
The code currently runs best on Ruby-1.9 due to encoding issues with the Amazon REST output (if *YOU* know how to backport this to 1.8.7, you are welcome!).
|
|
69
|
-
|
data/asin.gemspec
CHANGED
|
@@ -9,10 +9,10 @@ Gem::Specification.new do |s|
|
|
|
9
9
|
s.authors = ['Peter Schröder']
|
|
10
10
|
s.email = ['phoetmail@googlemail.com']
|
|
11
11
|
s.homepage = 'http://github.com/phoet/asin'
|
|
12
|
-
s.summary = 'Simple interface to
|
|
13
|
-
s.description = 'Amazon Simple INterface
|
|
12
|
+
s.summary = 'Simple interface to AWS Lookup, Search and Cart operations.'
|
|
13
|
+
s.description = 'Amazon Simple INterface.'
|
|
14
14
|
|
|
15
|
-
s.rubyforge_project = "asin"
|
|
15
|
+
s.rubyforge_project = "asin" # rubyforge, WTF?!
|
|
16
16
|
|
|
17
17
|
s.files = `git ls-files`.split("\n")
|
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
@@ -21,9 +21,9 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
|
|
22
22
|
s.add_dependency('crack', '~> 0.1.8')
|
|
23
23
|
s.add_dependency('hashie', '~> 1.0.0')
|
|
24
|
-
s.add_dependency('httpi', '~> 0.
|
|
24
|
+
s.add_dependency('httpi', '~> 0.9.2')
|
|
25
25
|
|
|
26
|
-
s.add_development_dependency('httpclient', '~> 2.1.
|
|
27
|
-
s.add_development_dependency('rspec', '~> 2.
|
|
26
|
+
s.add_development_dependency('httpclient', '~> 2.1.7.2')
|
|
27
|
+
s.add_development_dependency('rspec', '~> 2.5.0')
|
|
28
28
|
s.add_development_dependency('fuubar', '~> 0.0.3')
|
|
29
29
|
end
|
data/lib/asin.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
1
2
|
require 'httpi'
|
|
2
3
|
require 'crack/xml'
|
|
3
4
|
require 'cgi'
|
|
@@ -5,65 +6,97 @@ require 'base64'
|
|
|
5
6
|
require 'logger'
|
|
6
7
|
|
|
7
8
|
require 'asin/item'
|
|
9
|
+
require 'asin/cart'
|
|
8
10
|
require 'asin/version'
|
|
9
11
|
require 'asin/configuration'
|
|
10
12
|
|
|
11
13
|
# ASIN (Amazon Simple INterface) is a gem for easy access of the Amazon E-Commerce-API.
|
|
12
14
|
# It is simple to configure and use. Since it's very small and flexible, it is easy to extend it to your needs.
|
|
13
|
-
#
|
|
15
|
+
#
|
|
14
16
|
# Author:: Peter Schröder (mailto:phoetmail@googlemail.com)
|
|
15
|
-
#
|
|
16
|
-
# ==Usage
|
|
17
|
-
#
|
|
17
|
+
#
|
|
18
|
+
# == Usage
|
|
19
|
+
#
|
|
18
20
|
# The ASIN module is designed as a mixin.
|
|
19
|
-
#
|
|
21
|
+
#
|
|
20
22
|
# require 'asin'
|
|
21
23
|
# include ASIN
|
|
22
|
-
#
|
|
24
|
+
#
|
|
23
25
|
# In order to use the Amazon API properly, you need to be a registered user (http://aws.amazon.com).
|
|
24
|
-
#
|
|
26
|
+
#
|
|
25
27
|
# The registration process will give you a +secret-key+ and an +access-key+ (AWSAccessKeyId).
|
|
26
|
-
#
|
|
28
|
+
#
|
|
27
29
|
# Both are needed to use ASIN (see Configuration for more details):
|
|
28
|
-
#
|
|
30
|
+
#
|
|
29
31
|
# configure :secret => 'your-secret', :key => 'your-key'
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
+
#
|
|
33
|
+
# == Search
|
|
34
|
+
#
|
|
35
|
+
# After configuring your environment you can call the +lookup+ method to retrieve an +Item+ via the
|
|
32
36
|
# Amazon Standard Identification Number (ASIN):
|
|
33
|
-
#
|
|
37
|
+
#
|
|
34
38
|
# item = lookup '1430218150'
|
|
35
39
|
# item.title
|
|
36
40
|
# => "Learn Objective-C on the Mac (Learn Series)"
|
|
37
41
|
#
|
|
38
42
|
# OR search with fulltext/ASIN/ISBN
|
|
39
|
-
#
|
|
43
|
+
#
|
|
40
44
|
# items = search 'Learn Objective-C'
|
|
41
45
|
# items.first.title
|
|
42
46
|
# => "Learn Objective-C on the Mac (Learn Series)"
|
|
43
|
-
#
|
|
47
|
+
#
|
|
44
48
|
# The +Item+ uses a Hashie::Mash as its internal data representation and you can get fetched data from it:
|
|
45
|
-
#
|
|
49
|
+
#
|
|
46
50
|
# item.raw.ItemAttributes.ListPrice.FormattedPrice
|
|
47
51
|
# => "$39.99"
|
|
48
|
-
#
|
|
49
|
-
# ==Further Configuration
|
|
50
|
-
#
|
|
51
|
-
# If you need more controll over the request that is sent to the
|
|
52
|
+
#
|
|
53
|
+
# == Further Configuration
|
|
54
|
+
#
|
|
55
|
+
# If you need more controll over the request that is sent to the
|
|
52
56
|
# Amazon API (http://docs.amazonwebservices.com/AWSEcommerceService/4-0/),
|
|
53
57
|
# you can override some defaults or add additional query-parameters to the REST calls:
|
|
54
|
-
#
|
|
58
|
+
#
|
|
55
59
|
# configure :host => 'webservices.amazon.de'
|
|
56
60
|
# lookup(asin, :ResponseGroup => :Medium)
|
|
57
|
-
#
|
|
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
|
+
#
|
|
58
91
|
module ASIN
|
|
59
|
-
|
|
92
|
+
|
|
60
93
|
DIGEST = OpenSSL::Digest::Digest.new('sha256')
|
|
61
94
|
PATH = '/onca/xml'
|
|
62
95
|
|
|
63
96
|
# Convenience method to create an ASIN client.
|
|
64
|
-
#
|
|
97
|
+
#
|
|
65
98
|
# A client is not necessary though, you can simply include the ASIN module otherwise.
|
|
66
|
-
#
|
|
99
|
+
#
|
|
67
100
|
def self.client
|
|
68
101
|
client = Object.new
|
|
69
102
|
client.extend ASIN
|
|
@@ -71,48 +104,48 @@ module ASIN
|
|
|
71
104
|
end
|
|
72
105
|
|
|
73
106
|
# Configures the basic request parameters for ASIN.
|
|
74
|
-
#
|
|
107
|
+
#
|
|
75
108
|
# Expects at least +secret+ and +key+ for the API call:
|
|
76
|
-
#
|
|
109
|
+
#
|
|
77
110
|
# configure :secret => 'your-secret', :key => 'your-key'
|
|
78
|
-
#
|
|
111
|
+
#
|
|
79
112
|
# See ASIN::Configuration for more infos.
|
|
80
|
-
#
|
|
113
|
+
#
|
|
81
114
|
def configure(options={})
|
|
82
115
|
Configuration.configure(options)
|
|
83
116
|
end
|
|
84
117
|
|
|
85
118
|
# Performs an +ItemLookup+ REST call against the Amazon API.
|
|
86
|
-
#
|
|
119
|
+
#
|
|
87
120
|
# Expects an ASIN (Amazon Standard Identification Number) and returns an +Item+:
|
|
88
|
-
#
|
|
121
|
+
#
|
|
89
122
|
# item = lookup '1430218150'
|
|
90
123
|
# item.title
|
|
91
124
|
# => "Learn Objective-C on the Mac (Learn Series)"
|
|
92
|
-
#
|
|
125
|
+
#
|
|
93
126
|
# ==== Options:
|
|
94
|
-
#
|
|
127
|
+
#
|
|
95
128
|
# Additional parameters for the API call like this:
|
|
96
|
-
#
|
|
129
|
+
#
|
|
97
130
|
# lookup(asin, :ResponseGroup => :Medium)
|
|
98
|
-
#
|
|
131
|
+
#
|
|
99
132
|
def lookup(asin, params={})
|
|
100
133
|
response = call(params.merge(:Operation => :ItemLookup, :ItemId => asin))
|
|
101
134
|
Item.new(response['ItemLookupResponse']['Items']['Item'])
|
|
102
135
|
end
|
|
103
|
-
|
|
136
|
+
|
|
104
137
|
# Performs an +ItemSearch+ REST call against the Amazon API.
|
|
105
|
-
#
|
|
138
|
+
#
|
|
106
139
|
# Expects a search-string which can be an arbitrary array of strings (ASINs f.e.) and returns a list of +Items+:
|
|
107
|
-
#
|
|
140
|
+
#
|
|
108
141
|
# items = search_keywords 'Learn', 'Objective-C'
|
|
109
142
|
# items.first.title
|
|
110
143
|
# => "Learn Objective-C on the Mac (Learn Series)"
|
|
111
|
-
#
|
|
144
|
+
#
|
|
112
145
|
# ==== Options:
|
|
113
|
-
#
|
|
146
|
+
#
|
|
114
147
|
# Additional parameters for the API call like this:
|
|
115
|
-
#
|
|
148
|
+
#
|
|
116
149
|
# search_keywords('nirvana', 'never mind', :SearchIndex => :Music)
|
|
117
150
|
#
|
|
118
151
|
# Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
|
|
@@ -122,17 +155,17 @@ module ASIN
|
|
|
122
155
|
response = call(params.merge(:Operation => :ItemSearch, :Keywords => keywords.join(' ')))
|
|
123
156
|
(response['ItemSearchResponse']['Items']['Item'] || []).map {|item| Item.new(item)}
|
|
124
157
|
end
|
|
125
|
-
|
|
158
|
+
|
|
126
159
|
# Performs an +ItemSearch+ REST call against the Amazon API.
|
|
127
|
-
#
|
|
160
|
+
#
|
|
128
161
|
# Expects a Hash of search params where and returns a list of +Items+:
|
|
129
|
-
#
|
|
162
|
+
#
|
|
130
163
|
# items = search :SearchIndex => :Music
|
|
131
|
-
#
|
|
164
|
+
#
|
|
132
165
|
# ==== Options:
|
|
133
|
-
#
|
|
166
|
+
#
|
|
134
167
|
# Additional parameters for the API call like this:
|
|
135
|
-
#
|
|
168
|
+
#
|
|
136
169
|
# search(:Keywords => 'nirvana', :SearchIndex => :Music)
|
|
137
170
|
#
|
|
138
171
|
# Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
|
|
@@ -141,57 +174,154 @@ module ASIN
|
|
|
141
174
|
response = call(params.merge(:Operation => :ItemSearch))
|
|
142
175
|
(response['ItemSearchResponse']['Items']['Item'] || []).map {|item| Item.new(item)}
|
|
143
176
|
end
|
|
144
|
-
|
|
145
|
-
private
|
|
146
177
|
|
|
147
|
-
|
|
148
|
-
|
|
178
|
+
# Performs an +CartCreate+ REST call against the Amazon API.
|
|
179
|
+
#
|
|
180
|
+
# Expects one ore more Item-Hashes and returns a +Cart+:
|
|
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 +Cart+:
|
|
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})
|
|
149
204
|
end
|
|
150
205
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
else
|
|
168
|
-
log(:error, "got response='#{response.body}'")
|
|
169
|
-
raise "request failed with response-code='#{response.code}'"
|
|
170
|
-
end
|
|
206
|
+
# Performs an +CartAdd+ REST call against the Amazon API.
|
|
207
|
+
#
|
|
208
|
+
# Expects a +Cart+ created with +create_cart+ and one ore more Item-Hashes and returns an updated +Cart+:
|
|
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}))
|
|
171
222
|
end
|
|
172
223
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"#{query}&Signature=#{signature}"
|
|
224
|
+
# Performs an +CartModify+ REST call against the Amazon API.
|
|
225
|
+
#
|
|
226
|
+
# Expects a +Cart+ created with +create_cart+ and one ore more Item-Hashes to modify and returns an updated +Cart+:
|
|
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}))
|
|
190
240
|
end
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
241
|
+
|
|
242
|
+
# Performs an +CartClear+ REST call against the Amazon API.
|
|
243
|
+
#
|
|
244
|
+
# Expects a +Cart+ created with +create_cart+ and returns an empty +Cart+:
|
|
245
|
+
#
|
|
246
|
+
# cart = clear_cart(cart)
|
|
247
|
+
#
|
|
248
|
+
def clear_cart(cart)
|
|
249
|
+
cart(:CartClear, {:CartId => cart.cart_id, :HMAC => cart.hmac})
|
|
194
250
|
end
|
|
195
251
|
|
|
196
|
-
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
def create_item_params(items)
|
|
255
|
+
keyword_mappings = {
|
|
256
|
+
:asin => 'ASIN',
|
|
257
|
+
:quantity => 'Quantity',
|
|
258
|
+
:cart_item_id => 'CartItemId',
|
|
259
|
+
:offer_listing_id => 'OfferListingId',
|
|
260
|
+
:action => 'Action'
|
|
261
|
+
}
|
|
262
|
+
params = {}
|
|
263
|
+
items.each_with_index do |item, i|
|
|
264
|
+
item.each do |key, value|
|
|
265
|
+
next unless keyword = keyword_mappings[key]
|
|
266
|
+
params["Item.#{i}.#{keyword}"] = value.to_s
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
params
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def cart(operation, params={})
|
|
273
|
+
response = call(params.merge(:Operation => operation))
|
|
274
|
+
Cart.new(response["#{operation}Response"]['Cart'])
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def credentials_valid?
|
|
279
|
+
Configuration.secret && Configuration.key
|
|
280
|
+
end
|
|
197
281
|
|
|
282
|
+
def call(params)
|
|
283
|
+
raise "you have to configure ASIN: 'configure :secret => 'your-secret', :key => 'your-key''" unless credentials_valid?
|
|
284
|
+
|
|
285
|
+
log(:debug, "calling with params=#{params}")
|
|
286
|
+
signed = create_signed_query_string(params)
|
|
287
|
+
|
|
288
|
+
url = "http://#{Configuration.host}#{PATH}?#{signed}"
|
|
289
|
+
log(:info, "performing rest call to url='#{url}'")
|
|
290
|
+
|
|
291
|
+
response = HTTPI.get(url)
|
|
292
|
+
if response.code == 200
|
|
293
|
+
# force utf-8 chars, works only on 1.9 string
|
|
294
|
+
resp = response.body
|
|
295
|
+
resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
|
|
296
|
+
log(:debug, "got response='#{resp}'")
|
|
297
|
+
Crack::XML.parse(resp)
|
|
298
|
+
else
|
|
299
|
+
log(:error, "got response='#{response.body}'")
|
|
300
|
+
raise "request failed with response-code='#{response.code}'"
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def create_signed_query_string(params)
|
|
305
|
+
# nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
|
|
306
|
+
params[:Service] = :AWSECommerceService
|
|
307
|
+
params[:AWSAccessKeyId] = Configuration.key
|
|
308
|
+
# utc timestamp needed for signing
|
|
309
|
+
params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
310
|
+
|
|
311
|
+
# signing needs to order the query alphabetically
|
|
312
|
+
query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
|
|
313
|
+
|
|
314
|
+
# yeah, you really need to sign the get-request not the query
|
|
315
|
+
request_to_sign = "GET\n#{Configuration.host}\n#{PATH}\n#{query}"
|
|
316
|
+
hmac = OpenSSL::HMAC.digest(DIGEST, Configuration.secret, request_to_sign)
|
|
317
|
+
|
|
318
|
+
# don't forget to remove the newline from base64
|
|
319
|
+
signature = CGI.escape(Base64.encode64(hmac).chomp)
|
|
320
|
+
"#{query}&Signature=#{signature}"
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def log(severity, message)
|
|
324
|
+
Configuration.logger.send severity, message if Configuration.logger
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
end
|
data/lib/asin/cart.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'hashie'
|
|
2
|
+
|
|
3
|
+
module ASIN
|
|
4
|
+
|
|
5
|
+
# = Cart
|
|
6
|
+
#
|
|
7
|
+
# The +Cart+ class is a wrapper for the Amazon XML-REST-Response.
|
|
8
|
+
#
|
|
9
|
+
# A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
|
|
10
|
+
#
|
|
11
|
+
class Cart
|
|
12
|
+
|
|
13
|
+
attr_reader :raw
|
|
14
|
+
|
|
15
|
+
def initialize(hash)
|
|
16
|
+
@raw = Hashie::Mash.new(hash)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def cart_id
|
|
20
|
+
@raw.CartId
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def hmac
|
|
24
|
+
@raw.HMAC
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def url
|
|
28
|
+
@raw.PurchaseURL
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def price
|
|
32
|
+
@raw.SubTotal.FormattedPrice
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def items
|
|
36
|
+
return [] unless @raw.CartItems
|
|
37
|
+
@raw.CartItems.CartItem.is_a?(Array) ? @raw.CartItems.CartItem : [@raw.CartItems.CartItem]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def saved_items
|
|
41
|
+
return [] unless @raw.SavedForLaterItems
|
|
42
|
+
@raw.SavedForLaterItems.SavedForLaterItem.is_a?(Array) ? @raw.SavedForLaterItems.SavedForLaterItem : [@raw.SavedForLaterItems.SavedForLaterItem]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def valid?
|
|
46
|
+
@raw.Request.IsValid == 'True'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def empty?
|
|
50
|
+
@raw.CartItems.nil?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/asin/configuration.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
1
3
|
module ASIN
|
|
2
4
|
class Configuration
|
|
3
5
|
class << self
|
|
@@ -5,9 +7,9 @@ module ASIN
|
|
|
5
7
|
attr_accessor :secret, :key, :host, :logger
|
|
6
8
|
|
|
7
9
|
# Rails initializer configuration.
|
|
8
|
-
#
|
|
10
|
+
#
|
|
9
11
|
# Expects at least +secret+ and +key+ for the API call:
|
|
10
|
-
#
|
|
12
|
+
#
|
|
11
13
|
# ASIN::Configuration.configure do |config|
|
|
12
14
|
# config.secret = 'your-secret'
|
|
13
15
|
# config.key = 'your-key'
|
|
@@ -15,18 +17,35 @@ module ASIN
|
|
|
15
17
|
#
|
|
16
18
|
# You may pass options as a hash as well:
|
|
17
19
|
#
|
|
18
|
-
# ASIN::Configuration.configure
|
|
19
|
-
#
|
|
20
|
+
# ASIN::Configuration.configure :secret => 'your-secret', :key => 'your-key'
|
|
21
|
+
#
|
|
22
|
+
# Or configure everything using YAML:
|
|
23
|
+
#
|
|
24
|
+
# ASIN::Configuration.configure :yaml => 'config/asin.yml'
|
|
25
|
+
#
|
|
26
|
+
# ASIN::Configuration.configure :yaml => 'config/asin.yml' do |config, yml|
|
|
27
|
+
# config.key = yml[Rails.env]['aws_access_key']
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
20
30
|
# ==== Options:
|
|
21
|
-
#
|
|
31
|
+
#
|
|
22
32
|
# [secret] the API secret key
|
|
23
33
|
# [key] the API access key
|
|
24
34
|
# [host] the host, which defaults to 'webservices.amazon.com'
|
|
25
35
|
# [logger] a different logger than logging to STDERR (nil for no logging)
|
|
26
|
-
#
|
|
36
|
+
#
|
|
27
37
|
def configure(options={})
|
|
28
38
|
init_config
|
|
29
|
-
if
|
|
39
|
+
if yml_path = options[:yaml] || options[:yml]
|
|
40
|
+
yml = File.open(yml_path) { |file| YAML.load(file) }
|
|
41
|
+
if block_given?
|
|
42
|
+
yield self, yml
|
|
43
|
+
else
|
|
44
|
+
yml.each do |key, value|
|
|
45
|
+
send(:"#{key}=", value)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
elsif block_given?
|
|
30
49
|
yield self
|
|
31
50
|
else
|
|
32
51
|
options.each do |key, value|
|
|
@@ -36,17 +55,22 @@ module ASIN
|
|
|
36
55
|
self
|
|
37
56
|
end
|
|
38
57
|
|
|
58
|
+
# Resets configuration to defaults
|
|
59
|
+
#
|
|
60
|
+
def reset
|
|
61
|
+
init_config(true)
|
|
62
|
+
end
|
|
63
|
+
|
|
39
64
|
private
|
|
40
65
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
def init_config(force=false)
|
|
67
|
+
return if @init && !force
|
|
68
|
+
@init = true
|
|
69
|
+
@secret = ''
|
|
70
|
+
@key = ''
|
|
71
|
+
@host = 'webservices.amazon.com'
|
|
72
|
+
@logger = Logger.new(STDERR)
|
|
73
|
+
end
|
|
49
74
|
end
|
|
50
75
|
end
|
|
51
76
|
end
|
|
52
|
-
|
data/lib/asin/item.rb
CHANGED
|
@@ -3,11 +3,11 @@ require 'hashie'
|
|
|
3
3
|
module ASIN
|
|
4
4
|
|
|
5
5
|
# =Item
|
|
6
|
-
#
|
|
6
|
+
#
|
|
7
7
|
# The +Item+ class is a wrapper for the Amazon XML-REST-Response.
|
|
8
|
-
#
|
|
8
|
+
#
|
|
9
9
|
# A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
|
|
10
|
-
#
|
|
10
|
+
#
|
|
11
11
|
class Item
|
|
12
12
|
|
|
13
13
|
attr_reader :raw
|
|
@@ -20,5 +20,5 @@ module ASIN
|
|
|
20
20
|
@raw.ItemAttributes.Title
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
|
-
|
|
24
|
-
end
|
|
23
|
+
|
|
24
|
+
end
|
data/lib/asin/version.rb
CHANGED
data/rakefile.rb
CHANGED
|
@@ -1,42 +1,17 @@
|
|
|
1
1
|
require "bundler"
|
|
2
2
|
require "rake/rdoctask"
|
|
3
|
-
require "rake/gempackagetask"
|
|
4
3
|
require 'rspec/core/rake_task'
|
|
5
4
|
|
|
5
|
+
Bundler::GemHelper.install_tasks
|
|
6
|
+
|
|
6
7
|
RSpec::Core::RakeTask.new do |t|
|
|
7
8
|
t.rspec_opts = ["--format Fuubar", "--color", "-r ./spec/spec_helper.rb"]
|
|
8
9
|
t.pattern = 'spec/**/*_spec.rb'
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
Bundler::GemHelper.install_tasks
|
|
12
|
-
|
|
13
|
-
spec = eval(File.new("asin.gemspec").readlines.join("\n"))
|
|
14
|
-
|
|
15
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
|
16
|
-
pkg.need_zip = true
|
|
17
|
-
pkg.need_tar = true
|
|
18
|
-
end
|
|
19
|
-
|
|
20
12
|
Rake::RDocTask.new(:rdoc_dev) do |rd|
|
|
21
13
|
rd.rdoc_files.include("lib/**/*.rb", "README.rdoc")
|
|
22
14
|
rd.options + ['-a', '--inline-source', '--charset=UTF-8']
|
|
23
15
|
end
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
task :test do
|
|
27
|
-
require 'rake/testtask'
|
|
28
|
-
Rake::TestTask.new do |t|
|
|
29
|
-
t.libs << "test"
|
|
30
|
-
t.ruby_opts << "-rubygems"
|
|
31
|
-
t.test_files = FileList['test/test_*.rb']
|
|
32
|
-
t.verbose = true
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
task :default=>:test
|
|
36
|
-
|
|
37
|
-
desc "execute build on travis-ci"
|
|
38
|
-
task :travis_ci do
|
|
39
|
-
puts "WE LOOOOOOOOOOOOVE"
|
|
40
|
-
Rake::Task['spec'].invoke
|
|
41
|
-
puts "TRAVIS CI"
|
|
42
|
-
end
|
|
17
|
+
task :default=>:spec
|
data/spec/asin.yml
ADDED
data/spec/cart_spec.rb
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module ASIN
|
|
4
|
+
describe ASIN do
|
|
5
|
+
|
|
6
|
+
before do
|
|
7
|
+
ASIN::Configuration.reset
|
|
8
|
+
@helper = ASIN.client
|
|
9
|
+
@helper.configure :logger => nil
|
|
10
|
+
|
|
11
|
+
@secret = ENV['ASIN_SECRET']
|
|
12
|
+
@key = ENV['ASIN_KEY']
|
|
13
|
+
puts "configure #{@secret} and #{@key} for this test"
|
|
14
|
+
@helper.configure :secret => @secret, :key => @key
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "cart" do
|
|
18
|
+
|
|
19
|
+
it "should create a cart" do
|
|
20
|
+
cart = @helper.create_cart({:asin => ANY_ASIN, :quantity => 1}, {:asin => OTHER_ASIN, :quantity => 2})
|
|
21
|
+
cart.valid?.should be(true)
|
|
22
|
+
cart.empty?.should be(false)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should handle item paramters" do
|
|
26
|
+
params = @helper.send(:create_item_params, [{:asin => 'any_asin', :quantity => 1}, {:cart_item_id => 'any_cart_item_id', :quantity => 2}, {:offer_listing_id => 'any_offer_listing_id', :quantity => 3},{:cart_item_id => 'any_cart_item_id', :action => :SaveForLater}])
|
|
27
|
+
params.should eql({"Item.0.ASIN"=>"any_asin", "Item.0.Quantity"=>"1", "Item.1.CartItemId"=>"any_cart_item_id", "Item.1.Quantity"=>"2", "Item.2.OfferListingId"=>"any_offer_listing_id", "Item.2.Quantity"=>"3", "Item.3.CartItemId"=>"any_cart_item_id", "Item.3.Action"=>"SaveForLater"})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context "with an existing cart" do
|
|
31
|
+
|
|
32
|
+
before do
|
|
33
|
+
@cart = @helper.create_cart({:asin => ANY_ASIN, :quantity => 1})
|
|
34
|
+
@cart.valid?.should be(true)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "should clear a cart" do
|
|
38
|
+
cart = @helper.clear_cart(@cart)
|
|
39
|
+
cart.valid?.should be(true)
|
|
40
|
+
cart.empty?.should be(true)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should get a cart" do
|
|
44
|
+
cart = @helper.get_cart(@cart.cart_id, @cart.hmac)
|
|
45
|
+
cart.valid?.should be(true)
|
|
46
|
+
cart.empty?.should be(false)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "should add items to a cart" do
|
|
50
|
+
cart = @helper.add_items(@cart, {:asin => OTHER_ASIN, :quantity => 2})
|
|
51
|
+
cart.valid?.should be(true)
|
|
52
|
+
cart.empty?.should be(false)
|
|
53
|
+
cart.items.should have(2).things
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should update a cart" do
|
|
57
|
+
item_id = @cart.items.first.CartItemId
|
|
58
|
+
cart = @helper.update_items(@cart, {:cart_item_id => item_id, :action => 'SaveForLater'}, {:cart_item_id => item_id, :quantity => 7})
|
|
59
|
+
cart.saved_items.should have(1).things
|
|
60
|
+
cart.valid?.should be(true)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context Cart do
|
|
66
|
+
|
|
67
|
+
before do
|
|
68
|
+
@helper.configure :secret => @secret, :key => @key
|
|
69
|
+
@two_items = {"Request"=>
|
|
70
|
+
{"IsValid"=>"True",
|
|
71
|
+
"CartAddRequest"=>
|
|
72
|
+
{"CartId"=>"186-8702292-9782208",
|
|
73
|
+
"HMAC"=>"Ck5MXUE+OQiC/Jh8u6NhBf5FbV8=",
|
|
74
|
+
"Items"=>{"Item"=>{"ASIN"=>"1430216263", "Quantity"=>"2"}}}},
|
|
75
|
+
"CartId"=>"186-8702292-9782208",
|
|
76
|
+
"HMAC"=>"Ck5MXUE+OQiC/Jh8u6NhBf5FbV8=",
|
|
77
|
+
"URLEncodedHMAC"=>"Ck5MXUE%2BOQiC%2FJh8u6NhBf5FbV8%3D",
|
|
78
|
+
"PurchaseURL"=>
|
|
79
|
+
"https://www.amazon.com/gp/cart/aws-merge.html?cart-id=186-8702292-9782208%26associate-id=ws%26hmac=Ck5MXUE%2BOQiC/Jh8u6NhBf5FbV8=%26SubscriptionId=AKIAJFA5X7RTOKFNPVZQ%26MergeCart=False",
|
|
80
|
+
"SubTotal"=>
|
|
81
|
+
{"Amount"=>"6595", "CurrencyCode"=>"USD", "FormattedPrice"=>"$65.95"},
|
|
82
|
+
"CartItems"=>
|
|
83
|
+
{"SubTotal"=>
|
|
84
|
+
{"Amount"=>"6595", "CurrencyCode"=>"USD", "FormattedPrice"=>"$65.95"},
|
|
85
|
+
"CartItem"=>
|
|
86
|
+
[{"CartItemId"=>"U3CFEHHIPJNW3L",
|
|
87
|
+
"ASIN"=>"1430216263",
|
|
88
|
+
"MerchantId"=>"ATVPDKIKX0DER",
|
|
89
|
+
"SellerId"=>"A2R2RITDJNW1Q6",
|
|
90
|
+
"SellerNickname"=>"Amazon.com",
|
|
91
|
+
"Quantity"=>"2",
|
|
92
|
+
"Title"=>"Beginning iPhone Development: Exploring the iPhone SDK",
|
|
93
|
+
"ProductGroup"=>"Book",
|
|
94
|
+
"Price"=>
|
|
95
|
+
{"Amount"=>"1978", "CurrencyCode"=>"USD", "FormattedPrice"=>"$19.78"},
|
|
96
|
+
"ItemTotal"=>
|
|
97
|
+
{"Amount"=>"3956", "CurrencyCode"=>"USD", "FormattedPrice"=>"$39.56"}},
|
|
98
|
+
{"CartItemId"=>"U3G241HVLLB8N6",
|
|
99
|
+
"ASIN"=>"1430218150",
|
|
100
|
+
"MerchantId"=>"ATVPDKIKX0DER",
|
|
101
|
+
"SellerId"=>"A2R2RITDJNW1Q6",
|
|
102
|
+
"SellerNickname"=>"Amazon.com",
|
|
103
|
+
"Quantity"=>"1",
|
|
104
|
+
"Title"=>"Learn Objective-C on the Mac (Learn Series)",
|
|
105
|
+
"ProductGroup"=>"Book",
|
|
106
|
+
"Price"=>
|
|
107
|
+
{"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"},
|
|
108
|
+
"ItemTotal"=>
|
|
109
|
+
{"Amount"=>"2639",
|
|
110
|
+
"CurrencyCode"=>"USD",
|
|
111
|
+
"FormattedPrice"=>"$26.39"}}]}}
|
|
112
|
+
@one_item = {"Request"=>
|
|
113
|
+
{"IsValid"=>"True",
|
|
114
|
+
"CartCreateRequest"=>
|
|
115
|
+
{"Items"=>{"Item"=>{"ASIN"=>"1430218150", "Quantity"=>"1"}}}},
|
|
116
|
+
"CartId"=>"176-9182855-2326919",
|
|
117
|
+
"HMAC"=>"KgeVCA0YJTbuN/7Ibakrk/KnHWA=",
|
|
118
|
+
"URLEncodedHMAC"=>"KgeVCA0YJTbuN%2F7Ibakrk%2FKnHWA%3D",
|
|
119
|
+
"PurchaseURL"=>
|
|
120
|
+
"https://www.amazon.com/gp/cart/aws-merge.html?cart-id=176-9182855-2326919%26associate-id=ws%26hmac=KgeVCA0YJTbuN/7Ibakrk/KnHWA=%26SubscriptionId=AKIAJFA5X7RTOKFNPVZQ%26MergeCart=False",
|
|
121
|
+
"SubTotal"=>
|
|
122
|
+
{"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"},
|
|
123
|
+
"CartItems"=>
|
|
124
|
+
{"SubTotal"=>
|
|
125
|
+
{"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"},
|
|
126
|
+
"CartItem"=>
|
|
127
|
+
{"CartItemId"=>"U3G241HVLLB8N6",
|
|
128
|
+
"ASIN"=>"1430218150",
|
|
129
|
+
"MerchantId"=>"ATVPDKIKX0DER",
|
|
130
|
+
"SellerId"=>"A2R2RITDJNW1Q6",
|
|
131
|
+
"SellerNickname"=>"Amazon.com",
|
|
132
|
+
"Quantity"=>"1",
|
|
133
|
+
"Title"=>"Learn Objective-C on the Mac (Learn Series)",
|
|
134
|
+
"ProductGroup"=>"Book",
|
|
135
|
+
"Price"=>
|
|
136
|
+
{"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"},
|
|
137
|
+
"ItemTotal"=>
|
|
138
|
+
{"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"}}}}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "should handle response data" do
|
|
142
|
+
cart = Cart.new(@two_items)
|
|
143
|
+
cart.valid?.should be(true)
|
|
144
|
+
cart.cart_id.should eql('186-8702292-9782208')
|
|
145
|
+
cart.hmac.should eql('Ck5MXUE+OQiC/Jh8u6NhBf5FbV8=')
|
|
146
|
+
cart.url.should eql('https://www.amazon.com/gp/cart/aws-merge.html?cart-id=186-8702292-9782208%26associate-id=ws%26hmac=Ck5MXUE%2BOQiC/Jh8u6NhBf5FbV8=%26SubscriptionId=AKIAJFA5X7RTOKFNPVZQ%26MergeCart=False')
|
|
147
|
+
cart.price.should eql('$65.95')
|
|
148
|
+
cart.items.first.CartItemId eql('U3G241HVLLB8N6')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "should handle one item" do
|
|
152
|
+
cart = Cart.new(@two_items)
|
|
153
|
+
cart.items.first.CartItemId eql('U3G241HVLLB8N6')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/spec/search_spec.rb
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module ASIN
|
|
4
|
+
describe ASIN do
|
|
5
|
+
before do
|
|
6
|
+
ASIN::Configuration.reset
|
|
7
|
+
@helper = ASIN.client
|
|
8
|
+
@helper.configure :logger => nil
|
|
9
|
+
|
|
10
|
+
@secret = ENV['ASIN_SECRET']
|
|
11
|
+
@key = ENV['ASIN_KEY']
|
|
12
|
+
puts "configure #{@secret} and #{@key} for this test"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context "configuration" do
|
|
16
|
+
it "should fail without secret and key" do
|
|
17
|
+
lambda { @helper.lookup 'bla' }.should raise_error(RuntimeError)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should fail with wrong configuration key" do
|
|
21
|
+
lambda { @helper.configure :wrong => 'key' }.should raise_error(NoMethodError)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should not override the configuration" do
|
|
25
|
+
config = @helper.configure :key => 'wont get overridden'
|
|
26
|
+
config.key.should_not be_nil
|
|
27
|
+
|
|
28
|
+
config = @helper.configure :secret => 'is also set'
|
|
29
|
+
config.key.should_not be_nil
|
|
30
|
+
config.secret.should_not be_nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should work with a configuration block" do
|
|
34
|
+
conf = ASIN::Configuration.configure do |config|
|
|
35
|
+
config.key = 'bla'
|
|
36
|
+
end
|
|
37
|
+
conf.key.should eql('bla')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should read configuration from yml" do
|
|
41
|
+
config = ASIN::Configuration.configure :yaml => 'spec/asin.yml'
|
|
42
|
+
config.secret.should eql('secret_yml')
|
|
43
|
+
config.key.should eql('key_yml')
|
|
44
|
+
config.host.should eql('host_yml')
|
|
45
|
+
config.logger.should eql('logger_yml')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "should read configuration from yml with block" do
|
|
49
|
+
conf = ASIN::Configuration.configure :yaml => 'spec/asin.yml' do |config, yml|
|
|
50
|
+
config.secret = nil
|
|
51
|
+
config.key = yml['secret']
|
|
52
|
+
end
|
|
53
|
+
conf.secret.should be_nil
|
|
54
|
+
conf.key.should eql('secret_yml')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context "lookup and search" do
|
|
59
|
+
before do
|
|
60
|
+
@helper.configure :secret => @secret, :key => @key
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "should lookup a book" do
|
|
64
|
+
item = @helper.lookup(ANY_ASIN)
|
|
65
|
+
item.title.should =~ /Learn Objective/
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "should search_keywords a book with fulltext" do
|
|
69
|
+
items = @helper.search_keywords 'Learn', 'Objective-C'
|
|
70
|
+
items.should have(10).things
|
|
71
|
+
|
|
72
|
+
items.first.title.should =~ /Learn Objective/
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should search_keywords never mind music" do
|
|
76
|
+
items = @helper.search_keywords 'nirvana', 'never mind', :SearchIndex => :Music
|
|
77
|
+
items.should have(9).things
|
|
78
|
+
|
|
79
|
+
items.first.title.should =~ /Nevermind/
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "should search music" do
|
|
83
|
+
items = @helper.search :SearchIndex => :Music
|
|
84
|
+
items.should have(0).things
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "should search never mind music" do
|
|
88
|
+
items = @helper.search :Keywords => 'nirvana', :SearchIndex => :Music
|
|
89
|
+
items.should have(10).things
|
|
90
|
+
|
|
91
|
+
items.first.title.should =~ /Nevermind/
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
prerelease:
|
|
5
|
-
version: 0.
|
|
4
|
+
prerelease: 6
|
|
5
|
+
version: 0.4.0.beta1
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
8
8
|
- "Peter Schr\xC3\xB6der"
|
|
@@ -10,7 +10,7 @@ autorequire:
|
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
12
|
|
|
13
|
-
date: 2011-
|
|
13
|
+
date: 2011-04-12 00:00:00 +02:00
|
|
14
14
|
default_executable:
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
@@ -43,7 +43,7 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - ~>
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.
|
|
46
|
+
version: 0.9.2
|
|
47
47
|
type: :runtime
|
|
48
48
|
version_requirements: *id003
|
|
49
49
|
- !ruby/object:Gem::Dependency
|
|
@@ -54,7 +54,7 @@ dependencies:
|
|
|
54
54
|
requirements:
|
|
55
55
|
- - ~>
|
|
56
56
|
- !ruby/object:Gem::Version
|
|
57
|
-
version: 2.1.
|
|
57
|
+
version: 2.1.7.2
|
|
58
58
|
type: :development
|
|
59
59
|
version_requirements: *id004
|
|
60
60
|
- !ruby/object:Gem::Dependency
|
|
@@ -65,7 +65,7 @@ dependencies:
|
|
|
65
65
|
requirements:
|
|
66
66
|
- - ~>
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 2.
|
|
68
|
+
version: 2.5.0
|
|
69
69
|
type: :development
|
|
70
70
|
version_requirements: *id005
|
|
71
71
|
- !ruby/object:Gem::Dependency
|
|
@@ -79,7 +79,7 @@ dependencies:
|
|
|
79
79
|
version: 0.0.3
|
|
80
80
|
type: :development
|
|
81
81
|
version_requirements: *id006
|
|
82
|
-
description: Amazon Simple INterface
|
|
82
|
+
description: Amazon Simple INterface.
|
|
83
83
|
email:
|
|
84
84
|
- phoetmail@googlemail.com
|
|
85
85
|
executables: []
|
|
@@ -92,17 +92,19 @@ files:
|
|
|
92
92
|
- .document
|
|
93
93
|
- .gitignore
|
|
94
94
|
- .rvmrc
|
|
95
|
-
- .travis.yml
|
|
96
95
|
- Gemfile
|
|
97
96
|
- Gemfile.lock
|
|
98
97
|
- README.rdoc
|
|
99
98
|
- asin.gemspec
|
|
100
99
|
- lib/asin.rb
|
|
100
|
+
- lib/asin/cart.rb
|
|
101
101
|
- lib/asin/configuration.rb
|
|
102
102
|
- lib/asin/item.rb
|
|
103
103
|
- lib/asin/version.rb
|
|
104
104
|
- rakefile.rb
|
|
105
|
-
- spec/
|
|
105
|
+
- spec/asin.yml
|
|
106
|
+
- spec/cart_spec.rb
|
|
107
|
+
- spec/search_spec.rb
|
|
106
108
|
- spec/spec_helper.rb
|
|
107
109
|
has_rdoc: true
|
|
108
110
|
homepage: http://github.com/phoet/asin
|
|
@@ -122,16 +124,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
122
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
125
|
none: false
|
|
124
126
|
requirements:
|
|
125
|
-
- - "
|
|
127
|
+
- - ">"
|
|
126
128
|
- !ruby/object:Gem::Version
|
|
127
|
-
version:
|
|
129
|
+
version: 1.3.1
|
|
128
130
|
requirements: []
|
|
129
131
|
|
|
130
132
|
rubyforge_project: asin
|
|
131
133
|
rubygems_version: 1.5.2
|
|
132
134
|
signing_key:
|
|
133
135
|
specification_version: 3
|
|
134
|
-
summary: Simple interface to
|
|
136
|
+
summary: Simple interface to AWS Lookup, Search and Cart operations.
|
|
135
137
|
test_files:
|
|
136
|
-
- spec/
|
|
138
|
+
- spec/asin.yml
|
|
139
|
+
- spec/cart_spec.rb
|
|
140
|
+
- spec/search_spec.rb
|
|
137
141
|
- spec/spec_helper.rb
|
data/.travis.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
script: "rake travis_ci"
|
data/spec/asin_spec.rb
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
describe ASIN do
|
|
2
|
-
before do
|
|
3
|
-
@helper = ASIN.client
|
|
4
|
-
@helper.configure :logger => nil
|
|
5
|
-
|
|
6
|
-
@secret = ENV['ASIN_SECRET']
|
|
7
|
-
@key = ENV['ASIN_KEY']
|
|
8
|
-
puts "configure #{@secret} and #{@key} for this test"
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
context "configuration" do
|
|
12
|
-
it "should fail without secret and key" do
|
|
13
|
-
lambda { @helper.lookup 'bla' }.should raise_error(RuntimeError)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
it "should fail with wrong configuration key" do
|
|
17
|
-
lambda { @helper.configure :wrong => 'key' }.should raise_error(NoMethodError)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
it "should not override the configuration" do
|
|
21
|
-
config = @helper.configure :key => 'wont get overridden'
|
|
22
|
-
config.key.should_not be_nil
|
|
23
|
-
|
|
24
|
-
config = @helper.configure :secret => 'is also set'
|
|
25
|
-
config.key.should_not be_nil
|
|
26
|
-
config.secret.should_not be_nil
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "should work with a configuration block" do
|
|
30
|
-
config = ASIN::Configuration.configure do |config|
|
|
31
|
-
config.key = 'bla'
|
|
32
|
-
end
|
|
33
|
-
config.key.should eql('bla')
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
context "lookup and search" do
|
|
38
|
-
before do
|
|
39
|
-
@helper.configure :secret => @secret, :key => @key
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
it "should lookup a book" do
|
|
43
|
-
item = @helper.lookup('1430218150')
|
|
44
|
-
item.title.should =~ /Learn Objective/
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "should search_keywords a book with fulltext" do
|
|
48
|
-
items = @helper.search_keywords 'Learn', 'Objective-C'
|
|
49
|
-
items.should have(10).things
|
|
50
|
-
|
|
51
|
-
items.first.title.should =~ /Learn Objective/
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "should search_keywords never mind music" do
|
|
55
|
-
items = @helper.search_keywords 'nirvana', 'never mind', :SearchIndex => :Music
|
|
56
|
-
items.should have(10).things
|
|
57
|
-
|
|
58
|
-
items.first.title.should =~ /Nevermind/
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it "should search music" do
|
|
62
|
-
items = @helper.search :SearchIndex => :Music
|
|
63
|
-
items.should have(0).things
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
it "should search never mind music" do
|
|
67
|
-
items = @helper.search :Keywords => 'nirvana', :SearchIndex => :Music
|
|
68
|
-
items.should have(10).things
|
|
69
|
-
|
|
70
|
-
items.first.title.should =~ /Nevermind/
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|