summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/css/default.css5
-rw-r--r--src/invidious.cr34
-rw-r--r--src/invidious/mixes.cr38
-rw-r--r--src/invidious/playlists.cr50
-rw-r--r--src/invidious/views/watch.ecr68
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() {