This commit is contained in:
Richard Lora 2025-06-07 12:35:57 +00:00 committed by GitHub
commit 3629664d5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 143 additions and 71 deletions

View File

@ -311,12 +311,13 @@ https_only: false
# ----------------------------- # -----------------------------
## ##
## Enable/Disable the "Popular" tab on the main page. ## Enable/Disable specific pages on the main page.
## ##
## Accepted values: true, false #pages_enabled:
## Default: true # trending: true
# popular: true
# search: true
## ##
#popular_enabled: true
## ##
## Enable/Disable statstics (available at /api/v1/stats). ## Enable/Disable statstics (available at /api/v1/stats).

View File

@ -3,7 +3,8 @@
"Add to playlist: ": "Add to playlist: ", "Add to playlist: ": "Add to playlist: ",
"Answer": "Answer", "Answer": "Answer",
"Search for videos": "Search for videos", "Search for videos": "Search for videos",
"The Popular feed has been disabled by the administrator.": "The Popular feed has been disabled by the administrator.", "popular_page_disabled": "The Popular feed has been disabled by the administrator.",
"trending_page_disabled": "The Trending feed has been disabled by the administrator.",
"generic_channels_count": "{{count}} channel", "generic_channels_count": "{{count}} channel",
"generic_channels_count_plural": "{{count}} channels", "generic_channels_count_plural": "{{count}} channels",
"generic_views_count": "{{count}} view", "generic_views_count": "{{count}} view",
@ -500,7 +501,10 @@
"carousel_slide": "Slide {{current}} of {{total}}", "carousel_slide": "Slide {{current}} of {{total}}",
"carousel_skip": "Skip the Carousel", "carousel_skip": "Skip the Carousel",
"carousel_go_to": "Go to slide `x`", "carousel_go_to": "Go to slide `x`",
"preferences_trending_enabled_label": "Trending enabled: ",
"preferences_search_enabled_label": "Search enabled: ",
"timeline_parse_error_placeholder_heading": "Unable to parse item", "timeline_parse_error_placeholder_heading": "Unable to parse item",
"timeline_parse_error_placeholder_message": "Invidious encountered an error while trying to parse this item. For more information see below:", "timeline_parse_error_placeholder_message": "Invidious encountered an error while trying to parse this item. For more information see below:",
"timeline_parse_error_show_technical_details": "Show technical details" "timeline_parse_error_show_technical_details": "Show technical details",
} "search_page_disabled": "Search has been disabled by the administrator."
}

View File

@ -191,7 +191,7 @@ if (CONFIG.use_pubsub_feeds.is_a?(Bool) && CONFIG.use_pubsub_feeds.as(Bool)) ||
Invidious::Jobs.register Invidious::Jobs::SubscribeToFeedsJob.new(PG_DB, HMAC_KEY) Invidious::Jobs.register Invidious::Jobs::SubscribeToFeedsJob.new(PG_DB, HMAC_KEY)
end end
if CONFIG.popular_enabled if CONFIG.page_enabled?("popular")
Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB) Invidious::Jobs.register Invidious::Jobs::PullPopularVideosJob.new(PG_DB)
end end

View File

@ -71,6 +71,37 @@ struct HTTPProxyConfig
property port : Int32 property port : Int32
end end
# Structure used for global per-page feature toggles
struct PagesEnabled
include YAML::Serializable
property trending : Bool = true
property popular : Bool = true
property search : Bool = true
def has_key?(key : String) : Bool
%w(trending popular search).includes?(key)
end
def [](key : String) : Bool
case key
when "trending" then @trending
when "popular" then @popular
when "search" then @search
else raise KeyError.new("Unknown page '#{key}'")
end
end
def []=(key : String, value : Bool)
case key
when "trending" then @trending = value
when "popular" then @popular = value
when "search" then @search = value
else raise KeyError.new("Unknown page '#{key}'")
end
end
end
class Config class Config
include YAML::Serializable include YAML::Serializable
@ -111,13 +142,25 @@ class Config
# Used to tell Invidious it is behind a proxy, so links to resources should be https:// # Used to tell Invidious it is behind a proxy, so links to resources should be https://
property https_only : Bool? property https_only : Bool?
# HMAC signing key for CSRF tokens and verifying pubsub subscriptions # HMAC signing key for CSRF tokens and verifying pubsub subscriptions
property hmac_key : String = "" property hmac_key : String = ""
# Domain to be used for links to resources on the site where an absolute URL is required # Domain to be used for links to resources on the site where an absolute URL is required
property domain : String? property domain : String?
# Subscribe to channels using PubSubHubbub (requires domain, hmac_key) # Subscribe to channels using PubSubHubbub (requires domain, hmac_key)
property use_pubsub_feeds : Bool | Int32 = false property use_pubsub_feeds : Bool | Int32 = false
# —————————————————————————————————————————————————————————————————————————————————————
# DEPRECATED: use `pages_enabled["popular"]` instead.
@[Deprecated("`popular_enabled` will be removed in a future release; use pages_enabled[\"popular\"] instead")]
property popular_enabled : Bool = true property popular_enabled : Bool = true
# Global per-page feature toggles.
# Valid keys: "trending", "popular", "search"
# If someone sets both `popular_enabled` and `pages_enabled["popular"]`, the latter takes precedence.
property pages_enabled : PagesEnabled = PagesEnabled.new
# —————————————————————————————————————————————————————————————————————————————————————
property captcha_enabled : Bool = true property captcha_enabled : Bool = true
property login_enabled : Bool = true property login_enabled : Bool = true
property registration_enabled : Bool = true property registration_enabled : Bool = true
@ -188,13 +231,20 @@ class Config
when Bool when Bool
return disabled return disabled
when Array when Array
if disabled.includes? option disabled.includes?(option)
return true
else
return false
end
else else
return false false
end
end
# Centralized page toggle with legacy fallback for `popular_enabled`
def page_enabled?(page : String) : Bool
if @pages_enabled.has_key?(page)
@pages_enabled[page]
elsif page == "popular"
@popular_enabled
else
true
end end
end end

View File

@ -29,11 +29,6 @@ module Invidious::Routes::API::V1::Feeds
env.response.content_type = "application/json" env.response.content_type = "application/json"
if !CONFIG.popular_enabled
error_message = {"error" => "Administrator has disabled this endpoint."}.to_json
haltf env, 403, error_message
end
JSON.build do |json| JSON.build do |json|
json.array do json.array do
popular_videos.each do |video| popular_videos.each do |video|

View File

@ -102,6 +102,28 @@ module Invidious::Routes::BeforeAll
preferences.locale = locale preferences.locale = locale
env.set "preferences", preferences env.set "preferences", preferences
path = env.request.path
page_key = case path
when "/feed/popular", "/api/v1/popular"
"popular"
when "/feed/trending", "/api/v1/trending"
"trending"
when "/search", "/api/v1/search"
"search"
else
nil
end
if page_key && !CONFIG.page_enabled?(page_key)
if path.starts_with?("/api/")
error_message = {error: "Administrator has disabled this endpoint."}.to_json
haltf env, 403, error_message
else
message = "#{page_key}_page_disabled"
return error_template(403, message)
end
end
# Allow media resources to be loaded from google servers # Allow media resources to be loaded from google servers
# TODO: check if *.youtube.com can be removed # TODO: check if *.youtube.com can be removed
# #

View File

@ -33,18 +33,11 @@ module Invidious::Routes::Feeds
def self.popular(env) def self.popular(env)
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
templated "feeds/popular"
if CONFIG.popular_enabled
templated "feeds/popular"
else
message = translate(locale, "The Popular feed has been disabled by the administrator.")
templated "message"
end
end end
def self.trending(env) def self.trending(env)
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
trending_type = env.params.query["type"]? trending_type = env.params.query["type"]?
trending_type ||= "Default" trending_type ||= "Default"

View File

@ -199,9 +199,12 @@ module Invidious::Routes::PreferencesRoute
end end
CONFIG.default_user_preferences.feed_menu = admin_feed_menu CONFIG.default_user_preferences.feed_menu = admin_feed_menu
popular_enabled = env.params.body["popular_enabled"]?.try &.as(String) pages_enabled = {
popular_enabled ||= "off" popular: (env.params.body["popular_enabled"]?.try &.as(String) || "on") == "on",
CONFIG.popular_enabled = popular_enabled == "on" trending: (env.params.body["trending_enabled"]?.try &.as(String) || "on") == "on",
search: (env.params.body["search_enabled"]?.try &.as(String) || "on") == "on",
}
CONFIG.pages_enabled = pages_enabled
captcha_enabled = env.params.body["captcha_enabled"]?.try &.as(String) captcha_enabled = env.params.body["captcha_enabled"]?.try &.as(String)
captcha_enabled ||= "off" captcha_enabled ||= "off"

View File

@ -40,52 +40,47 @@ module Invidious::Routes::Search
prefs = env.get("preferences").as(Preferences) prefs = env.get("preferences").as(Preferences)
locale = prefs.locale locale = prefs.locale
# otherwise, do a normal search
region = env.params.query["region"]? || prefs.region region = env.params.query["region"]? || prefs.region
query = Invidious::Search::Query.new(env.params.query, :regular, region) query = Invidious::Search::Query.new(env.params.query, :regular, region)
# empty query → show homepage
if query.empty? if query.empty?
# Display the full page search box implemented in #1977
env.set "search", "" env.set "search", ""
templated "search_homepage", navbar_search: false return templated "search_homepage", navbar_search: false
else
user = env.get? "user"
# An URL was copy/pasted in the search box.
# Redirect the user to the appropriate page.
if query.url?
return env.redirect UrlSanitizer.process(query.text).to_s
end
begin
if user
items = query.process(user.as(User))
else
items = query.process
end
rescue ex : ChannelSearchException
return error_template(404, "Unable to find channel with id of '#{HTML.escape(ex.channel)}'. Are you sure that's an actual channel id? It should look like 'UC4QobU6STFB0P71PMvOGN5A'.")
rescue ex
return error_template(500, ex)
end
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
# Pagination
page_nav_html = Frontend::Pagination.nav_numeric(locale,
base_url: "/search?#{query.to_http_params}",
current_page: query.page,
show_next: (items.size >= 20)
)
if query.type == Invidious::Search::Query::Type::Channel
env.set "search", "channel:#{query.channel} #{query.text}"
else
env.set "search", query.text
end
templated "search"
end end
# nonempty query → process it
user = env.get?("user")
if query.url?
return env.redirect UrlSanitizer.process(query.text).to_s
end
begin
items = user ? query.process(user.as(User)) : query.process
rescue ex : ChannelSearchException
return error_template 404, "Unable to find channel with id “#{HTML.escape(ex.channel)}”…"
rescue ex
return error_template 500, ex
end
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
# Pagination
page_nav_html = Frontend::Pagination.nav_numeric(locale,
base_url: "/search?#{query.to_http_params}",
current_page: query.page,
show_next: (items.size >= 20)
)
# If it's a channel search, prefix the box; otherwise just show the text
if query.type == Invidious::Search::Query::Type::Channel
env.set "search", "channel:#{query.channel} #{query.text}"
else
env.set "search", query.text
end
templated "search"
end end
def self.hashtag(env : HTTP::Server::Context) def self.hashtag(env : HTTP::Server::Context)

View File

@ -289,9 +289,18 @@
<div class="pure-control-group"> <div class="pure-control-group">
<label for="popular_enabled"><%= translate(locale, "Popular enabled: ") %></label> <label for="popular_enabled"><%= translate(locale, "Popular enabled: ") %></label>
<input name="popular_enabled" id="popular_enabled" type="checkbox" <% if CONFIG.popular_enabled %>checked<% end %>> <input name="popular_enabled" id="popular_enabled" type="checkbox" <% if CONFIG.page_enabled?("popular") %>checked<% end %>>
</div> </div>
<div class="pure-control-group">
<label for="trending_enabled"><%= translate(locale, "preferences_trending_enabled_label") %></label>
<input name="trending_enabled" id="trending_enabled" type="checkbox" <% if CONFIG.page_enabled?("trending") %>checked<% end %>>
</div>
<div class="pure-control-group">
<label for="search_enabled"><%= translate(locale, "preferences_search_enabled_label") %></label>
<input name="search_enabled" id="search_enabled" type="checkbox" <% if CONFIG.page_enabled?("search") %>checked<% end %>>
</div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled: ") %></label> <label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled: ") %></label>