rest-gw2 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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