bih/spotify-ruby

View on GitHub
lib/spotify/sdk/me.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Spotify
  class SDK
    class Me < Base
      ##
      # Get the current user's information.
      # Respective information requires the `user-read-private user-read-email user-read-birthdate` scopes.
      # GET /v1/me
      #
      # @example
      #   me = @sdk.me.info
      #
      # @see https://developer.spotify.com/console/get-current-user/
      # @see https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/
      #
      # @param [Hash] override_opts Custom options for HTTParty.
      # @return [Spotify::SDK::Me::Info] user_info Return the user's information.
      #
      def info(override_opts={})
        me_info = send_http_request(:get, "/v1/me", override_opts)
        Spotify::SDK::Me::Info.new(me_info, self)
      end

      ##
      # Check what tracks a user has recently played.
      #
      # @example
      #   @sdk.me.history
      #   @sdk.me.history(20)
      #
      # @param [Integer] limit How many results to request. Defaults to 10.
      # @param [Hash] override_opts Custom options for HTTParty.
      # @return [Array] response List of recently played tracked, in chronological order.
      #
      def history(limit=10, override_opts={})
        request = {
          method:    :get,
          http_path: "/v1/me/player/recently-played",
          keys:      %i[items],
          limit:     limit
        }

        send_multiple_http_requests(request, override_opts).map do |item|
          Spotify::SDK::Item.new(item, self)
        end
      end

      ##
      # Check if the current user is following N users.
      #
      # @example
      #   artists = %w(3q7HBObVc0L8jNeTe5Gofh 0NbfKEOTQCcwd6o7wSDOHI 3TVXtAsR1Inumwj472S9r4)
      #   @sdk.me.following?(artists, :artist)
      #     # => {"3q7HBObVc0L8jNeTe5Gofh" => false, "0NbfKEOTQCcwd6o7wSDOHI" => false, ...}
      #
      #   users = %w(3q7HBObVc0L8jNeTe5Gofh 0NbfKEOTQCcwd6o7wSDOHI 3TVXtAsR1Inumwj472S9r4)
      #   @sdk.me.following?(users, :user)
      #     # => {"3q7HBObVc0L8jNeTe5Gofh" => false, "0NbfKEOTQCcwd6o7wSDOHI" => false, ...}
      #
      # @param [Array] list List of Spotify user/artist IDs. Cannot mix user and artist IDs in single request.
      # @param [Symbol] type Either :user or :artist. Checks if follows respective type of account.
      # @param [Hash] override_opts Custom options for HTTParty.
      # @return [Hash] hash A hash containing a key with the ID, and a value that equals is_following (boolean).
      #
      def following?(list, type=:artist, override_opts={})
        raise "Must contain an array" unless list.is_a?(Array)
        raise "Must contain an array of String or Spotify::SDK::Artist" if any_of?(list, [String, Spotify::SDK::Artist])
        raise "type must be either 'artist' or 'user'" unless %i[artist user].include?(type)

        send_is_following_http_requests(list.map {|id| id.try(:id) || id }, type, override_opts)
      end

      def following_artists?(list, override_opts={})
        following?(list, :artist, override_opts)
      end

      def following_users?(list, override_opts={})
        following?(list, :user, override_opts)
      end

      ##
      # Get the current user's followed artists. Requires the `user-read-follow` scope.
      # GET /v1/me/following
      #
      # @example
      #   @sdk.me.following
      #
      # @param [Integer] n Number of results to return.
      # @param [Hash] override_opts Custom options for HTTParty.
      # @return [Array] artists A list of followed artists, wrapped in Spotify::SDK::Artist
      #
      def following(limit=50, override_opts={})
        request = {
          method:    :get,
          # TODO: Spotify API bug - `limit={n}` returns n-1 artists.
          # ^ Example: `limit=5` returns 4 artists.
          # TODO: Support `type=users` as well as `type=artists`.
          http_path: "/v1/me/following?type=artist&limit=#{[limit, 50].min}",
          keys:      %i[artists items],
          limit:     limit
        }

        send_multiple_http_requests(request, override_opts).map do |artist|
          Spotify::SDK::Artist.new(artist, self)
        end
      end

      private

      def any_of?(array, klasses) # :nodoc:
        (array.map(&:class) - klasses).any?
      end

      def send_multiple_http_requests(opts, override_opts) # :nodoc:
        response = send_http_request(opts[:method], opts[:http_path], override_opts)
        responses, next_request = hash_deep_lookup(response, opts[:keys].dup)
        if next_request && responses.size < opts[:limit]
          responses += send_multiple_http_requests(opts.merge(http_path: next_request), override_opts)
        end
        responses.first(opts[:limit])
      end

      def hash_deep_lookup(response, keys) # :nodoc:
        error_message = "Cannot find '%s' key in Spotify::SDK::Me#hash_deep_lookup"
        while keys.any?
          next_request ||= response[:next]
          next_key = keys.shift
          response = next_key ? response[next_key] : raise(error_message % next_key)
        end
        [response, next_request ? next_request[23..-1] : nil]
      end

      # TODO: Migrate this into the abstracted send_multiple_http_requests
      def send_is_following_http_requests(list, type, override_opts) # :nodoc:
        max_ids = list.first(50)
        remaining_ids = list - max_ids

        ids = max_ids.map {|id| {id.strip => nil} }.inject(&:merge)
        following = send_http_request(
          :get,
          "/v1/me/following/contains?type=%s&ids=%s" % [type, ids.keys.join(",")],
          override_opts
        )
        ids.each_key {|id| ids[id] = following.shift }

        # rubocop:disable Style/IfUnlessModifier
        if remaining_ids.any?
          ids.merge(send_is_following_http_requests(remaining_ids, type, override_opts))
        end || ids
        # rubocop:enable Style/IfUnlessModifier
      end
    end
  end
end