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.
Files changed (5) hide show
  1. data/HISTORY +16 -0
  2. data/README +24 -10
  3. data/lib/ffxiv-lodestone.rb +51 -41
  4. data/test/spec.rb +30 -21
  5. 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
- chars = FFXIVLodestone::Character.search(:name => 'Lady G', :world => 'Figaro')
48
+ chars = FFXIVLodestone::Character.search(:name => 'Lady G', :world => 'Figaro')
49
49
 
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
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
- character = FFXIVLodestone::Character.new(char_id)
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.skills => SkillList with these methods:
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).
@@ -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.2'
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.gsub("\302\240",' ').split(' ')[0].to_i
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.gsub!("\302\240",'') # this is a &nbsp but it looks ugly in #inspect(), so remove it.
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
- character_id = args
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
- raise ArgumentError, 'No search paremeters were given.' if args.empty?
156
-
157
- if args.key? :id
158
- character_id = args[:id]
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.strip.gsub("\302\240",'')
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.strip.gsub("\302\240",' ').split(' / ')
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
- # TODO This should be more robust ... somehow. The search page uses indexes assigned in
231
- # a dropdown and I just copied them in to a constant. It's going to fuck up if they
232
- # release new realms.
233
- #
234
- # perhaps take an ID or a string...? and don't validate the ID?
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
- world_id = FFXIVLodestone::SERVER_SEARCH_INDEXES[args[:world].downcase.to_sym]
238
- doc = Nokogiri::HTML(get_search_html(args[:name],world_id))
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
- char[:name] = name = name_element.content.strip
252
- char[:id] = name_element.attr('href').gsub('/rc/character/top?cicuid=','').strip.to_i
253
- char[:portrait_thumb_url] = tr.search('td:first table tr td:first img').first.attr('src').strip
254
- char[:world] = tr.search('td:last').last.content.strip
255
-
256
- char
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)
@@ -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: 63
4
+ hash: 61
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 2
10
- version: 0.9.2
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-25 00:00:00 +00:00
18
+ date: 2010-09-26 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency