cardmarket_cli 0.0.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.
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cardmarket_cli/version'
4
+ require 'cardmarket_cli/account'
5
+ require 'cardmarket_cli/logger'
6
+ require 'cardmarket_cli/entities/deletable'
7
+ require 'cardmarket_cli/entities/entity'
8
+ require 'cardmarket_cli/entities/changeable'
9
+ require 'cardmarket_cli/entities/meta_product'
10
+ require 'cardmarket_cli/entities/product'
11
+ require 'cardmarket_cli/entities/wantslist'
12
+ require 'cardmarket_cli/entities/wantslist_item'
13
+
14
+ module CardmarketCLI
15
+ class Error < StandardError; end
16
+ # Your code goes here...
17
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oauth'
4
+ require 'typhoeus'
5
+ require 'oauth/request_proxy/typhoeus_request'
6
+ require 'xmlsimple'
7
+ require 'cgi'
8
+ require 'cardmarket_cli/logger'
9
+ require 'json'
10
+
11
+ module CardmarketCLI
12
+ ##
13
+ #
14
+ class Account
15
+ attr_reader :request_limit, :request_count
16
+
17
+ def initialize(app_token, app_secret, access_token, access_token_secret, options = {})
18
+ options[:site] ||= options[:test] ? 'https://sandbox.cardmarket.com' : 'https://api.cardmarket.com'
19
+ @oauth_consumer = OAuth::Consumer.new(app_token, app_secret, site: options[:site])
20
+ @access_token = OAuth::AccessToken.new(@oauth_consumer, access_token, access_token_secret)
21
+ end
22
+
23
+ def get(path, body: nil, format: :json, params: {})
24
+ request(path, :get, body: body, format: format, params: params)
25
+ end
26
+
27
+ def put(path, body: nil, format: :json, params: {})
28
+ request(path, :put, body: body, format: format, params: params)
29
+ end
30
+
31
+ def post(path, body: nil, format: :json, params: {})
32
+ request(path, :post, body: body, format: format, params: params)
33
+ end
34
+
35
+ def delete(path, body: nil, format: :json, params: {})
36
+ request(path, :delete, body: body, format: format, params: params)
37
+ end
38
+
39
+ def request(path, method, body: nil, format: :json, params: {})
40
+ uri = make_uri(path, format: format, params: params)
41
+ req = Typhoeus::Request.new(uri, method: method, body: make_body(body))
42
+ oauth_helper = OAuth::Client::Helper.new(req, consumer: @oauth_consumer, token: @access_token, request_uri: uri)
43
+ req.options[:headers].merge!(
44
+ { 'Authorization' => oauth_helper.header + ", realm=#{make_uri(path, format: format).inspect}" }
45
+ )
46
+ LOGGER.info log_request(method, uri, body)
47
+ run_req(req)
48
+ end
49
+
50
+ def make_uri(path, format: :json, params: {})
51
+ raise "Unknown format #{format}" unless %i[json xml].include?(format)
52
+
53
+ params = "#{'?' unless params.empty?}#{params.to_a.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }
54
+ .join('&')}"
55
+ "#{@oauth_consumer.site}/ws/v2.0/output.#{format}/#{path}#{params}"
56
+ end
57
+
58
+ def make_body(body)
59
+ if body.respond_to? :each
60
+ body = XmlSimple.xml_out(body, RootName: 'request', XmlDeclaration: '<?xml version="1.0" encoding="UTF-8" ?>',
61
+ SuppressEmpty: nil, NoAttr: true)
62
+ end
63
+ body
64
+ end
65
+
66
+ private
67
+
68
+ def run_req(req)
69
+ response = req.run
70
+ @request_count = get_from_header(response, /X-Request-Limit-Count: \d+/).to_i
71
+ @request_limit = get_from_header(response, /X-Request-Limit-Max: \d+/).to_i
72
+ LOGGER.info("#{response.response_code} (#{response.return_code}) (Limit: "\
73
+ "#{request_count || '?'}/#{request_limit || '?'})")
74
+ LOGGER.debug { JSON.parse(response.response_body).to_yaml }
75
+ response
76
+ end
77
+
78
+ def get_from_header(response, regex)
79
+ match = response.response_headers.match(regex)
80
+ match&.size&.positive? ? match[0].split(':')&.fetch(1) : nil
81
+ end
82
+
83
+ def log_request(method, uri, body)
84
+ LOGGER.info("#{method.to_s.capitalize}: #{uri.inspect}")
85
+ LOGGER.debug { body.to_yaml }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cardmarket_cli/entities/entity'
4
+
5
+ module CardmarketCLI
6
+ module Entities
7
+ ##
8
+ # Base for all entities with changeable attributes in the API
9
+ class Changeable < Entity
10
+ def initialize(id, account, params)
11
+ super(id, account, params)
12
+ @changed = false
13
+ end
14
+
15
+ def changed?
16
+ @changed
17
+ end
18
+
19
+ protected
20
+
21
+ attr_writer :changed
22
+
23
+ class << self
24
+ protected
25
+
26
+ def attr_(*symbols)
27
+ attr_r(*symbols)
28
+ symbols.each do |symbol|
29
+ define_method "#{symbol}=" do |val|
30
+ return if @params[symbol] == val
31
+
32
+ @params[symbol] = val
33
+ @changed = true
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CardmarketCLI
4
+ module Entities
5
+ ##
6
+ # marks that an entity can be deleted
7
+ module Deletable
8
+ def list_attr(symbol, options = {})
9
+ options = { plural: "#{symbol}s", default: true, suffix: true, add: 'add', delete: 'delete', hash: false }
10
+ .merge!(options)
11
+ def_modifiers(symbol, options[:plural], options)
12
+ def_reader(options[:plural], options[:hash])
13
+ def_brackets(options[:plural], options[:hash]) if options[:default]
14
+ def_clear(options[:plural], options[:hash])
15
+ symbol
16
+ end
17
+
18
+ private
19
+
20
+ def def_modifiers(symbol, plural, options)
21
+ options = { suffix: true, add: 'add', delete: 'delete', hash: false }.merge! options
22
+ if options[:hash]
23
+ def_add_hash(symbol, plural, options[:add], options[:suffix])
24
+ def_delete_hash(symbol, plural, options[:delete], options[:suffix])
25
+ else
26
+ def_add_array(symbol, plural, options[:add], options[:suffix])
27
+ def_delete_array(symbol, plural, options[:delete], options[:suffix])
28
+ end
29
+ end
30
+
31
+ def def_add_hash(symbol, plural, name, suffix)
32
+ define_method "#{name}#{"_#{symbol}" if suffix}" do |params|
33
+ key, value, = params
34
+ instance_variable_set("@#{plural}", {}) unless instance_variable_defined? "@#{plural}"
35
+ instance_variable_set("@deleted_#{plural}", {}) unless instance_variable_defined? "@deleted_#{plural}"
36
+ instance_variable_get("@#{plural}")[key] = value if key
37
+ instance_variable_get("@deleted_#{plural}").delete(key)
38
+ { key => value }
39
+ end
40
+ end
41
+
42
+ def def_add_array(symbol, plural, name, suffix)
43
+ define_method "#{name}#{"_#{symbol}" if suffix}" do |value|
44
+ instance_variable_set("@#{plural}", []) unless instance_variable_defined? "@#{plural}"
45
+ instance_variable_set("@deleted_#{plural}", []) unless instance_variable_defined? "@deleted_#{plural}"
46
+ instance_variable_get("@#{plural}") << value if value
47
+ instance_variable_get("@deleted_#{plural}").delete(value)
48
+ value
49
+ end
50
+ end
51
+
52
+ def def_delete_hash(symbol, plural, name, suffix)
53
+ define_method "#{name}#{"_#{symbol}" if suffix}" do |key|
54
+ instance_variable_set("@#{plural}", {}) unless instance_variable_defined? "@#{plural}"
55
+ instance_variable_set("@deleted_#{plural}", {}) unless instance_variable_defined? "@deleted_#{plural}"
56
+ deleted = instance_variable_get("@#{plural}").delete(key)
57
+ instance_variable_get("@deleted_#{plural}")[key] = deleted if deleted
58
+ deleted
59
+ end
60
+ end
61
+
62
+ def def_delete_array(symbol, plural, name, suffix)
63
+ define_method "#{name}#{"_#{symbol}" if suffix}" do |value|
64
+ instance_variable_set("@#{plural}", []) unless instance_variable_defined? "@#{plural}"
65
+ instance_variable_set("@deleted_#{plural}", []) unless instance_variable_defined? "@deleted_#{plural}"
66
+ deleted = instance_variable_get("@#{plural}").delete(value)
67
+ instance_variable_get("@deleted_#{plural}") << deleted if deleted
68
+ deleted
69
+ end
70
+ end
71
+
72
+ def def_reader(plural, hash)
73
+ return if respond_to? plural.to_s.to_sym
74
+
75
+ def_reader_normal(plural, hash)
76
+ def_reader_deleted(plural, hash)
77
+ end
78
+
79
+ def def_reader_normal(plural, hash)
80
+ define_method plural.to_s do
81
+ instance_variable_set("@#{plural}", hash ? {} : []) unless instance_variable_defined? "@#{plural}"
82
+ instance_variable_get("@#{plural}").dup
83
+ end
84
+ end
85
+
86
+ def def_reader_deleted(plural, hash)
87
+ define_method "deleted_#{plural}" do
88
+ unless instance_variable_defined? "@deleted_#{plural}"
89
+ instance_variable_set("@deleted_#{plural}", hash ? {} : [])
90
+ end
91
+ instance_variable_get("@deleted_#{plural}").dup
92
+ end
93
+ end
94
+
95
+ def def_brackets(plural, hash)
96
+ return if respond_to? :[]
97
+
98
+ define_method :[] do |key|
99
+ instance_variable_set("@#{plural}", hash ? {} : []) unless instance_variable_defined? "@#{plural}"
100
+ instance_variable_get("@#{plural}")[key]
101
+ end
102
+ end
103
+
104
+ def def_clear(plural, hash)
105
+ define_method :clear do
106
+ instance_variable_set("@#{plural}", hash ? {} : [])
107
+ instance_variable_set("@deleted_#{plural}", hash ? {} : [])
108
+ end
109
+ private :clear
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CardmarketCLI
4
+ module Entities
5
+ ##
6
+ # Base for all entities in the API
7
+ class Entity
8
+ attr_reader :id
9
+
10
+ def initialize(id, account, params = {})
11
+ @id = id
12
+ @params = params
13
+ @account = account
14
+ end
15
+
16
+ protected
17
+
18
+ attr_writer :id
19
+ attr_accessor :account, :params
20
+
21
+ class << self
22
+ protected
23
+
24
+ def attr_r(*symbols)
25
+ symbols.each do |symbol|
26
+ define_method symbol do
27
+ @params[symbol]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cardmarket_cli/entities/unique'
4
+ require 'cardmarket_cli/entities/entity'
5
+ require 'cardmarket_cli/entities/product'
6
+ require 'cardmarket_cli/logger'
7
+
8
+ module CardmarketCLI
9
+ module Entities
10
+ ##
11
+ # See https://api.cardmarket.com/ws/documentation/API_2.0:Entities:Metaproduct
12
+ class MetaProduct < Entity
13
+ PARAMS = %i[en_name loc_name products].freeze
14
+ PATH_BASE = 'metaproducts'
15
+ attr_r(*(PARAMS - [:products]))
16
+ attr_reader :updated_at
17
+
18
+ def initialize(id, account, params = {})
19
+ super(id, account, { products: [] })
20
+ merge_params(params)
21
+ MetaProduct.send(:add, self)
22
+ end
23
+
24
+ def meta?
25
+ true
26
+ end
27
+
28
+ def products
29
+ read unless params[:products]
30
+ params[:products].dup
31
+ end
32
+
33
+ def read
34
+ LOGGER.debug("Reading Metaproduct #{en_name}(#{id})")
35
+ response = account.get("#{PATH_BASE}/#{id}")
36
+ hash = JSON.parse(response.response_body)
37
+ hash['metaproduct']&.store('product', hash['product'])
38
+ MetaProduct.from_hash(account, hash['metaproduct'])
39
+ end
40
+
41
+ private
42
+
43
+ def merge_params(params)
44
+ self.params[:products] = self.params[:products].union(params[:products] || [])
45
+ self.params.merge!(params.slice(*PARAMS))
46
+ @updated_at = Time.now
47
+ self
48
+ end
49
+
50
+ class << self
51
+ extend Unique
52
+ uniq_attr :instance
53
+
54
+ private :new
55
+
56
+ def from_hash(account, hash)
57
+ hash.transform_keys! { |key| key.underscore.to_sym }
58
+ hash[:products] = []
59
+ hash.delete(:product)&.each do |product|
60
+ hash[:products] << Product.from_hash(account, product)
61
+ end
62
+ MetaProduct.create(hash[:id_metaproduct], account, hash)
63
+ end
64
+
65
+ def create(id, account, params = {})
66
+ self[id]&.send(:merge_params, params) || new(id, account, params)
67
+ end
68
+
69
+ def search_all(account, search_string, exact: false)
70
+ start = 0
71
+ products = []
72
+ loop do
73
+ search(account, search_string, products, start, exact: exact)
74
+ break unless products.count == start += 100
75
+ end
76
+ products
77
+ end
78
+
79
+ def search(account, search_string, array, start, exact: false)
80
+ response = account.get("#{PATH_BASE}/find", params: { search_all: search_string, exact: exact, start: start,
81
+ maxResults: 100, idGame: 1, idLanguage: 1 })
82
+ JSON.parse(response.response_body)['metaproduct']&.each do |product|
83
+ array << from_hash(account, product)
84
+ end
85
+ array
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cardmarket_cli/entities/unique'
4
+ require 'cardmarket_cli/entities/entity'
5
+ require 'cardmarket_cli/logger'
6
+
7
+ module CardmarketCLI
8
+ module Entities
9
+ ##
10
+ # See https://api.cardmarket.com/ws/documentation/API_2.0:Entities:Product
11
+ class Product < Entity
12
+ PARAMS = %i[en_name loc_name meta_product expansion_name rarity count_articles count_foils price_guide].freeze
13
+ PATH_BASE = 'products'
14
+ attr_r(*(PARAMS - [:price_guide]))
15
+
16
+ def initialize(id, account, params = {})
17
+ super(id, account, {})
18
+ merge_params(params)
19
+ Product.send(:add, self)
20
+ end
21
+
22
+ def meta?
23
+ false
24
+ end
25
+
26
+ def read
27
+ LOGGER.debug("Reading Product #{en_name}(#{id})")
28
+ response = account.get("#{PATH_BASE}/#{id}")
29
+ hash = JSON.parse(response.response_body)['product']
30
+ hash['expansion_name'] ||= hash['expansion']&.fetch('enName')
31
+ Product.from_hash(account, hash)
32
+ end
33
+
34
+ def price_guide
35
+ params[:price_guide].dup
36
+ end
37
+
38
+ private
39
+
40
+ def merge_params(params)
41
+ params[:price_guide]&.transform_keys! { |key| key.to_s.underscore.to_sym }&.delete(nil)
42
+ self.params.merge!(params.slice(*PARAMS))
43
+ self.params[:rarity] = self.params[:rarity]&.to_s&.downcase!&.to_sym
44
+ @updated_at = Time.now
45
+ self
46
+ end
47
+
48
+ class << self
49
+ extend Unique
50
+ uniq_attr :instance
51
+
52
+ private :new
53
+
54
+ def from_hash(account, hash)
55
+ hash.transform_keys! { |key| key.underscore.to_sym }
56
+ hash[:meta_product] = MetaProduct.create(hash.delete(:id_metaproduct), account)
57
+ product = Product.create(hash[:id_product], account, hash)
58
+ hash[:meta_product]&.send(:merge_params, { products: [product] })
59
+ product
60
+ end
61
+
62
+ def create(id, account, params = {})
63
+ self[id]&.send(:merge_params, params) || new(id, account, params)
64
+ end
65
+
66
+ def search_all(account, search_string, exact: false)
67
+ start = 0
68
+ products = []
69
+ loop do
70
+ search(account, search_string, products, start, exact: exact)
71
+ break unless products.count == start += 100
72
+ end
73
+ products
74
+ end
75
+
76
+ def search(account, search_string, array, start, exact: false)
77
+ response = account.get("#{PATH_BASE}/find", params: { search_all: search_string, exact: exact, start: start,
78
+ maxResults: 100, idGame: 1, idLanguage: 1 })
79
+ JSON.parse(response.response_body)['product']&.each do |product|
80
+ array << from_hash(account, product)
81
+ end
82
+ array
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end