Add ecr for individual compilation items (videos)

This commit is contained in:
broquemonsieur 2023-06-16 01:49:38 -07:00
parent bac4fd9097
commit 74d42c18dd
7 changed files with 545 additions and 2 deletions

View File

@ -0,0 +1,48 @@
'use strict';
var compilation_data = JSON.parse(document.getElementById('compilation_data').textContent);
var payload = 'csrf_token=' + compilation_data.csrf_token;
function add_compilation_video(target) {
var select = target.parentNode.children[0].children[1];
var option = select.children[select.selectedIndex];
var url = '/compilation_ajax?action_add_video=1&redirect=false' +
'&video_id=' + target.getAttribute('data-id') +
'&compilation_id=' + option.getAttribute('data-compid');
helpers.xhr('POST', url, {payload: payload}, {
on200: function (response) {
option.textContent = '✓' + option.textContent;
}
});
}
function add_compilation_item(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
tile.style.display = 'none';
var url = '/compilation_ajax?action_add_video=1&redirect=false' +
'&video_id=' + target.getAttribute('data-id') +
'&compilation_id=' + target.getAttribute('data-compid');
helpers.xhr('POST', url, {payload: payload}, {
onNon200: function (xhr) {
tile.style.display = '';
}
});
}
function remove_compilation_item(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
tile.style.display = 'none';
var url = '/compilation_ajax?action_remove_video=1&redirect=false' +
'&set_video_id=' + target.getAttribute('data-index') +
'&compilation_id=' + target.getAttribute('data-compid');
helpers.xhr('POST', url, {payload: payload}, {
onNon200: function (xhr) {
tile.style.display = '';
}
});
}

View File

@ -483,6 +483,14 @@ hmac_key: "CHANGE_ME!!"
##
#playlist_length_limit: 500
##
## Maximum custom compilation length limit.
##
## Accepted values: Integer
## Default: 500
##
#compilation_length_limit: 500
#########################################
#
# Default user preferences

View File

@ -177,6 +177,7 @@
"Create compilation": "Create compilation",
"Title": "Title",
"Playlist privacy": "Playlist privacy",
"Compilation privacy": "Compilation privacy",
"Editing playlist `x`": "Editing playlist `x`",
"playlist_button_add_items": "Add videos",
"Show more": "Show more",

View File

@ -0,0 +1,410 @@
{% skip_file if flag?(:api_only) %}
module Invidious::Routes::Compilations
def self.new(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)
return env.redirect "/" if user.nil?
user = user.as(User)
sid = sid.as(String)
csrf_token = generate_response(sid, {":create_compilation"}, HMAC_KEY)
templated "create_compilation"
end
def self.create(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)
return env.redirect "/" if user.nil?
user = user.as(User)
sid = sid.as(String)
token = env.params.body["csrf_token"]?
begin
validate_request(token, sid, env.request, HMAC_KEY, locale)
rescue ex
return error_template(400, ex)
end
title = env.params.body["title"]?.try &.as(String)
if !title || title.empty?
return error_template(400, "Title cannot be empty.")
end
privacy = CompilationPrivacy.parse?(env.params.body["privacy"]?.try &.as(String) || "")
if !privacy
return error_template(400, "Invalid privacy setting.")
end
if Invidious::Database::Compilations.count_owned_by(user.email) >= 100
return error_template(400, "User cannot have more than 100 compilations.")
end
compilation = create_compilation(title, privacy, user)
env.redirect "/compilation?list=#{compilation.id}"
end
def self.delete_page(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)
return env.redirect "/" if user.nil?
user = user.as(User)
sid = sid.as(String)
compid = env.params.query["list"]?
if !compid || compid.empty?
return error_template(400, "A compilation ID is required")
end
compilation = Invidious::Database::Compilations.select(id: compid)
if !compilation || compilation.author != user.email
return env.redirect referer
end
csrf_token = generate_response(sid, {":delete_compilation"}, HMAC_KEY)
templated "delete_compilation"
end
def self.delete(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)
return env.redirect "/" if user.nil?
compid = env.params.query["list"]?
return env.redirect referer if compid.nil?
user = user.as(User)
sid = sid.as(String)
token = env.params.body["csrf_token"]?
begin
validate_request(token, sid, env.request, HMAC_KEY, locale)
rescue ex
return error_template(400, ex)
end
compilation = Invidious::Database::Compilations.select(id: compid)
if !compilation || compilation.author != user.email
return env.redirect referer
end
Invidious::Database::Compilations.delete(compid)
env.redirect "/feed/compilations"
end
def self.edit(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)
return env.redirect "/" if user.nil?
user = user.as(User)
sid = sid.as(String)
compid = env.params.query["list"]?
if !compid || !compid.starts_with?("IV")
return env.redirect referer
end
page = env.params.query["page"]?.try &.to_i?
page ||= 1
compilation = Invidious::Database::Compilations.select(id: compid)
if !compilation || compilation.author != user.email
return env.redirect referer
end
begin
videos = get_compilation_videos(compilation, offset: (page - 1) * 100)
rescue ex
videos = [] of CompilationVideo
end
csrf_token = generate_response(sid, {":edit_compilation"}, HMAC_KEY)
templated "edit_compilation"
end
def self.update(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)
return env.redirect "/" if user.nil?
compid = env.params.query["list"]?
return env.redirect referer if compid.nil?
user = user.as(User)
sid = sid.as(String)
token = env.params.body["csrf_token"]?
begin
validate_request(token, sid, env.request, HMAC_KEY, locale)
rescue ex
return error_template(400, ex)
end
compilation = Invidious::Database::Compilations.select(id: compid)
if !compilation || compilation.author != user.email
return env.redirect referer
end
title = env.params.body["title"]?.try &.delete("<>") || ""
privacy = CompilationPrivacy.parse(env.params.body["privacy"]? || "Unlisted")
description = env.params.body["description"]?.try &.delete("\r") || ""
if title != compilation.title ||
compilation != compilation.privacy ||
description != compilation.description
updated = Time.utc
else
updated = compilation.updated
end
Invidious::Database::Compilations.update(compid, title, privacy, description, updated)
env.redirect "/compilation?list=#{compid}"
end
def self.add_compilation_items_page(env)
prefs = env.get("preferences").as(Preferences)
locale = prefs.locale
region = env.params.query["region"]? || prefs.region
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env)
return env.redirect "/" if user.nil?
user = user.as(User)
sid = sid.as(String)
compid = env.params.query["list"]?
if !compid || !compid.starts_with?("IV")
return env.redirect referer
end
page = env.params.query["page"]?.try &.to_i?
page ||= 1
compilation = Invidious::Database::Compilations.select(id: compid)
if !compilation || compilation.author != user.email
return env.redirect referer
end
begin
query = Invidious::Search::Query.new(env.params.query, :compilation, region)
videos = query.process.select(SearchVideo).map(&.as(SearchVideo))
rescue ex
videos = [] of SearchVideo
end
env.set "add_compilation_items", compid
templated "add_compilation_items"
end
def self.compilation_ajax(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
sid = env.get? "sid"
referer = get_referer(env, "/")
redirect = env.params.query["redirect"]?
redirect ||= "true"
redirect = redirect == "true"
if !user
if redirect
return env.redirect referer
else
return error_json(403, "No such user")
end
end
user = user.as(User)
sid = sid.as(String)
token = env.params.body["csrf_token"]?
begin
validate_request(token, sid, env.request, HMAC_KEY, locale)
rescue ex
if redirect
return error_template(400, ex)
else
return error_json(400, ex)
end
end
if env.params.query["action_create_compilation"]?
action = "action_create_compilation"
elsif env.params.query["action_delete_compilation"]?
action = "action_delete_compilation"
elsif env.params.query["action_edit_compilation"]?
action = "action_edit_compilation"
elsif env.params.query["action_add_video"]?
action = "action_add_video"
video_id = env.params.query["video_id"]
elsif env.params.query["action_remove_video"]?
action = "action_remove_video"
elsif env.params.query["action_move_video_before"]?
action = "action_move_video_before"
else
return env.redirect referer
end
begin
compilation_id = env.params.query["compilation_id"]
compilation = get_compilation(compilation_id).as(InvidiousCompilation)
raise "Invalid user" if compilation.author != user.email
rescue ex : NotFoundException
return error_json(404, ex)
rescue ex
if redirect
return error_template(400, ex)
else
return error_json(400, ex)
end
end
email = user.email
case action
when "action_edit_compilation"
# TODO: Compilation stub
when "action_add_video"
if compilation.index.size >= CONFIG.compilation_length_limit
if redirect
return error_template(400, "Compilation cannot have more than #{CONFIG.compilation_length_limit} videos")
else
return error_json(400, "Compilation cannot have more than #{CONFIG.compilation_length_limit} videos")
end
end
video_id = env.params.query["video_id"]
begin
video = get_video(video_id)
rescue ex : NotFoundException
return error_json(404, ex)
rescue ex
if redirect
return error_template(500, ex)
else
return error_json(500, ex)
end
end
compilation_video = CompilationVideo.new({
title: video.title,
id: video.id,
author: video.author,
ucid: video.ucid,
length_seconds: video.length_seconds,
starting_timestamp_seconds: video.length_seconds,
ending_timestamp_seconds: video.length_seconds,
published: video.published,
compid: compilation_id,
live_now: video.live_now,
index: Random::Secure.rand(0_i64..Int64::MAX),
})
Invidious::Database::CompilationVideos.insert(compilation_video)
Invidious::Database::Compilations.update_video_added(compilation_id, compilation_video.index)
when "action_remove_video"
index = env.params.query["set_video_id"]
Invidious::Database::CompilationVideos.delete(index)
Invidious::Database::Compilations.update_video_removed(compilation_id, index)
when "action_move_video_before"
# TODO: Compilation stub
else
return error_json(400, "Unsupported action #{action}")
end
if redirect
env.redirect referer
else
env.response.content_type = "application/json"
"{}"
end
end
def self.show(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get?("user").try &.as(User)
referer = get_referer(env)
compid = env.params.query["list"]?.try &.gsub(/[^a-zA-Z0-9_-]/, "")
if !compid
return env.redirect "/"
end
page = env.params.query["page"]?.try &.to_i?
page ||= 1
if compid.starts_with? "RD"
return env.redirect "/mix?list=#{compid}"
end
begin
compilation = get_compilation(compid)
rescue ex : NotFoundException
return error_template(404, ex)
rescue ex
return error_template(500, ex)
end
page_count = (compilation.video_count / 200).to_i
page_count += 1 if (compilation.video_count % 200) > 0
if page > page_count
return env.redirect "/compilation?list=#{compid}&page=#{page_count}"
end
if compilation.privacy == CompilationPrivacy::Private && compilation.author != user.try &.email
return error_template(403, "This compilation is private.")
end
begin
videos = get_compilation_videos(compilation, offset: (page - 1) * 200)
rescue ex
return error_template(500, "Error encountered while retrieving compilation videos.<br>#{ex.message}")
end
if compilation.author == user.try &.email
env.set "remove_compilation_items", compid
end
templated "compilation"
end
end

View File

@ -0,0 +1,40 @@
<% content_for "header" do %>
<title><%= compilation.title %> - Invidious</title>
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/compilation/<%= compid %>" />
<% end %>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-5"></div>
<div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box">
<form class="pure-form pure-form-aligned" action="/add_compilation_items" method="get">
<legend><a href="/compilation?list=<%= compilation.id %>"><%= translate(locale, "Editing compilation `x`", %|"#{HTML.escape(compilation.title)}"|) %></a></legend>
<fieldset>
<input class="pure-input-1" type="search" name="q"
<% if query %>value="<%= HTML.escape(query.text) %>"<% end %>
placeholder="<%= translate(locale, "Search for videos") %>">
<input type="hidden" name="list" value="<%= compid %>">
</fieldset>
</form>
</div>
</div>
<div class="pure-u-1 pure-u-lg-1-5"></div>
</div>
<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=<%= ASSET_COMMIT %>"></script>
<div class="pure-g">
<% videos.each_slice(4) do |slice| %>
<% slice.each do |item| %>
<%= rendered "components/item" %>
<% end %>
<% end %>
</div>

View File

@ -1,7 +1,7 @@
<div class="feed-menu">
<% feed_menu = env.get("preferences").as(Preferences).feed_menu.dup %>
<% if !env.get?("user") %>
<% feed_menu.reject! {|item| {"Subscriptions", "Playlists"}.includes? item} %>
<% feed_menu.reject! {|item| {"Subscriptions", "Playlists", "Compilations"}.includes? item} %>
<% end %>
<% feed_menu.each do |feed| %>
<a href="/feed/<%= feed.downcase %>" class="feed-menu-item pure-menu-heading">

View File

@ -1,3 +1,39 @@
<% content_for "header" do %>
<title><%= translate(locale, "Create compilation") %> - Invidious</title>
<% end %>
<% end %>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-5"></div>
<div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box">
<form class="pure-form pure-form-aligned" action="/create_compilation?referer=<%= URI.encode_www_form(referer) %>" method="post">
<fieldset>
<legend><%= translate(locale, "Create compilation") %></legend>
<div class="pure-control-group">
<label for="title"><%= translate(locale, "Title") %> :</label>
<input required name="title" type="text" placeholder="<%= translate(locale, "Title") %>">
</div>
<div class="pure-control-group">
<label for="privacy"><%= translate(locale, "Compilation privacy") %> :</label>
<select name="privacy" id="privacy">
<% CompilationPrivacy.names.each do |option| %>
<option value="<%= option %>" <% if option == "Unlisted" %> selected <% end %>><%= translate(locale, option) %></option>
<% end %>
</select>
</div>
<div class="pure-controls">
<button type="submit" name="action" value="create_compilation" class="pure-button pure-button-primary">
<%= translate(locale, "Create compilation") %>
</button>
</div>
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
</fieldset>
</form>
</div>
</div>
<div class="pure-u-1 pure-u-lg-1-5"></div>
</div>