maimai_net 0.0.1

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.
@@ -0,0 +1,485 @@
1
+ module MaimaiNet
2
+ module Constants
3
+ Constant = Module.new.freeze
4
+
5
+ module AutoConstant
6
+ def self.extend_object(cls)
7
+ super
8
+ return unless Module === cls
9
+
10
+ stack = caller_locations(0).find do |s| s.label == 'extend' end
11
+
12
+ cls.class_exec do
13
+ include Constant
14
+
15
+ attrs = instance_methods(false)
16
+ .map(&method(:instance_method))
17
+ .map(&:original_name)
18
+ .sort.uniq
19
+ const_set :VALID_ATTRIBUTES, attrs
20
+
21
+ alias_method :clone, :itself
22
+ alias_method :dup, :itself
23
+ undef_method :initialize_copy
24
+
25
+ constants.map(&method(:const_get))
26
+ .map(&:freeze)
27
+
28
+ attrs.each do |attr|
29
+ instance_eval <<~EOT, stack.path, stack.lineno
30
+ def #{attr}?(value)
31
+ new(#{attr}: value)
32
+ rescue TypeError
33
+ nil
34
+ end
35
+ EOT
36
+ end
37
+ end
38
+ end
39
+
40
+ def define_new(
41
+ key_enforce: nil,
42
+ key_lookup_enforce: nil,
43
+ key_assign_enforce: nil,
44
+ extra_lookup_keys: []
45
+ )
46
+ builder = []
47
+ builder << <<~EOT
48
+ @map ||= {}
49
+ @keys ||= {}
50
+ EOT
51
+
52
+ key_conditions = {}
53
+ key_conditions[Pathname] = 'key = key.to_s'
54
+ key_conditions[Hash] = <<~EOS
55
+ if !@map.empty? && key.size == 1 then
56
+ dk, dv = key.first
57
+ key = [
58
+ @map.values.find do |obj|
59
+ val = obj.public_send(dk)
60
+ !val.nil? && val == dv
61
+ end&.key,
62
+ key,
63
+ ].compact.each.next if const_get(:VALID_ATTRIBUTES).include? dk.to_sym
64
+ end
65
+ EOS
66
+
67
+ key_conditions[Integer] = <<-EOS if const_get(:VALID_ATTRIBUTES).include?(:id)
68
+ key = [
69
+ @map.values.find do |obj| obj.id == key end&.key,
70
+ key,
71
+ ].compact.each.next
72
+ EOS
73
+
74
+ builder << "case key\n%s\nend" % [key_conditions.map do |k, v| "when #{k}\n #{v}" end.join($/)]
75
+
76
+ key_lookup_enforce = key_enforce if key_lookup_enforce.nil?
77
+ key_assign_enforce = key_enforce if key_assign_enforce.nil?
78
+
79
+ builder << <<~'EOT'
80
+ key = key.to_sym if key.respond_to?(:to_sym)
81
+ fail TypeError, "expected Symbol, given %s" % [key.class] unless Symbol === key
82
+ EOT
83
+
84
+ extra_lookup_builder = extra_lookup_keys.map do |k|
85
+ "names << obj.#{k}"
86
+ end
87
+
88
+ key_lookup_enforce_builder = ''
89
+ key_lookup_enforce_builder = <<~EOT if Symbol === key_lookup_enforce
90
+ elsif @keys.key?(key.#{key_lookup_enforce}) then
91
+ obj = @map[@keys[key.#{key_lookup_enforce}]]
92
+ EOT
93
+
94
+ key_assign_enforce_builder = ''
95
+ key_assign_enforce_builder = "key = key.#{key_assign_enforce}" if Symbol === key_assign_enforce
96
+
97
+ builder << <<~EOT
98
+ if @keys.key?(key) then
99
+ obj = @map[@keys[key]]
100
+ #{key_lookup_enforce_builder}
101
+ else
102
+ #{key_assign_enforce_builder}
103
+ obj = super
104
+ @map[obj.object_id] = obj
105
+ names = [key]
106
+ #{extra_lookup_builder * $/}
107
+ names.each do |k|
108
+ @keys.store k, obj.object_id
109
+ end
110
+ end
111
+ EOT
112
+
113
+ builder << 'obj'
114
+
115
+ stack = caller_locations(1).first
116
+ instance_eval "def new(key)\n%s\nend" % [builder.join($/)],
117
+ stack.path, stack.lineno
118
+ end
119
+
120
+ def populate_entries(constant_or_list)
121
+ list = nil
122
+
123
+ case constant_or_list
124
+ when Symbol
125
+ list = const_get(constant_or_list)
126
+ else
127
+ list = constant_or_list
128
+ end
129
+
130
+ case list
131
+ when Hash
132
+ list = list.keys
133
+ when Enumerable
134
+ list = list.to_a
135
+ else
136
+ if Symbol === constant_or_list then
137
+ fail ArgumentError, "expected constant #{constant_or_list} is an enumerable, given #{list.class}"
138
+ else
139
+ fail TypeError, "expected a constant name or an enumerable, given #{constant_or_list.class}"
140
+ end
141
+ end
142
+
143
+ singleton_class.undef_method __method__ rescue 0
144
+ list.each &method(:new)
145
+ end
146
+ end
147
+
148
+ private_constant :AutoConstant
149
+
150
+ class AchievementFlag
151
+ COMBO = %i(fc ap)
152
+ SYNC = %i(sync fs fsd)
153
+ PLUS = %i(fc ap fs fsd)
154
+ RESULT_TO_RECORD = {
155
+ fsd: :fdx,
156
+ }
157
+
158
+ sym_iter = ->(&block){
159
+ (COMBO + SYNC).lazy.flat_map do |bk|
160
+ [bk, *(PLUS.include?(bk) ? [:"#{bk}+"] : [])]
161
+ end.map do |k|
162
+ is_plus = k.end_with?('+')
163
+ plusless = is_plus ? k[0...-1] : k.to_s
164
+ [k, plusless, is_plus]
165
+ end.map(&block)
166
+ }
167
+
168
+ KEYS = sym_iter.call do |k, bk, plus| k.upcase end
169
+ RECORD = sym_iter.call do |k, bk, plus|
170
+ bk = RESULT_TO_RECORD.fetch(bk.to_sym, bk).to_s
171
+ [k.upcase, -(plus ? "#{bk}p" : bk)]
172
+ end.to_h
173
+ RESULT = sym_iter.call do |k, bk, plus|
174
+ [k.upcase, -(plus ? "#{bk}plus" : bk)]
175
+ end.to_h
176
+
177
+ include ModuleExt
178
+
179
+ def initialize(key)
180
+ @key = key
181
+
182
+ @record_key = RECORD[key]
183
+ @result_key = RESULT[key]
184
+
185
+ freeze
186
+ end
187
+
188
+ attr_reader :key
189
+ attr_reader :record_key, :result_key
190
+
191
+ alias id key
192
+ alias to_sym key
193
+
194
+ extend AutoConstant
195
+ define_new key_enforce: :upcase
196
+ populate_entries :RECORD
197
+ end
198
+
199
+ class Difficulty
200
+ ORIGINAL = {
201
+ all: 0,
202
+ easy: 1,
203
+ basic: 2,
204
+ advanced: 3,
205
+ expert: 4,
206
+ master: 5,
207
+ remaster: 6,
208
+ utage: 10,
209
+ }
210
+
211
+ DELUXE = {
212
+ all: 0,
213
+ basic: 1,
214
+ advanced: 2,
215
+ expert: 3,
216
+ master: 4,
217
+ remaster: 5,
218
+ utage: 10,
219
+ }
220
+
221
+ DELUXE_WEBSITE = {
222
+ all: 99,
223
+ basic: 0,
224
+ advanced: 1,
225
+ expert: 2,
226
+ master: 3,
227
+ remaster: 4,
228
+ utage: 10,
229
+ }
230
+
231
+ LIBRARY = {
232
+ all: 0,
233
+ easy: 1,
234
+ basic: 2,
235
+ advanced: 3,
236
+ expert: 4,
237
+ master: 5,
238
+ remaster: 6,
239
+ utage: 10,
240
+ }
241
+
242
+ SHORTS = {
243
+ easy: :EM,
244
+ basic: :BS,
245
+ advanced: :AD,
246
+ expert: :EX,
247
+ master: :MS,
248
+ remaster: :RMS,
249
+ }
250
+
251
+ include ModuleExt
252
+
253
+ def initialize(key)
254
+ @key = key
255
+
256
+ @id = LIBRARY[key]
257
+ @original_id = ORIGINAL[key]
258
+ @deluxe_id = DELUXE[key]
259
+ @deluxe_web_id = DELUXE_WEBSITE[key]
260
+
261
+ @abbrev = SHORTS.fetch(key, key.upcase)
262
+
263
+ freeze
264
+ end
265
+
266
+ attr_reader :key, :abbrev
267
+ attr_reader :id, :original_id, :deluxe_id, :deluxe_web_id
268
+
269
+ alias long key
270
+ alias to_i id
271
+ alias to_sym key
272
+
273
+ extend AutoConstant
274
+ define_new key_enforce: :downcase, extra_lookup_keys: %i(abbrev)
275
+ populate_entries :LIBRARY
276
+ end
277
+
278
+ class Genre
279
+ ORIGINAL = {
280
+ pop_anime: 3,
281
+ niconico: 4,
282
+ touhou: 5,
283
+ sega: 6,
284
+ variety: 7,
285
+ original: 8,
286
+ all: 9,
287
+ }
288
+
289
+ DELUXE_WEBSITE = {
290
+ all: 99,
291
+ pop_anime: 101,
292
+ niconico: 102,
293
+ touhou: 103,
294
+ variety: 104,
295
+ maimai: 105,
296
+ siblings: 106,
297
+ }
298
+
299
+ include ModuleExt
300
+
301
+ def initialize(key)
302
+ @key = key
303
+
304
+ @original_id = ORIGINAL[key]
305
+ @deluxe_web_id = DELUXE_WEBSITE[key]
306
+
307
+ freeze
308
+ end
309
+
310
+ attr_reader :key, :abbrev
311
+ attr_reader :original_id, :deluxe_web_id
312
+
313
+ alias id key
314
+ alias to_sym key
315
+
316
+ extend AutoConstant
317
+ define_new key_enforce: :downcase
318
+ populate_entries :DELUXE_WEBSITE
319
+ end
320
+
321
+ class NameGroup
322
+ LIBRARY = {
323
+ japanese_a: 0,
324
+ japanese_ka: 1,
325
+ japanese_sa: 2,
326
+ japanese_ta: 3,
327
+ japanese_na: 4,
328
+ japanese_ha: 5,
329
+ japanese_ma: 6,
330
+ japanese_ya: 7,
331
+ japanese_ra: 8,
332
+ japanese_misc: 9,
333
+ latin_a: 10,
334
+ latin_e: 11,
335
+ latin_k: 12,
336
+ latin_p: 13,
337
+ latin_t: 14,
338
+ latin_misc: 15,
339
+ }
340
+
341
+ ORIGINAL = LIBRARY
342
+ DELUXE = LIBRARY
343
+ DELUXE_WEBSITE = LIBRARY
344
+
345
+ include ModuleExt
346
+
347
+ def initialize(key)
348
+ @key = key
349
+
350
+ @id = LIBRARY[key]
351
+ @deluxe_web_id = DELUXE_WEBSITE[key]
352
+
353
+ freeze
354
+ end
355
+
356
+ attr_reader :key
357
+ attr_reader :id, :deluxe_web_id
358
+
359
+ alias to_i id
360
+ alias to_sym key
361
+
362
+ extend AutoConstant
363
+ define_new key_enforce: :downcase
364
+ populate_entries :LIBRARY
365
+ end
366
+
367
+ class LevelGroup
368
+ LIBRARY = (1..15).flat_map do |i|
369
+ i < 7 ? i : [i, :"#{i}+"]
370
+ end.map do |k| :"L#{k}" end
371
+ .each_with_index.map do |k, i|
372
+ [k, i.succ]
373
+ end.to_h
374
+
375
+ DELUXE = LIBRARY
376
+ DELUXE_WEBSITE = LIBRARY
377
+
378
+ include ModuleExt
379
+
380
+ def initialize(key)
381
+ @key = key
382
+
383
+ @id = LIBRARY[key]
384
+ @deluxe_id = DELUXE[key]
385
+ @deluxe_web_id = DELUXE_WEBSITE[key]
386
+
387
+ freeze
388
+ end
389
+
390
+ attr_reader :key
391
+ attr_reader :id, :deluxe_id, :deluxe_web_id
392
+
393
+ alias to_i id
394
+ alias to_sym key
395
+
396
+ extend AutoConstant
397
+ define_new key_enforce: :upcase
398
+ populate_entries :LIBRARY
399
+ end
400
+
401
+ class GameVersion
402
+ ORIGINAL_VERSIONS = %w(maimai GReeN ORANGE PiNK MURASAKi MiLK FiNALE)
403
+ DELUXE_VERSIONS = %w(Deluxe Splash UNiVERSE FESTiVAL BUDDiES PRiSM CiRCLE)
404
+
405
+ VERSIONS = {}.tap do |ver|
406
+ ORIGINAL_VERSIONS.slice(0...-1).flat_map do |k|
407
+ [k, "#{k}_PLUS"]
408
+ end.push(ORIGINAL_VERSIONS.last).map(&:upcase).map(&:to_sym).each_with_index.map do |k, i|
409
+ case i
410
+ when 0..8
411
+ [k, 100 + i * 10]
412
+ else
413
+ [k, [180 + (i - 8) * 5, 199].min]
414
+ end
415
+ end.to_h.tap(&ver.method(:update))
416
+ DELUXE_VERSIONS.map(&:upcase).flat_map do |k|
417
+ [k, "#{k}_PLUS"]
418
+ end.map(&:to_sym).each_with_index.map do |k, i|
419
+ [k, 200 + i * 5]
420
+ end.to_h.tap(&ver.method(:update))
421
+ end
422
+ ORIGINAL = VERSIONS.select do |k, v| v < 200 end
423
+ DELUXE = VERSIONS.select do |k, v| v >= 200 end.transform_values do |v| v - 100 end
424
+ LIBRARY = VERSIONS
425
+ DELUXE_WEBSITE = LIBRARY.keys.each_with_index.to_h
426
+
427
+ include ModuleExt
428
+
429
+ def initialize(key)
430
+ @key = key
431
+
432
+ @id = LIBRARY[key]
433
+ @original_id = ORIGINAL[key]
434
+ @deluxe_id = DELUXE[key]
435
+ @deluxe_web_id = DELUXE_WEBSITE[key]
436
+
437
+ freeze
438
+ end
439
+
440
+ attr_reader :key
441
+ attr_reader :id, :original_id, :deluxe_id, :deluxe_web_id
442
+
443
+ alias to_i id
444
+ alias to_sym key
445
+
446
+ extend AutoConstant
447
+ define_new key_enforce: :upcase
448
+ populate_entries :LIBRARY
449
+ end
450
+
451
+ class BestScoreSortType
452
+ LIBRARY = %i(
453
+ achievement_high
454
+ achievement_low
455
+ deluxe_high
456
+ deluxe_low
457
+ combo_rank_high
458
+ combo_rank_low
459
+ ).each_with_index.to_h
460
+ .transform_values(&:succ)
461
+
462
+ def initialize(key)
463
+ @key = key
464
+
465
+ @id = LIBRARY[key]
466
+ @deluxe_web_id = LIBRARY[key]
467
+
468
+ freeze
469
+ end
470
+
471
+ attr_reader :key
472
+ attr_reader :id, :deluxe_web_id
473
+
474
+ alias to_i id
475
+ alias to_sym key
476
+
477
+ extend AutoConstant
478
+ define_new key_enforce: :downcase
479
+ populate_entries :LIBRARY
480
+ end
481
+ end
482
+
483
+ include Constants
484
+ private_constant :Constants
485
+ end
@@ -0,0 +1,12 @@
1
+ module MaimaiNet
2
+ module CoreExt
3
+ module KernelAutoConstantInclusion
4
+ MaimaiNet.constants.each do |k|
5
+ cls = MaimaiNet.const_get(k)
6
+ next unless Class === cls && cls < MaimaiNet::Constant
7
+
8
+ define_method k do |key| cls.new(key) end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ module MaimaiNet
2
+ module Error
3
+ class BaseError < StandardError
4
+ def maintenance?; false; end
5
+ end
6
+ class ClientError < BaseError; end
7
+ class ServerError < BaseError; end
8
+
9
+ class GeneralError < ServerError
10
+ def initialize(code)
11
+ super("Error #{code}")
12
+ @code = code
13
+ end
14
+ attr_reader :code
15
+ end
16
+
17
+ class LoginError < GeneralError; end
18
+ class SessionError < GeneralError; end
19
+ # This error is finnicky.
20
+ # Basically if this will happen when logging into non-home url
21
+ # either from previous cookie or through the provided callback.
22
+ class SessionRefreshError < SessionError
23
+ def initialize(code)
24
+ super
25
+ StandardError.instance_method(__method__).bind(self).call('Please access main page before accessing other pages.')
26
+ end
27
+ end
28
+ class SessionExpiredError < SessionError; end
29
+
30
+ class RequestRetry < ClientError; end
31
+ class RetryExhausted < ClientError; end
32
+
33
+ module Maintenance
34
+ def maintenance?
35
+ true
36
+ end
37
+ end
38
+ class RoutineMaintenance < ClientError
39
+ include Maintenance
40
+ def initialize(time_range)
41
+ start_time, end_time = time_range.begin, time_range.end
42
+ super("Maintenance from %s to %s." % [
43
+ start_time.strftime('%H:%M'),
44
+ end_time.strftime('%H:%M'),
45
+ ])
46
+
47
+ @start_time = start_time
48
+ @end_time = end_time
49
+ end
50
+
51
+ attr_reader :start_time, :end_time
52
+ end
53
+ class UnderMaintenance < ServerError; include Maintenance end
54
+ end
55
+ end
@@ -0,0 +1,38 @@
1
+ require 'faraday'
2
+ require 'http/cookie'
3
+
4
+ module MaimaiNet
5
+ module FaradayExt
6
+ # Slight modification from faraday-cookie_jar to follow domain redirects on response.
7
+ class CookieJar < Faraday::Middleware
8
+ def initialize(app, options = {})
9
+ super(app)
10
+ @jar = options[:jar] || HTTP::CookieJar.new
11
+ end
12
+
13
+ def call(env)
14
+ cookies = @jar.cookies(env[:url])
15
+ unless cookies.empty?
16
+ cookie_header = {}
17
+ # assign them to dummy cookie to make it compatible
18
+ HTTP::Cookie.parse(env[:request_headers]["Cookie"], env[:url]).each do |cookie|
19
+ cookie_header[cookie.name] = cookie.cookie_value
20
+ end if env[:request_headers]['Cookie']
21
+
22
+ cookies.each do |cookie| cookie_header[cookie.name] = cookie.cookie_value end
23
+ env[:request_headers]["Cookie"] = HTTP::Cookie.cookie_value(cookie_header.values)
24
+ end
25
+
26
+ @app.call(env).on_complete do |res|
27
+ if set_cookie = res[:response_headers]["Set-Cookie"]
28
+ @jar.parse(set_cookie, res[:url])
29
+ end if res[:response_headers]
30
+ end
31
+ end
32
+ end
33
+
34
+ Faraday::Middleware.tap do |m|
35
+ m.register_middleware cookie_jar: CookieJar
36
+ end if Faraday::Middleware.respond_to? :register_middleware
37
+ end
38
+ end