ffxiv-lodestone 0.7.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 (3) hide show
  1. data/README +56 -0
  2. data/lib/ffxiv-lodestone.rb +202 -0
  3. metadata +71 -0
data/README ADDED
@@ -0,0 +1,56 @@
1
+ == About ==
2
+ This is a screen scraper for the FFXIV community side, Lodestone. It gets information about
3
+ characters. The Lodestone pages are a huge pile of shit, so this will save you a few hours of
4
+ hating your life while you write a screen scraper.
5
+
6
+ The aim is to not break if SquareEnix adds new pieces of data to the page. Any new attributes,
7
+ skills, of general pieces of profile data should automatically be handled by this library.
8
+
9
+ She ain't pretty, but it's decently robust.
10
+
11
+ == Usage ==
12
+ Take, for example, this character: <http://lodestone.finalfantasyxiv.com/rc/character/status?cicuid=1502635>
13
+
14
+ To fire it up, call Character.new with the cicuid from that URL:
15
+
16
+ require 'ffxiv_lodestone'
17
+ char = FFXIVLodestone::Character.new(1502635)
18
+
19
+ Stuff you can do:
20
+
21
+ char.to_json => String containing a JSON representation of the character / jobs.
22
+
23
+ char.name, char.world, char.nameday, char.guardian, char.starting_city, char.physical_level,
24
+ char.current_exp, char.exp_to_next_level, char.gender, char.race, char.clan
25
+
26
+ char.skills => SkillList with these methods:
27
+ .list => Array of all skills that have been leveled (ie rank 0 jobs omitted)
28
+ .pugilist (or any class name) => The skill, regardless of if its been leveled or not
29
+ (ie you can explicitly ask for a skill to see if it's been leveled or not)
30
+
31
+ char.resistances => StatList (same deal as stats but with fire instead of strength,
32
+ see method list below)
33
+ char.stats => StatList with these methods:
34
+ .each {} => Does some iteration
35
+ .strength (or any stat name) => Integer value of the state
36
+
37
+ A skill object has these methods:
38
+ name, skill_name, rank, skillpoint_to_next_level, current_skill_points
39
+
40
+ == FAQ ==
41
+ Q. Hey, why doesn't this have HP / MP / TP? They're on the character profile page!
42
+ A. Because those values change based on the currently equipped job. I don't think they're useful
43
+ pieces of data. If you have a use case for them, please let me know and I will include them.
44
+
45
+ == TODO ==
46
+ * Error handling (if you put in a crap char ID you'll get some cryptic Nokogiri errors)
47
+ * Figure out what the fuck the stats in parenthesis mean and do something with them.
48
+ * Currently, these are dropped on the floor because I don't know what they mean. The current
49
+ theories include 'Base (with gear)' and 'Stat (with buffs)'.
50
+ * Make SkillList implement enumerable.
51
+ * Character search by name in the constructor. Pending Lodestone's character search not randomly
52
+ omitting people.
53
+
54
+ == Author, License, Etc ==
55
+ Copyright owlmanatt <owlmanatt@gmail.com> 2010. The library is free to use, modify, redistribute,
56
+ sell, or whatever. Go hog wild, just keep this license notice around.
@@ -0,0 +1,202 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+ require 'open-uri'
4
+ require 'json'
5
+
6
+ module FFXIVLodestone
7
+ VERSION = '0.7.0'
8
+
9
+ class Character
10
+ class StatList
11
+ include Enumerable
12
+ attr_reader :stats
13
+
14
+ def initialize(table)
15
+ @stats = {}
16
+
17
+ table.search('tr').each do |tr|
18
+ @stats[tr.children[0].content.strip.downcase.to_sym] = tr.children[2].content.gsub("\302\240",' ').split(' ')[0].to_i
19
+ end
20
+ end
21
+
22
+ def each
23
+ @stats.each {|stat| yield stat}
24
+ end
25
+
26
+ def to_h
27
+ @stats
28
+ end
29
+
30
+ def method_missing(method)
31
+ return @stats[method] if @stats.key? method
32
+ super
33
+ end
34
+ end # StatList
35
+
36
+ class SkillList
37
+ class Skill
38
+ attr_reader :name, :skill_name, :rank, :current_skill_points, :skillpoint_to_next_level
39
+
40
+ def initialize(job,skill_name,rank,cur_sp,skillup_sp)
41
+ @name = job
42
+ @skill_name = skill_name
43
+ @rank = rank
44
+ @current_skill_points = cur_sp
45
+ @skillpoint_to_next_level = skillup_sp
46
+ end # initalize
47
+
48
+ def to_h
49
+ {:name => @name, :skill_name => @skill_name, :rank => @rank, :current_skill_points => @current_skill_points, :skillpoint_to_next_level => @skillpoint_to_next_level}
50
+ end
51
+ end # Skill
52
+
53
+ # Alias the stupid names in Lodestone to class names.
54
+ SKILL_TO_CLASS = {
55
+ 'Hand-to-Hand' => :pugilist,
56
+ 'Sword' => :gladiator,
57
+ 'Axe' => :marauder,
58
+ 'Archery' => :archer,
59
+ 'Polearm' => :lancer,
60
+ 'Thaumaturgy' => :thaumaturge,
61
+ 'Conjury' => :conjurer,
62
+ 'Woodworking' => :carpenter,
63
+ 'Smithing' => :blacksmith,
64
+ 'Armorcraft' => :armorer,
65
+ 'Goldsmithing' => :goldsmith,
66
+ 'Leatherworking' => :leatherworker,
67
+ 'Clothcraft' => :weaver,
68
+ 'Alchemy' => :alchemist,
69
+ 'Cooking' => :culinarian,
70
+ 'Mining' => :miner,
71
+ 'Botany' => :botanist,
72
+ 'Fishing' => :fisher,
73
+ }
74
+
75
+ def initialize(skill_table)
76
+ @skills = {}
77
+
78
+ skill_table.children.each do |skill|
79
+ name = skill.children[0].children[1].content
80
+ if SKILL_TO_CLASS.key? name
81
+ key = SKILL_TO_CLASS[name]
82
+ job = key.to_s.capitalize
83
+ else
84
+ key = name.gsub('-', '_').downcase.to_sym
85
+ job = name
86
+ end
87
+
88
+ # '-' = not leveled (never equipped the class' weapon)
89
+ rank = skill.children[2].search('table tr td[last()]').children.first.content
90
+ rank = (rank.include?('-') ? 0 : rank.to_i)
91
+
92
+ # # '-' = not leveled, otherwise it will be in the format '391 / 1500'
93
+ sp = skill.children[4].search('table tr td[last()]').children.first.content
94
+
95
+ if sp.include? '-'
96
+ current_sp = 0
97
+ levelup_sp = 0
98
+ else
99
+ sp.gsub!("\302\240",'') # this is a &nbsp but it looks ugly in #inspect(), so remove it.
100
+ current_sp = sp.split('/')[0].strip.to_i
101
+ levelup_sp = sp.split('/')[1].strip.to_i
102
+ end
103
+
104
+ @skills[key] = Skill.new(job,name,rank,current_sp,levelup_sp)
105
+ end
106
+ end # initialize
107
+
108
+ # Lists all leveled jobs.
109
+ def list
110
+ list = []
111
+ @skills.each do |name,skill|
112
+ list << skill if skill.rank > 0
113
+ end
114
+
115
+ return list
116
+ end
117
+
118
+ def to_h
119
+ list = {}
120
+ @skills.each {|job,data| list[job] = data.to_h}
121
+
122
+ return list
123
+ end
124
+
125
+ def method_missing(method)
126
+ return @skills[method] if @skills.key? method
127
+ super
128
+ end
129
+
130
+ end # FFXIVLodestone::Character::SkillList
131
+
132
+ attr_reader :skills, :stats, :resistances, :profile
133
+ def initialize(character_id)
134
+ # TODO exception if ID isn't an integer
135
+ # TODO exception if we don't have a valid char ID
136
+ @character_id = character_id
137
+
138
+ doc = Nokogiri::HTML(open("http://lodestone.finalfantasyxiv.com/rc/character/status?cicuid=#{@character_id}", {'Accept-Language' => 'en-us,en;q=0.5', 'Accept-Charset' => 'utf-8;q=0.5'}))
139
+
140
+ # The skills table doesn't have a unqiue ID or class to find it by, so take the first skill lable and go up two elements (table -> tr -> th.mianskill-lable)
141
+ @skills = SkillList.new(doc.search('th.mainskill-label').first.parent.parent)
142
+ @stats = StatList.new(doc.search("div.contents-subheader[contains('Attributes')]").first.next_sibling.next_sibling)
143
+ @resistances = StatList.new(doc.search("div.contents-subheader[contains('Elements')]").first.next_sibling.next_sibling)
144
+
145
+ # The character info box at the top ... actually has a useful ID!
146
+ @profile = {}
147
+ profile = doc.search('#profile-plate2')
148
+ profile.search('tr th').each do |th|
149
+ key = th.content.strip.downcase.gsub(':','').gsub(' ','_').to_sym
150
+ value = th.next_sibling.next_sibling.content.strip.gsub("\302\240",'')
151
+
152
+ # HP/MP/TP are max values. They depend on the currently equipped job and are not very
153
+ # meaningful pieces of data. XP will be handled seperately.
154
+ unless [:hp, :mp, :tp, :experience_points].include? key
155
+ @profile[key] = value
156
+ end
157
+
158
+ if key == :experience_points
159
+ @profile[:current_exp] = value.split('/')[0].to_i
160
+ @profile[:exp_to_next_level] = value.split('/')[1].to_i
161
+ end
162
+ end
163
+
164
+ # Fix this datatype.
165
+ @profile[:physical_level] = @profile[:physical_level].to_i
166
+
167
+ # Parse the character name/world line...
168
+ name_line = profile.search('#charname').first.content.gsub(')','').strip.split(' (')
169
+ @profile[:world] = name_line[1]
170
+ @profile[:first_name] = name_line[0].split(' ')[0]
171
+ @profile[:last_name] = name_line[0].split(' ')[1]
172
+
173
+ # Parse the "Seeker of the Sun Female / Miqo'te" line... fun~
174
+ race_line = profile.search('tr td').first.content.strip.gsub("\302\240",' ').split(' / ')
175
+ @profile[:race] = race_line.pop
176
+
177
+ # horrible array splitting and popping trix. hidoi hidoi!
178
+ race_line = race_line.first.split ' '
179
+ @profile[:gender] = race_line.pop
180
+ @profile[:clan] = race_line.join ' '
181
+ end
182
+
183
+ def name
184
+ "#{@profile[:first_name]} #{@profile[:last_name]}"
185
+ end
186
+
187
+ def to_json
188
+ data = {}
189
+ data.merge!(@profile)
190
+ data[:jobs] = @skills.to_h
191
+ data[:attributes] = @stats.to_h
192
+ data[:resistances] = @resistances.to_h
193
+
194
+ data.to_json
195
+ end
196
+
197
+ def method_missing(method)
198
+ return @profile[method] if @profile.key? method
199
+ super
200
+ end
201
+ end # character
202
+ end # end FFXIVLodestone
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffxiv-lodestone
3
+ version: !ruby/object:Gem::Version
4
+ hash: 3
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 7
9
+ - 0
10
+ version: 0.7.0
11
+ platform: ruby
12
+ authors:
13
+ - owlmanatt
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-23 00:00:00 +00:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: A nice Ruby library for accessing character data on the FFXIV community site. It's a screen scraper, but you can PRETEND you're using something nice.
23
+ email:
24
+ - owlmanatt@gmail.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - lib/ffxiv-lodestone.rb
33
+ - README
34
+ has_rdoc: true
35
+ homepage: http://github.com/OwlManAtt/FFXIV-Lodestone-API
36
+ licenses: []
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ hash: 3
49
+ segments:
50
+ - 0
51
+ version: "0"
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 23
58
+ segments:
59
+ - 1
60
+ - 3
61
+ - 6
62
+ version: 1.3.6
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.7
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Screenscraper for FFXIV character data.
70
+ test_files: []
71
+