rasin 0.7.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.
- data/.document +3 -0
- data/.gitignore +6 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.rdoc +49 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +54 -0
- data/README.rdoc +166 -0
- data/asin.gemspec +36 -0
- data/lib/asin.rb +8 -0
- data/lib/asin/client.rb +398 -0
- data/lib/asin/configuration.rb +105 -0
- data/lib/asin/simple_cart.rb +54 -0
- data/lib/asin/simple_item.rb +44 -0
- data/lib/asin/simple_node.rb +44 -0
- data/lib/asin/version.rb +3 -0
- data/rakefile.rb +11 -0
- data/spec/asin.yml +4 -0
- data/spec/browse_node_spec.rb +17 -0
- data/spec/cart_spec.rb +150 -0
- data/spec/cassettes/asin/asin_cart_with_an_existing_cart_should_add_items_to_a_cart.yml +68 -0
- data/spec/cassettes/asin/asin_cart_with_an_existing_cart_should_clear_a_cart.yml +66 -0
- data/spec/cassettes/asin/asin_cart_with_an_existing_cart_should_get_a_cart.yml +67 -0
- data/spec/cassettes/asin/asin_cart_with_an_existing_cart_should_update_a_cart.yml +70 -0
- data/spec/cassettes/asin/browse_node_should_lookup_a_browse_node.yml +38 -0
- data/spec/cassettes/asin/cart_should_create_a_cart.yml +36 -0
- data/spec/cassettes/asin/lookup_and_search_should_have_metadata.yml +81 -0
- data/spec/cassettes/asin/lookup_and_search_should_lookup_a_book.yml +81 -0
- data/spec/cassettes/asin/lookup_and_search_should_lookup_multiple_books.yml +131 -0
- data/spec/cassettes/asin/lookup_and_search_should_lookup_multiple_response_groups.yml +43 -0
- data/spec/cassettes/asin/lookup_and_search_should_return_a_custom_item_class.yml +81 -0
- data/spec/cassettes/asin/lookup_and_search_should_return_a_mash_value.yml +81 -0
- data/spec/cassettes/asin/lookup_and_search_should_return_a_rash_value.yml +81 -0
- data/spec/cassettes/asin/lookup_and_search_should_return_a_raw_value.yml +81 -0
- data/spec/cassettes/asin/lookup_and_search_should_search_keywords_a_book_with_fulltext.yml +538 -0
- data/spec/cassettes/asin/lookup_and_search_should_search_keywords_and_handle_a_single_result.yml +72 -0
- data/spec/cassettes/asin/lookup_and_search_should_search_keywords_never_mind_music.yml +119 -0
- data/spec/cassettes/asin/lookup_and_search_should_search_music.yml +37 -0
- data/spec/cassettes/asin/lookup_and_search_should_search_never_mind_music.yml +122 -0
- data/spec/config_spec.rb +53 -0
- data/spec/search_spec.rb +101 -0
- data/spec/spec_helper.rb +47 -0
- metadata +197 -0
data/.document
ADDED
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
== 0.7.0
|
2
|
+
|
3
|
+
* jruby compatible
|
4
|
+
* loosen gem dependencies
|
5
|
+
|
6
|
+
== 0.6.1
|
7
|
+
|
8
|
+
* fix error when passing nil to config values
|
9
|
+
|
10
|
+
== 0.6.0
|
11
|
+
|
12
|
+
* change lookup method - pull request https://github.com/phoet/asin/pull/8
|
13
|
+
|
14
|
+
== 0.5.1
|
15
|
+
|
16
|
+
* fix for https://github.com/phoet/asin/issues/7
|
17
|
+
|
18
|
+
== 0.5.0
|
19
|
+
|
20
|
+
* move client to own file
|
21
|
+
* use autoload
|
22
|
+
* support for Hashie::Rash
|
23
|
+
* new method browse_node
|
24
|
+
|
25
|
+
== 0.4.0
|
26
|
+
|
27
|
+
* add configuration option for item/cart class
|
28
|
+
* add more functionality to item class
|
29
|
+
|
30
|
+
== 0.4.0.beta1
|
31
|
+
|
32
|
+
* added cart operations
|
33
|
+
* added yml configuration
|
34
|
+
|
35
|
+
== 0.3.0
|
36
|
+
|
37
|
+
* add search_keywords method
|
38
|
+
* open up search method to be more flexible
|
39
|
+
|
40
|
+
== 0.2.0
|
41
|
+
|
42
|
+
* rails initializer configuration
|
43
|
+
* rpsec for tests
|
44
|
+
|
45
|
+
== 0.1.0
|
46
|
+
|
47
|
+
* add logger
|
48
|
+
* use HTTPI as HTTP-adapter
|
49
|
+
* use bundler for dependencies
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
asin (0.7.0)
|
5
|
+
crack (~> 0.3)
|
6
|
+
hashie (~> 1.1)
|
7
|
+
httpi (~> 0.9)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
addressable (2.2.6)
|
13
|
+
crack (0.3.1)
|
14
|
+
diff-lcs (1.1.3)
|
15
|
+
fuubar (0.0.6)
|
16
|
+
rspec (~> 2.0)
|
17
|
+
rspec-instafail (~> 0.1.8)
|
18
|
+
ruby-progressbar (~> 0.0.10)
|
19
|
+
hashie (1.1.0)
|
20
|
+
httpclient (2.2.3)
|
21
|
+
httpi (0.9.5)
|
22
|
+
rack
|
23
|
+
rack (1.4.1)
|
24
|
+
rake (0.9.2.2)
|
25
|
+
rash (0.3.1)
|
26
|
+
hashie (~> 1.1.0)
|
27
|
+
rspec (2.7.0)
|
28
|
+
rspec-core (~> 2.7.0)
|
29
|
+
rspec-expectations (~> 2.7.0)
|
30
|
+
rspec-mocks (~> 2.7.0)
|
31
|
+
rspec-core (2.7.1)
|
32
|
+
rspec-expectations (2.7.0)
|
33
|
+
diff-lcs (~> 1.1.2)
|
34
|
+
rspec-instafail (0.1.9)
|
35
|
+
rspec-mocks (2.7.0)
|
36
|
+
ruby-progressbar (0.0.10)
|
37
|
+
vcr (1.11.3)
|
38
|
+
webmock (1.7.7)
|
39
|
+
addressable (~> 2.2, > 2.2.5)
|
40
|
+
crack (>= 0.1.7)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
java
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
asin!
|
48
|
+
fuubar (~> 0.0.5)
|
49
|
+
httpclient (~> 2.2.3)
|
50
|
+
rake (~> 0.9.2.2)
|
51
|
+
rash (~> 0.3.1)
|
52
|
+
rspec (~> 2.7.0)
|
53
|
+
vcr (~> 1.11.3)
|
54
|
+
webmock (~> 1.7.7)
|
data/README.rdoc
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
== Infos
|
2
|
+
|
3
|
+
Status: http://stillmaintained.com/phoet/asin.png
|
4
|
+
Build: http://travis-ci.org/phoet/asin.png
|
5
|
+
|
6
|
+
ASIN is a simple, extensible wrapper for parts of the REST-API of Amazon Product Advertising API (aka Associates Web Service aka Amazon E-Commerce Service).
|
7
|
+
|
8
|
+
For more information on the REST calls, have a look at the whole Amazon E-Commerce-API[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html].
|
9
|
+
|
10
|
+
Have a look at the RDOC[http://rdoc.info/projects/phoet/asin] for this project, if you like browsing some docs.
|
11
|
+
|
12
|
+
The gem runs smoothly with Rails 3 and is tested against multiple rubies. See +.travis.yml+ for details.
|
13
|
+
|
14
|
+
== Installation
|
15
|
+
|
16
|
+
gem install asin
|
17
|
+
gem install httpclient # optional, see HTTPI
|
18
|
+
gem install rash # optional, see Response Configuration
|
19
|
+
|
20
|
+
or in your Gemfile:
|
21
|
+
|
22
|
+
gem 'asin'
|
23
|
+
gem 'httpclient' # optional, see HTTPI
|
24
|
+
gem 'rash' # optional, see Response Configuration
|
25
|
+
|
26
|
+
== Configuration
|
27
|
+
|
28
|
+
Rails style initializer (config/initializers/asin.rb):
|
29
|
+
|
30
|
+
ASIN::Configuration.configure do |config|
|
31
|
+
config.secret = 'your-secret'
|
32
|
+
config.key = 'your-key'
|
33
|
+
end
|
34
|
+
|
35
|
+
YAML style configuration:
|
36
|
+
|
37
|
+
ASIN::Configuration.configure :yaml => 'config/asin.yml'
|
38
|
+
|
39
|
+
Inline style configuration:
|
40
|
+
|
41
|
+
ASIN::Configuration.configure :secret => 'your-secret', :key => 'your-key'
|
42
|
+
# or
|
43
|
+
client.configure :secret => 'your-secret', :key => 'your-key'
|
44
|
+
|
45
|
+
Have a look at ASIN::Configuration class for all the details.
|
46
|
+
|
47
|
+
===API Changes
|
48
|
+
|
49
|
+
With the latest version of the Product Advertising API you need to include your associate_tag[https://affiliate-program.amazon.com/gp/advertising/api/detail/api-changes.html].
|
50
|
+
|
51
|
+
ASIN::Configuration.configure do |config|
|
52
|
+
config.secret = 'your-secret'
|
53
|
+
config.key = 'your-key'
|
54
|
+
config.associate_tag = 'your-tag'
|
55
|
+
end
|
56
|
+
|
57
|
+
== Usage
|
58
|
+
|
59
|
+
ASIN is designed as a module, so you can include it into any object you like:
|
60
|
+
|
61
|
+
# require and include
|
62
|
+
require 'asin'
|
63
|
+
include ASIN::Client
|
64
|
+
|
65
|
+
# lookup an ASIN
|
66
|
+
lookup '1430218150'
|
67
|
+
|
68
|
+
But you can also use the +instance+ method to get a proxy-object:
|
69
|
+
|
70
|
+
# just require
|
71
|
+
require 'asin'
|
72
|
+
|
73
|
+
# create an ASIN client
|
74
|
+
client = ASIN::Client.instance
|
75
|
+
|
76
|
+
# lookup an item with the amazon standard identification number (asin)
|
77
|
+
items = client.lookup '1430218150'
|
78
|
+
|
79
|
+
# have a look at the title of the item
|
80
|
+
items.first.title
|
81
|
+
=> Learn Objective-C on the Mac (Learn Series)
|
82
|
+
|
83
|
+
# search for any kind of stuff on amazon with keywords
|
84
|
+
items = search_keywords 'Learn', 'Objective-C'
|
85
|
+
items.first.title
|
86
|
+
=> "Learn Objective-C on the Mac (Learn Series)"
|
87
|
+
|
88
|
+
# search for any kind of stuff on amazon with custom parameters
|
89
|
+
search :Keywords => 'Learn Objective-C', :SearchIndex => :Books
|
90
|
+
items.first.title
|
91
|
+
=> "Learn Objective-C on the Mac (Learn Series)"
|
92
|
+
|
93
|
+
# access the internal data representation (Hashie::Mash)
|
94
|
+
item.raw.ItemAttributes.ListPrice.FormattedPrice
|
95
|
+
=> $39.99
|
96
|
+
|
97
|
+
There is an additional set of methods to support AWS cart operations:
|
98
|
+
|
99
|
+
client = ASIN::Client.instance
|
100
|
+
|
101
|
+
# create a cart with an item
|
102
|
+
cart = client.create_cart({:asin => '1430218150', :quantity => 1})
|
103
|
+
cart.items
|
104
|
+
=> [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
|
105
|
+
|
106
|
+
# get an already existing cart from a CartId and HMAC
|
107
|
+
cart = client.get_cart('176-9182855-2326919', 'KgeVCA0YJTbuN/7Ibakrk/KnHWA=')
|
108
|
+
cart.empty?
|
109
|
+
=> false
|
110
|
+
|
111
|
+
# clear everything from the cart
|
112
|
+
cart = client.clear_cart(cart)
|
113
|
+
cart.empty?
|
114
|
+
=> true
|
115
|
+
|
116
|
+
# add items to the cart
|
117
|
+
cart = client.add_items(cart, {:asin => '1430216263', :quantity => 2})
|
118
|
+
cart.empty?
|
119
|
+
=> false
|
120
|
+
|
121
|
+
# update items in the cart
|
122
|
+
cart = client.update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :action => :SaveForLater}, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
|
123
|
+
cart.saved_items
|
124
|
+
=> [<#Hashie::Mash ASIN="1430218150" CartItemId="U3G241HVLLB8N6" ... >]
|
125
|
+
|
126
|
+
It's also possible to access browse nodes:
|
127
|
+
|
128
|
+
client = ASIN::Client.instance
|
129
|
+
|
130
|
+
# create a cart with an item
|
131
|
+
node = client.browse_node('163357', :ResponseGroup => :TopSellers)
|
132
|
+
node.node_id
|
133
|
+
=> '163357'
|
134
|
+
node.name
|
135
|
+
=> 'Comedy'
|
136
|
+
|
137
|
+
== Response Configuration
|
138
|
+
|
139
|
+
ASIN is customizable in the way it returns Responses from Amazon.
|
140
|
+
By default it will return +SimpleItem+, +SimpleCart+ or +SimpleNode+ instances,
|
141
|
+
but you can override this behavior for using your custom Classes:
|
142
|
+
|
143
|
+
client.configure :item_type => YourItemClass
|
144
|
+
client.configure :cart_type => YourCartClass
|
145
|
+
client.configure :node_type => YourNodeClass
|
146
|
+
|
147
|
+
You can also use built-in +:raw+, +:mash+ or +:rash+ types.
|
148
|
+
Since +rash+ is an additional library, you need to add it to your gemfile if you want to use it:
|
149
|
+
|
150
|
+
gem 'rash'
|
151
|
+
|
152
|
+
== HTTPI
|
153
|
+
|
154
|
+
ASIN uses HTTPI[https://github.com/rubiii/httpi] as a HTTP-Client adapter.
|
155
|
+
See the HTTPI documentation for how to configure different clients or the logger.
|
156
|
+
As a default HTTPI uses _httpclient_ so you should add that dependency to your project:
|
157
|
+
|
158
|
+
gem 'httpclient'
|
159
|
+
|
160
|
+
|
161
|
+
== License
|
162
|
+
|
163
|
+
"THE BEER-WARE LICENSE" (Revision 42):
|
164
|
+
ps@nofail.de[mailto:ps@nofail.de] wrote this file. As long as you retain this notice you
|
165
|
+
can do whatever you want with this stuff. If we meet some day, and you think
|
166
|
+
this stuff is worth it, you can buy me a beer in return Peter Schröder
|
data/asin.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "asin/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rasin"
|
7
|
+
s.version = ASIN::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Peter Schröder']
|
10
|
+
s.email = ['phoetmail@googlemail.com']
|
11
|
+
s.homepage = 'http://github.com/phoet/asin'
|
12
|
+
s.summary = 'Simple interface to AWS Lookup, Search and Cart operations.'
|
13
|
+
s.description = 'Amazon Simple INterface.'
|
14
|
+
|
15
|
+
s.rubyforge_project = "asin"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency('crack', '~> 0.3')
|
23
|
+
s.add_dependency('hashie', '~> 1.1')
|
24
|
+
s.add_dependency('httpi', '~> 0.9')
|
25
|
+
|
26
|
+
s.add_runtime_dependency('jruby-openssl') if RUBY_PLATFORM == 'java'
|
27
|
+
|
28
|
+
s.add_development_dependency('httpclient', '~> 2.2.3')
|
29
|
+
s.add_development_dependency('rash', '~> 0.3.1')
|
30
|
+
|
31
|
+
s.add_development_dependency('rake', '~> 0.9.2.2')
|
32
|
+
s.add_development_dependency('vcr', '~> 1.11.3')
|
33
|
+
s.add_development_dependency('webmock', '~> 1.7.7')
|
34
|
+
s.add_development_dependency('rspec', '~> 2.7.0')
|
35
|
+
s.add_development_dependency('fuubar', '~> 0.0.5')
|
36
|
+
end
|
data/lib/asin.rb
ADDED
data/lib/asin/client.rb
ADDED
@@ -0,0 +1,398 @@
|
|
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.first.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/latest/DG/index.html),
|
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
|
+
# == Nodes
|
86
|
+
#
|
87
|
+
# In order to browse Amazon nodes, you can use +browse_node+ method:
|
88
|
+
#
|
89
|
+
# node = browse_node('163357')
|
90
|
+
# node.node_id
|
91
|
+
# => '163357'
|
92
|
+
# node.name
|
93
|
+
# => 'Comedy'
|
94
|
+
# node.children
|
95
|
+
# node.ancestors
|
96
|
+
#
|
97
|
+
# you can configure the +:ResponseGroup+ option to your needs:
|
98
|
+
#
|
99
|
+
# node = browse_node('163357', :ResponseGroup => :TopSellers)
|
100
|
+
#
|
101
|
+
module ASIN
|
102
|
+
module Client
|
103
|
+
|
104
|
+
DIGEST = OpenSSL::Digest::Digest.new('sha256')
|
105
|
+
PATH = '/onca/xml'
|
106
|
+
|
107
|
+
# Convenience method to create an ASIN client.
|
108
|
+
#
|
109
|
+
# An instance is not necessary though, you can simply include the ASIN module otherwise.
|
110
|
+
#
|
111
|
+
def self.instance
|
112
|
+
ins = Object.new
|
113
|
+
ins.extend ASIN::Client
|
114
|
+
ins
|
115
|
+
end
|
116
|
+
|
117
|
+
# Configures the basic request parameters for ASIN.
|
118
|
+
#
|
119
|
+
# Expects at least +secret+ and +key+ for the API call:
|
120
|
+
#
|
121
|
+
# configure :secret => 'your-secret', :key => 'your-key'
|
122
|
+
#
|
123
|
+
# See ASIN::Configuration for more infos.
|
124
|
+
#
|
125
|
+
def configure(options={})
|
126
|
+
Configuration.configure(options)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Performs an +ItemLookup+ REST call against the Amazon API.
|
130
|
+
#
|
131
|
+
# Expects an arbitrary number of ASIN (Amazon Standard Identification Number) and returns an array of +SimpleItem+:
|
132
|
+
#
|
133
|
+
# item = lookup '1430218150'
|
134
|
+
# item.title
|
135
|
+
# => "Learn Objective-C on the Mac (Learn Series)"
|
136
|
+
# items = lookup ['1430218150', '0439023521']
|
137
|
+
# items[0].title
|
138
|
+
# => "Learn Objective-C on the Mac (Learn Series)"
|
139
|
+
# items[1].title
|
140
|
+
# => "The Hunger Games"
|
141
|
+
#
|
142
|
+
# ==== Options:
|
143
|
+
#
|
144
|
+
# Additional parameters for the API call like this:
|
145
|
+
#
|
146
|
+
# lookup(asin, :ResponseGroup => :Medium)
|
147
|
+
#
|
148
|
+
# Or with multiple parameters:
|
149
|
+
#
|
150
|
+
# lookup(asin, :ResponseGroup => [:Small, :AlternateVersions])
|
151
|
+
#
|
152
|
+
def lookup(*asins)
|
153
|
+
params = asins.last.is_a?(Hash) ? asins.pop : {:ResponseGroup => :Medium}
|
154
|
+
response = call(params.merge(:Operation => :ItemLookup, :ItemId => asins.join(',')))
|
155
|
+
[arrayfy(response['ItemLookupResponse']['Items']['Item']).map {|item| handle_item(item)},response]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Performs an +ItemSearch+ REST call against the Amazon API.
|
159
|
+
#
|
160
|
+
# Expects a search-string which can be an arbitrary array of strings (ASINs f.e.) and returns a list of +SimpleItem+s:
|
161
|
+
#
|
162
|
+
# items = search_keywords 'Learn', 'Objective-C'
|
163
|
+
# items.first.title
|
164
|
+
# => "Learn Objective-C on the Mac (Learn Series)"
|
165
|
+
#
|
166
|
+
# ==== Options:
|
167
|
+
#
|
168
|
+
# Additional parameters for the API call like this:
|
169
|
+
#
|
170
|
+
# search_keywords('nirvana', 'never mind', :SearchIndex => :Music)
|
171
|
+
#
|
172
|
+
# Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html]
|
173
|
+
#
|
174
|
+
def search_keywords(*keywords)
|
175
|
+
params = keywords.last.is_a?(Hash) ? keywords.pop : {:SearchIndex => :Books, :ResponseGroup => :Medium}
|
176
|
+
response = call(params.merge(:Operation => :ItemSearch, :Keywords => keywords.join(' ')))
|
177
|
+
|
178
|
+
[arrayfy(response['ItemSearchResponse']['Items']['Item']).map {|item| handle_item(item)},response]
|
179
|
+
end
|
180
|
+
|
181
|
+
# Performs an +ItemSearch+ REST call against the Amazon API.
|
182
|
+
#
|
183
|
+
# Expects a Hash of search params where and returns a list of +SimpleItem+s:
|
184
|
+
#
|
185
|
+
# items = search :SearchIndex => :Music
|
186
|
+
#
|
187
|
+
# ==== Options:
|
188
|
+
#
|
189
|
+
# Additional parameters for the API call like this:
|
190
|
+
#
|
191
|
+
# search(:Keywords => 'nirvana', :SearchIndex => :Music)
|
192
|
+
#
|
193
|
+
# Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html]
|
194
|
+
#
|
195
|
+
def search(params={:SearchIndex => :Books, :ResponseGroup => :Medium})
|
196
|
+
response = call(params.merge(:Operation => :ItemSearch))
|
197
|
+
[arrayfy(response['ItemSearchResponse']['Items']['Item']).map {|item| handle_item(item)},response]
|
198
|
+
end
|
199
|
+
|
200
|
+
# Performs an +BrowseNodeLookup+ REST call against the Amazon API.
|
201
|
+
#
|
202
|
+
# Expects a Hash of search params where and returns a +SimpleNode+:
|
203
|
+
#
|
204
|
+
# node = browse_node '163357'
|
205
|
+
#
|
206
|
+
# ==== Options:
|
207
|
+
#
|
208
|
+
# Additional parameters for the API call like this:
|
209
|
+
#
|
210
|
+
# browse_node('163357', :ResponseGroup => :TopSellers)
|
211
|
+
#
|
212
|
+
# Have a look at the different browse node values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html]
|
213
|
+
#
|
214
|
+
def browse_node(node_id, params={:ResponseGroup => :BrowseNodeInfo})
|
215
|
+
response = call(params.merge(:Operation => :BrowseNodeLookup, :BrowseNodeId => node_id))
|
216
|
+
[handle_type(response['BrowseNodeLookupResponse']['BrowseNodes']['BrowseNode'], Configuration.node_type),response]
|
217
|
+
end
|
218
|
+
|
219
|
+
# Performs an +CartCreate+ REST call against the Amazon API.
|
220
|
+
#
|
221
|
+
# Expects one ore more item-hashes and returns a +SimpleCart+:
|
222
|
+
#
|
223
|
+
# cart = create_cart({:asin => '1430218150', :quantity => 1})
|
224
|
+
#
|
225
|
+
# ==== Options:
|
226
|
+
#
|
227
|
+
# Additional parameters for the API call like this:
|
228
|
+
#
|
229
|
+
# create_cart({:asin => '1430218150', :quantity => 1}, {:asin => '1430216263', :quantity => 1, :action => :SaveForLater})
|
230
|
+
#
|
231
|
+
# Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html]
|
232
|
+
#
|
233
|
+
def create_cart(*items)
|
234
|
+
cart(:CartCreate, create_item_params(items))
|
235
|
+
end
|
236
|
+
|
237
|
+
# Performs an +CartGet+ REST call against the Amazon API.
|
238
|
+
#
|
239
|
+
# Expects the CartId and the HMAC to identify the returning +SimpleCart+:
|
240
|
+
#
|
241
|
+
# cart = get_cart('176-9182855-2326919', 'KgeVCA0YJTbuN/7Ibakrk/KnHWA=')
|
242
|
+
#
|
243
|
+
def get_cart(cart_id, hmac)
|
244
|
+
cart(:CartGet, {:CartId => cart_id, :HMAC => hmac})
|
245
|
+
end
|
246
|
+
|
247
|
+
# Performs an +CartAdd+ REST call against the Amazon API.
|
248
|
+
#
|
249
|
+
# Expects a +SimpleCart+ created with +create_cart+ and one ore more Item-Hashes and returns an updated +SimpleCart+:
|
250
|
+
#
|
251
|
+
# cart = add_items(cart, {:asin => '1430216263', :quantity => 2})
|
252
|
+
#
|
253
|
+
# ==== Options:
|
254
|
+
#
|
255
|
+
# Additional parameters for the API call like this:
|
256
|
+
#
|
257
|
+
# add_items(cart, {:asin => '1430218150', :quantity => 1}, {:asin => '1430216263', :quantity => 1, :action => :SaveForLater})
|
258
|
+
#
|
259
|
+
# Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html]
|
260
|
+
#
|
261
|
+
def add_items(cart, *items)
|
262
|
+
cart(:CartAdd, create_item_params(items).merge({:CartId => cart.cart_id, :HMAC => cart.hmac}))
|
263
|
+
end
|
264
|
+
|
265
|
+
# Performs an +CartModify+ REST call against the Amazon API.
|
266
|
+
#
|
267
|
+
# Expects a +SimpleCart+ created with +create_cart+ and one ore more Item-Hashes to modify and returns an updated +SimpleCart+:
|
268
|
+
#
|
269
|
+
# cart = update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
|
270
|
+
#
|
271
|
+
# ==== Options:
|
272
|
+
#
|
273
|
+
# Additional parameters for the API call like this:
|
274
|
+
#
|
275
|
+
# update_items(cart, {:cart_item_id => cart.items.first.CartItemId, :action => :SaveForLater}, {:cart_item_id => cart.items.first.CartItemId, :quantity => 7})
|
276
|
+
#
|
277
|
+
# Have a look at the different cart item operation values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html]
|
278
|
+
#
|
279
|
+
def update_items(cart, *items)
|
280
|
+
cart(:CartModify, create_item_params(items).merge({:CartId => cart.cart_id, :HMAC => cart.hmac}))
|
281
|
+
end
|
282
|
+
|
283
|
+
# Performs an +CartClear+ REST call against the Amazon API.
|
284
|
+
#
|
285
|
+
# Expects a +SimpleCart+ created with +create_cart+ and returns an empty +SimpleCart+:
|
286
|
+
#
|
287
|
+
# cart = clear_cart(cart)
|
288
|
+
#
|
289
|
+
def clear_cart(cart)
|
290
|
+
cart(:CartClear, {:CartId => cart.cart_id, :HMAC => cart.hmac})
|
291
|
+
end
|
292
|
+
|
293
|
+
private()
|
294
|
+
|
295
|
+
def arrayfy(item)
|
296
|
+
return [] unless item
|
297
|
+
item.is_a?(Array) ? item : [item]
|
298
|
+
end
|
299
|
+
|
300
|
+
def handle_item(item)
|
301
|
+
handle_type(item, Configuration.item_type)
|
302
|
+
end
|
303
|
+
|
304
|
+
def handle_type(data, type)
|
305
|
+
if type.is_a?(Class)
|
306
|
+
type.new(data)
|
307
|
+
elsif type == :mash
|
308
|
+
require 'hashie'
|
309
|
+
Hashie::Mash.new(data)
|
310
|
+
elsif type == :rash
|
311
|
+
require 'rash'
|
312
|
+
Hashie::Rash.new(data)
|
313
|
+
else
|
314
|
+
data
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def create_item_params(items)
|
319
|
+
keyword_mappings = {
|
320
|
+
:asin => 'ASIN',
|
321
|
+
:quantity => 'Quantity',
|
322
|
+
:cart_item_id => 'CartItemId',
|
323
|
+
:offer_listing_id => 'OfferListingId',
|
324
|
+
:action => 'Action'
|
325
|
+
}
|
326
|
+
params = {}
|
327
|
+
items.each_with_index do |item, i|
|
328
|
+
item.each do |key, value|
|
329
|
+
next unless keyword = keyword_mappings[key]
|
330
|
+
params["Item.#{i}.#{keyword}"] = value.to_s
|
331
|
+
end
|
332
|
+
end
|
333
|
+
params
|
334
|
+
end
|
335
|
+
|
336
|
+
def cart(operation, params={})
|
337
|
+
response = call(params.merge(:Operation => operation))
|
338
|
+
cart = response["#{operation}Response"]['Cart']
|
339
|
+
handle_type(cart, Configuration.cart_type)
|
340
|
+
end
|
341
|
+
|
342
|
+
def call(params)
|
343
|
+
Configuration.validate_credentials!
|
344
|
+
params[:ResponseGroup] = params[:ResponseGroup].collect{|g| g.to_s.strip}.join(',') if !params[:ResponseGroup].nil? && params[:ResponseGroup].is_a?(Array)
|
345
|
+
log(:debug, "calling with params=#{params}")
|
346
|
+
signed = create_signed_query_string(params)
|
347
|
+
|
348
|
+
url = "http://#{Configuration.host}#{PATH}?#{signed}"
|
349
|
+
log(:info, "performing rest call to url='#{url}'")
|
350
|
+
|
351
|
+
response = HTTPI.get(url)
|
352
|
+
if response.code == 200
|
353
|
+
# force utf-8 chars, works only on 1.9 string
|
354
|
+
resp = response.body
|
355
|
+
resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
|
356
|
+
log(:debug, "got response='#{resp}'")
|
357
|
+
Crack::XML.parse(resp)
|
358
|
+
else
|
359
|
+
log(:error, "got response='#{response.body}'")
|
360
|
+
raise "request failed with response-code='#{response.code}'"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def create_signed_query_string(params)
|
365
|
+
# nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
|
366
|
+
params[:Service] = :AWSECommerceService
|
367
|
+
params[:AWSAccessKeyId] = Configuration.key
|
368
|
+
|
369
|
+
params[:Version] = Configuration.version unless Configuration.blank? :version
|
370
|
+
params[:AssociateTag] = Configuration.associate_tag unless Configuration.blank? :associate_tag
|
371
|
+
|
372
|
+
# utc timestamp needed for signing
|
373
|
+
params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
374
|
+
|
375
|
+
|
376
|
+
query = create_query(params)
|
377
|
+
|
378
|
+
# yeah, you really need to sign the get-request not the query
|
379
|
+
request_to_sign = "GET\n#{Configuration.host}\n#{PATH}\n#{query}"
|
380
|
+
hmac = OpenSSL::HMAC.digest(DIGEST, Configuration.secret, request_to_sign)
|
381
|
+
|
382
|
+
# don't forget to remove the newline from base64
|
383
|
+
signature = CGI.escape(Base64.encode64(hmac).chomp)
|
384
|
+
"#{query}&Signature=#{signature}"
|
385
|
+
end
|
386
|
+
|
387
|
+
def create_query(params)
|
388
|
+
params.map do |key, value|
|
389
|
+
value = value.collect{|v| v.to_s.strip}.join(',') if value.is_a?(Array)
|
390
|
+
"#{key}=#{CGI.escape(value.to_s)}"
|
391
|
+
end.sort.join('&').gsub('+','%20') # signing needs to order the query alphabetically
|
392
|
+
end
|
393
|
+
|
394
|
+
def log(severity, message)
|
395
|
+
Configuration.logger.send severity, message if Configuration.logger
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|