diff options
| -rw-r--r-- | assets/css/default.css | 5 | ||||
| -rw-r--r-- | src/invidious.cr | 34 | ||||
| -rw-r--r-- | src/invidious/mixes.cr | 38 | ||||
| -rw-r--r-- | src/invidious/playlists.cr | 50 | ||||
| -rw-r--r-- | src/invidious/views/watch.ecr | 68 |
5 files changed, 190 insertions, 5 deletions
diff --git a/assets/css/default.css b/assets/css/default.css index 15cd1d4e..e60b9da8 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -17,6 +17,11 @@ div { animation: spin 2s linear infinite; } +.playlist-restricted { + height: 20em; + padding-right: 10px; +} + /* * Navbar */ diff --git a/src/invidious.cr b/src/invidious.cr index 9be7fd85..a6b77c54 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -217,6 +217,8 @@ get "/watch" do |env| next env.redirect "/" end + plid = env.params.query["list"]? + user = env.get? "user" if user user = user.as(User) @@ -2939,6 +2941,11 @@ get "/api/v1/playlists/:plid" do |env| page = env.params.query["page"]?.try &.to_i? page ||= 1 + format = env.params.query["format"]? + format ||= "json" + + continuation = env.params.query["continuation"]? + if plid.starts_with? "RD" next env.redirect "/api/v1/mixes/#{plid}" end @@ -2951,7 +2958,7 @@ get "/api/v1/playlists/:plid" do |env| end begin - videos = fetch_playlist_videos(plid, page, playlist.video_count) + videos = fetch_playlist_videos(plid, page, playlist.video_count, continuation) rescue ex videos = [] of PlaylistVideo end @@ -3010,6 +3017,17 @@ get "/api/v1/playlists/:plid" do |env| end end + if format == "html" + response = JSON.parse(response) + playlist_html = template_playlist(response) + next_video = response["videos"].as_a[1]?.try &.["videoId"] + + response = { + "playlistHtml" => playlist_html, + "nextVideo" => next_video, + }.to_json + end + response end @@ -3021,6 +3039,9 @@ get "/api/v1/mixes/:rdid" do |env| continuation = env.params.query["continuation"]? continuation ||= rdid.lchop("RD") + format = env.params.query["format"]? + format ||= "json" + begin mix = fetch_mix(rdid, continuation) rescue ex @@ -3059,6 +3080,17 @@ get "/api/v1/mixes/:rdid" do |env| end end + if format == "html" + response = JSON.parse(response) + playlist_html = template_mix(response) + next_video = response["videos"].as_a[1]?.try &.["videoId"] + + response = { + "playlistHtml" => playlist_html, + "nextVideo" => next_video, + }.to_json + end + response end diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index ef53ad4d..69a2d67d 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -35,6 +35,10 @@ def fetch_mix(rdid, video_id, cookies = nil) raise "Could not create mix." end + if !yt_data["contents"]["twoColumnWatchNextResults"]["playlist"] + raise "Could not create mix." + end + playlist = yt_data["contents"]["twoColumnWatchNextResults"]["playlist"]["playlist"] mix_title = playlist["title"].as_s @@ -74,3 +78,37 @@ def fetch_mix(rdid, video_id, cookies = nil) videos = videos.first(50) return Mix.new(mix_title, rdid, videos) end + +def template_mix(mix) + html = <<-END_HTML + <h3> + <a href="/mix?list=#{mix["mixId"]}"> + #{mix["title"]} + </a> + </h3> + <div class="pure-menu pure-menu-scrollable playlist-restricted"> + <ol class="pure-menu-list"> + END_HTML + + mix["videos"].as_a.each do |video| + html += <<-END_HTML + <li class="pure-menu-item"> + <a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}"> + <img style="width:100%;" src="/vi/#{video["videoId"]}/mqdefault.jpg"> + <p style="width:100%">#{video["title"]}</p> + <p> + <b style="width: 100%">#{video["author"]}</b> + </p> + </a> + </li> + END_HTML + end + + html += <<-END_HTML + </ol> + </div> + <hr> + END_HTML + + html +end diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 32fcd016..b75a3293 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -26,11 +26,23 @@ class Playlist }) end -def fetch_playlist_videos(plid, page, video_count) +def fetch_playlist_videos(plid, page, video_count, continuation = nil) client = make_client(YT_URL) - if video_count > 100 + if continuation + html = client.get("/watch?v=#{continuation}&list=#{plid}&bpctr=#{Time.new.epoch + 2000}&gl=US&hl=en&disable_polymer=1") + html = XML.parse_html(html.body) + + index = html.xpath_node(%q(//span[@id="playlist-current-index"])).try &.content.to_i? + if index + index -= 1 + end + index ||= 0 + else index = (page - 1) * 100 + end + + if video_count > 100 url = produce_playlist_url(plid, index) response = client.get(url) @@ -199,3 +211,37 @@ def fetch_playlist(plid) return playlist end + +def template_playlist(playlist) + html = <<-END_HTML + <h3> + <a href="/playlist?list=#{playlist["playlistId"]}"> + #{playlist["title"]} + </a> + </h3> + <div class="pure-menu pure-menu-scrollable playlist-restricted"> + <ol class="pure-menu-list"> + END_HTML + + playlist["videos"].as_a.each do |video| + html += <<-END_HTML + <li class="pure-menu-item"> + <a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}"> + <img style="width:100%;" src="/vi/#{video["videoId"]}/mqdefault.jpg"> + <p style="width:100%">#{video["title"]}</p> + <p> + <b style="width: 100%">#{video["author"]}</b> + </p> + </a> + </li> + END_HTML + end + + html += <<-END_HTML + </ol> + </div> + <hr> + END_HTML + + html +end diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 4eaaf9a6..7df3794b 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -123,6 +123,13 @@ </div> </div> <div class="pure-u-1 pure-u-md-1-5"> + <% if plid %> + <div id="playlist" class="h-box"> + <h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3> + <hr> + </div> + <% end %> + <% if !preferences || preferences && preferences.related_videos %> <div class="h-box"> <% rvs.each do |rv| %> @@ -145,6 +152,61 @@ </div> <script> +<% if plid %> +function get_playlist() { + var plid = "<%= plid %>" + + if (plid.startsWith("RD")) { + var plid_url = "/api/v1/mixes/<%= plid %>?continuation=<%= video.id %>&format=html"; + } else { + var plid_url = "/api/v1/playlists/<%= plid %>?continuation=<%= video.id %>&format=html"; + } + + var xhr = new XMLHttpRequest(); + xhr.responseType = "json"; + xhr.timeout = 20000; + xhr.open("GET", plid_url, true); + xhr.send(); + + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + playlist = document.getElementById("playlist"); + playlist.innerHTML = xhr.response.playlistHtml; + + if (xhr.response.nextVideo) { + player.on('ended', function() { + window.location.replace("/watch?v=" + + xhr.response.nextVideo + + "&list=<%= plid %>" + <% if params[:listen] %> + + "&listen=1" + <% end %> + <% if params[:autoplay] %> + + "&autoplay=1" + <% end %> + ); + }); + } + } else { + playlist.innerHTML = ""; + } + } + }; + + xhr.ontimeout = function() { + console.log("Pulling playlist timed out."); + + comments = document.getElementById("playlist"); + comments.innerHTML = + '<h3><center class="loading"><i class="icon ion-ios-refresh"></i></center></h3><hr>'; + get_playlist(); + }; +} + +get_playlist(); +<% end %> + function get_reddit_comments() { var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html"; var xhr = new XMLHttpRequest(); @@ -154,7 +216,7 @@ function get_reddit_comments() { xhr.send(); xhr.onreadystatechange = function() { - if (xhr.readyState == 4) + if (xhr.readyState == 4) { if (xhr.status == 200) { comments = document.getElementById("comments"); comments.innerHTML = ' \ @@ -188,6 +250,7 @@ function get_reddit_comments() { comments.innerHTML = ""; <% end %> } + } }; xhr.ontimeout = function() { @@ -206,7 +269,7 @@ function get_youtube_comments() { xhr.send(); xhr.onreadystatechange = function() { - if (xhr.readyState == 4) + if (xhr.readyState == 4) { if (xhr.status == 200) { comments = document.getElementById("comments"); if (xhr.response.commentCount > 0) { @@ -238,6 +301,7 @@ function get_youtube_comments() { comments.innerHTML = ""; <% end %> } + } }; xhr.ontimeout = function() { |
