Module: MaimaiNet::Client::ConnectionSupportSongList

Included in:
Connection
Defined in:
lib/maimai_net/client.rb

Instance Method Summary collapse

Instance Method Details

#song_list(category, **options) ⇒ Hash{Symbol => Array<Model::Record::InfoRating>}

access user’s best scores of all music on given sorting mode



528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/maimai_net/client.rb', line 528

def song_list(category, **options)
  fail ArgumentError, "#{category} is not a valid key" unless /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/.match?(category)

  converted_options = options.map do |key, value|
    next [key, value] unless Symbol === value
    raw_value = KEY_MAP_CONSTANT[key].new(value)
    [key, raw_value]
  end.to_h

  options.update(converted_options)

  options.transform_keys! do |key|
    if key == :character then
      :word
    else
      key
    end
  end

  options.transform_values! do |value|
    case value
    when MaimaiNet::Genre, MaimaiNet::NameGroup, MaimaiNet::LevelGroup, MaimaiNet::GameVersion, MaimaiNet::Difficulty
      value.deluxe_web_id
    else
      value
    end
  end

  send_request(
    'get', "/maimai-mobile/record/music#{category}/search", options,
    response_page: Page::MusicList,
  )
end

#song_list_by_custom(sort:, diffs:, played_only: true, **filters) ⇒ Array<MaimaiNet::Model::Record::InfoCategory>, Hash{Symbol => Array<MaimaiNet::Model::Record::InfoCategory>}

retrieves player’s best score of given difficulty based on given filtering



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
# File 'lib/maimai_net/client.rb', line 609

def song_list_by_custom(sort:, diffs:, played_only: true, **filters)
  normalize_isc = ->(type, value) {
    raw_value = value

    case value
    when Integer
      return value
    when Symbol
      base_class = case type
                   when :sort; MaimaiNet::BestScoreSortType
                   when :diff; MaimaiNet::Difficulty
                   when *KEY_MAP_CONSTANT.keys;
                     KEY_MAP_CONSTANT[type]
                   end
      fail ArgumentError, "expected key (#{type}) is compatible constant class" if base_class.nil?

      raw_value = base_class.new(value)
    end

    if MaimaiNet::Constant === raw_value then
      fail ArgumentError, "Provided value #{value.inspect} is not a valid value for '#{type}'" if raw_value.deluxe_web_id.nil?
      return raw_value.deluxe_web_id
    end
    fail ArgumentError, "expected Integer, Symbol or MaimaiNet::Constant classes. given #{raw_value.class}"
  }

  convert_values = ->(type, base_value) {
    case type
    when :all
      fail ArgumentError, '"all" filter must not a false' unless base_value
      return :A
    when *PLURAL_KEY_MAP.keys
    else fail ArgumentError, "invalid filter '#{type}'"
    end

    prefix = case type
             when :genres;             :G
             when :characters, :words; :W
             when :levels;             :L
             when :versions;           :V
             end

    return prefix if Symbol === base_value && base_value.downcase == :all

    base_value.as_unique_array.yield_self do |values|
      values.map do |raw_value|
        value = raw_value
        PLURAL_KEY_MAP[type].yield_self do |singular_type|
          KEY_MAP_CONSTANT[singular_type]
        end.yield_self do |cls|
          fail TypeError, "given Symbol, expected key (#{type}) is compatible constant class" if cls.nil?
          cls.new(value)
        end if Symbol === value

        case value
        when Integer
          "#{prefix}-#{value}"
        when MaimaiNet::Genre, MaimaiNet::NameGroup, MaimaiNet::LevelGroup, MaimaiNet::GameVersion
          "#{prefix}-#{value.deluxe_web_id}"
        end
      end
    end
  }

  # filtering rules:
  # - each filter represents an intersection relation
  # - each variant in a filter represents a union relation
  # - each variant in a difficulty parameter represents a union relation
  #
  # when first filter acts, it populates the song_list first
  # afterwards, every following filter does:
  # - all filters (all: true and <filter>: all) are skipped
  # - set filtered_list flag to true
  # - removes any song that doesn't intersect with the result
  head_type, head_value = filters.shift
  song_list = []

  if head_type === :all then
    head_values = convert_values.call(head_type, head_value)
  else
    head_values = convert_values.call(head_type, head_value)
  end

  sort  = normalize_isc.call(:sort, sort)
  diffs = diffs.as_unique_array.map do |diff|
    normalize_isc.call(:diff, diff)
  end

  filters.reject! do |key, value| (key == :all && value) || value == :all end
  processed_filters = filters.map &convert_values

  quick_concat = ->(k, v1, v2) { v1.concat(v2) }
  send = ->(search, diff) {
    send_request(
      'get', "/maimai-mobile/record/musicSort/search",
      {
        search:    search,
        sort:      sort,
        diff:      diff,
        playCheck: played_only ? 'on' : nil,
      }.compact,
      response_page: Page::MusicList,
    )
  }
  # do not use web_id to compare
  # web_id differs per source filter
  get_id = ->(chart_info) {
    [chart_info.info.type, chart_info.info.title].join(':')
  }

  # this is potentially adding unnecessary overhead for sorting everything first
  create_sort_indices = ->(ary) {
    # index 0 is always unique
    # index 1 or higher is based on sort rank, nullity gives lowest value automatically
    indices = Array.new(1 + MaimaiNet::BestScoreSortType::LIBRARY.size) do |j|
      ary.map.each_with_index do |best_info, i| [best_info.object_id, j.positive? ? ary.size : i] end.to_h
    end

    assign_ranks = ->(high_index:, low_index:) {
      ->(sorted) {
        sorted.each_with_index do |best_info, rank|
          high_rank = sorted.size - (rank + 1)
          low_rank  = rank

          indices[high_index][best_info.object_id] = high_rank
          indices[low_index][best_info.object_id]  = low_rank
        end
      }
    }

    ary.reject do |best_info| best_info.score.nil? end
      .tap do |played_ary|
        played_ary.sort_by do |best_info|
          best_info.score.score
        end.tap &assign_ranks.call(high_index: 1, low_index: 2)

        played_ary.sort_by do |best_info|
          dx = best_info.score.deluxe_score
          dx.max.positive? ? Rational(dx.value, dx.max) : 0
        end.tap &assign_ranks.call(high_index: 3, low_index: 4)

        combo_grades = i(AP+ AP FC+ FC)
        played_ary.sort_by do |best_info|
          flags = best_info.score.flags
          combo_grades.find_index do |flag| flags.include?(flag) end
            .yield_self do |rank| rank.nil? ? combo_grades.size : rank end
        end.tap &assign_ranks.call(high_index: 6, low_index: 5)
      end

    indices
  }

  head_values.as_unique_array.inject({}) do |result, search_value|
    diffs.inject({}) do |diff_result, diff_value|
      response = send.call(search_value, diff_value)
      response = {} if response.empty?

      diff_result.update(response, &quick_concat)
    end.yield_self do |diff_result|
      result.update(diff_result, &quick_concat)
    end
  end.yield_self do |result|
    ids = result.values.inject([], :concat)
                .map(&get_id)

    processed_filters.inject(ids) do |filter_result, search_values|
      break filter_result if filter_result.empty?

      search_values.inject([]) do |search_result, search_value|
        nil.yield_self do
          search_value.start_with?('L') ?
            diffs :
            diffs.min
        end.as_unique_array.inject([]) do |diff_result, diff_value|
          response = send.call(search_value, diff_value)
          response = response.values.inject([], :concat) if Hash === response
          diff_result.concat response
        end.yield_self do |diff_result|
          diff_result.map(&get_id)
        end.yield_self &search_result.method(:union)
      end.yield_self &filter_result.method(:intersection)
    end.yield_self do |filtered_ids|
      result.transform_values! do |category_info_list|
        category_info_list.select do |category_info|
          filtered_ids.include?(get_id.call(category_info))
        end
      end.transform_values! do |category_info_list|
        flat_indices = create_sort_indices.call(category_info_list)
          .values_at(sort, 0).yield_self do |sort_indices|
            head_indices = sort_indices.first.keys
            head_indices.map do |k|
              [k, sort_indices.map do |h| h[k] end]
            end.to_h
          end

        category_info_list.sort_by do |category_info|
          flat_indices[category_info.object_id]
        end
      end
    end
  end
end

#song_list_by_genre(genres:, diffs:) ⇒ Object



562
563
564
565
566
567
568
569
# File 'lib/maimai_net/client.rb', line 562

def song_list_by_genre(genres:, diffs:)
  map_product_combine_result(genres, diffs) do |genre, diff|
    assert_parameter :diff,  diff,  0..4, 10
    assert_parameter :genre, genre, 99, 101..106

    song_list :Genre, genre: genre, diff: diff
  end
end

#song_list_by_level(levels:) ⇒ Object



580
581
582
583
584
585
586
# File 'lib/maimai_net/client.rb', line 580

def song_list_by_level(levels:)
  levels.as_unique_array.map do |level|
    assert_parameter :level, level, 1..6, 7..23

    song_list :Level, level: level
  end
end

#song_list_by_title(characters:, diffs:) ⇒ Object



571
572
573
574
575
576
577
578
# File 'lib/maimai_net/client.rb', line 571

def song_list_by_title(characters:, diffs:)
  map_product_combine_result(characters, diffs) do |character, diff|
    assert_parameter :diff,      diff,      0..4
    assert_parameter :character, character, 0..15

    song_list :Word, character: character, diff: diff
  end
end

#song_list_by_version(versions:, diffs:) ⇒ Object



588
589
590
591
592
593
594
595
# File 'lib/maimai_net/client.rb', line 588

def song_list_by_version(versions:, diffs:)
  map_product_combine_result(versions, diffs) do |version, diff|
    assert_parameter :diff,    diff,    0..4
    assert_parameter :version, version, 0..23

    song_list :Version, version: version, diff: diff
  end
end