diff --git a/src/invidious.cr b/src/invidious.cr index d579ff4e..e97198d8 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1198,22 +1198,22 @@ post "/preferences" do |env| local = local == "on" 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 ||= DEFAULT_USER_PREFERENCES.quality + quality ||= CONFIG.default_user_preferences.quality volume = env.params.body["volume"]?.try &.as(String).to_i? - volume ||= DEFAULT_USER_PREFERENCES.volume + volume ||= CONFIG.default_user_preferences.volume comments = [] of String 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 captions = [] of String 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 related_videos = env.params.body["related_videos"]?.try &.as(String) @@ -1225,7 +1225,7 @@ post "/preferences" do |env| redirect_feed = redirect_feed == "on" 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 ||= "off" @@ -1236,10 +1236,10 @@ post "/preferences" do |env| thin_mode = thin_mode == "on" 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 ||= DEFAULT_USER_PREFERENCES.sort + sort ||= CONFIG.default_user_preferences.sort latest_only = env.params.body["latest_only"]?.try &.as(String) latest_only ||= "off" diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index b24159db..0a2f3503 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -1,5 +1,5 @@ struct InvidiousChannel - add_mapping({ + db_mapping({ id: String, author: String, updated: Time, @@ -9,7 +9,7 @@ struct InvidiousChannel end struct ChannelVideo - add_mapping({ + db_mapping({ id: String, title: String, published: Time, diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 33d7b6f4..f6c22f64 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -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 + 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({ 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 @@ -9,20 +81,24 @@ user: String, port: Int32, dbname: String, ), - 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:// - 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 - use_pubsub_feeds: {type: Bool, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) - default_home: {type: String, default: "Top"}, - feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending", "Subscriptions"]}, - top_enabled: {type: Bool, default: true}, - captcha_enabled: {type: Bool, default: true}, - login_enabled: {type: Bool, default: true}, - registration_enabled: {type: Bool, default: true}, - statistics_enabled: {type: Bool, default: false}, - admins: {type: Array(String), default: [] of String}, - external_port: {type: Int32 | Nil, default: nil}, + 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:// + 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 + use_pubsub_feeds: {type: Bool, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) + default_home: {type: String, default: "Top"}, + feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending", "Subscriptions"]}, + top_enabled: {type: Bool, default: true}, + captcha_enabled: {type: Bool, default: true}, + login_enabled: {type: Bool, default: true}, + registration_enabled: {type: Bool, default: true}, + statistics_enabled: {type: Bool, default: false}, + admins: {type: Array(String), default: [] of String}, + external_port: {type: Int32?, default: nil}, + default_user_preferences: {type: Preferences, + default: Preferences.new(*ConfigPreferences.from_yaml("").to_tuple), + converter: ConfigPreferencesConverter, + }, }) end diff --git a/src/invidious/helpers/macros.cr b/src/invidious/helpers/macros.cr index 5991faaf..fda34ed7 100644 --- a/src/invidious/helpers/macros.cr +++ b/src/invidious/helpers/macros.cr @@ -1,4 +1,4 @@ -macro add_mapping(mapping) +macro db_mapping(mapping) def initialize({{*mapping.keys.map { |id| "@#{id}".id }}}) end @@ -18,6 +18,22 @@ macro json_mapping(mapping) end 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 macro templated(filename, template = "template") diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index d290ac14..65e03a06 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -1,5 +1,5 @@ struct MixVideo - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -11,7 +11,7 @@ struct MixVideo end struct Mix - add_mapping({ + db_mapping({ title: String, id: String, videos: Array(MixVideo), diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 027bcae5..d04a5942 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -1,5 +1,5 @@ struct PlaylistVideo - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -13,7 +13,7 @@ struct PlaylistVideo end struct Playlist - add_mapping({ + db_mapping({ title: String, id: String, author: String, diff --git a/src/invidious/search.cr b/src/invidious/search.cr index 04eb63a2..9c985552 100644 --- a/src/invidious/search.cr +++ b/src/invidious/search.cr @@ -1,5 +1,5 @@ struct SearchVideo - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -17,7 +17,7 @@ struct SearchVideo end struct SearchPlaylistVideo - add_mapping({ + db_mapping({ title: String, id: String, length_seconds: Int32, @@ -25,7 +25,7 @@ struct SearchPlaylistVideo end struct SearchPlaylist - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -37,7 +37,7 @@ struct SearchPlaylist end struct SearchChannel - add_mapping({ + db_mapping({ author: String, ucid: String, author_thumbnail: String, diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 9140378e..80fc496c 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -11,7 +11,7 @@ struct User end end - add_mapping({ + db_mapping({ updated: Time, notifications: Array(String), subscriptions: Array(String), @@ -26,29 +26,6 @@ struct User }) 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 module StringToArray def self.to_json(value : Array(String), json : JSON::Builder) @@ -71,29 +48,62 @@ struct Preferences result 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 json_mapping({ - video_loop: {type: Bool, default: DEFAULT_USER_PREFERENCES.video_loop}, - autoplay: {type: Bool, default: DEFAULT_USER_PREFERENCES.autoplay}, - continue: {type: Bool, default: DEFAULT_USER_PREFERENCES.continue}, - local: {type: Bool, default: DEFAULT_USER_PREFERENCES.local}, - listen: {type: Bool, default: DEFAULT_USER_PREFERENCES.listen}, - speed: {type: Float32, default: DEFAULT_USER_PREFERENCES.speed}, - quality: {type: String, default: DEFAULT_USER_PREFERENCES.quality}, - volume: {type: Int32, default: DEFAULT_USER_PREFERENCES.volume}, - comments: {type: Array(String), default: DEFAULT_USER_PREFERENCES.comments, converter: StringToArray}, - captions: {type: Array(String), default: DEFAULT_USER_PREFERENCES.captions, converter: StringToArray}, - redirect_feed: {type: Bool, default: DEFAULT_USER_PREFERENCES.redirect_feed}, - related_videos: {type: Bool, default: DEFAULT_USER_PREFERENCES.related_videos}, - dark_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.dark_mode}, - thin_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.thin_mode}, - max_results: {type: Int32, default: DEFAULT_USER_PREFERENCES.max_results}, - sort: {type: String, default: DEFAULT_USER_PREFERENCES.sort}, - latest_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.latest_only}, - unseen_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.unseen_only}, - notifications_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.notifications_only}, - locale: {type: String, default: DEFAULT_USER_PREFERENCES.locale}, + autoplay: {type: Bool, default: CONFIG.default_user_preferences.autoplay}, + captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: StringToArray}, + comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: StringToArray}, + continue: {type: Bool, default: CONFIG.default_user_preferences.continue}, + dark_mode: {type: Bool, default: CONFIG.default_user_preferences.dark_mode}, + latest_only: {type: Bool, default: CONFIG.default_user_preferences.latest_only}, + listen: {type: Bool, default: CONFIG.default_user_preferences.listen}, + local: {type: Bool, default: CONFIG.default_user_preferences.local}, + locale: {type: String, default: CONFIG.default_user_preferences.locale}, + max_results: {type: Int32, default: CONFIG.default_user_preferences.max_results}, + notifications_only: {type: Bool, default: CONFIG.default_user_preferences.notifications_only}, + quality: {type: String, default: CONFIG.default_user_preferences.quality}, + redirect_feed: {type: Bool, default: CONFIG.default_user_preferences.redirect_feed}, + related_videos: {type: Bool, default: CONFIG.default_user_preferences.related_videos}, + sort: {type: String, default: CONFIG.default_user_preferences.sort}, + speed: {type: Float32, default: CONFIG.default_user_preferences.speed}, + thin_mode: {type: Bool, default: CONFIG.default_user_preferences.thin_mode}, + unseen_only: {type: Bool, default: CONFIG.default_user_preferences.unseen_only}, + video_loop: {type: Bool, default: CONFIG.default_user_preferences.video_loop}, + volume: {type: Int32, default: CONFIG.default_user_preferences.volume}, }) end @@ -174,7 +184,7 @@ def fetch_user(sid, headers, db) 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 end @@ -182,7 +192,7 @@ def create_user(sid, email, password) password = Crypto::Bcrypt::Password.create(password, cost: 10) 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 end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 0e9cf6c6..856b6447 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -521,7 +521,7 @@ struct Video return self.info["length_seconds"].to_i end - add_mapping({ + db_mapping({ id: String, info: { type: HTTP::Params, @@ -818,16 +818,16 @@ def process_video_params(query, preferences) volume ||= preferences.volume end - autoplay ||= DEFAULT_USER_PREFERENCES.autoplay.to_unsafe - continue ||= DEFAULT_USER_PREFERENCES.continue.to_unsafe - listen ||= DEFAULT_USER_PREFERENCES.listen.to_unsafe - local ||= DEFAULT_USER_PREFERENCES.local.to_unsafe - preferred_captions ||= DEFAULT_USER_PREFERENCES.captions - quality ||= DEFAULT_USER_PREFERENCES.quality - related_videos ||= DEFAULT_USER_PREFERENCES.related_videos.to_unsafe - speed ||= DEFAULT_USER_PREFERENCES.speed - video_loop ||= DEFAULT_USER_PREFERENCES.video_loop.to_unsafe - volume ||= DEFAULT_USER_PREFERENCES.volume + autoplay ||= CONFIG.default_user_preferences.autoplay.to_unsafe + continue ||= CONFIG.default_user_preferences.continue.to_unsafe + listen ||= CONFIG.default_user_preferences.listen.to_unsafe + local ||= CONFIG.default_user_preferences.local.to_unsafe + preferred_captions ||= CONFIG.default_user_preferences.captions + quality ||= CONFIG.default_user_preferences.quality + related_videos ||= CONFIG.default_user_preferences.related_videos.to_unsafe + speed ||= CONFIG.default_user_preferences.speed + video_loop ||= CONFIG.default_user_preferences.video_loop.to_unsafe + volume ||= CONFIG.default_user_preferences.volume autoplay = autoplay == 1 continue = continue == 1