gatherer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jonan Scheffler
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ _.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,
2
+
3
+ .,-:::::/ :::. :::::::::::: :: .: .,:::::: :::::::.. .,:::::: :::::::..
4
+ ,;;-'````' ;;`;;;;;;;;;;'''',;; ;;,;;;;'''' ;;;;``;;;; ;;;;'''' ;;;;``;;;;
5
+ [[[ [[[[[[/,[[ '[[, [[ ,[[[,,,[[[ [[cccc [[[,/[[[' [[cccc [[[,/[[['
6
+ "$$c. "$$c$$$cc$$$c $$ "$$$"""$$$ $$"""" $$$$$$c $$"""" $$$$$$c
7
+ `Y8bo,,,o88o888 888, 88, 888 "88o888oo,__ 888b "88bo,888oo,__ 888b "88bo,
8
+ `'YMUP"YMMYMM ""` MMM MMM YMM""""YUMMMMMMM "W" """"YUMMMMMMM "W"
9
+
10
+ `'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`'~,_.~'`
11
+
12
+
13
+ # Gatherer: The Magicking
14
+
15
+ Gatherer is a gem to scrape Magic: The Gathering card data from [gatherer.wizards.com](http://gatherer.wizards.com).
16
+
17
+ To grab a card you'll need the Multiverse ID; these IDs uniquely identify cards within gatherer.
18
+
19
+ client = Gatherer::Client.new
20
+ client.fetch_by_multiverse_id(111)
21
+
22
+ The fetch will give you a complete Gatherer::Card with any data that could be scraped from gatherer.
23
+ If you use a non-existent Multiverse ID you will get a Gatherer::CardNotFound error.
24
+
25
+ If you have any questions or you'd like this gem to do something else feel free to contact me.
@@ -0,0 +1,82 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'gatherer/card_parser'
4
+
5
+ module Gatherer
6
+ class Card
7
+ COLOR_SYMBOLS = {
8
+ "Variable Colorless" => "X",
9
+ "White" => "W",
10
+ "Blue" => "U",
11
+ "Black" => "B",
12
+ "Red" => "R",
13
+ "Green" => "G",
14
+ "White or Blue" => "(W/U)",
15
+ "White or Black" => "(W/B)",
16
+ "White or Red" => "(W/R)",
17
+ "White or Green" => "(W/G)",
18
+ "Blue or White" => "(U/W)",
19
+ "Blue or Black" => "(U/B)",
20
+ "Blue or Red" => "(U/R)",
21
+ "Blue or Green" => "(U/G)",
22
+ "Black or White" => "(B/W)",
23
+ "Black or Blue" => "(B/U)",
24
+ "Black or Green" => "(B/G)",
25
+ "Black or Red" => "(B/R)",
26
+ "Red or White" => "(R/W)",
27
+ "Red or Blue" => "(R/U)",
28
+ "Red or Black" => "(R/B)",
29
+ "Red or Green" => "(R/G)",
30
+ "Green or White" => "(G/W)",
31
+ "Green or Blue" => "(G/U)",
32
+ "Green or Black" => "(G/B)",
33
+ "Green or Red" => "(G/R)"
34
+ }
35
+
36
+ attr_reader :title,
37
+ :types,
38
+ :mana_cost,
39
+ :converted_mana_cost,
40
+ :subtypes,
41
+ :text,
42
+ :flavor_text,
43
+ :printings,
44
+ :power,
45
+ :toughness,
46
+ :loyalty,
47
+ :illustrator
48
+
49
+ def initialize(attributes = {})
50
+ @title = attributes[:title]
51
+ @types = attributes[:types]
52
+ @mana_cost = attributes[:mana_cost]
53
+ @converted_mana_cost = attributes[:converted_mana_cost]
54
+ @subtypes = attributes[:subtypes]
55
+ @text = attributes[:text]
56
+ @flavor_text = attributes[:flavor_text]
57
+ @printings = attributes[:printings]
58
+ @power = attributes[:power]
59
+ @toughness = attributes[:toughness]
60
+ @loyalty = attributes[:loyalty]
61
+ @illustrator = attributes[:illustrator]
62
+ end
63
+
64
+ def self.new_from_parser(parser)
65
+ new(
66
+ title: parser.title,
67
+ types: parser.types,
68
+ mana_cost: parser.mana_cost,
69
+ converted_mana_cost: parser.converted_mana_cost,
70
+ subtypes: parser.subtypes,
71
+ text: parser.text,
72
+ flavor_text: parser.flavor_text,
73
+ printings: parser.printings,
74
+ power: parser.power,
75
+ toughness: parser.toughness,
76
+ loyalty: parser.loyalty,
77
+ number: parser.number,
78
+ illustrator: parser.illustrator
79
+ )
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,230 @@
1
+ module Gatherer
2
+ class CardParser
3
+ attr_reader :document
4
+
5
+ SELECTORS = {
6
+ title: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_nameRow',
7
+ types: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_typeRow',
8
+ cmc: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_cmcRow',
9
+ mana: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_manaRow',
10
+ subtypes: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_typeRow',
11
+ text: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_textRow',
12
+ flavor_text: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_flavorRow',
13
+ set: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_setRow',
14
+ other_sets: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_otherSetsRow',
15
+ pt: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_ptRow',
16
+ number: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_numberRow',
17
+ illustrator: 'div#ctl00_ctl00_ctl00_MainContent_SubContent_SubContent_artistRow'
18
+ }
19
+
20
+ def initialize(html, validate_card_markup = true)
21
+ @document = Nokogiri::HTML(html)
22
+ validate if validate_card_markup
23
+ end
24
+
25
+ def validate
26
+ raise CardNotFound if document.css(SELECTORS[:illustrator]).empty?
27
+ end
28
+
29
+ def title(parsed_text = extract_title)
30
+ parsed_text.strip
31
+ end
32
+
33
+ def extract_title
34
+ row = document.css(SELECTORS[:title])
35
+ title_line = row.css('div.value').text
36
+ end
37
+
38
+ def types(parsed_text = extract_types)
39
+ [parsed_text.strip.split("\u2014").first.split].flatten
40
+ end
41
+
42
+ def extract_types
43
+ row = document.css(SELECTORS[:types])
44
+ type_line = row.css('div.value').text
45
+ end
46
+
47
+ def converted_mana_cost(parsed_text = extract_converted_mana_cost)
48
+ parsed_text.strip.to_i
49
+ end
50
+
51
+ def extract_converted_mana_cost
52
+ row = document.css(SELECTORS[:cmc])
53
+ cmc_line = row.css('div.value').text
54
+ end
55
+
56
+ def mana_cost(parsed_text = extract_mana_cost)
57
+ color_map = Gatherer::Card::COLOR_SYMBOLS
58
+ parsed_text.map { |mana| color_map[mana] ? color_map[mana] : mana }.join
59
+ end
60
+
61
+ def extract_mana_cost
62
+ row = document.css(SELECTORS[:mana])
63
+ mana_cost_line = row.css('div.value').css('img').map { |img| img['alt'] }
64
+ end
65
+
66
+ def subtypes(parsed_text = extract_subtypes)
67
+ if parsed_text.include?("\u2014")
68
+ parsed_text.split("\u2014").last.split(' ').map { |type| type.strip }
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ def extract_subtypes
75
+ row = document.css(SELECTORS[:subtypes])
76
+ row.css('div.value').text
77
+ end
78
+
79
+ def text(parsed_text = extract_text)
80
+ parsed_text.map { |line| line.strip }.compact.join("\n")
81
+ end
82
+
83
+ def extract_text
84
+ row = document.css(SELECTORS[:text]).first
85
+ row.inner_html = replace_mana_symbols(row.inner_html)
86
+ row.css('div.value div.cardtextbox').map(&:text)
87
+ end
88
+
89
+ def replace_mana_symbols(html)
90
+ while image = html.match(/<img.*?alt="([^"]*)"[^>]*>/)
91
+ html.gsub!(image.to_s, "{{#{image.captures.first}}}")
92
+ end
93
+
94
+ html
95
+ end
96
+
97
+ def flavor_text(parsed_text = extract_flavor_text)
98
+ parsed_text.strip
99
+ end
100
+
101
+ def extract_flavor_text
102
+ row = document.css(SELECTORS[:flavor_text])
103
+ row.css('div.cardtextbox').text
104
+ end
105
+
106
+ def parse_printing(printing)
107
+ title = printing.split(' (').first
108
+ Printing.new(
109
+ expansion: Expansion.new(title: title, abbreviation: abbreviation(title)),
110
+ rarity: printing.split(' (').last.chop,
111
+ number: (number if current_printing?(title))
112
+ )
113
+ end
114
+
115
+ def printings(parsed_text = extract_printings)
116
+ parsed_text.map do |printing|
117
+ parse_printing(printing)
118
+ end
119
+ end
120
+
121
+ def extract_printings
122
+ ([extract_current_printing] + extract_other_printings).uniq
123
+ end
124
+
125
+ def current_printing(parsed_text = extract_current_printing)
126
+ parse_printing(parsed_text)
127
+ end
128
+
129
+ def current_printing?(title)
130
+ current_printing_text = extract_current_printing
131
+ current_title = current_printing_text.split(' (').first if current_printing_text
132
+ title == current_title
133
+ end
134
+
135
+ def extract_current_printing
136
+ row = document.css(SELECTORS[:set])
137
+ row.css('img').map { |img| img['title'] }.first
138
+ end
139
+
140
+ def other_printings(parsed_text = extract_other_printings)
141
+ parsed_text.map do |printing|
142
+ Printing.new(
143
+ expansion: Expansion.new(:title => parsed_text.split(' (').first),
144
+ rarity: parsed_text.split(' (').last.chop
145
+ )
146
+ end
147
+ end
148
+
149
+ def extract_other_printings
150
+ row = document.css(SELECTORS[:other_sets])
151
+ row.css('img').map { |img| img['title'] }
152
+ end
153
+
154
+ def abbreviation(title, parsed_text = nil)
155
+ parsed_text ||= extract_abbreviation(title)
156
+ parsed_text.split('&set=').last.split('&').first if parsed_text
157
+ end
158
+
159
+ def extract_abbreviation(title)
160
+ images = document.css(SELECTORS[:set]).css('img')
161
+ images += document.css(SELECTORS[:other_sets]).css('img')
162
+
163
+ images.map do |image|
164
+ image['src'] if image['title'].include?(title)
165
+ end.compact.uniq.first
166
+ end
167
+
168
+ def power(parsed_text = extract_power_toughness)
169
+ parsed_text.split('/').first if parsed_text.include?('/')
170
+ end
171
+
172
+ def toughness(parsed_text = extract_power_toughness)
173
+ parsed_text.split('/').last if parsed_text.include?('/')
174
+ end
175
+
176
+ def extract_power_toughness
177
+ row = document.css(SELECTORS[:pt])
178
+ row.css('div.value').text
179
+ end
180
+
181
+ # gatherer uses the pt row to display loyalty
182
+ def loyalty(parsed_text = extract_power_toughness)
183
+ unless parsed_text.include?('/')
184
+ parsed_text.to_i if parsed_text.to_i > 0
185
+ end
186
+ end
187
+
188
+ def number(parsed_text = extract_number)
189
+ parsed_text.to_i
190
+ end
191
+
192
+ def extract_number
193
+ row = document.css(SELECTORS[:number])
194
+ row.css('div.value').text
195
+ end
196
+
197
+ def illustrator(parsed_text = extract_illustrator)
198
+ parsed_text.strip
199
+ end
200
+
201
+ def extract_illustrator
202
+ row = document.css(SELECTORS[:illustrator])
203
+ row.css('div.value').text
204
+ end
205
+
206
+ def to_hash
207
+ {
208
+ title: title,
209
+ types: types,
210
+ mana_cost: mana_cost,
211
+ converted_mana_cost: converted_mana_cost,
212
+ subtypes: subtypes,
213
+ text: text,
214
+ flavor_text: flavor_text,
215
+ printings: printings.map(&:to_hash),
216
+ power: power,
217
+ toughness: toughness,
218
+ loyalty: loyalty,
219
+ number: number,
220
+ illustrator: illustrator
221
+ }
222
+ end
223
+ end
224
+
225
+ class CardNotFound < StandardError
226
+ def initialize
227
+ super("Could not find card.")
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,24 @@
1
+ require 'open-uri'
2
+ require 'gatherer/scraper'
3
+ require 'gatherer/card_parser'
4
+
5
+ module Gatherer
6
+ class Client
7
+ def fetch_by_multiverse_id(id)
8
+ card_from(scraper(id))
9
+ end
10
+
11
+ def scraper(id)
12
+ Gatherer::Scraper.new(multiverse_id: id)
13
+ end
14
+
15
+ def page_from(scraper)
16
+ open(scraper.url)
17
+ end
18
+
19
+ def card_from(scraper)
20
+ parser = Gatherer::CardParser.new(page_from(scraper))
21
+ Card.new_from_parser(parser)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Gatherer
2
+ class Expansion
3
+ attr_reader :title, :abbreviation
4
+
5
+ def initialize(attributes)
6
+ @title = attributes[:title]
7
+ @abbreviation = attributes[:abbreviation]
8
+ end
9
+
10
+ def to_hash
11
+ {
12
+ title: @title,
13
+ abbreviation: @abbreviation
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Gatherer
2
+ class Printing
3
+ attr_reader :expansion, :rarity, :number
4
+
5
+ def initialize(attributes)
6
+ @expansion = attributes[:expansion]
7
+ @rarity = attributes[:rarity]
8
+ @number = attributes[:number]
9
+ end
10
+
11
+ def to_hash
12
+ {
13
+ expansion: @expansion.to_hash,
14
+ rarity: @rarity,
15
+ number: @number
16
+ }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ module Gatherer
2
+ class Scraper
3
+ BASE_PATH = "http://gatherer.wizards.com"
4
+
5
+ def initialize(params = {})
6
+ @query = ''
7
+ @subpath = ''
8
+
9
+ options = params.map do |k,v|
10
+ send(k, v)
11
+ end
12
+
13
+ if options
14
+ @query = URI.encode(options.join('&'))
15
+ end
16
+ end
17
+
18
+ def url
19
+ BASE_PATH + @subpath + (@query.empty? ? '' : '?' + @query)
20
+ end
21
+
22
+ def set(name)
23
+ "set=[#{name}]"
24
+ end
25
+
26
+ def page(number)
27
+ "page=#{number}"
28
+ end
29
+
30
+ def multiverse_id(id)
31
+ @subpath = "/Pages/Card/Details.aspx"
32
+ "multiverseid=#{id}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Gatherer
2
+ VERSION = "0.0.1"
3
+ end
data/lib/gatherer.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "gatherer/scraper"
2
+ require "gatherer/card"
3
+ require 'gatherer/card_parser'
4
+ require 'gatherer/printing'
5
+ require 'gatherer/expansion'
6
+ require 'gatherer/client'
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gatherer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jonan Scheffler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.5.5
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.5.5
46
+ description: ! 'Gatherer is a gem to scrape Magic: The Gathering cards.'
47
+ email:
48
+ - jonanscheffler@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - lib/gatherer/card.rb
54
+ - lib/gatherer/card_parser.rb
55
+ - lib/gatherer/client.rb
56
+ - lib/gatherer/expansion.rb
57
+ - lib/gatherer/printing.rb
58
+ - lib/gatherer/scraper.rb
59
+ - lib/gatherer/version.rb
60
+ - lib/gatherer.rb
61
+ - LICENSE
62
+ - CHANGELOG.md
63
+ - README.md
64
+ homepage: http://github.com/1337807/gatherer
65
+ licenses: []
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: 1.3.6
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.23
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: ! 'Gatherer: The Magicking'
88
+ test_files: []