paapi 0.0.2 → 0.0.3
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.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/lib/paapi.rb +46 -3
- data/lib/paapi/aws_request.rb +47 -0
- data/lib/paapi/client.rb +21 -14
- data/lib/paapi/item.rb +113 -0
- data/lib/paapi/response.rb +33 -2
- data/lib/paapi/version.rb +1 -1
- data/paapi.gemspec +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4bac67043fb32a957f0b015eb468efbcc852b9e5285940ff152bdc012b4c751
|
4
|
+
data.tar.gz: e4f35c14a82bf615d95167b869cb7b73c69cc85e7f05bd0ef1c37a96556b5b78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5943daef94869da7e96ca4d1b1b35fdd98f58f90bcebd7a00c24e1b11baf32cc5488986104b2d8edc95637076630437aaa2a5467683a8ca219e8a2a31b99aed4
|
7
|
+
data.tar.gz: 7dd1fd49d4c63a6e6ff67d2881137f0c7bd5c0fe6e166a917929b3f08d69f1a31dbbf63d3d5720f75fce53f3ff67fe0b5b3d097858eff99c362e0731fed68aec
|
data/README.md
CHANGED
data/lib/paapi.rb
CHANGED
@@ -1,21 +1,27 @@
|
|
1
1
|
require 'paapi/version'
|
2
2
|
|
3
|
-
require 'paapi/locales'
|
4
3
|
require 'paapi/aws_request'
|
5
4
|
require 'paapi/client'
|
5
|
+
require 'paapi/item'
|
6
6
|
require 'paapi/response'
|
7
7
|
|
8
8
|
module Paapi
|
9
9
|
class Error < StandardError; end
|
10
10
|
class NotImplemented < StandardError; end
|
11
|
+
SEARCH_PARAMS = %i[keywords actor, artist, author, brand title].freeze
|
11
12
|
DEFAULT_PARTNER_TYPE = 'Associates'
|
12
13
|
DEFAULT_MARKET = :us
|
13
14
|
DEFAULT_RESOURCES = [
|
14
15
|
'Images.Primary.Large',
|
16
|
+
'ItemInfo.ByLineInfo',
|
15
17
|
'ItemInfo.ContentInfo',
|
18
|
+
'ItemInfo.ExternalIds',
|
19
|
+
'ItemInfo.Features',
|
20
|
+
'ItemInfo.ManufactureInfo',
|
16
21
|
'ItemInfo.ProductInfo',
|
22
|
+
'ItemInfo.TechnicalInfo', # Includes format when Kindle
|
17
23
|
'ItemInfo.Title',
|
18
|
-
'ItemInfo.
|
24
|
+
'ItemInfo.TradeInInfo',
|
19
25
|
'Offers.Listings.Availability.Message',
|
20
26
|
'Offers.Listings.Condition',
|
21
27
|
'Offers.Listings.Condition.SubCondition',
|
@@ -25,7 +31,39 @@ module Paapi
|
|
25
31
|
'Offers.Listings.MerchantInfo',
|
26
32
|
'Offers.Listings.Price',
|
27
33
|
'Offers.Listings.SavingBasis'
|
28
|
-
]
|
34
|
+
].freeze
|
35
|
+
|
36
|
+
Locale = Struct.new(:key, :name, :host, :region) do
|
37
|
+
def site
|
38
|
+
host.sub('webservices', 'www')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
MARKETPLACES = {
|
43
|
+
au: Locale.new(:au, 'Australia', 'webservices.amazon.com.au', 'us-west-2'),
|
44
|
+
br: Locale.new(:br, 'Brazil', 'webservices.amazon.com.br' 'us-east-1'),
|
45
|
+
ca: Locale.new(:ca, 'Canada', 'webservices.amazon.ca', 'us-east-1'),
|
46
|
+
fr: Locale.new(:fr, 'France', 'webservices.amazon.fr', 'eu-west-1'),
|
47
|
+
de: Locale.new(:de, 'Germany', 'webservices.amazon.de', 'eu-west-1'),
|
48
|
+
in: Locale.new(:in, 'India', 'webservices.amazon.in', 'eu-west-1'),
|
49
|
+
it: Locale.new(:it, 'Italy', 'webservices.amazon.it', 'eu-west-1'),
|
50
|
+
jp: Locale.new(:jp, 'Japan', 'webservices.amazon.co.jp', 'us-west-2'),
|
51
|
+
mx: Locale.new(:mx, 'Mexico', 'webservices.amazon.com.mx', 'us-east-1'),
|
52
|
+
es: Locale.new(:es, 'Spain', 'webservices.amazon.es', 'eu-west-1'),
|
53
|
+
tr: Locale.new(:tk, 'Turkey', 'webservices.amazon.com.tr', 'eu-west-1'),
|
54
|
+
ae: Locale.new(:ae, 'United Arab Emirates', 'webservices.amazon.ae', 'eu-west-1'),
|
55
|
+
uk: Locale.new(:uk, 'United Kingdom', 'webservices.amazon.co.uk', 'eu-west-1'),
|
56
|
+
us: Locale.new(:us, 'United States', 'webservices.amazon.com', 'us-east-1'),
|
57
|
+
}.freeze
|
58
|
+
|
59
|
+
Operation = Struct.new(:target_name, :endpoint_suffix, :http_method, :service )
|
60
|
+
|
61
|
+
OPERATIONS = {
|
62
|
+
get_browse_nodes: Operation.new( 'GetBrowseNodes', 'getbrowsenodes', 'POST', 'ProductAdvertisingAPI' ),
|
63
|
+
get_items: Operation.new( 'GetItems', 'getitems', 'POST', 'ProductAdvertisingAPI' ),
|
64
|
+
get_variations: Operation.new( 'GetVariations', 'getvariations', 'POST', 'ProductAdvertisingAPI' ),
|
65
|
+
search_items: Operation.new( 'SearchItems', 'searchitems', 'POST', 'ProductAdvertisingAPI' )
|
66
|
+
}.freeze
|
29
67
|
|
30
68
|
class << self
|
31
69
|
attr_accessor :access_key,
|
@@ -43,4 +81,9 @@ module Paapi
|
|
43
81
|
end
|
44
82
|
alias_method :config, :configure
|
45
83
|
end
|
84
|
+
|
85
|
+
def symbolize_keys(hash)
|
86
|
+
Hash[hash.map{|k,v| v.is_a?(Hash) ? [k.to_sym, symbolize_keys(v)] : [k.to_sym, v] }]
|
87
|
+
end
|
46
88
|
end
|
89
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'http'
|
2
|
+
require 'aws-sigv4'
|
3
|
+
|
4
|
+
# https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html
|
5
|
+
module Paapi
|
6
|
+
module AwsRequest
|
7
|
+
def do_request(op:, payload:)
|
8
|
+
raise ArguemntError unless Paapi::OPERATIONS.keys.include?(op)
|
9
|
+
|
10
|
+
operation = OPERATIONS[op]
|
11
|
+
|
12
|
+
headers = {
|
13
|
+
'X-Amz-Target' => "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.#{operation.target_name}",
|
14
|
+
'Content-Encoding' => 'amz-1.0',
|
15
|
+
}
|
16
|
+
|
17
|
+
default_payload = {
|
18
|
+
'PartnerTag' => partner_tag,
|
19
|
+
'PartnerType' => partner_type,
|
20
|
+
'Marketplace' => marketplace.site
|
21
|
+
}
|
22
|
+
|
23
|
+
payload = default_payload.merge(payload)
|
24
|
+
|
25
|
+
endpoint = "https://#{marketplace.host}/paapi5/#{operation.endpoint_suffix}"
|
26
|
+
|
27
|
+
signer = Aws::Sigv4::Signer.new(
|
28
|
+
service: operation.service,
|
29
|
+
region: marketplace.region,
|
30
|
+
access_key_id: access_key,
|
31
|
+
secret_access_key: secret_key,
|
32
|
+
http_method: operation.http_method,
|
33
|
+
endpoint: marketplace.host
|
34
|
+
)
|
35
|
+
|
36
|
+
signature = signer.sign_request(http_method: operation.http_method, url: endpoint, headers: headers, body: payload.to_json)
|
37
|
+
|
38
|
+
headers['Host'] = marketplace.host
|
39
|
+
headers['X-Amz-Date'] = signature.headers['x-amz-date']
|
40
|
+
headers['X-Amz-Content-Sha256']= signature.headers['x-amz-content-sha256']
|
41
|
+
headers['Authorization'] = signature.headers['authorization']
|
42
|
+
headers['Content-Type'] = 'application/json; charset=utf-8'
|
43
|
+
|
44
|
+
HTTP.headers(headers).post(endpoint, json: payload )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/paapi/client.rb
CHANGED
@@ -23,35 +23,38 @@ module Paapi
|
|
23
23
|
@partner_tag = partner_tag if !partner_tag.nil?
|
24
24
|
end
|
25
25
|
|
26
|
-
def market=(
|
27
|
-
@market =
|
26
|
+
def market=(a_market)
|
27
|
+
@market = a_market
|
28
28
|
@marketplace = MARKETPLACES[market.to_sym]
|
29
29
|
if !Paapi.partner_market.nil?
|
30
|
-
@partner_tag = Paapi.partner_market.dig(
|
30
|
+
@partner_tag = Paapi.partner_market.dig(a_market) || @partner_tag
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
def get_items(item_ids:, **options)
|
35
|
-
|
36
|
-
|
37
|
-
payload = { ItemIds: item_ids, Resources: @resources }
|
35
|
+
payload = { ItemIds: Array(item_ids), Resources: @resources }
|
38
36
|
|
39
37
|
res = do_request(op: :get_items, payload: payload)
|
40
38
|
|
41
|
-
|
39
|
+
# Errors, ItemResults -> Items -> Item
|
40
|
+
return Response.new(res)
|
42
41
|
end
|
43
42
|
|
44
43
|
def get_variations(asin:, **options )
|
45
44
|
payload = { ASIN: asin, Resources: @resources }
|
46
45
|
|
47
46
|
res = do_request(op: :get_variations, payload: payload)
|
48
|
-
|
47
|
+
|
48
|
+
# Errors, VariationsResult->Items
|
49
|
+
Response.new(res)
|
49
50
|
end
|
50
51
|
|
51
|
-
def search_items(keywords:, **options )
|
52
|
-
search_index = 'All'
|
53
52
|
|
54
|
-
|
53
|
+
# TODO: Currently we assume Keywords, but we need one of the follow: [Keywords Actor Artist Author Brand Title ]
|
54
|
+
def search_items(keywords: nil, **options )
|
55
|
+
raise ArgumentError("Missing keywords") unless (options.keys | SEARCH_PARAMS).length.positive?
|
56
|
+
|
57
|
+
search_index = options.dig(:SearchIndex) ||'All'
|
55
58
|
|
56
59
|
payload = { Keywords: keywords, Resources: @resources, ItemCount: 10, ItemPage: 1, SearchIndex: search_index }.merge(options)
|
57
60
|
|
@@ -60,9 +63,13 @@ module Paapi
|
|
60
63
|
Response.new(res)
|
61
64
|
end
|
62
65
|
|
63
|
-
def get_browse_nodes(
|
64
|
-
|
65
|
-
|
66
|
+
def get_browse_nodes(browse_node_ids:, **options)
|
67
|
+
resources = ['BrowseNodes.Ancestor', 'BrowseNodes.Children']
|
68
|
+
payload = { BrowseNodeIds: Array(browse_node_ids), Resources: @resources }.merge(options)
|
69
|
+
|
70
|
+
res = do_request(op: :get_browse_nodes, payload: payload)
|
71
|
+
|
72
|
+
Response.new(res)
|
66
73
|
end
|
67
74
|
end
|
68
75
|
end
|
data/lib/paapi/item.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'nameable'
|
2
|
+
|
3
|
+
module Paapi
|
4
|
+
class Item
|
5
|
+
attr_accessor :raw
|
6
|
+
def initialize(data)
|
7
|
+
@raw = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def asin
|
11
|
+
get(['ASIN'])
|
12
|
+
end
|
13
|
+
|
14
|
+
def detail_url
|
15
|
+
get(['DetailPageURL'])
|
16
|
+
end
|
17
|
+
|
18
|
+
def image_url
|
19
|
+
get(%w{Images Primary Large Height URL})
|
20
|
+
end
|
21
|
+
|
22
|
+
def title
|
23
|
+
get(%w{ItemInfo Title DisplayValue})
|
24
|
+
end
|
25
|
+
|
26
|
+
def manufacturer
|
27
|
+
get(%w{ItemInfo ByLineInfo Manufacturer DisplayValue})
|
28
|
+
end
|
29
|
+
|
30
|
+
def publisher
|
31
|
+
manufacturer
|
32
|
+
end
|
33
|
+
|
34
|
+
def publication_date
|
35
|
+
d = get(%w{ItemInfo ContentInfo PublicationDate DisplayValue})
|
36
|
+
return d.nil? ? nil : Date.parse(d)
|
37
|
+
end
|
38
|
+
|
39
|
+
def release_date
|
40
|
+
d = get(%w{ItemInfo ProductInfo ReleaseDate DisplayValue})
|
41
|
+
return d.nil? ? nil : Date.parse(d)
|
42
|
+
end
|
43
|
+
|
44
|
+
def contributors
|
45
|
+
get(%w{ItemInfo ByLineInfo Contributors})
|
46
|
+
end
|
47
|
+
|
48
|
+
def contributors_of(kind)
|
49
|
+
contributors&.select { |e| e['Role'] == kind }&.map { |e| Nameable(e['Name'])}
|
50
|
+
end
|
51
|
+
|
52
|
+
def authors
|
53
|
+
contributors_of 'Author'
|
54
|
+
end
|
55
|
+
|
56
|
+
def illustrators
|
57
|
+
contributors_of 'Illustrator'
|
58
|
+
end
|
59
|
+
|
60
|
+
def actors
|
61
|
+
contributors_of 'Actor'
|
62
|
+
end
|
63
|
+
|
64
|
+
def narrators
|
65
|
+
contributors_of 'Narrator'
|
66
|
+
end
|
67
|
+
|
68
|
+
def publishers
|
69
|
+
contributors_of 'Publisher'
|
70
|
+
end
|
71
|
+
|
72
|
+
def release_date
|
73
|
+
get(%w{ItemInfo ReleaseDate})
|
74
|
+
end
|
75
|
+
|
76
|
+
def eans
|
77
|
+
get(%w{ItemInfo ExternalIds EANs DisplayValues})
|
78
|
+
end
|
79
|
+
|
80
|
+
def upcs
|
81
|
+
get(%w{ItemInfo ExternalIds UPCs DisplayValues})
|
82
|
+
end
|
83
|
+
|
84
|
+
def features
|
85
|
+
get(%w{ItemInfo Features DisplayValues})&.join(' ')
|
86
|
+
end
|
87
|
+
|
88
|
+
def brand
|
89
|
+
get(%w{ItemInfo ByLineInfo Brand DisplayValue})
|
90
|
+
end
|
91
|
+
|
92
|
+
def part_number
|
93
|
+
get(%w{ItemInfo ManufactureInfo ItemPartNumber DisplayValue})
|
94
|
+
end
|
95
|
+
|
96
|
+
def package
|
97
|
+
get(%w{ItemInfo TechnicalInfo Formats DisplayValues})
|
98
|
+
end
|
99
|
+
|
100
|
+
def kindle?
|
101
|
+
!package.nil? && package&.include?('Kindle eBook')
|
102
|
+
end
|
103
|
+
|
104
|
+
def get(keys)
|
105
|
+
@raw.dig(*keys)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.to_items(data)
|
109
|
+
data.map {|d| Item.new(d)}
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
data/lib/paapi/response.rb
CHANGED
@@ -2,10 +2,41 @@ require 'json'
|
|
2
2
|
|
3
3
|
module Paapi
|
4
4
|
class Response
|
5
|
-
attr_reader :http_response, :data
|
5
|
+
attr_reader :http_response, :data, :datas, :doc, :items
|
6
|
+
|
6
7
|
def initialize(response)
|
7
8
|
@http_response = response
|
8
|
-
@data = JSON.parse(
|
9
|
+
@data = JSON.parse(response.body.to_s)
|
10
|
+
|
11
|
+
@datas = symbolise(JSON.parse(response.body.to_s))
|
12
|
+
@doc = JSON.parse(@datas.to_json, object_class: OpenStruct)
|
13
|
+
|
14
|
+
@items_data = @data.dig('ItemsResult', 'Items')
|
15
|
+
@items_data ||= @data.dig('SearchResult', 'Items')
|
16
|
+
@items_data ||= []
|
17
|
+
|
18
|
+
@items = @items_data.map {|d| Item.new(d)}
|
19
|
+
|
9
20
|
end
|
21
|
+
|
22
|
+
def snake_case(s)
|
23
|
+
return s.downcase if s.match(/\A[A-Z]+\z/)
|
24
|
+
s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
25
|
+
gsub(/([a-z])([A-Z])/, '\1_\2').
|
26
|
+
downcase
|
27
|
+
end
|
28
|
+
|
29
|
+
def symbolise(obj)
|
30
|
+
if obj.is_a? Hash
|
31
|
+
return obj.inject({}) do |memo, (k, v)|
|
32
|
+
memo.tap { |m| m[snake_case(k)] = symbolise(v) }
|
33
|
+
end
|
34
|
+
elsif obj.is_a? Array
|
35
|
+
return obj.map { |memo| symbolise(memo) }
|
36
|
+
end
|
37
|
+
obj
|
38
|
+
end
|
39
|
+
|
40
|
+
|
10
41
|
end
|
11
42
|
end
|
data/lib/paapi/version.rb
CHANGED
data/paapi.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Milne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '1'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: nameable
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1'
|
111
125
|
description:
|
112
126
|
email:
|
113
127
|
- d@nmilne.com
|
@@ -124,7 +138,9 @@ files:
|
|
124
138
|
- bin/console
|
125
139
|
- bin/setup
|
126
140
|
- lib/paapi.rb
|
141
|
+
- lib/paapi/aws_request.rb
|
127
142
|
- lib/paapi/client.rb
|
143
|
+
- lib/paapi/item.rb
|
128
144
|
- lib/paapi/response.rb
|
129
145
|
- lib/paapi/version.rb
|
130
146
|
- paapi.gemspec
|