cardmarket_cli 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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