diff --git a/src/bottle/cli.cr b/src/bottle/cli.cr index d95b7c0..2cbf378 100755 --- a/src/bottle/cli.cr +++ b/src/bottle/cli.cr @@ -8,9 +8,7 @@ require "../glue/song" require "../glue/album" require "../glue/playlist" - class CLI - # layout: # [[shortflag, longflag], key, type] @options = [ @@ -21,10 +19,9 @@ class CLI [["-a", "--artist"], "artist", "string"], [["-s", "--song"], "song", "string"], [["-A", "--album"], "album", "string"], - [["-p", "--playlist"], "playlist", "string"] + [["-p", "--playlist"], "playlist", "string"], ] - @args : Hash(String, String) def initialize(argv : Array(String)) @@ -68,8 +65,7 @@ class CLI end def act_on_args - - Config.check_necessities() + Config.check_necessities if @args["help"]? || @args.keys.size == 0 help @@ -86,17 +82,17 @@ class CLI elsif @args["song"]? && @args["artist"]? s = Song.new(@args["song"], @args["artist"]) s.provide_client_keys(Config.client_key, Config.client_secret) - s.grab_it() + s.grab_it s.organize_it(Config.music_directory) exit elsif @args["album"]? && @args["artist"]? a = Album.new(@args["album"], @args["artist"]) a.provide_client_keys(Config.client_key, Config.client_secret) - a.grab_it() + a.grab_it elsif @args["playlist"]? && @args["artist"]? p = Playlist.new(@args["playlist"], @args["artist"]) p.provide_client_keys(Config.client_key, Config.client_secret) - p.grab_it() + p.grab_it else puts Style.red("Those arguments don't do anything when used that way.") puts "Type `irs -h` to see usage." @@ -111,12 +107,11 @@ class CLI current_key = "" pass_next_arg = false argv.each do |arg| - # If the previous arg was an arg flag, this is an arg, so pass it - if pass_next_arg + if pass_next_arg pass_next_arg = false i += 1 - next + next end flag = [] of Array(String) | String @@ -140,7 +135,6 @@ class CLI arg_error argv, i, %("#{arg}" needs an argument.) end - key = flag[1].as(String) if flag[2] == "string" arguments[key] = argv[i + 1] @@ -182,4 +176,4 @@ class CLI puts "Type `irs -h` to see usage." exit 1 end -end \ No newline at end of file +end diff --git a/src/bottle/config.cr b/src/bottle/config.cr index 866f46f..ddf3a07 100755 --- a/src/bottle/config.cr +++ b/src/bottle/config.cr @@ -2,7 +2,6 @@ require "yaml" require "./styles" - EXAMPLE_CONFIG = <<-EOP #{Style.dim "exampleconfig.yml"} #{Style.dim "===="} @@ -18,17 +17,16 @@ single_folder_playlist: EOP module Config - extend self @@arguments = [ - "binary_directory", - "music_directory", - "client_key", + "binary_directory", + "music_directory", + "client_key", "client_secret", - "single_folder_playlist: enabled", - "single_folder_playlist: retain_playlist_order", - "single_folder_playlist: overwrite_album" + "single_folder_playlist: enabled", + "single_folder_playlist: retain_playlist_order", + "single_folder_playlist: overwrite_album", ] @@conf = YAML.parse("") @@ -69,14 +67,14 @@ module Config def overwrite_album? : Bool return @@conf["single_folder_playlist"]["overwrite_album"].as_bool - end + end def check_necessities missing_configs = [] of String @@arguments.each do |argument| if !check_conf(argument) missing_configs.push(argument) - end + end end if missing_configs.size > 0 puts Style.red("You are missing the following key(s) in your YAML config file:") @@ -102,4 +100,4 @@ module Config return @@conf[key]? end end -end \ No newline at end of file +end diff --git a/src/bottle/styles.cr b/src/bottle/styles.cr index a474f37..a79928d 100755 --- a/src/bottle/styles.cr +++ b/src/bottle/styles.cr @@ -20,4 +20,4 @@ class Style def self.red(txt) txt.colorize(:light_red) end -end \ No newline at end of file +end diff --git a/src/bottle/version.cr b/src/bottle/version.cr index 7d6cb37..d9b75dc 100755 --- a/src/bottle/version.cr +++ b/src/bottle/version.cr @@ -1,3 +1,3 @@ module IRS VERSION = "0.1.0" -end \ No newline at end of file +end diff --git a/src/glue/album.cr b/src/glue/album.cr index e7281b0..94a140c 100755 --- a/src/glue/album.cr +++ b/src/glue/album.cr @@ -4,17 +4,15 @@ require "./mapper" require "./song" require "./list" - class Album < SpotifyList - @home_music_directory = Config.music_directory # Uses the `spotify_searcher` defined in parent `SpotifyList` to find the - # correct metadata of the list + # correct metadata of the list def find_it album = @spotify_searcher.find_item("album", { - "name" => @list_name.as(String), - "artist" => @list_author.as(String) + "name" => @list_name.as(String), + "artist" => @list_author.as(String), }) if album return album.as(JSON::Any) @@ -24,7 +22,7 @@ class Album < SpotifyList end end - # Will define specific metadata that may not be included in the raw return + # Will define specific metadata that may not be included in the raw return # of spotify's album json. Moves the title of the album and the album art # to the json of the single song def organize_song_metadata(list : JSON::Any, datum : JSON::Any) : JSON::Any diff --git a/src/glue/list.cr b/src/glue/list.cr index 44664bf..7a233f5 100755 --- a/src/glue/list.cr +++ b/src/glue/list.cr @@ -8,10 +8,9 @@ require "../interact/tagger" require "./song" - # A parent class for downloading albums and playlists from spotify abstract class SpotifyList - @spotify_searcher = SpotifySearcher.new() + @spotify_searcher = SpotifySearcher.new @file_names = [] of String def initialize(@list_name : String, @list_author : String?) @@ -19,7 +18,6 @@ abstract class SpotifyList # Finds the list, and downloads all of the songs using the `Song` class def grab_it - if !@spotify_searcher.authorized? raise("Need to call provide_client_keys on Album or Playlist class.") end @@ -38,7 +36,7 @@ abstract class SpotifyList song = Song.new(data["name"].to_s, data["artists"][0]["name"].to_s) song.provide_spotify(@spotify_searcher) song.provide_metadata(data) - song.grab_it() + song.grab_it organize(song) @@ -57,12 +55,11 @@ abstract class SpotifyList # If there's a need to organize the individual song data so that the `Song` # class can better handle it, this function will be defined in the subclass - private abstract def organize_song_metadata(list : JSON::Any, - datum : JSON::Any) : JSON::Any + private abstract def organize_song_metadata(list : JSON::Any, + datum : JSON::Any) : JSON::Any # Will define the specific type of organization for a list of songs. # Needed because most people want albums sorted by artist, but playlists all # in one folder private abstract def organize(song : Song) - -end \ No newline at end of file +end diff --git a/src/glue/mapper.cr b/src/glue/mapper.cr index ab4cc71..9a658ad 100755 --- a/src/glue/mapper.cr +++ b/src/glue/mapper.cr @@ -3,8 +3,8 @@ require "json" class PlaylistExtensionMapper JSON.mapping( tracks: { - type: PlaylistTracksMapper, - setter: true + type: PlaylistTracksMapper, + setter: true, }, id: String, images: JSON::Any, @@ -17,8 +17,8 @@ end class PlaylistTracksMapper JSON.mapping( items: { - type: Array(JSON::Any), - setter: true + type: Array(JSON::Any), + setter: true, }, total: Int32 ) @@ -26,10 +26,10 @@ end class AlbumTracksMapper JSON.mapping( - album: { - type: JSON::Any, + album: { + type: JSON::Any, nilable: true, - setter: true + setter: true, }, artists: JSON::Any, disc_number: Int32, @@ -41,7 +41,6 @@ class AlbumTracksMapper ) end - def parse_to_json(string_json : String) : JSON::Any return JSON.parse(string_json) -end \ No newline at end of file +end diff --git a/src/glue/playlist.cr b/src/glue/playlist.cr index 2ec048d..cb3ead1 100755 --- a/src/glue/playlist.cr +++ b/src/glue/playlist.cr @@ -3,18 +3,16 @@ require "../bottle/config" require "./song" require "./list" - class Playlist < SpotifyList - @home_music_directory = Config.music_directory @playlist : JSON::Any? # Uses the `spotify_searcher` defined in parent `SpotifyList` to find the - # correct metadata of the list + # correct metadata of the list def find_it @playlist = @spotify_searcher.find_item("playlist", { - "name" => @list_name.as(String), - "username" => @list_author.as(String) + "name" => @list_name.as(String), + "username" => @list_author.as(String), }) if @playlist return @playlist.as(JSON::Any) @@ -24,7 +22,7 @@ class Playlist < SpotifyList end end - # Will define specific metadata that may not be included in the raw return + # Will define specific metadata that may not be included in the raw return # of spotify's album json. Moves the title of the album and the album art # to the json of the single song def organize_song_metadata(list : JSON::Any, datum : JSON::Any) : JSON::Any diff --git a/src/glue/song.cr b/src/glue/song.cr index 23f9f98..9d7ea06 100755 --- a/src/glue/song.cr +++ b/src/glue/song.cr @@ -4,9 +4,8 @@ require "../search/youtube" require "../interact/ripper" require "../interact/tagger" - class Song - @spotify_searcher = SpotifySearcher.new() + @spotify_searcher = SpotifySearcher.new @client_id = "" @client_secret = "" @@ -21,29 +20,29 @@ class Song # Find, downloads, and tags the mp3 song that this class represents. # # ``` - # Song.new("Bohemian Rhapsody", "Queen").grab_it() - # ``` + # Song.new("Bohemian Rhapsody", "Queen").grab_it + # ``` def grab_it if !@spotify_searcher.authorized? && !@metadata if @client_id != "" && @client_secret != "" @spotify_searcher.authorize(@client_id, @client_secret) else raise("Need to call either `provide_metadata`, `provide_spotify`, " + - "or `provide_client_keys` so that Spotify can be interfaced with.") + "or `provide_client_keys` so that Spotify can be interfaced with.") end end if !@metadata puts "Searching for metadata ..." @metadata = @spotify_searcher.find_item("track", { - "name" => @song_name, - "artist" => @artist_name + "name" => @song_name, + "artist" => @artist_name, }) if !@metadata - raise("There was no metadata found on Spotify for\n" + - %("#{@song_name}" by "#{@artist_name}\n) + - "Check your input and try again.") + raise("There was no metadata found on Spotify for\n" + + %("#{@song_name}" by "#{@artist_name}\n) + + "Check your input and try again.") end end @@ -54,12 +53,12 @@ class Song # TODO: should this search_term be here? url = Youtube.find_url(@song_name, @artist_name, search_terms: "lyrics") - if !url - raise("There was no url found on youtube for\n" + - %("#{@song_name}" by "#{@artist_name}\n) + - "Check your input and try again.") + if !url + raise("There was no url found on youtube for\n" + + %("#{@song_name}" by "#{@artist_name}\n) + + "Check your input and try again.") end - + puts "Downloading video:" Ripper.download_mp3(url.as(String), @filename) @@ -76,27 +75,26 @@ class Song tagger.add_text_tag("title", data["name"].to_s) tagger.add_text_tag("artist", @artist) tagger.add_text_tag("album", @album) - tagger.add_text_tag("genre", + tagger.add_text_tag("genre", @spotify_searcher.find_genre(data["artists"][0]["id"].to_s)) tagger.add_text_tag("track", data["track_number"].to_s) tagger.add_text_tag("disc", data["disc_number"].to_s) puts "Tagging metadata ..." - tagger.save() + tagger.save File.delete(temp_albumart_filename) puts %("#{data["name"].to_s}" by "#{data["artists"][0]["name"].to_s}" downloaded.) - end - # Will organize the song into the user's provided music directory as + # Will organize the song into the user's provided music directory as # music_directory > artist_name > album_name > song # Must be called AFTER the song has been downloaded. # # ``` - # s = Song.new("Bohemian Rhapsody", "Queen").grab_it() + # s = Song.new("Bohemian Rhapsody", "Queen").grab_it # s.organize_it("/home/cooper/Music") - # # Will move the mp3 file to + # # Will move the mp3 file to # # /home/cooper/Music/Queen/A Night At The Opera/1 - Bohemian Rhapsody.mp3 # ``` def organize_it(music_directory : String) @@ -116,20 +114,20 @@ class Song # called. # # ``` - # Song.new(...).provide_metadata(...).grab_it() + # Song.new(...).provide_metadata(...).grab_it # ``` def provide_metadata(metadata : JSON::Any) : self @metadata = metadata return self end - # Provide an already authenticated `SpotifySearcher` class. Useful to avoid + # Provide an already authenticated `SpotifySearcher` class. Useful to avoid # authenticating over and over again. Must be called if provide_metadata and # provide_client_keys are not called. # # ``` - # Song.new(...).provide_spotify(SpotifySearcher.new() - # .authenticate("XXXXXXXXXX", "XXXXXXXXXXX")).grab_it() + # Song.new(...).provide_spotify(SpotifySearcher.new + # .authenticate("XXXXXXXXXX", "XXXXXXXXXXX")).grab_it # ``` def provide_spotify(spotify : SpotifySearcher) : self @spotify_searcher = spotify @@ -140,11 +138,11 @@ class Song # provide_spotify are not called. # # ``` - # Song.new(...).provide_client_keys("XXXXXXXXXX", "XXXXXXXXX").grab_it() + # Song.new(...).provide_client_keys("XXXXXXXXXX", "XXXXXXXXX").grab_it # ``` def provide_client_keys(client_id : String, client_secret : String) : self @client_id = client_id @client_secret = client_secret return self end -end \ No newline at end of file +end diff --git a/src/interact/logger.cr b/src/interact/logger.cr index a2cfc4f..3f21acd 100755 --- a/src/interact/logger.cr +++ b/src/interact/logger.cr @@ -1,5 +1,4 @@ class Logger - @done_signal = "---DONE---" @command : String @@ -11,16 +10,16 @@ class Logger def initialize(command : String, @log_name : String, @sleept = 0.01) # Have the command output its information to a log and after the command is # finished, append an end signal to the document - @command = "#{command} > #{@log_name} " # standard output to log - @command += "2> #{@log_name} && " # errors to log - @command += "echo #{@done_signal} >> #{@log_name}" # + @command = "#{command} > #{@log_name} " # standard output to log + @command += "2> #{@log_name} && " # errors to log + @command += "echo #{@done_signal} >> #{@log_name}" # end # Run @command in the background and pipe its output to the log file, with # something constantly monitoring the log file and yielding each new line to - # the block call. Useful for changing the output of binaries you don't have + # the block call. Useful for changing the output of binaries you don't have # much control over. - # Note that the created temp log will be deleted unless the command fails + # Note that the created temp log will be deleted unless the command fails # its exit or .start is called with delete_file: false # # ``` @@ -34,7 +33,7 @@ class Logger # end # end # ``` - def start(delete_file=true, &block) : Bool + def start(delete_file = true, &block) : Bool # Delete the log if it already exists File.delete(@log_name) if File.exists?(@log_name) @@ -44,7 +43,7 @@ class Logger } # Wait for the log file to be written to - while !File.exists?(@log_name) + while !File.exists?(@log_name) sleep @sleept end @@ -68,12 +67,12 @@ class Logger end end - status = called.get() - if status == true && delete_file == true - log.delete() + status = called.get + if status == true && delete_file == true + log.delete end - return called.get() + return called.get end # Reads each line of the file into an Array of Strings @@ -86,4 +85,4 @@ class Logger return content end -end \ No newline at end of file +end diff --git a/src/interact/ripper.cr b/src/interact/ripper.cr index 24e3607..35e84e0 100755 --- a/src/interact/ripper.cr +++ b/src/interact/ripper.cr @@ -2,7 +2,6 @@ require "./logger" require "../bottle/config" module Ripper - extend self BIN_LOC = Path[Config.binary_location] @@ -11,23 +10,23 @@ module Ripper # Will create any directories that don't exist specified in *output_filename* # # ``` - # Ripper.download_mp3("https://youtube.com/watch?v=0xnciFWAqa0", + # Ripper.download_mp3("https://youtube.com/watch?v=0xnciFWAqa0", # "Queen/A Night At The Opera/Bohemian Rhapsody.mp3") # ``` def download_mp3(video_url : String, output_filename : String) ydl_loc = BIN_LOC.join("youtube-dl") - + # remove the extension that will be added on by ydl output_filename = output_filename.split(".")[..-2].join(".") options = { "--output" => %("#{output_filename}.%(ext)s"), # auto-add correct ext # "--quiet" => "", - "--verbose" => "", + "--verbose" => "", "--ffmpeg-location" => BIN_LOC, - "--extract-audio" => "", - "--audio-format" => "mp3", - "--audio-quality" => "0", + "--extract-audio" => "", + "--audio-format" => "mp3", + "--audio-quality" => "0", } command = ydl_loc.to_s + " " + video_url @@ -35,7 +34,6 @@ module Ripper command += " #{option} #{options[option]}" end - l = Logger.new(command, ".ripper.log") o = RipperOutputCensor.new @@ -64,6 +62,5 @@ module Ripper end end end - end -end \ No newline at end of file +end diff --git a/src/interact/tagger.cr b/src/interact/tagger.cr index 3dde48e..3f4ab7e 100755 --- a/src/interact/tagger.cr +++ b/src/interact/tagger.cr @@ -5,10 +5,9 @@ require "../bottle/config" # t = Tags.new("bohem rap.mp3") # t.add_album_art("a night at the opera album cover.jpg") # t.add_text_tag("title", "Bohemian Rhapsody") -# t.save() +# t.save # ``` class Tags - # TODO: export this path to a config file @BIN_LOC = Config.binary_location @query_args = [] of String @@ -20,7 +19,6 @@ class Tags end @query_args.push(%(-i "#{@filename}")) - end # Add album art to the mp3. Album art must be added BEFORE text tags are. @@ -52,7 +50,7 @@ class Tags l = Logger.new(command, ".tagger.log") l.start { |line, start| } - + File.delete(@filename) File.rename("_" + @filename, @filename) end @@ -61,4 +59,4 @@ end # a = Tags.new("test.mp3") # a.add_text_tag("title", "Warwick Avenue") # a.add_album_art("file.png") -# a.save() \ No newline at end of file +# a.save() diff --git a/src/irs.cr b/src/irs.cr index 790e351..f57fb4b 100755 --- a/src/irs.cr +++ b/src/irs.cr @@ -2,7 +2,7 @@ require "./bottle/cli" def main cli = CLI.new(ARGV) - cli.act_on_args() + cli.act_on_args end -main() \ No newline at end of file +main() diff --git a/src/search/spotify.cr b/src/search/spotify.cr index 1d2d4c7..c1ea10e 100755 --- a/src/search/spotify.cr +++ b/src/search/spotify.cr @@ -11,18 +11,18 @@ class SpotifySearcher @authorized = false # Saves an access token for future program use with spotify using client IDs. - # Specs defined on spotify's developer api: + # Specs defined on spotify's developer api: # https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow # # ``` - # SpotifySearcher.new().authorize("XXXXXXXXXX", "XXXXXXXXXX") + # SpotifySearcher.new.authorize("XXXXXXXXXX", "XXXXXXXXXX") # ``` def authorize(client_id : String, client_secret : String) : self auth_url = "https://accounts.spotify.com/api/token" headers = HTTP::Headers{ - "Authorization" => "Basic " + - Base64.strict_encode "#{client_id}:#{client_secret}" + "Authorization" => "Basic " + + Base64.strict_encode "#{client_id}:#{client_secret}", } payload = "grant_type=client_credentials" @@ -31,9 +31,9 @@ class SpotifySearcher error_check(response) access_token = JSON.parse(response.body)["access_token"] - + @access_header = HTTP::Headers{ - "Authorization" => "Bearer #{access_token}" + "Authorization" => "Bearer #{access_token}", } @authorized = true @@ -55,12 +55,11 @@ class SpotifySearcher # }) # => {track metadata} # ``` - def find_item(item_type : String, item_parameters : Hash, offset=0, - limit=20) : JSON::Any? - + def find_item(item_type : String, item_parameters : Hash, offset = 0, + limit = 20) : JSON::Any? query = generate_query(item_type, item_parameters, offset, limit) - url = @root_url.join("search?q=#{query}").to_s() + url = @root_url.join("search?q=#{query}").to_s response = HTTP::Client.get(url, headers: @access_header) error_check(response) @@ -74,7 +73,7 @@ class SpotifySearcher begin # this means no points were assigned so don't return the "best guess" if points[0][0] <= 0 - to_return = nil + to_return = nil else to_return = get_item(item_type, items[points[0][1]]["id"].to_s) end @@ -82,15 +81,15 @@ class SpotifySearcher to_return = nil end - # if this triggers, it means that a playlist has failed to be found, so + # if this triggers, it means that a playlist has failed to be found, so # the search will be bootstrapped into find_user_playlist if to_return == nil && item_type == "playlist" return find_user_playlist( - item_parameters["username"], + item_parameters["username"], item_parameters["name"] ) end - + return to_return end @@ -100,9 +99,8 @@ class SpotifySearcher # spotify_searcher.find_user_playlist("prakkillian", "the little man") # => {playlist metadata} # ``` - def find_user_playlist(username : String, name : String, offset=0, - limit=20) : JSON::Any? - + def find_user_playlist(username : String, name : String, offset = 0, + limit = 20) : JSON::Any? url = "users/#{username}/playlists?limit=#{limit}&offset=#{offset}" url = @root_url.join(url).to_s @@ -116,14 +114,14 @@ class SpotifySearcher items.as_a.each_index do |i| points.push([points_compare(items[i]["name"].to_s, name), i]) end - points.sort!{ |a, b| b[0] <=> a[0] } + points.sort! { |a, b| b[0] <=> a[0] } begin if points[0][0] < 3 return find_user_playlist(username, name, offset + limit, limit) else return get_item("playlist", items[points[0][1]]["id"].to_s) - end + end rescue IndexError return nil end @@ -132,18 +130,17 @@ class SpotifySearcher # Get the complete metadata of an item based off of its id # # ``` - # SpotifySearcher.new().authorize(...).get_item("artist", "1dfeR4HaWDbWqFHLkxsg1d") + # SpotifySearcher.new.authorize(...).get_item("artist", "1dfeR4HaWDbWqFHLkxsg1d") # ``` - def get_item(item_type : String, id : String, offset=0, - limit=100) : JSON::Any - + def get_item(item_type : String, id : String, offset = 0, + limit = 100) : JSON::Any if item_type == "playlist" return get_playlist(id, offset, limit) end url = "#{item_type}s/#{id}?limit=#{limit}&offset=#{offset}" - url = @root_url.join(url).to_s() - + url = @root_url.join(url).to_s + response = HTTP::Client.get(url, headers: @access_header) error_check(response) @@ -156,12 +153,12 @@ class SpotifySearcher # insert ALL tracks from the playlist into the `JSON::Any` # # ``` - # SpotifySearcher.new().authorize(...).get_playlist("122Fc9gVuSZoksEjKEx7L0") + # SpotifySearcher.new.authorize(...).get_playlist("122Fc9gVuSZoksEjKEx7L0") # ``` - def get_playlist(id, offset=0, limit=100) : JSON::Any + def get_playlist(id, offset = 0, limit = 100) : JSON::Any url = "playlists/#{id}?limit=#{limit}&offset=#{offset}" - url = @root_url.join(url).to_s() - + url = @root_url.join(url).to_s + response = HTTP::Client.get(url, headers: @access_header) error_check(response) body = JSON.parse(response.body) @@ -169,7 +166,7 @@ class SpotifySearcher more_tracks = body["tracks"]["total"].as_i > offset + limit if more_tracks - return playlist_extension(parent, id, offset=offset + limit) + return playlist_extension(parent, id, offset = offset + limit) end return body @@ -177,10 +174,10 @@ class SpotifySearcher # This method exists to loop through spotify API requests and combine all # tracks that may not be captured by the limit of 100. - private def playlist_extension(parent : PlaylistExtensionMapper, - id : String, offset=0, limit=100) : JSON::Any + private def playlist_extension(parent : PlaylistExtensionMapper, + id : String, offset = 0, limit = 100) : JSON::Any url = "playlists/#{id}/tracks?limit=#{limit}&offset=#{offset}" - url = @root_url.join(url).to_s() + url = @root_url.join(url).to_s response = HTTP::Client.get(url, headers: @access_header) error_check(response) @@ -193,7 +190,7 @@ class SpotifySearcher more_tracks = body["total"].as_i > offset + limit if more_tracks - return playlist_extension(parent, id, offset=offset + limit) + return playlist_extension(parent, id, offset = offset + limit) end return JSON.parse(parent.to_json) @@ -202,10 +199,9 @@ class SpotifySearcher # Find the genre of an artist based off of their id # # ``` - # SpotifySearcher.new().authorize(...).find_genre("1dfeR4HaWDbWqFHLkxsg1d") + # SpotifySearcher.new.authorize(...).find_genre("1dfeR4HaWDbWqFHLkxsg1d") # ``` def find_genre(id : String) : String - genre = get_item("artist", id)["genres"][0].to_s genre = genre.split(" ").map { |x| x.capitalize }.join(" ") @@ -216,15 +212,15 @@ class SpotifySearcher private def error_check(response : HTTP::Client::Response) : Nil if response.status_code != 200 raise("There was an error with your request.\n" + - "Status code: #{response.status_code}\n" + + "Status code: #{response.status_code}\n" + "Response: \n#{response.body}") end end - + # Generates url to run a GET request against to the Spotify open API # Returns a `String.` private def generate_query(item_type : String, item_parameters : Hash, - offset : Int32, limit : Int32) : String + offset : Int32, limit : Int32) : String query = "" # parameter keys to exclude in the api request. These values will be put @@ -234,21 +230,20 @@ class SpotifySearcher item_parameters.keys.each do |k| # This will map album and track names from the name key to the query if k == "name" - # will remove the "name:" param from the query - if item_type == "playlist" + if item_type == "playlist" query += item_parameters[k].gsub(" ", "+") + "+" else query += param_encode(item_type, item_parameters[k]) end - # check if the key is to be excluded + # check if the key is to be excluded elsif query_exclude.includes?(k) next - - # if it's none of the above, treat it normally - # NOTE: playlist names will be inserted into the query normally, without - # a parameter. + + # if it's none of the above, treat it normally + # NOTE: playlist names will be inserted into the query normally, without + # a parameter. else query += param_encode(k, item_parameters[k]) end @@ -264,7 +259,7 @@ class SpotifySearcher # Meant to find the item that the user desires. # Returns an `Array` of `Array(Int32)` or [[3, 1], [...], ...] private def rank_items(items : Array, - parameters : Hash) : Array(Array(Int32)) + parameters : Hash) : Array(Array(Int32)) points = [] of Array(Int32) index = 0 @@ -274,7 +269,7 @@ class SpotifySearcher # 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| + parameters.keys.each do |k| val = parameters[k] # The key to compare to for artist @@ -286,7 +281,7 @@ class SpotifySearcher if k == "username" pts_to_add = points_compare(item["owner"]["display_name"].to_s, val) pts += pts_to_add - pts += -10 if pts_to_add == 0 + pts += -10 if pts_to_add == 0 end # The key regardless of whether item is track, album,or playlist @@ -299,12 +294,12 @@ class SpotifySearcher index += 1 end - points.sort!{ |a, b| b[0] <=> a[0] } + points.sort! { |a, b| b[0] <=> a[0] } return points end - # Returns an `Int` based off the number of points worth assigning to the + # 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. @@ -332,18 +327,16 @@ class SpotifySearcher private def param_encode(key : String, value : String) : String return key.gsub(" ", "+") + ":" + value.gsub(" ", "+") + "+" end - end - # puts SpotifySearcher.new() -# .authorize("XXXXXXXXXXXXXXX", +# .authorize("XXXXXXXXXXXXXXX", # "XXXXXXXXXXXXXXX") # .find_item("playlist", { -# "name" => "Brain Food", -# "username" => "spotify" +# "name" => "Brain Food", +# "username" => "spotify" # # "name " => "A Night At The Opera", # # "artist" => "Queen" # # "track" => "Bohemian Rhapsody", # # "artist" => "Queen" -# }) \ No newline at end of file +# }) diff --git a/src/search/youtube.cr b/src/search/youtube.cr index ee41312..5742852 100755 --- a/src/search/youtube.cr +++ b/src/search/youtube.cr @@ -1,24 +1,22 @@ require "http" require "xml" - module Youtube - extend self VALID_LINK_CLASSES = [ "yt-simple-endpoint style-scope ytd-video-renderer", - "yt-uix-tile-link yt-ui-ellipsis yt-ui-ellipsis-2 yt-uix-sessionlink spf-link " + "yt-uix-tile-link yt-ui-ellipsis yt-ui-ellipsis-2 yt-uix-sessionlink spf-link ", ] GARBAGE_PHRASES = [ "cover", "album", "live", "clean", "version", "full", "full album", "row", "at", "@", "session", "how to", "npr music", "reimagined", "hr version", - "trailer" + "trailer", ] GOLDEN_PHRASES = [ - "official video", "official music video" + "official video", "official music video", ] # Finds a youtube url based off of the given information. @@ -30,11 +28,11 @@ module Youtube # Youtube.find_url("Bohemian Rhapsody", "Queen") # => "https://www.youtube.com/watch?v=dQw4w9WgXcQ" # ``` - def find_url(song_name : String, artist_name : String, search_terms = "", - download_first = false) : String? + def find_url(song_name : String, artist_name : String, search_terms = "", + download_first = false) : String? query = (song_name + " " + artist_name + " " + search_terms).strip.gsub(" ", "+") - url = "https://www.youtube.com/results?search_query=" + query + url = "https://www.youtube.com/results?search_query=" + query response = HTTP::Client.get(url) @@ -61,11 +59,11 @@ module Youtube # Will rank videos according to their title and the user input # Return: # [ - # {"points" => x, "index" => x}, - # ... + # {"points" => x, "index" => x}, + # ... # ] - private def rank_videos(song_name : String, artist_name : String, - query : String, nodes : Array(XML::Node)) : Array(Hash(String, Int32)) + private def rank_videos(song_name : String, artist_name : String, + query : String, nodes : Array(XML::Node)) : Array(Hash(String, Int32)) points = [] of Hash(String, Int32) index = 0 @@ -78,14 +76,13 @@ module Youtube points.push({ "points" => pts, - "index" => index + "index" => index, }) index += 1 - end # Sort first by points and then by original index of the song - points.sort!{ |a, b| + points.sort! { |a, b| if b["points"] == a["points"] a["index"] <=> b["index"] else @@ -96,11 +93,11 @@ module Youtube return points end - # Returns an `Int` based off the number of points worth assigning to the + # 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 *item1* includes *item2*, return 3 pts. - # If after the items have been blanked, *item1* includes *item2*, + # If after the items have been blanked, *item1* includes *item2*, # return 1 pts. # Else, return 0 pts. private def points_compare(item1 : String, item2 : String) : Int32 @@ -118,10 +115,10 @@ module Youtube end end - # Checks if there are any phrases in the title of the video that would + # Checks if there are any phrases in the title of the video that would # indicate audio having what we want. # *video_name* is the title of the video, and *query* is what the user the - # program searched for. *query* is needed in order to make sure we're not + # program searched for. *query* is needed in order to make sure we're not # subtracting points from something that's naturally in the title private def count_buzzphrases(query : String, video_name : String) : Int32 good_phrases = 0 @@ -129,7 +126,7 @@ module Youtube GOLDEN_PHRASES.each do |gold_phrase| gold_phrase = gold_phrase.downcase.gsub(/[^a-z0-9]/, "") - + if query.downcase.gsub(/[^a-z0-9]/, "").includes?(gold_phrase) next elsif video_name.downcase.gsub(/[^a-z0-9]/, "").includes?(gold_phrase) @@ -180,4 +177,4 @@ module Youtube end return false end -end \ No newline at end of file +end