ffxiv-lodestone 0.9.2 → 0.9.3
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/HISTORY +16 -0
- data/README +24 -10
- data/lib/ffxiv-lodestone.rb +51 -41
- data/test/spec.rb +30 -21
- metadata +4 -4
data/HISTORY
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
== 0.9.3 ==
|
2
|
+
* Character.search() will now return up to 40 results; previously, it was only 20.
|
3
|
+
|
4
|
+
* Character#skills now has an alias - #jobs. Sementically, char.jobs.carpenter makes more sense
|
5
|
+
than char.skills.carpenter - the skill name is carpentry.
|
6
|
+
|
7
|
+
They return exactly the same data, but #skills should be considered deprecated. I may replace it
|
8
|
+
with something else in the future.
|
9
|
+
|
10
|
+
* When specifying :world in Character.search() and Character.new(), you can pass the server's ID
|
11
|
+
number instead of the server's name. You should only use the ID if new servers are opened and
|
12
|
+
the gem has not been updated with the name => ID mapping yet.
|
13
|
+
|
14
|
+
* strip_nbsp and strip_nbsp! are added to String to handle Nokogiri's oddness more cleanly. I hope
|
15
|
+
this doesn't fuck up your application.
|
16
|
+
|
1
17
|
== 0.9.2 ==
|
2
18
|
* Added Character.search() method. It wraps the character search page; an array of hashes will
|
3
19
|
be returned with the resulting characters.
|
data/README
CHANGED
@@ -45,16 +45,20 @@ of these options of you will get an exception.
|
|
45
45
|
wish to use the search method and allow your user to pick the correct character, then load it
|
46
46
|
by ID, as demonstrated by this psuedo-code:
|
47
47
|
|
48
|
-
|
48
|
+
chars = FFXIVLodestone::Character.search(:name => 'Lady G', :world => 'Figaro')
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
if chars.length == 1
|
51
|
+
char_id = chars.first[:id]
|
52
|
+
else
|
53
|
+
# display both on a screen for the user to pick, then load the right character by ID
|
54
|
+
char_id = . . .
|
55
|
+
end
|
56
56
|
|
57
|
-
|
57
|
+
character = FFXIVLodestone::Character.new(char_id)
|
58
|
+
|
59
|
+
Caveat: This will only display the first page of search results (so maximum 40). If the
|
60
|
+
character name you are searching for is _that_ ambiguous, then just fucking delete it and
|
61
|
+
reroll with a sane name.
|
58
62
|
|
59
63
|
== Ghetto API Doc ==
|
60
64
|
The static search method will return a list of hashes. You can use this to avoid ambiguious name
|
@@ -73,7 +77,7 @@ Stuff you can do with a loaded character:
|
|
73
77
|
char.current_exp, char.exp_to_next_level, char.gender, char.race, char.clan, char.character_id,
|
74
78
|
char.portrait_url, char.portrait_thumb_url
|
75
79
|
|
76
|
-
char.
|
80
|
+
char.jobs => SkillList with these methods:
|
77
81
|
.levelled => Array of all skills that have been leveled (ie rank 0 jobs omitted)
|
78
82
|
.pugilist (or any class name) => The skill, regardless of if its been leveled or not
|
79
83
|
(ie you can explicitly ask for a skill to see if it's been leveled or not)
|
@@ -91,6 +95,17 @@ Q. Hey, why doesn't this have HP / MP / TP? They're on the character profile pag
|
|
91
95
|
A. Because those values change based on the currently equipped job. I don't think they're useful
|
92
96
|
pieces of data. If you have a use case for them, please let me know and I will include them.
|
93
97
|
|
98
|
+
Q. I see you have a hard-coded list of server to ID mappings. That's awful! What am I supposed to
|
99
|
+
do when new servers open up?
|
100
|
+
A. Yes, it is quite horrible! Hard-coding the list is less bad than doing another HTTP GET when
|
101
|
+
you use Character.search(), though.
|
102
|
+
|
103
|
+
But I've anticipated your problem. If new worlds open, you can just pass :world as the ID from
|
104
|
+
the search page's dropdown until I update the gem:
|
105
|
+
|
106
|
+
FFXIVLodestone::Character.search(:name => 'Ayeron Lifebloom', :world => 7)
|
107
|
+
FFXIVLodestone::Character.new(:name => 'Ayeron Lifebloom', :world => 7)
|
108
|
+
|
94
109
|
== Contributing ==
|
95
110
|
If you want to fool around with the source code, you can see if you've fucked everything up:
|
96
111
|
|
@@ -102,4 +117,3 @@ If you want to fool around with the source code, you can see if you've fucked ev
|
|
102
117
|
* Figure out what the fuck the stats in parenthesis mean and do something with them.
|
103
118
|
* Currently, these are dropped on the floor because I don't know what they mean. The current
|
104
119
|
theories include 'Base (with gear)' and 'Stat (with buffs)'.
|
105
|
-
* Get the biography field (if its present).
|
data/lib/ffxiv-lodestone.rb
CHANGED
@@ -3,9 +3,25 @@ require 'nokogiri'
|
|
3
3
|
require 'open-uri'
|
4
4
|
require 'json'
|
5
5
|
|
6
|
+
# Nokogiri changes entities to \302\240. This may be OK in most instances,
|
7
|
+
# but at the very least, it makes #inspect() look like shit, which is a headache
|
8
|
+
# for people trying to troubleshoot.
|
9
|
+
class String
|
10
|
+
def strip_nbsp
|
11
|
+
self.strip.gsub("\302\240",' ')
|
12
|
+
end
|
13
|
+
|
14
|
+
def strip_nbsp!
|
15
|
+
before = self.reverse.reverse
|
16
|
+
self.strip!
|
17
|
+
self.gsub!("\302\240",' ')
|
18
|
+
before == self ? nil : self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
6
22
|
module FFXIVLodestone
|
7
23
|
# Gem version.
|
8
|
-
VERSION = '0.9.
|
24
|
+
VERSION = '0.9.3'
|
9
25
|
|
10
26
|
# Accept-language must be sent; their default is Japanese text.
|
11
27
|
HTTP_OPTIONS = {'Accept-Language' => 'en-us,en;q=0.5', 'Accept-Charset' => 'utf-8;q=0.5'}
|
@@ -49,10 +65,7 @@ module FFXIVLodestone
|
|
49
65
|
h
|
50
66
|
}
|
51
67
|
end
|
52
|
-
|
53
|
-
def to_h
|
54
|
-
to_hash
|
55
|
-
end
|
68
|
+
alias :to_h :to_hash # legacy API
|
56
69
|
|
57
70
|
def to_json(args={})
|
58
71
|
self.to_hash.to_json
|
@@ -72,7 +85,7 @@ module FFXIVLodestone
|
|
72
85
|
class StatList < Hash
|
73
86
|
def initialize(table)
|
74
87
|
table.search('tr').each do |tr|
|
75
|
-
self[tr.children[0].content.strip.downcase.to_sym] = tr.children[2].content.
|
88
|
+
self[tr.children[0].content.strip.downcase.to_sym] = tr.children[2].content.strip_nbsp.split(' ')[0].to_i
|
76
89
|
end
|
77
90
|
end
|
78
91
|
|
@@ -124,7 +137,7 @@ module FFXIVLodestone
|
|
124
137
|
current_sp = 0
|
125
138
|
levelup_sp = 0
|
126
139
|
else
|
127
|
-
sp.
|
140
|
+
sp.strip_nbsp!
|
128
141
|
current_sp = sp.split('/')[0].strip.to_i
|
129
142
|
levelup_sp = sp.split('/')[1].strip.to_i
|
130
143
|
end
|
@@ -148,21 +161,19 @@ module FFXIVLodestone
|
|
148
161
|
end # FFXIVLodestone::Character::SkillList
|
149
162
|
|
150
163
|
attr_reader :skills, :stats, :resistances, :profile
|
164
|
+
alias :jobs :skills
|
151
165
|
def initialize(args={})
|
152
|
-
unless args.class == Hash
|
153
|
-
|
166
|
+
args = {:id => args} unless args.class == Hash
|
167
|
+
raise ArgumentError, 'No search paremeters were given.' if args.empty?
|
168
|
+
|
169
|
+
if args.key? :id
|
170
|
+
character_id = args[:id]
|
171
|
+
raise ArgumentError, 'No other arguments may be specified in conjunction with :id.' if args.size > 1
|
154
172
|
else
|
155
|
-
|
156
|
-
|
157
|
-
if
|
158
|
-
|
159
|
-
raise ArgumentError, 'No other arguments may be specified in conjunction with :id.' if args.size > 1
|
160
|
-
else
|
161
|
-
characters = Character.search(args)
|
162
|
-
raise NotFoundException, 'Character search yielded no results.' if characters.empty?
|
163
|
-
raise AmbiguousNameError, 'Multiple characters matched that name.' if characters.size > 1
|
164
|
-
character_id = characters.first[:id]
|
165
|
-
end
|
173
|
+
characters = Character.search(args)
|
174
|
+
raise NotFoundException, 'Character search yielded no results.' if characters.empty?
|
175
|
+
raise AmbiguousNameError, 'Multiple characters matched that name.' if characters.size > 1
|
176
|
+
character_id = characters.first[:id]
|
166
177
|
end
|
167
178
|
|
168
179
|
@character_id = character_id
|
@@ -183,7 +194,7 @@ module FFXIVLodestone
|
|
183
194
|
profile = doc.search('#profile-plate2')
|
184
195
|
profile.search('tr th').each do |th|
|
185
196
|
key = th.content.strip.downcase.gsub(':','').gsub(' ','_').to_sym
|
186
|
-
value = th.next_sibling.next_sibling.content.
|
197
|
+
value = th.next_sibling.next_sibling.content.strip_nbsp
|
187
198
|
|
188
199
|
# HP/MP/TP are max values. They depend on the currently equipped job and are not very
|
189
200
|
# meaningful pieces of data. XP will be handled seperately.
|
@@ -207,7 +218,7 @@ module FFXIVLodestone
|
|
207
218
|
@profile[:last_name] = name_line[0].split(' ')[1]
|
208
219
|
|
209
220
|
# Parse the "Seeker of the Sun Female / Miqo'te" line... fun~
|
210
|
-
race_line = profile.search('tr td').first.content.
|
221
|
+
race_line = profile.search('tr td').first.content.strip_nbsp.split(' / ')
|
211
222
|
@profile[:race] = race_line.pop
|
212
223
|
|
213
224
|
# horrible array splitting and popping trix. hidoi hidoi!
|
@@ -227,33 +238,32 @@ module FFXIVLodestone
|
|
227
238
|
raise ArgumentError, ':name and :world must both be specified to perform a character search.'
|
228
239
|
end
|
229
240
|
|
230
|
-
#
|
231
|
-
#
|
232
|
-
#
|
233
|
-
|
234
|
-
|
235
|
-
raise ArgumentError, 'Unknown world server.' unless FFXIVLodestone::SERVER_SEARCH_INDEXES.key? args[:world].downcase.to_sym
|
241
|
+
# :world can be passed as a string ('Figaro') or as the integer used by the search page (7).
|
242
|
+
# This is so the library is not completely useless when new worlds are added - developers can
|
243
|
+
# fall back to the integers until the gem is updated.
|
244
|
+
if args[:world].class == String
|
245
|
+
raise ArgumentError, 'Unknown world server.' unless FFXIVLodestone::SERVER_SEARCH_INDEXES.key? args[:world].downcase.to_sym
|
236
246
|
|
237
|
-
|
238
|
-
|
247
|
+
world_id = FFXIVLodestone::SERVER_SEARCH_INDEXES[args[:world].downcase.to_sym]
|
248
|
+
else
|
249
|
+
world_id = args[:world].to_i # force it to an int to prevent any funny business.
|
250
|
+
end
|
239
251
|
|
252
|
+
doc = Nokogiri::HTML(get_search_html(args[:name],world_id))
|
240
253
|
results = doc.search("table.contents-table1 tr th[contains('Character Name')]")
|
241
254
|
return [] if results.empty? # No results = no results table header.
|
242
255
|
|
243
|
-
# Skip index 0 - it's the header row and it's very different from the character <tr>s.
|
244
256
|
results = results.last.parent.parent
|
245
257
|
results.children.first.remove # discard the table headers
|
246
|
-
|
258
|
+
|
247
259
|
results.children.map do |tr|
|
248
|
-
char = {}
|
249
|
-
|
250
260
|
name_element = tr.search('td:first table tr td:last a').first
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
261
|
+
{
|
262
|
+
:id => name_element.attr('href').gsub('/rc/character/top?cicuid=','').strip.to_i,
|
263
|
+
:name => name_element.content.strip,
|
264
|
+
:portrait_thumb_url => tr.search('td:first table tr td:first img').first.attr('src').strip,
|
265
|
+
:world => tr.search('td:last').last.content.strip
|
266
|
+
}
|
257
267
|
end
|
258
268
|
end # search
|
259
269
|
|
@@ -285,7 +295,7 @@ module FFXIVLodestone
|
|
285
295
|
|
286
296
|
# Another method to redefine in the test file...
|
287
297
|
def self.get_search_html(name,world_id)
|
288
|
-
open(URI.encode("http://lodestone.finalfantasyxiv.com/rc/search/search?tgt=77&q=#{name}&cw=#{world_id}"), FFXIVLodestone::HTTP_OPTIONS)
|
298
|
+
open(URI.encode("http://lodestone.finalfantasyxiv.com/rc/search/search?tgt=77&q=#{name}&cw=#{world_id}&num=40"), FFXIVLodestone::HTTP_OPTIONS)
|
289
299
|
end
|
290
300
|
|
291
301
|
def generate_portrait_urls(url)
|
data/test/spec.rb
CHANGED
@@ -14,6 +14,33 @@ class FFXIVLodestone::Character
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
describe 'Character.search' do
|
18
|
+
it 'should raise an argument error' do
|
19
|
+
should.raise(ArgumentError) { FFXIVLodestone::Character.search() }
|
20
|
+
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:irrelevant_key => 'value') }
|
21
|
+
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:world => 'Figaro') }
|
22
|
+
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:name => 'Ayeron Lifebloom') }
|
23
|
+
should.raise(ArgumentError) { FFXIVLodestone::Character.search(12345) }
|
24
|
+
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:name => 'Ayeron Lifebloom', :world => 'FAKE SERVER NAME') }
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should accept server as an integer' do
|
28
|
+
should.not.raise(ArgumentError) { FFXIVLodestone::Character.search(:name => 'Ayeron Lifebloom', :world => 7) }
|
29
|
+
|
30
|
+
c = FFXIVLodestone::Character.new(:name => 'Ayeron Lifebloom', :world => 7)
|
31
|
+
c.character_id.should.equal 1502635
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should be empty' do
|
35
|
+
FFXIVLodestone::Character.search(:name => 'ABLOO BLOO UGUU', :world => 'Selbina').should.equal([])
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should list characters' do
|
39
|
+
FFXIVLodestone::Character.search(:name => 'Lady', :world => 'Selbina').should.equal(
|
40
|
+
[{:world=>"Selbina", :portrait_thumb_url=>"http://static.finalfantasyxiv.com/csnap/v05m_ss_7bd793d507a92d2c415b306a83280d19.png?gediwpzz", :name=>"Lady Simmons", :id=>1015990}, {:world=>"Selbina", :portrait_thumb_url=>"http://static.finalfantasyxiv.com/csnap/14fij_ss_f19cd042628445e22a17a9362cb91f26.png?gee0yejc", :name=>"Shukick Fairlady", :id=>1195603}]
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
17
44
|
describe 'Character(invalid)' do
|
18
45
|
it 'is an invalid id' do
|
19
46
|
should.raise(FFXIVLodestone::Character::NotFoundException) { FFXIVLodestone::Character.new('invalid') }
|
@@ -90,6 +117,9 @@ describe 'Character(1015990)' do
|
|
90
117
|
@char.skills.carpenter.rank.should.equal 5
|
91
118
|
@char.skills.carpenter.current_skill_points.should.equal 305
|
92
119
|
@char.skills.carpenter.skillpoint_to_next_level.should.equal 1500
|
120
|
+
|
121
|
+
# make sure the alias is working!
|
122
|
+
@char.jobs == @char.skills
|
93
123
|
end
|
94
124
|
|
95
125
|
it 'should list all leveled jobs' do
|
@@ -104,24 +134,3 @@ LOLHEREDOC
|
|
104
134
|
JSON.parse(@char.to_json).should.equal JSON.parse(json)
|
105
135
|
end
|
106
136
|
end
|
107
|
-
|
108
|
-
describe 'Character.search' do
|
109
|
-
it 'should raise an argument error' do
|
110
|
-
should.raise(ArgumentError) { FFXIVLodestone::Character.search() }
|
111
|
-
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:irrelevant_key => 'value') }
|
112
|
-
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:world => 'Figaro') }
|
113
|
-
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:name => 'Ayeron Lifebloom') }
|
114
|
-
should.raise(ArgumentError) { FFXIVLodestone::Character.search(12345) }
|
115
|
-
should.raise(ArgumentError) { FFXIVLodestone::Character.search(:name => 'Ayeron Lifebloom', :world => 'FAKE SERVER NAME') }
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'should be empty' do
|
119
|
-
FFXIVLodestone::Character.search(:name => 'ABLOO BLOO UGUU', :world => 'Selbina').should.equal([])
|
120
|
-
end
|
121
|
-
|
122
|
-
it 'should list characters' do
|
123
|
-
FFXIVLodestone::Character.search(:name => 'Lady', :world => 'Selbina').should.equal(
|
124
|
-
[{:world=>"Selbina", :portrait_thumb_url=>"http://static.finalfantasyxiv.com/csnap/v05m_ss_7bd793d507a92d2c415b306a83280d19.png?gediwpzz", :name=>"Lady Simmons", :id=>1015990}, {:world=>"Selbina", :portrait_thumb_url=>"http://static.finalfantasyxiv.com/csnap/14fij_ss_f19cd042628445e22a17a9362cb91f26.png?gee0yejc", :name=>"Shukick Fairlady", :id=>1195603}]
|
125
|
-
)
|
126
|
-
end
|
127
|
-
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffxiv-lodestone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 61
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
9
|
+
- 3
|
10
|
+
version: 0.9.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- owlmanatt
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-09-
|
18
|
+
date: 2010-09-26 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|