rest-gw2 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 82b050b4c2120f41a06311ef37c84492054fbd43
4
- data.tar.gz: c4ce7c1508cbc82537f06c40b8a16c6685547776
3
+ metadata.gz: 8beaa0b13cd7d66c054200ad4f919a7751e66a1a
4
+ data.tar.gz: f48944e1a65d49072bc5bfdab62f739164978670
5
5
  SHA512:
6
- metadata.gz: 0778f5b31db7b67f567bc2a4c85e78c5fba06053c5dc3cf37719cde12ec2b1eccb1d8f885931ae015a395266deb25fd8602f78e5b03a610b2a5b237d2681a380
7
- data.tar.gz: 9f2a5abd8b652dd5e1d779fc84ec7dc648c7ff419bec097a30f2457521eb910ff4dd35bdffede528df53fb403b21711006033af795e8d58af40cf11eae9c6743
6
+ metadata.gz: 06507ee1885c0a5c2ebfe1d050f56541ae5a5168552a527c46d22f5d281fb70ee56a37cec353c10cd6609acca2bc942e808acb58a748283f5470a76d08d3b9f5
7
+ data.tar.gz: 4e5346ef0280fba26f7b9ed461a8fc60b2041a147ee4aca8310c095b2b821f60850df728b882e6966ae22507753bcec39092717bc1e5991a4d3293ec16d63de8
data/CHANGES.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # CHANGES
2
2
 
3
+ ## rest-gw2 0.4.0 -- 2016-02-05
4
+
5
+ * Added `RestGW2::Client#stash_with_detail`
6
+ * Added `RestGW2::Client#treasury_with_detail`
7
+ * Added `RestGW2::Client#get_character`
8
+ * Added `RestGW2::Client#characters_with_detail`
9
+ * Added `RestGW2::Client#bags_with_detail`
10
+ * Added `RestGW2::Client#dyes_with_detail`
11
+ * Added `RestGW2::Client#skins_with_detail`
12
+ * Added `RestGW2::Client#all_skins`
13
+ * Added `RestGW2::Client#minis_with_detail`
14
+ * Added `RestGW2::Client#expand_item_detail`
15
+ * Added a bunch of new pages for the server.
16
+
3
17
  ## rest-gw2 0.2.0 -- 2015-12-18
4
18
 
5
19
  ### RestGW2::Client
data/README.md CHANGED
@@ -50,7 +50,7 @@ example using the client.
50
50
  ``` ruby
51
51
  require 'rest-gw2'
52
52
  gw2 = RestGW2::Client.new(:access_token => '...')
53
- gw2.get('account/bank') # => list of items in your bank
53
+ gw2.get('v2/account/bank') # => list of items in your bank
54
54
  ```
55
55
 
56
56
  ## SYNOPSIS: (if you need the web application)
@@ -77,8 +77,22 @@ The format for the config file would be like:
77
77
 
78
78
  git clone git@github.com:godfat/rest-gw2.git
79
79
  cd rest-gw2
80
- gem install rack jellyfish lru_redux yahns rack-handlers
81
- ruby -Ilib:rest-core/lib -S bin/rest-gw2
80
+ gem install rack jellyfish dalli lru_redux rack-handlers rib
81
+ gem install httpclient mime-types timers
82
+ gem install yahns
83
+ rake server
84
+ rake console
85
+
86
+ ## Using JRuby:
87
+
88
+ git clone git@github.com:godfat/rest-gw2.git
89
+ cd rest-gw2
90
+ jruby -S gem install rack jellyfish dalli lru_redux rack-handlers rib
91
+ jruby -S gem install httpclient mime-types timers
92
+ jruby -S gem install jruby-openssl
93
+ jruby -S gem install torquebox-web --pre
94
+ jruby -S rake server
95
+ jruby -S rake console
82
96
 
83
97
  ## CONTRIBUTORS:
84
98
 
@@ -88,7 +102,7 @@ The format for the config file would be like:
88
102
 
89
103
  Apache License 2.0
90
104
 
91
- Copyright (c) 2015, Lin Jen-Shin (godfat)
105
+ Copyright (c) 2015-2016, Lin Jen-Shin (godfat)
92
106
 
93
107
  Licensed under the Apache License, Version 2.0 (the "License");
94
108
  you may not use this file except in compliance with the License.
data/Rakefile CHANGED
@@ -2,15 +2,32 @@
2
2
  begin
3
3
  require "#{dir = File.dirname(__FILE__)}/task/gemgem"
4
4
  rescue LoadError
5
- sh 'git submodule update --init'
5
+ sh 'git submodule update --init --recursive'
6
6
  exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV
7
7
  end
8
8
 
9
+ %w[lib rest-builder/lib rest-builder/promise_pool/lib].each do |path|
10
+ $LOAD_PATH.unshift(File.expand_path("#{dir}/rest-core/#{path}"))
11
+ end
12
+
13
+ desc 'Run console'
14
+ task 'console' do
15
+ ARGV.shift
16
+ ARGV.unshift 'rack'
17
+ load `which rib`.chomp
18
+ end
19
+
20
+ desc 'Run server'
21
+ task 'server' do
22
+ ARGV.shift
23
+ load 'bin/rest-gw2'
24
+ end
25
+
9
26
  Gemgem.init(dir) do |s|
10
27
  require 'rest-gw2/version'
11
28
  s.name = 'rest-gw2'
12
29
  s.version = RestGW2::VERSION
13
- s.add_runtime_dependency('rest-core', '>=3.5.92')
30
+ s.add_runtime_dependency('rest-core', '>=4.0.0')
14
31
  %w[jellyfish rack rack-handlers
15
32
  dalli lru_redux].each{ |g| s.add_development_dependency(g) }
16
33
 
data/config.ru CHANGED
@@ -1,6 +1,8 @@
1
1
 
2
2
  require 'rest-gw2'
3
3
  warmup do
4
+ RestCore.eagerload
5
+ RestCore.eagerload(RestGW2)
4
6
  RestGW2::Client.new
5
7
  end
6
8
  run RestGW2::Server
@@ -65,6 +65,117 @@ module RestGW2
65
65
  me.merge('world' => world_detail(worlds.first), 'guilds' => guilds)
66
66
  end
67
67
 
68
+ # https://wiki.guildwars2.com/wiki/API:2/guild/:id/stash
69
+ def stash_with_detail gid, opts={}
70
+ stash = get("v2/guild/#{gid}/stash")
71
+ uids = stash.map{ |u| u['upgrade_id'] }.join(',')
72
+ upgrades = get('v2/guild/upgrades', :ids => uids)
73
+ bags = bags_with_detail(stash)
74
+
75
+ upgrades.zip(bags).map do |(u, b)|
76
+ u.merge('inventory' => b['inventory'])
77
+ end
78
+ end
79
+
80
+ # https://wiki.guildwars2.com/wiki/API:2/guild/:id/treasury
81
+ def treasury_with_detail gid, opts={}
82
+ with_item_detail("v2/guild/#{gid}/treasury") do |items|
83
+ items.map do |i|
84
+ i.merge('id' => i['item_id'])
85
+ end
86
+ end
87
+ end
88
+
89
+ # https://wiki.guildwars2.com/wiki/API:2/characters
90
+ def get_character name, opts={}
91
+ get("v2/characters/#{RC::Middleware.escape(name)}", {}, opts)
92
+ end
93
+
94
+ # https://wiki.guildwars2.com/wiki/API:2/characters
95
+ def characters_with_detail opts={}
96
+ chars = get('v2/characters', {}, opts).map do |name|
97
+ get_character(name, opts)
98
+ end
99
+
100
+ guilds = chars.map do |c|
101
+ get_guild(c['guild']) if c['guild']
102
+ end
103
+
104
+ chars.zip(guilds).map do |(c, g)|
105
+ c['guild'] = g
106
+ c
107
+ end.sort_by{ |c| -c['age'] }
108
+ end
109
+
110
+ def bags_with_detail bags, opts={}
111
+ detail = expand_item_detail(
112
+ bags + bags.flat_map{ |c| c && c['inventory'] }, opts)
113
+ detail.shift(bags.size).map do |b|
114
+ b && b.merge('inventory' => detail.shift(b['size']))
115
+ end
116
+ end
117
+
118
+ # https://wiki.guildwars2.com/wiki/API:2/colors
119
+ # https://wiki.guildwars2.com/wiki/API:2/account/dyes
120
+ def dyes_with_detail opts={}
121
+ mine = get('v2/account/dyes', opts)
122
+ get('v2/colors').each_slice(100).map do |slice|
123
+ slice.join(',')
124
+ end.map do |ids|
125
+ with_item_detail('v2/colors', :ids => ids) do |colors|
126
+ colors.map{ |c| c.merge('id' => c['item'], 'color_id' => c['id']) }
127
+ end
128
+ end.flatten.map do |color|
129
+ color['count'] = if mine.include?(color['color_id'])
130
+ 1
131
+ else
132
+ 0
133
+ end
134
+ color
135
+ end.sort_by{ |c| c['categories'] }
136
+ end
137
+
138
+ # https://wiki.guildwars2.com/wiki/API:2/account/skins
139
+ def skins_with_detail opts={}
140
+ mine = get('v2/account/skins', {}, opts)
141
+ all_skins.flatten.map do |skin|
142
+ skin['count'] = if mine.include?(skin['id'])
143
+ 1
144
+ else
145
+ 0
146
+ end
147
+ skin['nolink'] = true
148
+ skin
149
+ end.sort_by{ |s| s['name'] || '' }
150
+ end
151
+
152
+ # https://wiki.guildwars2.com/wiki/API:2/skins
153
+ def all_skins
154
+ get('v2/skins').each_slice(100).map do |slice|
155
+ get('v2/skins', :ids => slice.join(','))
156
+ end
157
+ end
158
+
159
+ # https://wiki.guildwars2.com/wiki/API:2/minis
160
+ # https://wiki.guildwars2.com/wiki/API:2/account/minis
161
+ def minis_with_detail opts={}
162
+ mine = get('v2/account/minis', {}, opts)
163
+ get('v2/minis').each_slice(100).map do |slice|
164
+ slice.join(',')
165
+ end.map do |ids|
166
+ with_item_detail('v2/minis', :ids => ids) do |minis|
167
+ minis.map{ |m| m.merge('id' => m['item_id'], 'mini_id' => m['id']) }
168
+ end
169
+ end.flatten.map do |mini|
170
+ mini['count'] = if mine.include?(mini['mini_id'])
171
+ 1
172
+ else
173
+ 0
174
+ end
175
+ mini
176
+ end.sort_by{ |m| m['order'] }
177
+ end
178
+
68
179
  # https://wiki.guildwars2.com/wiki/API:2/account/wallet
69
180
  # https://wiki.guildwars2.com/wiki/API:2/currencies
70
181
  def wallet_with_detail opts={}
@@ -76,22 +187,9 @@ module RestGW2
76
187
  end.sort_by{ |c| c['order'] }
77
188
  end
78
189
 
79
- # https://wiki.guildwars2.com/wiki/API:2/items
80
- # https://wiki.guildwars2.com/wiki/API:2/commerce/prices
81
190
  def with_item_detail path, query={}, opts={}, &block
82
191
  block ||= :itself.to_proc
83
- items = block.call(get(path, query, opts))
84
- ids = items.map{ |i| i && i['id'] }
85
-
86
- detail = ids.compact.each_slice(100).map do |slice|
87
- query = {:ids => slice.join(',')}
88
- [get('v2/items', query),
89
- get('v2/commerce/prices', query, opts)]
90
- end.flatten.group_by{ |i| i['id'] }
91
-
92
- items.map do |i|
93
- i && detail[i['id']].inject(i, &:merge).merge('count' => i['count'])
94
- end
192
+ expand_item_detail(block.call(get(path, query, opts)), opts)
95
193
  end
96
194
 
97
195
  # https://wiki.guildwars2.com/wiki/API:2/commerce/transactions
@@ -109,7 +207,7 @@ module RestGW2
109
207
  last = ret.last
110
208
  if last && last['item_id'] == trans['item_id'] &&
111
209
  last['price'] == trans['price']
112
- last['count'] += trans['count']
210
+ last['count'] += trans['count']
113
211
  else
114
212
  ret << trans
115
213
  end
@@ -117,7 +215,51 @@ module RestGW2
117
215
  end
118
216
  end
119
217
 
218
+ # https://wiki.guildwars2.com/wiki/API:2/items
219
+ # https://wiki.guildwars2.com/wiki/API:2/commerce/prices
220
+ def expand_item_detail items, opts={}
221
+ skins = all_skins
222
+ detail = item_detail_group_by_id(items, opts)
223
+ upgrades = extract_items_in_slots(items, opts, 'upgrades', 'infusions')
224
+
225
+ skins_detail = skins.flatten.group_by{ |s| s['id'] }
226
+ items.map do |i|
227
+ next i unless data = i && detail[i['id']]
228
+ s = i['skin']
229
+ u = i['upgrades']
230
+ f = i['infusions']
231
+ i.merge(data).merge(
232
+ 'count' => i['count'] || 1,
233
+ 'skin' => s && skins_detail[s].first,
234
+ 'upgrades' => u && u.flat_map(&upgrades.method(:[])),
235
+ 'infusions' => f && f.flat_map(&upgrades.method(:[])))
236
+ end
237
+ end
238
+
120
239
  private
240
+ def item_detail_group_by_id items, opts={}
241
+ items.map{ |i| i && i['id'] }.compact.each_slice(100).map do |slice|
242
+ q = {:ids => slice.join(',')}
243
+ [get('v2/items', q),
244
+ get('v2/commerce/prices', q, {:error_detector => false}.merge(opts))]
245
+ end.flat_map(&:itself).map(&:to_a).flatten.group_by{ |i| i['id'] }.
246
+ inject({}){ |r, (id, v)| r[id] = v.inject(&:merge); r }
247
+ # this is probably a dirty way to workaround converting hashes to arrays
248
+ end
249
+
250
+ def extract_items_in_slots items, opts, *slots
251
+ upgrades = items.flat_map do |i|
252
+ if i
253
+ i.values_at(*slots).flatten.compact.map do |id|
254
+ {'id' => id}
255
+ end
256
+ else
257
+ []
258
+ end
259
+ end
260
+ item_detail_group_by_id(upgrades, opts)
261
+ end
262
+
121
263
  # https://wiki.guildwars2.com/wiki/API:2/worlds
122
264
  def world_detail world
123
265
  region = case r = world['id'] / 1000
@@ -145,11 +287,11 @@ module RestGW2
145
287
 
146
288
  # https://wiki.guildwars2.com/wiki/API:1/guild_details
147
289
  def guilds_detail guilds
148
- guilds.map do |gid|
149
- get('v1/guild_details', :guild_id => gid)
150
- end.map do |guild|
151
- "#{guild['guild_name']} [#{guild['tag']}]"
152
- end
290
+ guilds.map(&method(:get_guild))
291
+ end
292
+
293
+ def get_guild gid
294
+ get('v1/guild_details', :guild_id => gid)
153
295
  end
154
296
  })
155
297
  end
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'rest-gw2/server/cache'
3
+ require 'rest-gw2/client'
3
4
  require 'mime/types'
4
5
  require 'rest-core'
5
6
  require 'jellyfish'
@@ -33,18 +34,48 @@ module RestGW2
33
34
  https://wiki.guildwars2.com/images/3/3c/Silver_coin.png
34
35
  https://wiki.guildwars2.com/images/e/eb/Copper_coin.png
35
36
  ]).freeze
36
- controller_include Module.new{
37
+
38
+ def self.weapons
39
+ %w[Greatsword Sword Hammer Mace Axe Dagger
40
+ Staff Scepter
41
+ LongBow ShortBow Rifle Pistol
42
+ Shield Torch Focus Warhorn
43
+ Harpoon Speargun Trident]
44
+ end
45
+
46
+ def self.armors
47
+ %w[Helm Shoulders Coat Gloves Leggings Boots HelmAquatic]
48
+ end
49
+
50
+ def self.armors_weight
51
+ %w[Light Medium Heavy Clothing]
52
+ end
53
+
54
+ def self.crafting
55
+ %w[Weaponsmith Huntsman Artificer
56
+ Armorsmith Leatherworker Tailor
57
+ Jeweler Chef Scribe]
58
+ end
59
+
60
+ HTML = Class.new(Struct.new(:to_s))
61
+
62
+ controller_include NormalizedPath, Module.new{
37
63
  # VIEW
38
- def render path
39
- erb(:layout){ erb(path) }
64
+ def render path, arg=nil
65
+ erb(:layout){ erb(path, arg) }
40
66
  end
41
67
 
42
- def erb path, &block
43
- ERB.new(views(path)).result(binding, &block)
68
+ def erb name, arg=nil, &block
69
+ ERB.new(views(name)).result(binding, &block)
44
70
  end
45
71
 
46
72
  def h str
47
- CGI.escape_html(str) if str.kind_of?(String)
73
+ case str
74
+ when String
75
+ CGI.escape_html(str)
76
+ when HTML
77
+ str.to_s
78
+ end
48
79
  end
49
80
 
50
81
  def u str
@@ -57,9 +88,9 @@ module RestGW2
57
88
  RC::REQUEST_QUERY => q)
58
89
  end
59
90
 
60
- def views path
91
+ def views name
61
92
  @views ||= {}
62
- @views[path] = File.read("#{__dir__}/view/#{path}.erb")
93
+ @views[name] = File.read("#{__dir__}/view/#{name}.erb")
63
94
  end
64
95
 
65
96
  def refresh_path
@@ -77,8 +108,8 @@ module RestGW2
77
108
  end
78
109
 
79
110
  # TODO: clean me up
80
- def menu_trans item, title
81
- key = "/transactions#{item}"
111
+ def menu_sub prefix, item, title
112
+ key = "#{prefix}#{item}"
82
113
  if path(request.path) == path(key)
83
114
  menu(key, title, :p => p)
84
115
  else
@@ -86,27 +117,61 @@ module RestGW2
86
117
  end
87
118
  end
88
119
 
120
+ def menu_char name
121
+ menu("/characters/#{RC::Middleware.escape(name)}", name)
122
+ end
123
+
124
+ def menu_skin item, title
125
+ menu_sub('/skins', item, title)
126
+ end
127
+
128
+ def menu_trans item, title
129
+ menu_sub('/transactions', item, title)
130
+ end
131
+
89
132
  def page num
90
133
  menu(request.path, num.to_s, :p => zero_is_nil(num))
91
134
  end
92
135
 
93
136
  # HELPER
137
+ def show_guild g
138
+ HTML.new(menu("/guilds/#{g['guild_id']}",
139
+ h("#{g['guild_name']} [#{g['tag']}]")))
140
+ end
141
+
94
142
  def blank_icon
95
143
  %Q{<img class="icon" src="https://upload.wikimedia.org/wikipedia/commons/d/d2/Blank.png"/>}
96
144
  end
97
145
 
146
+ def item_wiki_list items
147
+ items.map(&method(:item_wiki)).join("\n")
148
+ end
149
+
98
150
  def item_wiki item
99
- if item['name']
100
- page = item['name'].tr(' ', '_')
151
+ if item['name'] && item['icon']
152
+ name = item['name'].tr(' ', '_')
101
153
  missing = if item['count'] == 0 then ' missing' else nil end
102
154
  img = %Q{<img class="icon#{missing}" title="#{item_title(item)}"} +
103
155
  %Q{ src="#{h item['icon']}"/>}
104
- %Q{<a href="http://wiki.guildwars2.com/wiki/#{u page}">#{img}</a>}
156
+ %Q{<a href="http://wiki.guildwars2.com/wiki/#{u name}">#{img}</a>}
105
157
  else
106
158
  blank_icon
107
159
  end
108
160
  end
109
161
 
162
+ def item_link item
163
+ name = item_name(item)
164
+ if item['nolink']
165
+ name
166
+ else
167
+ menu("/items/#{item['id']}", name)
168
+ end
169
+ end
170
+
171
+ def item_name item
172
+ h(item['name'] || "?#{item['id']}?")
173
+ end
174
+
110
175
  def item_title item
111
176
  d = item['description']
112
177
  d && d.unpack('U*').map{ |c| "&##{c};" }.join
@@ -137,6 +202,18 @@ module RestGW2
137
202
  end.join(' ')
138
203
  end
139
204
 
205
+ def dye_color dye
206
+ %w[cloth leather metal].map do |kind|
207
+ rgb = dye[kind]['rgb']
208
+ rgb && dye_preview(kind, rgb.join(', '))
209
+ end.join("\n")
210
+ end
211
+
212
+ def dye_preview kind, rgb
213
+ %Q{<span class="icon" title="#{kind}, rgb(#{rgb})"} +
214
+ %Q{ style="background-color: rgb(#{rgb})"></span>}
215
+ end
216
+
140
217
  def abbr_time_ago time, precision=1
141
218
  return unless time
142
219
  ago = time_ago(time)
@@ -144,8 +221,11 @@ module RestGW2
144
221
  %Q{(<abbr title="#{time}, #{ago.join(' ')} ago">#{short} ago</abbr>)}
145
222
  end
146
223
 
147
- def time_ago time, precision=1
148
- delta = (Time.now - Time.parse(time)).to_i
224
+ def time_ago time
225
+ duration((Time.now - Time.parse(time)).to_i)
226
+ end
227
+
228
+ def duration delta
149
229
  result = []
150
230
 
151
231
  [[ 60, :seconds],
@@ -180,22 +260,98 @@ module RestGW2
180
260
  end
181
261
  end
182
262
 
263
+ def all_items
264
+ bank, mats, chars = all_items_defer
265
+ flatten_chars = chars.flat_map do |c|
266
+ c['equipment'] +
267
+ c['bags'] +
268
+ c['bags'].flat_map{ |c| c && c['inventory'] }
269
+ end
270
+ (bank + mats + flatten_chars).compact.
271
+ sort_by{ |i| i['name'] || i['id'].to_s }.inject([]) do |r, i|
272
+ last = r.last
273
+ if last && last['id'] == i['id'] &&
274
+ last.values_at('skin', 'upgrades', 'infusions').compact.empty?
275
+ last['count'] += i['count']
276
+ else
277
+ r << i
278
+ end
279
+ r
280
+ end
281
+ end
282
+
283
+ def find_my_item id
284
+ bank, mats, chars = all_items_defer
285
+ [select_item(bank.compact, id), select_item(mats.compact, id),
286
+ chars.inject({}){ |r, c|
287
+ equi = select_item(c['equipment'].compact, id)
288
+ bags = c['bags'].reject(&:nil?).map do |b|
289
+ selected = select_item(b['inventory'].compact, id)
290
+ b.merge('inventory' => selected) if selected.any? ||
291
+ b['id'] == id
292
+ end.compact
293
+ r[c['name']] = [equi, bags] if equi.any? || bags.any?
294
+ r
295
+ }]
296
+ end
297
+
298
+ def select_item items, id
299
+ items.select{ |i| i['id'] == id }
300
+ end
301
+
302
+ def all_items_defer
303
+ bank = gw2_defer(:with_item_detail, 'v2/account/bank')
304
+ mats = gw2_defer(:with_item_detail, 'v2/account/materials')
305
+ chars = gw2_defer(:characters_with_detail).map do |c|
306
+ c['equipment'] = gw2_defer(:expand_item_detail, c['equipment'])
307
+ c['bags'] = gw2_defer(:bags_with_detail , c['bags'])
308
+ c
309
+ end
310
+ [bank, mats, chars]
311
+ end
312
+
183
313
  # CONTROLLER
184
- def gw2_call msg, *args
314
+ def gw2_request msg, *args
315
+ block ||= :itself.to_proc
185
316
  refresh = !!request.GET['r']
186
317
  opts = {'cache.update' => refresh, 'expires_in' => 600}
187
- yield(gw2.public_send(msg, *args, opts).itself)
188
- rescue RestGW2::Error => e
189
- @error = e.error['text']
190
- render :error
318
+ args << {} if msg == :with_item_detail
319
+ gw2.public_send(msg, *args, opts)
320
+ end
321
+
322
+ def gw2_defer msg, *args
323
+ PromisePool::Promise.new.defer do
324
+ gw2_request(msg, *args)
325
+ end.future
191
326
  end
192
327
 
193
- def trans_call msg, path, &block
194
- gw2_call(msg, path, :page => p) do |trans|
195
- @pages = calculate_pages("v2/commerce/transactions/#{path}")
196
- @trans = trans
197
- @total = sum_trans(trans)
198
- render :transactions
328
+ def skin_request type, subtype=nil, weight=nil
329
+ @items = gw2_request(:skins_with_detail).select do |i|
330
+ i['type'] == type &&
331
+ (subtype.nil? || subtype == i['details']['type']) &&
332
+ (weight.nil? || weight == i['details']['weight_class'])
333
+ end
334
+ @skin_submenu = "menu_#{type.downcase}s" if subtype
335
+ @subtype = subtype.downcase if subtype
336
+ @weight = weight.downcase if weight
337
+ @unlocked = @items.count{ |i| i['count'] > 0 }
338
+ render :skins
339
+ end
340
+
341
+ def trans_request msg, path
342
+ @trans = gw2_request(msg, path, :page => p)
343
+ @pages = calculate_pages("v2/commerce/transactions/#{path}")
344
+ @total = sum_trans(@trans)
345
+ render :transactions
346
+ end
347
+
348
+ def group_by_crafting characters
349
+ characters.inject(Hash.new{|h,k|h[k]=[]}) do |group, char|
350
+ char['crafting'].each do |crafting|
351
+ group[crafting['discipline']] <<
352
+ [crafting['rating'], char['name'], crafting['active']]
353
+ end
354
+ group
199
355
  end
200
356
  end
201
357
 
@@ -241,23 +397,23 @@ module RestGW2
241
397
 
242
398
  # UTILITIES
243
399
  def encrypt data
244
- cipher = OpenSSL::Cipher.new('aes-128-gcm')
400
+ cipher = new_cipher
245
401
  cipher.encrypt
246
402
  cipher.key = SECRET
247
403
  iv = cipher.random_iv
248
404
  encrypted = cipher.update(data) + cipher.final
249
- tag = cipher.auth_tag
405
+ tag = auth_tag(cipher)
250
406
  encode_base64(iv, encrypted, tag)
251
407
  end
252
408
 
253
409
  def decrypt data
254
410
  iv, encrypted, tag = decode_base64(data)
255
- decipher = OpenSSL::Cipher.new('aes-128-gcm')
256
- decipher.decrypt
257
- decipher.key = SECRET
258
- decipher.iv = iv
259
- decipher.auth_tag = tag
260
- decipher.update(encrypted) + decipher.final
411
+ cipher = new_cipher
412
+ cipher.decrypt
413
+ cipher.key = SECRET
414
+ cipher.iv = iv
415
+ set_auth_tag(cipher, tag)
416
+ cipher.update(encrypted) + cipher.final
261
417
  end
262
418
 
263
419
  def encode_base64 *data
@@ -268,6 +424,20 @@ module RestGW2
268
424
  str.split('.').map{ |d| d.tr('-_~', '+/=').unpack('m0').first }
269
425
  end
270
426
 
427
+ def new_cipher
428
+ OpenSSL::Cipher.new(ENV['CIPHER_ALGO'] || 'aes-128-gcm')
429
+ rescue OpenSSL::Cipher::CipherError
430
+ OpenSSL::Cipher.new('aes-128-cbc')
431
+ end
432
+
433
+ def auth_tag cipher
434
+ cipher.respond_to?(:auth_tag) && cipher.auth_tag || ''
435
+ end
436
+
437
+ def set_auth_tag cipher, tag
438
+ cipher.respond_to?(:auth_tag=) && cipher.auth_tag = tag
439
+ end
440
+
271
441
  # MISC
272
442
  def logger env
273
443
  env['rack.logger'] || begin
@@ -278,10 +448,17 @@ module RestGW2
278
448
  }
279
449
 
280
450
  handle Timeout::Error do
451
+ status 504
281
452
  @error = 'Timeout. Please try again.'
282
453
  render :error
283
454
  end
284
455
 
456
+ handle RestGW2::Error do |e|
457
+ status 502
458
+ @error = e.error['text']
459
+ render :error
460
+ end
461
+
285
462
  post '/access_token' do
286
463
  t = encrypt(request.POST['access_token'])
287
464
  r = request.POST['referrer']
@@ -294,26 +471,77 @@ module RestGW2
294
471
  end
295
472
 
296
473
  get '/account' do
297
- gw2_call(:account_with_detail) do |account|
298
- @info = account
299
- render :info
474
+ @info = gw2_request(:account_with_detail)
475
+ @info['guilds'].map!(&method(:show_guild))
476
+ render :info
477
+ end
478
+
479
+ get %r{\A/guilds/(?<uuid>[^/]+)\z} do |m|
480
+ gid = m[:uuid]
481
+ @guilds = gw2_request(:account_with_detail)['guilds']
482
+ if @guilds.find{ |g| g['guild_id'] == gid }
483
+ @treasury = gw2_defer(:treasury_with_detail, gid)
484
+ @stash = gw2_defer( :stash_with_detail, gid)
485
+ render :guild
486
+ else
487
+ status 404
488
+ @error = "Cannot find guild id: #{gid}"
489
+ render :error
300
490
  end
301
491
  end
302
492
 
303
493
  get '/characters' do
304
- render :wip
494
+ @chars = gw2_request(:characters_with_detail)
495
+ @total = @chars.inject(0){ |t, c| t + c['age'] }
496
+ @craftings = group_by_crafting(@chars)
497
+ render :characters
498
+ end
499
+
500
+ get %r{\A/characters/(?<name>[\w ]+)\z} do |m|
501
+ characters = gw2_request(:characters_with_detail)
502
+ @names = characters.map { |c| c['name'] }
503
+ name = m[:name]
504
+ char = characters.find{ |c| c['name'] == name }
505
+
506
+ equi = gw2_defer(:expand_item_detail, char['equipment'])
507
+ bags = gw2_defer(:bags_with_detail , char['bags'])
508
+
509
+ @equi = equi
510
+ @bags = bags
511
+
512
+ @equi_buy, @equi_sell = sum_items(@equi)
513
+ @bags_buy, @bags_sell = sum_items(@bags +
514
+ @bags.flat_map{ |c| c && c['inventory'] })
515
+ render :profile
305
516
  end
306
517
 
307
518
  get '/dyes' do
308
- render :wip
519
+ @dyes = gw2_request(:dyes_with_detail)
520
+ @buy, @sell = sum_items(@dyes)
521
+ @unlocked = @dyes.count{ |d| d['count'] > 0 }
522
+ render :dyes
309
523
  end
310
524
 
311
- get '/skins' do
312
- render :wip
525
+ get '/skins/backpacks' do
526
+ skin_request('Back')
527
+ end
528
+
529
+ weapons.each do |weapon|
530
+ get "/skins/weapons/#{weapon.downcase}" do
531
+ skin_request('Weapon', weapon)
532
+ end
533
+ end
534
+
535
+ armors.each do |armor|
536
+ armors_weight.each do |weight|
537
+ get "/skins/armors/#{armor.downcase}/#{weight.downcase}" do
538
+ skin_request('Armor', armor, weight)
539
+ end
540
+ end
313
541
  end
314
542
 
315
543
  get '/minis' do
316
- render :wip
544
+ render :items, gw2_request(:minis_with_detail)
317
545
  end
318
546
 
319
547
  get '/achievements' do
@@ -321,49 +549,48 @@ module RestGW2
321
549
  end
322
550
 
323
551
  get '/bank' do
324
- gw2_call(:with_item_detail, 'v2/account/bank') do |items|
325
- @items = items
326
- @buy, @sell = sum_items(items)
327
- render :items
328
- end
552
+ render :items, gw2_request(:with_item_detail, 'v2/account/bank')
329
553
  end
330
554
 
331
555
  get '/materials' do
332
- gw2_call(:with_item_detail, 'v2/account/materials') do |items|
333
- @items = items
334
- @buy, @sell = sum_items(items)
335
- render :items
336
- end
556
+ render :items, gw2_request(:with_item_detail, 'v2/account/materials')
337
557
  end
338
558
 
339
559
  get '/wallet' do
340
- gw2_call(:wallet_with_detail) do |wallet|
341
- @wallet = wallet
342
- render :wallet
343
- end
560
+ @wallet = gw2_request(:wallet_with_detail)
561
+ render :wallet
562
+ end
563
+
564
+ get '/items' do
565
+ render :items, all_items
566
+ end
567
+
568
+ get %r{\A/items/(?<id>\d+)\z} do |m|
569
+ items = find_my_item(m[:id].to_i)
570
+ @bank, @materials, @chars = items
571
+ @buy, @sell = sum_items(@bank + @materials + @chars.values.flatten)
572
+ render :items_from
344
573
  end
345
574
 
346
575
  get '/transactions/buying' do
347
- trans_call(:transactions_with_detail, 'current/buys')
576
+ trans_request(:transactions_with_detail_compact, 'current/buys')
348
577
  end
349
578
 
350
579
  get '/transactions/selling' do
351
- trans_call(:transactions_with_detail, 'current/sells')
580
+ trans_request(:transactions_with_detail_compact, 'current/sells')
352
581
  end
353
582
 
354
583
  get '/transactions/bought' do
355
- trans_call(:transactions_with_detail_compact, 'history/buys')
584
+ trans_request(:transactions_with_detail_compact, 'history/buys')
356
585
  end
357
586
 
358
587
  get '/transactions/sold' do
359
- trans_call(:transactions_with_detail_compact, 'history/sells')
588
+ trans_request(:transactions_with_detail_compact, 'history/sells')
360
589
  end
361
590
 
362
591
  get '/tokeninfo' do
363
- gw2_call(:get, 'v2/tokeninfo') do |info|
364
- @info = info
365
- render :info
366
- end
592
+ @info = gw2_request(:get, 'v2/tokeninfo')
593
+ render :info
367
594
  end
368
595
  end
369
596