mirror of
https://github.com/iv-org/invidious.git
synced 2025-09-15 00:08:30 +00:00
Merge 20e4e52b8b
into 325e013e0d
This commit is contained in:
commit
b08cc5b117
@ -312,12 +312,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).
|
||||||
|
@ -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",
|
||||||
@ -502,7 +503,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."
|
||||||
|
}
|
50
spec/invidious/config_spec.cr
Normal file
50
spec/invidious/config_spec.cr
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
require "../spec_helper"
|
||||||
|
require "../../src/invidious/jobs.cr"
|
||||||
|
require "../../src/invidious/jobs/*"
|
||||||
|
require "../../src/invidious/config.cr"
|
||||||
|
require "../../src/invidious/user/preferences.cr"
|
||||||
|
|
||||||
|
# Allow this file to be executed independently of other specs
|
||||||
|
{% if !@type.has_constant?("CONFIG") %}
|
||||||
|
CONFIG = Config.from_yaml("")
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
private def construct_config(yaml)
|
||||||
|
config = Config.from_yaml(yaml)
|
||||||
|
File.open(File::NULL, "w") { |io| config.process_deprecation(io) }
|
||||||
|
return config
|
||||||
|
end
|
||||||
|
|
||||||
|
Spectator.describe Config do
|
||||||
|
context "page_enabled" do
|
||||||
|
it "Can disable pages" do
|
||||||
|
config = construct_config <<-YAML
|
||||||
|
pages_enabled:
|
||||||
|
popular: false
|
||||||
|
search: false
|
||||||
|
YAML
|
||||||
|
|
||||||
|
expect(config.page_enabled?("trending")).to eq(true)
|
||||||
|
expect(config.page_enabled?("popular")).to eq(false)
|
||||||
|
expect(config.page_enabled?("search")).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Takes precedence over popular_enabled" do
|
||||||
|
config = construct_config <<-YAML
|
||||||
|
popular_enabled: false
|
||||||
|
pages_enabled:
|
||||||
|
popular: true
|
||||||
|
YAML
|
||||||
|
|
||||||
|
expect(config.page_enabled?("popular")).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Deprecated popular_enabled still works" do
|
||||||
|
config = construct_config <<-YAML
|
||||||
|
popular_enabled: false
|
||||||
|
YAML
|
||||||
|
|
||||||
|
expect(config.page_enabled?("popular")).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
@ -197,7 +197,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
|
||||||
|
|
||||||
|
@ -73,6 +73,31 @@ struct HTTPProxyConfig
|
|||||||
property port : Int32
|
property port : Int32
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Structure used for global per-page feature toggles
|
||||||
|
record PagesEnabled,
|
||||||
|
trending : Bool = true,
|
||||||
|
popular : Bool = true,
|
||||||
|
search : Bool = true do
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
def [](key : String) : Bool
|
||||||
|
fetch(key) { raise KeyError.new("Unknown page '#{key}'") }
|
||||||
|
end
|
||||||
|
|
||||||
|
def []?(key : String) : Bool
|
||||||
|
fetch(key) { nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
private def fetch(key : String, &)
|
||||||
|
case key
|
||||||
|
when "trending" then @trending
|
||||||
|
when "popular" then @popular
|
||||||
|
when "search" then @search
|
||||||
|
else yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Config
|
class Config
|
||||||
include YAML::Serializable
|
include YAML::Serializable
|
||||||
|
|
||||||
@ -116,13 +141,37 @@ 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
|
||||||
|
|
||||||
|
# —————————————————————————————————————————————————————————————————————————————————————
|
||||||
|
|
||||||
|
# A @{{key}}_present variable is required for both fields in order to handle the precedence for
|
||||||
|
# the deprecated `popular_enabled` in relations to `pages_enabled`
|
||||||
|
|
||||||
|
# DEPRECATED: use `pages_enabled["popular"]` instead.
|
||||||
|
@[Deprecated("`popular_enabled` will be removed in a future release; use pages_enabled[\"popular\"] instead")]
|
||||||
|
@[YAML::Field(presence: true)]
|
||||||
property popular_enabled : Bool = true
|
property popular_enabled : Bool = true
|
||||||
|
|
||||||
|
@[YAML::Field(ignore: true)]
|
||||||
|
property popular_enabled_present : Bool
|
||||||
|
|
||||||
|
# Global per-page feature toggles.
|
||||||
|
# Valid keys: "trending", "popular", "search"
|
||||||
|
# If someone sets both `popular_enabled` and `pages_enabled["popular"]`, the latter takes precedence.
|
||||||
|
@[YAML::Field(presence: true)]
|
||||||
|
property pages_enabled : PagesEnabled = PagesEnabled.from_yaml("")
|
||||||
|
|
||||||
|
@[YAML::Field(ignore: true)]
|
||||||
|
property pages_enabled_present : Bool
|
||||||
|
# —————————————————————————————————————————————————————————————————————————————————————
|
||||||
|
|
||||||
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
|
||||||
@ -193,16 +242,17 @@ 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Centralized page toggle with legacy fallback for `popular_enabled`
|
||||||
|
def page_enabled?(page : String) : Bool
|
||||||
|
return @pages_enabled[page]
|
||||||
|
end
|
||||||
|
|
||||||
def self.load
|
def self.load
|
||||||
# Load config from file or YAML string env var
|
# Load config from file or YAML string env var
|
||||||
env_config_file = "INVIDIOUS_CONFIG_FILE"
|
env_config_file = "INVIDIOUS_CONFIG_FILE"
|
||||||
@ -240,6 +290,12 @@ class Config
|
|||||||
begin
|
begin
|
||||||
config.{{ivar.id}} = ivar_type.from_yaml(env_value)
|
config.{{ivar.id}} = ivar_type.from_yaml(env_value)
|
||||||
success = true
|
success = true
|
||||||
|
|
||||||
|
# Update associated _present key if any
|
||||||
|
{% other_ivar = @type.instance_vars.find { |other_ivar| other_ivar.name == ivar.name + "_present" } %}
|
||||||
|
{% if other_ivar && (ann = other_ivar.annotation(YAML::Field)) && ann[:ignore] == true %}
|
||||||
|
config.{{other_ivar.name.id}} = true
|
||||||
|
{% end %}
|
||||||
rescue
|
rescue
|
||||||
# nop
|
# nop
|
||||||
end
|
end
|
||||||
@ -297,6 +353,8 @@ class Config
|
|||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
config.process_deprecation
|
||||||
|
|
||||||
# Build database_url from db.* if it's not set directly
|
# Build database_url from db.* if it's not set directly
|
||||||
if config.database_url.to_s.empty?
|
if config.database_url.to_s.empty?
|
||||||
if db = config.db
|
if db = config.db
|
||||||
@ -334,4 +392,24 @@ class Config
|
|||||||
|
|
||||||
return config
|
return config
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Processes deprecated values
|
||||||
|
#
|
||||||
|
# Warns when they are set and handles any precedence issue that may arise when present alongside a successor attribute
|
||||||
|
#
|
||||||
|
# This method is public as to allow specs to test the behavior without going through #load
|
||||||
|
#
|
||||||
|
# :nodoc:
|
||||||
|
def process_deprecation(log_io : IO = STDOUT)
|
||||||
|
# Handle deprecated popular_enabled config and warn if it is set
|
||||||
|
if self.popular_enabled_present
|
||||||
|
log_io.puts "Warning: `popular_enabled` has been deprecated and replaced by the `pages_enabled` config"
|
||||||
|
log_io.puts "If both are set `pages_enabled` will take precedence over `popular_enabled`"
|
||||||
|
|
||||||
|
# Only use popular_enabled value when pages_enabled is unset
|
||||||
|
if !self.pages_enabled_present
|
||||||
|
self.pages_enabled = self.pages_enabled.copy_with(popular: self.popular_enabled)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -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|
|
||||||
|
@ -103,6 +103,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
|
||||||
#
|
#
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -202,9 +202,11 @@ 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)
|
CONFIG.pages_enabled = PagesEnabled.new(
|
||||||
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",
|
||||||
|
)
|
||||||
|
|
||||||
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"
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
# non‐empty 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)
|
||||||
|
@ -302,9 +302,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>
|
||||||
|
Loading…
Reference in New Issue
Block a user