maimai_net 0.0.1 → 0.0.2
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 +4 -4
- data/lib/maimai_net/client.rb +6 -2
- data/lib/maimai_net/core_ext.rb +2 -1
- data/lib/maimai_net/model.rb +72 -63
- data/lib/maimai_net/page-track_result_helper.rb +16 -5
- data/lib/maimai_net/page.rb +46 -7
- data/lib/maimai_net/refines.rb +4 -1
- data/lib/maimai_net/version.rb +1 -1
- data/lib/maimai_net.rb +2 -0
- metadata +3 -11
- data/.github/workflows/gem.yml +0 -53
- data/.gitignore +0 -165
- data/.rspec +0 -5
- data/Gemfile +0 -4
- data/Rakefile +0 -6
- data/bin/console +0 -16
- data/bin/setup +0 -6
- data/maimai-net.gemspec +0 -44
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1536775b0efe6486ed28430da4fdeb186acf36aa70a18f3b3e67ad14b8f6db79
|
|
4
|
+
data.tar.gz: f6dae985de61be79aa0a249bb3b4bddfd174a2398ae23a9441528d0a4a833afe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7d0544158ff4426a591cd89b12d5a8436c654732c07ea2b013691d15c04277639e1db624ea5c1a059a103c039e402d9a30ad2a0532d65fadb88a4a1a7a01f01
|
|
7
|
+
data.tar.gz: d1da766e1d1bf3095b811fd2f35ccd51e31c4cc700b67082043eb4f9528a49e173f7184539d6ef4763ba6b2864a7fcadeafafbdfb7a6efe0f662b46507c7488b
|
data/lib/maimai_net/client.rb
CHANGED
|
@@ -61,12 +61,16 @@ module MaimaiNet
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
class Connection
|
|
64
|
+
extend Forwardable
|
|
65
|
+
|
|
64
66
|
# @param client [Base] client data
|
|
65
67
|
def initialize(client)
|
|
66
68
|
@client = client
|
|
67
69
|
@conn = nil
|
|
68
70
|
end
|
|
69
71
|
|
|
72
|
+
def_delegator :@client, :cookies
|
|
73
|
+
|
|
70
74
|
# automatically private hook methods
|
|
71
75
|
# @return [void]
|
|
72
76
|
def self.method_added(meth)
|
|
@@ -314,7 +318,7 @@ module MaimaiNet
|
|
|
314
318
|
# @param opts [Hash{Symbol => Object}]
|
|
315
319
|
# @option response_page [Class<Page::Base>] response parser class
|
|
316
320
|
# @option response [#call] response method, takes one argument, response body raw.
|
|
317
|
-
# @return [Model::Base
|
|
321
|
+
# @return [Model::Base] returns page data based from provided response_page field
|
|
318
322
|
# @return [void]
|
|
319
323
|
def send_request(method, url, data, **opts)
|
|
320
324
|
fail NotImplementedError, 'abstract method called' if Connection == method(__method__).owner
|
|
@@ -358,7 +362,7 @@ module MaimaiNet
|
|
|
358
362
|
# @!api private
|
|
359
363
|
# @param url [URI] response url
|
|
360
364
|
# @param body [String] response body
|
|
361
|
-
# @return [Model::Base
|
|
365
|
+
# @return [Model::Base, Array<Model::Base>] response page handled result
|
|
362
366
|
# @return [nil] no response page defined to handle the response
|
|
363
367
|
def process_response(url:, body:, request_options:)
|
|
364
368
|
info = @client.class.region_info
|
data/lib/maimai_net/core_ext.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
module MaimaiNet
|
|
2
2
|
module CoreExt
|
|
3
|
-
module
|
|
3
|
+
module AutoConstantInclusion
|
|
4
4
|
MaimaiNet.constants.each do |k|
|
|
5
5
|
cls = MaimaiNet.const_get(k)
|
|
6
6
|
next unless Class === cls && cls < MaimaiNet::Constant
|
|
7
7
|
|
|
8
8
|
define_method k do |key| cls.new(key) end
|
|
9
|
+
private k
|
|
9
10
|
end
|
|
10
11
|
end
|
|
11
12
|
end
|
data/lib/maimai_net/model.rb
CHANGED
|
@@ -3,31 +3,30 @@ module MaimaiNet
|
|
|
3
3
|
module Model
|
|
4
4
|
require 'maimai_net/model-typing'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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)
|
|
6
|
+
class Base < ::Struct
|
|
7
|
+
using GenericComparison
|
|
8
|
+
# @param kwargs [Hash] options are strong-typed based on class definition
|
|
9
|
+
def initialize(**kwargs)
|
|
10
|
+
props = self.class.instance_variable_get(:@_properties)
|
|
11
|
+
keys = props.keys
|
|
12
|
+
optional_keys = props.select do |k, pr|
|
|
13
|
+
Either === pr[:class] &&
|
|
14
|
+
pr[:class].variants.include?(NilClass)
|
|
15
|
+
end.keys
|
|
16
|
+
|
|
17
|
+
missing_keys = keys - (kwargs.keys | optional_keys)
|
|
18
|
+
fail KeyError, "#{missing_keys.join(', ')} is not defined for #{self.class}" unless missing_keys.empty?
|
|
19
|
+
kwargs.each do |key, value|
|
|
20
|
+
fail KeyError, "#{key} is not defined as struct member" unless keys.include?(key)
|
|
21
|
+
class_str = value.respond_to?(:map_class) ? value.map_class : value.class
|
|
22
|
+
fail TypeError, "#{key} type mismatch, given #{class_str}, expected #{props[key][:class]}" unless props[key][:class] === value
|
|
28
23
|
end
|
|
24
|
+
|
|
25
|
+
args = kwargs.values_at(*keys)
|
|
26
|
+
super(*args)
|
|
29
27
|
end
|
|
30
|
-
|
|
28
|
+
|
|
29
|
+
class << self
|
|
31
30
|
# creates a strong-typed struct data
|
|
32
31
|
# @param opts [Hash{Symbol => Module}]
|
|
33
32
|
# list of struct members along with respective type definition
|
|
@@ -52,7 +51,7 @@ module MaimaiNet
|
|
|
52
51
|
end
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
SongCount = Base
|
|
54
|
+
SongCount = Base.new(achieved: Integer, total: Integer) do
|
|
56
55
|
def to_s
|
|
57
56
|
"#{achieved}/#{total}"
|
|
58
57
|
end
|
|
@@ -60,7 +59,7 @@ module MaimaiNet
|
|
|
60
59
|
end
|
|
61
60
|
|
|
62
61
|
module PlayerCommon
|
|
63
|
-
Info = Base
|
|
62
|
+
Info = Base.new(
|
|
64
63
|
name: String,
|
|
65
64
|
title: String,
|
|
66
65
|
grade: String,
|
|
@@ -68,16 +67,16 @@ module MaimaiNet
|
|
|
68
67
|
end
|
|
69
68
|
|
|
70
69
|
module PlayerData
|
|
71
|
-
Decoration = Base
|
|
70
|
+
Decoration = Base.new(
|
|
72
71
|
icon: URI::Generic,
|
|
73
72
|
)
|
|
74
|
-
ExtendedInfo = Base
|
|
73
|
+
ExtendedInfo = Base.new(
|
|
75
74
|
rating: Integer,
|
|
76
75
|
class_grade: String,
|
|
77
76
|
partner_star_total: Integer,
|
|
78
77
|
)
|
|
79
78
|
|
|
80
|
-
DifficultyStatistic = Base
|
|
79
|
+
DifficultyStatistic = Base.new(
|
|
81
80
|
clears: SongCount,
|
|
82
81
|
ranks: Generic[Hash, Symbol, SongCount],
|
|
83
82
|
dx_ranks: Generic[Hash, Integer, SongCount],
|
|
@@ -85,22 +84,22 @@ module MaimaiNet
|
|
|
85
84
|
sync_flags: Generic[Hash, Symbol, SongCount],
|
|
86
85
|
)
|
|
87
86
|
|
|
88
|
-
InfoPlate = Base
|
|
87
|
+
InfoPlate = Base.new(
|
|
89
88
|
info: PlayerCommon::Info,
|
|
90
89
|
decoration: Decoration,
|
|
91
90
|
extended: ExtendedInfo,
|
|
92
91
|
)
|
|
93
|
-
Lite = Base
|
|
92
|
+
Lite = Base.new(
|
|
94
93
|
name: String,
|
|
95
94
|
rating: Integer,
|
|
96
95
|
)
|
|
97
|
-
Data = Base
|
|
96
|
+
Data = Base.new(
|
|
98
97
|
plate: InfoPlate,
|
|
99
98
|
statistics: Generic[Hash, Symbol, DifficultyStatistic],
|
|
100
99
|
)
|
|
101
100
|
end
|
|
102
101
|
|
|
103
|
-
WebID = Base
|
|
102
|
+
WebID = Base.new(
|
|
104
103
|
item_hash: String,
|
|
105
104
|
item_key: String,
|
|
106
105
|
) do
|
|
@@ -131,9 +130,11 @@ module MaimaiNet
|
|
|
131
130
|
title: String,
|
|
132
131
|
type: String,
|
|
133
132
|
difficulty: Integer,
|
|
133
|
+
variant: Optional[String],
|
|
134
|
+
flags: Optional[Integer],
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
InfoLite = Base
|
|
137
|
+
InfoLite = Base.new(**info_base) do
|
|
137
138
|
def to_info(level_text: '?')
|
|
138
139
|
Info.new(
|
|
139
140
|
web_id: WebID::DUMMY,
|
|
@@ -145,7 +146,7 @@ module MaimaiNet
|
|
|
145
146
|
end
|
|
146
147
|
end
|
|
147
148
|
|
|
148
|
-
Info = Base
|
|
149
|
+
Info = Base.new(
|
|
149
150
|
web_id: WebID,
|
|
150
151
|
**info_base,
|
|
151
152
|
level_text: String,
|
|
@@ -155,7 +156,7 @@ module MaimaiNet
|
|
|
155
156
|
end
|
|
156
157
|
end
|
|
157
158
|
|
|
158
|
-
Song = Base
|
|
159
|
+
Song = Base.new(
|
|
159
160
|
title: String,
|
|
160
161
|
artist: String,
|
|
161
162
|
genre: String,
|
|
@@ -163,18 +164,18 @@ module MaimaiNet
|
|
|
163
164
|
)
|
|
164
165
|
end
|
|
165
166
|
|
|
166
|
-
SongEntry = Base
|
|
167
|
+
SongEntry = Base.new(
|
|
167
168
|
web_id: WebID,
|
|
168
169
|
title: String,
|
|
169
170
|
genre: String,
|
|
170
171
|
)
|
|
171
172
|
|
|
172
|
-
SongFavoriteInfo = Base
|
|
173
|
+
SongFavoriteInfo = Base.new(
|
|
173
174
|
song: SongEntry,
|
|
174
175
|
flag: Boolean,
|
|
175
176
|
)
|
|
176
177
|
|
|
177
|
-
PhotoUpload = Base
|
|
178
|
+
PhotoUpload = Base.new(
|
|
178
179
|
info: Chart::InfoLite,
|
|
179
180
|
url: URI::Generic,
|
|
180
181
|
location: String,
|
|
@@ -182,7 +183,7 @@ module MaimaiNet
|
|
|
182
183
|
)
|
|
183
184
|
|
|
184
185
|
module Result
|
|
185
|
-
Progress = Base
|
|
186
|
+
Progress = Base.new(
|
|
186
187
|
value: Integer,
|
|
187
188
|
max: Integer,
|
|
188
189
|
) do
|
|
@@ -191,18 +192,23 @@ module MaimaiNet
|
|
|
191
192
|
alias inspect to_s
|
|
192
193
|
end
|
|
193
194
|
|
|
194
|
-
RivalInfo = Base
|
|
195
|
+
RivalInfo = Base.new(
|
|
195
196
|
player: PlayerData::Lite,
|
|
196
197
|
score: Float,
|
|
197
198
|
)
|
|
198
199
|
|
|
199
|
-
|
|
200
|
+
PlayerInfo = Base.new(
|
|
201
|
+
player_name: String,
|
|
202
|
+
difficulty: Integer,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
TourMember = Base.new(
|
|
200
206
|
icon: URI::Generic,
|
|
201
207
|
grade: Integer,
|
|
202
208
|
level: Integer,
|
|
203
209
|
)
|
|
204
210
|
|
|
205
|
-
Judgment = Base
|
|
211
|
+
Judgment = Base.new(
|
|
206
212
|
just: Integer,
|
|
207
213
|
perfect: Integer,
|
|
208
214
|
great: Integer,
|
|
@@ -210,33 +216,35 @@ module MaimaiNet
|
|
|
210
216
|
miss: Integer,
|
|
211
217
|
)
|
|
212
218
|
|
|
213
|
-
Offset = Base
|
|
219
|
+
Offset = Base.new(
|
|
214
220
|
early: Integer,
|
|
215
221
|
late: Integer,
|
|
216
222
|
)
|
|
217
223
|
|
|
218
|
-
Challenge = Base
|
|
224
|
+
Challenge = Base.new(
|
|
219
225
|
type: Symbol,
|
|
220
226
|
lives: Progress,
|
|
221
227
|
)
|
|
222
228
|
|
|
223
|
-
ScoreLite = Base
|
|
229
|
+
ScoreLite = Base.new(
|
|
224
230
|
score: Float,
|
|
225
231
|
deluxe_score: Progress,
|
|
226
232
|
grade: Symbol,
|
|
227
233
|
flags: Generic[Array, Symbol],
|
|
234
|
+
position: Optional[Integer],
|
|
228
235
|
)
|
|
229
236
|
|
|
230
|
-
Score = Base
|
|
237
|
+
Score = Base.new(
|
|
231
238
|
score: Float,
|
|
232
239
|
deluxe_score: Progress,
|
|
233
240
|
combo: Progress,
|
|
234
241
|
sync_score: Progress,
|
|
235
242
|
grade: Symbol,
|
|
236
243
|
flags: Generic[Array, Symbol],
|
|
244
|
+
position: Optional[Integer],
|
|
237
245
|
)
|
|
238
246
|
|
|
239
|
-
ReferenceWebID = Base
|
|
247
|
+
ReferenceWebID = Base.new(
|
|
240
248
|
order: Integer,
|
|
241
249
|
time: Time,
|
|
242
250
|
) do
|
|
@@ -251,7 +259,7 @@ module MaimaiNet
|
|
|
251
259
|
alias to_s to_str
|
|
252
260
|
end
|
|
253
261
|
|
|
254
|
-
Track = Base
|
|
262
|
+
Track = Base.new(
|
|
255
263
|
info: Chart::Info,
|
|
256
264
|
score: Either[Score, ScoreLite],
|
|
257
265
|
order: Integer,
|
|
@@ -259,32 +267,33 @@ module MaimaiNet
|
|
|
259
267
|
challenge: Optional[Challenge],
|
|
260
268
|
)
|
|
261
269
|
|
|
262
|
-
TrackReference = Base
|
|
270
|
+
TrackReference = Base.new(
|
|
263
271
|
track: Track,
|
|
264
272
|
ref_web_id: ReferenceWebID,
|
|
265
273
|
)
|
|
266
274
|
|
|
267
|
-
Data = Base
|
|
275
|
+
Data = Base.new(
|
|
268
276
|
track: Track,
|
|
269
277
|
breakdown: Generic[Hash, Symbol, Judgment],
|
|
270
278
|
timing: Offset,
|
|
271
279
|
members: Generic[Array, TourMember],
|
|
272
280
|
rival: Optional[RivalInfo],
|
|
281
|
+
players: Generic[Array, PlayerInfo],
|
|
273
282
|
)
|
|
274
283
|
end
|
|
275
284
|
|
|
276
285
|
module Record
|
|
277
|
-
History = Base
|
|
286
|
+
History = Base.new(
|
|
278
287
|
play_count: Integer,
|
|
279
288
|
last_played: Time,
|
|
280
289
|
)
|
|
281
290
|
|
|
282
|
-
ScoreOnly = Base
|
|
291
|
+
ScoreOnly = Base.new(
|
|
283
292
|
score: Float,
|
|
284
293
|
grade: Symbol,
|
|
285
294
|
)
|
|
286
295
|
|
|
287
|
-
Score = Base
|
|
296
|
+
Score = Base.new(
|
|
288
297
|
web_id: WebID,
|
|
289
298
|
score: Float,
|
|
290
299
|
deluxe_score: Result::Progress,
|
|
@@ -293,50 +302,50 @@ module MaimaiNet
|
|
|
293
302
|
flags: Generic[Array, Symbol],
|
|
294
303
|
)
|
|
295
304
|
|
|
296
|
-
ChartRecord = Base
|
|
305
|
+
ChartRecord = Base.new(
|
|
297
306
|
info: Chart::Info,
|
|
298
307
|
record: Optional[Score],
|
|
299
308
|
history: Optional[History],
|
|
300
309
|
)
|
|
301
310
|
|
|
302
|
-
InfoCategory = Base
|
|
311
|
+
InfoCategory = Base.new(
|
|
303
312
|
info: Chart::Info,
|
|
304
313
|
score: Optional[Result::ScoreLite],
|
|
305
314
|
)
|
|
306
315
|
|
|
307
|
-
InfoBest = Base
|
|
316
|
+
InfoBest = Base.new(
|
|
308
317
|
info: Chart::Info,
|
|
309
318
|
play_count: Integer,
|
|
310
319
|
)
|
|
311
320
|
|
|
312
|
-
InfoRating = Base
|
|
321
|
+
InfoRating = Base.new(
|
|
313
322
|
info: Chart::Info,
|
|
314
323
|
score: ScoreOnly,
|
|
315
324
|
)
|
|
316
325
|
|
|
317
|
-
Data = Base
|
|
326
|
+
Data = Base.new(
|
|
318
327
|
info: Chart::Song,
|
|
319
328
|
charts: Generic[Hash, Symbol, ChartRecord],
|
|
320
329
|
)
|
|
321
330
|
end
|
|
322
331
|
|
|
323
332
|
module FinaleArchive
|
|
324
|
-
Decoration = Base
|
|
333
|
+
Decoration = Base.new(
|
|
325
334
|
icon: URI::Generic,
|
|
326
335
|
player_frame: URI::Generic,
|
|
327
336
|
nameplate: URI::Generic,
|
|
328
337
|
)
|
|
329
|
-
Currency = Base
|
|
338
|
+
Currency = Base.new(
|
|
330
339
|
amount: Integer, piece: Integer, parts: Integer,
|
|
331
340
|
)
|
|
332
|
-
ExtendedInfo = Base
|
|
341
|
+
ExtendedInfo = Base.new(
|
|
333
342
|
rating: Float, rating_highest: Float,
|
|
334
343
|
region_count: Integer,
|
|
335
344
|
currency: Currency,
|
|
336
345
|
partner_level_total: Integer,
|
|
337
346
|
)
|
|
338
347
|
|
|
339
|
-
DifficultyStatistic = Base
|
|
348
|
+
DifficultyStatistic = Base.new(
|
|
340
349
|
total_score: Integer,
|
|
341
350
|
clears: SongCount,
|
|
342
351
|
ranks: Generic[Hash, Symbol, SongCount],
|
|
@@ -345,7 +354,7 @@ module MaimaiNet
|
|
|
345
354
|
multi_flags: Generic[Hash, Symbol, SongCount],
|
|
346
355
|
)
|
|
347
356
|
|
|
348
|
-
Data = Base
|
|
357
|
+
Data = Base.new(
|
|
349
358
|
info: PlayerCommon::Info,
|
|
350
359
|
decoration: Decoration,
|
|
351
360
|
extended: ExtendedInfo,
|
|
@@ -10,7 +10,12 @@ module MaimaiNet
|
|
|
10
10
|
)
|
|
11
11
|
HelperBlock.send(:new, nil).instance_exec do
|
|
12
12
|
header_block = elm.at_css('.playlog_top_container')
|
|
13
|
-
difficulty =
|
|
13
|
+
difficulty = Difficulty(::Kernel.Pathname(src(header_block.at_css('img.playlog_diff'))).sub_ext('').sub(/.+_/, '').basename)
|
|
14
|
+
utage_variant = header_block.at_css('.playlog_music_kind_icon_utage').yield_self do |elm|
|
|
15
|
+
next if elm.nil?
|
|
16
|
+
|
|
17
|
+
strip(elm)
|
|
18
|
+
end
|
|
14
19
|
|
|
15
20
|
dx_container_classes = MaimaiNet::Difficulty::DELUXE.select do |k, v| v.positive? end
|
|
16
21
|
.keys.map do |k| ".playlog_#{k}_container" end
|
|
@@ -27,9 +32,10 @@ module MaimaiNet
|
|
|
27
32
|
song_name = strip(chart_header_block.children.last)
|
|
28
33
|
chart_level = strip(chart_header_block.at_css('div:nth-of-type(1)'))
|
|
29
34
|
song_jacket = src(result_block.at_css('img.music_img'))
|
|
30
|
-
chart_type =
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
chart_type = result_block.at_css('img.playlog_music_kind_icon').yield_self do |elm|
|
|
36
|
+
next if elm.nil?
|
|
37
|
+
|
|
38
|
+
::Kernel.Pathname(src(elm))&.sub_ext('')&.sub(/.+_/, '')&.basename&.to_s
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
result_score = strip(result_block.at_css('.playlog_achievement_txt')).to_f
|
|
@@ -38,10 +44,13 @@ module MaimaiNet
|
|
|
38
44
|
result_flags = result_block.css('.playlog_result_innerblock > img').map do |elm|
|
|
39
45
|
flag = ::Kernel.Pathname(::Kernel.URI(src(elm)).path).sub_ext('')&.basename.to_s
|
|
40
46
|
case flag
|
|
41
|
-
when *MaimaiNet::AchievementFlag::RESULT.values;
|
|
47
|
+
when *MaimaiNet::AchievementFlag::RESULT.values; AchievementFlag(result_key: flag)
|
|
42
48
|
when /_dummy$/; nil
|
|
43
49
|
end
|
|
44
50
|
end.compact
|
|
51
|
+
result_position = result_block.at_css('.playlog_result_innerblock img.playlog_matching_icon')&.yield_self do |elm|
|
|
52
|
+
/^\d+/.match(::Kernel.Pathname(::Kernel.URI(src(elm)).path).sub_ext('')&.basename.to_s)[0].to_i
|
|
53
|
+
end
|
|
45
54
|
|
|
46
55
|
challenge_info = nil
|
|
47
56
|
result_block.at_css('div:has(> .playlog_life_block)')&.tap do |elm|
|
|
@@ -63,6 +72,7 @@ module MaimaiNet
|
|
|
63
72
|
end.to_h,
|
|
64
73
|
grade: result_grade,
|
|
65
74
|
flags: result_flags.map(&:to_sym),
|
|
75
|
+
position: result_position,
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
score_cls = score_data.key?(:combo) && score_data.key?(:sync_score) ?
|
|
@@ -76,6 +86,7 @@ module MaimaiNet
|
|
|
76
86
|
title: song_name,
|
|
77
87
|
type: (chart_type or -'unknown'),
|
|
78
88
|
difficulty: difficulty.id,
|
|
89
|
+
variant: utage_variant,
|
|
79
90
|
level_text: chart_level,
|
|
80
91
|
),
|
|
81
92
|
score: score_info,
|
data/lib/maimai_net/page.rb
CHANGED
|
@@ -265,6 +265,7 @@ module MaimaiNet
|
|
|
265
265
|
|
|
266
266
|
start_anchor = @root.at_css('img.title')
|
|
267
267
|
@otomodachi_block = start_anchor.at_css('~ div#vsUser > :first-child')
|
|
268
|
+
@multiplayer_block = start_anchor.at_css('~ div#matching')
|
|
268
269
|
@score_block = start_anchor.at_css('~ div:not(#vsUser):nth-of-type(1)')
|
|
269
270
|
@breakdown_block = start_anchor.at_css('~ div:not(#vsUser):nth-of-type(2)')
|
|
270
271
|
end
|
|
@@ -310,6 +311,16 @@ module MaimaiNet
|
|
|
310
311
|
)
|
|
311
312
|
end
|
|
312
313
|
|
|
314
|
+
result_players = @multiplayer_block&.css(':has(img[src*="/diff"])').to_a.map do |elm|
|
|
315
|
+
difficulty = Difficulty(Pathname(src(elm.at_css('> img:nth-of-type(1)'))).sub_ext('').sub(/.+_/, '').basename)
|
|
316
|
+
name = strip(elm.at_css('> div.basic_block:nth-of-type(1)'))
|
|
317
|
+
|
|
318
|
+
Model::Result::PlayerInfo.new(
|
|
319
|
+
player_name: name,
|
|
320
|
+
difficulty: difficulty.id,
|
|
321
|
+
)
|
|
322
|
+
end
|
|
323
|
+
|
|
313
324
|
chart_web_id = Model::WebID.parse(@root.at_css('form[action$="/record/musicDetail/"] input[name=idx]')['value'])
|
|
314
325
|
|
|
315
326
|
Model::Result::Data.new(
|
|
@@ -323,6 +334,7 @@ module MaimaiNet
|
|
|
323
334
|
timing: Model::Result::Offset.new(**Model::Result::Offset.members.zip(result_offset_breakdown).to_h),
|
|
324
335
|
members: result_tour_members,
|
|
325
336
|
rival: result_otomodachi_rival,
|
|
337
|
+
players: result_players,
|
|
326
338
|
)
|
|
327
339
|
end
|
|
328
340
|
end
|
|
@@ -350,24 +362,51 @@ module MaimaiNet
|
|
|
350
362
|
if track_group_blocks.empty? then
|
|
351
363
|
track_segmented_blocks[current_group] = @root.css('div:has(> form[action$="/musicDetail/"] input[name=idx])')
|
|
352
364
|
else
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
365
|
+
%w(.see_through_block .scroll_point).each do |anchor_class|
|
|
366
|
+
anchor_elm = @root.at_css(anchor_class)
|
|
367
|
+
next if anchor_elm.nil?
|
|
368
|
+
|
|
369
|
+
anchor_elm.css('~ div').each do |elm|
|
|
370
|
+
if elm.classes.include? 'screw_block' then
|
|
371
|
+
current_group = elm.content
|
|
372
|
+
next
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
track_segmented_blocks[current_group] ||= Nokogiri::XML::NodeSet.new(@document)
|
|
376
|
+
track_segmented_blocks[current_group] << elm
|
|
357
377
|
end
|
|
358
378
|
|
|
359
|
-
|
|
360
|
-
track_segmented_blocks[current_group] << elm
|
|
379
|
+
break
|
|
361
380
|
end
|
|
362
381
|
end
|
|
363
382
|
|
|
364
383
|
result = track_segmented_blocks.transform_values do |elm_group|
|
|
365
384
|
elm_group.map do |elm|
|
|
385
|
+
chart_info = {}
|
|
386
|
+
chart_info[:flags] = 0
|
|
387
|
+
|
|
388
|
+
chart_info[:type] = elm.at_css('.music_kind_icon').yield_self do |_elm|
|
|
389
|
+
next if _elm.nil?
|
|
390
|
+
|
|
391
|
+
Pathname(URI(src(_elm)).path).sub_ext('').sub(/.+_/, '').basename.to_s
|
|
392
|
+
end.yield_self do |type|
|
|
393
|
+
type || -'unknown'
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
nil.tap do
|
|
397
|
+
next if elm.at_css('.music_kind_icon_utage').nil?
|
|
398
|
+
|
|
399
|
+
chart_info[:variant] = strip(elm.at_css('.music_kind_icon_utage_text:nth-of-type(1)'))
|
|
400
|
+
chart_info[:flags] |= elm.at_css('.music_kind_icon_utage:has(img[src*=music_utage_buddy])').nil? ? 0 : 1
|
|
401
|
+
end
|
|
402
|
+
|
|
366
403
|
chart_info = Model::Chart::Info.new(
|
|
367
404
|
web_id: Model::WebID.parse(elm.at_css('input[name=idx][type=hidden]')['value']),
|
|
368
405
|
title: elm.at_css('.music_name_block').content,
|
|
369
|
-
type:
|
|
406
|
+
type: chart_info.fetch(:type, -'unknown'),
|
|
370
407
|
difficulty: Difficulty(Pathname(URI(src(elm.at_css('form > img:nth-of-type(1)'))).path).sub_ext('').sub(/.+_/, '').basename.to_s).id,
|
|
408
|
+
variant: chart_info.fetch(:variant, nil),
|
|
409
|
+
flags: chart_info[:flags],
|
|
371
410
|
level_text: elm.at_css('.music_lv_block').content,
|
|
372
411
|
)
|
|
373
412
|
|
data/lib/maimai_net/refines.rb
CHANGED
|
@@ -2,7 +2,10 @@ module MaimaiNet
|
|
|
2
2
|
# includes AutoConstants into invokable class
|
|
3
3
|
module IncludeAutoConstant
|
|
4
4
|
refine Kernel do
|
|
5
|
-
include CoreExt::
|
|
5
|
+
include CoreExt::AutoConstantInclusion
|
|
6
|
+
end
|
|
7
|
+
refine BasicObject do
|
|
8
|
+
include CoreExt::AutoConstantInclusion
|
|
6
9
|
end
|
|
7
10
|
end
|
|
8
11
|
|
data/lib/maimai_net/version.rb
CHANGED
data/lib/maimai_net.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: maimai_net
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rei Hakurei
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-10-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -115,15 +115,8 @@ executables: []
|
|
|
115
115
|
extensions: []
|
|
116
116
|
extra_rdoc_files: []
|
|
117
117
|
files:
|
|
118
|
-
- ".github/workflows/gem.yml"
|
|
119
|
-
- ".gitignore"
|
|
120
|
-
- ".rspec"
|
|
121
|
-
- Gemfile
|
|
122
118
|
- LICENSE
|
|
123
119
|
- README.md
|
|
124
|
-
- Rakefile
|
|
125
|
-
- bin/console
|
|
126
|
-
- bin/setup
|
|
127
120
|
- lib/maimai_net.rb
|
|
128
121
|
- lib/maimai_net/client.rb
|
|
129
122
|
- lib/maimai_net/constants.rb
|
|
@@ -143,7 +136,6 @@ files:
|
|
|
143
136
|
- lib/maimai_net/user_option.rb
|
|
144
137
|
- lib/maimai_net/util.rb
|
|
145
138
|
- lib/maimai_net/version.rb
|
|
146
|
-
- maimai-net.gemspec
|
|
147
139
|
homepage: https://github.com/ReiFan49/rb.maimai-net
|
|
148
140
|
licenses:
|
|
149
141
|
- BSD-3-Clause-Clear
|
|
@@ -165,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
165
157
|
- !ruby/object:Gem::Version
|
|
166
158
|
version: '0'
|
|
167
159
|
requirements: []
|
|
168
|
-
rubygems_version: 3.
|
|
160
|
+
rubygems_version: 3.2.33
|
|
169
161
|
signing_key:
|
|
170
162
|
specification_version: 4
|
|
171
163
|
summary: Parses maimai-net into readable data.
|
data/.github/workflows/gem.yml
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
name: Ruby Gem
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- v*
|
|
7
|
-
|
|
8
|
-
concurrency:
|
|
9
|
-
group: ${{ github.ref_name }}
|
|
10
|
-
cancel-in-progress: true
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build:
|
|
14
|
-
name: Build + Publish
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
permissions:
|
|
17
|
-
contents: read
|
|
18
|
-
packages: write
|
|
19
|
-
id-token: write
|
|
20
|
-
env:
|
|
21
|
-
RUBY_VERSION: '2.7'
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v4
|
|
24
|
-
- name: Set up Ruby ${{ env.RUBY_VERSION }}
|
|
25
|
-
uses: ruby/setup-ruby@v1
|
|
26
|
-
with:
|
|
27
|
-
bundler-cache: true
|
|
28
|
-
ruby-version: ${{ env.RUBY_VERSION }}
|
|
29
|
-
# Publish
|
|
30
|
-
- name: Publish to GPR
|
|
31
|
-
run: |
|
|
32
|
-
mkdir -p $HOME/.gem
|
|
33
|
-
touch $HOME/.gem/credentials
|
|
34
|
-
chmod 0600 $HOME/.gem/credentials
|
|
35
|
-
printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
|
36
|
-
gem build *.gemspec
|
|
37
|
-
gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
|
|
38
|
-
env:
|
|
39
|
-
GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
|
|
40
|
-
OWNER: ${{ github.repository_owner }}
|
|
41
|
-
- name: Publish to RubyGems
|
|
42
|
-
if: ${{ false }}
|
|
43
|
-
run: |
|
|
44
|
-
mkdir -p $HOME/.gem
|
|
45
|
-
touch $HOME/.gem/credentials
|
|
46
|
-
chmod 0600 $HOME/.gem/credentials
|
|
47
|
-
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
|
48
|
-
gem build *.gemspec
|
|
49
|
-
gem push *.gem
|
|
50
|
-
env:
|
|
51
|
-
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
|
52
|
-
- name: Publish to RubyGems (Trusted)
|
|
53
|
-
uses: rubygems/release-gem@v1
|
data/.gitignore
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
# Created by https://www.toptal.com/developers/gitignore/api/ruby,rubymine+all,linux
|
|
2
|
-
# Edit at https://www.toptal.com/developers/gitignore?templates=ruby,rubymine+all,linux
|
|
3
|
-
|
|
4
|
-
### Linux ###
|
|
5
|
-
*~
|
|
6
|
-
.*.swp
|
|
7
|
-
|
|
8
|
-
# temporary files which can be created if a process still has a handle open of a deleted file
|
|
9
|
-
.fuse_hidden*
|
|
10
|
-
|
|
11
|
-
# KDE directory preferences
|
|
12
|
-
.directory
|
|
13
|
-
|
|
14
|
-
# Linux trash folder which might appear on any partition or disk
|
|
15
|
-
.Trash-*
|
|
16
|
-
|
|
17
|
-
# .nfs files are created when an open file is removed but is still being accessed
|
|
18
|
-
.nfs*
|
|
19
|
-
|
|
20
|
-
### Ruby ###
|
|
21
|
-
*.gem
|
|
22
|
-
*.rbc
|
|
23
|
-
/.config
|
|
24
|
-
/coverage/
|
|
25
|
-
/InstalledFiles
|
|
26
|
-
/pkg/
|
|
27
|
-
/spec/reports/
|
|
28
|
-
/spec/examples.txt
|
|
29
|
-
/test/tmp/
|
|
30
|
-
/test/version_tmp/
|
|
31
|
-
/tmp/
|
|
32
|
-
|
|
33
|
-
# Used by dotenv library to load environment variables.
|
|
34
|
-
# .env
|
|
35
|
-
|
|
36
|
-
# Ignore Byebug command history file.
|
|
37
|
-
.byebug_history
|
|
38
|
-
|
|
39
|
-
## Specific to RubyMotion:
|
|
40
|
-
.dat*
|
|
41
|
-
.repl_history
|
|
42
|
-
build/
|
|
43
|
-
*.bridgesupport
|
|
44
|
-
build-iPhoneOS/
|
|
45
|
-
build-iPhoneSimulator/
|
|
46
|
-
|
|
47
|
-
## Specific to RubyMotion (use of CocoaPods):
|
|
48
|
-
#
|
|
49
|
-
# We recommend against adding the Pods directory to your .gitignore. However
|
|
50
|
-
# you should judge for yourself, the pros and cons are mentioned at:
|
|
51
|
-
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
|
52
|
-
# vendor/Pods/
|
|
53
|
-
|
|
54
|
-
## Documentation cache and generated files:
|
|
55
|
-
/.yardoc/
|
|
56
|
-
/_yardoc/
|
|
57
|
-
/doc/
|
|
58
|
-
/rdoc/
|
|
59
|
-
|
|
60
|
-
## Environment normalization:
|
|
61
|
-
/.bundle/
|
|
62
|
-
/vendor/bundle
|
|
63
|
-
/lib/bundler/man/
|
|
64
|
-
|
|
65
|
-
# for a library or gem, you might want to ignore these files since the code is
|
|
66
|
-
# intended to run in multiple environments; otherwise, check them in:
|
|
67
|
-
Gemfile.lock
|
|
68
|
-
.ruby-version
|
|
69
|
-
.ruby-gemset
|
|
70
|
-
|
|
71
|
-
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
72
|
-
.rvmrc
|
|
73
|
-
|
|
74
|
-
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
|
75
|
-
# .rubocop-https?--*
|
|
76
|
-
|
|
77
|
-
### RubyMine+all ###
|
|
78
|
-
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
79
|
-
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
80
|
-
|
|
81
|
-
# User-specific stuff
|
|
82
|
-
.idea/**/workspace.xml
|
|
83
|
-
.idea/**/tasks.xml
|
|
84
|
-
.idea/**/usage.statistics.xml
|
|
85
|
-
.idea/**/dictionaries
|
|
86
|
-
.idea/**/shelf
|
|
87
|
-
|
|
88
|
-
# AWS User-specific
|
|
89
|
-
.idea/**/aws.xml
|
|
90
|
-
|
|
91
|
-
# Generated files
|
|
92
|
-
.idea/**/contentModel.xml
|
|
93
|
-
|
|
94
|
-
# Sensitive or high-churn files
|
|
95
|
-
.idea/**/dataSources/
|
|
96
|
-
.idea/**/dataSources.ids
|
|
97
|
-
.idea/**/dataSources.local.xml
|
|
98
|
-
.idea/**/sqlDataSources.xml
|
|
99
|
-
.idea/**/dynamic.xml
|
|
100
|
-
.idea/**/uiDesigner.xml
|
|
101
|
-
.idea/**/dbnavigator.xml
|
|
102
|
-
|
|
103
|
-
# Gradle
|
|
104
|
-
.idea/**/gradle.xml
|
|
105
|
-
.idea/**/libraries
|
|
106
|
-
|
|
107
|
-
# Gradle and Maven with auto-import
|
|
108
|
-
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
109
|
-
# since they will be recreated, and may cause churn. Uncomment if using
|
|
110
|
-
# auto-import.
|
|
111
|
-
# .idea/artifacts
|
|
112
|
-
# .idea/compiler.xml
|
|
113
|
-
# .idea/jarRepositories.xml
|
|
114
|
-
# .idea/modules.xml
|
|
115
|
-
# .idea/*.iml
|
|
116
|
-
# .idea/modules
|
|
117
|
-
# *.iml
|
|
118
|
-
# *.ipr
|
|
119
|
-
|
|
120
|
-
# CMake
|
|
121
|
-
cmake-build-*/
|
|
122
|
-
|
|
123
|
-
# Mongo Explorer plugin
|
|
124
|
-
.idea/**/mongoSettings.xml
|
|
125
|
-
|
|
126
|
-
# File-based project format
|
|
127
|
-
*.iws
|
|
128
|
-
|
|
129
|
-
# IntelliJ
|
|
130
|
-
out/
|
|
131
|
-
|
|
132
|
-
# mpeltonen/sbt-idea plugin
|
|
133
|
-
.idea_modules/
|
|
134
|
-
|
|
135
|
-
# JIRA plugin
|
|
136
|
-
atlassian-ide-plugin.xml
|
|
137
|
-
|
|
138
|
-
# Cursive Clojure plugin
|
|
139
|
-
.idea/replstate.xml
|
|
140
|
-
|
|
141
|
-
# SonarLint plugin
|
|
142
|
-
.idea/sonarlint/
|
|
143
|
-
|
|
144
|
-
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
145
|
-
com_crashlytics_export_strings.xml
|
|
146
|
-
crashlytics.properties
|
|
147
|
-
crashlytics-build.properties
|
|
148
|
-
fabric.properties
|
|
149
|
-
|
|
150
|
-
# Editor-based Rest Client
|
|
151
|
-
.idea/httpRequests
|
|
152
|
-
|
|
153
|
-
# Android studio 3.1+ serialized cache file
|
|
154
|
-
.idea/caches/build_file_checksums.ser
|
|
155
|
-
|
|
156
|
-
### RubyMine+all Patch ###
|
|
157
|
-
# Ignore everything but code style settings and run configurations
|
|
158
|
-
# that are supposed to be shared within teams.
|
|
159
|
-
|
|
160
|
-
.idea/*
|
|
161
|
-
|
|
162
|
-
!.idea/codeStyles
|
|
163
|
-
!.idea/runConfigurations
|
|
164
|
-
|
|
165
|
-
# End of https://www.toptal.com/developers/gitignore/api/ruby,rubymine+all,linux
|
data/.rspec
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/bin/console
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
begin; require 'byebug'; rescue LoadError; end
|
|
5
|
-
require "maimai_net"
|
|
6
|
-
|
|
7
|
-
console = nil
|
|
8
|
-
begin; require 'pry'
|
|
9
|
-
rescue LoadError; require 'irb'; console = :irb
|
|
10
|
-
else; console = :pry
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
case console
|
|
14
|
-
when :pry; Pry.start
|
|
15
|
-
else; IRB.start(__FILE__)
|
|
16
|
-
end
|
data/bin/setup
DELETED
data/maimai-net.gemspec
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
require_relative 'lib/maimai_net/version'
|
|
2
|
-
|
|
3
|
-
Gem::Specification.new do |spec|
|
|
4
|
-
spec.name = "maimai_net"
|
|
5
|
-
spec.version = MaimaiNet::VERSION
|
|
6
|
-
spec.authors = [
|
|
7
|
-
%(Rei Hakurei),
|
|
8
|
-
]
|
|
9
|
-
spec.email = %w(contact@bloom-juery.net)
|
|
10
|
-
|
|
11
|
-
spec.summary = %q(Parses maimai-net into readable data.)
|
|
12
|
-
spec.homepage = 'https://github.com/ReiFan49/rb.maimai-net'
|
|
13
|
-
spec.license = 'BSD-3-Clause-Clear'
|
|
14
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
|
15
|
-
|
|
16
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
|
17
|
-
|
|
18
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
|
20
|
-
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
|
21
|
-
|
|
22
|
-
# Specify which files should be added to the gem when it is released.
|
|
23
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
24
|
-
begin
|
|
25
|
-
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
26
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
27
|
-
end
|
|
28
|
-
rescue Errno::ENOENT
|
|
29
|
-
spec.files = Dir.glob('lib/**/*.rb', base: File.expand_path('..', __FILE__))
|
|
30
|
-
end
|
|
31
|
-
spec.bindir = "exe"
|
|
32
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
33
|
-
spec.require_paths = ["lib"]
|
|
34
|
-
|
|
35
|
-
spec.add_development_dependency 'rake'
|
|
36
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
37
|
-
spec.add_development_dependency 'byebug'
|
|
38
|
-
|
|
39
|
-
spec.add_runtime_dependency 'nokogiri', '~> 1.13'
|
|
40
|
-
spec.add_runtime_dependency 'faraday', '~> 2.0'
|
|
41
|
-
spec.add_runtime_dependency 'faraday-follow_redirects'
|
|
42
|
-
|
|
43
|
-
spec.add_runtime_dependency 'http-cookie', '~> 1.0.0'
|
|
44
|
-
end
|