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,309 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest-core'
|
4
|
+
|
5
|
+
require 'time'
|
6
|
+
require 'erb'
|
7
|
+
require 'cgi'
|
8
|
+
require 'zlib'
|
9
|
+
|
10
|
+
module RestGW2
|
11
|
+
class View < Struct.new(:request, :query_t)
|
12
|
+
HTML = Struct.new(:to_s)
|
13
|
+
COINS = %w[gold silver copper].zip(%w[
|
14
|
+
https://wiki.guildwars2.com/images/d/d1/Gold_coin.png
|
15
|
+
https://wiki.guildwars2.com/images/3/3c/Silver_coin.png
|
16
|
+
https://wiki.guildwars2.com/images/e/eb/Copper_coin.png
|
17
|
+
]).freeze
|
18
|
+
GEM = 'https://wiki.guildwars2.com/images/a/aa/Gem.png'
|
19
|
+
FAVICONS = %w[
|
20
|
+
https://wiki.guildwars2.com/images/4/42/SAB_1_Bauble_Icon.png
|
21
|
+
https://wiki.guildwars2.com/images/9/96/SAB_5_Bauble_Icon.png
|
22
|
+
https://wiki.guildwars2.com/images/7/70/SAB_10_Bauble_Icon.png
|
23
|
+
https://wiki.guildwars2.com/images/9/93/SAB_20_Bauble_Icon.png
|
24
|
+
https://wiki.guildwars2.com/images/c/cd/SAB_50_Bauble_Icon.png
|
25
|
+
]
|
26
|
+
|
27
|
+
def render name, arg=nil
|
28
|
+
erb(:layout){ erb(name, arg) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# FIXME: controller shouldn't call this directly
|
32
|
+
def sum_trans trans
|
33
|
+
trans.inject(0) do |sum, t|
|
34
|
+
sum + t['price'] * t['quantity']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# FIXME: controller shouldn't call this directly
|
39
|
+
def sum_items items
|
40
|
+
items.inject([0, 0]) do |sum, i|
|
41
|
+
next sum unless i
|
42
|
+
b = i['buys']
|
43
|
+
s = i['sells']
|
44
|
+
sum[0] += b['unit_price'] * i['count'] if b
|
45
|
+
sum[1] += s['unit_price'] * i['count'] if s
|
46
|
+
sum
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# FIXME: controller shouldn't call this directly
|
51
|
+
def show_guild g
|
52
|
+
HTML.new(menu("/guilds/#{g['id']}",
|
53
|
+
h("#{g['name']} [#{g['tag']}]")))
|
54
|
+
end
|
55
|
+
|
56
|
+
# FIXME: controller shouldn't call this directly
|
57
|
+
def query_p
|
58
|
+
@query_p ||= zero_is_nil(request.GET['p'])
|
59
|
+
end
|
60
|
+
|
61
|
+
# FIXME: controller shouldn't call this directly
|
62
|
+
def path str, q={}
|
63
|
+
RC::Middleware.request_uri(
|
64
|
+
RC::REQUEST_PATH => "#{ENV['RESTGW2_PREFIX']}#{str}",
|
65
|
+
RC::REQUEST_QUERY => q)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def erb name, arg=nil, &block
|
70
|
+
ERB.new(views(name)).result(binding, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def favicon
|
74
|
+
FAVICONS[Zlib.crc32(query_t.to_s) % FAVICONS.size]
|
75
|
+
end
|
76
|
+
|
77
|
+
def h str
|
78
|
+
case str
|
79
|
+
when String
|
80
|
+
CGI.escape_html(str)
|
81
|
+
when HTML
|
82
|
+
str.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def u str
|
87
|
+
CGI.escape(str) if str.kind_of?(String)
|
88
|
+
end
|
89
|
+
|
90
|
+
def views name
|
91
|
+
File.read("#{__dir__}/view/#{name}.erb")
|
92
|
+
end
|
93
|
+
|
94
|
+
def refresh_path
|
95
|
+
path(request.path, :p => query_p, :r => '1', :t => query_t)
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO: clean me up; can we not use block?
|
99
|
+
def menu item, name, query={}
|
100
|
+
href = path(item, query.merge(:t => query_t))
|
101
|
+
if path(request.path, :p => query_p, :t => query_t) == href
|
102
|
+
name
|
103
|
+
else
|
104
|
+
title = block_given? && yield
|
105
|
+
%Q{<a href="#{href}"#{title}>#{name}</a>}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# TODO: clean me up
|
110
|
+
def menu_sub prefix, item, name
|
111
|
+
key = "#{prefix}#{item}"
|
112
|
+
if path(request.path) == path(key)
|
113
|
+
menu(key, name, :p => query_p)
|
114
|
+
else
|
115
|
+
menu(key, name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def menu_guild gid, item, name
|
120
|
+
menu("/guilds/#{gid}#{item}", name)
|
121
|
+
end
|
122
|
+
|
123
|
+
def menu_char name
|
124
|
+
menu("/characters/#{RC::Middleware.escape(name)}", name)
|
125
|
+
end
|
126
|
+
|
127
|
+
def menu_item item, name
|
128
|
+
menu_sub('/items', item, name)
|
129
|
+
end
|
130
|
+
|
131
|
+
def menu_unlock item, name
|
132
|
+
menu_sub('/unlocks', item, name)
|
133
|
+
end
|
134
|
+
|
135
|
+
def menu_skin item, name
|
136
|
+
menu_unlock("/skins#{item}", name)
|
137
|
+
end
|
138
|
+
|
139
|
+
def menu_commerce item, name
|
140
|
+
menu_sub('/commerce', item, name)
|
141
|
+
end
|
142
|
+
|
143
|
+
def page num
|
144
|
+
menu(request.path, num.to_s, :p => zero_is_nil(num))
|
145
|
+
end
|
146
|
+
|
147
|
+
# HELPER
|
148
|
+
def blank_icon
|
149
|
+
%Q{<img class="icon" alt="blank" src="https://upload.wikimedia.org/wikipedia/commons/d/d2/Blank.png"/>}
|
150
|
+
end
|
151
|
+
|
152
|
+
def item_wiki_list items
|
153
|
+
items.map(&method(:item_wiki)).join("\n")
|
154
|
+
end
|
155
|
+
|
156
|
+
def item_wiki item
|
157
|
+
if item['name'] && item['icon']
|
158
|
+
name = item['name'].tr(' ', '_')
|
159
|
+
img = %Q{<img class="#{item_class(item)}"} +
|
160
|
+
%Q{ alt="#{item_name(item)}"} +
|
161
|
+
%Q{ title="#{item_title(item)}"} +
|
162
|
+
%Q{ src="#{h item['icon']}"/>}
|
163
|
+
if name.empty?
|
164
|
+
img
|
165
|
+
else
|
166
|
+
%Q{<a href="http://wiki.guildwars2.com/wiki/#{u name}">#{img}</a>}
|
167
|
+
end
|
168
|
+
else
|
169
|
+
blank_icon
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def item_link item
|
174
|
+
name = item_name(item)
|
175
|
+
type = item_type(item)
|
176
|
+
title = %Q{ title="#{type}"} if type
|
177
|
+
|
178
|
+
if item['nolink']
|
179
|
+
%Q{<span#{title}>#{name}</span>}
|
180
|
+
else
|
181
|
+
menu("/items/#{item['id']}", name) do
|
182
|
+
title
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def item_name item
|
188
|
+
name = item['name'].to_s
|
189
|
+
|
190
|
+
h(!name.empty? && name || "?#{item['id']}?")
|
191
|
+
end
|
192
|
+
|
193
|
+
def item_type item
|
194
|
+
type = [item.dig('details', 'damage_type'),
|
195
|
+
item.dig('details', 'type')].join(' ')
|
196
|
+
|
197
|
+
!type.empty? && type || nil
|
198
|
+
end
|
199
|
+
|
200
|
+
def item_title item
|
201
|
+
d = item['description']
|
202
|
+
d && d.unpack('U*').map{ |c| "&##{c};" }.join
|
203
|
+
end
|
204
|
+
|
205
|
+
def item_count item
|
206
|
+
c = item['count']
|
207
|
+
"(#{c})" if c > 1
|
208
|
+
end
|
209
|
+
|
210
|
+
def item_price item
|
211
|
+
b = item['buys']
|
212
|
+
s = item['sells']
|
213
|
+
bb = b && price(b['unit_price'])
|
214
|
+
ss = s && price(s['unit_price'])
|
215
|
+
%Q{#{bb} / #{ss}#{item_total_price(item, b, s)}} if bb || ss
|
216
|
+
end
|
217
|
+
|
218
|
+
def item_total_price item, b, s
|
219
|
+
count = item['count']
|
220
|
+
|
221
|
+
if count > 1
|
222
|
+
bb = b && price(b['unit_price'] * count)
|
223
|
+
ss = s && price(s['unit_price'] * count)
|
224
|
+
" (#{bb} / #{ss})"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def item_class item
|
229
|
+
missing = if item['count'] == 0 then ' missing' end
|
230
|
+
rarity = if item['rarity']
|
231
|
+
" rarity rarity-#{item['rarity'].downcase}"
|
232
|
+
end
|
233
|
+
"icon#{rarity}#{missing}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def item_attributes stats
|
237
|
+
stats.map do |name, value|
|
238
|
+
"#{name}: #{value}"
|
239
|
+
end.join(', ')
|
240
|
+
end
|
241
|
+
|
242
|
+
def price copper
|
243
|
+
g = copper / 100_00
|
244
|
+
s = copper % 100_00 / 100
|
245
|
+
c = copper % 100
|
246
|
+
l = [g, s, c]
|
247
|
+
n = l.index(&:nonzero?)
|
248
|
+
return '-' unless n
|
249
|
+
l.zip(COINS).drop(n).map do |(num, (name, src))|
|
250
|
+
price_tag(num, name, src)
|
251
|
+
end.join(' ')
|
252
|
+
end
|
253
|
+
|
254
|
+
def price_gem num
|
255
|
+
price_tag(num, 'gem', GEM)
|
256
|
+
end
|
257
|
+
|
258
|
+
def price_tag num, name, src
|
259
|
+
%Q{#{num}<img class="price"} +
|
260
|
+
%Q{ alt="#{name}" title="#{name}" src="#{src}"/>}
|
261
|
+
end
|
262
|
+
|
263
|
+
def dye_color dye
|
264
|
+
%w[cloth leather metal].map do |kind|
|
265
|
+
rgb = dye[kind]['rgb']
|
266
|
+
rgb && dye_preview(kind, rgb.join(', '))
|
267
|
+
end.join("\n")
|
268
|
+
end
|
269
|
+
|
270
|
+
def dye_preview kind, rgb
|
271
|
+
%Q{<span class="icon" title="#{kind}, rgb(#{rgb})"} +
|
272
|
+
%Q{ style="background-color: rgb(#{rgb})"></span>}
|
273
|
+
end
|
274
|
+
|
275
|
+
def abbr_time_ago time, precision=1
|
276
|
+
return unless time
|
277
|
+
ago = time_ago(time)
|
278
|
+
short = ago.take(precision).join(' ')
|
279
|
+
%Q{(<abbr title="#{time}, #{ago.join(' ')} ago">#{short} ago</abbr>)}
|
280
|
+
end
|
281
|
+
|
282
|
+
def time_ago time
|
283
|
+
duration((Time.now - Time.parse(time)).to_i)
|
284
|
+
end
|
285
|
+
|
286
|
+
def duration delta
|
287
|
+
result = []
|
288
|
+
|
289
|
+
[[ 60, :seconds],
|
290
|
+
[ 60, :minutes],
|
291
|
+
[ 24, :hours ],
|
292
|
+
[365, :days ],
|
293
|
+
[999, :years ]].
|
294
|
+
inject(delta) do |length, (divisor, name)|
|
295
|
+
quotient, remainder = length.divmod(divisor)
|
296
|
+
result.unshift("#{remainder} #{name}")
|
297
|
+
break if quotient == 0
|
298
|
+
quotient
|
299
|
+
end
|
300
|
+
|
301
|
+
result
|
302
|
+
end
|
303
|
+
|
304
|
+
def zero_is_nil n
|
305
|
+
r = n.to_i
|
306
|
+
r if r != 0
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
Total Played:
|
2
|
-
<%= duration(
|
2
|
+
<%= duration(arg[:total]).join(' ') %> (<%= arg[:total] / 3600 %> hours)
|
3
3
|
|
4
4
|
<h2>Characters</h2>
|
5
5
|
<table id="characters">
|
@@ -12,12 +12,16 @@ Total Played:
|
|
12
12
|
<th>Death</th>
|
13
13
|
<th>Birth</th>
|
14
14
|
</tr>
|
15
|
-
<%
|
15
|
+
<% arg[:chars].each do |c| %>
|
16
16
|
<tr>
|
17
17
|
<td>
|
18
18
|
<%= menu_char(c['name']) %> (<%= h c['level'].to_s %>)
|
19
|
-
|
20
|
-
|
19
|
+
<% if c['guild'] %>
|
20
|
+
<br/><%= show_guild(c['guild']) %>
|
21
|
+
<% end %>
|
22
|
+
<% if c['title'] %>
|
23
|
+
<br/><%= c['title'] %>
|
24
|
+
<% end %>
|
21
25
|
</td>
|
22
26
|
<td><%= h c['profession'] %></td>
|
23
27
|
<td><%= h "#{c['race']} #{c['gender']}" %></td>
|
@@ -32,10 +36,10 @@ Total Played:
|
|
32
36
|
|
33
37
|
<h2>Crafting</h2>
|
34
38
|
<table id="crafting">
|
35
|
-
<%
|
39
|
+
<% ServerAction.crafting.each do |d| %>
|
36
40
|
<tr>
|
37
41
|
<th><%= d %></th>
|
38
|
-
<%
|
42
|
+
<% arg[:craftings][d].sort.reverse_each do |(rating, name, active)| %>
|
39
43
|
<td class="active-<%= active %>"><%= "#{name} (#{rating})" %></td>
|
40
44
|
<% end %>
|
41
45
|
</tr>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<%= erb :menu_commerce %>
|
2
|
+
<%= erb :pages, arg[:pages] %>
|
3
|
+
|
4
|
+
Total: <%= price(arg[:total]) %>
|
5
|
+
<ul class="item">
|
6
|
+
<% arg[:items].each do |item| %>
|
7
|
+
<li>
|
8
|
+
<%= item_wiki(item) %>
|
9
|
+
<span class="description">
|
10
|
+
<%= item_link(item) %>
|
11
|
+
<%= h item_count(item) %>
|
12
|
+
<br/>
|
13
|
+
<% if item['price'] %>
|
14
|
+
<%= price(item['price']) %>
|
15
|
+
<%= abbr_time_ago(item['purchased'] || item['created']) %>
|
16
|
+
<br/>
|
17
|
+
<% end %>
|
18
|
+
<%= item_price(item) %>
|
19
|
+
</span>
|
20
|
+
</li>
|
21
|
+
<% end %>
|
22
|
+
</ul>
|
23
|
+
|
24
|
+
<%= erb :pages, arg[:pages] %>
|
@@ -1,11 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
<%= erb :menu_unlocks %>
|
2
|
+
|
3
|
+
<%= erb :unlock_percentage, arg[:dyes] %>
|
4
|
+
|
5
|
+
Total: <%= price(arg[:buy]) %> / <%= price(arg[:sell]) %>
|
6
|
+
|
7
7
|
<ul class="item">
|
8
|
-
<%
|
8
|
+
<% arg[:dyes].each do |dye| %>
|
9
9
|
<li class="dye">
|
10
10
|
<%= item_wiki(dye) %>
|
11
11
|
<%= dye_color(dye) %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<div class="error">Error: <%= h arg %></div>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<h2>Gem to Gold</h2>
|
2
|
+
<ul class="item">
|
3
|
+
<% arg[:buy_gold].each do |gold| %>
|
4
|
+
<li>
|
5
|
+
<%= item_wiki(gold) %>
|
6
|
+
<span class="description">
|
7
|
+
<%= h gold['name'] %>
|
8
|
+
(<%= price(gold['count']) %>)
|
9
|
+
<br/>
|
10
|
+
<%= price_gem(gold['price']) %>
|
11
|
+
</span>
|
12
|
+
</li>
|
13
|
+
<% end %>
|
14
|
+
</ul>
|
15
|
+
|
16
|
+
<h2>Gold to Gem</h2>
|
17
|
+
<ul class="item">
|
18
|
+
<% arg[:buy_gem].each do |gem| %>
|
19
|
+
<li>
|
20
|
+
<%= item_wiki(gem) %>
|
21
|
+
<span class="description">
|
22
|
+
<%= h gem['name'] %>
|
23
|
+
(<%= price_gem(gem['count']) %>)
|
24
|
+
<br/>
|
25
|
+
<%= price(gem['price']) %>
|
26
|
+
</span>
|
27
|
+
</li>
|
28
|
+
<% end %>
|
29
|
+
</ul>
|