diff --git a/assets/css/default.css b/assets/css/default.css index 2cedcf0c..995b8440 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -550,6 +550,11 @@ span > select { color: #565d64; } +.light-theme .video-badges > span { + background: rgb(235, 235, 235); + color: #828282; +} + @media (prefers-color-scheme: light) { .no-theme a:hover, .no-theme a:active, @@ -596,6 +601,11 @@ span > select { .light-theme .pure-menu-heading { color: #565d64; } + + .no-theme .video-badges > span { + background: rgb(235, 235, 235); + color: #828282; + } } @@ -658,6 +668,12 @@ body.dark-theme { color: inherit; } +.dark-theme .video-badges > span { + background: rgb(50, 50, 50); + color: #9e9e9e; +} + + @media (prefers-color-scheme: dark) { .no-theme a:hover, .no-theme a:active, @@ -719,6 +735,11 @@ body.dark-theme { .no-theme footer a { color: #adadad !important; } + + .no-theme .video-badges > span { + background: rgb(50, 50, 50); + color: #9e9e9e; + } } @@ -816,3 +837,19 @@ h1, h2, h3, h4, h5, p, #download_widget { width: 100%; } + +.video-badges > span { + display: flex; + align-items: center; + gap: 5px; + + padding: 2px 10px; + border-radius: 10px; + + font-size: 12px; + font-weight: 600; +} + +.video-badges > i { + margin-right: 5px; +} \ No newline at end of file diff --git a/config/config.example.yml b/config/config.example.yml index 8d3e6212..49511488 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -1008,3 +1008,12 @@ default_user_preferences: ## Default: false ## #extend_desc: false + + ## + ## Allows excluding videos that are exclusive to channel members + ## from the frontend + ## + ## Accepted values: true, false + ## Default: false + ## + #exclude_members_only_videos: false diff --git a/locales/en-US.json b/locales/en-US.json index 4f2c2770..93c99e81 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -501,5 +501,8 @@ "toggle_theme": "Toggle Theme", "carousel_slide": "Slide {{current}} of {{total}}", "carousel_skip": "Skip the Carousel", - "carousel_go_to": "Go to slide `x`" + "carousel_go_to": "Go to slide `x`", + "video_badges_members_only": "Members only", + "video_badges_members_first": "Members first", + "preferences_exclude_members_only_videos_label": "Hide channel member-only videos" } diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 4d69854c..2f64071a 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -52,6 +52,7 @@ struct ConfigPreferences property vr_mode : Bool = true property show_nick : Bool = true property save_player_pos : Bool = false + property exclude_members_only_videos : Bool = false def to_tuple {% begin %} diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index f8e8f187..0cdbcd1b 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -9,6 +9,8 @@ enum VideoBadges VR180 VR360 ClosedCaptions + MembersOnly + MembersFirst end struct SearchVideo @@ -133,6 +135,7 @@ struct SearchVideo json.field "isVr360", self.badges.vr360? json.field "is3d", self.badges.three_d? json.field "hasCaptions", self.badges.closed_captions? + json.field "isMembersOnly", self.badges.members_only? end end @@ -150,6 +153,13 @@ struct SearchVideo def upcoming? premiere_timestamp ? true : false end + + # Shorthand to check whether the video is restricted to only channel members + # + # Whether as an early access video ("members first") or only for members ("members only") + def restricted_to_members? + return badges.members_only? || badges.members_first? + end end struct SearchPlaylistVideo diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index 39ca77c0..87fc1c67 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -144,6 +144,10 @@ module Invidious::Routes::PreferencesRoute notifications_only ||= "off" notifications_only = notifications_only == "on" + exclude_members_only_videos = env.params.body["exclude_members_only_videos"]?.try &.as(String) + exclude_members_only_videos ||= "off" + exclude_members_only_videos = exclude_members_only_videos == "on" + # Convert to JSON and back again to take advantage of converters used for compatibility preferences = Preferences.from_json({ annotations: annotations, @@ -180,6 +184,7 @@ module Invidious::Routes::PreferencesRoute vr_mode: vr_mode, show_nick: show_nick, save_player_pos: save_player_pos, + exclude_members_only_videos: exclude_members_only_videos, }.to_json) if user = env.get? "user" diff --git a/src/invidious/user/preferences.cr b/src/invidious/user/preferences.cr index 0a8525f3..979c15d0 100644 --- a/src/invidious/user/preferences.cr +++ b/src/invidious/user/preferences.cr @@ -57,6 +57,8 @@ struct Preferences property volume : Int32 = CONFIG.default_user_preferences.volume property save_player_pos : Bool = CONFIG.default_user_preferences.save_player_pos + property exclude_members_only_videos : Bool = CONFIG.default_user_preferences.exclude_members_only_videos + module BoolToString def self.to_json(value : String, json : JSON::Builder) json.string value diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index c966a926..91154af7 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -200,6 +200,19 @@ <% end %> + <% if item.responds_to?(:badges) && !item.badges.none? %> +