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.
- data/README +56 -0
- data/lib/ffxiv-lodestone.rb +202 -0
- 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   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
|
+
|