getcompilation route to routes that are registered

This commit is contained in:
broquemonsieur 2023-06-27 00:25:01 -07:00
parent 7cfa09984e
commit 1d209c422e
17 changed files with 196 additions and 50 deletions

View File

@ -240,7 +240,9 @@ struct InvidiousCompilation
end end
def create_compilation(title, privacy, user) def create_compilation(title, privacy, user)
LOGGER.info("2. create_compilation")
compid = "IVPL#{Random::Secure.urlsafe_base64(24)[0, 31]}" compid = "IVPL#{Random::Secure.urlsafe_base64(24)[0, 31]}"
LOGGER.info("generated compilation id")
compilation = InvidiousCompilation.new({ compilation = InvidiousCompilation.new({
title: title.byte_slice(0, 150), title: title.byte_slice(0, 150),
@ -253,8 +255,10 @@ def create_compilation(title, privacy, user)
privacy: privacy, privacy: privacy,
index: [] of Int64, index: [] of Int64,
}) })
LOGGER.info("Creating compilation db")
Invidious::Database::Compilations.insert(compilation) Invidious::Database::Compilations.insert(compilation)
LOGGER.info("inserted compilation db entry")
return compilation return compilation
end end
@ -313,6 +317,7 @@ def produce_compilation_continuation(id, index)
end end
def get_compilation(compid : String) def get_compilation(compid : String)
LOGGER.info("8. get_compilation")
if compid.starts_with? "IV" if compid.starts_with? "IV"
if compilation = Invidious::Database::Compilations.select(id: compid) if compilation = Invidious::Database::Compilations.select(id: compid)
return compilation return compilation
@ -323,6 +328,8 @@ def get_compilation(compid : String)
end end
def get_compilation_videos(compilation : InvidiousCompilation | Compilation, offset : Int32, video_id = nil) def get_compilation_videos(compilation : InvidiousCompilation | Compilation, offset : Int32, video_id = nil)
LOGGER.info("1. get_compilation")
LOGGER.info("Getting compilation")
# Show empty compilation if requested page is out of range # Show empty compilation if requested page is out of range
# (e.g, when a new compilation has been created, offset will be negative) # (e.g, when a new compilation has been created, offset will be negative)
if offset >= compilation.video_count || offset < 0 if offset >= compilation.video_count || offset < 0
@ -405,7 +412,7 @@ def extract_compilation_videos(initial_data : Hash(String, JSON::Any))
starting_timestamp_seconds: starting_timestamp_seconds, starting_timestamp_seconds: starting_timestamp_seconds,
ending_timestamp_seconds: ending_timestamp_seconds, ending_timestamp_seconds: ending_timestamp_seconds,
published: Time.utc, published: Time.utc,
compid: plid, compid: compid,
index: index, index: index,
}) })
end end

View File

@ -10,11 +10,14 @@ module Invidious::Database
def check_integrity(cfg) def check_integrity(cfg)
return if !cfg.check_tables return if !cfg.check_tables
Invidious::Database.check_enum("privacy", PlaylistPrivacy) Invidious::Database.check_enum("privacy", PlaylistPrivacy)
Invidious::Database.check_enum("compilation_privacy", CompilationPrivacy)
Invidious::Database.check_table("channels", InvidiousChannel) Invidious::Database.check_table("channels", InvidiousChannel)
Invidious::Database.check_table("channel_videos", ChannelVideo) Invidious::Database.check_table("channel_videos", ChannelVideo)
Invidious::Database.check_table("playlists", InvidiousPlaylist) Invidious::Database.check_table("playlists", InvidiousPlaylist)
Invidious::Database.check_table("playlist_videos", PlaylistVideo) Invidious::Database.check_table("playlist_videos", PlaylistVideo)
Invidious::Database.check_table("compilations", InvidiousCompilation)
Invidious::Database.check_table("compilation_videos", CompilationVideo)
Invidious::Database.check_table("nonces", Nonce) Invidious::Database.check_table("nonces", Nonce)
Invidious::Database.check_table("session_ids", SessionId) Invidious::Database.check_table("session_ids", SessionId)
Invidious::Database.check_table("users", User) Invidious::Database.check_table("users", User)

View File

@ -202,7 +202,7 @@ module Invidious::Database::CompilationVideos
end end
# ------------------- # -------------------
# Salect # Select
# ------------------- # -------------------
def select(compid : String, index : VideoIndex, offset, limit = 100) : Array(CompilationVideo) def select(compid : String, index : VideoIndex, offset, limit = 100) : Array(CompilationVideo)

View File

@ -212,7 +212,7 @@ module Invidious::Database::PlaylistVideos
end end
# ------------------- # -------------------
# Salect # Select
# ------------------- # -------------------
def select(plid : String, index : VideoIndex, offset, limit = 100) : Array(PlaylistVideo) def select(plid : String, index : VideoIndex, offset, limit = 100) : Array(PlaylistVideo)

View File

@ -240,6 +240,8 @@ struct InvidiousPlaylist
end end
def create_playlist(title, privacy, user) def create_playlist(title, privacy, user)
LOGGER.info("2. create_playlist")
LOGGER.info("create playlist inv/pl.cr")
plid = "IVPL#{Random::Secure.urlsafe_base64(24)[0, 31]}" plid = "IVPL#{Random::Secure.urlsafe_base64(24)[0, 31]}"
playlist = InvidiousPlaylist.new({ playlist = InvidiousPlaylist.new({
@ -313,6 +315,7 @@ def produce_playlist_continuation(id, index)
end end
def get_playlist(plid : String) def get_playlist(plid : String)
LOGGER.info("8. get_playlist")
if plid.starts_with? "IV" if plid.starts_with? "IV"
if playlist = Invidious::Database::Playlists.select(id: plid) if playlist = Invidious::Database::Playlists.select(id: plid)
return playlist return playlist
@ -401,6 +404,8 @@ def fetch_playlist(plid : String)
end end
def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32, video_id = nil) def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32, video_id = nil)
LOGGER.info("1. get_playlist_videos")
LOGGER.info("get_playlist_videos")
# Show empty playlist if requested page is out of range # Show empty playlist if requested page is out of range
# (e.g, when a new playlist has been created, offset will be negative) # (e.g, when a new playlist has been created, offset will be negative)
if offset >= playlist.video_count || offset < 0 if offset >= playlist.video_count || offset < 0

View File

@ -192,6 +192,21 @@ module Invidious::Routes::API::V1::Authenticated
env.response.status_code = 204 env.response.status_code = 204
end end
def self.list_compilations(env)
env.response.content_type = "application/json"
user = env.get("user").as(User)
compilations = Invidious::Database::Compilations.select_all(author: user.email)
JSON.build do |json|
json.array do
compilations.each do |compilation|
compilation.to_json(0, json)
end
end
end
end
def self.list_playlists(env) def self.list_playlists(env)
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
@ -208,33 +223,40 @@ module Invidious::Routes::API::V1::Authenticated
end end
def self.create_compilation(env) def self.create_compilation(env)
LOGGER.info("creating comp in auth fashion")
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)
LOGGER.info("app json compilation")
title = env.params.json["title"]?.try &.as(String).delete("<>").byte_slice(0, 150) title = env.params.json["title"]?.try &.as(String).delete("<>").byte_slice(0, 150)
if !title if !title
return error_json(400, "Invalid title.") return error_json(400, "Invalid title.")
end end
LOGGER.info("set title")
privacy = env.params.json["privacy"]?.try { |p| CompilationPrivacy.parse(p.as(String).downcase) } privacy = env.params.json["privacy"]?.try { |p| CompilationPrivacy.parse(p.as(String).downcase) }
if !privacy if !privacy
return error_json(400, "Invalid privacy setting.") return error_json(400, "Invalid privacy setting.")
end end
LOGGER.info("set privacy")
if Invidious::Database::Compilations.count_owned_by(user.email) >= 100 if Invidious::Database::Compilations.count_owned_by(user.email) >= 100
return error_json(400, "User cannot have more than 100 compilations.") return error_json(400, "User cannot have more than 100 compilations.")
end end
LOGGER.info("400 forgone")
compilation = create_compilation(title, privacy, user) compilation = create_compilation(title, privacy, user)
env.response.headers["Location"] = "#{HOST_URL}/api/v1/auth/compilations/#{compilation.id}" env.response.headers["Location"] = "#{HOST_URL}/api/v1/auth/compilations/#{compilation.id}"
env.response.status_code = 201 env.response.status_code = 201
LOGGER.info("location set")
{ {
"title" => title, "title" => title,
"compilationId" => compilation.id, "compilationId" => compilation.id,
}.to_json }.to_json
LOGGER.info("Creating json")
end end
def self.create_playlist(env) def self.create_playlist(env)
LOGGER.info("7. create_playlist")
env.response.content_type = "application/json" env.response.content_type = "application/json"
user = env.get("user").as(User) user = env.get("user").as(User)

View File

@ -10,6 +10,75 @@ module Invidious::Routes::API::V1::Misc
end end
end end
def self.get_compilation(env : HTTP::Server::Context)
LOGGER.info("15. get_compilation")
env.response.content_type = "application/json"
compid = env.params.url["compid"]
LOGGER.info("the compid is #{compid}")
offset = env.params.query["index"]?.try &.to_i?
offset ||= env.params.query["page"]?.try &.to_i?.try { |page| (page - 1) * 100 }
offset ||= 0
video_id = env.params.query["continuation"]?
format = env.params.query["format"]?
format ||= "json"
if compid.starts_with? "RD"
return env.redirect "/api/v1/mixes/#{compid}"
end
begin
compilation = get_compilation(compid)
rescue ex : InfoException
return error_json(404, ex)
rescue ex
return error_json(404, "Compilation does not exist.")
end
user = env.get?("user").try &.as(User)
if !compilation || compilation.privacy.private? && compilation.author != user.try &.email
return error_json(404, "Compilation does not exist.")
end
# includes into the compilation a maximum of 50 videos, before the offset
if offset > 0
lookback = offset < 50 ? offset : 50
response = compilation.to_json(offset - lookback)
json_response = JSON.parse(response)
else
# Unless the continuation is really the offset 0, it becomes expensive.
# It happens when the offset is not set.
# First we find the actual offset, and then we lookback
# it shouldn't happen often though
lookback = 0
response = compilation.to_json(offset, video_id: video_id)
json_response = JSON.parse(response)
if json_response["videos"].as_a[0]["index"] != offset
offset = json_response["videos"].as_a[0]["index"].as_i
lookback = offset < 50 ? offset : 50
response = compilation.to_json(offset - lookback)
json_response = JSON.parse(response)
end
end
if format == "html"
compilation_html = template_compilation(json_response)
index, next_video = json_response["videos"].as_a.skip(1 + lookback).select { |video| !video["author"].as_s.empty? }[0]?.try { |v| {v["index"], v["videoId"]} } || {nil, nil}
response = {
"compilationHtml" => compilation_html,
"index" => index,
"nextVideo" => next_video,
}.to_json
end
response
end
# APIv1 currently uses the same logic for both # APIv1 currently uses the same logic for both
# user playlists and Invidious playlists. This means that we can't # user playlists and Invidious playlists. This means that we can't
# reasonably split them yet. This should be addressed in APIv2 # reasonably split them yet. This should be addressed in APIv2

View File

@ -85,6 +85,7 @@ module Invidious::Routes::BeforeAll
csrf_token = generate_response(sid, { csrf_token = generate_response(sid, {
":authorize_token", ":authorize_token",
":playlist_ajax", ":playlist_ajax",
":compilation_ajax",
":signout", ":signout",
":subscription_ajax", ":subscription_ajax",
":token_ajax", ":token_ajax",

View File

@ -1,7 +1,11 @@
{% skip_file if flag?(:api_only) %} {% skip_file if flag?(:api_only) %}
module Invidious::Routes::Compilations module Invidious::Routes::Compilations
def self.handle(env)
return "<htm><body><p>Hello</p></body></html>"
end
def self.new(env) def self.new(env)
LOGGER.info("15. new")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
user = env.get? "user" user = env.get? "user"
@ -18,6 +22,7 @@ module Invidious::Routes::Compilations
end end
def self.create(env) def self.create(env)
LOGGER.info("3. create")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
user = env.get? "user" user = env.get? "user"
@ -49,7 +54,8 @@ module Invidious::Routes::Compilations
if Invidious::Database::Compilations.count_owned_by(user.email) >= 100 if Invidious::Database::Compilations.count_owned_by(user.email) >= 100
return error_template(400, "User cannot have more than 100 compilations.") return error_template(400, "User cannot have more than 100 compilations.")
end end
LOGGER.info("creating a compilation")
# POST /create_compilation?referer=%2Ffeed%2Fcompilations 12.11ms
compilation = create_compilation(title, privacy, user) compilation = create_compilation(title, privacy, user)
env.redirect "/compilation?list=#{compilation.id}" env.redirect "/compilation?list=#{compilation.id}"
@ -195,6 +201,7 @@ module Invidious::Routes::Compilations
end end
def self.add_compilation_items_page(env) def self.add_compilation_items_page(env)
LOGGER.info("13. add_compilation_items")
prefs = env.get("preferences").as(Preferences) prefs = env.get("preferences").as(Preferences)
locale = prefs.locale locale = prefs.locale
@ -234,6 +241,7 @@ module Invidious::Routes::Compilations
end end
def self.compilation_ajax(env) def self.compilation_ajax(env)
LOGGER.info("14. compilation_ajax")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
user = env.get? "user" user = env.get? "user"
@ -359,22 +367,29 @@ module Invidious::Routes::Compilations
end end
def self.show(env) def self.show(env)
LOGGER.info("4. show | comp")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
LOGGER.info("set locale")
user = env.get?("user").try &.as(User) user = env.get?("user").try &.as(User)
LOGGER.info("got user")
referer = get_referer(env) referer = get_referer(env)
LOGGER.info("got referer")
compid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "") compid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "")
LOGGER.info("got compid comp")
if !compid if !compid
return env.redirect "/" return env.redirect "/"
end end
page = env.params.query["page"]?.try &.to_i? page = env.params.query["page"]?.try &.to_i?
page ||= 1 page ||= 1
LOGGER.info("set page")
if compid.starts_with? "RD" if compid.starts_with? "RD"
return env.redirect "/mix?list=#{compid}" return env.redirect "/mix?list=#{compid}"
end end
LOGGER.info("RD comp")
begin begin
compilation = get_compilation(compid) compilation = get_compilation(compid)
@ -383,27 +398,33 @@ module Invidious::Routes::Compilations
rescue ex rescue ex
return error_template(500, ex) return error_template(500, ex)
end end
LOGGER.info("got 200 comp")
page_count = (compilation.video_count / 200).to_i page_count = (compilation.video_count / 200).to_i
page_count += 1 if (compilation.video_count % 200) > 0 page_count += 1 if (compilation.video_count % 200) > 0
LOGGER.info("set page count")
if page > page_count if page > page_count
return env.redirect "/compilation?list=#{compid}&page=#{page_count}" return env.redirect "/compilation?list=#{compid}&page=#{page_count}"
end end
if compilation.privacy == CompilationPrivacy::Private && compilation.author != user.try &.email if compilation.privacy == CompilationPrivacy::Private && compilation.author != user.try &.email
return error_template(403, "This compilation is private.") return error_template(403, "This compilation is private.")
end end
LOGGER.info("set privacy")
begin begin
videos = get_compilation_videos(compilation, offset: (page - 1) * 200) videos = get_compilation_videos(compilation, offset: (page - 1) * 200)
rescue ex rescue ex
return error_template(500, "Error encountered while retrieving compilation videos.<br>#{ex.message}") return error_template(500, "Error encountered while retrieving compilation videos.<br>#{ex.message}")
end end
LOGGER.info("set offset")
if compilation.author == user.try &.email if compilation.author == user.try &.email
env.set "remove_compilation_items", compid env.set "remove_compilation_items", compid
end end
LOGGER.info("showing author")
templated "compilation" templated "compilation"
end end

View File

@ -31,6 +31,7 @@ module Invidious::Routes::Embed
end end
def self.show(env) def self.show(env)
LOGGER.info("9? show")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
id = env.params.url["id"] id = env.params.url["id"]

View File

@ -10,6 +10,7 @@ module Invidious::Routes::Feeds
end end
def self.compilations(env) def self.compilations(env)
LOGGER.info("5. compilations")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
user = env.get? "user" user = env.get? "user"
@ -36,6 +37,8 @@ module Invidious::Routes::Feeds
end end
def self.playlists(env) def self.playlists(env)
LOGGER.info("5. playlists")
LOGGER.info("Generating the playlist items")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
user = env.get? "user" user = env.get? "user"

View File

@ -18,6 +18,8 @@ module Invidious::Routes::Playlists
end end
def self.create(env) def self.create(env)
LOGGER.info("3. create")
LOGGER.info("creating a play")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
user = env.get? "user" user = env.get? "user"
@ -396,6 +398,8 @@ module Invidious::Routes::Playlists
end end
def self.show(env) def self.show(env)
LOGGER.info("4. show")
LOGGER.info("showing a play")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
user = env.get?("user").try &.as(User) user = env.get?("user").try &.as(User)

View File

@ -2,6 +2,7 @@
module Invidious::Routes::Watch module Invidious::Routes::Watch
def self.handle(env) def self.handle(env)
LOGGER.info("6. handle")
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
region = env.params.query["region"]? region = env.params.query["region"]?
@ -202,6 +203,7 @@ module Invidious::Routes::Watch
end end
def self.redirect(env) def self.redirect(env)
LOGGER.info("10? redirect")
url = "/watch?v=#{env.params.url["id"]}" url = "/watch?v=#{env.params.url["id"]}"
if env.params.query.size > 0 if env.params.query.size > 0
url += "&#{env.params.query}" url += "&#{env.params.query}"
@ -275,6 +277,7 @@ module Invidious::Routes::Watch
end end
def self.clip(env) def self.clip(env)
LOGGER.info("11? clip")
clip_id = env.params.url["clip"]? clip_id = env.params.url["clip"]?
return error_template(400, "A clip ID is required") if !clip_id return error_template(400, "A clip ID is required") if !clip_id

View File

@ -28,6 +28,7 @@ module Invidious::Routing
self.register_iv_playlist_routes self.register_iv_playlist_routes
self.register_iv_compilation_routes self.register_iv_compilation_routes
self.register_yt_playlist_routes self.register_yt_playlist_routes
self.register_compilation_routes
self.register_search_routes self.register_search_routes
@ -172,6 +173,10 @@ module Invidious::Routing
get "/watch_videos", Routes::Playlists, :watch_videos get "/watch_videos", Routes::Playlists, :watch_videos
end end
def register_compilation_routes
get "/compilation", Routes::Compilations, :show
end
def register_search_routes def register_search_routes
get "/opensearch.xml", Routes::Search, :opensearch get "/opensearch.xml", Routes::Search, :opensearch
get "/results", Routes::Search, :results get "/results", Routes::Search, :results
@ -282,6 +287,7 @@ module Invidious::Routing
delete "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :unsubscribe_channel delete "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :unsubscribe_channel
post "/api/v1/auth/compilations", {{namespace}}::Authenticated, :create_compilation post "/api/v1/auth/compilations", {{namespace}}::Authenticated, :create_compilation
get "/api/v1/auth/compilations", {{namespace}}::Authenticated, :list_compilations
get "/api/v1/auth/playlists", {{namespace}}::Authenticated, :list_playlists get "/api/v1/auth/playlists", {{namespace}}::Authenticated, :list_playlists
post "/api/v1/auth/playlists", {{namespace}}::Authenticated, :create_playlist post "/api/v1/auth/playlists", {{namespace}}::Authenticated, :create_playlist
@ -303,6 +309,8 @@ module Invidious::Routing
get "/api/v1/stats", {{namespace}}::Misc, :stats get "/api/v1/stats", {{namespace}}::Misc, :stats
get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist
get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist
get "/api/v1/compilations/:compid", {{namespace}}::Misc, :get_compilation
get "/api/v1/auth/compilations/:compid", {{namespace}}::Misc, :get_compilation
get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes
get "/api/v1/resolveurl", {{namespace}}::Misc, :resolve_url get "/api/v1/resolveurl", {{namespace}}::Misc, :resolve_url
{% end %} {% end %}

View File

@ -3,49 +3,6 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= title %> - Invidious</title> <title><%= title %> - Invidious</title>
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" /> <link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/compilation/<%= compid %>" />
<% end %> <% end %>
<div class="pure-g h-box">
<div class="pure-u-2-3">
<h3><%= title %></h3>
<% if compilation.is_a? InvidiousCompilation %>
<b>
<% if compilation.author == user.try &.email %>
<a href="/feed/compilations"><%= author %></a> |
<% else %>
<%= author %> |
<% end %>
<%= translate_count(locale, "generic_videos_count", compilation.video_count) %> |
<%= translate(locale, "Updated `x` ago", recode_date(compilation.updated, locale)) %> |
<% case compilation.as(InvidiousCompilation).privacy when %>
<% when CompilationPrivacy::Unlisted %>
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
<% when CompilationPrivacy::Private %>
<i class="icon ion-ios-lock"></i> <%= translate(locale, "Private") %>
<% end %>
</b>
<% else %>
<b>
<a href="/channel/<%= compilation.ucid %>"><%= author %></a> |
<%= translate_count(locale, "generic_videos_count", compilation.video_count) %> |
<%= translate(locale, "Updated `x` ago", recode_date(compilation.updated, locale)) %>
</b>
<% end %>
</div>
<div class="pure-u-1-3" style="text-align:right">
<h3>
<div class="pure-g user-field">
<% if compilation.is_a?(InvidiousCompilation) && compilation.author == user.try &.email %>
<div class="pure-u-1-3"><a href="/edit_compilation?list=<%= compid %>"><i class="icon ion-md-create"></i></a></div>
<div class="pure-u-1-3"><a href="/delete_compilation?list=<%= compid %>"><i class="icon ion-md-trash"></i></a></div>
<% else %>
<% if !Invidious::Database::Compilations.exists?(compilation.id) %>
<div class="pure-u-1-3"><a href="/delete_compilation?list=<%= compid %>"><i class="icon ion-md-trash"></i></a></div>
<% end %>
<% end %>
<div class="pure-u-1-3"><a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a></div>
</div>
</h3>
</div>
</div>

View File

@ -53,7 +53,7 @@
<p><%= translate_count(locale, "generic_channels_count", item.channel_count, NumberFormatting::Separator) %></p> <p><%= translate_count(locale, "generic_channels_count", item.channel_count, NumberFormatting::Separator) %></p>
<%- end -%> <%- end -%>
</div> </div>
<% when SearchPlaylist, InvidiousPlaylist, InvidiousCompilation %> <% when SearchPlaylist, InvidiousPlaylist %>
<%- <%-
if item.id.starts_with? "RD" if item.id.starts_with? "RD"
link_url = "/mix?list=#{item.id}&continuation=#{URI.parse(item.thumbnail || "/vi/-----------").request_target.split("/")[2]}" link_url = "/mix?list=#{item.id}&continuation=#{URI.parse(item.thumbnail || "/vi/-----------").request_target.split("/")[2]}"
@ -87,6 +87,20 @@
</p> </p>
</a></div> </a></div>
</div> </div>
<% when InvidiousCompilation %>
<% url = "/compilation?list=#{item.id}" %>
<a style="width:100%" href="<%= url %>">
<% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail">
<img loading="lazy" tabindex="-1" class="thumbnail" src="<%= URI.parse(item.thumbnail || "/").request_target %>" alt="" />
<p class="length"><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
</div>
<% end %>
<p dir="auto"><%= HTML.escape(item.title) %></p>
</a>
<a href="/channel/<%= item.ucid %>">
<p dir="auto"><b><%= HTML.escape(item.author) %><% if !item.is_a?(InvidiousCompilation) && !item.is_a?(InvidiousPlaylist) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b></p>
</a>
<% when CompilationVideo %> <% when CompilationVideo %>
<a style="width:100%" href="/watch?v=<%= item.id %>&list=<%= item.compid %>&index=<%= item.index %>"> <a style="width:100%" href="/watch?v=<%= item.id %>&list=<%= item.compid %>&index=<%= item.index %>">
<% if !env.get("preferences").as(Preferences).thin_mode %> <% if !env.get("preferences").as(Preferences).thin_mode %>

View File

@ -139,6 +139,34 @@ we're going to need to do it here in order to allow for translations.
<% if user %> <% if user %>
<% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %> <% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %>
<% compilations = Invidious::Database::Compilations.select_user_created_compilations(user.email) %>
<% if !compilations.empty? %>
<form data-onsubmit="return_false" class="pure-form pure-form-stacked" action="/compilation_ajax" method="post" target="_blank">
<div class="pure-control-group">
<label for="compilation_id"><%= translate(locale, "Add to compilation: ") %></label>
<select style="width:100%" name="compilation_id" id="compilation_id">
<% compilations.each do |compid, compilation_title| %>
<option data-compid="<%= compid %>" value="<%= compid %>"><%= HTML.escape(compilation_title) %></option>
<% end %>
</select>
</div>
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
<input type="hidden" name="action_add_video" value="1">
<input type="hidden" name="video_id" value="<%= video.id %>">
<button data-onclick="add_compilation_video" data-id="<%= video.id %>" type="submit" class="pure-button pure-button-primary">
<b><%= translate(locale, "Add to compilation") %></b>
</button>
</form>
<script id="compilation_data" type="application/json">
<%=
{
"csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "")
}.to_pretty_json
%>
</script>
<script src="/js/compilation_widget.js?v=<%= Time.utc.to_unix_ms %>"></script>
<% end %>
<% if !playlists.empty? %> <% if !playlists.empty? %>
<form data-onsubmit="return_false" class="pure-form pure-form-stacked" action="/playlist_ajax" method="post" target="_blank"> <form data-onsubmit="return_false" class="pure-form pure-form-stacked" action="/playlist_ajax" method="post" target="_blank">
<div class="pure-control-group"> <div class="pure-control-group">