finished youtube searcher

This commit is contained in:
Cooper Hammond 2019-06-12 12:52:37 -07:00
parent ddcd611585
commit 45f4d998a4
2 changed files with 132 additions and 11 deletions

View file

@ -1,7 +1,7 @@
require "http"
require "json"
require "base64"
require "spotify"
class SpotifySearcher
@root_url = Path["https://api.spotify.com/v1/"]
@ -74,7 +74,8 @@ class SpotifySearcher
end
# Generates url to run a GET request against
# 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)
query = ""
@ -106,6 +107,7 @@ class SpotifySearcher
# Ranks the given items based off of the info from parameters.
# 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)
points = [] of Array(Int32)
index = 0
@ -163,7 +165,7 @@ class SpotifySearcher
end
end
# Returns a parameter encoded for the spotify api
# Returns a `String` encoded for the spotify api
#
# ```
# __query_encode("album", "A Night At The Opera")
@ -176,10 +178,10 @@ class SpotifySearcher
end
puts SpotifySearcher.new()
.authorize("e4198f6a3f7b48029366f22528b5dc66",
"ba057d0621a5496bbb64edccf758bde5")
.find_item("track", {
"name" => "Bohemian Rhapsody",
"artist" => "Queen"
})
# puts SpotifySearcher.new()
# .authorize("e4198f6a3f7b48029366f22528b5dc66",
# "ba057d0621a5496bbb64edccf758bde5")
# .find_item("album", {
# "name" => "A Night At The Opera",
# "artist" => "Queen"
# })

119
src/search/youtube.cr Normal file
View file

@ -0,0 +1,119 @@
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 "
]
# Finds a youtube url based off of the given information.
# The query to youtube is constructed like this:
# "<song_name> <artist_name> <search terms>"
# If *download_first* is provided, the first link found will be downloaded.
#
# ```
# 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)
query = (song_name + " " + artist_name + " " + search_terms).strip.gsub(" ", "+")
url = "https://www.youtube.com/results?search_query=" + query
response = HTTP::Client.get(url)
valid_nodes = __get_video_link_nodes(response.body)
if valid_nodes.size == 0
puts "There were no results for that query."
return nil
end
root = "https://youtube.com"
return root + valid_nodes[0]["href"] if download_first
ranked = __rank_videos(song_name, artist_name, valid_nodes)
return root + valid_nodes[ranked[0][1]]["href"]
end
# Will rank videos according to their title and the user input
# Returns an `Array` of Arrays each layed out like
# [<points>, <original index>].
private def __rank_videos(song_name, artist_name, nodes : Array(XML::Node))
points = [] of Array(Int32)
index = 0
nodes.each do |node|
pts = 0
pts += __points_compare(song_name, node["title"])
pts += __points_compare(artist_name, node["title"])
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 *item1* includes *item2*, return 3 pts.
# 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)
if item1.includes?(item2)
return 3
end
item1 = item1.downcase.gsub(/[^a-z0-9]/, "")
item2 = item2.downcase.gsub(/[^a-z0-9]/, "")
if item1.includes?(item2)
return 1
else
return 0
end
end
# Finds valid video links from a `HTTP::Client.get` request
# Returns an `Array` of `XML::Node`
private def __get_video_link_nodes(doc : String)
nodes = XML.parse(doc).xpath_nodes("//a")
valid_nodes = [] of XML::Node
nodes.each do |node|
if __video_link_node?(node)
valid_nodes.push(node)
end
end
return valid_nodes
end
# Tests if the provided `XML::Node` has a valid link to a video
# Returns a `Bool`
private def __video_link_node?(node : XML::Node)
# If this passes, then the node links to a playlist, not a video
if node["href"]?
return false if node["href"].includes?("&list=")
end
VALID_LINK_CLASSES.each do |valid_class|
if node["class"]?
return true if node["class"].includes?(valid_class)
end
end
end
end