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 +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
|
|