diff --git a/src/search/spotify.cr b/src/search/spotify.cr index edfe811..cb5f847 100644 --- a/src/search/spotify.cr +++ b/src/search/spotify.cr @@ -42,26 +42,6 @@ class SpotifySearcher return self end - # Searches spotify and returns track metadata in `Hash` format. - # - # ``` - # spotify_searcher.find_track("Bohemian Rhapsody", "Queen") - # => hash of metadata - # ``` - def find_track(track_name : String, artist_name : String) - - query = "track:#{track_name.sub(" ", "+")}+" + - "artist:#{artist_name.sub(" ", "+")}" + - "&type=track" - - url = @root_url.join("search?q=#{query}").to_s() - - response = HTTP::Client.get(url, headers: @access_header) - - puts response.body - - end - # Searches spotify with the specified parameters for the specified items # # ``` @@ -69,14 +49,11 @@ class SpotifySearcher # "artist" => "Queen", # "track" => "Bohemian Rhapsody" # }) + # => {track metadata} # ``` - def find_item(item_type : String, item_parameters : Hash) - query = "" + def find_item(item_type : String, item_parameters : Hash, offset=0, limit=20) - item_parameters.keys.each do |i| - query += "#{i.sub(" ", "+")}:#{item_parameters[i].sub(" ", "+")}+" - end - query += "&type=#{item_type}" + query = __generate_query(item_type, item_parameters, offset, limit) url = @root_url.join("search?q=#{query}").to_s() @@ -84,21 +61,125 @@ class SpotifySearcher if response.status_code != 200 puts "There was an error with your request." - puts "Status code #{response.status_code}" + puts "Status code: #{response.status_code}" + puts "Reponse: \n#{response.body}" return nil end - items = JSON.parse(response.body)[item_type + "s"] + items = JSON.parse(response.body)[item_type + "s"]["items"].as_a + points = __rank_items(items, item_parameters) + + return items[points[0][1]] + + end + + # Generates url to run a GET request against + private def __generate_query(item_type : String, item_parameters : Hash, + offset : Int32, limit : Int32) + query = "" + + # parameter keys to exclude in the api request. These values will be put + # in, just not their keys. + query_exclude = ["username"] + + item_parameters.keys.each do |k| + # This will map album, playlist, and track from the name key to the query + if k == "name" + query += __param_encode(item_type, item_parameters[k]) + + # check if the key is to be excluded + elsif !query_exclude.includes?(k) + query += item_parameters[k].gsub(" ", "+") + "+" + + # if it's none of the above, treat it normally + else + query += __param_encode(k, item_parameters[k]) + end + end + + # extra api info + query += "&type=#{item_type}&offset=#{offset}&limit=#{limit}" + + return query + end + + # Ranks the given items based off of the info from parameters. + # Meant to find the item that the user desires. + private def __rank_items(items : Array, parameters : Hash) + points = [] of Array(Int32) + index = 0 + + items.each do |item| + pts = 0 + + # Think about whether this following logic is worth having in one method. + # Is it nice to have a single method that handles it all or having a few + # methods for each of the item types? (track, album, playlist) + parameters.keys.each do |k| + val = parameters[k] + + # The key to compare to for artist + if k == "artist" + pts += __points_compare(item["artists"][0]["name"].to_s, val) + end + + # The key to compare to for playlists + if k == "username" + pts += __points_compare(item["owner"]["display_name"].to_s, val) + end + + # The key regardless of whether item is track, album,or playlist + if k == "name" + pts += __points_compare(item["name"].to_s, val) + end + end + + points.push([pts, index]) + index += 1 + end + + points.sort!{ |a, b| b[0] <=> a[0] } + + return points + end + + # Returns an `Int` based off the number of points worth assigning to the + # matchiness of the string. First the strings are downcased and then all + # nonalphanumeric characters are stripped. + # If the strings are the exact same, return 3 pts. + # If *item1* includes *item2*, return 1 pt. + # Else, return 0 pts. + private def __points_compare(item1 : String, item2 : String) + item1 = item1.downcase.gsub(/[^a-z0-9]/, "") + item2 = item2.downcase.gsub(/[^a-z0-9]/, "") + + if item1 == item2 + return 3 + elsif item1.includes?(item2) + return 1 + else + return 0 + end + end + + # Returns a parameter encoded for the spotify api + # + # ``` + # __query_encode("album", "A Night At The Opera") + # => "album:A+Night+At+The+Opera" + # ``` + private def __param_encode(key : String, value : String) + return key.gsub(" ", "+") + ":" + value.gsub(" ", "+") + "+" end end -SpotifySearcher.new +puts SpotifySearcher.new() .authorize("e4198f6a3f7b48029366f22528b5dc66", "ba057d0621a5496bbb64edccf758bde5") .find_item("track", { - "artist" => "Queen", - "track" => "Bohemian Rhapsody" + "name" => "Bohemian Rhapsody", + "artist" => "Queen" }) \ No newline at end of file