From e1b2eb2a7bd98ee9d8179e48977ec4f386fc71ba Mon Sep 17 00:00:00 2001 From: syeopite Date: Wed, 26 Mar 2025 14:04:57 -0700 Subject: [PATCH 1/5] Parse members only badges of videos --- src/invidious/helpers/serialized_yt_data.cr | 2 ++ src/invidious/yt_backend/extractors.cr | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index f8e8f187..69dc91d6 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -9,6 +9,7 @@ enum VideoBadges VR180 VR360 ClosedCaptions + MembersOnly end struct SearchVideo @@ -133,6 +134,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 diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index edd7bf1b..c0994ffb 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -135,6 +135,10 @@ private module Parsers when "Premium" # TODO: Potentially available as item_contents["topStandaloneBadge"]["metadataBadgeRenderer"] badges |= VideoBadges::Premium + when "Members only" + # TODO: Identify based on style attribute instead of label + # It should be more resistant to Youtube changes. + badges |= VideoBadges::MembersOnly else nil # Ignore end end From 7c0117253a60f228a377b64bd55f8f291e871c05 Mon Sep 17 00:00:00 2001 From: syeopite Date: Wed, 26 Mar 2025 14:45:02 -0700 Subject: [PATCH 2/5] Add badge to UI to differentiate member videos --- assets/css/default.css | 37 +++++++++++++++++++++++++ locales/en-US.json | 3 +- src/invidious/views/components/item.ecr | 10 +++++++ 3 files changed, 49 insertions(+), 1 deletion(-) 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/locales/en-US.json b/locales/en-US.json index 4f2c2770..78ec0203 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -501,5 +501,6 @@ "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" } diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index c966a926..59282165 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -200,6 +200,16 @@ <% end %> + <% if item.responds_to?(:badges) && !item.badges.none? %> +
+ <% + # TODO Other types of badges + %> + <% if item.badges.members_only? %> + <%=translate(locale, "video_badges_members_only")%> + <% end %> +
+ <%end%> <% end %> From 643675490d45101b29411a94234a9cbde336c0ed Mon Sep 17 00:00:00 2001 From: syeopite Date: Wed, 26 Mar 2025 14:57:17 -0700 Subject: [PATCH 3/5] Add setting to hide member-only videos --- locales/en-US.json | 3 ++- src/invidious/config.cr | 1 + src/invidious/routes/preferences.cr | 5 +++++ src/invidious/user/preferences.cr | 2 ++ src/invidious/views/components/items_paginated.ecr | 3 +++ src/invidious/views/feeds/trending.ecr | 1 + src/invidious/views/user/preferences.ecr | 6 ++++++ 7 files changed, 20 insertions(+), 1 deletion(-) diff --git a/locales/en-US.json b/locales/en-US.json index 78ec0203..9a7ac3cf 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -502,5 +502,6 @@ "carousel_slide": "Slide {{current}} of {{total}}", "carousel_skip": "Skip the Carousel", "carousel_go_to": "Go to slide `x`", - "video_badges_members_only": "Members only" + "video_badges_members_only": "Members only", + "preferences_exclude_members_only_videos_label": "Hide channel member-only videos" } diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 140b0daf..c1c00c5e 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/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/items_paginated.ecr b/src/invidious/views/components/items_paginated.ecr index f69df3fe..1863b7d4 100644 --- a/src/invidious/views/components/items_paginated.ecr +++ b/src/invidious/views/components/items_paginated.ecr @@ -1,7 +1,10 @@ <%= page_nav_html %> +<% exclude_members_only_videos = env.get("preferences").as(Preferences).exclude_members_only_videos %>
<%- items.each do |item| -%> + <% next if exclude_members_only_videos && item.responds_to?(:badges) && + item.is_a? SearchVideo && item.badges.members_only? %> <%= rendered "components/item" %> <%- end -%>
diff --git a/src/invidious/views/feeds/trending.ecr b/src/invidious/views/feeds/trending.ecr index 7dc416c6..002f3a72 100644 --- a/src/invidious/views/feeds/trending.ecr +++ b/src/invidious/views/feeds/trending.ecr @@ -42,6 +42,7 @@
<% trending.each do |item| %> + <% next %> <%= rendered "components/item" %> <% end %>
diff --git a/src/invidious/views/user/preferences.ecr b/src/invidious/views/user/preferences.ecr index cf8b5593..dd324879 100644 --- a/src/invidious/views/user/preferences.ecr +++ b/src/invidious/views/user/preferences.ecr @@ -194,6 +194,12 @@ <% end %> + +
+ + checked<% end %>> +
+ <% if env.get? "user" %>
From 2a50d20715dc01134dd60a466e32cebe651216f1 Mon Sep 17 00:00:00 2001 From: syeopite Date: Thu, 10 Apr 2025 01:21:00 -0700 Subject: [PATCH 4/5] Document `exclude_members_only_videos` --- config/config.example.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/config/config.example.yml b/config/config.example.yml index 8484c6be..ee608e7b 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 From 789b561c76b3f746ad1ce22d78a14919d5d4eb5a Mon Sep 17 00:00:00 2001 From: syeopite Date: Sun, 20 Apr 2025 19:57:21 -0700 Subject: [PATCH 5/5] Add support for "members first" badge --- locales/en-US.json | 1 + src/invidious/helpers/serialized_yt_data.cr | 8 ++++++++ src/invidious/views/components/item.ecr | 3 +++ src/invidious/views/components/items_paginated.ecr | 3 +-- src/invidious/yt_backend/extractors.cr | 4 ++-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/locales/en-US.json b/locales/en-US.json index 9a7ac3cf..93c99e81 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -503,5 +503,6 @@ "carousel_skip": "Skip the Carousel", "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/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index 69dc91d6..0cdbcd1b 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -10,6 +10,7 @@ enum VideoBadges VR360 ClosedCaptions MembersOnly + MembersFirst end struct SearchVideo @@ -152,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/views/components/item.ecr b/src/invidious/views/components/item.ecr index 59282165..91154af7 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -208,6 +208,9 @@ <% if item.badges.members_only? %> <%=translate(locale, "video_badges_members_only")%> <% end %> + <% if item.badges.members_first? %> + <%=translate(locale, "video_badges_members_first")%> + <% end %>
<%end%> <% end %> diff --git a/src/invidious/views/components/items_paginated.ecr b/src/invidious/views/components/items_paginated.ecr index 1863b7d4..02b47be1 100644 --- a/src/invidious/views/components/items_paginated.ecr +++ b/src/invidious/views/components/items_paginated.ecr @@ -3,8 +3,7 @@
<%- items.each do |item| -%> - <% next if exclude_members_only_videos && item.responds_to?(:badges) && - item.is_a? SearchVideo && item.badges.members_only? %> + <% next if exclude_members_only_videos && item.responds_to?(:restricted_to_members?) && item.restricted_to_members? %> <%= rendered "components/item" %> <%- end -%>
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index c0994ffb..76b5a8ad 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -136,9 +136,9 @@ private module Parsers # TODO: Potentially available as item_contents["topStandaloneBadge"]["metadataBadgeRenderer"] badges |= VideoBadges::Premium when "Members only" - # TODO: Identify based on style attribute instead of label - # It should be more resistant to Youtube changes. badges |= VideoBadges::MembersOnly + when "Members first" + badges |= VideoBadges::MembersFirst else nil # Ignore end end