ffxiv-lodestone 0.9.1 → 0.9.2

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 CHANGED
@@ -1,3 +1,8 @@
1
+ == 0.9.2 ==
2
+ * Added Character.search() method. It wraps the character search page; an array of hashes will
3
+ be returned with the resulting characters.
4
+ * The full-size portrait URL and thumbnail portrait URLs are now scraped in.
5
+
1
6
  == 0.9.1 ==
2
7
  * Rails compatability! The #to_json() methods now take an argument (which is promptly dropped on
3
8
  the floor) to make ActiveSupport happy.
data/README CHANGED
@@ -41,17 +41,37 @@ of these options of you will get an exception.
41
41
 
42
42
  3. Character search has no way of specifying that you want to search for an exact name. If you
43
43
  look up a character with a simple name, like 'Ab Lo', you get multiple search results. In the
44
- event that the search has more than one result, an AmbiguousNameError will be raised. You will
45
- have to request that character by the ID.
44
+ event that the search has more than one result, an AmbiguousNameError will be raised. You may
45
+ wish to use the search method and allow your user to pick the correct character, then load it
46
+ by ID, as demonstrated by this psuedo-code:
47
+
48
+ chars = FFXIVLodestone::Character.search(:name => 'Lady G', :world => 'Figaro')
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
56
+
57
+ character = FFXIVLodestone::Character.new(char_id)
46
58
 
47
59
  == Ghetto API Doc ==
48
- Stuff you can do:
60
+ The static search method will return a list of hashes. You can use this to avoid ambiguious name
61
+ errors. Each hash in the array will have id, name, world, and portrait_thumb_url.
62
+
63
+ results = Character.search(:name => 'Lady G', :world => 'Figaro')
64
+
65
+ You can results.to_json or results.to_yaml this without any problems.
66
+
67
+ Stuff you can do with a loaded character:
49
68
 
50
69
  char.to_json => String containing a JSON representation of the character / jobs.
51
70
  char.to_yaml => YAML representation of the character (EXPERIMENTAL).
52
71
 
53
72
  char.name, char.world, char.nameday, char.guardian, char.starting_city, char.physical_level,
54
- char.current_exp, char.exp_to_next_level, char.gender, char.race, char.clan, char.character_id
73
+ char.current_exp, char.exp_to_next_level, char.gender, char.race, char.clan, char.character_id,
74
+ char.portrait_url, char.portrait_thumb_url
55
75
 
56
76
  char.skills => SkillList with these methods:
57
77
  .levelled => Array of all skills that have been leveled (ie rank 0 jobs omitted)
@@ -63,8 +83,8 @@ Stuff you can do:
63
83
  char.stats => StatList with these methods:
64
84
  .strength (or any stat name) => Integer value of the state
65
85
 
66
- A skill object has these methods:
67
- name, skill_name, rank, skillpoint_to_next_level, current_skill_points
86
+ A skill object has these methods:
87
+ name, skill_name, rank, skillpoint_to_next_level, current_skill_points
68
88
 
69
89
  == FAQ ==
70
90
  Q. Hey, why doesn't this have HP / MP / TP? They're on the character profile page!
@@ -82,4 +102,4 @@ If you want to fool around with the source code, you can see if you've fucked ev
82
102
  * Figure out what the fuck the stats in parenthesis mean and do something with them.
83
103
  * Currently, these are dropped on the floor because I don't know what they mean. The current
84
104
  theories include 'Base (with gear)' and 'Stat (with buffs)'.
85
- * Get the portrait URL & biography field (if present).
105
+ * Get the biography field (if its present).
@@ -5,7 +5,7 @@ require 'json'
5
5
 
6
6
  module FFXIVLodestone
7
7
  # Gem version.
8
- VERSION = '0.9.1'
8
+ VERSION = '0.9.2'
9
9
 
10
10
  # Accept-language must be sent; their default is Japanese text.
11
11
  HTTP_OPTIONS = {'Accept-Language' => 'en-us,en;q=0.5', 'Accept-Charset' => 'utf-8;q=0.5'}
@@ -152,21 +152,21 @@ module FFXIVLodestone
152
152
  unless args.class == Hash
153
153
  character_id = args
154
154
  else
155
+ raise ArgumentError, 'No search paremeters were given.' if args.empty?
156
+
155
157
  if args.key? :id
156
158
  character_id = args[:id]
157
-
158
159
  raise ArgumentError, 'No other arguments may be specified in conjunction with :id.' if args.size > 1
159
160
  else
160
- if args.key? :name and args.key? :world
161
- character_id = determine_character_id(args[:name],args[:world])
162
- else
163
- raise ArgumentError, 'Neither :id or :name && :world have been specified.'
164
- end
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
165
  end
166
166
  end
167
167
 
168
168
  @character_id = character_id
169
- doc = Nokogiri::HTML(get_profile_html(@character_id))
169
+ doc = Nokogiri::HTML(Character.get_profile_html(@character_id))
170
170
 
171
171
  # Did we get an error page? Invalid ID, etc.
172
172
  if !((doc.search('head title').first.content.match /error/i) == nil)
@@ -215,8 +215,47 @@ module FFXIVLodestone
215
215
  @profile[:gender] = race_line.pop
216
216
  @profile[:clan] = race_line.join ' '
217
217
 
218
+ @profile.merge! generate_portrait_urls(doc.search('div.image-mount-image img').first.attr('src').strip)
219
+
218
220
  @profile[:character_id] = @character_id.to_i
219
221
  end
222
+
223
+ # FFXIVLodestone::Character.search(:name => 'Character Name', :world => 'Server') => Array
224
+ def self.search(args={})
225
+ raise ArgumentError, 'Search parameters must be hash.' unless args.class == Hash
226
+ unless args.key? :name and args.key? :world
227
+ raise ArgumentError, ':name and :world must both be specified to perform a character search.'
228
+ end
229
+
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
236
+
237
+ world_id = FFXIVLodestone::SERVER_SEARCH_INDEXES[args[:world].downcase.to_sym]
238
+ doc = Nokogiri::HTML(get_search_html(args[:name],world_id))
239
+
240
+ results = doc.search("table.contents-table1 tr th[contains('Character Name')]")
241
+ return [] if results.empty? # No results = no results table header.
242
+
243
+ # Skip index 0 - it's the header row and it's very different from the character <tr>s.
244
+ results = results.last.parent.parent
245
+ results.children.first.remove # discard the table headers
246
+
247
+ results.children.map do |tr|
248
+ char = {}
249
+
250
+ 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
257
+ end
258
+ end # search
220
259
 
221
260
  # Returns first name / last name seperated by a space.
222
261
  def name
@@ -240,32 +279,26 @@ module FFXIVLodestone
240
279
 
241
280
  protected
242
281
  # This method can be redefined in a test class.
243
- def get_profile_html(id)
282
+ def self.get_profile_html(id)
244
283
  open("http://lodestone.finalfantasyxiv.com/rc/character/status?cicuid=#{id}", FFXIVLodestone::HTTP_OPTIONS)
245
284
  end
246
285
 
247
286
  # Another method to redefine in the test file...
248
- def get_search_html(name,world_id)
287
+ def self.get_search_html(name,world_id)
249
288
  open(URI.encode("http://lodestone.finalfantasyxiv.com/rc/search/search?tgt=77&q=#{name}&cw=#{world_id}"), FFXIVLodestone::HTTP_OPTIONS)
250
289
  end
251
290
 
252
- def determine_character_id(name,world)
253
- # TODO This should be more robust ... somehow. The search page uses indexes assigned in
254
- # a dropdown and I just copied them in to a constant. It's going to fuck up if they
255
- # release new realms.
256
- raise ArgumentError, 'Unknown world server.' unless FFXIVLodestone::SERVER_SEARCH_INDEXES.key? world.downcase.to_sym
257
- doc = Nokogiri::HTML(get_search_html(name,FFXIVLodestone::SERVER_SEARCH_INDEXES[world.downcase.to_sym]))
291
+ def generate_portrait_urls(url)
292
+ url_list = {}
258
293
 
259
- # Results table should have two TRs if the search has found a unique character -
260
- # one TR for headers and one TR with the character info. If nothing was found, the
261
- # resul table is not even present.
262
- results = doc.search("table.contents-table1 tr th[contains('Character Name')]")
263
- raise NotFoundException, 'Character search yielded no results.' if results.empty?
264
-
265
- results = results.last.parent.parent
266
- raise AmbiguousNameError, 'Character search turned up multiple results.' if results.children.length > 2
294
+ a = URI.parse(url)
295
+ a.query = nil
296
+ url_list[:portrait_url] = a.to_s
297
+
298
+ a.path = a.path.gsub(/\/([A-Z0-9]+)_m_/i,'/\1_ss_')
299
+ url_list[:portrait_thumb_url] = a.to_s
267
300
 
268
- return results.search('tr:last a').first.attr('href').gsub('/rc/character/top?cicuid=','')
301
+ return url_list
269
302
  end
270
303
  end # character
271
304
  end # end FFXIVLodestone
@@ -0,0 +1,956 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2
+ <html>
3
+ <head>
4
+
5
+ <meta http-equiv="content-Type" content="text/html; charset=UTF-8" />
6
+ <meta http-equiv="content-Style-type" content="text/css" />
7
+ <meta http-equiv="content-Script-type" content="text/javascript" />
8
+
9
+ <link rel="shortcut icon" href="http://static.finalfantasyxiv.com/favicon.ico">
10
+
11
+ <link rel="stylesheet" type="text/css" href="http://static.finalfantasyxiv.com/global/css/basic.css?_v=4mk" /><link rel="stylesheet" type="text/css" href="http://static.finalfantasyxiv.com/global/css/common/default/base.css?_v=4mk" /><link rel="stylesheet" type="text/css" href="http://static.finalfantasyxiv.com/global/css/en/default/base.css?_v=4mk" /><link rel="stylesheet" type="text/css" href="http://static.finalfantasyxiv.com/community/css/common/default/base.css?_v=4mk" /><link rel="stylesheet" type="text/css" href="http://static.finalfantasyxiv.com/community/css/en/default/base.css?_v=4mk" /><script type="text/javascript" src="http://static.finalfantasyxiv.com/js/conf/init.js?_v=4mk"></script><script type="text/javascript">
12
+ var userToken = '';
13
+ </script>
14
+
15
+ <style type="text/css">
16
+ .tmp-inputW {
17
+ width : 100%;
18
+ }
19
+ </style>
20
+ <title>
21
+ FINAL FANTASY XIV, The Lodestone</title>
22
+
23
+ </head>
24
+ <body>
25
+
26
+ <!-- #bg -->
27
+
28
+ <div id="bg" class="search logout characterpage other">
29
+
30
+ <!-- #bg2 -->
31
+ <div id="bg2" class=" characterpage">
32
+
33
+ <!-- #wrap -->
34
+ <div id="wrap">
35
+
36
+ <!-- コーポレートヘッダー -->
37
+ <script type="text/javascript" src="http://static.finalfantasyxiv.com/global/js/mini/common/sqexheader.js?_v=4mk"></script><!-- #wrap-header -->
38
+ <div id="wrap-header">
39
+
40
+ <form id="logoutForm" name="logoutForm" action="/rc/logout" method="POST" class="hide"><input type="hidden" name="_ut" value="" id="logoutForm__ut"/></form>
41
+
42
+
43
+
44
+ <script type="text/javascript">
45
+ <!-- ログアウト -->
46
+ function toLogout() {
47
+ document.getElementById("logoutForm").submit();
48
+ };
49
+
50
+ <!-- 現在のタイムゾーンID -->
51
+ var currentTimezoneID = 'CST';
52
+ </script>
53
+
54
+ <div id="wrap-header-image">
55
+
56
+ <!-- ロゴ -->
57
+ <div id="wrap-header-logo"></div>
58
+
59
+ <!-- ログインしている時だけログアウトボタンを表示 -->
60
+ <!-- タイムゾーンのクッキーが未設定の場合 -->
61
+ <!-- タイムゾーンポップアップ -->
62
+ <div id="timezonePopup">
63
+ <div id="timezonePopupMsg">
64
+ Current Time Zone:<br />(GMT-06:00) Central Time (US and Canada)</div>
65
+ <div id="timezonePopupBtn">
66
+ <a href="javascript:setCurrentTimeZone()" id="btSetTimezone" class="button" style="float:left"></a>
67
+ <a href="javascript:showTimeZoneWindow()" id="btChangeTimezone" class="button" style="float:left"></a>
68
+ </div>
69
+ </div><!-- タイムゾーンポップアップ -->
70
+ </div>
71
+
72
+ <!-- 簡易検索エリア -->
73
+ <div id="wrap-header-news">
74
+ <form id="headerSearchForm" name="headerSearchForm" action="/rc/search/search" method="GET"><div id="simple-search">
75
+ <input type="text" name="q" maxlength="100" value="" id="simple-search-input" class=""/><a id="btSimpleSearch" class="button" href="javascript:;" onclick="document.getElementById('headerSearchForm').submit()"></a>
76
+ <a id="btSimpleSearchChara" class="button" href="/rc/search/characterForm"></a>
77
+ </div>
78
+ </form>
79
+
80
+
81
+
82
+ <div class="floatEnd"></div>
83
+ </div><!-- 簡易検索エリア -->
84
+ </div><!-- #wrap-header -->
85
+
86
+ <!-- #wrap-body -->
87
+ <div id="wrap-body">
88
+
89
+ <!-- ======================================================================= -->
90
+ <!-- サイドメニュー -->
91
+ <!-- ======================================================================= -->
92
+ <!--◎◎◎
93
+ 画面左に表示される縦のメニューです。内部で状態を切り替えられるようにしてください。
94
+ ログイン、ログアウト、マイページ、プレイヤーサイトなど
95
+ ◎◎◎-->
96
+
97
+ <div id="menu">
98
+
99
+ <!-- ログインボタン -->
100
+ <div id="btLogin">
101
+ <a href="https://secure.finalfantasyxiv.com/rc/login" class="button"></a>
102
+ </div>
103
+ <div id="mainmenu">
104
+ <!-- プレイヤーズページトップへ -->
105
+ <div class="menu-lv1">
106
+ <a class="menu-btLv1" id="btMenuGotoPlayersTop" href="/pl/index.html"></a>
107
+ </div>
108
+
109
+ <!-- メインメニュー生成開始 -->
110
+ <div class="menu-lv1">
111
+ <a class="menu-btLv1" id="btMenuInformation" href="/pl/news/information" target=""></a>
112
+ </div>
113
+ <!-- menu-lv1 -->
114
+ <div class="menu-lv1">
115
+ <a class="menu-btLv1-hasChild" id="btMenuGameStart" href="javascript:;"></a>
116
+ <!-- menu-lv1-body -->
117
+ <div class="menu-lv1-body">
118
+ <!-- menu-lv2 -->
119
+ <div class="menu-lv2">
120
+ <!-- menu-slide -->
121
+ <div class="menu-slide">
122
+ <a class="menu-btLv2" href="/pl/envi/envi01.html">
123
+ <div class="menu-btLv2-header"></div>
124
+ <div class="menu-btLv2-body">
125
+ <div class="menu-btLv2-inner">Required Specifications</div>
126
+ </div>
127
+ <div class="menu-btLv2-footer"></div>
128
+ </a>
129
+ </div><!-- menu-slide -->
130
+ <!-- menu-slide -->
131
+ <div class="menu-slide">
132
+ <a class="menu-btLv2" href="/pl/envi/envi02.html">
133
+ <div class="menu-btLv2-header"></div>
134
+ <div class="menu-btLv2-body">
135
+ <div class="menu-btLv2-inner">Installation</div>
136
+ </div>
137
+ <div class="menu-btLv2-footer"></div>
138
+ </a>
139
+ </div><!-- menu-slide -->
140
+ <!-- menu-slide -->
141
+ <div class="menu-slide">
142
+ <a class="menu-btLv2" href="/pl/envi/envi03.html">
143
+ <div class="menu-btLv2-header"></div>
144
+ <div class="menu-btLv2-body">
145
+ <div class="menu-btLv2-inner">Getting Ready</div>
146
+ </div>
147
+ <div class="menu-btLv2-footer"></div>
148
+ </a>
149
+ </div><!-- menu-slide -->
150
+ <!-- menu-slide -->
151
+ <div class="menu-slide">
152
+ <a class="menu-btLv2" href="/pl/envi/envi04.html">
153
+ <div class="menu-btLv2-header"></div>
154
+ <div class="menu-btLv2-body">
155
+ <div class="menu-btLv2-inner">Fees & Options</div>
156
+ </div>
157
+ <div class="menu-btLv2-footer"></div>
158
+ </a>
159
+ </div><!-- menu-slide -->
160
+ <!-- menu-slide -->
161
+ <div class="menu-slide">
162
+ <a class="menu-btLv2" href="/pl/envi/envi05.html">
163
+ <div class="menu-btLv2-header"></div>
164
+ <div class="menu-btLv2-body">
165
+ <div class="menu-btLv2-inner">Settings</div>
166
+ </div>
167
+ <div class="menu-btLv2-footer"></div>
168
+ </a>
169
+ </div><!-- menu-slide -->
170
+ </div><!-- menu-lv2 -->
171
+ </div><!-- menu-lv1-body -->
172
+ <div class="menu-lv1-footer"></div>
173
+ </div><!-- menu-lv1 -->
174
+ <!-- menu-lv1 -->
175
+ <div class="menu-lv1">
176
+ <a class="menu-btLv1-hasChild" id="btMenuPlayGuide" href="javascript:;"></a>
177
+ <!-- menu-lv1-body -->
178
+ <div class="menu-lv1-body">
179
+ <!-- menu-lv2 -->
180
+ <div class="menu-lv2">
181
+ <!-- menu-slide -->
182
+ <div class="menu-slide">
183
+ <a class="menu-btLv2-hasChild" href="javascript:;">
184
+ <div class="menu-btLv2-header"></div>
185
+ <div class="menu-btLv2-body">
186
+ <div class="menu-btLv2-inner">Basic Controls</div>
187
+ </div>
188
+ <div class="menu-btLv2-footer"></div>
189
+ </a>
190
+ <!-- menu-lvx -->
191
+ <div class="menu-lvx">
192
+ <div class="menu-lvx-header"></div>
193
+ <div class="menu-lvx-body">
194
+ <!-- menu-slide -->
195
+ <div class="menu-slide">
196
+ <a class="menu-btLvx" href="/pl/guide/basis01.html">
197
+ <div class="menu-btLvx-header"></div>
198
+ <div class="menu-btLvx-body">
199
+ <div class="menu-btLvx-inner">Controlling Your Character</div>
200
+ </div>
201
+ <div class="menu-btLvx-footer"></div>
202
+ </a>
203
+ </div><!-- menu-slide -->
204
+ <!-- menu-slide -->
205
+ <div class="menu-slide">
206
+ <a class="menu-btLvx" href="/pl/guide/basis02.html">
207
+ <div class="menu-btLvx-header"></div>
208
+ <div class="menu-btLvx-body">
209
+ <div class="menu-btLvx-inner">Launching & Closing the Game</div>
210
+ </div>
211
+ <div class="menu-btLvx-footer"></div>
212
+ </a>
213
+ </div><!-- menu-slide -->
214
+ </div>
215
+ <div class="menu-lvx-footer"></div>
216
+ </div><!-- menu-lvx -->
217
+ </div><!-- menu-slide -->
218
+ <!-- menu-slide -->
219
+ <div class="menu-slide">
220
+ <a class="menu-btLv2-hasChild" href="javascript:;">
221
+ <div class="menu-btLv2-header"></div>
222
+ <div class="menu-btLv2-body">
223
+ <div class="menu-btLv2-inner">Beginning the Game</div>
224
+ </div>
225
+ <div class="menu-btLv2-footer"></div>
226
+ </a>
227
+ <!-- menu-lvx -->
228
+ <div class="menu-lvx">
229
+ <div class="menu-lvx-header"></div>
230
+ <div class="menu-lvx-body">
231
+ <!-- menu-slide -->
232
+ <div class="menu-slide">
233
+ <a class="menu-btLvx" href="/pl/guide/start01.html">
234
+ <div class="menu-btLvx-header"></div>
235
+ <div class="menu-btLvx-body">
236
+ <div class="menu-btLvx-inner">Character Creation & World Selection</div>
237
+ </div>
238
+ <div class="menu-btLvx-footer"></div>
239
+ </a>
240
+ </div><!-- menu-slide -->
241
+ <!-- menu-slide -->
242
+ <div class="menu-slide">
243
+ <a class="menu-btLvx" href="/pl/guide/start02.html">
244
+ <div class="menu-btLvx-header"></div>
245
+ <div class="menu-btLvx-body">
246
+ <div class="menu-btLvx-inner">Getting Started</div>
247
+ </div>
248
+ <div class="menu-btLvx-footer"></div>
249
+ </a>
250
+ </div><!-- menu-slide -->
251
+ </div>
252
+ <div class="menu-lvx-footer"></div>
253
+ </div><!-- menu-lvx -->
254
+ </div><!-- menu-slide -->
255
+ <!-- menu-slide -->
256
+ <div class="menu-slide">
257
+ <a class="menu-btLv2" href="/pl/guide/gamescreen01.html">
258
+ <div class="menu-btLv2-header"></div>
259
+ <div class="menu-btLv2-body">
260
+ <div class="menu-btLv2-inner">The Game Screen</div>
261
+ </div>
262
+ <div class="menu-btLv2-footer"></div>
263
+ </a>
264
+ </div><!-- menu-slide -->
265
+ <!-- menu-slide -->
266
+ <div class="menu-slide">
267
+ <a class="menu-btLv2" href="/pl/guide/guildleve01.html">
268
+ <div class="menu-btLv2-header"></div>
269
+ <div class="menu-btLv2-body">
270
+ <div class="menu-btLv2-inner">Guildleves</div>
271
+ </div>
272
+ <div class="menu-btLv2-footer"></div>
273
+ </a>
274
+ </div><!-- menu-slide -->
275
+ <!-- menu-slide -->
276
+ <div class="menu-slide">
277
+ <a class="menu-btLv2" href="/pl/guide/battle01.html">
278
+ <div class="menu-btLv2-header"></div>
279
+ <div class="menu-btLv2-body">
280
+ <div class="menu-btLv2-inner">Battle & Being KO'd</div>
281
+ </div>
282
+ <div class="menu-btLv2-footer"></div>
283
+ </a>
284
+ </div><!-- menu-slide -->
285
+ <!-- menu-slide -->
286
+ <div class="menu-slide">
287
+ <a class="menu-btLv2-hasChild" href="javascript:;">
288
+ <div class="menu-btLv2-header"></div>
289
+ <div class="menu-btLv2-body">
290
+ <div class="menu-btLv2-inner">Playing With Others</div>
291
+ </div>
292
+ <div class="menu-btLv2-footer"></div>
293
+ </a>
294
+ <!-- menu-lvx -->
295
+ <div class="menu-lvx">
296
+ <div class="menu-lvx-header"></div>
297
+ <div class="menu-lvx-body">
298
+ <!-- menu-slide -->
299
+ <div class="menu-slide">
300
+ <a class="menu-btLvx" href="/pl/guide/comm01.html">
301
+ <div class="menu-btLvx-header"></div>
302
+ <div class="menu-btLvx-body">
303
+ <div class="menu-btLvx-inner">Means of Communication</div>
304
+ </div>
305
+ <div class="menu-btLvx-footer"></div>
306
+ </a>
307
+ </div><!-- menu-slide -->
308
+ <!-- menu-slide -->
309
+ <div class="menu-slide">
310
+ <a class="menu-btLvx" href="/pl/guide/comm02.html">
311
+ <div class="menu-btLvx-header"></div>
312
+ <div class="menu-btLvx-body">
313
+ <div class="menu-btLvx-inner">Parties</div>
314
+ </div>
315
+ <div class="menu-btLvx-footer"></div>
316
+ </a>
317
+ </div><!-- menu-slide -->
318
+ </div>
319
+ <div class="menu-lvx-footer"></div>
320
+ </div><!-- menu-lvx -->
321
+ </div><!-- menu-slide -->
322
+ <!-- menu-slide -->
323
+ <div class="menu-slide">
324
+ <a class="menu-btLv2-hasChild" href="javascript:;">
325
+ <div class="menu-btLv2-header"></div>
326
+ <div class="menu-btLv2-body">
327
+ <div class="menu-btLv2-inner">Synthesis</div>
328
+ </div>
329
+ <div class="menu-btLv2-footer"></div>
330
+ </a>
331
+ <!-- menu-lvx -->
332
+ <div class="menu-lvx">
333
+ <div class="menu-lvx-header"></div>
334
+ <div class="menu-lvx-body">
335
+ <!-- menu-slide -->
336
+ <div class="menu-slide">
337
+ <a class="menu-btLvx-hasChild" href="javascript:;">
338
+ <div class="menu-btLvx-header"></div>
339
+ <div class="menu-btLvx-body">
340
+ <div class="menu-btLvx-inner">Leatherworking</div>
341
+ </div>
342
+ <div class="menu-btLvx-footer"></div>
343
+ </a>
344
+ <!-- menu-lvx -->
345
+ <div class="menu-lvx">
346
+ <div class="menu-lvx-header"></div>
347
+ <div class="menu-lvx-body">
348
+ <!-- menu-slide -->
349
+ <div class="menu-slide">
350
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=E&rankId=1">
351
+ <div class="menu-btLvx-header"></div>
352
+ <div class="menu-btLvx-body">
353
+ <div class="menu-btLvx-inner">Leatherworking Recipes<br>(Lv. 1-10)</div>
354
+ </div>
355
+ <div class="menu-btLvx-footer"></div>
356
+ </a>
357
+ </div><!-- menu-slide -->
358
+ <!-- menu-slide -->
359
+ <div class="menu-slide">
360
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=E&rankId=2">
361
+ <div class="menu-btLvx-header"></div>
362
+ <div class="menu-btLvx-body">
363
+ <div class="menu-btLvx-inner">Leatherworking Recipes<br>(Lv. 11-20)</div>
364
+ </div>
365
+ <div class="menu-btLvx-footer"></div>
366
+ </a>
367
+ </div><!-- menu-slide -->
368
+ <!-- menu-slide -->
369
+ <div class="menu-slide">
370
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=E&rankId=3">
371
+ <div class="menu-btLvx-header"></div>
372
+ <div class="menu-btLvx-body">
373
+ <div class="menu-btLvx-inner">Leatherworking Recipes<br>(Lv. 21-30)</div>
374
+ </div>
375
+ <div class="menu-btLvx-footer"></div>
376
+ </a>
377
+ </div><!-- menu-slide -->
378
+ </div>
379
+ <div class="menu-lvx-footer"></div>
380
+ </div><!-- menu-lvx -->
381
+ </div><!-- menu-slide -->
382
+ <!-- menu-slide -->
383
+ <div class="menu-slide">
384
+ <a class="menu-btLvx-hasChild" href="javascript:;">
385
+ <div class="menu-btLvx-header"></div>
386
+ <div class="menu-btLvx-body">
387
+ <div class="menu-btLvx-inner">Armoring</div>
388
+ </div>
389
+ <div class="menu-btLvx-footer"></div>
390
+ </a>
391
+ <!-- menu-lvx -->
392
+ <div class="menu-lvx">
393
+ <div class="menu-lvx-header"></div>
394
+ <div class="menu-lvx-body">
395
+ <!-- menu-slide -->
396
+ <div class="menu-slide">
397
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=C&rankId=1">
398
+ <div class="menu-btLvx-header"></div>
399
+ <div class="menu-btLvx-body">
400
+ <div class="menu-btLvx-inner">Armorcraft Recipes<br>(Lv. 1-10)</div>
401
+ </div>
402
+ <div class="menu-btLvx-footer"></div>
403
+ </a>
404
+ </div><!-- menu-slide -->
405
+ <!-- menu-slide -->
406
+ <div class="menu-slide">
407
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=C&rankId=2">
408
+ <div class="menu-btLvx-header"></div>
409
+ <div class="menu-btLvx-body">
410
+ <div class="menu-btLvx-inner">Armorcraft Recipes<br>(Lv. 11-20)</div>
411
+ </div>
412
+ <div class="menu-btLvx-footer"></div>
413
+ </a>
414
+ </div><!-- menu-slide -->
415
+ <!-- menu-slide -->
416
+ <div class="menu-slide">
417
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=C&rankId=3">
418
+ <div class="menu-btLvx-header"></div>
419
+ <div class="menu-btLvx-body">
420
+ <div class="menu-btLvx-inner">Armorcraft Recipes<br>(Lv. 21-30)</div>
421
+ </div>
422
+ <div class="menu-btLvx-footer"></div>
423
+ </a>
424
+ </div><!-- menu-slide -->
425
+ </div>
426
+ <div class="menu-lvx-footer"></div>
427
+ </div><!-- menu-lvx -->
428
+ </div><!-- menu-slide -->
429
+ <!-- menu-slide -->
430
+ <div class="menu-slide">
431
+ <a class="menu-btLvx-hasChild" href="javascript:;">
432
+ <div class="menu-btLvx-header"></div>
433
+ <div class="menu-btLvx-body">
434
+ <div class="menu-btLvx-inner">Clothcraft</div>
435
+ </div>
436
+ <div class="menu-btLvx-footer"></div>
437
+ </a>
438
+ <!-- menu-lvx -->
439
+ <div class="menu-lvx">
440
+ <div class="menu-lvx-header"></div>
441
+ <div class="menu-lvx-body">
442
+ <!-- menu-slide -->
443
+ <div class="menu-slide">
444
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=F&rankId=1">
445
+ <div class="menu-btLvx-header"></div>
446
+ <div class="menu-btLvx-body">
447
+ <div class="menu-btLvx-inner">Clothcraft Recipes (Lv. 1-10)</div>
448
+ </div>
449
+ <div class="menu-btLvx-footer"></div>
450
+ </a>
451
+ </div><!-- menu-slide -->
452
+ <!-- menu-slide -->
453
+ <div class="menu-slide">
454
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=F&rankId=2">
455
+ <div class="menu-btLvx-header"></div>
456
+ <div class="menu-btLvx-body">
457
+ <div class="menu-btLvx-inner">Clothcraft Recipes (Lv. 11-20)</div>
458
+ </div>
459
+ <div class="menu-btLvx-footer"></div>
460
+ </a>
461
+ </div><!-- menu-slide -->
462
+ <!-- menu-slide -->
463
+ <div class="menu-slide">
464
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=F&rankId=3">
465
+ <div class="menu-btLvx-header"></div>
466
+ <div class="menu-btLvx-body">
467
+ <div class="menu-btLvx-inner">Clothcraft Recipes (Lv. 21-30)</div>
468
+ </div>
469
+ <div class="menu-btLvx-footer"></div>
470
+ </a>
471
+ </div><!-- menu-slide -->
472
+ </div>
473
+ <div class="menu-lvx-footer"></div>
474
+ </div><!-- menu-lvx -->
475
+ </div><!-- menu-slide -->
476
+ <!-- menu-slide -->
477
+ <div class="menu-slide">
478
+ <a class="menu-btLvx-hasChild" href="javascript:;">
479
+ <div class="menu-btLvx-header"></div>
480
+ <div class="menu-btLvx-body">
481
+ <div class="menu-btLvx-inner">Blacksmithing</div>
482
+ </div>
483
+ <div class="menu-btLvx-footer"></div>
484
+ </a>
485
+ <!-- menu-lvx -->
486
+ <div class="menu-lvx">
487
+ <div class="menu-lvx-header"></div>
488
+ <div class="menu-lvx-body">
489
+ <!-- menu-slide -->
490
+ <div class="menu-slide">
491
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=B&rankId=1">
492
+ <div class="menu-btLvx-header"></div>
493
+ <div class="menu-btLvx-body">
494
+ <div class="menu-btLvx-inner">Smithing Recipes (Lv. 1-10)</div>
495
+ </div>
496
+ <div class="menu-btLvx-footer"></div>
497
+ </a>
498
+ </div><!-- menu-slide -->
499
+ <!-- menu-slide -->
500
+ <div class="menu-slide">
501
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=B&rankId=2">
502
+ <div class="menu-btLvx-header"></div>
503
+ <div class="menu-btLvx-body">
504
+ <div class="menu-btLvx-inner">Smithing Recipes (Lv. 11-20)</div>
505
+ </div>
506
+ <div class="menu-btLvx-footer"></div>
507
+ </a>
508
+ </div><!-- menu-slide -->
509
+ <!-- menu-slide -->
510
+ <div class="menu-slide">
511
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=B&rankId=3">
512
+ <div class="menu-btLvx-header"></div>
513
+ <div class="menu-btLvx-body">
514
+ <div class="menu-btLvx-inner">Smithing Recipes (Lv. 21-30)</div>
515
+ </div>
516
+ <div class="menu-btLvx-footer"></div>
517
+ </a>
518
+ </div><!-- menu-slide -->
519
+ </div>
520
+ <div class="menu-lvx-footer"></div>
521
+ </div><!-- menu-lvx -->
522
+ </div><!-- menu-slide -->
523
+ <!-- menu-slide -->
524
+ <div class="menu-slide">
525
+ <a class="menu-btLvx-hasChild" href="javascript:;">
526
+ <div class="menu-btLvx-header"></div>
527
+ <div class="menu-btLvx-body">
528
+ <div class="menu-btLvx-inner">Goldsmithing</div>
529
+ </div>
530
+ <div class="menu-btLvx-footer"></div>
531
+ </a>
532
+ <!-- menu-lvx -->
533
+ <div class="menu-lvx">
534
+ <div class="menu-lvx-header"></div>
535
+ <div class="menu-lvx-body">
536
+ <!-- menu-slide -->
537
+ <div class="menu-slide">
538
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=D&rankId=1">
539
+ <div class="menu-btLvx-header"></div>
540
+ <div class="menu-btLvx-body">
541
+ <div class="menu-btLvx-inner">Goldsmithing Recipes<br>(Lv. 1-10)</div>
542
+ </div>
543
+ <div class="menu-btLvx-footer"></div>
544
+ </a>
545
+ </div><!-- menu-slide -->
546
+ <!-- menu-slide -->
547
+ <div class="menu-slide">
548
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=D&rankId=2">
549
+ <div class="menu-btLvx-header"></div>
550
+ <div class="menu-btLvx-body">
551
+ <div class="menu-btLvx-inner">Goldsmithing Recipes<br>(Lv. 11-20)</div>
552
+ </div>
553
+ <div class="menu-btLvx-footer"></div>
554
+ </a>
555
+ </div><!-- menu-slide -->
556
+ <!-- menu-slide -->
557
+ <div class="menu-slide">
558
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=D&rankId=3">
559
+ <div class="menu-btLvx-header"></div>
560
+ <div class="menu-btLvx-body">
561
+ <div class="menu-btLvx-inner">Goldsmithing Recipes<br>(Lv. 21-30)</div>
562
+ </div>
563
+ <div class="menu-btLvx-footer"></div>
564
+ </a>
565
+ </div><!-- menu-slide -->
566
+ </div>
567
+ <div class="menu-lvx-footer"></div>
568
+ </div><!-- menu-lvx -->
569
+ </div><!-- menu-slide -->
570
+ <!-- menu-slide -->
571
+ <div class="menu-slide">
572
+ <a class="menu-btLvx-hasChild" href="javascript:;">
573
+ <div class="menu-btLvx-header"></div>
574
+ <div class="menu-btLvx-body">
575
+ <div class="menu-btLvx-inner">Woodworking</div>
576
+ </div>
577
+ <div class="menu-btLvx-footer"></div>
578
+ </a>
579
+ <!-- menu-lvx -->
580
+ <div class="menu-lvx">
581
+ <div class="menu-lvx-header"></div>
582
+ <div class="menu-lvx-body">
583
+ <!-- menu-slide -->
584
+ <div class="menu-slide">
585
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=A&rankId=1">
586
+ <div class="menu-btLvx-header"></div>
587
+ <div class="menu-btLvx-body">
588
+ <div class="menu-btLvx-inner">Woodworking Recipes<br>(Lv. 1-10)</div>
589
+ </div>
590
+ <div class="menu-btLvx-footer"></div>
591
+ </a>
592
+ </div><!-- menu-slide -->
593
+ <!-- menu-slide -->
594
+ <div class="menu-slide">
595
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=A&rankId=2">
596
+ <div class="menu-btLvx-header"></div>
597
+ <div class="menu-btLvx-body">
598
+ <div class="menu-btLvx-inner">Woodworking Recipes<br>(Lv. 11-20)</div>
599
+ </div>
600
+ <div class="menu-btLvx-footer"></div>
601
+ </a>
602
+ </div><!-- menu-slide -->
603
+ <!-- menu-slide -->
604
+ <div class="menu-slide">
605
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=A&rankId=3">
606
+ <div class="menu-btLvx-header"></div>
607
+ <div class="menu-btLvx-body">
608
+ <div class="menu-btLvx-inner">Woodworking Recipes<br>(Lv. 21-30)</div>
609
+ </div>
610
+ <div class="menu-btLvx-footer"></div>
611
+ </a>
612
+ </div><!-- menu-slide -->
613
+ </div>
614
+ <div class="menu-lvx-footer"></div>
615
+ </div><!-- menu-lvx -->
616
+ </div><!-- menu-slide -->
617
+ <!-- menu-slide -->
618
+ <div class="menu-slide">
619
+ <a class="menu-btLvx-hasChild" href="javascript:;">
620
+ <div class="menu-btLvx-header"></div>
621
+ <div class="menu-btLvx-body">
622
+ <div class="menu-btLvx-inner">Cooking</div>
623
+ </div>
624
+ <div class="menu-btLvx-footer"></div>
625
+ </a>
626
+ <!-- menu-lvx -->
627
+ <div class="menu-lvx">
628
+ <div class="menu-lvx-header"></div>
629
+ <div class="menu-lvx-body">
630
+ <!-- menu-slide -->
631
+ <div class="menu-slide">
632
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=H&rankId=1">
633
+ <div class="menu-btLvx-header"></div>
634
+ <div class="menu-btLvx-body">
635
+ <div class="menu-btLvx-inner">Cooking Recipes (Lv. 1-10)</div>
636
+ </div>
637
+ <div class="menu-btLvx-footer"></div>
638
+ </a>
639
+ </div><!-- menu-slide -->
640
+ <!-- menu-slide -->
641
+ <div class="menu-slide">
642
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=H&rankId=2">
643
+ <div class="menu-btLvx-header"></div>
644
+ <div class="menu-btLvx-body">
645
+ <div class="menu-btLvx-inner">Cooking Recipes (Lv. 11-20)</div>
646
+ </div>
647
+ <div class="menu-btLvx-footer"></div>
648
+ </a>
649
+ </div><!-- menu-slide -->
650
+ <!-- menu-slide -->
651
+ <div class="menu-slide">
652
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=H&rankId=3">
653
+ <div class="menu-btLvx-header"></div>
654
+ <div class="menu-btLvx-body">
655
+ <div class="menu-btLvx-inner">Cooking Recipes (Lv. 21-30)</div>
656
+ </div>
657
+ <div class="menu-btLvx-footer"></div>
658
+ </a>
659
+ </div><!-- menu-slide -->
660
+ </div>
661
+ <div class="menu-lvx-footer"></div>
662
+ </div><!-- menu-lvx -->
663
+ </div><!-- menu-slide -->
664
+ <!-- menu-slide -->
665
+ <div class="menu-slide">
666
+ <a class="menu-btLvx-hasChild" href="javascript:;">
667
+ <div class="menu-btLvx-header"></div>
668
+ <div class="menu-btLvx-body">
669
+ <div class="menu-btLvx-inner">Alchemy</div>
670
+ </div>
671
+ <div class="menu-btLvx-footer"></div>
672
+ </a>
673
+ <!-- menu-lvx -->
674
+ <div class="menu-lvx">
675
+ <div class="menu-lvx-header"></div>
676
+ <div class="menu-lvx-body">
677
+ <!-- menu-slide -->
678
+ <div class="menu-slide">
679
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=G&rankId=1">
680
+ <div class="menu-btLvx-header"></div>
681
+ <div class="menu-btLvx-body">
682
+ <div class="menu-btLvx-inner">Alchemy Recipes (Lv. 1-10)</div>
683
+ </div>
684
+ <div class="menu-btLvx-footer"></div>
685
+ </a>
686
+ </div><!-- menu-slide -->
687
+ <!-- menu-slide -->
688
+ <div class="menu-slide">
689
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=G&rankId=2">
690
+ <div class="menu-btLvx-header"></div>
691
+ <div class="menu-btLvx-body">
692
+ <div class="menu-btLvx-inner">Alchemy Recipes (Lv. 11-20)</div>
693
+ </div>
694
+ <div class="menu-btLvx-footer"></div>
695
+ </a>
696
+ </div><!-- menu-slide -->
697
+ <!-- menu-slide -->
698
+ <div class="menu-slide">
699
+ <a class="menu-btLvx" href="/pl/guide/recipe.html?classId=G&rankId=3">
700
+ <div class="menu-btLvx-header"></div>
701
+ <div class="menu-btLvx-body">
702
+ <div class="menu-btLvx-inner">Alchemy Recipes (Lv. 21-30)</div>
703
+ </div>
704
+ <div class="menu-btLvx-footer"></div>
705
+ </a>
706
+ </div><!-- menu-slide -->
707
+ </div>
708
+ <div class="menu-lvx-footer"></div>
709
+ </div><!-- menu-lvx -->
710
+ </div><!-- menu-slide -->
711
+ </div>
712
+ <div class="menu-lvx-footer"></div>
713
+ </div><!-- menu-lvx -->
714
+ </div><!-- menu-slide -->
715
+ <!-- menu-slide -->
716
+ <div class="menu-slide">
717
+ <a class="menu-btLv2" href="/pl/guide/macro01.html">
718
+ <div class="menu-btLv2-header"></div>
719
+ <div class="menu-btLv2-body">
720
+ <div class="menu-btLv2-inner">Text Commands & Macros</div>
721
+ </div>
722
+ <div class="menu-btLv2-footer"></div>
723
+ </a>
724
+ </div><!-- menu-slide -->
725
+ </div><!-- menu-lv2 -->
726
+ </div><!-- menu-lv1-body -->
727
+ <div class="menu-lv1-footer"></div>
728
+ </div><!-- menu-lv1 -->
729
+ <!-- menu-lv1 -->
730
+ <div class="menu-lv1">
731
+ <a class="menu-btLv1-hasChild" id="btMenuDownload" href="javascript:;"></a>
732
+ <!-- menu-lv1-body -->
733
+ <div class="menu-lv1-body">
734
+ <!-- menu-lv2 -->
735
+ <div class="menu-lv2">
736
+ <!-- menu-slide -->
737
+ <div class="menu-slide">
738
+ <a class="menu-btLv2" href="/pl/download/benchmark01.html">
739
+ <div class="menu-btLv2-header"></div>
740
+ <div class="menu-btLv2-body">
741
+ <div class="menu-btLv2-inner">Official Benchmark</div>
742
+ </div>
743
+ <div class="menu-btLv2-footer"></div>
744
+ </a>
745
+ </div><!-- menu-slide -->
746
+ <!-- menu-slide -->
747
+ <div class="menu-slide">
748
+ <a class="menu-btLv2" href="/pl/download/fansite01.html">
749
+ <div class="menu-btLv2-header"></div>
750
+ <div class="menu-btLv2-body">
751
+ <div class="menu-btLv2-inner">Fan Site Kit</div>
752
+ </div>
753
+ <div class="menu-btLv2-footer"></div>
754
+ </a>
755
+ </div><!-- menu-slide -->
756
+ </div><!-- menu-lv2 -->
757
+ </div><!-- menu-lv1-body -->
758
+ <div class="menu-lv1-footer"></div>
759
+ </div><!-- menu-lv1 -->
760
+ <div class="menu-lv1">
761
+ <a class="menu-btLv1" id="btMenuSupportEN" href="http://support.na.square-enix.com/main.php?id=902&la=1" target="_blank"></a>
762
+ </div>
763
+ <div class="menu-lv1">
764
+ <a class="menu-btLv1" id="btMenuRuleEN" href="http://support.jp.square-enix.com/rule.php?id=902&la=1" target="_blank"></a>
765
+ </div>
766
+ <div class="menu-lv1">
767
+ <a class="menu-btLv1" id="btMenuSqexAccountEN" href="https://secure.square-enix.com/account/app/svc/login?cont=account" target="_blank"></a>
768
+ </div>
769
+ </div>
770
+
771
+ <div class="floatEnd"></div>
772
+
773
+ </div><!-- menu -->
774
+
775
+ <!-- ======================================================================= -->
776
+ <!-- //サイドメニュー -->
777
+ <!-- ======================================================================= -->
778
+
779
+ <!-- ======================================================================= -->
780
+ <!-- コンテナ -->
781
+ <!-- ======================================================================= -->
782
+ <!-- container -->
783
+ <div id="container">
784
+
785
+ <!-- ======================================================================= -->
786
+ <!-- マイページメニュー &ログインキャラクター -->
787
+ <!-- ======================================================================= -->
788
+ <!-- ======================================================================= -->
789
+ <!-- ログインキャラクター -->
790
+ <!-- ======================================================================= -->
791
+ <!-- ======================================================================= -->
792
+ <!-- 非ログイン時にログインをうながすメッセージを表示する -->
793
+ <!-- ======================================================================= -->
794
+ <div id="unlessloginmenu-message"></div>
795
+ <!-- ======================================================================= -->
796
+ <!-- キャラクターページメニュー -->
797
+ <!-- ======================================================================= -->
798
+ <!-- ======================================================================= -->
799
+ <!-- //マイページメニュー -->
800
+ <!-- ======================================================================= -->
801
+ <div id="unlessloginmenu"></div>
802
+ <span class="hide" id="followLimitErrorText">You cannot follow more than 500 characters.</span>
803
+ <span class="hide" id="followErrorText">An unexpected error has occurred.</span>
804
+
805
+ <!-- ======================================================================= -->
806
+ <!-- //キャラクターページメニュー -->
807
+ <!-- ======================================================================= -->
808
+
809
+ <!-- ======================================================================= -->
810
+ <!-- トピックパス -->
811
+ <!-- ======================================================================= -->
812
+
813
+ <!-- topicPath -->
814
+
815
+
816
+ <!-- ======================================================================= -->
817
+ <!-- //トピックパス -->
818
+ <!-- ======================================================================= -->
819
+
820
+ <!-- contents -->
821
+ <div id="contents">
822
+ <!-- 手書き編集エリア===================================================== -->
823
+
824
+ <!-- #contents-body -->
825
+ <div id="contents-body">
826
+
827
+ <!-- #two-column-L -->
828
+ <div id="two-column-L">
829
+
830
+ <!--=--------------------------------------------------------
831
+ Aゾーン
832
+ ---------------------------------------------------------=-->
833
+ <!-- #azone -->
834
+ <div id="azone">
835
+ <!-- #azone-contents -->
836
+ <div id="azone-contents">
837
+
838
+ <!-- .contents-headline -->
839
+ <div class="contents-headline">
840
+ <!-- .contents-headlineInner -->
841
+ <div class="contents-headlineInner">
842
+ <div class="contents-headlineHeader">
843
+ <div class="contents-headlineTL"></div>
844
+ <div class="contents-headlineTR"></div>
845
+ </div>
846
+ <div class="contents-headlineBody">
847
+ <div>Search Results</div>
848
+ </div>
849
+ <div class="contents-headlineFooter">
850
+ <div class="contents-headlineBL"></div>
851
+ <div class="contents-headlineBR"></div>
852
+ </div>
853
+ </div><!-- .contents-headlineInner -->
854
+ </div><!-- .contents-headline -->
855
+
856
+ <!-- .contents-frame -->
857
+ <div class="contents-frame">
858
+ <!-- .contents-frame-lv1 -->
859
+ <div class="contents-frame-lv1">
860
+ <!-- .contents-frame-lv2 -->
861
+ <div class="contents-frame-lv2">
862
+
863
+ <!-- .contents-frame-inner -->
864
+ <div class="contents-frame-inner">
865
+ <div class="contents-error-text">
866
+ Your search yielded no results.</div>
867
+ <!-- FIXME: キャラクタ検索の場合はこれではダメ.tgtが指定されている場合は別のメッセージを。 -->
868
+
869
+ </div><!-- .contents-frame-inner -->
870
+
871
+ </div><!-- .contents-frame-lv2 -->
872
+ </div><!-- .contents-frame-lv1 -->
873
+ </div><!-- .contents-frame -->
874
+
875
+ </div><!-- #azone-contents -->
876
+ </div><!-- #azone -->
877
+ <!--=--------------------------------------------------------
878
+ //Aゾーン
879
+ ---------------------------------------------------------=-->
880
+
881
+ <!--=--------------------------------------------------------
882
+ Bゾーン
883
+ ---------------------------------------------------------=-->
884
+ <!-- bzone -->
885
+ <div id="bzone">
886
+ <!-- #bzone-contents -->
887
+ <div id="bzone-contents">
888
+
889
+ </div><!-- #bzone-contents -->
890
+ </div><!-- bzone -->
891
+ <!--=--------------------------------------------------------
892
+ //Bゾーン
893
+ ---------------------------------------------------------=-->
894
+ <div class="floatEnd"></div>
895
+
896
+ </div><!-- two-column-L -->
897
+
898
+ </div><!-- contents-body -->
899
+
900
+ <!-- //手書き編集エリア=================================================== -->
901
+
902
+ </div><!-- contents -->
903
+
904
+ </div><!-- container -->
905
+
906
+ <!-- ======================================================================= -->
907
+ <!-- //コンテンツ -->
908
+ <!-- ======================================================================= -->
909
+
910
+ <!-- フロート終了 -->
911
+ <div class="floatEnd"></div>
912
+
913
+ </div><!-- #wrap-body -->
914
+
915
+ <!-- #wrap-footer -->
916
+ <div id="wrap-footer">
917
+ <div align="left" id="wrap-footer-bg">
918
+ <a class="button" id="btShowTimeZoneWindow" href="javascript:;"></a>
919
+ <div id="copyright"><div>&#xA9; 2010 SQUARE ENIX CO., LTD. All Rights Reserved.<br/>FINAL FANTASY, FFXIV, SQUARE ENIX and the SQUARE ENIX logo are registered trademarks or trademarks of Square Enix Holdings Co., Ltd. &quot;PlayStation&quot; and the &quot;PS&quot;<br/>Family logo are registered trademarks and &quot;PS3&quot; is a trademark of Sony Computer Entertainment Inc. The PlayStation Network Logo is a service mark of Sony Computer Entertainment Inc. The ESRB rating icon is a registered trademark of the Entertainment Software Association. All other trademarks are the properties of their respective owners.</div>
920
+ <table class="floatRight" cellpadding="0" cellspacing="0">
921
+ <tr>
922
+ <td><img src="http://static.finalfantasyxiv.com/global/images/common/default/wrap/footer/copyright/esrbrate.gif?_v=4mk"/></td>
923
+ <td width="30"></td>
924
+ <td><img src="http://static.finalfantasyxiv.com/global/images/common/default/wrap/footer/copyright/ps.gif?_v=4mk"/></td>
925
+ <td width="30"></td>
926
+ <td><img src="http://static.finalfantasyxiv.com/global/images/common/default/wrap/footer/copyright/ps3.gif?_v=4mk"/></td>
927
+ <td width="30"></td>
928
+ <td><img src="http://static.finalfantasyxiv.com/global/images/common/default/wrap/footer/copyright/psn.gif?_v=4mk"/></td>
929
+ <td width="30"></td>
930
+ <td><img src="http://static.finalfantasyxiv.com/global/images/common/default/wrap/footer/copyright/pcdvd.gif?_v=4mk"/></td>
931
+ <td width="30"></td>
932
+ <td><a href="http://www.square-enix.com/na/privacy/" target="_blank"><img src="http://static.finalfantasyxiv.com/global/images/common/default/wrap/footer/copyright/esrb.gif?_v=4mk"/></a></td>
933
+ </tr>
934
+ </table>
935
+ </div>
936
+ <div class="floatEnd"></div>
937
+ </div>
938
+ </div><!-- #wrap-footer -->
939
+ </div><!-- #wrap -->
940
+ </div><!-- #bg2 -->
941
+ </div><!-- #bg --></body>
942
+ </html>
943
+ <script type="text/javascript" src="http://static.finalfantasyxiv.com/global/js/mini/common/init.js?_v=4mk"></script><script type="text/javascript" src="http://static.finalfantasyxiv.com/community/js/mini/search/result.js?_v=4mk"></script><script type="text/javascript">
944
+
945
+ var _gaq = _gaq || [];
946
+ _gaq.push(['_setAccount', 'UA-18627772-1']);
947
+ _gaq.push(['_setDomainName', '.finalfantasyxiv.com']);
948
+ _gaq.push(['_trackPageview']);
949
+
950
+ (function() {
951
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
952
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
953
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
954
+ })();
955
+
956
+ </script>
@@ -4,11 +4,11 @@ require 'ffxiv-lodestone'
4
4
 
5
5
  # Make the character class open our saved HTML...
6
6
  class FFXIVLodestone::Character
7
- def get_profile_html(id)
7
+ def self.get_profile_html(id)
8
8
  open("./characters/#{id}.html")
9
9
  end
10
10
 
11
- def get_search_html(character,world_id)
11
+ def self.get_search_html(character,world_id)
12
12
  n = character.downcase.gsub(' ','-')
13
13
  open("./searches/#{n}_#{world_id}.html")
14
14
  end
@@ -48,6 +48,7 @@ describe "Character(:name => 'Lady Simmons', :world => 'Selbina')" do
48
48
  char = FFXIVLodestone::Character.new(:name => 'Ayeron Lifebloom', :world => 'Figaro')
49
49
  char.name.should.equal 'Ayeron Lifebloom'
50
50
  char.character_id.should.equal 1502635
51
+ char.physical_level.should.equal 12
51
52
 
52
53
  char = FFXIVLodestone::Character.new(:id => 2172370)
53
54
  char.name.should.equal 'Karen Kranfel'
@@ -70,6 +71,8 @@ describe 'Character(1015990)' do
70
71
  @char.gender.should.equal 'Female'
71
72
  @char.race.should.equal "Miqo'te"
72
73
  @char.clan.should.equal 'Seeker of the Sun'
74
+ @char.portrait_url.should.equal 'http://static.finalfantasyxiv.com/csnap/v05m_m_7bd793d507a92d2c415b306a83280d19.png'
75
+ @char.portrait_thumb_url.should.equal 'http://static.finalfantasyxiv.com/csnap/v05m_ss_7bd793d507a92d2c415b306a83280d19.png'
73
76
  end
74
77
 
75
78
  it 'should have stat lists' do
@@ -95,9 +98,30 @@ describe 'Character(1015990)' do
95
98
 
96
99
  it 'should be convertable to json' do
97
100
  json = <<LOLHEREDOC
98
- {"first_name":"Lady","character_id":1015990,"nameday":"4th Sun of the 3rd Umbral Moon","world":"Selbina","last_name":"Simmons","attributes":{"vitality":24,"dexterity":17,"intelligence":36,"mind":37,"piety":28,"strength":16},"guardian":"Nymeia, the Spinner","current_exp":18880,"starting_city":"Gridania","exp_to_next_level":28000,"race":"Miqo'te","resistances":{"fire":28,"water":29,"lightning":30,"wind":30,"earth":25,"ice":15},"physical_level":12,"jobs":{"armorer":{"skill_name":"Armorcraft","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Armorer"},"miner":{"skill_name":"Mining","rank":1,"current_skill_points":431,"skillpoint_to_next_level":570,"name":"Miner"},"lancer":{"skill_name":"Polearm","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Lancer"},"goldsmith":{"skill_name":"Goldsmithing","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Goldsmith"},"botanist":{"skill_name":"Botany","rank":9,"current_skill_points":1800,"skillpoint_to_next_level":4300,"name":"Botanist"},"thaumaturge":{"skill_name":"Thaumaturgy","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Thaumaturge"},"leatherworker":{"skill_name":"Leatherworking","rank":1,"current_skill_points":78,"skillpoint_to_next_level":570,"name":"Leatherworker"},"pugilist":{"skill_name":"Hand-to-Hand","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Pugilist"},"fisher":{"skill_name":"Fishing","rank":3,"current_skill_points":748,"skillpoint_to_next_level":880,"name":"Fisher"},"conjurer":{"skill_name":"Conjury","rank":1,"current_skill_points":272,"skillpoint_to_next_level":570,"name":"Conjurer"},"weaver":{"skill_name":"Clothcraft","rank":6,"current_skill_points":1145,"skillpoint_to_next_level":1800,"name":"Weaver"},"shield":{"skill_name":"Shield","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Shield"},"gladiator":{"skill_name":"Sword","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Gladiator"},"carpenter":{"skill_name":"Woodworking","rank":5,"current_skill_points":305,"skillpoint_to_next_level":1500,"name":"Carpenter"},"alchemist":{"skill_name":"Alchemy","rank":8,"current_skill_points":1753,"skillpoint_to_next_level":3200,"name":"Alchemist"},"marauder":{"skill_name":"Axe","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Marauder"},"blacksmith":{"skill_name":"Smithing","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Blacksmith"},"culinarian":{"skill_name":"Cooking","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Culinarian"},"archer":{"skill_name":"Archery","rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Archer"}},"gender":"Female","clan":"Seeker of the Sun"}
101
+ {"world":"Selbina","clan":"Seeker of the Sun","first_name":"Lady","portrait_url":"http:\/\/static.finalfantasyxiv.com\/csnap\/v05m_m_7bd793d507a92d2c415b306a83280d19.png","character_id":1015990,"current_exp":18880,"last_name":"Simmons","nameday":"4th Sun of the 3rd Umbral Moon","exp_to_next_level":28000,"portrait_thumb_url":"http:\/\/static.finalfantasyxiv.com\/csnap\/v05m_ss_7bd793d507a92d2c415b306a83280d19.png","guardian":"Nymeia, the Spinner","physical_level":12,"jobs":{"alchemist":{"rank":8,"current_skill_points":1753,"skillpoint_to_next_level":3200,"name":"Alchemist","skill_name":"Alchemy"},"marauder":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Marauder","skill_name":"Axe"},"blacksmith":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Blacksmith","skill_name":"Smithing"},"culinarian":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Culinarian","skill_name":"Cooking"},"archer":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Archer","skill_name":"Archery"},"armorer":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Armorer","skill_name":"Armorcraft"},"miner":{"rank":1,"current_skill_points":431,"skillpoint_to_next_level":570,"name":"Miner","skill_name":"Mining"},"lancer":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Lancer","skill_name":"Polearm"},"goldsmith":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Goldsmith","skill_name":"Goldsmithing"},"botanist":{"rank":9,"current_skill_points":1800,"skillpoint_to_next_level":4300,"name":"Botanist","skill_name":"Botany"},"thaumaturge":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Thaumaturge","skill_name":"Thaumaturgy"},"leatherworker":{"rank":1,"current_skill_points":78,"skillpoint_to_next_level":570,"name":"Leatherworker","skill_name":"Leatherworking"},"pugilist":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Pugilist","skill_name":"Hand-to-Hand"},"fisher":{"rank":3,"current_skill_points":748,"skillpoint_to_next_level":880,"name":"Fisher","skill_name":"Fishing"},"conjurer":{"rank":1,"current_skill_points":272,"skillpoint_to_next_level":570,"name":"Conjurer","skill_name":"Conjury"},"weaver":{"rank":6,"current_skill_points":1145,"skillpoint_to_next_level":1800,"name":"Weaver","skill_name":"Clothcraft"},"gladiator":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Gladiator","skill_name":"Sword"},"carpenter":{"rank":5,"current_skill_points":305,"skillpoint_to_next_level":1500,"name":"Carpenter","skill_name":"Woodworking"},"shield":{"rank":0,"current_skill_points":0,"skillpoint_to_next_level":0,"name":"Shield","skill_name":"Shield"}},"race":"Miqo'te","resistances":{"earth":25,"ice":15,"fire":28,"water":29,"lightning":30,"wind":30},"starting_city":"Gridania","attributes":{"mind":37,"piety":28,"strength":16,"vitality":24,"dexterity":17,"intelligence":36},"gender":"Female"}
99
102
  LOLHEREDOC
100
103
 
101
104
  JSON.parse(@char.to_json).should.equal JSON.parse(json)
102
105
  end
103
106
  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: 57
4
+ hash: 63
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 9
9
- - 1
10
- version: 0.9.1
9
+ - 2
10
+ version: 0.9.2
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-24 00:00:00 +00:00
18
+ date: 2010-09-25 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -62,6 +62,7 @@ files:
62
62
  - test/spec.rb
63
63
  - test/searches/ayeron-lifebloom_7.html
64
64
  - test/searches/lady_12.html
65
+ - test/searches/abloo-bloo-uguu_12.html
65
66
  - test/searches/lady-simmons_12.html
66
67
  - test/searches/fakey-fakerson_12.html
67
68
  - test/characters/2172370.html