tcg-player-sdk 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 444973875234b87717e9ece0dd3c0373d707d2a13bac732330f7ab8d6a740f72
4
+ data.tar.gz: cc526ef5f4bc6cadb4404096dfdf9b5a414b5a438d99c4ddf58171b2b6beb214
5
+ SHA512:
6
+ metadata.gz: cdede9b22fe30a6f4106af572ca2f0523e6989ec426de0d307fdc37252cfee511ed6275bae255d954eb9cd386465042d650ac78a6096e522d2810cbe26d1d6bb
7
+ data.tar.gz: e4649464a2dd938a07b9ee991cd1b9635248d36685f4f301feb31a5dcded44c43446f32e6a5172cf688938d2e36725c4277b1ef2e2e4e716f0aef7d75445b91a
@@ -0,0 +1,20 @@
1
+ class TCGPlayerSDK::BearerToken
2
+ attr_accessor :expires_in, :token, :expiration, :issued
3
+
4
+ def initialize(params = {})
5
+ self.expires_in = params['expires_in']
6
+ self.token = params['access_token']
7
+ self.issued = DateTime.parse(params['.issued'])
8
+ self.expiration = DateTime.parse(params['.expires'])
9
+ end
10
+
11
+ ##
12
+ # @return true if the bearer token is within five minutes of expiring
13
+ def expired?
14
+ return expiration.nil? || (DateTime.now >= expiration)
15
+ end
16
+
17
+ def to_s
18
+ ap self, indent: -2
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ # Helpers for pokemon-centric tasks
2
+ class TCGPlayerSDK::Pokemon
3
+ attr_accessor :tcg
4
+
5
+ def initialize(_tcg)
6
+ self.tcg = _tcg
7
+ @category = nil
8
+ end
9
+
10
+ def category
11
+ @category ||= tcg.categories(limit: 100).select{|c| c.name =~ /Pokemon/i}.first
12
+ end
13
+
14
+ def categoryId
15
+ category.categoryId
16
+ end
17
+
18
+ def manifest
19
+ @manifest ||= tcg.category_search_manifest(categoryId)
20
+ end
21
+
22
+ ##
23
+ # Returns the TCGPlaeryAPI filter corresponding to Sets
24
+ def sets
25
+ @set_filter ||= @manifest.results.first.filters.select{|f| f.name == 'SetName'}.first
26
+ end
27
+
28
+ ##
29
+ # Returns the TCGPlayerSDK filter item corresponding to the input set
30
+ #
31
+ # pokemon.set('Base Set')
32
+ # [
33
+ # [0] TCGPlayerSDK::ResponseStruct {
34
+ # :text => "Base Set",
35
+ # :value => "Base Set"
36
+ # }
37
+ # ]
38
+ def set(set_name)
39
+ sets.items.select{|i| i.text == set_name}
40
+ end
41
+ end
@@ -0,0 +1,84 @@
1
+ class TCGPlayerSDK::ProductPriceList
2
+ attr_accessor :response
3
+
4
+ ##
5
+ # Group prices by product ID. This will set
6
+ # prices to an empty hash if there was an error. Check response.errors for details about any errors.
7
+ #
8
+ # @param _response[TCGPlayerSDK::ResponseStruct] the result of calling TCGPlayerAPI#product_pricing
9
+ def initialize(_response)
10
+ @response = _response
11
+
12
+ if(response.success && response.results)
13
+ @prices = response.results.map{|r| TCGPlayerSDK::ProductPrice.new(r)}.group_by{|r| r.productId}
14
+ else
15
+ @prices = {}
16
+ end
17
+ end
18
+
19
+ ##
20
+ # Returns a hash with productIds as keys, and a list of prices with subtypes as values:
21
+ # {
22
+ # 85736 => [
23
+ # {
24
+ # "productId" => 85737,
25
+ # "lowPrice" => 4.99,
26
+ # "midPrice" => 5.63,
27
+ # "highPrice" => 7.73,
28
+ # "marketPrice" => 10.35,
29
+ # "directLowPrice" => nil,
30
+ # "subTypeName" => "Reverse Holofoil"
31
+ # },
32
+ # {
33
+ # "productId" => 85737,
34
+ # "lowPrice" => nil,
35
+ # "midPrice" => nil,
36
+ # "highPrice" => nil,
37
+ # "marketPrice" => nil,
38
+ # "directLowPrice" => nil,
39
+ # "subTypeName" => "Unlimited Holofoil"
40
+ # },
41
+ # ]
42
+ # }
43
+ #
44
+ # @return [Hash<Integer, Array<TCGPlayerSDK::ProductPrice>>]
45
+ def prices
46
+ @prices
47
+ end
48
+
49
+ ##
50
+ # Weed out any ProductPrice objects that have no valid prices.
51
+ #
52
+ # @return [Hash<Integer, Array<TCGPlayerSDK::ProductPrice>>]
53
+ def valid_prices
54
+ valid_prices = {}
55
+ @prices.each do |id, prl|
56
+ valid_prices[id] ||= []
57
+ valid_prices[id] = prl.select{|pr| pr.has_valid_prices?}
58
+ end
59
+
60
+ return valid_prices
61
+ end
62
+ end
63
+
64
+ # A wrapper around the ResponseStruct for an individual product subtype's price.
65
+ # {
66
+ # "productId" => 85737,
67
+ # "lowPrice" => nil,
68
+ # "midPrice" => nil,
69
+ # "highPrice" => nil,
70
+ # "marketPrice" => nil,
71
+ # "directLowPrice" => nil,
72
+ # "subTypeName" => "Unlimited Holofoil"
73
+ # },
74
+ class TCGPlayerSDK::ProductPrice < TCGPlayerSDK::ResponseStruct
75
+ def initialize(hash = nil)
76
+ super
77
+ end
78
+
79
+ ##
80
+ # @return [Boolean] Returns false if there are no non-nil prices
81
+ def has_valid_prices?
82
+ return !(lowPrice.nil? && midPrice.nil? && highPrice.nil? && directLowPrice.nil? && marketPrice.nil?)
83
+ end
84
+ end
@@ -0,0 +1,113 @@
1
+ class TCGPlayerSDK::ResponseStruct < OpenStruct
2
+ include Enumerable
3
+
4
+ ##
5
+ # Attempt to execute a given block of code. Return the result on success,
6
+ # or the *default* on failure.
7
+ #
8
+ # try(nil) {self.results.first.name}
9
+ def try(default, &blk)
10
+ begin
11
+ return self.instance_eval(&blk)
12
+ rescue
13
+ return default
14
+ end
15
+ end
16
+
17
+ ##
18
+ # Iterates over `self.results` and attempts to fetch any missing results.
19
+ # For example, if you get a response that has "totalItems" equal to a number that is larger
20
+ # than the size of "results", then this function easily lets you iterate over every item,
21
+ # and will automatically fetch any items not included in "results."
22
+ #
23
+ # Since this class includes Enumberable, any Enumerable methods are also available.
24
+ #
25
+ # Example:
26
+ # 2.7.2 :057 > cl = tcg.categories
27
+ # D, [2022-01-25T22:20:34.597036 #67042] DEBUG -- : Query: https://api.tcgplayer.com/catalog/categories params:
28
+ # D, [2022-01-25T22:20:34.597129 #67042] DEBUG -- : {}
29
+ # TCGPlayerSDK::ResponseStruct {
30
+ # :totalItems => 67,
31
+ # :results => [...]
32
+ #
33
+ # 2.7.2 :072 > cl.results.size
34
+ # 10
35
+ #
36
+ # 2.7.2 :073 > allcl = cl.map{|result| result.name}
37
+ # D, [2022-01-25T22:29:11.506994 #67042] DEBUG -- : Query: https://api.tcgplayer.com/catalog/categories params:
38
+ # D, [2022-01-25T22:29:11.507161 #67042] DEBUG -- : {
39
+ # :offset => 10,
40
+ # :limit => 100
41
+ # }
42
+ # [
43
+ # [ 0] "Alternate Souls",
44
+ # [ 1] "Architect TCG",
45
+ # [ 2] "Argent Saga TCG",
46
+ # [ 3] "Axis & Allies",
47
+ # ...
48
+ # [64] "WoW",
49
+ # [65] "YuGiOh",
50
+ # [66] "Zombie World Order TCG"
51
+ # ]
52
+ def each(&block)
53
+ if(!self.totalItems.nil? && !self.results.nil? && !self.tcg_object.nil?)
54
+ max_items = self.totalItems
55
+
56
+ # Defaults
57
+ offset = self.results.size
58
+ limit = defined?(self.base_query.params.limit) ? self.base_query.params.limit : 100
59
+ offset += defined?(self.base_query.params.offset) ? self.base_query.params.offset : 0
60
+
61
+ self.results.each(&block)
62
+ while(offset < max_items && !self.results.empty?)
63
+ # Fetch more results
64
+ fetch_params = self.base_query.params.dup.to_h
65
+ fetch_params[:offset] = offset
66
+ fetch_params[:limit] = limit
67
+ more_results = self.tcg_object.query(self.base_query.url, fetch_params)
68
+
69
+ # iterate
70
+ more_results.results.each(&block)
71
+ offset += more_results.results.size
72
+ end
73
+ else
74
+ # Throw an exception?
75
+ end
76
+ end
77
+
78
+ # Create ResponseStructs out of any nested hashes or arrays of hashes
79
+ def method_missing(mid, *args)
80
+ # Avoid assignments
81
+ if(mid.to_s =~ /=/)
82
+ return super
83
+ else
84
+ # Get original value
85
+ value = super
86
+
87
+ # Turn it into a ResponseStruct
88
+ new_val = (value.is_a?(Array) && value.first.is_a?(Hash)) ?
89
+ value.map{|v| TCGPlayerSDK::ResponseStruct.new(v)} :
90
+ ((value.is_a?(Hash)) ? TCGPlayerSDK::ResponseStruct.new(value) : value)
91
+
92
+ # Save it back as a ResponseStruct
93
+ send("#{mid.to_s}=", new_val)
94
+ return new_val
95
+ end
96
+ end
97
+
98
+ def respond_to_missing?(mid, include_private = nil)
99
+ super
100
+ end
101
+
102
+ def to_s
103
+ ap self, indent: -2
104
+ end
105
+
106
+ def to_h(*args)
107
+ self.marshal_dump
108
+ end
109
+
110
+ def keys
111
+ return self.to_h.keys
112
+ end
113
+ end
@@ -0,0 +1,142 @@
1
+ ##
2
+ # Wrap up request handling and common functions for TCGPlayer price API
3
+ class TCGPlayerSDK
4
+ attr_accessor :bearer_token, :user_agent, :logger, :noretry, :public_key, :private_key
5
+
6
+ API_VERSION = '1.39'
7
+ BASE_URL = 'https://api.tcgplayer.com'
8
+ TOKEN_URL = "#{BASE_URL}/token"
9
+ CATALOG_URL = "#{BASE_URL}/catalog"
10
+ CATEGORIES_URL = "#{CATALOG_URL}/categories"
11
+ PRICING_URL = "#{BASE_URL}/pricing"
12
+
13
+ ##
14
+ # @param params[Hash]
15
+ # - user_agent: An identifying user agent string to be passed along with each request
16
+ # - bearer_token: Optionally pass in a previously authenticated bearer token
17
+ # - noretry: Set this to true to disable retrying queries when the bearer token is invalid
18
+ # - logger: Optionally pass a custom logging object
19
+ # - debug: Set output verbosity to DEBUG. Default verbosity is WARN
20
+ def initialize(params = {})
21
+ self.user_agent = params[:user_agent] || 'Unknown'
22
+ self.bearer_token = params[:bearer_token]
23
+ self.noretry = params[:noretry]
24
+
25
+ self.public_key = params[:public_key]
26
+ self.private_key = params[:private_key]
27
+
28
+ # Setup logging
29
+ self.logger = params[:logger] || Logger.new(STDOUT)
30
+ if(params[:debug])
31
+ self.logger.level = Logger::DEBUG
32
+ else
33
+ self.logger.level = Logger::WARN
34
+ end
35
+ end
36
+
37
+ # Get a new bearer token. Specify your app's public and private key as parameters
38
+ # or via Environment variables (or via .env) TCG_PLAYER_API_PUBLIC_KEY and TCG_PLAYER_API_PRIVATE_KEY
39
+ #
40
+ # @param params[Hash]
41
+ # - public_key: your TCP Player API pubic key
42
+ # - private_key: your TCP Player API pubic key
43
+ #
44
+ # @return [TCGPlayerSDK::BearerToken]
45
+ def authorize(params = {})
46
+ pub_key = params[:public_key] || public_key || ENV['TCG_PLAYER_API_PUBLIC_KEY']
47
+ pri_key = params[:private_key] || private_key || ENV['TCG_PLAYER_API_PRIVATE_KEY']
48
+
49
+ #"grant_type=client_credentials&client_id=PUBLIC_KEY&client_secret=PRIVATE_KEY"
50
+ query_params = {grant_type: 'client_credentials', client_id: pub_key, client_secret: pri_key}
51
+ response = HTTP.post(TOKEN_URL, form: query_params)
52
+ resp_hash = response.parse
53
+ logger.info resp_hash
54
+
55
+ self.bearer_token = BearerToken.new resp_hash
56
+ end
57
+
58
+ ##
59
+ # Perform a query on the TCGPlayer API
60
+ #
61
+ # @param url The API endpoint url, without arguments
62
+ # @param _params[Hash]
63
+ # - post: When true, use a post request instead of a get request
64
+ # - noretry: Override any other retry settings and skip retry if true
65
+ # - *: Additional entries in _params hash are passed through to API as arguments
66
+ #
67
+ # @return [TCGPlayerSDK::ResponseStruct]
68
+ def query(url, _params = {})
69
+ params = _params.dup
70
+ post = params.delete :post
71
+ method = post ? 'post' : 'get'
72
+ pkey = post ? :json : :params
73
+ skip_retry = params.delete(:noretry) || noretry
74
+
75
+ logger.debug "Query: #{url} params: "
76
+ logger.ap params
77
+
78
+ # Check for expiration of bearer token
79
+ response = HTTP.auth("bearer #{bearer_token ? bearer_token.token : 'none'}").headers('User-Agent' => user_agent).send(method, url, pkey => params)
80
+ ret = ResponseStruct.new response.parse.merge({base_query: {url: url, params: _params}, http_response: response, tcg_object: self})
81
+
82
+ # Detect an invalid bearer token and attempt to retry
83
+ if(!skip_retry && ret.errors && ret.errors.size > 0 && ret.errors.reduce(false){|sum, err| sum = (sum || (err =~ /bearer token/))})
84
+ # Reauthorize and try again
85
+ authorize
86
+ ret = query(url, _params.merge({noretry: true}))
87
+ end
88
+
89
+ return ret
90
+ end
91
+
92
+ ##
93
+ # Endpoint https://docs.tcgplayer.com/reference/catalog_getcategories-1
94
+ #
95
+ # @param params[Hash]
96
+ # - limit: max to return (default 10)
97
+ # - offset: number of categories to skip (for paging)
98
+ # - sort_order: property to sort by (defaults to name)
99
+ # - sort_desc: descending sort order
100
+ #
101
+ # @return [TCGPlayerSDK::ResponseStruct]
102
+ def categories(params = {})
103
+ query(CATEGORIES_URL, params)
104
+ end
105
+
106
+ ##
107
+ # https://docs.tcgplayer.com/reference/catalog_getcategory-1
108
+ #
109
+ # @return [TCGPlayerSDK::ResponseStruct]
110
+ def category_details(ids)
111
+ query("#{CATEGORIES_URL}/#{ids.join(',')}")
112
+ end
113
+
114
+ # https://docs.tcgplayer.com/reference/catalog_getcategorysearchmanifest
115
+ #
116
+ # @return [TCGPlayerSDK::ResponseStruct]
117
+ def category_search_manifest(id)
118
+ query("#{CATEGORIES_URL}/#{id}/search/manifest")
119
+ end
120
+
121
+ # https://docs.tcgplayer.com/reference/catalog_searchcategory
122
+ #
123
+ # @return [TCGPlayerSDK::ResponseStruct]
124
+ def category_search_products(id, params = {})
125
+ search_params = {post: true}.merge(params)
126
+ query("#{CATEGORIES_URL}/#{id}/search", search_params)
127
+ end
128
+
129
+ #def product_details(pids, params = {})
130
+ # query("#{CATEGORIES_URL}/#{id}/search", search_params)
131
+ #end
132
+
133
+ ##
134
+ # Accessor to https://docs.tcgplayer.com/reference/pricing_getproductprices-1
135
+ #
136
+ # @param ids An array of product IDs to query
137
+ # @return [TCGPlayerSDK::ProductPriceList]
138
+ def product_pricing(_ids)
139
+ ids = _ids.is_a?(Array) ? _ids.join(',') : _ids
140
+ TCGPlayerSDK::ProductPriceList.new(query("#{PRICING_URL}/product/#{ids}"))
141
+ end
142
+ end
@@ -0,0 +1,11 @@
1
+ require 'http'
2
+ require 'logger'
3
+ require 'amazing_print'
4
+ require 'dotenv/load'
5
+
6
+ require 'tcg-player-sdk/tcg_player_sdk'
7
+ require 'tcg-player-sdk/bearer_token'
8
+ require 'tcg-player-sdk/response_struct'
9
+ require 'tcg-player-sdk/product_price_list'
10
+ require 'tcg-player-sdk/pokemon'
11
+
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tcg-player-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Carl Svensson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.0.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.0.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.7.6
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.7.6
41
+ - !ruby/object:Gem::Dependency
42
+ name: amazing_print
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.4.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.4.0
55
+ description: The tcg-player-sdk gem is a convenient wrapper to the TCGPlayer JSON
56
+ API. Easily query the entire API, with helpers and accessors for common queries. This
57
+ gem also provides helpers for Pokemon-specific queries. A work in progress.
58
+ email: csvenss2@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/tcg-player-sdk.rb
64
+ - lib/tcg-player-sdk/bearer_token.rb
65
+ - lib/tcg-player-sdk/pokemon.rb
66
+ - lib/tcg-player-sdk/product_price_list.rb
67
+ - lib/tcg-player-sdk/response_struct.rb
68
+ - lib/tcg-player-sdk/tcg_player_sdk.rb
69
+ homepage: https://cecomp64.github.io/tcg-player-sdk/
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.1.2
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: A Ruby interface to the TCGPlayer.com API for trading card prices.
92
+ test_files: []