From 07b9c36020f06839da412114d7a5766df993704a Mon Sep 17 00:00:00 2001 From: Sommerwiesel Date: Wed, 18 Jun 2025 11:31:14 +0200 Subject: [PATCH] Fix a lot of stupid git rebase errors --- README.md | 15 - create_network.sh | 2 + etc/haproxy/haproxy.cfg | 67 ++ nginx/snippets/favicon.conf | 5 + nginx/snippets/robots.conf | 7 + patches/002-multiple-companion-backends.patch | 577 +++++++++++++ patches/003-proxy-csp.patch.d | 37 + patches/010-use-redis-for-video-cache.patch.d | 151 ++++ patches/015-status-code.patch | 15 + patches/016-hide-shorts.patch | 793 ++++++++++++++++++ 10 files changed, 1654 insertions(+), 15 deletions(-) create mode 100755 create_network.sh create mode 100644 etc/haproxy/haproxy.cfg create mode 100644 nginx/snippets/favicon.conf create mode 100644 nginx/snippets/robots.conf create mode 100644 patches/002-multiple-companion-backends.patch create mode 100644 patches/003-proxy-csp.patch.d create mode 100644 patches/010-use-redis-for-video-cache.patch.d create mode 100644 patches/015-status-code.patch create mode 100644 patches/016-hide-shorts.patch diff --git a/README.md b/README.md index 599feb0..91c5d71 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,6 @@ Based upon patches from: https://github.com/yewtudotbe/invidious-custom https://git.nadeko.net/Fijxu/invidious -<<<<<<< HEAD -# IMPORTANT -The instructions below are outdated. -YouTube is currently quadrupling down on trying to block Invidious instances, so there are almost daily changes to my server setup. -I haven't found the time yet to provide new/better instructions, so the instructions below should be taken with some grain of salt. -======= -# Requirements - you need to set this up before running my fork -1. HAProxy 2.x either run on the host or added to the docker-compose.yaml with my config under `etc/haproxy/haproxy.cfg` -2. PostgreSQL 16 server either run on the host or added to the docker-compose.yaml -3. (optional bvut highly recommended) PGBouncer to pool the connections to PostgreSQL. You'll otherwise run into lots of db pool errors under high loads -4. Nginx with my configs under etc/nginx -5. Valid ssl certificate for your domain. I recommend using dehydrated: https://github.com/dehydrated-io/dehydrated -6. Docker compose V2 ->>>>>>> 9de2789 (Change to multiple backends) - # Build instructions Do this in a separate dir (for example /srv/invidious) under non-root permissions diff --git a/create_network.sh b/create_network.sh new file mode 100755 index 0000000..e33cf1f --- /dev/null +++ b/create_network.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +docker network create --subnet=192.42.6.0/24 --ipv6 --opt com.docker.network.bridge.name=inv_proxy invidious_proxy diff --git a/etc/haproxy/haproxy.cfg b/etc/haproxy/haproxy.cfg new file mode 100644 index 0000000..7dcbfc6 --- /dev/null +++ b/etc/haproxy/haproxy.cfg @@ -0,0 +1,67 @@ +global + log /dev/log local0 + log /dev/log local1 notice + chroot /var/lib/haproxy + stats socket /run/haproxy/admin.sock mode 660 level admin + stats timeout 30s + user haproxy + group haproxy + maxconn 4000 + daemon + +defaults + log global + mode http + option httplog + option dontlognull + errorfile 400 /etc/haproxy/errors/400.http + errorfile 403 /etc/haproxy/errors/403.http + errorfile 408 /etc/haproxy/errors/408.http + errorfile 500 /etc/haproxy/errors/500.http + errorfile 502 /etc/haproxy/errors/502.http + #errorfile 502 /etc/haproxy/errors/502-invidious.http + errorfile 503 /etc/haproxy/errors/503.http + errorfile 504 /etc/haproxy/errors/504.http + timeout connect 10s + timeout check 10s + timeout client 15s + timeout server 15s + timeout http-request 30s + +frontend statistics + bind :::8404 v4v6 + mode http + http-request use-service prometheus-exporter if { path /metrics } + stats enable + stats uri /stats + stats refresh 30s + no log + +frontend invidious + bind 192.42.6.1:8118 + mode http + + default_backend invidious_proxy + +backend invidious_proxy + mode http + cookie COMPANION_ID insert indirect nocache httponly secure + balance leastconn + #balance hdr(X-Forwarded-For) + hash-type consistent + + retry-on all-retryable-errors + + # Ipv6 + server proxy_1 192.42.6.11:8282 cookie 0 + server proxy_2 192.42.6.12:8282 cookie 1 + server proxy_3 192.42.6.13:8282 cookie 2 + server proxy_4 192.42.6.14:8282 cookie 3 + server proxy_5 192.42.6.15:8282 cookie 4 + server proxy_6 192.42.6.16:8282 cookie 5 + server proxy_7 192.42.6.17:8282 cookie 6 + server proxy_8 192.42.6.18:8282 cookie 7 + + # IPv4 + #server proxy_1 192.42.6.200:8282 cookie 10 + #server proxy_2 192.42.6.201:8282 cookie 11 diff --git a/nginx/snippets/favicon.conf b/nginx/snippets/favicon.conf new file mode 100644 index 0000000..e6b5313 --- /dev/null +++ b/nginx/snippets/favicon.conf @@ -0,0 +1,5 @@ +location = /favicon.ico { + allow all; + log_not_found off; + access_log off; +} diff --git a/nginx/snippets/robots.conf b/nginx/snippets/robots.conf new file mode 100644 index 0000000..2cd2a08 --- /dev/null +++ b/nginx/snippets/robots.conf @@ -0,0 +1,7 @@ +location = /robots.txt { + add_header Content-Type text/plain; + root /srv/nginx/public; + allow all; + log_not_found off; + access_log off; +} diff --git a/patches/002-multiple-companion-backends.patch b/patches/002-multiple-companion-backends.patch new file mode 100644 index 0000000..d0d1130 --- /dev/null +++ b/patches/002-multiple-companion-backends.patch @@ -0,0 +1,577 @@ +From aecf481a4e569f20e08294734162412a3ad9c83a Mon Sep 17 00:00:00 2001 +From: Fijxu +Date: Fri, 6 Jun 2025 12:27:08 -0400 +Subject: [PATCH] feat: Support multiple companion backends + +Stripped down version of what inv.nadeko.net Invidious instance does to +balance and allow the user to choose a backend. +--- + locales/en-US.json | 3 +- + src/invidious.cr | 14 +++- + src/invidious/config.cr | 8 ++ + src/invidious/helpers/backend_info.cr | 90 +++++++++++++++++++++ + src/invidious/jobs/backend_checker.cr | 14 ++++ + src/invidious/routes/backend_switcher.cr | 16 ++++ + src/invidious/routes/before_all.cr | 53 +++++++++++- + src/invidious/routes/watch.cr | 2 +- + src/invidious/routing.cr | 1 + + src/invidious/user/cookies.cr | 26 ++++++ + src/invidious/videos.cr | 10 +-- + src/invidious/videos/parser.cr | 8 +- + src/invidious/views/template.ecr | 31 +++++++ + src/invidious/yt_backend/connection_pool.cr | 9 ++- + src/invidious/yt_backend/youtube_api.cr | 12 ++- + 15 files changed, 274 insertions(+), 23 deletions(-) + create mode 100644 src/invidious/helpers/backend_info.cr + create mode 100644 src/invidious/jobs/backend_checker.cr + create mode 100644 src/invidious/routes/backend_switcher.cr + +diff --git a/locales/en-US.json b/locales/en-US.json +index 3f42a509..0b752d93 100644 +--- a/locales/en-US.json ++++ b/locales/en-US.json +@@ -502,5 +502,6 @@ + "carousel_go_to": "Go to slide `x`", + "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_show_technical_details": "Show technical details" ++ "timeline_parse_error_show_technical_details": "Show technical details", ++ "backend_unavailable": "The backend you selected is unavailable. You have been redirected to another one" + } +diff --git a/src/invidious.cr b/src/invidious.cr +index 69f8a26c..c3071be1 100644 +--- a/src/invidious.cr ++++ b/src/invidious.cr +@@ -96,9 +96,11 @@ YT_POOL = YoutubeConnectionPool.new(YT_URL, capacity: CONFIG.pool_size) + + GGPHT_POOL = YoutubeConnectionPool.new(URI.parse("https://yt3.ggpht.com"), capacity: CONFIG.pool_size) + +-COMPANION_POOL = CompanionConnectionPool.new( +- capacity: CONFIG.pool_size +-) ++COMPANION_POOL = [] of CompanionConnectionPool ++ ++CONFIG.invidious_companion.each do |companion| ++ COMPANION_POOL << CompanionConnectionPool.new(companion, capacity: CONFIG.pool_size) ++end + + # CLI + Kemal.config.extra_options do |parser| +@@ -203,6 +205,12 @@ Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new + + Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new + ++if CONFIG.invidious_companion.present? ++ Invidious::Jobs.register Invidious::Jobs::CheckBackend.new ++else ++ LOGGER.info("jobs: Disabling CheckBackend job. invidious-companion and their respective external video playback proxies (if set on invidious-companion) will not be checked") ++end ++ + Invidious::Jobs.start_all + + def popular_videos +diff --git a/src/invidious/config.cr b/src/invidious/config.cr +index 4d69854c..4879b7f0 100644 +--- a/src/invidious/config.cr ++++ b/src/invidious/config.cr +@@ -82,6 +82,8 @@ class Config + + @[YAML::Field(converter: Preferences::URIConverter)] + property public_url : URI = URI.parse("") ++ ++ property note : String = "" + end + + # Number of threads to use for crawling videos from channels (for updating subscriptions) +@@ -183,6 +185,12 @@ class Config + # Playlist length limit + property playlist_length_limit : Int32 = 500 + ++ # Fijxu companion modifications ++ ++ property server_id_cookie_name : String = "COMPANION_ID" ++ property check_backends_interval : Int32 = 30 ++ property backend_name_prefix : String = "Backend" ++ + def disabled?(option) + case disabled = CONFIG.disable_proxy + when Bool +diff --git a/src/invidious/helpers/backend_info.cr b/src/invidious/helpers/backend_info.cr +new file mode 100644 +index 00000000..f576245e +--- /dev/null ++++ b/src/invidious/helpers/backend_info.cr +@@ -0,0 +1,90 @@ ++module BackendInfo ++ extend self ++ ++ enum Status ++ Dead = 0 ++ Working = 1 ++ end ++ ++ @@status : Array(Int32) = Array.new(CONFIG.invidious_companion.size, Status::Dead.to_i) ++ @@csp : Array(String) = Array.new(CONFIG.invidious_companion.size, "") ++ @@working_ends : Array(Int32) = Array(Int32).new(0) ++ @@csp_mutex : Mutex = Mutex.new ++ @@check_mutex : Mutex = Mutex.new ++ ++ def check_backends ++ check_companion() ++ LOGGER.debug("Invidious companion: New working_ends \"#{@@working_ends}\"") ++ LOGGER.debug("Invidious companion: New status \"#{@@status}\"") ++ end ++ ++ private def check_companion ++ # Create Channels the size of CONFIG.invidious_companion ++ comp_size = CONFIG.invidious_companion.size ++ channels = Channel(Nil).new(comp_size) ++ updated_ends = Array(Int32).new(0) ++ updated_status = Array(Int32).new(CONFIG.invidious_companion.size, 0) ++ ++ LOGGER.debug("Invidious companion: comp_size \"#{comp_size}\"") ++ CONFIG.invidious_companion.each_with_index do |companion, index| ++ spawn do ++ begin ++ client = HTTP::Client.new(companion.private_url) ++ client.connect_timeout = 10.seconds ++ response = client.get("/healthz") ++ if response.status_code == 200 ++ @@check_mutex.synchronize do ++ updated_status[index] = Status::Working.to_i ++ updated_ends.push(index) ++ end ++ generate_csp([companion.public_url.to_s], index) ++ else ++ @@check_mutex.synchronize do ++ updated_status[index] = Status::Dead.to_i ++ end ++ end ++ rescue ++ @@check_mutex.synchronize do ++ updated_status[index] = Status::Dead.to_i ++ end ++ ensure ++ LOGGER.trace("Invidious companion: Done Index: \"#{index}\"") ++ channels.send(nil) ++ end ++ end ++ end ++ # Wait until we receive a signal from them all ++ LOGGER.debug("Invidious companion: Updating working_ends") ++ comp_size.times { channels.receive } ++ @@working_ends = updated_ends.sort! ++ @@status = updated_status ++ end ++ ++ private def generate_csp(companion_url : Array(String), index : Int32? = nil) ++ @@csp_mutex.synchronize do ++ @@csp[index] = "" ++ companion_url.each do |url| ++ @@csp[index] += " #{url}" ++ end ++ end ++ end ++ ++ def get_status ++ # Shouldn't need to lock since we never edit this array, only change the pointer. ++ return @@status ++ end ++ ++ def get_working_ends ++ # Shouldn't need to lock since we never edit this array, only change the pointer. ++ return @@working_ends ++ end ++ ++ def get_csp(index : Int32) ++ # A little mutex to prevent sending a partial CSP header ++ # Not sure if this is necessary. But if the @@csp[index] is being assigned ++ # at the same time when it's being accessed, a data race will appear ++ @@csp_mutex.synchronize do ++ return @@csp[index], @@csp[index] ++ end ++ end ++end +diff --git a/src/invidious/jobs/backend_checker.cr b/src/invidious/jobs/backend_checker.cr +new file mode 100644 +index 00000000..fdfa0a45 +--- /dev/null ++++ b/src/invidious/jobs/backend_checker.cr +@@ -0,0 +1,14 @@ ++class Invidious::Jobs::CheckBackend < Invidious::Jobs::BaseJob ++ def initialize ++ end ++ ++ def begin ++ loop do ++ LOGGER.info("Backend Checker: Starting") ++ BackendInfo.check_backends ++ LOGGER.info("Backend Checker: Done, sleeping for #{CONFIG.check_backends_interval} seconds") ++ sleep CONFIG.check_backends_interval.seconds ++ Fiber.yield ++ end ++ end ++end +diff --git a/src/invidious/routes/backend_switcher.cr b/src/invidious/routes/backend_switcher.cr +new file mode 100644 +index 00000000..e4702729 +--- /dev/null ++++ b/src/invidious/routes/backend_switcher.cr +@@ -0,0 +1,16 @@ ++{% skip_file if flag?(:api_only) %} ++ ++module Invidious::Routes::BackendSwitcher ++ def self.switch(env) ++ referer = get_referer(env, unroll: false) ++ backend_id = env.params.query["backend_id"]?.try &.to_i ++ ++ if backend_id.nil? ++ return error_template(400, "Backend ID is required") ++ end ++ ++ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(env.request.headers["Host"], backend_id) ++ ++ env.redirect referer ++ end ++end +diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr +index b5269668..6fb2eef4 100644 +--- a/src/invidious/routes/before_all.cr ++++ b/src/invidious/routes/before_all.cr +@@ -1,6 +1,9 @@ + module Invidious::Routes::BeforeAll + def self.handle(env) + preferences = Preferences.from_json("{}") ++ host = env.request.headers["Host"] ++ scheme = env.request.headers["X-Forwarded-Proto"]? || ("https" if CONFIG.https_only) || "http" ++ env.set "scheme", scheme + + begin + if prefs_cookie = env.request.cookies["PREFS"]? +@@ -20,6 +23,50 @@ module Invidious::Routes::BeforeAll + env.response.headers["X-XSS-Protection"] = "1; mode=block" + env.response.headers["X-Content-Type-Options"] = "nosniff" + ++ extra_media_csp = "" ++ extra_connect_csp = "" ++ ++ if CONFIG.invidious_companion.present? ++ if !{ ++ "/sb/", ++ "/vi/", ++ "/s_p/", ++ "/yts/", ++ "/ggpht/", ++ }.any? { |r| env.request.resource.starts_with? r } ++ if !env.request.cookies[CONFIG.server_id_cookie_name]? ++ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host) ++ end ++ ++ begin ++ current_companion = env.request.cookies[CONFIG.server_id_cookie_name].value.try &.to_i ++ rescue ++ working_ends = BackendInfo.get_working_ends ++ current_companion = working_ends.sample ++ end ++ ++ if current_companion > CONFIG.invidious_companion.size ++ current_companion = current_companion % CONFIG.invidious_companion.size ++ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host, current_companion) ++ end ++ ++ companion_status = BackendInfo.get_status ++ ++ if companion_status[current_companion] != BackendInfo::Status::Working.to_i ++ current_companion = 0 if current_companion == companion_status.size - 1 ++ alive_companion = companion_status.index(BackendInfo::Status::Working.to_i, offset: current_companion) ++ if alive_companion ++ env.set "companion_switched", true ++ current_companion = alive_companion ++ env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host, current_companion) ++ end ++ end ++ ++ env.set "current_companion", current_companion ++ extra_media_csp, extra_connect_csp = BackendInfo.get_csp(current_companion) ++ end ++ end ++ + # Only allow the pages at /embed/* to be embedded + if env.request.resource.starts_with?("/embed") + frame_ancestors = "'self' file: http: https:" +@@ -33,11 +80,11 @@ module Invidious::Routes::BeforeAll + "default-src 'none'", + "script-src 'self'", + "style-src 'self' 'unsafe-inline'", +- "img-src 'self' data:", ++ "img-src 'self' data: " + "#{scheme}://#{env.request.headers["Host"]?}", + "font-src 'self' data:", +- "connect-src 'self'", ++ "connect-src 'self'" + extra_connect_csp, + "manifest-src 'self'", +- "media-src 'self' blob:", ++ "media-src 'self' blob:" + extra_media_csp, + "child-src 'self' blob:", + "frame-src 'self'", + "frame-ancestors " + frame_ancestors, +diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr +index e777b3f1..7adb98ea 100644 +--- a/src/invidious/routes/watch.cr ++++ b/src/invidious/routes/watch.cr +@@ -52,7 +52,7 @@ module Invidious::Routes::Watch + env.params.query.delete_all("listen") + + begin +- video = get_video(id, region: params.region) ++ video = get_video(id, region: params.region, env: env) + rescue ex : NotFoundException + LOGGER.error("get_video not found: #{id} : #{ex.message}") + return error_template(404, ex) +diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr +index 46b71f1f..769a7f71 100644 +--- a/src/invidious/routing.cr ++++ b/src/invidious/routing.cr +@@ -21,6 +21,7 @@ module Invidious::Routing + get "/privacy", Routes::Misc, :privacy + get "/licenses", Routes::Misc, :licenses + get "/redirect", Routes::Misc, :cross_instance_redirect ++ get "/switchbackend", Routes::BackendSwitcher, :switch + + self.register_channel_routes + self.register_watch_routes +diff --git a/src/invidious/user/cookies.cr b/src/invidious/user/cookies.cr +index 654efc15..affcfdca 100644 +--- a/src/invidious/user/cookies.cr ++++ b/src/invidious/user/cookies.cr +@@ -35,5 +35,31 @@ struct Invidious::User + samesite: HTTP::Cookie::SameSite::Lax + ) + end ++ ++ # Backend (CONFIG.server_id_cookie_name) cookie ++ # Parameter "domain" comes from the global config ++ def server_id(domain : String?, server_id : Int32? = nil) : HTTP::Cookie ++ if server_id.nil? ++ server_id = rand(CONFIG.invidious_companion.size) ++ end ++ # Strip the port from the domain if it's being accessed from another port ++ # Browsers will reject the cookie if it contains the port number. This is ++ # because `example.com:3000` is not the same as `example.com` on a cookie. ++ domain = domain.split(":")[0] ++ # Not secure if it's being accessed from I2P ++ # Browsers expect the domain to include https. On I2P there is no HTTPS ++ if domain.not_nil!.split(".").last == "i2p" ++ @@secure = false ++ end ++ return HTTP::Cookie.new( ++ name: CONFIG.server_id_cookie_name, ++ domain: domain, ++ path: "/", ++ value: server_id.to_s, ++ secure: @@secure, ++ http_only: true, ++ samesite: HTTP::Cookie::SameSite::Lax ++ ) ++ end + end + end +diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr +index 348a0a66..404f5fed 100644 +--- a/src/invidious/videos.cr ++++ b/src/invidious/videos.cr +@@ -294,7 +294,7 @@ struct Video + predicate_bool upcoming, isUpcoming + end + +-def get_video(id, refresh = true, region = nil, force_refresh = false) ++def get_video(id, refresh = true, region = nil, force_refresh = false, env : HTTP::Server::Context | Nil = nil) + if (video = Invidious::Database::Videos.select(id)) && !region + # If record was last updated over 10 minutes ago, or video has since premiered, + # refresh (expire param in response lasts for 6 hours) +@@ -304,7 +304,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false) + force_refresh || + video.schema_version != Video::SCHEMA_VERSION # cache control + begin +- video = fetch_video(id, region) ++ video = fetch_video(id, region, env) + Invidious::Database::Videos.update(video) + rescue ex + Invidious::Database::Videos.delete(id) +@@ -312,7 +312,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false) + end + end + else +- video = fetch_video(id, region) ++ video = fetch_video(id, region, env) + Invidious::Database::Videos.insert(video) if !region + end + +@@ -320,10 +320,10 @@ def get_video(id, refresh = true, region = nil, force_refresh = false) + rescue DB::Error + # Avoid common `DB::PoolRetryAttemptsExceeded` error and friends + # Note: All DB errors inherit from `DB::Error` +- return fetch_video(id, region) ++ return fetch_video(id, region, env) + end + +-def fetch_video(id, region) ++def fetch_video(id, region, env) + info = extract_video_info(video_id: id) + + if reason = info["reason"]? +diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr +index feb58440..c4f946e8 100644 +--- a/src/invidious/videos/parser.cr ++++ b/src/invidious/videos/parser.cr +@@ -58,7 +58,7 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)? + } + end + +-def extract_video_info(video_id : String) ++def extract_video_info(video_id : String, env : HTTP::Server::Context | Nil = nil) + # Init client config for the API + client_config = YoutubeAPI::ClientConfig.new + +@@ -116,7 +116,7 @@ def extract_video_info(video_id : String) + players_fallback.each do |player_fallback| + client_config.client_type = player_fallback + +- next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config)) ++ next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config, env)) + + if player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "url") + streaming_data = player_response["streamingData"].as_h +@@ -159,9 +159,9 @@ def extract_video_info(video_id : String) + return params + end + +-def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)? ++def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig, env : HTTP::Server::Context | Nil = nil) : Hash(String, JSON::Any)? + LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.") +- response = YoutubeAPI.player(video_id: id, params: "2AMB", client_config: client_config) ++ response = YoutubeAPI.player(video_id: id, params: "2AMB", client_config: client_config, env: env) + + playability_status = response["playabilityStatus"]["status"] + LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.") +diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr +index 9904b4fc..ad6c1c0e 100644 +--- a/src/invidious/views/template.ecr ++++ b/src/invidious/views/template.ecr +@@ -104,6 +104,37 @@ + + + ++ <% ++ if CONFIG.invidious_companion.present? ++ current_backend = env.get?("current_companion").try &.as(Int32) ++ scheme = env.get("scheme") ++ companion_switched = env.get?("companion_switched") ++ status = BackendInfo.get_status ++ %> ++
++ Backend
++ <% current_page = env.get("current_page") %> ++ <% CONFIG.invidious_companion.each_with_index do | companion, index | %> ++ display: inline-block;"> ++ <%= HTML.escape(CONFIG.backend_name_prefix + (index + 1).to_s) %> <%= HTML.escape(companion.note) %> ++ ++ ++ <% if !(index == CONFIG.invidious_companion.size-1) %> ++ | ++ <% end %> ++ <% end %> ++
++ <% end %> ++ ++ <% if companion_switched %> ++
++

<%= translate(locale, "backend_unavailable") %>

++
++ <% end %> ++ + <% if CONFIG.banner %> +
+

<%= CONFIG.banner %>

+diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr +index 0daed46c..4c7227c4 100644 +--- a/src/invidious/yt_backend/connection_pool.cr ++++ b/src/invidious/yt_backend/connection_pool.cr +@@ -49,7 +49,7 @@ end + struct CompanionConnectionPool + property pool : DB::Pool(HTTP::Client) + +- def initialize(capacity = 5, timeout = 5.0) ++ def initialize(companion, capacity = 5, timeout = 10.0) + options = DB::Pool::Options.new( + initial_pool_size: 0, + max_pool_size: capacity, +@@ -58,7 +58,6 @@ struct CompanionConnectionPool + ) + + @pool = DB::Pool(HTTP::Client).new(options) do +- companion = CONFIG.invidious_companion.sample + next make_client(companion.private_url, use_http_proxy: false) + end + end +@@ -71,8 +70,10 @@ struct CompanionConnectionPool + rescue ex + conn.close + +- companion = CONFIG.invidious_companion.sample +- conn = make_client(companion.private_url, use_http_proxy: false) ++ scheme = "http" ++ same_companion = "#{scheme}://#{conn.host}:#{conn.port}" ++ ++ conn = make_client(URI.parse(same_companion), use_http_proxy: false) + + response = yield conn + ensure +diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr +index b40092a1..1b37c4c9 100644 +--- a/src/invidious/yt_backend/youtube_api.cr ++++ b/src/invidious/yt_backend/youtube_api.cr +@@ -465,6 +465,7 @@ module YoutubeAPI + *, # Force the following parameters to be passed by name + params : String, + client_config : ClientConfig | Nil = nil, ++ env : HTTP::Server::Context | Nil = nil, + ) + # Playback context, separate because it can be different between clients + playback_ctx = { +@@ -501,7 +502,7 @@ module YoutubeAPI + end + + if CONFIG.invidious_companion.present? +- return self._post_invidious_companion("/youtubei/v1/player", data) ++ return self._post_invidious_companion("/youtubei/v1/player", data, env) + else + return self._post_json("/youtubei/v1/player", data, client_config) + end +@@ -682,6 +683,7 @@ module YoutubeAPI + def _post_invidious_companion( + endpoint : String, + data : Hash, ++ env : HTTP::Server::Context | Nil, + ) : Hash(String, JSON::Any) + headers = HTTP::Headers{ + "Content-Type" => "application/json; charset=UTF-8", +@@ -695,7 +697,13 @@ module YoutubeAPI + # Send the POST request + + begin +- response = COMPANION_POOL.client &.post(endpoint, headers: headers, body: data.to_json) ++ if env.nil? ++ working_ends = BackendInfo.get_working_ends ++ current_companion = working_ends.sample ++ else ++ current_companion = env.get("current_companion").as(Int32) ++ end ++ response = COMPANION_POOL[current_companion].client &.post(endpoint, headers: headers, body: data.to_json) + body = response.body + if (response.status_code != 200) + raise Exception.new( +-- +2.49.0 diff --git a/patches/003-proxy-csp.patch.d b/patches/003-proxy-csp.patch.d new file mode 100644 index 0000000..c6e8bf7 --- /dev/null +++ b/patches/003-proxy-csp.patch.d @@ -0,0 +1,37 @@ +From 236a2c70d3974f5a1fc55a5bc8047ff24d94bce2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=C3=89milien=20Devos?= +Date: Tue, 16 Aug 2022 06:51:52 +0000 +Subject: [PATCH] more csp + +--- + src/invidious/routes/before_all.cr | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr +index 8e2a253f..5c7cc959 100644 +--- a/src/invidious/routes/before_all.cr ++++ b/src/invidious/routes/before_all.cr +@@ -23,9 +23,9 @@ module Invidious::Routes::BeforeAll + # Allow media resources to be loaded from google servers + # TODO: check if *.youtube.com can be removed + if CONFIG.disabled?("local") || !preferences.local +- extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443" ++ extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443 https://*.invidious.nerdvpn.de:443" + else +- extra_media_csp = "" ++ extra_media_csp = " https://*.invidious.nerdvpn.de:443" + end + + # Only allow the pages at /embed/* to be embedded +@@ -43,7 +43,7 @@ module Invidious::Routes::BeforeAll + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data:", + "font-src 'self' data:", +- "connect-src 'self'", ++ "connect-src 'self' https://*.invidious.nerdvpn.de:443", + "manifest-src 'self'", + "media-src 'self' blob:" + extra_media_csp, + "child-src 'self' blob:", +-- +2.36.1 + diff --git a/patches/010-use-redis-for-video-cache.patch.d b/patches/010-use-redis-for-video-cache.patch.d new file mode 100644 index 0000000..c810b62 --- /dev/null +++ b/patches/010-use-redis-for-video-cache.patch.d @@ -0,0 +1,151 @@ +From 7c4c24f829b4eeb8ca035b4a9d77eef53ca8aefa Mon Sep 17 00:00:00 2001 +From: Emilien Devos +Date: Sat, 10 Jun 2023 22:21:38 +0200 +Subject: [PATCH 1/1] use redis for video cache + +--- + shard.lock | 8 ++++++++ + shard.yml | 2 ++ + src/invidious.cr | 8 +++++++- + src/invidious/config.cr | 3 +++ + src/invidious/database/videos.cr | 16 +++++++++++++--- + src/invidious/videos.cr | 2 +- + 6 files changed, 34 insertions(+), 5 deletions(-) + +diff --git a/shard.lock b/shard.lock +index 397bd8bc..8c599d1b 100644 +--- a/shard.lock ++++ b/shard.lock +@@ -36,6 +36,10 @@ shards: + git: https://github.com/will/crystal-pg.git + version: 0.28.0 + ++ pool: ++ git: https://github.com/ysbaddaden/pool.git ++ version: 0.2.4 ++ + protodec: + git: https://github.com/iv-org/protodec.git + version: 0.1.5 +@@ -44,6 +48,10 @@ shards: + git: https://github.com/luislavena/radix.git + version: 0.4.1 + ++ redis: ++ git: https://github.com/stefanwille/crystal-redis.git ++ version: 2.9.1 ++ + spectator: + git: https://github.com/icy-arctic-fox/spectator.git + version: 0.10.6 +diff --git a/shard.yml b/shard.yml +index 367f7c73..c46e1c42 100644 +--- a/shard.yml ++++ b/shard.yml +@@ -31,6 +31,8 @@ dependencies: + http_proxy: + github: mamantoha/http_proxy + version: ~> 0.10.3 ++ redis: ++ github: stefanwille/crystal-redis + + development_dependencies: + spectator: +diff --git a/src/invidious.cr b/src/invidious.cr +index 3804197e..e2d40597 100644 +--- a/src/invidious.cr ++++ b/src/invidious.cr +@@ -31,6 +31,7 @@ require "xml" + require "yaml" + require "compress/zip" + require "protodec/utils" ++require "redis" + + require "./invidious/database/*" + require "./invidious/database/migrations/*" +@@ -60,7 +61,12 @@ alias IV = Invidious + CONFIG = Config.load + HMAC_KEY = CONFIG.hmac_key + +-PG_DB = DB.open CONFIG.database_url ++PG_DB = DB.open CONFIG.database_url ++REDIS_DB = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil, database: CONFIG.redis_db || 0, password: CONFIG.redis_auth || nil) ++ ++if REDIS_DB.ping ++ puts "Connected to redis" ++end + ARCHIVE_URL = URI.parse("https://archive.org") + PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com") + REDDIT_URL = URI.parse("https://www.reddit.com") +diff --git a/src/invidious/config.cr b/src/invidious/config.cr +index c4ddcdb3..5b6c576c 100644 +--- a/src/invidious/config.cr ++++ b/src/invidious/config.cr +@@ -77,6 +77,11 @@ class Config + # Used for crawling channels: threads should check all videos uploaded by a channel + property full_refresh : Bool = false + ++ property redis_url : String? ++ property redis_socket : String? ++ property redis_auth : String? ++ property redis_db : Int32? ++ + # Jobs config structure. See jobs.cr and jobs/base_job.cr + property jobs = Invidious::Jobs::JobsConfig.new + +diff --git a/src/invidious/database/videos.cr b/src/invidious/database/videos.cr +index 695f5b33..776ef5b1 100644 +--- a/src/invidious/database/videos.cr ++++ b/src/invidious/database/videos.cr +@@ -10,7 +10,8 @@ module Invidious::Database::Videos + ON CONFLICT (id) DO NOTHING + SQL + +- PG_DB.exec(request, video.id, video.info.to_json, video.updated) ++ REDIS_DB.set(video.id, video.info.to_json, ex: 3600) ++ REDIS_DB.set(video.id + ":time", video.updated, ex: 3600) + end + + def delete(id) +@@ -19,7 +20,8 @@ module Invidious::Database::Videos + WHERE id = $1 + SQL + +- PG_DB.exec(request, id) ++ REDIS_DB.del(id) ++ REDIS_DB.del(id + ":time") + end + + def delete_expired +@@ -47,6 +49,14 @@ module Invidious::Database::Videos + WHERE id = $1 + SQL + +- return PG_DB.query_one?(request, id, as: Video) ++ if ((info = REDIS_DB.get(id)) && (time = REDIS_DB.get(id + ":time"))) ++ return Video.new({ ++ id: id, ++ info: JSON.parse(info).as_h, ++ updated: Time.parse(time, "%Y-%m-%d %H:%M:%S %z", Time::Location::UTC), ++ }) ++ else ++ return nil ++ end + end + end +diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr +index 6d0cf9ba..dde35291 100644 +--- a/src/invidious/videos.cr ++++ b/src/invidious/videos.cr +@@ -400,7 +400,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false) + video.schema_version != Video::SCHEMA_VERSION # cache control + begin + video = fetch_video(id, region, env) +- Invidious::Database::Videos.update(video) ++ Invidious::Database::Videos.insert(video) + rescue ex + Invidious::Database::Videos.delete(id) + raise ex +-- +2.46.0 + diff --git a/patches/015-status-code.patch b/patches/015-status-code.patch new file mode 100644 index 0000000..72cb6be --- /dev/null +++ b/patches/015-status-code.patch @@ -0,0 +1,15 @@ +diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr +index 8f5aa61d..f97a8732 100644 +--- a/src/invidious/yt_backend/youtube_api.cr ++++ b/src/invidious/yt_backend/youtube_api.cr +@@ -638,9 +638,7 @@ module YoutubeAPI + body = YT_POOL.client() do |client| + client.post(url, headers: headers, body: data.to_json) do |response| + if response.status_code != 200 +- raise InfoException.new("Error: non 200 status code. Youtube API returned \ +- status code #{response.status_code}. See \ +- https://docs.invidious.io/youtube-errors-explained/ for troubleshooting.") ++ raise InfoException.new("

Error loading video

Received error from YouTube: #{response.status_code}

") + end + self._decompress(response.body_io, response.headers["Content-Encoding"]?) + end diff --git a/patches/016-hide-shorts.patch b/patches/016-hide-shorts.patch new file mode 100644 index 0000000..ad8f5e3 --- /dev/null +++ b/patches/016-hide-shorts.patch @@ -0,0 +1,793 @@ +diff --git a/locales/ar.json b/locales/ar.json +index 94103c29..427ddaf9 100644 +--- a/locales/ar.json ++++ b/locales/ar.json +@@ -436,6 +436,7 @@ + "Vietnamese (auto-generated)": "فيتنامي (تم إنشاؤه تلقائيًا)", + "crash_page_report_issue": "إذا لم يساعد أي مما سبق، يرجى فتح مشكلة جديدة على GitHub (ويفضل أن يكون باللغة الإنجليزية) وتضمين النص التالي في رسالتك (لا تترجم هذا النص):", + "crash_page_read_the_faq": "قراءة الأسئلة المتكررة (الأسئلة الشائعة) ", ++ "preferences_hide_shorts_label": "إخفاء YouTube Shorts: ", + "preferences_watch_history_label": "تمكين سجل المشاهدة: ", + "English (United Kingdom)": "الإنجليزية (المملكة المتحدة)", + "Cantonese (Hong Kong)": "الكانتونية (هونغ كونغ)", +diff --git a/locales/bg.json b/locales/bg.json +index 5c99d98f..acf5a6f7 100644 +--- a/locales/bg.json ++++ b/locales/bg.json +@@ -95,6 +95,7 @@ + "search_filters_sort_option_rating": "Рейтинг", + "Manage subscriptions": "Управление на абонаментите", + "preferences_quality_dash_option_720p": "720p", ++ "preferences_hide_shorts_label": "Скрий YouTube Shorts: ", + "preferences_watch_history_label": "Активирай историята на гледане: ", + "user_saved_playlists": "`x` запази плейлисти", + "preferences_extend_desc_label": "Автоматично разшири описанието на видеото ", +diff --git a/locales/bn.json b/locales/bn.json +index 501a1ca3..e236bcc8 100644 +--- a/locales/bn.json ++++ b/locales/bn.json +@@ -61,6 +61,7 @@ + "generic_videos_count_plural": "{{count}}টি ভিডিও", + "generic_subscribers_count": "{{count}}জন অনুসরণকারী", + "generic_subscribers_count_plural": "{{count}}জন অনুসরণকারী", ++ "preferences_hide_shorts_label": "YouTube Shorts লুকান: ", + "preferences_watch_history_label": "দেখার ইতিহাস চালু করো: ", + "preferences_quality_option_dash": "ড্যাশ (সময়োপযোগী মান)", + "preferences_quality_dash_option_auto": "স্বয়ংক্রিয়", +diff --git a/locales/bn_BD.json b/locales/bn_BD.json +index a82b0da7..78c664fa 100644 +--- a/locales/bn_BD.json ++++ b/locales/bn_BD.json +@@ -45,6 +45,7 @@ + "Register": "নিবন্ধন", + "E-mail": "ই-মেইল", + "Preferences": "পছন্দসমূহ", ++ "preferences_hide_shorts_label": "YouTube Shorts লুকান: ", + "preferences_category_player": "প্লেয়ারের পছন্দসমূহ", + "preferences_video_loop_label": "সর্বদা লুপ: ", + "preferences_autoplay_label": "স্বয়ংক্রিয় চালু: ", +diff --git a/locales/ca.json b/locales/ca.json +index 474d6a3c..2c442c81 100644 +--- a/locales/ca.json ++++ b/locales/ca.json +@@ -443,6 +443,7 @@ + "Trending": "Tendència", + "Updated `x` ago": "Actualitzat fa `x`", + "Haitian Creole": "Crioll Haitià", ++ "preferences_hide_shorts_label": "Amaga YouTube Shorts: ", + "preferences_watch_history_label": "Habilita historial de reproduccions: ", + "generic_count_hours": "{{count}} hora", + "generic_count_hours_plural": "{{count}} hores", +diff --git a/locales/cs.json b/locales/cs.json +index 41f3db5c..08bb3f8a 100644 +--- a/locales/cs.json ++++ b/locales/cs.json +@@ -338,6 +338,7 @@ + "generic_subscribers_count_0": "{{count}} odběratel", + "generic_subscribers_count_1": "{{count}} odběratelé", + "generic_subscribers_count_2": "{{count}} odběratelů", ++ "preferences_hide_shorts_label": "Skrýt YouTube Shorts: ", + "preferences_watch_history_label": "Povolit historii sledování: ", + "preferences_quality_dash_option_240p": "240p", + "preferences_region_label": "Země obsahu: ", +diff --git a/locales/cy.json b/locales/cy.json +index eb391572..55bcc3d8 100644 +--- a/locales/cy.json ++++ b/locales/cy.json +@@ -151,6 +151,7 @@ + "preferences_category_player": "Hoffterau'r chwaraeydd", + "preferences_autoplay_label": "Chwarae'n awtomatig: ", + "preferences_local_label": "Llwytho fideos drwy ddirprwy weinydd: ", ++ "preferences_hide_shorts_label": "Cuddio YouTube Shorts: ", + "preferences_watch_history_label": "Galluogi hanes gwylio: ", + "preferences_speed_label": "Cyflymder rhagosodedig: ", + "preferences_quality_label": "Ansawdd fideos: ", +diff --git a/locales/da.json b/locales/da.json +index 9cbb446a..86e3445d 100644 +--- a/locales/da.json ++++ b/locales/da.json +@@ -418,6 +418,7 @@ + "crash_page_refresh": "forsøgte at opdatere siden", + "generic_playlists_count": "{{count}} spilleliste", + "generic_playlists_count_plural": "{{count}} spillelister", ++ "preferences_hide_shorts_label": "Skjul YouTube Shorts: ", + "preferences_watch_history_label": "Aktiver afspilningshistorik: ", + "tokens_count": "{{count}} token", + "tokens_count_plural": "{{count}} tokens", +diff --git a/locales/de.json b/locales/de.json +index e51d40b9..70ab245f 100644 +--- a/locales/de.json ++++ b/locales/de.json +@@ -432,6 +432,7 @@ + "Chinese (Hong Kong)": "Chinesisch (Hong Kong)", + "generic_playlists_count": "{{count}} Wiedergabeliste", + "generic_playlists_count_plural": "{{count}} Wiedergabelisten", ++ "preferences_hide_shorts_label": "YouTube-Shorts ausblenden: ", + "preferences_watch_history_label": "Wiedergabeverlauf aktivieren: ", + "English (United Kingdom)": "Englisch (Vereinigtes Königreich)", + "English (United States)": "Englisch (Vereinigte Staaten)", +diff --git a/locales/el.json b/locales/el.json +index e5a89a44..47ff3239 100644 +--- a/locales/el.json ++++ b/locales/el.json +@@ -440,6 +440,7 @@ + "videoinfo_youTube_embed_link": "Ενσωμάτωση", + "videoinfo_invidious_embed_link": "Σύνδεσμος Ενσωμάτωσης", + "search_filters_type_option_show": "Μπάρα προόδου διαβάσματος", ++ "preferences_hide_shorts_label": "Απόκρυψη YouTube Shorts: ", + "preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ", + "search_filters_title": "Φίλτρο", + "search_message_no_results": "Δε βρέθηκαν αποτελέσματα.", +diff --git a/locales/en-US.json b/locales/en-US.json +index 0b752d93..401ab9f2 100644 +--- a/locales/en-US.json ++++ b/locales/en-US.json +@@ -76,6 +76,7 @@ + "preferences_continue_autoplay_label": "Autoplay next video: ", + "preferences_listen_label": "Listen by default: ", + "preferences_local_label": "Proxy videos: ", ++ "preferences_hide_shorts_label": "Hide YouTube-Shorts: ", + "preferences_watch_history_label": "Enable watch history: ", + "preferences_speed_label": "Default speed: ", + "preferences_quality_label": "Preferred video quality: ", +diff --git a/locales/eo.json b/locales/eo.json +index 7276c890..d5f3b7d4 100644 +--- a/locales/eo.json ++++ b/locales/eo.json +@@ -425,6 +425,7 @@ + "comments_points_count": "{{count}} poento", + "comments_points_count_plural": "{{count}} poentoj", + "Cantonese (Hong Kong)": "Kantona (Honkongo)", ++ "preferences_hide_shorts_label": "Kaŝi YouTube Shorts: ", + "preferences_watch_history_label": "Ebligi vidohistorion: ", + "preferences_quality_option_small": "Eta", + "generic_playlists_count": "{{count}} ludlisto", +diff --git a/locales/es.json b/locales/es.json +index 46217943..1ce51dd6 100644 +--- a/locales/es.json ++++ b/locales/es.json +@@ -465,6 +465,7 @@ + "Korean (auto-generated)": "Coreano (generados automáticamente)", + "Spanish (Mexico)": "Español (México)", + "Spanish (auto-generated)": "Español (generados automáticamente)", ++ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ", + "preferences_watch_history_label": "Habilitar historial de reproducciones: ", + "search_message_no_results": "No se han encontrado resultados.", + "search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.", +diff --git a/locales/et.json b/locales/et.json +index 7f652810..f2a41b92 100644 +--- a/locales/et.json ++++ b/locales/et.json +@@ -46,6 +46,7 @@ + "Register": "Registreeru", + "E-mail": "E-post", + "Preferences": "Eelistused", ++ "preferences_hide_shorts_label": "Peida YouTube Shorts: ", + "preferences_category_player": "Mängija eelistused", + "preferences_continue_autoplay_label": "Mängi järgmine video automaatselt: ", + "preferences_quality_label": "Eelistatud videokvaliteet: ", +diff --git a/locales/eu.json b/locales/eu.json +index fbca537b..11e5972b 100644 +--- a/locales/eu.json ++++ b/locales/eu.json +@@ -119,6 +119,7 @@ + "Belarusian": "Bielorrusiara", + "Burmese": "Burmesera", + "Chinese (Simplified)": "Txinera (sinplifikatua)", ++ "preferences_hide_shorts_label": "Ezkutatu YouTube Shorts: ", + "preferences_watch_history_label": "Baimendu historia ikusi ", + "generic_videos_count": "{{count}}bideo", + "generic_videos_count_plural": "{{count}}bideoak", +diff --git a/locales/fa.json b/locales/fa.json +index 2326370d..4662c771 100644 +--- a/locales/fa.json ++++ b/locales/fa.json +@@ -422,6 +422,7 @@ + "search_filters_title": "پالایه", + "Chinese (Hong Kong)": "چینی (هنگ‌کنگ)", + "Dutch (auto-generated)": "هلندی (تولید خودکار)", ++ "preferences_hide_shorts_label": "مخفی کردن YouTube Shorts: ", + "preferences_watch_history_label": "فعال‌سازی تاریخچه‌ی پخش ", + "Indonesian (auto-generated)": "اندونزیایی (تولید خودکار)", + "English (United States)": "انگلیسی (ایالات متحده)", +diff --git a/locales/fi.json b/locales/fi.json +index 13fef6de..804aa4c1 100644 +--- a/locales/fi.json ++++ b/locales/fi.json +@@ -432,6 +432,7 @@ + "German (auto-generated)": "saksa (automaattisesti luotu)", + "Portuguese (auto-generated)": "portugali (automaattisesti luotu)", + "Russian (auto-generated)": "Venäjä (automaattisesti luotu)", ++ "preferences_hide_shorts_label": "Piilota YouTube Shorts: ", + "preferences_watch_history_label": "Ota katseluhistoria käyttöön: ", + "English (United Kingdom)": "englanti (Iso-Britannia)", + "English (United States)": "englanti (Yhdysvallat)", +diff --git a/locales/fr.json b/locales/fr.json +index 49aa09df..3076623a 100644 +--- a/locales/fr.json ++++ b/locales/fr.json +@@ -477,6 +477,7 @@ + "Vietnamese (auto-generated)": "Vietnamien (auto-généré)", + "Russian (auto-generated)": "Russe (auto-généré)", + "Spanish (Spain)": "Espagnol (Espagne)", ++ "preferences_hide_shorts_label": "Masquer YouTube Shorts: ", + "preferences_watch_history_label": "Activer l'historique de visionnage : ", + "search_filters_title": "Filtres", + "search_message_change_filters_or_query": "Essayez d'élargir votre recherche et/ou de changer les filtres.", +diff --git a/locales/he.json b/locales/he.json +index 6fee93b2..62c19b02 100644 +--- a/locales/he.json ++++ b/locales/he.json +@@ -45,6 +45,7 @@ + "Register": "הרשמה", + "E-mail": "דוא״ל", + "Preferences": "העדפות", ++ "preferences_hide_shorts_label": "הסתר YouTube Shorts: ", + "preferences_category_player": "העדפות הנגן", + "preferences_autoplay_label": "ניגון אוטומטי: ", + "preferences_continue_label": "ניגון הסרטון הבא כברירת מחדל: ", +diff --git a/locales/hi.json b/locales/hi.json +index 0a1c09dd..63afc443 100644 +--- a/locales/hi.json ++++ b/locales/hi.json +@@ -90,6 +90,7 @@ + "preferences_continue_autoplay_label": "अगला वीडियो अपने आप चलाएँ: ", + "preferences_listen_label": "डिफ़ॉल्ट से सुनें: ", + "preferences_local_label": "प्रॉक्सी वीडियो: ", ++ "preferences_hide_shorts_label": "YouTube Shorts छुपाएं: ", + "preferences_watch_history_label": "देखने का इतिहास सक्षम करें: ", + "preferences_speed_label": "वीडियो चलाने की डिफ़ॉल्ट रफ़्तार: ", + "preferences_quality_label": "वीडियो की प्राथमिक क्वालिटी: ", +diff --git a/locales/hr.json b/locales/hr.json +index 6adbcdc3..7b1d701d 100644 +--- a/locales/hr.json ++++ b/locales/hr.json +@@ -467,6 +467,7 @@ + "Korean (auto-generated)": "Korejski (automatski generirano)", + "Portuguese (auto-generated)": "Portugalski (automatski generirano)", + "Spanish (auto-generated)": "Španjolski (automatski generirano)", ++ "preferences_hide_shorts_label": "Sakrij YouTube Shorts: ", + "preferences_watch_history_label": "Aktiviraj povijest gledanja: ", + "search_filters_title": "Filtri", + "search_filters_date_option_none": "Bilo koji datum", +diff --git a/locales/hu-HU.json b/locales/hu-HU.json +index 8fbdd82f..2571c9d9 100644 +--- a/locales/hu-HU.json ++++ b/locales/hu-HU.json +@@ -452,6 +452,7 @@ + "French (auto-generated)": "francia (automatikusan generált)", + "Vietnamese (auto-generated)": "vietnámi (automatikusan generált)", + "search_filters_title": "Szűrők", ++ "preferences_hide_shorts_label": "YouTube Shorts elrejtése: ", + "preferences_watch_history_label": "Megnézett videók naplózása: ", + "search_message_no_results": "Nincs találat.", + "search_message_change_filters_or_query": "Próbálj meg bővebben rákeresni vagy a szűrőkön állítani.", +diff --git a/locales/ia.json b/locales/ia.json +index 236ec4b4..84c29150 100644 +--- a/locales/ia.json ++++ b/locales/ia.json +@@ -25,6 +25,7 @@ + "youtube": "YouTube", + "LIVE": "IN DIRECTO", + "reddit": "Reddit", ++ "preferences_hide_shorts_label": "Celar YouTube Shorts: ", + "preferences_category_player": "Preferentias de reproductor", + "Preferences": "Preferentias", + "preferences_quality_dash_option_auto": "Automatic", +diff --git a/locales/id.json b/locales/id.json +index 4c6e8548..6f9870de 100644 +--- a/locales/id.json ++++ b/locales/id.json +@@ -408,6 +408,7 @@ + "crash_page_you_found_a_bug": "Sepertinya kamu telah menemukan masalah di invidious!", + "crash_page_before_reporting": "Sebelum melaporkan masalah, pastikan anda memiliki:", + "English (United States)": "Inggris (US)", ++ "preferences_hide_shorts_label": "Sembunyikan YouTube Shorts: ", + "preferences_watch_history_label": "Aktifkan riwayat tontonan: ", + "English (United Kingdom)": "Inggris (UK)", + "search_filters_title": "Saring", +diff --git a/locales/is.json b/locales/is.json +index 28cacf31..264cb5f5 100644 +--- a/locales/is.json ++++ b/locales/is.json +@@ -311,6 +311,7 @@ + "Playlists": "Spilunarlistar", + "channel_tab_community_label": "Samfélag", + "Current version: ": "Núverandi útgáfa: ", ++ "preferences_hide_shorts_label": "Fela YouTube Shorts: ", + "preferences_watch_history_label": "Virkja áhorfsferil: ", + "Chinese (China)": "Kínverska (Kína)", + "Turkish (auto-generated)": "Tyrkneska (sjálfvirkt útbúið)", +diff --git a/locales/it.json b/locales/it.json +index c7143ef6..15c2b85e 100644 +--- a/locales/it.json ++++ b/locales/it.json +@@ -447,6 +447,7 @@ + "Popular enabled: ": "Popolare attivato: ", + "English (United Kingdom)": "Inglese (Regno Unito)", + "Portuguese (Brazil)": "Portoghese (Brasile)", ++ "preferences_hide_shorts_label": "Nascondi YouTube Shorts: ", + "preferences_watch_history_label": "Attiva cronologia di riproduzione: ", + "French (auto-generated)": "Francese (generati automaticamente)", + "search_message_use_another_instance": "Puoi anche cercare in un'altra istanza.", +diff --git a/locales/ja.json b/locales/ja.json +index c4b82486..37c98713 100644 +--- a/locales/ja.json ++++ b/locales/ja.json +@@ -439,6 +439,7 @@ + "user_saved_playlists": "`x`個の保存済みの再生リスト", + "crash_page_you_found_a_bug": "Invidious のバグのようです!", + "crash_page_refresh": "ページを更新を試す", ++ "preferences_hide_shorts_label": "YouTube Shorts を非表示にする: ", + "preferences_watch_history_label": "再生履歴を有効化 ", + "search_filters_date_option_none": "すべて", + "search_filters_type_option_all": "すべての種類", +diff --git a/locales/ko.json b/locales/ko.json +index 0224955f..d5a38de7 100644 +--- a/locales/ko.json ++++ b/locales/ko.json +@@ -407,6 +407,7 @@ + "Spanish (auto-generated)": "스페인어 (자동 생성됨)", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_worst": "최저", ++ "preferences_hide_shorts_label": "YouTube Shorts 숨기기: ", + "preferences_watch_history_label": "시청 기록 저장: ", + "invidious": "인비디어스", + "preferences_quality_option_small": "낮음", +diff --git a/locales/lmo.json b/locales/lmo.json +index 9d2fe2a8..cce7774a 100644 +--- a/locales/lmo.json ++++ b/locales/lmo.json +@@ -183,6 +183,7 @@ + "preferences_continue_autoplay_label": "Fa partì en automatico el video seguént: ", + "preferences_listen_label": "Modalità de sól audio preimpostà: ", + "preferences_local_label": "Proxy par i video: ", ++ "preferences_hide_shorts_label": "Scund YouTube Shorts: ", + "preferences_watch_history_label": "Ativà la istoria de reproduzion: ", + "preferences_speed_label": "Velocità preimpostà: ", + "preferences_volume_label": "Volume del reprodutòr: ", +diff --git a/locales/lt.json b/locales/lt.json +index 740be7b6..7a9c3455 100644 +--- a/locales/lt.json ++++ b/locales/lt.json +@@ -373,6 +373,7 @@ + "generic_subscriptions_count_0": "{{count}} prenumerata", + "generic_subscriptions_count_1": "{{count}} prenumeratos", + "generic_subscriptions_count_2": "{{count}} prenumeratų", ++ "preferences_hide_shorts_label": "Slėpti YouTube Shorts: ", + "preferences_watch_history_label": "Įgalinti žiūrėjimo istoriją: ", + "preferences_quality_dash_option_1080p": "1080p", + "invidious": "Invidious", +diff --git a/locales/lv.json b/locales/lv.json +index a867c8f3..b53707a4 100644 +--- a/locales/lv.json ++++ b/locales/lv.json +@@ -27,6 +27,7 @@ + "Import YouTube subscriptions": "Ievietot YouTube CSV vai OPML abonementus", + "E-mail": "E-pasts", + "Preferences": "Iestatījumi", ++ "preferences_hide_shorts_label": "Paslēpt YouTube Shorts: ", + "preferences_category_player": "Atskaņotāja iestatījumi", + "preferences_quality_option_hd720": "HD - 720p", + "preferences_quality_option_medium": "Vidēja", +diff --git a/locales/nb-NO.json b/locales/nb-NO.json +index 38402fed..a81744e6 100644 +--- a/locales/nb-NO.json ++++ b/locales/nb-NO.json +@@ -436,6 +436,7 @@ + "Spanish (Spain)": "Spansk (Spania)", + "Spanish (auto-generated)": "Spansk (laget automatisk)", + "Vietnamese (auto-generated)": "Vietnamesisk (laget automatisk)", ++ "preferences_hide_shorts_label": "Skjul YouTube Shorts: ", + "preferences_watch_history_label": "Aktiver seerhistorikk: ", + "Chinese": "Kinesisk", + "Chinese (China)": "Kinesisk (Kina)", +diff --git a/locales/nl.json b/locales/nl.json +index e9ce7674..507bcb7c 100644 +--- a/locales/nl.json ++++ b/locales/nl.json +@@ -409,6 +409,7 @@ + "generic_subscriptions_count_plural": "{{count}} abonnementen", + "subscriptions_unseen_notifs_count": "{{count}} ongeziene melding", + "subscriptions_unseen_notifs_count_plural": "{{count}} ongeziene meldingen", ++ "preferences_hide_shorts_label": "Verberg YouTube Shorts: ", + "preferences_watch_history_label": "Kijkgeschiedenis inschakelen: ", + "crash_page_switch_instance": "geprobeerd hebt om een andere instantie te gebruiken", + "Portuguese (auto-generated)": "Portugees (automatisch gegenereerd)", +diff --git a/locales/or.json b/locales/or.json +index 948610f1..ba7e9d56 100644 +--- a/locales/or.json ++++ b/locales/or.json +@@ -16,6 +16,7 @@ + "last": "ଗତ", + "New password": "ନୂଆ ପାସ୍‌ୱର୍ଡ଼", + "preferences_quality_dash_option_1440p": "୧୪୪୦ପି", ++ "preferences_hide_shorts_label": "YouTube Shorts ଲୁଚାନ୍ତୁ: ", + "preferences_quality_dash_option_360p": "୩୬୦ପି", + "preferences_quality_option_medium": "ମଧ୍ୟମ", + "preferences_quality_dash_option_1080p": "୧୦୮୦ପି", +diff --git a/locales/pl.json b/locales/pl.json +index d78b7a95..d1dcc62c 100644 +--- a/locales/pl.json ++++ b/locales/pl.json +@@ -472,6 +472,7 @@ + "error_video_not_in_playlist": "Żądany film nie istnieje na tej playliście. Kliknij tutaj, aby przejść do strony głównej playlisty.", + "Popular enabled: ": "Popularne włączone: ", + "search_message_no_results": "Nie znaleziono wyników.", ++ "preferences_hide_shorts_label": "Ukryj YouTube Shorts: ", + "preferences_watch_history_label": "Włącz historię oglądania: ", + "search_filters_apply_button": "Zastosuj wybrane filtry", + "search_message_change_filters_or_query": "Spróbuj poszerzyć zapytanie wyszukiwania i/lub zmienić filtry.", +diff --git a/locales/pt-BR.json b/locales/pt-BR.json +index 1eb3c989..77b4ee4a 100644 +--- a/locales/pt-BR.json ++++ b/locales/pt-BR.json +@@ -445,6 +445,7 @@ + "Video unavailable": "Vídeo indisponível", + "videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`", + "search_filters_title": "Filtro", ++ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ", + "preferences_watch_history_label": "Ativar histórico de exibição: ", + "search_message_no_results": "Nenhum resultado encontrado.", + "search_message_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.", +diff --git a/locales/pt-PT.json b/locales/pt-PT.json +index 449bde77..3538bb1a 100644 +--- a/locales/pt-PT.json ++++ b/locales/pt-PT.json +@@ -402,6 +402,7 @@ + "preferences_region_label": "País do conteúdo: ", + "preferences_quality_dash_option_1440p": "1440p", + "preferences_quality_dash_option_720p": "720p", ++ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ", + "preferences_watch_history_label": "Ativar histórico de reprodução: ", + "preferences_quality_dash_option_best": "Melhor", + "preferences_quality_dash_option_worst": "Pior", +diff --git a/locales/pt.json b/locales/pt.json +index 6438e15b..397aa006 100644 +--- a/locales/pt.json ++++ b/locales/pt.json +@@ -471,6 +471,7 @@ + "search_filters_apply_button": "Aplicar filtros selecionados", + "Spanish (auto-generated)": "Espanhol (gerado automaticamente)", + "Spanish (Mexico)": "Espanhol (México)", ++ "preferences_hide_shorts_label": "Ocultar YouTube Shorts: ", + "preferences_watch_history_label": "Ativar histórico de reprodução: ", + "Chinese (China)": "Chinês (China)", + "Russian (auto-generated)": "Russo (gerado automaticamente)", +diff --git a/locales/ro.json b/locales/ro.json +index ccbeef63..6c39b1f8 100644 +--- a/locales/ro.json ++++ b/locales/ro.json +@@ -329,6 +329,7 @@ + "subscriptions_unseen_notifs_count_2": "{{count}} de notificări neverificate", + "crash_page_refresh": "încercat să reîmprospătați pagina", + "crash_page_switch_instance": "am încercat să folosim o altă instanță", ++ "preferences_hide_shorts_label": "Ascunde YouTube Shorts: ", + "preferences_watch_history_label": "Activează istoricul: ", + "invidious": "Invidious", + "preferences_vr_mode_label": "Videoclipuri interactive de 360 de grade (necesită WebGL): ", +diff --git a/locales/ru.json b/locales/ru.json +index 906f00fc..a42a9d82 100644 +--- a/locales/ru.json ++++ b/locales/ru.json +@@ -468,6 +468,7 @@ + "Video unavailable": "Видео недоступно", + "preferences_save_player_pos_label": "Запоминать позицию: ", + "preferences_region_label": "Страна источник ", ++ "preferences_hide_shorts_label": "Скрыть YouTube Shorts: ", + "preferences_watch_history_label": "Включить историю просмотров: ", + "search_filters_title": "Фильтр", + "search_filters_duration_option_none": "Любой длины", +diff --git a/locales/si.json b/locales/si.json +index 4637cbd2..f9103ea5 100644 +--- a/locales/si.json ++++ b/locales/si.json +@@ -40,6 +40,7 @@ + "preferences_continue_label": "මීලඟට වාදනය කරන්න: ", + "preferences_continue_autoplay_label": "මීළඟ වීඩියෝව ස්වයංක්‍රීයව ධාවනය කරන්න: ", + "preferences_local_label": "Proxy වීඩියෝ: ", ++ "preferences_hide_shorts_label": "YouTube Shorts සඟවන්න: ", + "preferences_watch_history_label": "නැරඹුම් ඉතිහාසය සබල කරන්න: ", + "preferences_speed_label": "පෙරනිමි වේගය: ", + "preferences_quality_option_dash": "DASH (අනුවර්තිත ගුණත්වය)", +diff --git a/locales/sk.json b/locales/sk.json +index 8add0f57..e90a0852 100644 +--- a/locales/sk.json ++++ b/locales/sk.json +@@ -108,6 +108,7 @@ + "preferences_quality_dash_label": "Preferovaná video kvalita DASH: ", + "preferences_quality_option_dash": "DASH (adaptívna kvalita)", + "preferences_quality_option_small": "Malá", ++ "preferences_hide_shorts_label": "Skryť YouTube Shorts: ", + "preferences_watch_history_label": "Zapnúť históriu pozerania: ", + "preferences_quality_dash_option_240p": "240p", + "preferences_quality_dash_option_1080p": "1080p", +diff --git a/locales/sl.json b/locales/sl.json +index c36ad522..11465ae4 100644 +--- a/locales/sl.json ++++ b/locales/sl.json +@@ -272,6 +272,7 @@ + "An alternative front-end to YouTube": "Alternativni vmesnik za YouTube", + "preferences_category_player": "Nastavitve predvajalnika", + "preferences_continue_label": "Privzeto predvajaj naslednjega: ", ++ "preferences_hide_shorts_label": "Skrij YouTube Shorts: ", + "preferences_watch_history_label": "Omogoči zgodovino ogledov: ", + "preferences_quality_option_medium": "srednja", + "preferences_quality_option_dash": "DASH (prilagodljiva kakovost)", +diff --git a/locales/sq.json b/locales/sq.json +index cdf4b605..6de0e267 100644 +--- a/locales/sq.json ++++ b/locales/sq.json +@@ -444,6 +444,7 @@ + "error_video_not_in_playlist": "Videoja e kërkuar s’ekziston në këtë luajlistë. Klikoni këtu për faqen hyrëse të luajlistës.", + "search_message_use_another_instance": "Mundeni edhe të kërkoni në një instancë tjetër.", + "search_filters_date_label": "Datë ngarkimi", ++ "preferences_hide_shorts_label": "Fshih YouTube Shorts: ", + "preferences_watch_history_label": "Aktivizo historik parjesh: ", + "Top enabled: ": "Me kryesueset të aktivizuara: ", + "preferences_video_loop_label": "Përsërite gjithmonë: ", +diff --git a/locales/sr.json b/locales/sr.json +index c6614ba5..4c9f98da 100644 +--- a/locales/sr.json ++++ b/locales/sr.json +@@ -394,6 +394,7 @@ + "generic_count_minutes_1": "{{count}} minuta", + "generic_count_minutes_2": "{{count}} minuta", + "preferences_quality_dash_option_720p": "720p", ++ "preferences_hide_shorts_label": "Сакриј YouTube Shorts: ", + "preferences_watch_history_label": "Omogući istoriju gledanja: ", + "user_saved_playlists": "Sačuvanih plejlista: `x`", + "Spanish (Spain)": "Španski (Španija)", +diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json +index e6ab0f35..1d52b578 100644 +--- a/locales/sr_Cyrl.json ++++ b/locales/sr_Cyrl.json +@@ -394,6 +394,7 @@ + "generic_count_minutes_1": "{{count}} минута", + "generic_count_minutes_2": "{{count}} минута", + "preferences_quality_dash_option_720p": "720p", ++ "preferences_hide_shorts_label": "Сакриј YouTube Shorts: ", + "preferences_watch_history_label": "Омогући историју гледања: ", + "user_saved_playlists": "Сачуваних плејлиста: `x`", + "Spanish (Spain)": "Шпански (Шпанија)", +diff --git a/locales/sv-SE.json b/locales/sv-SE.json +index 8f050d98..86d6996d 100644 +--- a/locales/sv-SE.json ++++ b/locales/sv-SE.json +@@ -384,6 +384,7 @@ + "generic_count_minutes": "{{count}}minut", + "generic_count_minutes_plural": "{{count}}minuter", + "preferences_quality_dash_option_720p": "720p", ++ "preferences_hide_shorts_label": "Dölj YouTube Shorts: ", + "preferences_watch_history_label": "Aktivera visningshistorik: ", + "user_saved_playlists": "`x` sparade spellistor", + "Spanish (Spain)": "Spanska (Spanien)", +diff --git a/locales/ta.json b/locales/ta.json +index 89e58668..2c0ca849 100644 +--- a/locales/ta.json ++++ b/locales/ta.json +@@ -54,6 +54,7 @@ + "preferences_autoplay_label": "தன்னியக்க: ", + "preferences_continue_label": "இயல்பாக அடுத்து விளையாடுங்கள்: ", + "preferences_local_label": "பதிலாள் வீடியோக்கள்: ", ++ "preferences_hide_shorts_label": "YouTube Shorts மறைக்கவும் : ", + "preferences_watch_history_label": "கண்காணிப்பு வரலாற்றை இயக்கு: ", + "preferences_speed_label": "இயல்புநிலை வேகம்: ", + "preferences_quality_label": "விருப்பமான வீடியோ தரம்: ", +diff --git a/locales/tr.json b/locales/tr.json +index cf3f8987..8fbfa848 100644 +--- a/locales/tr.json ++++ b/locales/tr.json +@@ -451,6 +451,7 @@ + "Portuguese (auto-generated)": "Portekizce (Otomatik Oluşturuldu)", + "Spanish (Spain)": "İspanyolca (İspanya)", + "Vietnamese (auto-generated)": "Vietnamca (Otomatik Oluşturuldu)", ++ "preferences_hide_shorts_label": "YouTube Shorts gizle: ", + "preferences_watch_history_label": "İzleme Geçmişini Etkinleştir: ", + "search_message_use_another_instance": "Ayrıca başka bir örnekte arayabilirsiniz.", + "search_filters_type_option_all": "Herhangi Bir Tür", +diff --git a/locales/uk.json b/locales/uk.json +index b99923e2..b1b8da03 100644 +--- a/locales/uk.json ++++ b/locales/uk.json +@@ -362,6 +362,7 @@ + "user_created_playlists": "Створено списків відтворення: `x`", + "user_saved_playlists": "Збережено списків відтворення: `x`", + "Video unavailable": "Відео недоступне", ++ "preferences_hide_shorts_label": "Сховати YouTube Shorts: ", + "preferences_watch_history_label": "Історія переглядів: ", + "preferences_quality_dash_label": "Бажана DASH-якість відео: ", + "preferences_quality_dash_option_144p": "144p", +diff --git a/locales/vi.json b/locales/vi.json +index 9c4a5a15..0d8a7eec 100644 +--- a/locales/vi.json ++++ b/locales/vi.json +@@ -343,6 +343,7 @@ + "Song: ": "Ca khúc: ", + "Premieres in `x`": "Trình chiếu ở `x`", + "preferences_quality_dash_option_worst": "Tệ nhất", ++ "preferences_hide_shorts_label": "Ẩn YouTube Shorts: ", + "preferences_watch_history_label": "Bật lịch sử video đã xem ", + "preferences_quality_option_hd720": "HD720", + "unsubscribe": "hủy đăng kí", +diff --git a/locales/zh-CN.json b/locales/zh-CN.json +index f3bc660b..f4d51449 100644 +--- a/locales/zh-CN.json ++++ b/locales/zh-CN.json +@@ -435,6 +435,7 @@ + "French (auto-generated)": "法语 (自动生成)", + "Turkish (auto-generated)": "土耳其语 (自动生成)", + "Spanish (Spain)": "西班牙语 (西班牙)", ++ "preferences_hide_shorts_label": "隐藏 YouTube Shorts: ", + "preferences_watch_history_label": "启用观看历史: ", + "search_message_use_another_instance": "你也可以 在另一实例上搜索。", + "search_filters_title": "过滤器", +diff --git a/locales/zh-TW.json b/locales/zh-TW.json +index 77805349..23852f40 100644 +--- a/locales/zh-TW.json ++++ b/locales/zh-TW.json +@@ -435,6 +435,7 @@ + "Portuguese (Brazil)": "葡萄牙語(巴西)", + "Japanese (auto-generated)": "日語(自動產生)", + "Portuguese (auto-generated)": "葡萄牙語(自動產生)", ++ "preferences_hide_shorts_label": "隱藏 YouTube Shorts: ", + "preferences_watch_history_label": "啟用觀看紀錄: ", + "search_message_change_filters_or_query": "嘗試擴大您的查詢字詞與/或變更過濾條件。", + "search_filters_apply_button": "套用選定的過濾條件", +diff --git a/src/invidious/config.cr b/src/invidious/config.cr +index 6a36c335..c889a776 100644 +--- a/src/invidious/config.cr ++++ b/src/invidious/config.cr +@@ -31,6 +31,7 @@ struct ConfigPreferences + property listen : Bool = false + property local : Bool = false + property locale : String = "en-US" ++ property hide_shorts : Bool = false + property watch_history : Bool = true + property max_results : Int32 = 40 + property notifications_only : Bool = false +diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr +index 39ca77c0..74c78dcc 100644 +--- a/src/invidious/routes/preferences.cr ++++ b/src/invidious/routes/preferences.cr +@@ -140,6 +140,10 @@ module Invidious::Routes::PreferencesRoute + unseen_only ||= "off" + unseen_only = unseen_only == "on" + ++ hide_shorts = env.params.body["hide_shorts"]?.try &.as(String) ++ hide_shorts ||= "off" ++ hide_shorts = hide_shorts == "on" ++ + notifications_only = env.params.body["notifications_only"]?.try &.as(String) + notifications_only ||= "off" + notifications_only = notifications_only == "on" +@@ -174,6 +178,7 @@ module Invidious::Routes::PreferencesRoute + speed: speed, + thin_mode: thin_mode, + unseen_only: unseen_only, ++ hide_shorts: hide_shorts, + video_loop: video_loop, + volume: volume, + extend_desc: extend_desc, +diff --git a/src/invidious/user/preferences.cr b/src/invidious/user/preferences.cr +index 0a8525f3..76bb00de 100644 +--- a/src/invidious/user/preferences.cr ++++ b/src/invidious/user/preferences.cr +@@ -52,6 +52,7 @@ struct Preferences + property speed : Float32 = CONFIG.default_user_preferences.speed + property thin_mode : Bool = CONFIG.default_user_preferences.thin_mode + property unseen_only : Bool = CONFIG.default_user_preferences.unseen_only ++ property hide_shorts : Bool = CONFIG.default_user_preferences.hide_shorts + property video_loop : Bool = CONFIG.default_user_preferences.video_loop + property extend_desc : Bool = CONFIG.default_user_preferences.extend_desc + property volume : Int32 = CONFIG.default_user_preferences.volume +diff --git a/src/invidious/users.cr b/src/invidious/users.cr +index 0b2d1ef5..529228ac 100644 +--- a/src/invidious/users.cr ++++ b/src/invidious/users.cr +@@ -54,37 +54,69 @@ def get_subscription_feed(user, max_results = 40, page = 1) + + # "SELECT cv.* FROM channel_videos cv JOIN users ON cv.ucid = any(users.subscriptions) WHERE users.email = $1 AND published > now() - interval '1 month' ORDER BY published DESC" + # "SELECT DISTINCT ON (cv.ucid) cv.* FROM channel_videos cv JOIN users ON cv.ucid = any(users.subscriptions) WHERE users.email = ? AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' ORDER BY ucid, published DESC" +- videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \ +- "FROM channel_videos cv " \ +- "JOIN users ON cv.ucid = any(users.subscriptions) " \ +- "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \ +- "ORDER BY ucid, published DESC", user.email, as: ChannelVideo) ++ if user.preferences.hide_shorts ++ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \ ++ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo) ++ else ++ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \ ++ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo) ++ end + else + # Show latest video from each channel + +- videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \ +- "FROM channel_videos cv " \ +- "JOIN users ON cv.ucid = any(users.subscriptions) " \ +- "WHERE users.email = $1 AND published > now() - interval '1 month' " \ +- "ORDER BY ucid, published DESC", user.email, as: ChannelVideo) ++ if user.preferences.hide_shorts ++ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \ ++ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo) ++ else ++ videos = PG_DB.query_all("SELECT DISTINCT ON (cv.ucid) cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND published > now() - interval '1 month' " \ ++ "ORDER BY ucid, published DESC", user.email, as: ChannelVideo) ++ end + end + + videos.sort_by!(&.published).reverse! + else + if user.preferences.unseen_only + # Only show unwatched +- videos = PG_DB.query_all("SELECT cv.* " \ +- "FROM channel_videos cv " \ +- "JOIN users ON cv.ucid = any(users.subscriptions) " \ +- "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \ +- "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo) ++ if user.preferences.hide_shorts ++ videos = PG_DB.query_all("SELECT cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \ ++ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo) ++ else ++ videos = PG_DB.query_all("SELECT cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND NOT cv.id = any(users.watched) AND published > now() - interval '1 month' " \ ++ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo) ++ end + else + # Sort subscriptions as normal +- videos = PG_DB.query_all("SELECT cv.* " \ +- "FROM channel_videos cv " \ +- "JOIN users ON cv.ucid = any(users.subscriptions) " \ +- "WHERE users.email = $1 AND published > now() - interval '1 month' " \ +- "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo) ++ if user.preferences.hide_shorts ++ videos = PG_DB.query_all("SELECT cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND published > now() - interval '1 month' AND cv.length_seconds > 180 " \ ++ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo) ++ else ++ videos = PG_DB.query_all("SELECT cv.* " \ ++ "FROM channel_videos cv " \ ++ "JOIN users ON cv.ucid = any(users.subscriptions) " \ ++ "WHERE users.email = $1 AND published > now() - interval '1 month' " \ ++ "ORDER BY published DESC LIMIT $2 OFFSET $3", user.email, limit, offset, as: ChannelVideo) ++ end + end + end + +diff --git a/src/invidious/views/user/preferences.ecr b/src/invidious/views/user/preferences.ecr +index cf8b5593..83847bea 100644 +--- a/src/invidious/views/user/preferences.ecr ++++ b/src/invidious/views/user/preferences.ecr +@@ -216,6 +216,11 @@ + checked<% end %>> +
+ ++
++ ++ checked<% end %>> ++
++ +
+ + checked<% end %>>