rest-gw2 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGES.md +30 -0
- data/README.md +7 -3
- data/Rakefile +1 -0
- data/TODO.md +26 -0
- data/config.ru +6 -0
- data/lib/rest-gw2.rb +1 -0
- data/lib/rest-gw2/client.rb +249 -110
- data/lib/rest-gw2/client/item_detail.rb +80 -0
- data/lib/rest-gw2/server.rb +5 -583
- data/lib/rest-gw2/server/action.rb +283 -0
- data/lib/rest-gw2/server/cache.rb +14 -4
- data/lib/rest-gw2/server/imp.rb +270 -0
- data/lib/rest-gw2/server/runner.rb +1 -0
- data/lib/rest-gw2/server/view.rb +309 -0
- data/lib/rest-gw2/{view → server/view}/characters.erb +10 -6
- data/lib/rest-gw2/server/view/check_list.erb +10 -0
- data/lib/rest-gw2/server/view/check_percentage.erb +9 -0
- data/lib/rest-gw2/server/view/commerce.erb +24 -0
- data/lib/rest-gw2/{view → server/view}/dyes.erb +7 -7
- data/lib/rest-gw2/server/view/error.erb +1 -0
- data/lib/rest-gw2/server/view/exchange.erb +29 -0
- data/lib/rest-gw2/server/view/guild_info.erb +37 -0
- data/lib/rest-gw2/{view → server/view}/index.erb +0 -0
- data/lib/rest-gw2/{view → server/view}/info.erb +1 -1
- data/lib/rest-gw2/{view → server/view}/item_list.erb +0 -0
- data/lib/rest-gw2/{view/items.erb → server/view/item_section.erb} +0 -0
- data/lib/rest-gw2/{view → server/view}/item_show.erb +7 -1
- data/lib/rest-gw2/server/view/items.erb +8 -0
- data/lib/rest-gw2/server/view/items_from.erb +35 -0
- data/lib/rest-gw2/{view → server/view}/layout.erb +14 -4
- data/lib/rest-gw2/server/view/members.erb +23 -0
- data/lib/rest-gw2/server/view/menu.erb +13 -0
- data/lib/rest-gw2/server/view/menu_armors.erb +17 -0
- data/lib/rest-gw2/server/view/menu_commerce.erb +7 -0
- data/lib/rest-gw2/server/view/menu_guild.erb +11 -0
- data/lib/rest-gw2/server/view/menu_unlocks.erb +11 -0
- data/lib/rest-gw2/{view → server/view}/menu_weapons.erb +2 -1
- data/lib/rest-gw2/{view → server/view}/pages.erb +2 -2
- data/lib/rest-gw2/{view → server/view}/profile.erb +7 -7
- data/lib/rest-gw2/server/view/skins.erb +15 -0
- data/lib/rest-gw2/server/view/stash.erb +10 -0
- data/lib/rest-gw2/server/view/titles.erb +5 -0
- data/lib/rest-gw2/server/view/unlock_percentage.erb +1 -0
- data/lib/rest-gw2/server/view/unlocks_items.erb +5 -0
- data/lib/rest-gw2/server/view/unlocks_list.erb +3 -0
- data/lib/rest-gw2/{view → server/view}/wallet.erb +1 -1
- data/lib/rest-gw2/server/view/wip.erb +1 -0
- data/lib/rest-gw2/version.rb +2 -1
- data/rest-gw2.gemspec +43 -25
- data/task/README.md +8 -8
- data/task/gemgem.rb +29 -7
- metadata +42 -25
- data/lib/rest-gw2/view/error.erb +0 -1
- data/lib/rest-gw2/view/guild.erb +0 -14
- data/lib/rest-gw2/view/items_from.erb +0 -30
- data/lib/rest-gw2/view/menu.erb +0 -16
- data/lib/rest-gw2/view/menu_armors.erb +0 -11
- data/lib/rest-gw2/view/skins.erb +0 -14
- data/lib/rest-gw2/view/transactions.erb +0 -28
- data/lib/rest-gw2/view/wip.erb +0 -1
@@ -0,0 +1,283 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest-gw2/server/imp'
|
4
|
+
|
5
|
+
module RestGW2
|
6
|
+
class ServerAction
|
7
|
+
def self.weapons
|
8
|
+
%w[Greatsword Sword Hammer Mace Axe Dagger
|
9
|
+
Staff Scepter
|
10
|
+
Longbow Shortbow Rifle Pistol
|
11
|
+
Shield Torch Focus Warhorn
|
12
|
+
Spear Speargun Trident]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.armors
|
16
|
+
%w[Helm Shoulders Coat Gloves Leggings Boots HelmAquatic]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.armors_weight
|
20
|
+
%w[Light Medium Heavy Clothing]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.crafting
|
24
|
+
%w[Weaponsmith Huntsman Artificer
|
25
|
+
Armorsmith Leatherworker Tailor
|
26
|
+
Jeweler Chef Scribe]
|
27
|
+
end
|
28
|
+
|
29
|
+
include Jellyfish
|
30
|
+
controller_include NormalizedPath, ServerImp
|
31
|
+
|
32
|
+
handle Timeout::Error do
|
33
|
+
status 504
|
34
|
+
render :error, 'Timeout. Please try again.'
|
35
|
+
end
|
36
|
+
|
37
|
+
handle RestGW2::Error do |e|
|
38
|
+
status 502
|
39
|
+
render :error, e.error['text']
|
40
|
+
end
|
41
|
+
|
42
|
+
post '/access_token' do
|
43
|
+
t = encrypt(request.POST['access_token'])
|
44
|
+
r = request.POST['referrer']
|
45
|
+
u = if r == view.path('/') then view.path('/account') else r end
|
46
|
+
found "#{u}?t=#{t}"
|
47
|
+
end
|
48
|
+
|
49
|
+
get '/' do
|
50
|
+
render :index
|
51
|
+
end
|
52
|
+
|
53
|
+
get '/account' do
|
54
|
+
info = gw2_request(:account_with_detail).dup
|
55
|
+
%w[guilds guild_leader].each do |guild|
|
56
|
+
info[guild] = info[guild].map(&view.method(:show_guild))
|
57
|
+
end
|
58
|
+
|
59
|
+
render :info, info
|
60
|
+
end
|
61
|
+
|
62
|
+
get %r{\A/guilds/(?<uuid>[^/]+)\z} do |m|
|
63
|
+
guild_request(m[:uuid]) do |arg|
|
64
|
+
render :guild_info, arg.merge(:guild => arg.dig(:guilds, arg[:gid]))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
get %r{\A/guilds/(?<uuid>[^/]+)/members\z} do |m|
|
69
|
+
guild_request(m[:uuid]) do |arg|
|
70
|
+
members = gw2_defer(:guild_members, arg[:gid])
|
71
|
+
|
72
|
+
render :members, arg.merge(:members => members)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
get %r{\A/guilds/(?<uuid>[^/]+)/items\z} do |m|
|
77
|
+
guild_request(m[:uuid]) do |arg|
|
78
|
+
stash = gw2_defer( :stash_with_detail, arg[:gid])
|
79
|
+
treasury = gw2_defer(:treasury_with_detail, arg[:gid])
|
80
|
+
|
81
|
+
render :stash, arg.merge(:stash => stash, :treasury => treasury)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
get '/characters' do
|
86
|
+
chars = gw2_request(:characters_with_detail)
|
87
|
+
total = chars.inject(0){ |t, c| t + c['age'] }
|
88
|
+
craftings = group_by_crafting(chars)
|
89
|
+
|
90
|
+
render :characters, :chars => chars, :total => total,
|
91
|
+
:craftings => craftings
|
92
|
+
end
|
93
|
+
|
94
|
+
get %r{\A/characters/(?<name>[\w ]+)\z} do |m|
|
95
|
+
characters = gw2_request(:characters_with_detail)
|
96
|
+
names = characters.map { |c| c['name'] }
|
97
|
+
name = m[:name]
|
98
|
+
char = characters.find{ |c| c['name'] == name }
|
99
|
+
equi = gw2_defer(:expand_item_detail, char['equipment'])
|
100
|
+
bags = gw2_defer(:bags_with_detail , char['bags'])
|
101
|
+
|
102
|
+
equi_buy, equi_sell = view.sum_items(equi)
|
103
|
+
bags_buy, bags_sell = view.sum_items(bags +
|
104
|
+
bags.flat_map{ |c| c && c['inventory'] })
|
105
|
+
|
106
|
+
render :profile, :names => names, :equi => equi, :bags => bags,
|
107
|
+
:equi_buy => equi_buy, :equi_sell => equi_sell,
|
108
|
+
:bags_buy => bags_buy, :bags_sell => bags_sell
|
109
|
+
end
|
110
|
+
|
111
|
+
get '/items' do
|
112
|
+
render :items, gw2_request(:with_item_detail, 'v2/account/inventory')
|
113
|
+
end
|
114
|
+
|
115
|
+
get '/items/bank' do
|
116
|
+
render :items, gw2_request(:with_item_detail, 'v2/account/bank')
|
117
|
+
end
|
118
|
+
|
119
|
+
get '/items/materials' do
|
120
|
+
render :items, gw2_request(:with_item_detail, 'v2/account/materials')
|
121
|
+
end
|
122
|
+
|
123
|
+
get '/items/all' do
|
124
|
+
render :items, all_items
|
125
|
+
end
|
126
|
+
|
127
|
+
get %r{\A/items/(?<id>\d+)\z} do |m|
|
128
|
+
acct, bank, materials, chars = find_my_item(m[:id].to_i)
|
129
|
+
buy, sell = view.sum_items(acct + bank + materials +
|
130
|
+
chars.values.flatten)
|
131
|
+
|
132
|
+
render :items_from, :acct => acct, :bank => bank,
|
133
|
+
:materials => materials, :chars => chars,
|
134
|
+
:buy => buy, :sell => sell
|
135
|
+
end
|
136
|
+
|
137
|
+
get '/wallet' do
|
138
|
+
render :wallet, gw2_request(:wallet_with_detail)
|
139
|
+
end
|
140
|
+
|
141
|
+
get '/unlocks/skins/backpacks' do
|
142
|
+
skin_request('Back')
|
143
|
+
end
|
144
|
+
|
145
|
+
weapons.each do |weapon|
|
146
|
+
get "/unlocks/skins/weapons/#{weapon.downcase}" do
|
147
|
+
skin_request('Weapon', weapon)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
get '/unlocks/skins/weapons/other' do
|
152
|
+
subtype = Regexp.new("\\A(?:#{Regexp.union(*ServerAction.weapons)})\\z")
|
153
|
+
|
154
|
+
skin_request('Weapon', 'Other') do |item|
|
155
|
+
item.dig('details', 'type') !~ subtype
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
armors.each do |armor|
|
160
|
+
armors_weight.each do |weight|
|
161
|
+
get "/unlocks/skins/armors/#{armor.downcase}/#{weight.downcase}" do
|
162
|
+
skin_request('Armor', armor, weight)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
get '/unlocks/skins/armors/other' do
|
168
|
+
subtype = Regexp.new("\\A(?:#{Regexp.union(*ServerAction.armors)})\\z")
|
169
|
+
|
170
|
+
skin_request('Armor', 'Other') do |item|
|
171
|
+
item.dig('details', 'type') !~ subtype
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
get '/unlocks/skins/gathering' do
|
176
|
+
skin_request('Gathering')
|
177
|
+
end
|
178
|
+
|
179
|
+
get '/unlocks/dyes' do
|
180
|
+
dyes = gw2_request(:dyes_with_detail)
|
181
|
+
buy, sell = view.sum_items(dyes)
|
182
|
+
unlocked = dyes.count{ |d| d['count'] > 0 }
|
183
|
+
|
184
|
+
render :dyes, :dyes => dyes,
|
185
|
+
:buy => buy, :sell => sell,
|
186
|
+
:unlocked => unlocked
|
187
|
+
end
|
188
|
+
|
189
|
+
get '/unlocks/outfits' do
|
190
|
+
render :unlocks_items, gw2_request(:outfits_with_detail)
|
191
|
+
end
|
192
|
+
|
193
|
+
get '/unlocks/minis' do
|
194
|
+
render :unlocks_items, gw2_request(:minis_with_detail)
|
195
|
+
end
|
196
|
+
|
197
|
+
get '/unlocks/finishers' do
|
198
|
+
render :unlocks_items, gw2_request(:finishers_with_detail)
|
199
|
+
end
|
200
|
+
|
201
|
+
get '/unlocks/mailcarriers' do
|
202
|
+
render :unlocks_items, gw2_request(:mailcarriers_with_detail)
|
203
|
+
end
|
204
|
+
|
205
|
+
get '/unlocks/gliders' do
|
206
|
+
render :unlocks_items, gw2_request(:gliders_with_detail)
|
207
|
+
end
|
208
|
+
|
209
|
+
get '/unlocks/cats' do
|
210
|
+
render :unlocks_list, gw2_request(:cats_with_detail)
|
211
|
+
end
|
212
|
+
|
213
|
+
get '/unlocks/nodes' do
|
214
|
+
render :unlocks_list, gw2_request(:nodes_with_detail)
|
215
|
+
end
|
216
|
+
|
217
|
+
get '/achievements/titles' do
|
218
|
+
render :titles, gw2_request(:titles_with_detail)
|
219
|
+
end
|
220
|
+
|
221
|
+
get '/commerce/delivery' do
|
222
|
+
items = gw2_request(:delivery_with_detail, :page => view.query_p)
|
223
|
+
total = items.shift['price']
|
224
|
+
|
225
|
+
render :commerce, :items => items, :total => total
|
226
|
+
end
|
227
|
+
|
228
|
+
get '/commerce/buying' do
|
229
|
+
trans_request(:transactions_with_detail_compact, 'current/buys')
|
230
|
+
end
|
231
|
+
|
232
|
+
get '/commerce/selling' do
|
233
|
+
trans_request(:transactions_with_detail_compact, 'current/sells')
|
234
|
+
end
|
235
|
+
|
236
|
+
get '/commerce/bought' do
|
237
|
+
trans_request(:transactions_with_detail_compact, 'history/buys')
|
238
|
+
end
|
239
|
+
|
240
|
+
get '/commerce/sold' do
|
241
|
+
trans_request(:transactions_with_detail_compact, 'history/sells')
|
242
|
+
end
|
243
|
+
|
244
|
+
get '/exchange' do
|
245
|
+
# We try to spend 800 gems to buy golds as the standard for buying gold,
|
246
|
+
# and try to spend 100 golds to buy gems as the standard for buying gems
|
247
|
+
# Then we try to use those ratios to calculate the prices and match
|
248
|
+
# the information in the game. We also try to include the standard
|
249
|
+
# to the list, sorted it to the proper place.
|
250
|
+
# This is trying to match whatever the game is showing, and also show
|
251
|
+
# the real responses from the API.
|
252
|
+
gold_std, gem_std =
|
253
|
+
[stub_gold(800), stub_gem(100_00_00)].map(&method(:resolve_count))
|
254
|
+
|
255
|
+
buy_gold_coins_per_gem = gold_std['coins_per_gem']
|
256
|
+
buy_gold = [gold_std, 1, 10, 50, 100, 250].map do |gold|
|
257
|
+
case gold
|
258
|
+
when Numeric
|
259
|
+
coins = gold * 100_00
|
260
|
+
stub_gold((coins.to_f / buy_gold_coins_per_gem).round, coins)
|
261
|
+
else
|
262
|
+
gold
|
263
|
+
end
|
264
|
+
end.sort_by{ |g| g['count'] }
|
265
|
+
|
266
|
+
buy_gem_coins_per_gem = gem_std['coins_per_gem']
|
267
|
+
buy_gem = [gem_std, 400, 800, 1200, 2000].map do |gem|
|
268
|
+
case gem
|
269
|
+
when Numeric
|
270
|
+
stub_gem(gem * buy_gem_coins_per_gem, gem)
|
271
|
+
else
|
272
|
+
gem
|
273
|
+
end
|
274
|
+
end.sort_by{ |g| g['count'] }
|
275
|
+
|
276
|
+
render :exchange, :buy_gold => buy_gold, :buy_gem => buy_gem
|
277
|
+
end
|
278
|
+
|
279
|
+
get '/tokeninfo' do
|
280
|
+
render :info, gw2_request(:get, 'v2/tokeninfo')
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module RestGW2
|
3
4
|
module Cache
|
5
|
+
EXPIRES_IN = 600
|
6
|
+
LRU_SIZE = 8192
|
7
|
+
|
4
8
|
module_function
|
5
9
|
def default logger
|
6
10
|
@cache ||= Cache.pick(logger)
|
@@ -12,8 +16,8 @@ module RestGW2
|
|
12
16
|
|
13
17
|
def memcache logger
|
14
18
|
require 'dalli'
|
15
|
-
client = Dalli::Client.new
|
16
|
-
File.open(IO::NULL) do |null|
|
19
|
+
client = Dalli::Client.new(nil, :expires_in => EXPIRES_IN)
|
20
|
+
File.open(IO::NULL, 'w') do |null|
|
17
21
|
Dalli.logger = Logger.new(null)
|
18
22
|
client.alive!
|
19
23
|
Dalli.logger = logger
|
@@ -28,8 +32,14 @@ module RestGW2
|
|
28
32
|
|
29
33
|
def lru_cache logger
|
30
34
|
require 'lru_redux'
|
31
|
-
logger.info("LRU cache size:
|
32
|
-
LruRedux::ThreadSafeCache.new(
|
35
|
+
logger.info("LRU cache size: #{LRU_SIZE}")
|
36
|
+
cache = LruRedux::ThreadSafeCache.new(LRU_SIZE)
|
37
|
+
cache.extend(Module.new{
|
38
|
+
def fetch key # original fetch could deadlock
|
39
|
+
self[key] || self[key] = yield
|
40
|
+
end
|
41
|
+
})
|
42
|
+
cache
|
33
43
|
rescue LoadError => e
|
34
44
|
logger.debug("Skip LRU cache because: #{e}")
|
35
45
|
nil
|
@@ -0,0 +1,270 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest-gw2/server/cache'
|
4
|
+
require 'rest-gw2/server/view'
|
5
|
+
require 'rest-gw2/client'
|
6
|
+
|
7
|
+
require 'rest-core'
|
8
|
+
require 'jellyfish'
|
9
|
+
|
10
|
+
require 'uri'
|
11
|
+
require 'timeout'
|
12
|
+
require 'openssl'
|
13
|
+
|
14
|
+
module RestGW2
|
15
|
+
module ServerImp
|
16
|
+
SECRET = ENV['RESTGW2_SECRET'] || 'RESTGW2_SECRET'*2
|
17
|
+
|
18
|
+
GemIcon = 'https://render.guildwars2.com/' \
|
19
|
+
'file/220061640ECA41C0577758030357221B4ECCE62C/502065.png'
|
20
|
+
GoldIcon = 'https://render.guildwars2.com/' \
|
21
|
+
'file/98457F504BA2FAC8457F532C4B30EDC23929ACF9/619316.png'
|
22
|
+
|
23
|
+
# VIEW
|
24
|
+
def view
|
25
|
+
@view ||= View.new(request, query_t)
|
26
|
+
end
|
27
|
+
|
28
|
+
def render *args
|
29
|
+
view.render(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def all_items
|
33
|
+
acct, bank, mats, chars = all_items_defer
|
34
|
+
flatten_chars = chars.flat_map do |c|
|
35
|
+
c['equipment'] +
|
36
|
+
c['bags'] +
|
37
|
+
c['bags'].flat_map{ |c| c && c['inventory'] }
|
38
|
+
end
|
39
|
+
(acct + bank + mats + flatten_chars).compact.
|
40
|
+
sort_by{ |i| i['name'] || i['id'].to_s }.inject([]) do |r, i|
|
41
|
+
last = r.last
|
42
|
+
if last && last['id'] == i['id'] &&
|
43
|
+
last.values_at('skin', 'upgrades', 'infusions').compact.empty?
|
44
|
+
last['count'] += i['count']
|
45
|
+
else
|
46
|
+
r << i
|
47
|
+
end
|
48
|
+
r
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_my_item id
|
53
|
+
acct, bank, mats, chars = all_items_defer
|
54
|
+
[select_item(acct.compact, id),
|
55
|
+
select_item(bank.compact, id),
|
56
|
+
select_item(mats.compact, id),
|
57
|
+
chars.inject({}){ |r, c|
|
58
|
+
equi = select_item(c['equipment'].compact, id)
|
59
|
+
bags = c['bags'].reject(&:nil?).map do |b|
|
60
|
+
selected = select_item(b['inventory'].compact, id)
|
61
|
+
b.merge('inventory' => selected) if selected.any? ||
|
62
|
+
b['id'] == id
|
63
|
+
end.compact
|
64
|
+
r[c['name']] = [equi, bags] if equi.any? || bags.any?
|
65
|
+
r
|
66
|
+
}]
|
67
|
+
end
|
68
|
+
|
69
|
+
def select_item items, id
|
70
|
+
items.select{ |i| i['id'] == id }
|
71
|
+
end
|
72
|
+
|
73
|
+
def all_items_defer
|
74
|
+
acct = gw2_defer(:with_item_detail, 'v2/account/inventory')
|
75
|
+
bank = gw2_defer(:with_item_detail, 'v2/account/bank')
|
76
|
+
mats = gw2_defer(:with_item_detail, 'v2/account/materials')
|
77
|
+
chars = gw2_defer(:characters_with_detail).map do |c|
|
78
|
+
c['equipment'] = gw2_defer(:expand_item_detail, c['equipment'])
|
79
|
+
c['bags'] = gw2_defer(:bags_with_detail , c['bags'])
|
80
|
+
c
|
81
|
+
end
|
82
|
+
[acct, bank, mats, chars]
|
83
|
+
end
|
84
|
+
|
85
|
+
# CONTROLLER
|
86
|
+
def gw2_request msg, *args
|
87
|
+
block ||= :itself.to_proc
|
88
|
+
refresh = !!request.GET['r']
|
89
|
+
opts = {'cache.update' => refresh, 'expires_in' => Cache::EXPIRES_IN}
|
90
|
+
args << {} if msg == :with_item_detail
|
91
|
+
key = cache_key(msg, args)
|
92
|
+
cache.delete(key) if refresh
|
93
|
+
cache.fetch(key) do
|
94
|
+
PromisePool::Future.resolve(gw2.public_send(msg, *args, opts))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def gw2_defer msg, *args
|
99
|
+
PromisePool::Promise.new.defer do
|
100
|
+
gw2_request(msg, *args)
|
101
|
+
end.future
|
102
|
+
end
|
103
|
+
|
104
|
+
def guild_request gid
|
105
|
+
guilds = gw2_request(:account_with_detail)['guilds'].
|
106
|
+
group_by{ |g| g['id'] }.inject({}){ |r, (id, v)| r[id] = v.first; r }
|
107
|
+
|
108
|
+
if guilds[gid]
|
109
|
+
yield(:gid => gid, :guilds => guilds)
|
110
|
+
else
|
111
|
+
status 404
|
112
|
+
render :error, "Cannot find guild id: #{gid}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def skin_request type, subtype=nil, weight=nil, &block
|
117
|
+
items = gw2_request(:skins_with_detail).select do |i|
|
118
|
+
filter_skin(i, type, subtype, weight, &block)
|
119
|
+
end
|
120
|
+
skin_submenu = "menu_#{type.downcase}s" if subtype
|
121
|
+
subtype = subtype.downcase if subtype
|
122
|
+
weight = weight.downcase if weight
|
123
|
+
unlocked = items.count{ |i| i['count'] > 0 }
|
124
|
+
|
125
|
+
render :skins, :items => items,
|
126
|
+
:skin_submenu => skin_submenu,
|
127
|
+
:subtype => subtype,
|
128
|
+
:weight => weight,
|
129
|
+
:unlocked => unlocked
|
130
|
+
end
|
131
|
+
|
132
|
+
def filter_skin item, type, subtype, weight
|
133
|
+
item['type'] == type &&
|
134
|
+
if block_given?
|
135
|
+
yield(item)
|
136
|
+
else
|
137
|
+
(subtype.nil? || subtype == item.dig('details', 'type')) &&
|
138
|
+
(weight.nil? || weight == item.dig('details', 'weight_class'))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def trans_request msg, path
|
143
|
+
items = gw2_request(msg, path, :page => view.query_p)
|
144
|
+
total = view.sum_trans(items)
|
145
|
+
pages = calculate_pages("v2/commerce/transactions/#{path}")
|
146
|
+
|
147
|
+
render :commerce, :items => items, :total => total, :pages => pages
|
148
|
+
end
|
149
|
+
|
150
|
+
def group_by_crafting characters
|
151
|
+
characters.inject(Hash.new{|h,k|h[k]=[]}) do |group, char|
|
152
|
+
char['crafting'].each do |crafting|
|
153
|
+
group[crafting['discipline']] <<
|
154
|
+
[crafting['rating'], char['name'], crafting['active']]
|
155
|
+
end
|
156
|
+
group
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def calculate_pages path
|
161
|
+
link = gw2.get(path, {:page_size => 200},
|
162
|
+
RC::RESPONSE_KEY => RC::RESPONSE_HEADERS)['LINK']
|
163
|
+
pages = RC::ParseLink.parse_link(link)
|
164
|
+
parse_page(pages['first']['uri'])..parse_page(pages['last']['uri'])
|
165
|
+
end
|
166
|
+
|
167
|
+
def parse_page uri
|
168
|
+
RC::ParseQuery.parse_query(URI.parse(uri).query)['page'].to_i
|
169
|
+
end
|
170
|
+
|
171
|
+
def stub_gold num, count=nil
|
172
|
+
count ||=
|
173
|
+
gw2_request(:get, 'v2/commerce/exchange/gems', :quantity => num)
|
174
|
+
|
175
|
+
{'name' => 'Gold', 'icon' => GoldIcon, 'price' => num, 'count' => count}
|
176
|
+
end
|
177
|
+
|
178
|
+
def stub_gem num, count=nil
|
179
|
+
count ||=
|
180
|
+
gw2_request(:get, 'v2/commerce/exchange/coins', :quantity => num)
|
181
|
+
|
182
|
+
{'name' => 'Gem', 'icon' => GemIcon, 'price' => num, 'count' => count}
|
183
|
+
end
|
184
|
+
|
185
|
+
def resolve_count item
|
186
|
+
item['coins_per_gem'] = item['count']['coins_per_gem']
|
187
|
+
item['count'] = item['count']['quantity']
|
188
|
+
item
|
189
|
+
end
|
190
|
+
|
191
|
+
def gw2
|
192
|
+
@gw2 ||= Client.new(:access_token => access_token,
|
193
|
+
:log_method => logger(env).method(:info),
|
194
|
+
:cache => cache)
|
195
|
+
end
|
196
|
+
|
197
|
+
def cache
|
198
|
+
@cache ||= RestGW2::Cache.default(logger(env))
|
199
|
+
end
|
200
|
+
|
201
|
+
def cache_key msg, args
|
202
|
+
[msg, *args, access_token].join(':')
|
203
|
+
end
|
204
|
+
|
205
|
+
# ACCESS TOKEN
|
206
|
+
def access_token
|
207
|
+
query_t && decrypt(query_t) || ENV['RESTGW2_ACCESS_TOKEN']
|
208
|
+
rescue ArgumentError, OpenSSL::Cipher::CipherError => e
|
209
|
+
raise RestGW2::Error.new({'text' => e.message}, 0)
|
210
|
+
end
|
211
|
+
|
212
|
+
def query_t
|
213
|
+
@query_t ||= begin
|
214
|
+
r = request.GET['t']
|
215
|
+
r if r && !r.strip.empty?
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# UTILITIES
|
220
|
+
def encrypt data
|
221
|
+
cipher = new_cipher
|
222
|
+
cipher.encrypt
|
223
|
+
cipher.key = SECRET
|
224
|
+
iv = cipher.random_iv
|
225
|
+
encrypted = cipher.update(data) + cipher.final
|
226
|
+
tag = auth_tag(cipher)
|
227
|
+
encode_base64(iv, encrypted, tag)
|
228
|
+
end
|
229
|
+
|
230
|
+
def decrypt data
|
231
|
+
iv, encrypted, tag = decode_base64(data)
|
232
|
+
cipher = new_cipher
|
233
|
+
cipher.decrypt
|
234
|
+
cipher.key = SECRET
|
235
|
+
cipher.iv = iv
|
236
|
+
set_auth_tag(cipher, tag)
|
237
|
+
cipher.update(encrypted) + cipher.final
|
238
|
+
end
|
239
|
+
|
240
|
+
def encode_base64 *data
|
241
|
+
data.map{ |d| [d].pack('m0') }.join('.').tr('+/=', '-_~')
|
242
|
+
end
|
243
|
+
|
244
|
+
def decode_base64 str
|
245
|
+
str.split('.').map{ |d| d.tr('-_~', '+/=').unpack('m0').first }
|
246
|
+
end
|
247
|
+
|
248
|
+
def new_cipher
|
249
|
+
OpenSSL::Cipher.new(ENV['CIPHER_ALGO'] || 'aes-128-gcm')
|
250
|
+
rescue OpenSSL::Cipher::CipherError
|
251
|
+
OpenSSL::Cipher.new('aes-128-cbc')
|
252
|
+
end
|
253
|
+
|
254
|
+
def auth_tag cipher
|
255
|
+
cipher.respond_to?(:auth_tag) && cipher.auth_tag || ''
|
256
|
+
end
|
257
|
+
|
258
|
+
def set_auth_tag cipher, tag
|
259
|
+
cipher.respond_to?(:auth_tag=) && cipher.auth_tag = tag
|
260
|
+
end
|
261
|
+
|
262
|
+
# MISC
|
263
|
+
def logger env
|
264
|
+
env['rack.logger'] || begin
|
265
|
+
require 'logger'
|
266
|
+
Logger.new(env['rack.errors'])
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|