amazonian 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.rdoc +16 -8
- data/lib/amazonian.rb +123 -38
- data/lib/amazonian/version.rb +1 -1
- metadata +4 -4
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
== Status
|
2
|
-
This project is in active development as of 2010-
|
3
|
-
|
4
|
-
Currently it can be used as a rails plugin, and maybe as a gem (untested).
|
2
|
+
This project is in active development as of 2010-12.
|
5
3
|
|
6
4
|
== Installation
|
7
|
-
|
8
5
|
Copy or symlink into your application's /lib folder.
|
9
6
|
|
10
7
|
In your ApplicationController:
|
@@ -16,20 +13,31 @@ In your ApplicationController:
|
|
16
13
|
Then create an initializer in config/initializers:
|
17
14
|
require 'amazonian'
|
18
15
|
Amazonian.setup do |config|
|
19
|
-
|
20
|
-
config.
|
21
|
-
|
16
|
+
#Put your Amazon Web Services Key and Secret Key in here
|
17
|
+
config.secret = '<>'
|
18
|
+
config.key = '<>'
|
19
|
+
|
20
|
+
# prints more debug info to the console. Default: false
|
21
|
+
# config.debug = true
|
22
|
+
|
23
|
+
# the default default_search is :All, other tempting options: :Music, :GourmetFood, etc
|
24
|
+
config.default_search = :Music
|
25
|
+
|
26
|
+
# the gem will attempt to filter out duplicate requests. Default: true
|
27
|
+
config.cache_last = true
|
22
28
|
end
|
23
29
|
|
30
|
+
|
24
31
|
== Usage
|
25
32
|
Amazonian.asin "1430218150"
|
26
33
|
#=> #<Amazonian::Item>
|
27
34
|
|
28
|
-
#soon:
|
29
35
|
Amazonian.search "The art of Computer Programming" #Title
|
30
36
|
Amazonian.search "978-0201853926" #ISBN-13
|
31
37
|
Amazonian.search "0201853922" #ISBN-10
|
32
38
|
Amazonian.search "<whateveryouwant>" #Anything you might put in the search box on Amazon.com.
|
39
|
+
#=> #<Amazonian::Search>
|
40
|
+
|
33
41
|
|
34
42
|
== Info
|
35
43
|
|
data/lib/amazonian.rb
CHANGED
@@ -2,48 +2,84 @@ require 'patron'
|
|
2
2
|
require 'crack'
|
3
3
|
require 'hashie'
|
4
4
|
|
5
|
+
#
|
6
|
+
# This module is designed to allow easier querying of the Amazon Product Advertising API
|
7
|
+
# from within your Ruby or Rails applications.
|
8
|
+
#
|
9
|
+
# Basic usage requires first calling +Amazonian.setup+ to provide your Amazon AWS Key and
|
10
|
+
# Secret key for the module to use in querying the database.
|
11
|
+
#
|
12
|
+
# Amazons internal product ids can be used to retrieve data from the API with +Amazonian.asin+.
|
13
|
+
#
|
14
|
+
# Searching for products is done via +Amazonian.search+
|
15
|
+
#
|
16
|
+
# @author Robert L. Carpenter (Modern codebase, interface, gem-ability)
|
17
|
+
# @author phoet (Original ASIN interface and implementation of Request Signing, etc.) https://github.com/phoet/asin
|
18
|
+
#
|
19
|
+
# @api Amazon Web Services Product Advertising API
|
20
|
+
#
|
21
|
+
# @version 0.2.0
|
22
|
+
#
|
5
23
|
module Amazonian
|
24
|
+
|
6
25
|
#worker objects
|
7
26
|
@@digest = OpenSSL::Digest::Digest.new('sha256')
|
8
27
|
@@logger = Logger.new(STDERR)
|
9
28
|
@@patron = Patron::Session.new
|
10
29
|
|
11
30
|
#hold the most recent request/response
|
12
|
-
|
13
|
-
|
14
|
-
|
31
|
+
@request = nil
|
32
|
+
@response = nil
|
33
|
+
@query = nil
|
15
34
|
|
16
35
|
#configuration variables
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
36
|
+
@host = 'webservices.amazon.com'
|
37
|
+
@path = '/onca/xml'
|
38
|
+
@debug = true
|
39
|
+
@key = ''
|
40
|
+
@secret = ''
|
41
|
+
@default_search = :All
|
42
|
+
@cache_last = true
|
22
43
|
|
23
44
|
mattr_reader :host,:path,:response,:request
|
24
|
-
mattr_accessor :key,:secret,:debug
|
45
|
+
mattr_accessor :key,:secret,:debug,:default_search,:cache_last
|
25
46
|
|
26
47
|
#Configure Patron
|
27
48
|
@@patron.timeout = 10
|
28
49
|
|
50
|
+
#
|
29
51
|
# Configure the basic request parameters for Amazonian.
|
52
|
+
#
|
53
|
+
# Pass in a block with 1 parameter and modify configuration
|
54
|
+
# variables from there.
|
55
|
+
#
|
56
|
+
# @yield [amazonian] Configuration code block.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# require 'Amazonian'
|
60
|
+
# Amazonian.setup do |ama|
|
61
|
+
# ama.key = "my awesome key for AWS"
|
62
|
+
# ama.secret = "super secret secret key for AWS"
|
63
|
+
# ama.debug = true
|
64
|
+
# ama.default_search = :Music
|
65
|
+
# ama.cache_last = false
|
66
|
+
# end
|
67
|
+
#
|
30
68
|
def self.setup
|
31
69
|
yield self if block_given?
|
32
70
|
end
|
33
71
|
|
34
|
-
# Performs an +ItemLookup+ REST call against the Amazon API.
|
35
|
-
#
|
36
|
-
# Expects an ASIN (Amazon Standard Identification Number) and returns an +Item+:
|
37
72
|
#
|
38
|
-
#
|
39
|
-
# item.title
|
40
|
-
# => "Learn Objective-C on the Mac (Learn Series)"
|
73
|
+
# Perform an ASIN (Amazon Standard Identification Number) lookup.
|
41
74
|
#
|
42
|
-
#
|
75
|
+
# @param [String] The ASIN with which to query the API.
|
76
|
+
# @param [Hash] Additional options to be passed to the API.
|
77
|
+
# @option params [Symbol] :Operation defaults to :ItemLookup
|
78
|
+
# @option params [Symbol] :ItemLookup defaults to the ASIN passed as param 1.
|
43
79
|
#
|
44
|
-
#
|
80
|
+
# @see For more information on the parameters the API accepts, see http://docs.amazonwebservices.com/AWSEcommerceService/4-0/
|
45
81
|
#
|
46
|
-
#
|
82
|
+
# @return [Amazonian::Item] Representing the response from the API
|
47
83
|
#
|
48
84
|
def self.asin(asin, params={})
|
49
85
|
params = params.merge :Operation => :ItemLookup, :ItemId => asin
|
@@ -51,61 +87,87 @@ module Amazonian
|
|
51
87
|
Item.new xml['ItemLookupResponse']['Items']['Item']
|
52
88
|
end
|
53
89
|
|
90
|
+
#
|
91
|
+
# Perform a search query to the API. This is basically the same thing as
|
92
|
+
# searching with the Amazon website.
|
93
|
+
#
|
94
|
+
# @param [String] The search query
|
95
|
+
# @param [Hash] Additional options to be passed to the API
|
96
|
+
# @option params [Symbol] :Operation defaults to :ItemSearch
|
97
|
+
# @option params [Symbol] :Keywords defaults to the passed search query
|
98
|
+
#
|
99
|
+
# @see For more information on the parameters the API accepts, see http://docs.amazonwebservices.com/AWSEcommerceService/4-0/
|
100
|
+
#
|
101
|
+
# @return [Amazonian::Search] Representing the response from the API. Items returned by the search query are represented as
|
102
|
+
# +Amazonian::Item+ inside +Amazonian::Search+
|
103
|
+
#
|
54
104
|
def self.search(query, params={})
|
55
105
|
params = params.merge :Operation => :ItemSearch,
|
56
106
|
:Keywords => query
|
57
107
|
|
58
|
-
params[:SearchIndex] =
|
108
|
+
params[:SearchIndex] = @default_search if params[:SearchIndex].nil?
|
59
109
|
|
60
110
|
xml = self.call params
|
61
111
|
Search.new xml['ItemSearchResponse']
|
62
112
|
end
|
63
113
|
|
64
|
-
|
114
|
+
private
|
65
115
|
|
116
|
+
#
|
117
|
+
# Director function. Builds out the Request, Signs it, Sends it off, and Parses its XML all via small helper functions.
|
118
|
+
#
|
119
|
+
# @param params [Hash] All of the parameters to be formatted into the API REST call.
|
120
|
+
# @return [Crack::XML] The Parsed XML
|
121
|
+
#
|
66
122
|
def self.call(params)
|
67
|
-
raise "Cannot call the Amazon API without key and secret key." if
|
123
|
+
raise "Cannot call the Amazon API without key and secret key." if @key.blank? || @secret.blank?
|
68
124
|
|
69
125
|
#get the signed query, and assemble the querystring
|
70
|
-
log :debug, "Started Amazonian request for params: #{params.map {|p| "#{p[0]}=>#{p[1]}" }.join ','}" if
|
126
|
+
log :debug, "Started Amazonian request for params: #{params.map {|p| "#{p[0]}=>#{p[1]}" }.join ','}" if @debug
|
71
127
|
|
72
128
|
#memoize the last request for faster API querying...
|
73
129
|
query = assemble_querystring params
|
74
|
-
if query ==
|
130
|
+
if @cache_last && query == @query && @response.body
|
75
131
|
log :debug, "MEMO'D! Shortcutting API call for dup request."
|
76
|
-
return Crack::XML.parse
|
132
|
+
return Crack::XML.parse @response.body
|
77
133
|
end
|
78
|
-
|
134
|
+
@query = query
|
79
135
|
|
80
136
|
#sign the query
|
81
137
|
signed = sign_query query
|
82
138
|
|
83
139
|
#assemble the full URL
|
84
|
-
|
140
|
+
@request = "http://#{@host}#{@path}?#{signed}"
|
85
141
|
|
86
142
|
#make the call
|
87
|
-
log :info, "performing rest call to '#{
|
88
|
-
|
143
|
+
log :info, "performing rest call to '#{@request}'" if @debug
|
144
|
+
@response = @@patron.get @request
|
89
145
|
|
90
|
-
log :debug, "Response Code: #{
|
146
|
+
log :debug, "Response Code: #{@response.status}" if @debug
|
91
147
|
|
92
148
|
#todo, this memo logic is broken....an error code is not always without a body
|
93
|
-
log :error, "Amazon API Error: #{
|
149
|
+
log :error, "Amazon API Error: #{@response.status}" if @response.status >= 400
|
94
150
|
|
95
151
|
#parse the response and return it
|
96
|
-
Crack::XML.parse
|
152
|
+
Crack::XML.parse @response.body
|
97
153
|
end
|
98
154
|
|
155
|
+
#
|
156
|
+
# Builds out a Query String from a hash of symbols.
|
157
|
+
#
|
99
158
|
def self.assemble_querystring(params)
|
100
159
|
# Nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
|
101
160
|
params[:Service] = :AWSECommerceService
|
102
|
-
params[:AWSAccessKeyId] =
|
161
|
+
params[:AWSAccessKeyId] = @key
|
103
162
|
|
104
163
|
# CGI escape each param
|
105
164
|
# signing needs to order the query alphabetically
|
106
165
|
params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
|
107
166
|
end
|
108
167
|
|
168
|
+
#
|
169
|
+
# Signs a query string
|
170
|
+
#
|
109
171
|
def self.sign_query(query)
|
110
172
|
#make a copy... fixme fixme I'm awkward
|
111
173
|
q = query.clone
|
@@ -114,27 +176,30 @@ module Amazonian
|
|
114
176
|
|
115
177
|
# Sign the entire get-request (not just the querystring)
|
116
178
|
# possible gotcha if Patron starts using more/different headers.
|
117
|
-
request_to_sign = "GET\n#{
|
179
|
+
request_to_sign = "GET\n#{@host}\n#{@path}\n#{q}"
|
118
180
|
|
119
181
|
"#{q}&Signature=#{sign_request request_to_sign}"
|
120
182
|
end
|
121
183
|
|
184
|
+
#
|
185
|
+
# Signs an entire request string
|
186
|
+
#
|
122
187
|
def self.sign_request(request_to_sign)
|
123
188
|
# Sign it.
|
124
|
-
hmac = OpenSSL::HMAC.digest(
|
189
|
+
hmac = OpenSSL::HMAC.digest(@digest, @secret, request_to_sign)
|
125
190
|
|
126
191
|
# Don't forget to remove the newline from base64
|
127
192
|
CGI.escape(Base64.encode64(hmac).chomp)
|
128
193
|
end
|
129
194
|
|
130
195
|
def self.log(severity, message)
|
131
|
-
|
196
|
+
@logger.send severity, message if @logger
|
132
197
|
end
|
133
198
|
|
134
199
|
|
135
200
|
# =Item
|
136
201
|
#
|
137
|
-
# The +Item+ class is
|
202
|
+
# The +Item+ class is used to neatly box up the Amazon REST API responses into an Object.
|
138
203
|
#
|
139
204
|
# A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
|
140
205
|
#
|
@@ -145,16 +210,36 @@ module Amazonian
|
|
145
210
|
end
|
146
211
|
|
147
212
|
def title
|
148
|
-
@raw.ItemAttributes.Title
|
213
|
+
if @raw.ItemAttributes && @raw.ItemAttributes.Title
|
214
|
+
@raw.ItemAttributes.Title
|
215
|
+
else
|
216
|
+
nil
|
217
|
+
end
|
149
218
|
end
|
150
219
|
end
|
151
220
|
|
221
|
+
# =Search
|
222
|
+
# The +Search+ class is used to neatly box up the Amazon REST API responses into Objects.
|
223
|
+
#
|
224
|
+
# When using Amazonian.search you should receive an Search object. Search also attempts
|
225
|
+
# to autoboxe all of the Items returned in the search results into an array of +Amazonian::Item+
|
226
|
+
# objects.
|
227
|
+
#
|
228
|
+
# A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
|
229
|
+
#
|
152
230
|
class Search
|
153
231
|
attr_reader :items
|
154
232
|
def initialize(hash)
|
155
233
|
@raw = Hashie::Mash.new(hash)
|
156
234
|
@items = []
|
157
|
-
|
235
|
+
|
236
|
+
if @raw.Items && @raw.Items.Item
|
237
|
+
if @raw.Items.TotalResults.to_i > 1
|
238
|
+
@raw.Items.Item.each {|i| @items.push Amazonian::Item.new(i) }
|
239
|
+
else
|
240
|
+
@items.push Amazonian::Item.new(@raw.Items.Item)
|
241
|
+
end
|
242
|
+
end
|
158
243
|
end
|
159
244
|
end
|
160
245
|
|
data/lib/amazonian/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amazonian
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Robert L. Carpenter
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-05 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|