maimai_net 0.0.2 → 0.0.3
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 +27 -8
- data/lib/maimai_net/core_ext.rb +18 -0
- data/lib/maimai_net/model.rb +4 -0
- data/lib/maimai_net/page-html_helper.rb +49 -0
- data/lib/maimai_net/page-track_result_helper.rb +9 -20
- data/lib/maimai_net/page.rb +42 -42
- data/lib/maimai_net/refines.rb +7 -2
- data/lib/maimai_net/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6a043bf61fd5bd226b1f0772ee5a31014fdea73a61f66aac3f016b2f7fa3a74
|
|
4
|
+
data.tar.gz: 835c2b17b0fce9fe35dc83517193e135cd1bf99d052ad9618116dc311b2fa592
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7afa65b670dfb11d8f1827381c8b3264580784871581b6238f3de84040040607e6bd728b0aac6ad50abcb45707e28a398cd45d2c8a7798b9be98b61363b5584
|
|
7
|
+
data.tar.gz: ea17dd808b4c8030426715535325aed6eb5d12d152e7b888726cba95d63e12d9e79d3078e46e6332f43622662ad82a23719d088e329eb18a99521ed90c77bac0
|
data/lib/maimai_net/client.rb
CHANGED
|
@@ -25,6 +25,12 @@ module MaimaiNet
|
|
|
25
25
|
versions: :version,
|
|
26
26
|
}.freeze
|
|
27
27
|
|
|
28
|
+
module ErrorCodes
|
|
29
|
+
LOGIN_ERROR = 100_101
|
|
30
|
+
SESSION_REFRESH = 200_002
|
|
31
|
+
SESSION_INVALID = 200_004
|
|
32
|
+
end
|
|
33
|
+
|
|
28
34
|
class Base
|
|
29
35
|
include ModuleExt
|
|
30
36
|
|
|
@@ -193,10 +199,14 @@ module MaimaiNet
|
|
|
193
199
|
fail TypeError, 'expected a valid index ID format'
|
|
194
200
|
end
|
|
195
201
|
|
|
202
|
+
actual_time = Time.at(/^(\d+),(\d+)$/.match(id).captures[1].to_i).localtime(32400).freeze
|
|
203
|
+
|
|
196
204
|
send_request(
|
|
197
205
|
'get', '/maimai-mobile/record/playlogDetail', {idx: id},
|
|
198
206
|
response_page: Page::TrackResult,
|
|
199
|
-
)
|
|
207
|
+
).tap do |track_result|
|
|
208
|
+
track_result.track.time = actual_time
|
|
209
|
+
end
|
|
200
210
|
end
|
|
201
211
|
|
|
202
212
|
# access recent session gameplay detailed info
|
|
@@ -282,7 +292,16 @@ module MaimaiNet
|
|
|
282
292
|
# @return [void]
|
|
283
293
|
# @raise [Error::LoginError]
|
|
284
294
|
def on_login_error
|
|
285
|
-
fail Error::LoginError,
|
|
295
|
+
fail Error::LoginError, ErrorCodes::LOGIN_ERROR
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# hook upon receiving login expired page,
|
|
299
|
+
# triggering this hook causes client cookies wiped out.
|
|
300
|
+
# @return [void]
|
|
301
|
+
# @raise [Error::SessionExpiredError]
|
|
302
|
+
def on_login_expired_error
|
|
303
|
+
@client.cookies.clear
|
|
304
|
+
fail Error::SessionExpiredError, ErrorCodes::SESSION_INVALID
|
|
286
305
|
end
|
|
287
306
|
|
|
288
307
|
# hook upon receiving generic error page
|
|
@@ -298,12 +317,12 @@ module MaimaiNet
|
|
|
298
317
|
error_code = error_note.match(/\d+/).to_s.to_i
|
|
299
318
|
|
|
300
319
|
case error_code
|
|
301
|
-
when
|
|
302
|
-
|
|
303
|
-
when
|
|
320
|
+
when ErrorCodes::LOGIN_ERROR
|
|
321
|
+
on_login_error
|
|
322
|
+
when ErrorCodes::SESSION_REFRESH
|
|
304
323
|
fail Error::SessionRefreshError, error_code
|
|
305
|
-
when
|
|
306
|
-
|
|
324
|
+
when ErrorCodes::SESSION_INVALID
|
|
325
|
+
on_login_expired_error
|
|
307
326
|
else
|
|
308
327
|
fail Error::GeneralError, error_code
|
|
309
328
|
end
|
|
@@ -1002,7 +1021,7 @@ module MaimaiNet
|
|
|
1002
1021
|
|
|
1003
1022
|
@conn = Faraday.new(url: info[:base_host]) do |builder|
|
|
1004
1023
|
builder.request :url_encoded
|
|
1005
|
-
builder.response :follow_redirects
|
|
1024
|
+
builder.response :follow_redirects, limit: 5
|
|
1006
1025
|
builder.use :cookie_jar, jar: client.cookies
|
|
1007
1026
|
end
|
|
1008
1027
|
end
|
data/lib/maimai_net/core_ext.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
module MaimaiNet
|
|
2
2
|
module CoreExt
|
|
3
|
+
# contains every {Constants AutoConstant} classes turned into a function.
|
|
4
|
+
# used for include or prepend in a scope.
|
|
5
|
+
#
|
|
6
|
+
# also used in `refine` {IncludeAutoConstant}.
|
|
3
7
|
module AutoConstantInclusion
|
|
4
8
|
MaimaiNet.constants.each do |k|
|
|
5
9
|
cls = MaimaiNet.const_get(k)
|
|
@@ -9,5 +13,19 @@ module MaimaiNet
|
|
|
9
13
|
private k
|
|
10
14
|
end
|
|
11
15
|
end
|
|
16
|
+
|
|
17
|
+
# adds JSON conversion support through `#to_h` conversion.
|
|
18
|
+
module JSONSupport
|
|
19
|
+
def as_json(options = nil)
|
|
20
|
+
to_h.transform_values do |val|
|
|
21
|
+
val.respond_to?(:as_json) ?
|
|
22
|
+
val.as_json(options) : val
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_json(options = nil)
|
|
27
|
+
as_json.to_json(options)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
12
30
|
end
|
|
13
31
|
end
|
data/lib/maimai_net/model.rb
CHANGED
|
@@ -26,6 +26,8 @@ module MaimaiNet
|
|
|
26
26
|
super(*args)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
include CoreExt::JSONSupport
|
|
30
|
+
|
|
29
31
|
class << self
|
|
30
32
|
# creates a strong-typed struct data
|
|
31
33
|
# @param opts [Hash{Symbol => Module}]
|
|
@@ -276,6 +278,8 @@ module MaimaiNet
|
|
|
276
278
|
track: Track,
|
|
277
279
|
breakdown: Generic[Hash, Symbol, Judgment],
|
|
278
280
|
timing: Offset,
|
|
281
|
+
rating_before: Integer,
|
|
282
|
+
rating_after: Integer,
|
|
279
283
|
members: Generic[Array, TourMember],
|
|
280
284
|
rival: Optional[RivalInfo],
|
|
281
285
|
players: Generic[Array, PlayerInfo],
|
|
@@ -64,6 +64,18 @@ module MaimaiNet
|
|
|
64
64
|
# and de-group all of the string into array of integers
|
|
65
65
|
# @return [Array<Integer>]
|
|
66
66
|
def scan_int(content); content.scan(GROUPED_INTEGER).map(&method(:int)); end
|
|
67
|
+
# parse time string as JST
|
|
68
|
+
# @return [Time]
|
|
69
|
+
def jst(time); ::Time.strptime(time + ' +09:00', '%Y/%m/%d %H:%M %z'); end
|
|
70
|
+
# parse time string as JST from stripped text content
|
|
71
|
+
# @return [Time]
|
|
72
|
+
def jst_from(node); jst(strip(node)); end
|
|
73
|
+
# @return [String] basename part of the path without any prefixes
|
|
74
|
+
def subpath(uri); ::Kernel.Pathname(::Kernel.URI(uri).path)&.sub_ext('')&.sub(/.+_/, '')&.basename.to_s; end
|
|
75
|
+
# (see #subpath)
|
|
76
|
+
def subpath_from(node); node ? subpath(src(node)) : -'' end
|
|
77
|
+
# @return [String] text contained directly under the node
|
|
78
|
+
def text(node); node.children.select(&:text?).map(&:content).inject('', :concat).strip end
|
|
67
79
|
|
|
68
80
|
inspect_permit_variable_exclude :_page
|
|
69
81
|
inspect_permit_expression do |value| false end
|
|
@@ -73,6 +85,43 @@ module MaimaiNet
|
|
|
73
85
|
private :new
|
|
74
86
|
end
|
|
75
87
|
|
|
88
|
+
module TrackHelper
|
|
89
|
+
# @return [Constants::Difficulty] difficulty value of given html element
|
|
90
|
+
def get_chart_difficulty_from(node)
|
|
91
|
+
Difficulty(subpath_from(node))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [String] normalized difficulty text
|
|
95
|
+
def get_chart_level_text_from(node)
|
|
96
|
+
strip(node).sub(/\?$/, '')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [String] chart type of given html element
|
|
100
|
+
# @return ["unknown"] if the chart element is not defined
|
|
101
|
+
def get_chart_type_from(node)
|
|
102
|
+
return -'unknown' if node.nil?
|
|
103
|
+
|
|
104
|
+
subpath_from(node)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @return [String] chart variant of given html element
|
|
108
|
+
# @return [nil] if the chart element is not utage
|
|
109
|
+
# @see HelperBlock#strip
|
|
110
|
+
# @note this is a semantic clarity for strip function.
|
|
111
|
+
def get_chart_variant_from(node)
|
|
112
|
+
node&.at_css('img[src*="music_utage.png"]').nil? ?
|
|
113
|
+
nil : strip(node)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @return [0] for non buddy chart
|
|
117
|
+
# @return [1] for buddy chart
|
|
118
|
+
def get_chart_buddy_flag_from(node)
|
|
119
|
+
node&.at_css('img[src*="music_utage_buddy.png"]').nil? ? 0 : 1
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
HelperBlock.include TrackHelper
|
|
124
|
+
|
|
76
125
|
# adds capability to inject methods using hidden helper block
|
|
77
126
|
module HelperSupport
|
|
78
127
|
# defines the method to be injected with hidden helper block.
|
|
@@ -10,12 +10,8 @@ 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 =
|
|
14
|
-
utage_variant = header_block.at_css('.playlog_music_kind_icon_utage')
|
|
15
|
-
next if elm.nil?
|
|
16
|
-
|
|
17
|
-
strip(elm)
|
|
18
|
-
end
|
|
13
|
+
difficulty = get_chart_difficulty_from(header_block.at_css('img.playlog_diff'))
|
|
14
|
+
utage_variant = get_chart_variant_from(header_block.at_css('.playlog_music_kind_icon_utage'))
|
|
19
15
|
|
|
20
16
|
dx_container_classes = MaimaiNet::Difficulty::DELUXE.select do |k, v| v.positive? end
|
|
21
17
|
.keys.map do |k| ".playlog_#{k}_container" end
|
|
@@ -25,36 +21,29 @@ module MaimaiNet
|
|
|
25
21
|
result_block = info_block.at_css('.basic_block ~ div:nth-of-type(1)')
|
|
26
22
|
|
|
27
23
|
track_order = get_fullint(strip(header_block.at_css('div.sub_title > span:nth-of-type(1)')))
|
|
28
|
-
play_time =
|
|
29
|
-
strip(header_block.at_css('div.sub_title > span:nth-of-type(2)')) + ' +09:00',
|
|
30
|
-
'%Y/%m/%d %H:%M %z',
|
|
31
|
-
)
|
|
24
|
+
play_time = jst_from(header_block.at_css('div.sub_title > span:nth-of-type(2)'))
|
|
32
25
|
song_name = strip(chart_header_block.children.last)
|
|
33
|
-
chart_level =
|
|
26
|
+
chart_level = get_chart_level_text_from(chart_header_block.at_css('div:nth-of-type(1)'))
|
|
34
27
|
song_jacket = src(result_block.at_css('img.music_img'))
|
|
35
|
-
chart_type
|
|
36
|
-
next if elm.nil?
|
|
37
|
-
|
|
38
|
-
::Kernel.Pathname(src(elm))&.sub_ext('')&.sub(/.+_/, '')&.basename&.to_s
|
|
39
|
-
end
|
|
28
|
+
chart_type = get_chart_type_from(result_block.at_css('img.playlog_music_kind_icon'))
|
|
40
29
|
|
|
41
30
|
result_score = strip(result_block.at_css('.playlog_achievement_txt')).to_f
|
|
42
31
|
result_deluxe_scores = scan_int(strip(result_block.at_css('.playlog_result_innerblock .playlog_score_block div:nth-of-type(1)')))
|
|
43
|
-
result_grade =
|
|
32
|
+
result_grade = subpath_from(result_block.at_css('.playlog_scorerank')).to_sym
|
|
44
33
|
result_flags = result_block.css('.playlog_result_innerblock > img').map do |elm|
|
|
45
|
-
flag =
|
|
34
|
+
flag = subpath_from(elm)
|
|
46
35
|
case flag
|
|
47
36
|
when *MaimaiNet::AchievementFlag::RESULT.values; AchievementFlag(result_key: flag)
|
|
48
37
|
when /_dummy$/; nil
|
|
49
38
|
end
|
|
50
39
|
end.compact
|
|
51
40
|
result_position = result_block.at_css('.playlog_result_innerblock img.playlog_matching_icon')&.yield_self do |elm|
|
|
52
|
-
/^\d+/.match(
|
|
41
|
+
/^\d+/.match(subpath_from(elm))[0].to_i
|
|
53
42
|
end
|
|
54
43
|
|
|
55
44
|
challenge_info = nil
|
|
56
45
|
result_block.at_css('div:has(> .playlog_life_block)')&.tap do |elm|
|
|
57
|
-
challenge_type =
|
|
46
|
+
challenge_type = subpath_from(elm.at_css('img:nth-of-type(1)')).to_sym
|
|
58
47
|
challenge_lives = scan_int(strip(elm.at_css('.playlog_life_block')))
|
|
59
48
|
|
|
60
49
|
challenge_info = Model::Result::Challenge.new(
|
data/lib/maimai_net/page.rb
CHANGED
|
@@ -135,22 +135,23 @@ module MaimaiNet
|
|
|
135
135
|
images.map do |elm|
|
|
136
136
|
elm = elm.at_css('> div')
|
|
137
137
|
|
|
138
|
-
chart_type =
|
|
139
|
-
difficulty =
|
|
138
|
+
chart_type = get_chart_type_from(elm.at_css('> .music_kind_icon'))
|
|
139
|
+
difficulty = get_chart_difficulty_from(elm.at_css('> .block_info:nth-of-type(1) ~ img:nth-of-type(1)'))
|
|
140
|
+
chart_flags = [
|
|
141
|
+
get_chart_buddy_flag_from(elm.at_css('.music_kind_icon_utage:has(img[src*="music_utage_buddy.png"])')),
|
|
142
|
+
].inject(0, :|)
|
|
140
143
|
|
|
141
144
|
Model::PhotoUpload.new(
|
|
142
145
|
info: Model::Chart::InfoLite.new(
|
|
143
|
-
title: strip(elm.at_css('>
|
|
146
|
+
title: strip(elm.at_css('> .clearfix:nth-of-type(1) ~ div:nth-of-type(1)')),
|
|
144
147
|
type: chart_type.to_s,
|
|
148
|
+
variant: get_chart_variant_from(elm.at_css('.music_kind_icon_utage:has(img[src*="music_utage.png"])')),
|
|
149
|
+
flags: chart_flags,
|
|
145
150
|
difficulty: difficulty.id,
|
|
146
151
|
),
|
|
147
|
-
url: URI(src(elm.at_css('> img:nth-of-type(
|
|
148
|
-
location: strip(elm.at_css('>
|
|
149
|
-
time:
|
|
150
|
-
strip(elm.at_css('> div:not(.clearfix):nth-of-type(1)')) + ' +09:00',
|
|
151
|
-
'%Y/%m/%d %H:%M %z',
|
|
152
|
-
Time.now.localtime(32400),
|
|
153
|
-
),
|
|
152
|
+
url: URI(src(elm.at_css('> .block_info:nth-of-type(1) ~ img:nth-of-type(2)'))),
|
|
153
|
+
location: strip(elm.at_css('> .clearfix:nth-of-type(1) ~ div:nth-of-type(3)')),
|
|
154
|
+
time: jst_from(elm.at_css('> div:not(.clearfix):nth-of-type(1)')),
|
|
154
155
|
)
|
|
155
156
|
end
|
|
156
157
|
end
|
|
@@ -167,8 +168,12 @@ module MaimaiNet
|
|
|
167
168
|
song_info_elm = @summary_block.at_css('> div:nth-of-type(1)')
|
|
168
169
|
|
|
169
170
|
song_jacket = URI(src(@summary_block.at_css('> img:nth-of-type(1)')))
|
|
170
|
-
set_type =
|
|
171
|
-
|
|
171
|
+
set_type = get_chart_type_from(song_info_elm.at_css('> div:nth-of-type(1) > img'))
|
|
172
|
+
set_utage_variant = get_chart_variant_from(song_info_elm.at_css('> div:nth-of-type(1) > .music_kind_icon_utage:has(img[src*="music_utage.png"])'))
|
|
173
|
+
set_flag = [
|
|
174
|
+
get_chart_buddy_flag_from(song_info_elm.at_css('> div:nth-of-type(1) > .music_kind_icon_utage:has(img[src*="music_utage_buddy.png"])')),
|
|
175
|
+
].inject(0, :|)
|
|
176
|
+
song_genre = text(song_info_elm.at_css('> div:nth-of-type(1)'))
|
|
172
177
|
song_name = strip(song_info_elm.at_css('> div:nth-of-type(2)'))
|
|
173
178
|
song_artist = strip(song_info_elm.at_css('> div:nth-of-type(3)'))
|
|
174
179
|
|
|
@@ -185,30 +190,33 @@ module MaimaiNet
|
|
|
185
190
|
chart_score_blocks = @summary_block.css('~ div:has(~ img)')
|
|
186
191
|
|
|
187
192
|
info_blocks.each do |info_block|
|
|
188
|
-
level_text =
|
|
193
|
+
level_text = get_chart_level_text_from(info_block.at_css('.music_lv_back'))
|
|
189
194
|
form_block = info_block.at_css('form')
|
|
190
195
|
form_inputs = form_block.css('input[name][type=hidden]').map do |elm|
|
|
191
196
|
[elm['name'].to_sym, elm['value']]
|
|
192
197
|
end.to_h
|
|
198
|
+
|
|
193
199
|
difficulty = Difficulty(deluxe_web_id: form_inputs[:diff].to_i)
|
|
194
200
|
difficulty_data[difficulty.abbrev] ||= {}
|
|
195
201
|
difficulty_data[difficulty.abbrev].store(:info, Model::Chart::Info.new(
|
|
196
202
|
web_id: Model::WebID.parse(form_inputs[:idx]),
|
|
197
203
|
title: song_name,
|
|
198
204
|
type: set_type,
|
|
205
|
+
variant: set_utage_variant,
|
|
206
|
+
flags: set_flag,
|
|
199
207
|
difficulty: difficulty.id,
|
|
200
208
|
level_text: level_text,
|
|
201
209
|
))
|
|
202
210
|
end
|
|
203
211
|
|
|
204
212
|
chart_score_blocks.each do |chart_score_block|
|
|
205
|
-
difficulty =
|
|
206
|
-
chart_type =
|
|
213
|
+
difficulty = get_chart_difficulty_from(chart_score_block.at_css('> img:nth-of-type(1)'))
|
|
214
|
+
chart_type = get_chart_type_from(chart_score_block.at_css('> img:nth-of-type(2)')) || set_type
|
|
207
215
|
clearfixes = chart_score_block.css('.clearfix')
|
|
208
216
|
|
|
209
217
|
chart_record_block = clearfixes[0].at_css('~ div:nth-of-type(2)')
|
|
210
218
|
record_grade, record_flag, record_sync_flag = chart_record_block.css('> img').map do |elm|
|
|
211
|
-
value =
|
|
219
|
+
value = subpath_from(elm)
|
|
212
220
|
case value
|
|
213
221
|
when 'back'; nil
|
|
214
222
|
when *MaimaiNet::AchievementFlag::RECORD.values; MaimaiNet::AchievementFlag.new(record_key: value)
|
|
@@ -216,7 +224,7 @@ module MaimaiNet
|
|
|
216
224
|
end
|
|
217
225
|
end
|
|
218
226
|
last_played_date, total_play_count = chart_record_block.css('table tr td:nth-of-type(2)').zip([
|
|
219
|
-
|
|
227
|
+
method(:jst),
|
|
220
228
|
method(:int),
|
|
221
229
|
]).map do |elm, block|
|
|
222
230
|
block.call(strip(elm))
|
|
@@ -227,7 +235,7 @@ module MaimaiNet
|
|
|
227
235
|
chart_deluxe_scores = scan_int(strip(chart_best_block.at_css('.music_score_block:nth-of-type(2)')))
|
|
228
236
|
chart_deluxe_grade_elm = chart_best_block.at_css('.music_score_block:nth-of-type(2) img:nth-child(2)')
|
|
229
237
|
record_deluxe_grade = chart_deluxe_grade_elm ?
|
|
230
|
-
|
|
238
|
+
subpath_from(chart_deluxe_grade_elm).to_i :
|
|
231
239
|
0
|
|
232
240
|
|
|
233
241
|
difficulty_data[difficulty.abbrev].tap do |d|
|
|
@@ -272,7 +280,7 @@ module MaimaiNet
|
|
|
272
280
|
|
|
273
281
|
helper_method :data do
|
|
274
282
|
result_breakdown = @breakdown_block.css('table.playlog_notes_detail tr:not(:first-child)').map do |row|
|
|
275
|
-
key =
|
|
283
|
+
key = subpath_from(row.at_css('th img')).to_sym
|
|
276
284
|
values = Model::Result::Judgment.new(**Model::Result::Judgment.members.zip(
|
|
277
285
|
row.css('td').map(&method(:strip)).map(&method(:get_int))
|
|
278
286
|
).to_h)
|
|
@@ -282,8 +290,8 @@ module MaimaiNet
|
|
|
282
290
|
result_offset_breakdown = @breakdown_block.css('.playlog_fl_block > div').map do |elm|
|
|
283
291
|
get_int(strip(elm))
|
|
284
292
|
end
|
|
285
|
-
result_rating_after = int(strip(@breakdown_block.at_css('.playlog_rating_detail_block > div:
|
|
286
|
-
result_rating_delta =
|
|
293
|
+
result_rating_after = int(strip(@breakdown_block.at_css('.playlog_rating_detail_block > div:has(.rating_block) .rating_block')))
|
|
294
|
+
result_rating_delta = get_int(@breakdown_block.at_css('.playlog_rating_detail_block > div:has(.rating_block) ~ img[src*="/playlog/rating"] ~ div > span'))
|
|
287
295
|
result_combos, result_sync_scores = @breakdown_block.css('.playlog_score_block').map do |elm|
|
|
288
296
|
scan_int(strip(elm)).tap do |ary| ary.fill(0, ary.size...2) end
|
|
289
297
|
end
|
|
@@ -312,7 +320,7 @@ module MaimaiNet
|
|
|
312
320
|
end
|
|
313
321
|
|
|
314
322
|
result_players = @multiplayer_block&.css(':has(img[src*="/diff"])').to_a.map do |elm|
|
|
315
|
-
difficulty =
|
|
323
|
+
difficulty = get_chart_difficulty_from(elm.at_css('> img:nth-of-type(1)'))
|
|
316
324
|
name = strip(elm.at_css('> div.basic_block:nth-of-type(1)'))
|
|
317
325
|
|
|
318
326
|
Model::Result::PlayerInfo.new(
|
|
@@ -332,6 +340,8 @@ module MaimaiNet
|
|
|
332
340
|
),
|
|
333
341
|
breakdown: result_breakdown,
|
|
334
342
|
timing: Model::Result::Offset.new(**Model::Result::Offset.members.zip(result_offset_breakdown).to_h),
|
|
343
|
+
rating_before: result_rating_after - result_rating_delta,
|
|
344
|
+
rating_after: result_rating_after,
|
|
335
345
|
members: result_tour_members,
|
|
336
346
|
rival: result_otomodachi_rival,
|
|
337
347
|
players: result_players,
|
|
@@ -383,31 +393,21 @@ module MaimaiNet
|
|
|
383
393
|
result = track_segmented_blocks.transform_values do |elm_group|
|
|
384
394
|
elm_group.map do |elm|
|
|
385
395
|
chart_info = {}
|
|
386
|
-
chart_info[:flags] = 0
|
|
387
396
|
|
|
388
|
-
chart_info[:type] = elm.at_css('.music_kind_icon')
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
|
397
|
+
chart_info[:type] = get_chart_type_from(elm.at_css('.music_kind_icon'))
|
|
398
|
+
chart_info[:variant] = get_chart_variant_from(elm.at_css('.music_kind_icon_utage:has(img[src*="music_utage.png"])'))
|
|
399
|
+
chart_info[:flags] = [
|
|
400
|
+
get_chart_buddy_flag_from(elm.at_css('.music_kind_icon_utage:has(img[src*="music_utage_buddy.png"])')),
|
|
401
|
+
].inject(0, :|)
|
|
402
402
|
|
|
403
403
|
chart_info = Model::Chart::Info.new(
|
|
404
404
|
web_id: Model::WebID.parse(elm.at_css('input[name=idx][type=hidden]')['value']),
|
|
405
|
-
title: elm.at_css('.music_name_block')
|
|
405
|
+
title: strip(elm.at_css('.music_name_block')),
|
|
406
406
|
type: chart_info.fetch(:type, -'unknown'),
|
|
407
|
-
difficulty:
|
|
407
|
+
difficulty: get_chart_difficulty_from(elm.at_css('form > img:nth-of-type(1)')).id,
|
|
408
408
|
variant: chart_info.fetch(:variant, nil),
|
|
409
409
|
flags: chart_info[:flags],
|
|
410
|
-
level_text: elm.at_css('.music_lv_block')
|
|
410
|
+
level_text: get_chart_level_text_from(elm.at_css('.music_lv_block')),
|
|
411
411
|
)
|
|
412
412
|
|
|
413
413
|
score_info = nil
|
|
@@ -421,7 +421,7 @@ module MaimaiNet
|
|
|
421
421
|
)
|
|
422
422
|
else
|
|
423
423
|
# ratingTargetMusic page
|
|
424
|
-
best_grade =
|
|
424
|
+
best_grade = subpath_from(elm.at_css('.music_score_block:nth-of-type(1) > div > img')).to_sym
|
|
425
425
|
score_info = Model::Result::ScoreOnly.new(
|
|
426
426
|
score: strip(elm.at_css('.music_score_block:nth-of-type(1)')).to_f,
|
|
427
427
|
grade: best_grade,
|
|
@@ -437,7 +437,7 @@ module MaimaiNet
|
|
|
437
437
|
if !elm.at_css('.music_score_block').nil? then
|
|
438
438
|
best_deluxe_score = scan_int(strip(elm.at_css('.music_score_block:nth-of-type(2)')))
|
|
439
439
|
flairs = elm.css('.music_score_block ~ img:has(~ .clearfix)').map do |img|
|
|
440
|
-
|
|
440
|
+
subpath_from(img).to_sym.yield_self do |value|
|
|
441
441
|
value == :back ? nil : value
|
|
442
442
|
end
|
|
443
443
|
end
|
data/lib/maimai_net/refines.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
module MaimaiNet
|
|
2
|
-
#
|
|
2
|
+
# injects {CoreExt::AutoConstantInclusion} into {Kernel} and {BasicObject}.
|
|
3
|
+
# @note A bug noticed from v0.0.1 where refining Kernel only
|
|
4
|
+
# is not reliable due to prepending on Kernel causes
|
|
5
|
+
# all refines on Kernel are invalidated. A band-aid solution
|
|
6
|
+
# for this is also injecting BasicObject with the same refine.
|
|
3
7
|
module IncludeAutoConstant
|
|
4
8
|
refine Kernel do
|
|
5
9
|
include CoreExt::AutoConstantInclusion
|
|
@@ -9,7 +13,8 @@ module MaimaiNet
|
|
|
9
13
|
end
|
|
10
14
|
end
|
|
11
15
|
|
|
12
|
-
#
|
|
16
|
+
# grants any object an ability to convert itself into a single-element array.
|
|
17
|
+
# unless it's an array already.
|
|
13
18
|
module ObjectAsArray
|
|
14
19
|
refine Object do
|
|
15
20
|
def as_array
|
data/lib/maimai_net/version.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.3
|
|
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-10-
|
|
11
|
+
date: 2025-10-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|