vacuum 4.3.0 → 5.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9defa7d4e94d0039667f94ede365982df2d9fac5a63be691a90e0efe3124aa1
4
- data.tar.gz: fc35f8f66b767c8f8a9951e83cd247611b00c221715ca633eb84916d6d9d294c
3
+ metadata.gz: 0ab3db70bf188be7e645a18cd7faa49836662625763031a72e213af37b246d78
4
+ data.tar.gz: 1558eea1b389f27c029efba19c9dd244d8c594eb056ed944e0016e4dc362dbd8
5
5
  SHA512:
6
- metadata.gz: e21de47a9ce2ab5fdfb290c7d429e884c9c41c09e19a276ae76a90b2157aa38f83e99538633f2c7cad26cdc85e595885f28654d6072dd308fd87cd793fb36633
7
- data.tar.gz: 619767b577fc2ecdcd04e4f034973a244d9b5f37319094df8fb58633c048c8644bce310fe5b16c0e27bb432ec7b7760b29689598b9417af3c37ca3c698d82685
6
+ metadata.gz: b677583ab6f8759042ce9e7805f1646e72af0b307e22b55a433c7f141b9ab4e21a17d4969a9741a503c1e5940fe6c4912abc3bf4b693538d8fa31f01874d7c20
7
+ data.tar.gz: 07fe15c4f43ab1735d0c6cd111cb94f6fb79a1c17ed2f33e1e94ea08e30630d78f52a50193f6945767171aede03d3be6393c3b5b7fa8d2adcf7196569f701e25
data/README.md CHANGED
@@ -1,14 +1,6 @@
1
1
  # Vacuum
2
2
 
3
- [![Build](https://github.com/hakanensari/vacuum/workflows/build/badge.svg)](https://github.com/hakanensari/vacuum/actions)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/ffbab4aa5fedf5780332/maintainability)](https://codeclimate.com/github/hakanensari/vacuum/maintainability)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/ffbab4aa5fedf5780332/test_coverage)](https://codeclimate.com/github/hakanensari/vacuum/test_coverage)
6
-
7
- Vacuum is a Ruby wrapper to [Amazon Product Advertising API 5.0](https://webservices.amazon.com/paapi5/documentation/). The API provides programmatic access to query product information on the Amazon marketplaces.
8
-
9
- Cart Form functionality is not covered by this gem but is a primary focus on [carriage gem](https://github.com/skatkov/carriage)
10
-
11
- You need to [register first](https://webservices.amazon.com/paapi5/documentation/register-for-pa-api.html) to use the API.
3
+ Vacuum is a Ruby wrapper to the [Amazon Creators API](https://affiliate-program.amazon.com/creatorsapi/docs/en-us/). The API provides programmatic access to product information on the Amazon marketplaces.
12
4
 
13
5
  ![vacuum](https://github.com/hakanensari/vacuum/blob/main/images/vacuum.jpg?raw=true)
14
6
 
@@ -16,40 +8,56 @@ You need to [register first](https://webservices.amazon.com/paapi5/documentation
16
8
 
17
9
  ### Getting Started
18
10
 
19
- Create a request with your marketplace credentials. Set the marketplace by passing its two-letter country code.
11
+ Create a client with your credentials.
20
12
 
21
13
  ```ruby
22
- request = Vacuum.new(marketplace: 'US',
23
- access_key: '<ACCESS_KEY>',
24
- secret_key: '<SECRET_KEY>',
25
- partner_tag: '<PARTNER_TAG>')
14
+ client = Vacuum.new(
15
+ credential_id: "YOUR_CREDENTIAL_ID",
16
+ credential_secret: "YOUR_CREDENTIAL_SECRET",
17
+ version: "2.1"
18
+ )
26
19
  ```
27
20
 
21
+ The client handles authentication automatically and caches access tokens for one hour.
22
+
28
23
  You can now access the API using the available operations.
29
24
 
30
25
  ```ruby
31
- response = request.search_items(title: 'lean startup')
32
- puts response.to_h
26
+ response = client.search_items(
27
+ marketplace: "www.amazon.com",
28
+ partner_tag: "yourtag-20",
29
+ keywords: "lean startup"
30
+ )
31
+ response.parse
33
32
  ```
34
33
 
35
- Create a persistent connection to make multiple requests.
34
+ ### Versions and Marketplaces
36
35
 
37
- ```ruby
38
- request.persistent
39
- ```
36
+ The `version` determines which authentication endpoint to use. Versions `2.x` use Amazon Cognito; versions `3.x` use Login with Amazon (LwA).
37
+
38
+ | Version | Region |
39
+ |---------|--------|
40
+ | `"2.1"` | North America |
41
+ | `"2.2"` | Europe |
42
+ | `"2.3"` | Far East |
43
+ | `"3.1"` | North America |
44
+ | `"3.2"` | Europe |
45
+ | `"3.3"` | Far East |
40
46
 
41
47
  ### Operations
42
48
 
43
- Refer to the [API docs](https://webservices.amazon.com/paapi5/documentation/) for more detailed information.
49
+ Each operation requires `marketplace` (e.g., `"www.amazon.com"`) and `partner_tag` parameters.
44
50
 
45
51
  #### GetBrowseNodes
46
52
 
47
- Given a BrowseNodeId, the `GetBrowseNodes` operation returns details about the specified browse node, like name, children and ancestors, depending on the resources specified in the request. The names and browse node IDs of the children and ancestor browse nodes are also returned. `GetBrowseNodes` enables you to traverse the browse node hierarchy to find a browse node.
53
+ Given a BrowseNodeId, the `GetBrowseNodes` operation returns details about the specified browse node, like name, children and ancestors, depending on the resources specified in the request.
48
54
 
49
55
  ```ruby
50
- request.get_browse_nodes(
51
- browse_node_ids: ['283155', '3040'],
52
- resources: ['BrowseNodes.Ancestor', 'BrowseNodes.Children']
56
+ client.get_browse_nodes(
57
+ marketplace: "www.amazon.com",
58
+ partner_tag: "yourtag-20",
59
+ browse_node_ids: ["283155", "3040"],
60
+ resources: ["browseNodes.ancestor", "browseNodes.children"]
53
61
  )
54
62
  ```
55
63
 
@@ -58,23 +66,27 @@ request.get_browse_nodes(
58
66
  Given an Item identifier, the `GetItems` operation returns the item attributes, based on the resources specified in the request.
59
67
 
60
68
  ```ruby
61
- request.get_items(
62
- item_ids: ['B0199980K4', 'B000HZD168', 'B01180YUXS', 'B00BKQTA4A'],
63
- resources: ['Images.Primary.Small', 'ItemInfo.Title', 'ItemInfo.Features',
64
- 'Offers.Summaries.HighestPrice' , 'ParentASIN']
69
+ client.get_items(
70
+ marketplace: "www.amazon.com",
71
+ partner_tag: "yourtag-20",
72
+ item_ids: ["B0199980K4", "B000HZD168"],
73
+ resources: ["images.primary.small", "itemInfo.title", "itemInfo.features",
74
+ "offersV2.listings.price", "parentASIN"]
65
75
  )
66
76
  ```
67
77
 
68
78
  #### GetVariations
69
79
 
70
- Given an ASIN, the `GetVariations` operation returns a set of items that are the same product, but differ according to a consistent theme, for example size and color. These items which differ according to a consistent theme are called variations. A variation is a child ASIN. The parent ASIN is an abstraction of the children items. For example, a shirt is a parent ASIN and parent ASINs cannot be sold. A child ASIN would be a blue shirt, size 16, sold by MyApparelStore. This child ASIN is one of potentially many variations. The ways in which variations differ are called dimensions.
80
+ Given an ASIN, the `GetVariations` operation returns a set of items that are the same product, but differ according to a consistent theme, for example size and color.
71
81
 
72
82
  ```ruby
73
- request.get_variations(
74
- asin: 'B00422MCUS',
75
- resources: ['ItemInfo.Title', 'VariationSummary.Price.HighestPrice',
76
- 'VariationSummary.Price.LowestPrice',
77
- 'VariationSummary.VariationDimension']
83
+ client.get_variations(
84
+ marketplace: "www.amazon.com",
85
+ partner_tag: "yourtag-20",
86
+ asin: "B00422MCUS",
87
+ resources: ["itemInfo.title", "variationSummary.price.highestPrice",
88
+ "variationSummary.price.lowestPrice",
89
+ "variationSummary.variationDimension"]
78
90
  )
79
91
  ```
80
92
 
@@ -83,65 +95,35 @@ request.get_variations(
83
95
  The `SearchItems` operation searches for items on Amazon based on a search query. The API returns up to ten items per search request.
84
96
 
85
97
  ```ruby
86
- request.search_items(keywords: 'harry potter')
98
+ client.search_items(
99
+ marketplace: "www.amazon.com",
100
+ partner_tag: "yourtag-20",
101
+ keywords: "harry potter"
102
+ )
87
103
  ```
88
104
 
89
105
  ### Response
90
106
 
91
- Consume a response by parsing it into a Ruby hash.
92
-
93
- ```ruby
94
- response.to_h
95
- ```
96
-
97
- You can also `#dig` into this hash.
98
-
99
- ```ruby
100
- response.dig('ItemsResult', 'Items')
101
- ```
102
-
103
- ### Logging
104
-
105
- Write requests and reponses to a logger using the logging feature of the [HTTP gem](https://github.com/httprb/http) under the hood:
106
-
107
- ```ruby
108
- require 'logger'
109
-
110
- logger = Logger.new(STDOUT)
111
- request.use(logging: {logger: logger})
112
- ```
113
-
114
- ### Bring your parser
115
- You can extend Vacuum with a custom parser. Just swap the original with a class or module that responds to `.parse`.
107
+ Parse the response body into a hash.
116
108
 
117
109
  ```ruby
118
- response.parser = MyParser
119
- response.parse
110
+ response.parse.dig("searchResult", "items")
120
111
  ```
121
112
 
122
- If no custom parser is set, `Vacuum::Response#parse` delegates to `#to_h`.
123
-
124
- ### VCR
113
+ ### Shared Token Cache
125
114
 
126
- If you are using [VCR](https://github.com/vcr/vcr) to test an app that accesses the API, you can use the custom VCR matcher of Vacuum to stub requests.
115
+ By default, each client instance caches its own access token. In multi-process environments like Rails, you can share the token across processes by providing a cache store.
127
116
 
128
117
  ```ruby
129
- require 'vacuum/matcher'
130
-
131
- # in your test
132
- VCR.insert_cassette('cassette_name',
133
- match_requests_on: [Vacuum::Matcher])
118
+ client = Vacuum.new(
119
+ credential_id: "YOUR_CREDENTIAL_ID",
120
+ credential_secret: "YOUR_CREDENTIAL_SECRET",
121
+ version: "2.1",
122
+ cache: Rails.cache
123
+ )
134
124
  ```
135
125
 
136
- In RSpec, use the `:paapi` metadata.
137
-
138
- ```ruby
139
- require 'vacuum/matcher'
140
-
141
- # in your test
142
- it 'queries Amazon', :paapi do
143
- end
144
- ```
126
+ The cache must respond to `fetch(key, expires_in:, &block)`.
145
127
 
146
128
  ## Development
147
129
 
@@ -151,30 +133,16 @@ Clone the repo and install dependencies.
151
133
  bundle install
152
134
  ```
153
135
 
154
- Tests and Rubocop should now pass as-is.
136
+ Run tests and Rubocop.
155
137
 
156
138
  ```sh
157
139
  bundle exec rake
158
140
  ```
159
141
 
160
- By default, the tests stub requests. Use the `RECORD` env var to record new interactions.
161
-
162
- ```sh
163
- RECORD=true bundle exec rake test
164
- ```
165
-
166
- You can set the `LIVE` env var to run all tests against live data.
167
-
168
- ```sh
169
- LIVE=true bundle exec rake test
170
- ```
171
-
172
- In either case, add actual API credentials to a [`locales.yml`](https://github.com/hakanensari/vacuum/blob/master/test/locales.yml.example) file under `test`.
173
-
174
- ## Getting Help
142
+ To run API tests, copy `spec/credentials.yml.example` to `spec/credentials.yml` and fill in your credentials.
175
143
 
176
- * Ask specific questions about the API on the [Amazon forum](https://forums.aws.amazon.com/forum.jspa?forumID=9).
177
- * Report bugs in [GitHub issues](https://github.com/hakanensari/vacuum/issues).
178
- * Discuss potential features in [GitHub Discussions](https://github.com/hakanensari/vacuum/discussions).
144
+ ---
179
145
 
146
+ > Amazon is a $250 billion dollar company that reacts to you buying a vacuum by going THIS GUY LOVES BUYING VACUUMS HERE ARE SOME MORE VACUUMS
180
147
 
148
+ — [@kibblesmith](https://web.archive.org/web/2017/https://twitter.com/kibblesmith/status/724817086309142529)
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http"
4
+
5
+ module Vacuum
6
+ # A client for the Amazon Creators API
7
+ #
8
+ # Handles authentication automatically and caches access tokens.
9
+ #
10
+ # @example Basic usage (v2.x)
11
+ # client = Vacuum::Client.new(
12
+ # credential_id: "YOUR_CREDENTIAL_ID",
13
+ # credential_secret: "YOUR_CREDENTIAL_SECRET",
14
+ # version: "2.1"
15
+ # )
16
+ #
17
+ # @example Basic usage (v3.x)
18
+ # client = Vacuum::Client.new(
19
+ # credential_id: "YOUR_CREDENTIAL_ID",
20
+ # credential_secret: "YOUR_CREDENTIAL_SECRET",
21
+ # version: "3.3"
22
+ # )
23
+ #
24
+ # @example With shared cache (for multi-process environments like Rails)
25
+ # client = Vacuum::Client.new(
26
+ # credential_id: "YOUR_CREDENTIAL_ID",
27
+ # credential_secret: "YOUR_CREDENTIAL_SECRET",
28
+ # version: "2.1",
29
+ # cache: Rails.cache
30
+ # )
31
+ class Client
32
+ AUTH_URLS = {
33
+ "2.1" => "https://creatorsapi.auth.us-east-1.amazoncognito.com/oauth2/token",
34
+ "2.2" => "https://creatorsapi.auth.eu-south-2.amazoncognito.com/oauth2/token",
35
+ "2.3" => "https://creatorsapi.auth.us-west-2.amazoncognito.com/oauth2/token",
36
+ "3.1" => "https://api.amazon.com/auth/o2/token",
37
+ "3.2" => "https://api.amazon.co.uk/auth/o2/token",
38
+ "3.3" => "https://api.amazon.co.jp/auth/o2/token",
39
+ }.freeze
40
+
41
+ API_URL = "https://creatorsapi.amazon/catalog/v1"
42
+
43
+ TOKEN_TTL = 3570 # 59.5 minutes (tokens valid for 1 hour)
44
+
45
+ # Creates a new client
46
+ #
47
+ # @param credential_id [String] Your Creators API credential ID
48
+ # @param credential_secret [String] Your Creators API credential secret
49
+ # @param version [String] API version ("2.1"-"2.3" for Cognito, "3.1"-"3.3" for LwA)
50
+ # @param cache [#fetch] Optional cache store
51
+ # @param http [HTTP::Client]
52
+ def initialize(version:, credential_id:, credential_secret:, cache: nil, http: HTTP)
53
+ @version = version
54
+ @auth_url = AUTH_URLS.fetch(version) do
55
+ raise ArgumentError, "Unknown version: #{version}"
56
+ end
57
+ @credential_id = credential_id
58
+ @credential_secret = credential_secret
59
+ @cache = cache
60
+ @http = http
61
+ end
62
+
63
+ # Returns details about specified browse nodes
64
+ #
65
+ # @param marketplace [String] The marketplace (e.g., "www.amazon.com")
66
+ # @param partner_tag [String] Your partner/tracking tag
67
+ # @param browse_node_ids [Array<String>] The browse node IDs to look up
68
+ # @return [HTTP::Response]
69
+ def get_browse_nodes(marketplace:, partner_tag:, browse_node_ids:, **params)
70
+ params[:browse_node_ids] = Array(browse_node_ids)
71
+ request("getBrowseNodes", marketplace:, partner_tag:, **params)
72
+ end
73
+
74
+ # Returns the attributes of one or more items
75
+ #
76
+ # @param marketplace [String] The marketplace (e.g., "www.amazon.com")
77
+ # @param partner_tag [String] Your partner/tracking tag
78
+ # @param item_ids [Array<String>, String] The item IDs (ASINs) to look up
79
+ # @return [HTTP::Response]
80
+ def get_items(marketplace:, partner_tag:, item_ids:, **params)
81
+ params[:item_ids] = Array(item_ids)
82
+ request("getItems", marketplace:, partner_tag:, **params)
83
+ end
84
+
85
+ # Returns variations of a product
86
+ #
87
+ # @param marketplace [String] The marketplace (e.g., "www.amazon.com")
88
+ # @param partner_tag [String] Your partner/tracking tag
89
+ # @param asin [String] The ASIN to get variations for
90
+ # @return [HTTP::Response]
91
+ def get_variations(marketplace:, partner_tag:, **params)
92
+ request("getVariations", marketplace:, partner_tag:, **params)
93
+ end
94
+
95
+ # Searches for items on Amazon
96
+ #
97
+ # @param marketplace [String] The marketplace (e.g., "www.amazon.com")
98
+ # @param partner_tag [String] Your partner/tracking tag
99
+ # @param keywords [String] Search keywords
100
+ # @return [HTTP::Response]
101
+ def search_items(marketplace:, partner_tag:, **params)
102
+ request("searchItems", marketplace:, partner_tag:, **params)
103
+ end
104
+
105
+ private
106
+
107
+ def v3?
108
+ @version.start_with?("3.")
109
+ end
110
+
111
+ def request(operation, marketplace:, partner_tag:, **params)
112
+ auth_header = if v3?
113
+ "Bearer #{access_token}"
114
+ else
115
+ "Bearer #{access_token}, Version #{@version}"
116
+ end
117
+
118
+ @http
119
+ .headers(
120
+ "Authorization" => auth_header,
121
+ "x-marketplace" => marketplace,
122
+ )
123
+ .post("#{API_URL}/#{operation}", json: camelize_keys(marketplace:, partner_tag:, **params))
124
+ end
125
+
126
+ def camelize_keys(**params)
127
+ params.transform_keys { |key| key.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase } }
128
+ end
129
+
130
+ def access_token
131
+ if @cache
132
+ @cache.fetch(cache_key, expires_in: TOKEN_TTL) { fetch_token }
133
+ else
134
+ @access_token = fetch_token if token_expired?
135
+ @access_token
136
+ end
137
+ end
138
+
139
+ def cache_key
140
+ "vacuum/#{@credential_id}/token"
141
+ end
142
+
143
+ def token_expired?
144
+ @access_token.nil? || Time.now >= @token_expires_at
145
+ end
146
+
147
+ def fetch_token
148
+ response = if v3?
149
+ fetch_token_lwa
150
+ else
151
+ fetch_token_cognito
152
+ end
153
+
154
+ data = response.parse
155
+ @token_expires_at = Time.now + TOKEN_TTL
156
+ @access_token = data["access_token"]
157
+ end
158
+
159
+ # v2.x: Cognito with Basic Auth and form-encoded body
160
+ def fetch_token_cognito
161
+ @http
162
+ .basic_auth(user: @credential_id, pass: @credential_secret)
163
+ .post(@auth_url, form: {
164
+ grant_type: "client_credentials",
165
+ scope: "creatorsapi/default",
166
+ })
167
+ end
168
+
169
+ # v3.x: Login with Amazon (LwA) with JSON body
170
+ def fetch_token_lwa
171
+ @http
172
+ .post(@auth_url, json: {
173
+ grant_type: "client_credentials",
174
+ client_id: @credential_id,
175
+ client_secret: @credential_secret,
176
+ scope: "creatorsapi::default",
177
+ })
178
+ end
179
+ end
180
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vacuum
4
- VERSION = '4.3.0'
4
+ VERSION = "5.1.0"
5
5
  end
data/lib/vacuum.rb CHANGED
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
3
+ require "forwardable"
4
4
 
5
- require 'vacuum/request'
6
- require 'vacuum/version'
5
+ require "vacuum/client"
6
+ require "vacuum/version"
7
7
 
8
- # Ruby wrapper to the Amazon Product Advertising API
8
+ # Ruby wrapper to the Amazon Creators API
9
9
  module Vacuum
10
10
  class << self
11
11
  extend Forwardable
12
12
 
13
13
  # @!method new
14
- # Delegates to {Request} to create a new request
14
+ # Delegates to {Client} to create a new client
15
15
  #
16
- # @return [Request]
17
- # @see Request#initialize
18
- def_delegator 'Vacuum::Request', :new
16
+ # @return [Client]
17
+ # @see Client#initialize
18
+ def_delegator "Vacuum::Client", :new
19
19
  end
20
20
  end
metadata CHANGED
@@ -1,50 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vacuum
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hakan Ensari
8
8
  - Stanislav Katkov
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-15 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: aws-sigv4
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: http
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
17
  - - ">="
32
18
  - !ruby/object:Gem::Version
33
- version: '4.0'
34
- - - "<"
35
- - !ruby/object:Gem::Version
36
- version: '6.0'
19
+ version: '5.0'
37
20
  type: :runtime
38
21
  prerelease: false
39
22
  version_requirements: !ruby/object:Gem::Requirement
40
23
  requirements:
41
24
  - - ">="
42
25
  - !ruby/object:Gem::Version
43
- version: '4.0'
44
- - - "<"
45
- - !ruby/object:Gem::Version
46
- version: '6.0'
47
- description: A wrapper to the Amazon Product Advertising API
26
+ version: '5.0'
27
+ description: A wrapper to the Amazon Creators API
48
28
  email:
49
29
  - hakanensari@gmail.com
50
30
  - sk@skylup.com
@@ -55,11 +35,7 @@ files:
55
35
  - LICENSE
56
36
  - README.md
57
37
  - lib/vacuum.rb
58
- - lib/vacuum/locale.rb
59
- - lib/vacuum/matcher.rb
60
- - lib/vacuum/operation.rb
61
- - lib/vacuum/request.rb
62
- - lib/vacuum/response.rb
38
+ - lib/vacuum/client.rb
63
39
  - lib/vacuum/version.rb
64
40
  homepage: https://github.com/hakanensari/vacuum
65
41
  licenses:
@@ -80,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
56
  - !ruby/object:Gem::Version
81
57
  version: '0'
82
58
  requirements: []
83
- rubygems_version: 3.6.2
59
+ rubygems_version: 4.0.6
84
60
  specification_version: 4
85
- summary: Amazon Product Advertising in Ruby
61
+ summary: Amazon Creators API in Ruby
86
62
  test_files: []
data/lib/vacuum/locale.rb DELETED
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vacuum
4
- # The target locale
5
- #
6
- # @see https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html#host-and-region
7
- class Locale
8
- # Raised when the provided marketplace does not correspond to an existing
9
- # Amazon locale
10
- class NotFound < KeyError; end
11
-
12
- HOSTS_AND_REGIONS = {
13
- au: ['webservices.amazon.com.au', 'us-west-2'],
14
- be: ['webservices.amazon.be', 'eu-west-1'],
15
- br: ['webservices.amazon.com.br', 'us-east-1'],
16
- ca: ['webservices.amazon.ca', 'us-east-1'],
17
- eg: ['webservices.amazon.eg', 'eu-west-1'],
18
- fr: ['webservices.amazon.fr', 'eu-west-1'],
19
- de: ['webservices.amazon.de', 'eu-west-1'],
20
- in: ['webservices.amazon.in', 'eu-west-1'],
21
- ie: ['webservices.amazon.ie', 'eu-west-1'],
22
- it: ['webservices.amazon.it', 'eu-west-1'],
23
- jp: ['webservices.amazon.co.jp', 'us-west-2'],
24
- mx: ['webservices.amazon.com.mx', 'us-east-1'],
25
- nl: ['webservices.amazon.nl', 'eu-west-1'],
26
- pl: ['webservices.amazon.pl', 'eu-west-1'],
27
- sg: ['webservices.amazon.sg', 'us-west-2'],
28
- sa: ['webservices.amazon.sa', 'eu-west-1'],
29
- es: ['webservices.amazon.es', 'eu-west-1'],
30
- se: ['webservices.amazon.se', 'eu-west-1'],
31
- tr: ['webservices.amazon.com.tr', 'eu-west-1'],
32
- ae: ['webservices.amazon.ae', 'eu-west-1'],
33
- gb: ['webservices.amazon.co.uk', 'eu-west-1'],
34
- us: ['webservices.amazon.com', 'us-east-1']
35
- }.freeze
36
- private_constant :HOSTS_AND_REGIONS
37
-
38
- # @return [String]
39
- attr_reader :host, :region, :access_key, :secret_key, :partner_tag,
40
- :partner_type
41
-
42
- # Creates a locale
43
- #
44
- # @param [Symbol,String] marketplace
45
- # @param [String] access_key
46
- # @param [String] secret_key
47
- # @param [String] partner_tag
48
- # @param [String] partner_type
49
- # @raise [NotFound] if marketplace is not found
50
- def initialize(marketplace, access_key:, secret_key:, partner_tag:,
51
- partner_type: 'Associates')
52
- @host, @region = find_host_and_region(marketplace)
53
- @access_key = access_key
54
- @secret_key = secret_key
55
- @partner_tag = partner_tag
56
- @partner_type = partner_type
57
- end
58
-
59
- private
60
-
61
- def find_host_and_region(marketplace)
62
- marketplace = marketplace.to_sym.downcase
63
- marketplace = :gb if marketplace == :uk
64
-
65
- HOSTS_AND_REGIONS.fetch(marketplace)
66
- rescue KeyError
67
- raise NotFound, "marketplace not found: :#{marketplace}"
68
- end
69
- end
70
- end
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Vacuum
6
- # Custom VCR matcher for stubbing calls to the Product Advertising API
7
- #
8
- # The matcher is not required by default.
9
- #
10
- # @example
11
- # require 'vacuum/matcher'
12
- #
13
- # # in your test
14
- # VCR.insert_cassette('cassette_name',
15
- # match_requests_on: [Vacuum::Matcher])
16
- #
17
- # @see https://relishapp.com/vcr/vcr/v/5-0-0/docs/request-matching/register-and-use-a-custom-matcher
18
- class Matcher
19
- IGNORED_KEYS = %w[PartnerTag].freeze
20
- private_constant :IGNORED_KEYS
21
-
22
- # @!visibility private
23
- attr_reader :requests
24
-
25
- # @!visibility private
26
- def self.call(*requests)
27
- new(*requests).compare
28
- end
29
-
30
- # @!visibility private
31
- def initialize(*requests)
32
- @requests = requests
33
- end
34
-
35
- # @!visibility private
36
- def compare
37
- uris.reduce(:==) && bodies.reduce(:==)
38
- end
39
-
40
- private
41
-
42
- def uris
43
- requests.map(&:uri)
44
- end
45
-
46
- def bodies
47
- requests.map do |req|
48
- params = JSON.parse(req.body)
49
- IGNORED_KEYS.each { |k| params.delete(k) }
50
-
51
- params
52
- end
53
- end
54
- end
55
- end
56
-
57
- # :nocov:
58
- if defined?(RSpec)
59
- RSpec.configure do |config|
60
- config.around do |example|
61
- if example.metadata[:paapi]
62
- metadata = example.metadata[:paapi]
63
- metadata = {} if metadata == true
64
- example.metadata[:vcr] = metadata.merge(
65
- match_requests_on: [Vacuum::Matcher]
66
- )
67
- end
68
-
69
- example.run
70
- end
71
- end
72
- end
73
- # :nocov:
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'aws-sigv4'
4
- require 'http'
5
- require 'json'
6
-
7
- module Vacuum
8
- # An Amazon Product Advertising API operation
9
- class Operation
10
- # @!visibility private
11
- attr_reader :locale, :name, :params
12
-
13
- # Creates a new operation
14
- #
15
- # @param [String] name
16
- # @param [Hash] params
17
- # @param [Locale] locale
18
- def initialize(name, params:, locale:)
19
- @name = name
20
- @params = params
21
- @locale = locale
22
- end
23
-
24
- # @return [Hash]
25
- def headers
26
- signature.headers.merge(
27
- 'x-amz-target' =>
28
- "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.#{name}",
29
- 'content-encoding' => 'amz-1.0',
30
- 'content-type' => 'application/json; charset=utf-8'
31
- )
32
- end
33
-
34
- # @return [String]
35
- def body
36
- @body ||= build_body
37
- end
38
-
39
- # @return [String]
40
- def url
41
- @url ||= build_url
42
- end
43
-
44
- private
45
-
46
- def build_body
47
- hsh = { 'PartnerTag' => locale.partner_tag,
48
- 'PartnerType' => locale.partner_type }
49
-
50
- params.each do |key, val|
51
- key = key.to_s.split('_')
52
- .map { |word| word == 'asin' ? 'ASIN' : word.capitalize }.join
53
- hsh[key] = val
54
- end
55
-
56
- JSON.generate(hsh)
57
- end
58
-
59
- def build_url
60
- "https://#{locale.host}/paapi5/#{name.downcase}"
61
- end
62
-
63
- def signature
64
- signer.sign_request(http_method: 'POST', url:, body:)
65
- end
66
-
67
- def signer
68
- Aws::Sigv4::Signer.new(service: 'ProductAdvertisingAPI',
69
- region: locale.region,
70
- access_key_id: locale.access_key,
71
- secret_access_key: locale.secret_key,
72
- http_method: 'POST', endpoint: locale.host)
73
- end
74
- end
75
- end
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'http'
4
-
5
- require 'vacuum/locale'
6
- require 'vacuum/operation'
7
- require 'vacuum/response'
8
-
9
- module Vacuum
10
- # A request to the Amazon Product Advertising API
11
- class Request
12
- # @return [HTTP::Client]
13
- attr_reader :client
14
-
15
- # @return [Locale]
16
- attr_reader :locale
17
-
18
- # @return [Operation]
19
- attr_reader :operation
20
-
21
- # Creates a new request
22
- #
23
- # @overload initialize(marketplace: :us, access_key:, secret_key:, partner_tag:, partner_type:)
24
- # @param [Symbol,String] marketplace
25
- # @param [String] access_key
26
- # @param [String] secret_key
27
- # @param [String] partner_tag
28
- # @param [String] partner_type
29
- # @raise [Locale::NotFound] if marketplace is not found
30
- def initialize(marketplace: :us, **args)
31
- @locale = Locale.new(marketplace, **args)
32
- @client = HTTP::Client.new
33
- end
34
-
35
- # Returns details about specified browse nodes
36
- #
37
- # @see https://webservices.amazon.com/paapi5/documentation/getbrowsenodes.html
38
- # @overload get_browse_nodes(browse_node_ids:, languages_of_preference: nil, marketplace: nil, partner_tag: nil, partner_type: nil, resources: nil)
39
- # @param [Array<String,Integer>,String,Integer] browse_node_ids
40
- # @param [Array<String>,nil] languages_of_preference
41
- # @param [String,nil] marketplace
42
- # @param [String,nil] partner_tag
43
- # @param [String,nil] partner_type
44
- # @param [Array<String>,nil] resources
45
- # @return [Response]
46
- def get_browse_nodes(browse_node_ids:, **params)
47
- params.update(browse_node_ids: Array(browse_node_ids))
48
- request('GetBrowseNodes', params)
49
- end
50
-
51
- # Returns the attributes of one or more items
52
- #
53
- # @see https://webservices.amazon.com/paapi5/documentation/get-items.html
54
- # @overload get_items(condition: nil, currency_of_preference: nil, item_id_type: nil, item_ids:, languages_of_preference: nil, marketplace: nil, merchant: nil, offer_count: nil, partner_tag: nil, partner_type: nil, resources: nil)
55
- # @param [String,nil] condition
56
- # @param [String,nil] currency_of_preference
57
- # @param [String,nil] item_id_type
58
- # @param [Array<String>,String] item_ids
59
- # @param [Array<String>,nil] languages_of_preference
60
- # @param [String,nil] marketplace
61
- # @param [String,nil] merchant
62
- # @param [Integer,nil] offer_count
63
- # @param [String,nil] partner_tag
64
- # @param [String,nil] partner_type
65
- # @param [Array<String>,nil] resources
66
- # @return [Response]
67
- def get_items(item_ids:, **params)
68
- params.update(item_ids: Array(item_ids))
69
- request('GetItems', params)
70
- end
71
-
72
- # Returns a set of items that are the same product, but differ according to
73
- # a consistent theme
74
- #
75
- # @see https://webservices.amazon.com/paapi5/documentation/get-variations.html
76
- # @overload get_variations(asin:, condition: nil, currency_of_preference: nil, languages_of_preference: nil, marketplace: nil, merchant: nil, offer_count: nil, partner_tag: nil, partner_type: nil, resources: nil, variation_count: nil, variation_page: nil)
77
- # @param [String] asin
78
- # @param [String,nil] condition
79
- # @param [String,nil] currency_of_preference
80
- # @param [Array<String>,nil] languages_of_preference
81
- # @param [String,nil] marketplace
82
- # @param [String,nil] merchant
83
- # @param [Integer,nil] offer_count
84
- # @param [String,nil] partner_tag
85
- # @param [String,nil] partner_type
86
- # @param [Array<String>,nil] resources
87
- # @param [Integer,nil] variation_count
88
- # @param [Integer,nil] variation_page
89
- # @return [Response]
90
- def get_variations(**params)
91
- request('GetVariations', params)
92
- end
93
-
94
- # Searches for items on Amazon based on a search query
95
- #
96
- # @see https://webservices.amazon.com/paapi5/documentation/search-items.html
97
- # @overload search_items(actor: nil, artist: nil, author: nil, availability: nil, brand: nil, browse_node_id: nil, condition: nil, currency_of_preference: nil, delivery_flags: nil, item_count: nil, item_page: nil, keywords: nil, languages_of_preference: nil, marketplace: nil, max_price: nil, merchant: nil, min_price: nil, min_reviews_rating: nil, min_savings_percent: nil, offer_count: nil, partner_tag: nil, partner_type: nil, resources: nil, search_index: nil, sort_by: nil, title: nil)
98
- # @param [String,nil] actor
99
- # @param [String,nil] artist
100
- # @param [String,nil] availability
101
- # @param [String,nil] brand
102
- # @param [Integer,nil] browse_node_id
103
- # @param [String,nil] condition
104
- # @param [String,nil] currency_of_preference
105
- # @param [Array<String>,nil] delivery_flags
106
- # @param [Integer,nil] item_count
107
- # @param [Integer,nil] item_page
108
- # @param [String,nil] keywords
109
- # @param [Array<String>,nil] languages_of_preference
110
- # @param [Integer,nil] max_price
111
- # @param [String,nil] merchant
112
- # @param [Integer,nil] min_price
113
- # @param [Integer,nil] min_reviews_rating
114
- # @param [Integer,nil] min_savings_percent
115
- # @param [Integer,nil] offer_count
116
- # @param [Hash,nil] properties
117
- # @param [Array<String>,nil] resources
118
- # @param [String,nil] search_index
119
- # @param [String,nil] sort_by
120
- # @param [String,nil] title
121
- # @return [Response]
122
- def search_items(**params)
123
- request('SearchItems', params)
124
- end
125
-
126
- # Flags as persistent
127
- #
128
- # @param [Integer] timeout
129
- # @return [self]
130
- def persistent(timeout: 5)
131
- host = "https://#{locale.host}"
132
- @client = client.persistent(host, timeout:)
133
-
134
- self
135
- end
136
-
137
- # @!method use(*features)
138
- # Turn on {https://github.com/httprb/http HTTP} features
139
- #
140
- # @param features
141
- # @return [self]
142
- #
143
- # @!method via(*proxy)
144
- # Make a request through an HTTP proxy
145
- #
146
- # @param [Array] proxy
147
- # @raise [HTTP::Request::Error] if HTTP proxy is invalid
148
- # @return [self]
149
- %i[timeout via through headers use].each do |method_name|
150
- define_method(method_name) do |*args, &block|
151
- @client = client.send(method_name, *args, &block)
152
- end
153
- end
154
-
155
- private
156
-
157
- def validate_keywords(params)
158
- return unless params[:keywords]
159
- return if params[:keywords].is_a?(String)
160
-
161
- raise ArgumentError, ':keyword argument expects a String'
162
- end
163
-
164
- def request(operation_name, params)
165
- validate_keywords(params)
166
- @operation = Operation.new(operation_name, params:, locale:)
167
- response = client.headers(operation.headers)
168
- .post(operation.url, body: operation.body)
169
-
170
- Response.new(response)
171
- end
172
- end
173
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'delegate'
4
- require 'forwardable'
5
- require 'json'
6
-
7
- module Vacuum
8
- # A wrapper around the API response
9
- class Response < SimpleDelegator
10
- extend Forwardable
11
-
12
- # @!method dig(*key)
13
- # Delegates to the Hash returned by {Response#to_h} to extract a nested
14
- # value specified by the sequence of keys
15
- #
16
- # @param [String] key one or more keys
17
- # @see https://ruby-doc.org/core/Hash.html#method-i-dig
18
- def_delegator :to_h, :dig
19
-
20
- class << self
21
- # @return [nil,.parse] an optional custom parser
22
- attr_accessor :parser
23
- end
24
-
25
- # @return [nil,.parse] an optional custom parser
26
- attr_writer :parser
27
-
28
- # @!attribute [r] parser
29
- # @return [nil,.parse] an optional custom parser
30
- def parser
31
- @parser || self.class.parser
32
- end
33
-
34
- # Parses the response body
35
- #
36
- # @note Delegates to {#to_h} if no custom parser is set
37
- def parse
38
- parser&.parse(body) || to_h
39
- end
40
-
41
- # Casts body to Hash
42
- #
43
- # @return [Hash]
44
- def to_h
45
- JSON.parse(body)
46
- end
47
- end
48
- end