Class: MaimaiNet::Client::Connection

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
ConnectionSupportSongList, ConnectionSupportUserFavorite, ConnectionSupportUserOption
Defined in:
lib/maimai_net/client.rb,
lib/maimai_net/client.rb

Direct Known Subclasses

FaradayConnection

Hooks collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ConnectionSupportUserFavorite

#get_favorites, #get_songs, #set_favorites

Methods included from ConnectionSupportUserOption

#get_gameplay_settings, #set_gameplay_settings

Methods included from ConnectionSupportSongList

#song_list, #song_list_by_custom, #song_list_by_genre, #song_list_by_level, #song_list_by_title, #song_list_by_version

Constructor Details

#initialize(client) ⇒ Connection

Returns a new instance of Connection.

Parameters:

  • client (Base)

    client data



73
74
75
76
# File 'lib/maimai_net/client.rb', line 73

def initialize(client)
  @client = client
  @conn   = nil
end

Class Method Details

.inherited(cls) ⇒ Object



1011
1012
1013
# File 'lib/maimai_net/client.rb', line 1011

def inherited(cls)
  cls.prepend ConnectionProtocol, ConnectionMaintenanceSafety
end

.method_added(meth) ⇒ void

This method returns an undefined value.

automatically private hook methods



82
83
84
85
# File 'lib/maimai_net/client.rb', line 82

def self.method_added(meth)
  return super unless /^on_/.match? meth
  private meth
end

Instance Method Details

#fetch_and_submit_form(endpoint, query, response_page:) {|data, content| ... } ⇒ void

This method returns an undefined value.

wraps form-based submission request

Parameters:

  • endpoint (URI)

    request path

  • query (String, Object)

    request query

Yield Parameters:

  • data (Hash{String => Object})

    request data

  • content

    parsed page content

Yield Returns:

  • (Boolean)

    any falsy-values will stop the processing



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/maimai_net/client.rb', line 354

def fetch_and_submit_form(endpoint, query, response_page:)
  send_request(
    'get', endpoint, query,
    response: ->(body) {
      page = response_page.parse(body)
      root = page.instance_variable_get(:@root)
      form = root.at_css('form[action][method=post]')

      data = {}

      form.css('input[type=hidden]').each do |hidden_input|
        data.store hidden_input['name'], hidden_input['value']
      end

      result = yield data, page.data
      return unless result

      send_request(
        form['method'], form['action'], data,
      )

      true
    },
  )
end

#finale_archiveModel::FinaleArchive::Data

access finale archive page

Returns:



251
252
253
254
255
256
# File 'lib/maimai_net/client.rb', line 251

def finale_archive
  send_request(
    'get', '/maimai-mobile/home/congratulations', nil,
    response_page: Page::FinaleArchive,
  )
end

#homevoid

This method returns an undefined value.

access home page



91
92
93
# File 'lib/maimai_net/client.rb', line 91

def home
  send_request('get', '/maimai-mobile/home', nil)
end

#logoutvoid

This method returns an undefined value.

logs out current session



97
98
99
# File 'lib/maimai_net/client.rb', line 97

def logout
  send_request('get', '/maimai-mobile/home/userOption/logout', nil)
end

#music_record_info(ref) ⇒ Model::Record::Data

access given set best score

Returns:



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/maimai_net/client.rb', line 231

def music_record_info(ref)
  id = case ref
       when Model::WebID::DUMMY, Model::WebID::DUMMY_ID
         fail ArgumentError, 'unable to use dummy ID for lookup'
       when Model::WebID
         ref.to_s
       when String
         ref
       else
         fail TypeError, 'expected a valid index ID format'
       end

  send_request(
    'get', '/maimai-mobile/record/musicDetail', {idx: id},
    response_page: Page::ChartsetRecord,
  )
end

#on_error(body) ⇒ void

This method returns an undefined value.

hook upon receiving generic error page

Raises:



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/maimai_net/client.rb', line 313

def on_error(body)
  page = Nokogiri::HTML.parse(body)
  error_elm = page.at_css('.container_red > div')
  error_note = error_elm.text
  error_code = error_note.match(/\d+/).to_s.to_i

  case error_code
  when ErrorCodes::LOGIN_ERROR
    
  when ErrorCodes::SESSION_REFRESH
    fail Error::SessionRefreshError, error_code
  when ErrorCodes::SESSION_INVALID
    
  else
    fail Error::GeneralError, error_code
  end
end

#on_login_errorvoid

This method returns an undefined value.

hook upon receiving login error page

Raises:



294
295
296
# File 'lib/maimai_net/client.rb', line 294

def 
  fail Error::LoginError, ErrorCodes::LOGIN_ERROR
end

#on_login_expired_errorvoid

This method returns an undefined value.

hook upon receiving login expired page, triggering this hook causes client cookies wiped out.



302
303
304
305
# File 'lib/maimai_net/client.rb', line 302

def 
  @client.cookies.clear
  fail Error::SessionExpiredError, ErrorCodes::SESSION_INVALID
end

#on_login_request(url, body, **opts) ⇒ void

This method returns an undefined value.

hook upon receiving a login page



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/maimai_net/client.rb', line 264

def (url, body, **opts)
  page = Nokogiri::HTML.parse(body)
  form = page.at_css('form[action][method=post]:has(input[type=password])')
  data = form.css('input').select do |elm|
    %w(text password hidden).include? elm['type'].downcase
  end.map do |elm|
    [elm['name'], elm['value']]
  end.inject({}) do |res, (name, value)|
    res[name] = value
    res
  end

  userkey = if data.key?('segaId') then 'segaId'
            elsif data.key?('sid') then 'sid'
            else fail NotImplementedError, 'user id compatible field not found'
            end

  data[userkey]    = @client.username
  data['password'] = @client.password

  send_request(
    form['method'],
    url + form['action'],
    data, **opts,
  )
end

#photo_albumArray<Model::PhotoUpload>

access recently uploaded photo album page

Returns:



172
173
174
175
176
177
# File 'lib/maimai_net/client.rb', line 172

def photo_album
  send_request(
    'get', '/maimai-mobile/playerData/photo', nil,
    response_page: Page::PhotoUpload,
  )
end

#player_data(*diffs) ⇒ Model::PlayerData::Data

access player data

Parameters:

Returns:

Raises:

  • (TypeError)

    invalid difficulty provided

  • (ArgumentError)

    no difficulty provided



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/maimai_net/client.rb', line 106

def player_data(*diffs)
  diffs.compact!
  diffs.uniq!
  fail ArgumentError, "expected at least 1, given #{diffs.size}" if diffs.empty?

  diff_errors = []
  diffs.reject do |diff|
    case diff
    when String, Symbol
      MaimaiNet::Difficulty::DELUXE_WEBSITE.key?(diff.to_sym) ||
      MaimaiNet::Difficulty::DELUXE_WEBSITE.key?(MaimaiNet::Difficulty::SHORTS.key(diff.to_sym))
    when MaimaiNet::Difficulty # always true
      true
    when Integer
      MaimaiNet::Difficulty::DELUXE_WEBSITE.value?(diff)
    else
      false
    end
  end.each do |diff|
    case diff
    when String, Symbol; diff_errors << [diff, KeyError]
    when Integer;        diff_errors << [diff, ArgumentError]
    else;                diff_errors << [diff, TypeError]
    end
  end

  unless diff_errors.empty?
    fail TypeError, "at least one of difficulty provided are erroneous.\n%s" % [
      diff_errors.map do |d, et| '(%s: %p)' % [et, d] end.join(', '),
    ]
  end

  diffs.map! do |diff|
    case diff
    when String, Symbol;        Difficulty(diff)
    when MaimaiNet::Difficulty; diff
    when Integer;               Difficulty(deluxe_web_id: diff)
    end
  end
  diffs.sort_by! &:id

  results = diffs.map do |diff|
    send_request(
      'get', '/maimai-mobile/playerData',
      {diff: diff.deluxe_web_id},
      response_page: Page::PlayerData,
    )
  end

  # aggregate results if necessary
  if results.size > 1 then
    user_diff_stat = {}
    results.each do |result|
      user_diff_stat.update result.statistics
    end
    results.first.class.new(
      plate: results.first.plate,
      statistics: user_diff_stat,
    )
  else
    results.shift
  end
end

#recent_play_details(limit = nil) ⇒ Array<Model::Result::Data>

access recent session gameplay detailed info

Parameters:

  • amount (Integer, nil)

    of tracks to fetch

Returns:



215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/maimai_net/client.rb', line 215

def recent_play_details(limit = nil)
  commands = []
  if Integer === limit then
    if limit.positive? then
      commands << ->(plays){plays.last(limit)}
    else
      fail ArgumentError, "expected positive size limit, given #{limit}"
    end
  end
  plays = recent_plays.map(&:ref_web_id)
  commands.each do |cmd| plays.replace cmd[plays] end
  plays.map(&method(:recent_play_info))
end

#recent_play_info(ref) ⇒ Model::Result::Data

access recent session gameplay info detail

Returns:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/maimai_net/client.rb', line 190

def recent_play_info(ref)
  id = case ref
       when Model::Result::TrackReference
         ref.ref_web_id.to_s
       when Model::Result::ReferenceWebID
         ref.to_s
       when /^\d+,\d+$/
         ref
       else
         fail TypeError, 'expected a valid index ID format'
       end

  actual_time = Time.at(/^(\d+),(\d+)$/.match(id).captures[1].to_i).localtime(32400).freeze

  send_request(
    'get', '/maimai-mobile/record/playlogDetail', {idx: id},
    response_page: Page::TrackResult,
  ).tap do |track_result|
    track_result.track.time = actual_time
  end
end

#recent_playsArray<Model::Result::TrackReference>

access recent session gameplay info

Returns:



181
182
183
184
185
186
# File 'lib/maimai_net/client.rb', line 181

def recent_plays
  send_request(
    'get', '/maimai-mobile/record', nil,
    response_page: Page::RecentTrack,
  )
end

#send_request(method, url, data, **opts) ⇒ Model::Base, void

This method is abstract.

sends request to given connection object

Parameters:

  • method (Symbol, String)

    request method

  • url (URI)

    request path

  • data (String, Object)

    request body

  • opts (Hash{Symbol => Object})
  • response_page (Hash)

    a customizable set of options

  • response (Hash)

    a customizable set of options

Returns:

  • (Model::Base)

    returns page data based from provided response_page field

  • (void)


342
343
344
# File 'lib/maimai_net/client.rb', line 342

def send_request(method, url, data, **opts)
  fail NotImplementedError, 'abstract method called' if Connection == method(__method__).owner
end