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.
- checksums.yaml +7 -0
- data/.github/workflows/gem.yml +53 -0
- data/.gitignore +165 -0
- data/.rspec +5 -0
- data/Gemfile +4 -0
- data/LICENSE +32 -0
- data/README.md +47 -0
- data/Rakefile +6 -0
- data/bin/console +16 -0
- data/bin/setup +6 -0
- data/lib/maimai_net/client.rb +1104 -0
- data/lib/maimai_net/constants.rb +485 -0
- data/lib/maimai_net/core_ext.rb +12 -0
- data/lib/maimai_net/error.rb +55 -0
- data/lib/maimai_net/faraday_ext/cookie_jar.rb +38 -0
- data/lib/maimai_net/model-typing.rb +202 -0
- data/lib/maimai_net/model.rb +359 -0
- data/lib/maimai_net/module_ext.rb +437 -0
- data/lib/maimai_net/page-debug.rb +9 -0
- data/lib/maimai_net/page-html_helper.rb +131 -0
- data/lib/maimai_net/page-player_data_helper.rb +33 -0
- data/lib/maimai_net/page-track_result_helper.rb +90 -0
- data/lib/maimai_net/page.rb +606 -0
- data/lib/maimai_net/refines.rb +28 -0
- data/lib/maimai_net/region.rb +108 -0
- data/lib/maimai_net/user_option.rb +147 -0
- data/lib/maimai_net/util.rb +9 -0
- data/lib/maimai_net/version.rb +3 -0
- data/lib/maimai_net.rb +14 -0
- data/maimai-net.gemspec +44 -0
- metadata +172 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
module MaimaiNet::Model
|
|
2
|
+
module GenericComparison
|
|
3
|
+
refine Array do
|
|
4
|
+
def generic_of?(variants)
|
|
5
|
+
each_with_index.all? do |val, i| variants[i % variants.size] === val end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def map_class
|
|
9
|
+
empty? ? self.class : Generic[self.class, Either[*map(&:class)]]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
refine Hash do
|
|
14
|
+
def generic_of?(variants)
|
|
15
|
+
[keys, values].each_with_index.all? do |li, i|
|
|
16
|
+
li.all? do |val| variants[i % variants.size] === val end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def map_class
|
|
21
|
+
empty? ?
|
|
22
|
+
self.class :
|
|
23
|
+
Generic[
|
|
24
|
+
self.class,
|
|
25
|
+
*([keys, values].map do |li| Either[*li.map(&:class)] end),
|
|
26
|
+
]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
using GenericComparison
|
|
32
|
+
|
|
33
|
+
module Variant
|
|
34
|
+
@class_method = Module.new do
|
|
35
|
+
def [](*args)
|
|
36
|
+
fail ArgumentError, 'no variants given' if args.empty?
|
|
37
|
+
fail ArgumentError, 'variants must be a module or class' if args.any? do |var|
|
|
38
|
+
!(Module === var) && !(Variant === var)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@class_method.singleton_class.class_exec do
|
|
46
|
+
def to_s
|
|
47
|
+
[Variant.to_s, 'InternalLookupFailsafe'].join('::')
|
|
48
|
+
end
|
|
49
|
+
alias inspect to_s
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.included(cls)
|
|
53
|
+
super
|
|
54
|
+
class_method = @class_method
|
|
55
|
+
cls.singleton_class.prepend @class_method
|
|
56
|
+
cls.define_singleton_method :inherited do |subcls|
|
|
57
|
+
super(subcls)
|
|
58
|
+
subcls.singleton_class.prepend class_method
|
|
59
|
+
end if Class === cls
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
freeze
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @!api private
|
|
66
|
+
# defines a generic typing
|
|
67
|
+
class Generic
|
|
68
|
+
include Variant
|
|
69
|
+
include MaimaiNet::ModuleExt::MethodCache
|
|
70
|
+
|
|
71
|
+
def initialize(cls, variants)
|
|
72
|
+
@class = cls
|
|
73
|
+
@variants = variants.freeze
|
|
74
|
+
freeze
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
cache_method :hash do
|
|
78
|
+
[@class, *@variants].inject(0) do |hsh, type|
|
|
79
|
+
((hsh >> 11) | type.hash) % (1 << (0.size << 3))
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ===(obj)
|
|
84
|
+
class_match = @class === obj
|
|
85
|
+
fail NotImplementedError, "#{obj.class} does not support generic" unless obj.respond_to?(:generic_of?)
|
|
86
|
+
internal_match = obj.generic_of?(variants)
|
|
87
|
+
|
|
88
|
+
class_match && internal_match
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_s
|
|
92
|
+
"%s[%s]" % [
|
|
93
|
+
to_class.name,
|
|
94
|
+
variants.join(', '),
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
alias inspect to_s
|
|
98
|
+
|
|
99
|
+
def to_class
|
|
100
|
+
@class
|
|
101
|
+
end
|
|
102
|
+
def variants
|
|
103
|
+
@variants
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class << self
|
|
107
|
+
private :new
|
|
108
|
+
|
|
109
|
+
# defines a generic class statement
|
|
110
|
+
# @return [Generic]
|
|
111
|
+
def [](cls, *variants)
|
|
112
|
+
@_list ||= {}
|
|
113
|
+
gen = @_list.fetch(cls, []).find do |gen_| gen_.variants.eql?(variants) end
|
|
114
|
+
return gen unless gen.nil?
|
|
115
|
+
|
|
116
|
+
gen = new(cls, variants)
|
|
117
|
+
(@_list[cls] ||= []) << gen
|
|
118
|
+
gen
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class Either
|
|
124
|
+
include Variant
|
|
125
|
+
include MaimaiNet::ModuleExt::MethodCache
|
|
126
|
+
|
|
127
|
+
def initialize(variants)
|
|
128
|
+
@variants = variants.freeze
|
|
129
|
+
freeze
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
cache_method :hash do
|
|
133
|
+
[*@variants].inject(0) do |hsh, type|
|
|
134
|
+
((hsh >> 11) | type.hash) % (1 << (0.size << 3))
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def ===(obj)
|
|
139
|
+
variants.any? do |variant| variant === obj end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def to_s
|
|
143
|
+
prefix = nil
|
|
144
|
+
prefix = 'Optional' if variants.include? NilClass
|
|
145
|
+
|
|
146
|
+
"%s(%s)" % [
|
|
147
|
+
prefix,
|
|
148
|
+
clean_variants.join('|'),
|
|
149
|
+
]
|
|
150
|
+
end
|
|
151
|
+
alias inspect to_s
|
|
152
|
+
|
|
153
|
+
def variants
|
|
154
|
+
@variants
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
cache_method :clean_variants do
|
|
158
|
+
variants.reject do |variant| NilClass.eql? variant end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class << self
|
|
162
|
+
private :new
|
|
163
|
+
|
|
164
|
+
# defines an either class statement
|
|
165
|
+
# @return [Either]
|
|
166
|
+
def [](*variants)
|
|
167
|
+
variants.uniq!
|
|
168
|
+
return variants.first if variants.size == 1
|
|
169
|
+
|
|
170
|
+
@_list ||= {}
|
|
171
|
+
sorted_hash = variants.sort_by(&:object_id).hash
|
|
172
|
+
gen = @_list.fetch(sorted_hash, nil)
|
|
173
|
+
return gen unless gen.nil?
|
|
174
|
+
|
|
175
|
+
gen = new(variants)
|
|
176
|
+
@_list[sorted_hash] ||= gen
|
|
177
|
+
gen
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
module Optional
|
|
183
|
+
include Variant
|
|
184
|
+
|
|
185
|
+
class << self
|
|
186
|
+
def [](*variants)
|
|
187
|
+
variants << NilClass
|
|
188
|
+
variants.uniq!
|
|
189
|
+
fail ArgumentError, "invoking #{self.name}[#{variants.join(', ')}] is not allowed" if variants.size == 1
|
|
190
|
+
|
|
191
|
+
Either[*variants]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def append_features(cls); end
|
|
195
|
+
def prepend_features(cls); end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
Boolean = Either[TrueClass, FalseClass]
|
|
200
|
+
|
|
201
|
+
private_constant :GenericComparison
|
|
202
|
+
end
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
module MaimaiNet
|
|
2
|
+
# data model used for parsed data from MaimaiNet::Page
|
|
3
|
+
module Model
|
|
4
|
+
require 'maimai_net/model-typing'
|
|
5
|
+
|
|
6
|
+
module Base
|
|
7
|
+
class Struct < ::Struct
|
|
8
|
+
using GenericComparison
|
|
9
|
+
# @param kwargs [Hash] options are strong-typed based on class definition
|
|
10
|
+
def initialize(**kwargs)
|
|
11
|
+
props = self.class.instance_variable_get(:@_properties)
|
|
12
|
+
keys = props.keys
|
|
13
|
+
optional_keys = props.select do |k, pr|
|
|
14
|
+
Either === pr[:class] &&
|
|
15
|
+
pr[:class].variants.include?(NilClass)
|
|
16
|
+
end.keys
|
|
17
|
+
|
|
18
|
+
missing_keys = keys - (kwargs.keys | optional_keys)
|
|
19
|
+
fail KeyError, "#{missing_keys.join(', ')} is not defined for #{self.class}" unless missing_keys.empty?
|
|
20
|
+
kwargs.each do |key, value|
|
|
21
|
+
fail KeyError, "#{key} is not defined as struct member" unless keys.include?(key)
|
|
22
|
+
class_str = value.respond_to?(:map_class) ? value.map_class : value.class
|
|
23
|
+
fail TypeError, "#{key} type mismatch, given #{class_str}, expected #{props[key][:class]}" unless props[key][:class] === value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
args = kwargs.values_at(*keys)
|
|
27
|
+
super(*args)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
class << Struct
|
|
31
|
+
# creates a strong-typed struct data
|
|
32
|
+
# @param opts [Hash{Symbol => Module}]
|
|
33
|
+
# list of struct members along with respective type definition
|
|
34
|
+
# @return [Struct] new subclass with defined types
|
|
35
|
+
def new(**opts, &block)
|
|
36
|
+
super(*opts.keys) do
|
|
37
|
+
@_properties = {}
|
|
38
|
+
opts.each do |key, typedata|
|
|
39
|
+
@_properties[key] = case typedata
|
|
40
|
+
when Array
|
|
41
|
+
{class: Generic[*typedata]}
|
|
42
|
+
when Module, Variant
|
|
43
|
+
{class: typedata}
|
|
44
|
+
else
|
|
45
|
+
fail TypeError, "invalid type definition"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class_exec(&block) if block_given?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
SongCount = Base::Struct.new(achieved: Integer, total: Integer) do
|
|
56
|
+
def to_s
|
|
57
|
+
"#{achieved}/#{total}"
|
|
58
|
+
end
|
|
59
|
+
alias inspect to_s
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module PlayerCommon
|
|
63
|
+
Info = Base::Struct.new(
|
|
64
|
+
name: String,
|
|
65
|
+
title: String,
|
|
66
|
+
grade: String,
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module PlayerData
|
|
71
|
+
Decoration = Base::Struct.new(
|
|
72
|
+
icon: URI::Generic,
|
|
73
|
+
)
|
|
74
|
+
ExtendedInfo = Base::Struct.new(
|
|
75
|
+
rating: Integer,
|
|
76
|
+
class_grade: String,
|
|
77
|
+
partner_star_total: Integer,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
DifficultyStatistic = Base::Struct.new(
|
|
81
|
+
clears: SongCount,
|
|
82
|
+
ranks: Generic[Hash, Symbol, SongCount],
|
|
83
|
+
dx_ranks: Generic[Hash, Integer, SongCount],
|
|
84
|
+
flags: Generic[Hash, Symbol, SongCount],
|
|
85
|
+
sync_flags: Generic[Hash, Symbol, SongCount],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
InfoPlate = Base::Struct.new(
|
|
89
|
+
info: PlayerCommon::Info,
|
|
90
|
+
decoration: Decoration,
|
|
91
|
+
extended: ExtendedInfo,
|
|
92
|
+
)
|
|
93
|
+
Lite = Base::Struct.new(
|
|
94
|
+
name: String,
|
|
95
|
+
rating: Integer,
|
|
96
|
+
)
|
|
97
|
+
Data = Base::Struct.new(
|
|
98
|
+
plate: InfoPlate,
|
|
99
|
+
statistics: Generic[Hash, Symbol, DifficultyStatistic],
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
WebID = Base::Struct.new(
|
|
104
|
+
item_hash: String,
|
|
105
|
+
item_key: String,
|
|
106
|
+
) do
|
|
107
|
+
def self.parse(s)
|
|
108
|
+
hash, key = s[0, 128].b, s[128, s.size - 128].unpack1('m*').unpack1('H*')
|
|
109
|
+
new(item_hash: -hash, item_key: -key)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def to_str
|
|
113
|
+
self.item_hash + [[self.item_key].pack('H*')].pack('m0')
|
|
114
|
+
end
|
|
115
|
+
alias to_s to_str
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class WebID
|
|
119
|
+
DUMMY_ID = -('0' * 128 + 'A' * 44)
|
|
120
|
+
DUMMY = parse(DUMMY_ID)
|
|
121
|
+
def DUMMY.inspect
|
|
122
|
+
'#<%s %s>' % [
|
|
123
|
+
self.class,
|
|
124
|
+
-'dummy',
|
|
125
|
+
]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
module Chart
|
|
130
|
+
info_base = {
|
|
131
|
+
title: String,
|
|
132
|
+
type: String,
|
|
133
|
+
difficulty: Integer,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
InfoLite = Base::Struct.new(**info_base) do
|
|
137
|
+
def to_info(level_text: '?')
|
|
138
|
+
Info.new(
|
|
139
|
+
web_id: WebID::DUMMY,
|
|
140
|
+
title: title,
|
|
141
|
+
type: type,
|
|
142
|
+
difficulty: difficulty,
|
|
143
|
+
level_text: level_text,
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
Info = Base::Struct.new(
|
|
149
|
+
web_id: WebID,
|
|
150
|
+
**info_base,
|
|
151
|
+
level_text: String,
|
|
152
|
+
) do
|
|
153
|
+
def to_lite
|
|
154
|
+
InfoLite.new(title: title, type: type, difficulty: difficulty)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
Song = Base::Struct.new(
|
|
159
|
+
title: String,
|
|
160
|
+
artist: String,
|
|
161
|
+
genre: String,
|
|
162
|
+
jacket: URI::Generic,
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
SongEntry = Base::Struct.new(
|
|
167
|
+
web_id: WebID,
|
|
168
|
+
title: String,
|
|
169
|
+
genre: String,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
SongFavoriteInfo = Base::Struct.new(
|
|
173
|
+
song: SongEntry,
|
|
174
|
+
flag: Boolean,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
PhotoUpload = Base::Struct.new(
|
|
178
|
+
info: Chart::InfoLite,
|
|
179
|
+
url: URI::Generic,
|
|
180
|
+
location: String,
|
|
181
|
+
time: Time,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
module Result
|
|
185
|
+
Progress = Base::Struct.new(
|
|
186
|
+
value: Integer,
|
|
187
|
+
max: Integer,
|
|
188
|
+
) do
|
|
189
|
+
def to_s; "#{value}/#{max}"; end
|
|
190
|
+
alias to_i value
|
|
191
|
+
alias inspect to_s
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
RivalInfo = Base::Struct.new(
|
|
195
|
+
player: PlayerData::Lite,
|
|
196
|
+
score: Float,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
TourMember = Base::Struct.new(
|
|
200
|
+
icon: URI::Generic,
|
|
201
|
+
grade: Integer,
|
|
202
|
+
level: Integer,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
Judgment = Base::Struct.new(
|
|
206
|
+
just: Integer,
|
|
207
|
+
perfect: Integer,
|
|
208
|
+
great: Integer,
|
|
209
|
+
good: Integer,
|
|
210
|
+
miss: Integer,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
Offset = Base::Struct.new(
|
|
214
|
+
early: Integer,
|
|
215
|
+
late: Integer,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
Challenge = Base::Struct.new(
|
|
219
|
+
type: Symbol,
|
|
220
|
+
lives: Progress,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
ScoreLite = Base::Struct.new(
|
|
224
|
+
score: Float,
|
|
225
|
+
deluxe_score: Progress,
|
|
226
|
+
grade: Symbol,
|
|
227
|
+
flags: Generic[Array, Symbol],
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
Score = Base::Struct.new(
|
|
231
|
+
score: Float,
|
|
232
|
+
deluxe_score: Progress,
|
|
233
|
+
combo: Progress,
|
|
234
|
+
sync_score: Progress,
|
|
235
|
+
grade: Symbol,
|
|
236
|
+
flags: Generic[Array, Symbol],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
ReferenceWebID = Base::Struct.new(
|
|
240
|
+
order: Integer,
|
|
241
|
+
time: Time,
|
|
242
|
+
) do
|
|
243
|
+
def self.parse(s)
|
|
244
|
+
order, time = s.split(',').first(2).map(&:to_i)
|
|
245
|
+
new(order: order, time: Time.at(time).localtime(32400).freeze)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def to_str
|
|
249
|
+
[order, time.to_i].join(',')
|
|
250
|
+
end
|
|
251
|
+
alias to_s to_str
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
Track = Base::Struct.new(
|
|
255
|
+
info: Chart::Info,
|
|
256
|
+
score: Either[Score, ScoreLite],
|
|
257
|
+
order: Integer,
|
|
258
|
+
time: Time,
|
|
259
|
+
challenge: Optional[Challenge],
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
TrackReference = Base::Struct.new(
|
|
263
|
+
track: Track,
|
|
264
|
+
ref_web_id: ReferenceWebID,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
Data = Base::Struct.new(
|
|
268
|
+
track: Track,
|
|
269
|
+
breakdown: Generic[Hash, Symbol, Judgment],
|
|
270
|
+
timing: Offset,
|
|
271
|
+
members: Generic[Array, TourMember],
|
|
272
|
+
rival: Optional[RivalInfo],
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
module Record
|
|
277
|
+
History = Base::Struct.new(
|
|
278
|
+
play_count: Integer,
|
|
279
|
+
last_played: Time,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
ScoreOnly = Base::Struct.new(
|
|
283
|
+
score: Float,
|
|
284
|
+
grade: Symbol,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
Score = Base::Struct.new(
|
|
288
|
+
web_id: WebID,
|
|
289
|
+
score: Float,
|
|
290
|
+
deluxe_score: Result::Progress,
|
|
291
|
+
grade: Symbol,
|
|
292
|
+
deluxe_grade: Integer,
|
|
293
|
+
flags: Generic[Array, Symbol],
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
ChartRecord = Base::Struct.new(
|
|
297
|
+
info: Chart::Info,
|
|
298
|
+
record: Optional[Score],
|
|
299
|
+
history: Optional[History],
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
InfoCategory = Base::Struct.new(
|
|
303
|
+
info: Chart::Info,
|
|
304
|
+
score: Optional[Result::ScoreLite],
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
InfoBest = Base::Struct.new(
|
|
308
|
+
info: Chart::Info,
|
|
309
|
+
play_count: Integer,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
InfoRating = Base::Struct.new(
|
|
313
|
+
info: Chart::Info,
|
|
314
|
+
score: ScoreOnly,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
Data = Base::Struct.new(
|
|
318
|
+
info: Chart::Song,
|
|
319
|
+
charts: Generic[Hash, Symbol, ChartRecord],
|
|
320
|
+
)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
module FinaleArchive
|
|
324
|
+
Decoration = Base::Struct.new(
|
|
325
|
+
icon: URI::Generic,
|
|
326
|
+
player_frame: URI::Generic,
|
|
327
|
+
nameplate: URI::Generic,
|
|
328
|
+
)
|
|
329
|
+
Currency = Base::Struct.new(
|
|
330
|
+
amount: Integer, piece: Integer, parts: Integer,
|
|
331
|
+
)
|
|
332
|
+
ExtendedInfo = Base::Struct.new(
|
|
333
|
+
rating: Float, rating_highest: Float,
|
|
334
|
+
region_count: Integer,
|
|
335
|
+
currency: Currency,
|
|
336
|
+
partner_level_total: Integer,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
DifficultyStatistic = Base::Struct.new(
|
|
340
|
+
total_score: Integer,
|
|
341
|
+
clears: SongCount,
|
|
342
|
+
ranks: Generic[Hash, Symbol, SongCount],
|
|
343
|
+
flags: Generic[Hash, Symbol, SongCount],
|
|
344
|
+
sync_flags: Generic[Hash, Symbol, SongCount],
|
|
345
|
+
multi_flags: Generic[Hash, Symbol, SongCount],
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
Data = Base::Struct.new(
|
|
349
|
+
info: PlayerCommon::Info,
|
|
350
|
+
decoration: Decoration,
|
|
351
|
+
extended: ExtendedInfo,
|
|
352
|
+
|
|
353
|
+
statistics: Generic[Hash, Symbol, DifficultyStatistic],
|
|
354
|
+
)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
private_constant :Base
|
|
358
|
+
end
|
|
359
|
+
end
|