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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +14 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +100 -0
- data/Guardfile +35 -0
- data/LICENSE.md +55 -0
- data/README.md +29 -0
- data/Rakefile +18 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/cardmarket_cli.gemspec +44 -0
- data/lib/cardmarket_cli.rb +17 -0
- data/lib/cardmarket_cli/account.rb +88 -0
- data/lib/cardmarket_cli/entities/changeable.rb +40 -0
- data/lib/cardmarket_cli/entities/deletable.rb +113 -0
- data/lib/cardmarket_cli/entities/entity.rb +34 -0
- data/lib/cardmarket_cli/entities/meta_product.rb +90 -0
- data/lib/cardmarket_cli/entities/product.rb +87 -0
- data/lib/cardmarket_cli/entities/unique.rb +48 -0
- data/lib/cardmarket_cli/entities/wantslist.rb +138 -0
- data/lib/cardmarket_cli/entities/wantslist_item.rb +79 -0
- data/lib/cardmarket_cli/logger.rb +7 -0
- data/lib/cardmarket_cli/test.rb +32 -0
- data/lib/cardmarket_cli/util/string.rb +32 -0
- data/lib/cardmarket_cli/version.rb +5 -0
- metadata +225 -0
@@ -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
|