diff options
| author | Omar Roth <omarroth@hotmail.com> | 2018-09-28 23:12:35 -0500 |
|---|---|---|
| committer | Omar Roth <omarroth@hotmail.com> | 2018-09-29 10:59:11 -0500 |
| commit | 20130db556c575e4434b1ce8a1429bc8b4ddbdf2 (patch) | |
| tree | 6c3452e03706c4e1f7ce114082e2b6ba94dd195d | |
| parent | 66f3ab06632e8114d2c1ac0bed801ba9c6fd8f11 (diff) | |
| download | invidious-20130db556c575e4434b1ce8a1429bc8b4ddbdf2.tar.gz invidious-20130db556c575e4434b1ce8a1429bc8b4ddbdf2.tar.bz2 invidious-20130db556c575e4434b1ce8a1429bc8b4ddbdf2.zip | |
Add mixes
| -rw-r--r-- | src/invidious.cr | 73 | ||||
| -rw-r--r-- | src/invidious/helpers/helpers.cr | 15 | ||||
| -rw-r--r-- | src/invidious/mixes.cr | 74 | ||||
| -rw-r--r-- | src/invidious/playlists.cr | 26 | ||||
| -rw-r--r-- | src/invidious/views/components/item.ecr | 18 | ||||
| -rw-r--r-- | src/invidious/views/mix.ecr | 22 |
6 files changed, 210 insertions, 18 deletions
diff --git a/src/invidious.cr b/src/invidious.cr index dc61c105..383a12d7 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -390,6 +390,7 @@ get "/embed/:id" do |env| end # Playlists + get "/playlist" do |env| plid = env.params.query["list"]? if !plid @@ -415,6 +416,25 @@ get "/playlist" do |env| templated "playlist" end +get "/mix" do |env| + rdid = env.params.query["list"]? + if !rdid + next env.redirect "/" + end + + continuation = env.params.query["continuation"]? + continuation ||= rdid.lchop("RD") + + begin + mix = fetch_mix(rdid, continuation) + rescue ex + error_message = ex.message + next templated "error" + end + + templated "mix" +end + # Search get "/results" do |env| @@ -2166,12 +2186,13 @@ get "/api/v1/insights/:id" do |env| end get "/api/v1/videos/:id" do |env| + env.response.content_type = "application/json" + id = env.params.url["id"] begin video = get_video(id, PG_DB, proxies) rescue ex - env.response.content_type = "application/json" error_message = {"error" => ex.message}.to_json halt env, status_code: 500, response: error_message end @@ -2181,7 +2202,6 @@ get "/api/v1/videos/:id" do |env| captions = video.captions - env.response.content_type = "application/json" video_info = JSON.build do |json| json.object do json.field "title", video.title @@ -2945,6 +2965,55 @@ get "/api/v1/playlists/:plid" do |env| response end +get "/api/v1/mixes/:rdid" do |env| + env.response.content_type = "application/json" + + rdid = env.params.url["rdid"] + + continuation = env.params.query["continuation"]? + continuation ||= rdid.lchop("RD") + + begin + mix = fetch_mix(rdid, continuation) + rescue ex + error_message = {"error" => ex.message}.to_json + halt env, status_code: 500, response: error_message + end + + response = JSON.build do |json| + json.object do + json.field "title", mix.title + json.field "mixId", mix.id + + json.field "videos" do + json.array do + mix.videos.each do |video| + json.object do + json.field "title", video.title + json.field "videoId", video.id + json.field "author", video.author + + json.field "authorId", video.ucid + json.field "authorUrl", "/channel/#{video.ucid}" + + json.field "videoThumbnails" do + json.array do + generate_thumbnails(json, video.id) + end + end + + json.field "index", video.index + json.field "lengthSeconds", video.length_seconds + end + end + end + end + end + end + + response +end + get "/api/manifest/dash/id/videoplayback" do |env| env.response.headers["Access-Control-Allow-Origin"] = "*" env.redirect "/videoplayback?#{env.params.query}" diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 906d9fa5..ab33c3af 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -244,11 +244,22 @@ def extract_items(nodeset, ucid = nil) plid = HTTP::Params.parse(URI.parse(id).query.not_nil!)["list"] anchor = node.xpath_node(%q(.//div[contains(@class, "yt-lockup-meta")]/a)) + if !anchor anchor = node.xpath_node(%q(.//ul[@class="yt-lockup-meta-info"]/li/a)) end - if anchor - video_count = anchor.content.match(/View full playlist \((?<count>\d+)/).try &.["count"].to_i? + + video_count = node.xpath_node(%q(.//span[@class="formatted-video-count-label"]/b)) + if video_count + video_count = video_count.content + + if video_count == "50+" + author = "YouTube" + author_id = "UC-9-kyTW8ZkZNDHQJ6FgpwQ" + video_count = video_count.rchop("+") + end + + video_count = video_count.to_i? end video_count ||= 0 diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr new file mode 100644 index 00000000..e7e76b80 --- /dev/null +++ b/src/invidious/mixes.cr @@ -0,0 +1,74 @@ +class MixVideo + add_mapping({ + title: String, + id: String, + author: String, + ucid: String, + length_seconds: Int32, + index: Int32, + }) +end + +class Mix + add_mapping({ + title: String, + id: String, + videos: Array(MixVideo), + }) +end + +def fetch_mix(rdid, video_id, cookies = nil) + client = make_client(YT_URL) + headers = HTTP::Headers.new + headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" + + if cookies + headers = cookies.add_request_headers(headers) + end + response = client.get("/watch?v=#{video_id}&list=#{rdid}&bpctr=#{Time.new.epoch + 2000}&gl=US&hl=en", headers) + + yt_data = response.body.match(/window\["ytInitialData"\] = (?<data>.*);/) + if yt_data + yt_data = JSON.parse(yt_data["data"].rchop(";")) + else + raise "Could not create mix." + end + + playlist = yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"] + mix_title = playlist["title"].as_s + + contents = playlist["contents"].as_a + until contents[0]["playlistPanelVideoRenderer"]["videoId"].as_s == video_id + contents.shift + end + + videos = [] of MixVideo + contents.each do |item| + item = item["playlistPanelVideoRenderer"] + + id = item["videoId"].as_s + title = item["title"]["simpleText"].as_s + author = item["longBylineText"]["runs"][0]["text"].as_s + ucid = item["longBylineText"]["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s + length_seconds = decode_length_seconds(item["lengthText"]["simpleText"].as_s) + index = item["navigationEndpoint"]["watchEndpoint"]["index"].as_i + + videos << MixVideo.new( + title, + id, + author, + ucid, + length_seconds, + index + ) + end + + if !cookies + next_page = fetch_mix(rdid, videos[-1].id, response.cookies) + videos += next_page.videos + end + + videos.uniq! { |video| video.id } + videos = videos.first(50) + return Mix.new(mix_title, rdid, videos) +end diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 0626f4ae..32fcd016 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -1,3 +1,16 @@ +class PlaylistVideo + add_mapping({ + title: String, + id: String, + author: String, + ucid: String, + length_seconds: Int32, + published: Time, + playlists: Array(String), + index: Int32, + }) +end + class Playlist add_mapping({ title: String, @@ -13,19 +26,6 @@ class Playlist }) end -class PlaylistVideo - add_mapping({ - title: String, - id: String, - author: String, - ucid: String, - length_seconds: Int32, - published: Time, - playlists: Array(String), - index: Int32, - }) -end - def fetch_playlist_videos(plid, page, video_count) client = make_client(YT_URL) diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 734f47f7..1ffc6467 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -14,7 +14,12 @@ <p><%= number_with_separator(item.subscriber_count) %> subscribers</p> <h5><%= item.description_html %></h5> <% when SearchPlaylist %> - <a style="width:100%;" href="/playlist?list=<%= item.id %>"> + <% if item.id.starts_with? "RD" %> + <% url = "/mix?list=#{item.id}&continuation=#{item.videos[0]?.try &.id}" %> + <% else %> + <% url = "/playlist?list=#{item.id}" %> + <% end %> + <a style="width:100%;" href="<%= url %>"> <% if env.get?("user") && env.get("user").as(User).preferences.thin_mode %> <% else %> <img style="width:100%;" src="/vi/<%= item.videos[0]?.try &.id %>/mqdefault.jpg"/> @@ -26,6 +31,17 @@ </p> <p><%= number_with_separator(item.video_count) %> videos</p> <p>PLAYLIST</p> + <% when MixVideo %> + <a style="width:100%;" href="/watch?v=<%= item.id %>"> + <% if env.get?("user") && env.get("user").as(User).preferences.thin_mode %> + <% else %> + <img style="width:100%;" src="/vi/<%= item.id %>/mqdefault.jpg"/> + <% end %> + <p><%= item.title %></p> + </a> + <p> + <b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b> + </p> <% else %> <% if item.responds_to?(:playlists) && !item.playlists.empty? %> <% params = "&list=#{item.playlists[0]}" %> diff --git a/src/invidious/views/mix.ecr b/src/invidious/views/mix.ecr new file mode 100644 index 00000000..139e01b9 --- /dev/null +++ b/src/invidious/views/mix.ecr @@ -0,0 +1,22 @@ +<% content_for "header" do %> +<title><%= mix.title %> - Invidious</title> +<% end %> + +<div class="pure-g h-box"> + <div class="pure-u-2-3"> + <h3><%= mix.title %></h3> + </div> + <div class="pure-u-1-3" style="text-align:right;"> + <h3> + <a href="/feed/playlist/<%= mix.id %>"><i class="icon ion-logo-rss"></i></a> + </h3> + </div> +</div> + +<% mix.videos.each_slice(4) do |slice| %> +<div class="pure-g"> + <% slice.each do |item| %> + <%= rendered "components/item" %> + <% end %> +</div> +<% end %> |
