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