rest-gw2 0.4.0 → 0.5.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 +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
|