cockatrice_feeder 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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/gobble +4 -0
  3. data/lib/cockatrice_feeder.rb +369 -0
  4. metadata +89 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9bc7f2a80c7c57558a92463c5415eac22bfc4832
4
+ data.tar.gz: a646c73dfbc6946e1eba60ae9a4addfaf89d9733
5
+ SHA512:
6
+ metadata.gz: 280d96f1a6c97ce6f92b38feb6cb33618f991b57a07c8934051d01c230172c318feea3737b02e32d1d606b7bc7bd1c6c6bdb34855ee5f530da846772d85575d2
7
+ data.tar.gz: f394b4ab9cf428e80ba1f7df8a11dac7d5112cad210ec7c8a9618dd59b765bdffb5a0534bb27d5c362c9ac74ff336798c7d1d143f7647bc8ea8e6eabd310523c
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cockatrice_feeder'
4
+ CockatriceFeeder.gobble
@@ -0,0 +1,369 @@
1
+ module CockatriceFeeder
2
+ require 'httparty'
3
+ require 'awesome_print'
4
+ require 'nokogiri'
5
+
6
+ @@app_dir = Dir.pwd+"/"
7
+ @@deck_dir = @@app_dir+"decks/"
8
+ @@meta_dir = @@app_dir+"meta/"
9
+
10
+ def self.set_app_dir(dir)
11
+ @@app_dir = (dir + (dir[-1] != "/" ? "/" : ""))
12
+ @@deck_dir = @@app_dir+"decks/"
13
+ @@meta_dir = @@app_dir+"meta/"
14
+ end
15
+
16
+ def self.app_dir
17
+ @@app_dir
18
+ end
19
+
20
+ def self.set_deck_dir(dir)
21
+ @@deck_dir = (dir + (dir[-1] != "/" ? "/" : ""))
22
+ end
23
+
24
+ def self.deck_dir
25
+ @@deck_dir
26
+ end
27
+
28
+ def self.set_meta_dir(dir)
29
+ @@meta_dir = (dir + (dir[-1] != "/" ? "/" : ""))
30
+ end
31
+
32
+ def self.meta_dir
33
+ @@meta_dir
34
+ end
35
+
36
+ def self.setup
37
+ unless File.directory?(@@meta_dir)
38
+ Dir.mkdir(@@meta_dir)
39
+ puts "Creating a folder at '#{@@meta_dir}' for storing meta data."
40
+ puts "Fetching meta data."
41
+ update_commanders()
42
+ update_banned()
43
+ update_commander_tiers()
44
+ end
45
+
46
+ unless File.directory?(@@deck_dir)
47
+ Dir.mkdir(@@deck_dir)
48
+ puts "Creating a folder at '#{@@deck_dir}' for generated decks."
49
+
50
+ folders = %w(edhrecavg mtgdecks tappedout deckstats)
51
+
52
+ folders.each do |folder|
53
+ unless File.directory?(@@deck_dir+folder)
54
+ Dir.mkdir(@@deck_dir+folder)
55
+ puts "Creating a deck subfolder at '#{@@deck_dir+folder}'."
56
+ end
57
+ end
58
+ end
59
+
60
+ puts "Ready to scraw!"
61
+ end
62
+
63
+ def self.update_commanders
64
+ puts "Downloading a list of all commanderse from EDHREC."
65
+ commander_cids = %w(
66
+ w g r u b
67
+ wu ub br rg gw wb ur bg rw gu
68
+ wub ubr brg rgw gwu wbg urw bgu rwb gur
69
+ wubr ubrg brgw rgwu gwub
70
+ wubrg
71
+ )
72
+
73
+ commanders = []
74
+
75
+ commander_cids.each do |cid|
76
+ url = "https://edhrec-json.s3.amazonaws.com/en/commanders/#{cid}.json"
77
+ commanders.concat(
78
+ JSON.parse(
79
+ HTTParty.get(url).body
80
+ )["container"]["json_dict"]["cardlists"].first["cardviews"].map{|c|
81
+ {
82
+ link: c["sanitized"],
83
+ name: c["names"].first,
84
+ color_identity: cid,
85
+ deckstats_uri: c["deckstats_uri"]
86
+ }
87
+ }
88
+ )
89
+ end
90
+
91
+ File.open(@@meta_dir+"commanders.json", "wb") do |file|
92
+ file.write(commanders.to_json)
93
+ end
94
+ end
95
+
96
+ def self.update_banned
97
+ puts "Downloading the current EDH banned card list."
98
+ banned_list = []
99
+
100
+ more_pages = true
101
+ page = 1
102
+ while(more_pages)
103
+ url = "https://api.magicthegathering.io/v1/cards?gameFormat=Commander&legality=Banned&page=#{page}"
104
+ cards = JSON.parse(HTTParty.get(url).body)["cards"]
105
+ banned_list.concat(cards)
106
+ page += 1
107
+ if cards.length == 0
108
+ more_pages = false
109
+ end
110
+ end
111
+
112
+ File.open(@@meta_dir+"banned.json", "wb") do |file|
113
+ file.write(banned_list.to_json)
114
+ end
115
+ end
116
+
117
+ def self.update_commander_tiers
118
+ puts "Downloading a tappedout deck that ranks EDG commanders into tiers."
119
+ doc = Nokogiri::HTML(HTTParty.get("https://tappedout.net/mtg-decks/list-multiplayer-edh-generals-by-tier/").body)
120
+
121
+ tiers = {}
122
+ doc.css(".board-container .board-col").each do |col|
123
+ col.css("h3").each_with_index do |h,i|
124
+ tier = h.content.split(")").first.gsub("(","")
125
+
126
+ tiers[tier] = []
127
+ col.css("ul")[i].css("a.card-hover").each do |a|
128
+ tiers[tier] << a.attribute("href").value.split("/").last.gsub("-foil","").downcase
129
+ end
130
+
131
+ end
132
+ end
133
+
134
+ File.open(@@meta_dir+"tiers.json", "wb") do |file|
135
+ file.write(tiers.to_json)
136
+ end
137
+
138
+ tiers
139
+ end
140
+
141
+ def self.commanders
142
+ JSON.parse(File.read(@@meta_dir+"commanders.json"))
143
+ end
144
+
145
+ def self.banned
146
+ JSON.parse(File.read(@@meta_dir+"banned.json"))
147
+ end
148
+ # names = banned.map{|c| c["name"]}.uniq.sort
149
+
150
+ def self.tiers
151
+ JSON.parse(File.read(@@meta_dir+"tiers.json"))
152
+ end
153
+
154
+ def self.output_cod(deck, subfolder)
155
+ comments = [
156
+ deck[:name],
157
+ deck[:link],
158
+ deck[:commanders].join("\n"),
159
+ deck[:price]
160
+ ].join("\n")
161
+
162
+
163
+ filename = [
164
+ deck[:commanders].join("-and-"),
165
+ deck[:name],
166
+ deck[:price],
167
+ subfolder
168
+ ].compact.reject(&:empty?).uniq.join('_')
169
+
170
+ builder = Nokogiri::XML::Builder.new do |xml|
171
+ xml.cockatrice_deck(:version => "1"){
172
+ xml.deckname {
173
+ xml.text(deck[:name])
174
+ }
175
+ xml.comments {
176
+ xml.text(comments)
177
+ }
178
+ xml.zone(:name => "main") {
179
+ deck[:cardlist].each do |crd|
180
+ xml.card(
181
+ :number => crd.split(" ").first,
182
+ :name => crd.split(" ")[1..-1].join(" ")
183
+ )
184
+ end
185
+ }
186
+ }
187
+ end
188
+
189
+ File.open(@@deck_dir+"#{subfolder}/#{filename}.cod", "wb") do |file|
190
+ file.write(builder.to_xml)
191
+ end
192
+
193
+ puts "created deck at #{@@deck_dir+"#{subfolder}/#{filename}.cod"}"
194
+ end
195
+
196
+
197
+ # ordering
198
+ # -rating
199
+ # -date_updated
200
+ # -competitive_score
201
+ def self.tappedout_decklist(pages = (1..10), order_by = "-date_updated", price_min = "", price_max = "")
202
+ decks = []
203
+ pages.each do |page|
204
+ doc = Nokogiri::HTML(
205
+ HTTParty.get(
206
+ "https://tappedout.net/mtg-decks/search/?q=&format=edh&is_top=on&price_min=#{price_min}&price_max=#{price_max}&o=#{order_by}&submit=Filter+results&p=#{page}&page=#{page}"
207
+ ).body
208
+ )
209
+
210
+ doc.css(".deck-wide-header a").each do |a|
211
+ link = a.attribute("href").value
212
+ if link.include?("/mtg-decks/")
213
+ decks << {
214
+ name: link.split("/").last,
215
+ commanders: [],
216
+ link: "https://tappedout.net"+link,
217
+ date: nil,
218
+ price: nil,
219
+ cardlist: []
220
+ }
221
+ end
222
+ end
223
+ end
224
+
225
+ decks
226
+ end
227
+
228
+ def self.tappedout_deck(deck)
229
+ doc2 = Nokogiri::HTML(HTTParty.get(deck[:link]).body)
230
+
231
+ deck[:cardlist] = doc2.css("#mtga-textarea").first.content.
232
+ split("\n").select{|c| c != ""}.
233
+ map{|c| c.split("(").first.strip}
234
+
235
+ commanders = []
236
+ doc2.css(".board-container .board-col").each do |col|
237
+ col.css("h3").each_with_index do |h,i|
238
+ if h.content.include?("Commander")
239
+ col.css("ul")[i].css("a.card-hover").each do |a|
240
+ if a.css("img.commander-img").length > 0
241
+ commanders << a.attribute("href").value.split("/").last.gsub("-foil","")
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+ deck[:commanders] = commanders
248
+
249
+ price = nil
250
+ doc2.css("form").each do |f|
251
+ fname = f.attribute("name")
252
+ if !fname.nil? && fname.value == "ck_checkout"
253
+ price = f.css("span.pull-right").first.content.split(" - ").first.
254
+ strip.gsub("$","").split(".").first
255
+ end
256
+ end
257
+ deck[:price] = price
258
+
259
+ subfolder = "tappedout"
260
+ output_cod(deck, subfolder)
261
+ end
262
+
263
+ def self.edhrecavg_decklist
264
+ commanders.map{|c| c["link"]}.map do |c|
265
+ {
266
+ name: c,
267
+ commanders: [c],
268
+ link: "https://edhrec-json.s3.amazonaws.com/en/decks/#{c}.json",
269
+ price: nil,
270
+ date: nil,
271
+ cardlist: []
272
+ }
273
+ end
274
+ end
275
+
276
+ def self.edhrecavg_deck(deck)
277
+ deck[:cardlist] = JSON.parse(HTTParty.get(deck[:link]).body)["description"].
278
+ split("</a>").last.split("\n").select{|s| s != ""}
279
+
280
+ output_cod(deck,"edhrecavg")
281
+ end
282
+
283
+ def self.deckstats_decklist
284
+
285
+ end
286
+
287
+ def self.deckstats_deck
288
+
289
+ end
290
+
291
+ def self.mtgdecks_decklist(pages = (1..1))
292
+ decks = []
293
+ pages.each do |page|
294
+ puts "https://mtgdecks.net/Commander/decklists/page:#{page}"
295
+ doc = Nokogiri::HTML(HTTParty.get("https://mtgdecks.net/Commander/decklists/page:#{page}").body)
296
+ doc.css(".decks tr.previewable").each do |r|
297
+ if r.css("td")[0].css(".label-danger").length == 0
298
+ decks << {
299
+ name: "", # r.css("td")[1].css("a")[0].content,
300
+ link: "https://mtgdecks.net"+r.css("td")[1].css("a")[0].attribute("href").value,
301
+ date: r.css("td")[6].css("strong")[0].content.
302
+ gsub("<span class=\"hidden-xs\">","").
303
+ gsub("</span>","").gsub(/\s+/, ""),
304
+ price: r.css("td")[7].css("span.paper")[0].content.gsub("$","").gsub(/\s+/, ""),
305
+ commanders: [],
306
+ cardlist: []
307
+ }
308
+ end
309
+ end
310
+ end
311
+
312
+ decks
313
+ end
314
+
315
+ def self.mtgdecks_deck(deck)
316
+ puts deck[:link]
317
+ doc = Nokogiri::HTML(HTTParty.get(deck[:link]).body)
318
+
319
+ cardlist = []
320
+ doc.css(".cardItem").each do |card|
321
+ cardlist << "#{card.attribute("data-required").value} #{card.attribute("data-card-id").value}"
322
+ end
323
+ deck[:cardlist] = cardlist
324
+
325
+ commanders = []
326
+ doc.css(".breadcrumbs a").each do |a|
327
+ href = a.attribute("href").value
328
+ if href.include?("/Commander/")
329
+ commanders << href.split("/").last
330
+ end
331
+ end
332
+ deck[:commanders] = commanders
333
+
334
+ output_cod(deck,"mtgdecks")
335
+ end
336
+
337
+ def self.gobble
338
+ setup()
339
+
340
+ total_decks = 0
341
+
342
+ # puts "Fetching the most recently updated ranked decks from tappedout"
343
+ # decks = CockatriceFeeder.tappedout_decklist(1..10)
344
+ # puts "#{decks.length} decks found."
345
+ # decks.each {|d|
346
+ # CockatriceFeeder.tappedout_deck(d)
347
+ # total_decks += 1
348
+ # }
349
+ #
350
+ # puts "Fetching the first 10 pages of decks at https://mtgdecks.net/Commander/decklists/"
351
+ # decks = CockatriceFeeder.mtgdecks_decklist(1..10)
352
+ # puts "#{decks.length} decks found."
353
+ # decks.each {|d|
354
+ # CockatriceFeeder.mtgdecks_deck(d)
355
+ # total_decks += 1
356
+ # }
357
+
358
+ puts "Fetching the average deck for every commander on EDHREC"
359
+ decks = CockatriceFeeder.edhrecavg_decklist
360
+ puts "#{decks.length} decks found."
361
+ decks.each {|d|
362
+ CockatriceFeeder.edhrecavg_deck(d)
363
+ total_decks += 1
364
+ }
365
+
366
+ puts "#{total_decks} decks created."
367
+ puts "Scraw!"
368
+ end
369
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cockatrice_feeder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Steinwachs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.18'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.18'
41
+ - !ruby/object:Gem::Dependency
42
+ name: awesome_print
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.8'
55
+ description: A tool to scrape MTG decks from the internet along with some meta information
56
+ and create Cockatrice compatible deck files.
57
+ email: matt.steinwachs@gmail.com
58
+ executables:
59
+ - gobble
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - bin/gobble
64
+ - lib/cockatrice_feeder.rb
65
+ homepage: https://github.com/matt-steinwachs/cockatrice_feeder
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message: Scraw!
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.6.14
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Scrape and generate decks for cockatrice
89
+ test_files: []