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 +4 -4
- data/CHANGES.md +14 -0
- data/README.md +18 -4
- data/Rakefile +19 -2
- data/config.ru +2 -0
- data/lib/rest-gw2/client.rb +162 -20
- data/lib/rest-gw2/server.rb +291 -64
- data/lib/rest-gw2/version.rb +1 -1
- data/lib/rest-gw2/view/characters.erb +43 -0
- data/lib/rest-gw2/view/dyes.erb +21 -0
- data/lib/rest-gw2/view/error.erb +1 -1
- data/lib/rest-gw2/view/guild.erb +14 -0
- data/lib/rest-gw2/view/info.erb +1 -1
- data/lib/rest-gw2/view/item_list.erb +11 -0
- data/lib/rest-gw2/view/item_show.erb +13 -0
- data/lib/rest-gw2/view/items.erb +4 -17
- data/lib/rest-gw2/view/items_from.erb +30 -0
- data/lib/rest-gw2/view/layout.erb +14 -4
- data/lib/rest-gw2/view/menu.erb +2 -1
- data/lib/rest-gw2/view/menu_armors.erb +11 -0
- data/lib/rest-gw2/view/menu_weapons.erb +5 -0
- data/lib/rest-gw2/view/profile.erb +42 -0
- data/lib/rest-gw2/view/skins.erb +14 -0
- data/lib/rest-gw2/view/transactions.erb +1 -1
- data/lib/rest-gw2/view/wip.erb +1 -1
- data/rest-gw2.gemspec +69 -59
- metadata +15 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8beaa0b13cd7d66c054200ad4f919a7751e66a1a
|
4
|
+
data.tar.gz: f48944e1a65d49072bc5bfdab62f739164978670
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
81
|
-
|
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', '>=
|
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
data/lib/rest-gw2/client.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
data/lib/rest-gw2/server.rb
CHANGED
@@ -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
|
-
|
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
|
43
|
-
ERB.new(views(
|
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
|
-
|
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
|
91
|
+
def views name
|
61
92
|
@views ||= {}
|
62
|
-
@views[
|
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
|
81
|
-
key = "
|
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
|
-
|
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
|
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
|
148
|
-
|
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
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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 =
|
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
|
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
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
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
|
-
|
298
|
-
|
299
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 :
|
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
|
-
|
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
|
-
|
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
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
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
|
-
|
576
|
+
trans_request(:transactions_with_detail_compact, 'current/buys')
|
348
577
|
end
|
349
578
|
|
350
579
|
get '/transactions/selling' do
|
351
|
-
|
580
|
+
trans_request(:transactions_with_detail_compact, 'current/sells')
|
352
581
|
end
|
353
582
|
|
354
583
|
get '/transactions/bought' do
|
355
|
-
|
584
|
+
trans_request(:transactions_with_detail_compact, 'history/buys')
|
356
585
|
end
|
357
586
|
|
358
587
|
get '/transactions/sold' do
|
359
|
-
|
588
|
+
trans_request(:transactions_with_detail_compact, 'history/sells')
|
360
589
|
end
|
361
590
|
|
362
591
|
get '/tokeninfo' do
|
363
|
-
|
364
|
-
|
365
|
-
render :info
|
366
|
-
end
|
592
|
+
@info = gw2_request(:get, 'v2/tokeninfo')
|
593
|
+
render :info
|
367
594
|
end
|
368
595
|
end
|
369
596
|
|