forked from phoet/asin
/
asin.rb
171 lines (153 loc) · 5.48 KB
/
asin.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
require 'httpi'
require 'crack/xml'
require 'cgi'
require 'base64'
require 'logger'
require 'asin/item'
require 'asin/version'
require 'asin/configuration'
# ASIN (Amazon Simple INterface) is a gem for easy access of the Amazon E-Commerce-API.
# It is simple to configure and use. Since it's very small and flexible, it is easy to extend it to your needs.
#
# Author:: Peter Schröder (mailto:phoetmail@googlemail.com)
#
# ==Usage
#
# The ASIN module is designed as a mixin.
#
# require 'asin'
# include ASIN
#
# In order to use the Amazon API properly, you need to be a registered user (http://aws.amazon.com).
#
# The registration process will give you a +secret-key+ and an +access-key+ (AWSAccessKeyId).
#
# Both are needed to use ASIN:
#
# configure :secret => 'your-secret', :key => 'your-key'
#
# After configuring your environment you can call the +lookup+ method to retrieve an +Item+ via the
# Amazon Standard Identification Number (ASIN):
#
# item = lookup '1430218150'
# item.title
# => "Learn Objective-C on the Mac (Learn Series)"
#
# OR search with fulltext/ASIN/ISBN
#
# items = search 'Learn Objective-C'
# items.first.title
# => "Learn Objective-C on the Mac (Learn Series)"
#
# The +Item+ uses a Hashie::Mash as its internal data representation and you can get fetched data from it:
#
# item.raw.ItemAttributes.ListPrice.FormattedPrice
# => "$39.99"
#
# ==Further Configuration
#
# If you need more controll over the request that is sent to the
# Amazon API (http://docs.amazonwebservices.com/AWSEcommerceService/4-0/),
# you can override some defaults or add additional query-parameters to the REST calls:
#
# configure :host => 'webservices.amazon.de'
# lookup(asin, :ResponseGroup => :Medium)
#
module ASIN
DIGEST = OpenSSL::Digest::Digest.new('sha256')
PATH = '/onca/xml'
# Configures the basic request parameters for ASIN.
#
# Expects at least +secret+ and +key+ for the API call:
#
# configure :secret => 'your-secret', :key => 'your-key'
#
# ==== Options:
#
# [secret] the API secret key
# [key] the API access key
# [host] the host, which defaults to 'webservices.amazon.com'
# [logger] a different logger than logging to STDERR
#
def configure(options={})
Configuration.configure(options)
end
# Performs an +ItemLookup+ REST call against the Amazon API.
#
# Expects an ASIN (Amazon Standard Identification Number) and returns an +Item+:
#
# item = lookup '1430218150'
# item.title
# => "Learn Objective-C on the Mac (Learn Series)"
#
# ==== Options:
#
# Additional parameters for the API call like this:
#
# lookup(asin, :ResponseGroup => :Medium)
#
def lookup(asin, params={})
response = call(params.merge(:Operation => :ItemLookup, :ItemId => asin))
Item.new(response['ItemLookupResponse']['Items']['Item'])
end
# Performs an +ItemSearch+ REST call against the Amazon API.
#
# Expects a search-string which can be an ASIN (Amazon Standard Identification Number) and returns a list of +Items+:
#
# items = search 'Learn Objective-C'
# items.first.title
# => "Learn Objective-C on the Mac (Learn Series)"
#
# ==== Options:
#
# Additional parameters for the API call like this:
#
# search(asin, :SearchIndex => :Music)
#
# Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
#
def search(search_string, params={:SearchIndex => :Books})
response = call(params.merge(:Operation => :ItemSearch, :Keywords => search_string))
response['ItemSearchResponse']['Items']['Item'].map {|item| Item.new(item)}
end
private
def credentials_valid?
!Configuration.secret.nil? && !Configuration.key.nil?
end
def call(params)
raise "you have to configure ASIN: 'configure :secret => 'your-secret', :key => 'your-key''" unless credentials_valid?
log(:debug, "calling with params=#{params}")
signed = create_signed_query_string(params)
url = "http://#{Configuration.host}#{PATH}?#{signed}"
log(:info, "performing rest call to url='#{url}'")
response = HTTPI.get(url)
if response.code == 200
# force utf-8 chars, works only on 1.9 string
resp = response.body
resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
log(:debug, "got response='#{resp}'")
Crack::XML.parse(resp)
else
log(:error, "got response='#{response.body}'")
raise "request failed with response-code='#{response.code}'"
end
end
def create_signed_query_string(params)
# nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
params[:Service] = :AWSECommerceService
params[:AWSAccessKeyId] = Configuration.key
# utc timestamp needed for signing
params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
# signing needs to order the query alphabetically
query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
# yeah, you really need to sign the get-request not the query
request_to_sign = "GET\n#{Configuration.host}\n#{PATH}\n#{query}"
hmac = OpenSSL::HMAC.digest(DIGEST, Configuration.secret, request_to_sign)
# don't forget to remove the newline from base64
signature = CGI.escape(Base64.encode64(hmac).chomp)
"#{query}&Signature=#{signature}"
end
def log(severity, message)
Configuration.logger.send severity, message if Configuration.logger
end
end