Add option to configure default user preferences

This commit is contained in:
Omar Roth 2019-04-03 11:35:58 -05:00
parent 1fd7ff5655
commit bd4f5ebcdf
9 changed files with 192 additions and 90 deletions

View File

@ -1198,22 +1198,22 @@ post "/preferences" do |env|
local = local == "on" local = local == "on"
speed = env.params.body["speed"]?.try &.as(String).to_f? speed = env.params.body["speed"]?.try &.as(String).to_f?
speed ||= DEFAULT_USER_PREFERENCES.speed speed ||= CONFIG.default_user_preferences.speed
quality = env.params.body["quality"]?.try &.as(String) quality = env.params.body["quality"]?.try &.as(String)
quality ||= DEFAULT_USER_PREFERENCES.quality quality ||= CONFIG.default_user_preferences.quality
volume = env.params.body["volume"]?.try &.as(String).to_i? volume = env.params.body["volume"]?.try &.as(String).to_i?
volume ||= DEFAULT_USER_PREFERENCES.volume volume ||= CONFIG.default_user_preferences.volume
comments = [] of String comments = [] of String
2.times do |i| 2.times do |i|
comments << (env.params.body["comments[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[i]) comments << (env.params.body["comments[#{i}]"]?.try &.as(String) || CONFIG.default_user_preferences.comments[i])
end end
captions = [] of String captions = [] of String
3.times do |i| 3.times do |i|
captions << (env.params.body["captions[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[i]) captions << (env.params.body["captions[#{i}]"]?.try &.as(String) || CONFIG.default_user_preferences.captions[i])
end end
related_videos = env.params.body["related_videos"]?.try &.as(String) related_videos = env.params.body["related_videos"]?.try &.as(String)
@ -1225,7 +1225,7 @@ post "/preferences" do |env|
redirect_feed = redirect_feed == "on" redirect_feed = redirect_feed == "on"
locale = env.params.body["locale"]?.try &.as(String) locale = env.params.body["locale"]?.try &.as(String)
locale ||= DEFAULT_USER_PREFERENCES.locale locale ||= CONFIG.default_user_preferences.locale
dark_mode = env.params.body["dark_mode"]?.try &.as(String) dark_mode = env.params.body["dark_mode"]?.try &.as(String)
dark_mode ||= "off" dark_mode ||= "off"
@ -1236,10 +1236,10 @@ post "/preferences" do |env|
thin_mode = thin_mode == "on" thin_mode = thin_mode == "on"
max_results = env.params.body["max_results"]?.try &.as(String).to_i? max_results = env.params.body["max_results"]?.try &.as(String).to_i?
max_results ||= DEFAULT_USER_PREFERENCES.max_results max_results ||= CONFIG.default_user_preferences.max_results
sort = env.params.body["sort"]?.try &.as(String) sort = env.params.body["sort"]?.try &.as(String)
sort ||= DEFAULT_USER_PREFERENCES.sort sort ||= CONFIG.default_user_preferences.sort
latest_only = env.params.body["latest_only"]?.try &.as(String) latest_only = env.params.body["latest_only"]?.try &.as(String)
latest_only ||= "off" latest_only ||= "off"

View File

@ -1,5 +1,5 @@
struct InvidiousChannel struct InvidiousChannel
add_mapping({ db_mapping({
id: String, id: String,
author: String, author: String,
updated: Time, updated: Time,
@ -9,7 +9,7 @@ struct InvidiousChannel
end end
struct ChannelVideo struct ChannelVideo
add_mapping({ db_mapping({
id: String, id: String,
title: String, title: String,
published: Time, published: Time,

View File

@ -1,4 +1,76 @@
require "./macros"
struct ConfigPreferences
module StringToArray
def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder)
yaml.sequence do
value.each do |element|
yaml.scalar element
end
end
end
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Array(String)
begin
unless node.is_a?(YAML::Nodes::Sequence)
node.raise "Expected sequence, not #{node.class}"
end
result = [] of String
node.nodes.each do
unless node.is_a?(YAML::Nodes::Scalar)
node.raise "Expected scalar, not #{node.class}"
end
result << node.value
end
rescue ex
if node.is_a?(YAML::Nodes::Scalar)
result = [node.value, ""]
else
result = ["", ""]
end
end
result
end
end
yaml_mapping({
autoplay: {type: Bool, default: false},
captions: {type: Array(String), default: ["", "", ""], converter: StringToArray},
comments: {type: Array(String), default: ["youtube", ""], converter: StringToArray},
continue: {type: Bool, default: false},
dark_mode: {type: Bool, default: false},
latest_only: {type: Bool, default: false},
listen: {type: Bool, default: false},
local: {type: Bool, default: false},
locale: {type: String, default: "en-US"},
max_results: {type: Int32, default: 40},
notifications_only: {type: Bool, default: false},
quality: {type: String, default: "hd720"},
redirect_feed: {type: Bool, default: false},
related_videos: {type: Bool, default: true},
sort: {type: String, default: "published"},
speed: {type: Float32, default: 1.0_f32},
thin_mode: {type: Bool, default: false},
unseen_only: {type: Bool, default: false},
video_loop: {type: Bool, default: false},
volume: {type: Int32, default: 100},
})
end
struct Config struct Config
module ConfigPreferencesConverter
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Preferences
Preferences.new(*ConfigPreferences.new(ctx, node).to_tuple)
end
def self.to_yaml(value : Preferences, yaml : YAML::Nodes::Builder)
value.to_yaml(yaml)
end
end
YAML.mapping({ YAML.mapping({
channel_threads: Int32, # Number of threads to use for crawling videos from channels (for updating subscriptions) channel_threads: Int32, # Number of threads to use for crawling videos from channels (for updating subscriptions)
feed_threads: Int32, # Number of threads to use for updating feeds feed_threads: Int32, # Number of threads to use for updating feeds
@ -9,20 +81,24 @@ user: String,
port: Int32, port: Int32,
dbname: String, dbname: String,
), ),
full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel
https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https://
hmac_key: String?, # HMAC signing key for CSRF tokens and verifying pubsub subscriptions hmac_key: String?, # HMAC signing key for CSRF tokens and verifying pubsub subscriptions
domain: String?, # Domain to be used for links to resources on the site where an absolute URL is required domain: String?, # Domain to be used for links to resources on the site where an absolute URL is required
use_pubsub_feeds: {type: Bool, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) use_pubsub_feeds: {type: Bool, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key)
default_home: {type: String, default: "Top"}, default_home: {type: String, default: "Top"},
feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending", "Subscriptions"]}, feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending", "Subscriptions"]},
top_enabled: {type: Bool, default: true}, top_enabled: {type: Bool, default: true},
captcha_enabled: {type: Bool, default: true}, captcha_enabled: {type: Bool, default: true},
login_enabled: {type: Bool, default: true}, login_enabled: {type: Bool, default: true},
registration_enabled: {type: Bool, default: true}, registration_enabled: {type: Bool, default: true},
statistics_enabled: {type: Bool, default: false}, statistics_enabled: {type: Bool, default: false},
admins: {type: Array(String), default: [] of String}, admins: {type: Array(String), default: [] of String},
external_port: {type: Int32 | Nil, default: nil}, external_port: {type: Int32?, default: nil},
default_user_preferences: {type: Preferences,
default: Preferences.new(*ConfigPreferences.from_yaml("").to_tuple),
converter: ConfigPreferencesConverter,
},
}) })
end end

View File

@ -1,4 +1,4 @@
macro add_mapping(mapping) macro db_mapping(mapping)
def initialize({{*mapping.keys.map { |id| "@#{id}".id }}}) def initialize({{*mapping.keys.map { |id| "@#{id}".id }}})
end end
@ -18,6 +18,22 @@ macro json_mapping(mapping)
end end
JSON.mapping({{mapping}}) JSON.mapping({{mapping}})
YAML.mapping({{mapping}})
end
macro yaml_mapping(mapping)
def initialize({{*mapping.keys.map { |id| "@#{id}".id }}})
end
def to_a
return [{{*mapping.keys.map { |id| "@#{id}".id }}}]
end
def to_tuple
return { {{*mapping.keys.map { |id| "@#{id}".id }}} }
end
YAML.mapping({{mapping}})
end end
macro templated(filename, template = "template") macro templated(filename, template = "template")

View File

@ -1,5 +1,5 @@
struct MixVideo struct MixVideo
add_mapping({ db_mapping({
title: String, title: String,
id: String, id: String,
author: String, author: String,
@ -11,7 +11,7 @@ struct MixVideo
end end
struct Mix struct Mix
add_mapping({ db_mapping({
title: String, title: String,
id: String, id: String,
videos: Array(MixVideo), videos: Array(MixVideo),

View File

@ -1,5 +1,5 @@
struct PlaylistVideo struct PlaylistVideo
add_mapping({ db_mapping({
title: String, title: String,
id: String, id: String,
author: String, author: String,
@ -13,7 +13,7 @@ struct PlaylistVideo
end end
struct Playlist struct Playlist
add_mapping({ db_mapping({
title: String, title: String,
id: String, id: String,
author: String, author: String,

View File

@ -1,5 +1,5 @@
struct SearchVideo struct SearchVideo
add_mapping({ db_mapping({
title: String, title: String,
id: String, id: String,
author: String, author: String,
@ -17,7 +17,7 @@ struct SearchVideo
end end
struct SearchPlaylistVideo struct SearchPlaylistVideo
add_mapping({ db_mapping({
title: String, title: String,
id: String, id: String,
length_seconds: Int32, length_seconds: Int32,
@ -25,7 +25,7 @@ struct SearchPlaylistVideo
end end
struct SearchPlaylist struct SearchPlaylist
add_mapping({ db_mapping({
title: String, title: String,
id: String, id: String,
author: String, author: String,
@ -37,7 +37,7 @@ struct SearchPlaylist
end end
struct SearchChannel struct SearchChannel
add_mapping({ db_mapping({
author: String, author: String,
ucid: String, ucid: String,
author_thumbnail: String, author_thumbnail: String,

View File

@ -11,7 +11,7 @@ struct User
end end
end end
add_mapping({ db_mapping({
updated: Time, updated: Time,
notifications: Array(String), notifications: Array(String),
subscriptions: Array(String), subscriptions: Array(String),
@ -26,29 +26,6 @@ struct User
}) })
end end
DEFAULT_USER_PREFERENCES = Preferences.new(
video_loop: false,
autoplay: false,
continue: false,
local: false,
listen: false,
speed: 1.0_f32,
quality: "hd720",
volume: 100,
comments: ["youtube", ""],
captions: ["", "", ""],
related_videos: true,
redirect_feed: false,
locale: "en-US",
dark_mode: false,
thin_mode: false,
max_results: 40,
sort: "published",
latest_only: false,
unseen_only: false,
notifications_only: false,
)
struct Preferences struct Preferences
module StringToArray module StringToArray
def self.to_json(value : Array(String), json : JSON::Builder) def self.to_json(value : Array(String), json : JSON::Builder)
@ -71,29 +48,62 @@ struct Preferences
result result
end end
def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder)
yaml.sequence do
value.each do |element|
yaml.scalar element
end
end
end
def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Array(String)
begin
unless node.is_a?(YAML::Nodes::Sequence)
node.raise "Expected sequence, not #{node.class}"
end
result = [] of String
node.nodes.each do
unless node.is_a?(YAML::Nodes::Scalar)
node.raise "Expected scalar, not #{node.class}"
end
result << node.value
end
rescue ex
if node.is_a?(YAML::Nodes::Scalar)
result = [node.value, ""]
else
result = ["", ""]
end
end
result
end
end end
json_mapping({ json_mapping({
video_loop: {type: Bool, default: DEFAULT_USER_PREFERENCES.video_loop}, autoplay: {type: Bool, default: CONFIG.default_user_preferences.autoplay},
autoplay: {type: Bool, default: DEFAULT_USER_PREFERENCES.autoplay}, captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: StringToArray},
continue: {type: Bool, default: DEFAULT_USER_PREFERENCES.continue}, comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: StringToArray},
local: {type: Bool, default: DEFAULT_USER_PREFERENCES.local}, continue: {type: Bool, default: CONFIG.default_user_preferences.continue},
listen: {type: Bool, default: DEFAULT_USER_PREFERENCES.listen}, dark_mode: {type: Bool, default: CONFIG.default_user_preferences.dark_mode},
speed: {type: Float32, default: DEFAULT_USER_PREFERENCES.speed}, latest_only: {type: Bool, default: CONFIG.default_user_preferences.latest_only},
quality: {type: String, default: DEFAULT_USER_PREFERENCES.quality}, listen: {type: Bool, default: CONFIG.default_user_preferences.listen},
volume: {type: Int32, default: DEFAULT_USER_PREFERENCES.volume}, local: {type: Bool, default: CONFIG.default_user_preferences.local},
comments: {type: Array(String), default: DEFAULT_USER_PREFERENCES.comments, converter: StringToArray}, locale: {type: String, default: CONFIG.default_user_preferences.locale},
captions: {type: Array(String), default: DEFAULT_USER_PREFERENCES.captions, converter: StringToArray}, max_results: {type: Int32, default: CONFIG.default_user_preferences.max_results},
redirect_feed: {type: Bool, default: DEFAULT_USER_PREFERENCES.redirect_feed}, notifications_only: {type: Bool, default: CONFIG.default_user_preferences.notifications_only},
related_videos: {type: Bool, default: DEFAULT_USER_PREFERENCES.related_videos}, quality: {type: String, default: CONFIG.default_user_preferences.quality},
dark_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.dark_mode}, redirect_feed: {type: Bool, default: CONFIG.default_user_preferences.redirect_feed},
thin_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.thin_mode}, related_videos: {type: Bool, default: CONFIG.default_user_preferences.related_videos},
max_results: {type: Int32, default: DEFAULT_USER_PREFERENCES.max_results}, sort: {type: String, default: CONFIG.default_user_preferences.sort},
sort: {type: String, default: DEFAULT_USER_PREFERENCES.sort}, speed: {type: Float32, default: CONFIG.default_user_preferences.speed},
latest_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.latest_only}, thin_mode: {type: Bool, default: CONFIG.default_user_preferences.thin_mode},
unseen_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.unseen_only}, unseen_only: {type: Bool, default: CONFIG.default_user_preferences.unseen_only},
notifications_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.notifications_only}, video_loop: {type: Bool, default: CONFIG.default_user_preferences.video_loop},
locale: {type: String, default: DEFAULT_USER_PREFERENCES.locale}, volume: {type: Int32, default: CONFIG.default_user_preferences.volume},
}) })
end end
@ -174,7 +184,7 @@ def fetch_user(sid, headers, db)
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
user = User.new(Time.now, [] of String, channels, email, DEFAULT_USER_PREFERENCES, nil, token, [] of String) user = User.new(Time.now, [] of String, channels, email, CONFIG.default_user_preferences, nil, token, [] of String)
return user, sid return user, sid
end end
@ -182,7 +192,7 @@ def create_user(sid, email, password)
password = Crypto::Bcrypt::Password.create(password, cost: 10) password = Crypto::Bcrypt::Password.create(password, cost: 10)
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
user = User.new(Time.now, [] of String, [] of String, email, DEFAULT_USER_PREFERENCES, password.to_s, token, [] of String) user = User.new(Time.now, [] of String, [] of String, email, CONFIG.default_user_preferences, password.to_s, token, [] of String)
return user, sid return user, sid
end end

View File

@ -521,7 +521,7 @@ struct Video
return self.info["length_seconds"].to_i return self.info["length_seconds"].to_i
end end
add_mapping({ db_mapping({
id: String, id: String,
info: { info: {
type: HTTP::Params, type: HTTP::Params,
@ -818,16 +818,16 @@ def process_video_params(query, preferences)
volume ||= preferences.volume volume ||= preferences.volume
end end
autoplay ||= DEFAULT_USER_PREFERENCES.autoplay.to_unsafe autoplay ||= CONFIG.default_user_preferences.autoplay.to_unsafe
continue ||= DEFAULT_USER_PREFERENCES.continue.to_unsafe continue ||= CONFIG.default_user_preferences.continue.to_unsafe
listen ||= DEFAULT_USER_PREFERENCES.listen.to_unsafe listen ||= CONFIG.default_user_preferences.listen.to_unsafe
local ||= DEFAULT_USER_PREFERENCES.local.to_unsafe local ||= CONFIG.default_user_preferences.local.to_unsafe
preferred_captions ||= DEFAULT_USER_PREFERENCES.captions preferred_captions ||= CONFIG.default_user_preferences.captions
quality ||= DEFAULT_USER_PREFERENCES.quality quality ||= CONFIG.default_user_preferences.quality
related_videos ||= DEFAULT_USER_PREFERENCES.related_videos.to_unsafe related_videos ||= CONFIG.default_user_preferences.related_videos.to_unsafe
speed ||= DEFAULT_USER_PREFERENCES.speed speed ||= CONFIG.default_user_preferences.speed
video_loop ||= DEFAULT_USER_PREFERENCES.video_loop.to_unsafe video_loop ||= CONFIG.default_user_preferences.video_loop.to_unsafe
volume ||= DEFAULT_USER_PREFERENCES.volume volume ||= CONFIG.default_user_preferences.volume
autoplay = autoplay == 1 autoplay = autoplay == 1
continue = continue == 1 continue = continue == 1