From 89c8b1b901062c729370370116aa9127a39cd214 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Tue, 2 Sep 2025 10:57:29 -0400 Subject: [PATCH 01/12] CI: fix wrong if statement for build-docker job (#5442) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52559825..ce166b7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,7 @@ jobs: - uses: actions/checkout@v5 - name: Use ARM64 Dockerfile if ARM64 - if: ${{ matrix.name }} == "ARM64" + if: ${{ matrix.name == 'ARM64' }} run: sed -i 's/Dockerfile/Dockerfile.arm64/' docker-compose.yml - name: Build Docker From 324a416fd47cb7adbfdda20622dd0f47b60dd661 Mon Sep 17 00:00:00 2001 From: Emilien <4016501+unixfox@users.noreply.github.com> Date: Sat, 3 May 2025 01:10:12 +0200 Subject: [PATCH 02/12] initial support for base_url with invidious companion + proxy invidious_companion --- config/config.example.yml | 6 ++-- src/invidious/routes/companion.cr | 37 +++++++++++++++++++++ src/invidious/routes/embed.cr | 8 +++-- src/invidious/routes/watch.cr | 8 +++-- src/invidious/routing.cr | 9 ++++- src/invidious/yt_backend/connection_pool.cr | 34 ++++++++++++++----- src/invidious/yt_backend/youtube_api.cr | 30 ++++++++++------- 7 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 src/invidious/routes/companion.cr diff --git a/config/config.example.yml b/config/config.example.yml index e8ab658b..60fbc825 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -75,7 +75,7 @@ db: ## If you are using a reverse proxy then you will probably need to ## configure the public_url to be the same as the domain used for Invidious. ## Also apply when used from an external IP address (without a domain). -## Examples: https://MYINVIDIOUSDOMAIN or http://192.168.1.100:8282 +## Examples: https://MYINVIDIOUSDOMAIN/companion or http://192.168.1.100:8282/companion ## ## Both parameter can have identical URL when Invidious is hosted in ## an internal network or at home or locally (localhost). @@ -84,8 +84,8 @@ db: ## Default: ## #invidious_companion: -# - private_url: "http://localhost:8282" -# public_url: "http://localhost:8282" +# - private_url: "http://localhost:8282/companion" +# public_url: "http://localhost:8282/companion" ## ## API key for Invidious companion, used for securing the communication diff --git a/src/invidious/routes/companion.cr b/src/invidious/routes/companion.cr new file mode 100644 index 00000000..23b62e9d --- /dev/null +++ b/src/invidious/routes/companion.cr @@ -0,0 +1,37 @@ +module Invidious::Routes::Companion + # /companion + def self.get_companion(env) + url = env.request.path.lchop("/companion") + + begin + COMPANION_POOL.client &.get(url, env.request.header) do |resp| + return self.proxy_companion(env, resp) + end + rescue ex + end + end + + def self.options_companion(env) + url = env.request.path.lchop("/companion") + + begin + COMPANION_POOL.client &.options(url, env.request.header) do |resp| + return self.proxy_companion(env, resp) + end + rescue ex + end + end + + private def self.proxy_companion(env, response) + env.response.status_code = response.status_code + response.headers.each do |key, value| + env.response.headers[key] = value + end + + if response.status_code >= 300 + return env.response.headers.delete("Transfer-Encoding") + end + + return proxy_file(response, env) + end +end diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr index 721a57f8..2fb7bebf 100644 --- a/src/invidious/routes/embed.cr +++ b/src/invidious/routes/embed.cr @@ -209,10 +209,14 @@ module Invidious::Routes::Embed if CONFIG.invidious_companion.present? invidious_companion = CONFIG.invidious_companion.sample + invidious_companion_urls = CONFIG.invidious_companion.map do |companion| + uri = + "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" + end.join(" ") env.response.headers["Content-Security-Policy"] = env.response.headers["Content-Security-Policy"] - .gsub("media-src", "media-src #{invidious_companion.public_url}") - .gsub("connect-src", "connect-src #{invidious_companion.public_url}") + .gsub("media-src", "media-src #{invidious_companion_urls}") + .gsub("connect-src", "connect-src #{invidious_companion_urls}") end rendered "embed" diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index e777b3f1..a50a146d 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -194,10 +194,14 @@ module Invidious::Routes::Watch if CONFIG.invidious_companion.present? invidious_companion = CONFIG.invidious_companion.sample + invidious_companion_urls = CONFIG.invidious_companion.map do |companion| + uri = + "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" + end.join(" ") env.response.headers["Content-Security-Policy"] = env.response.headers["Content-Security-Policy"] - .gsub("media-src", "media-src #{invidious_companion.public_url}") - .gsub("connect-src", "connect-src #{invidious_companion.public_url}") + .gsub("media-src", "media-src #{invidious_companion_urls}") + .gsub("connect-src", "connect-src #{invidious_companion_urls}") end templated "watch" diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index 46b71f1f..b95ac706 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -188,7 +188,7 @@ module Invidious::Routing end # ------------------- - # Media proxy routes + # Proxy routes # ------------------- def register_api_manifest_routes @@ -223,6 +223,13 @@ module Invidious::Routing get "/vi/:id/:name", Routes::Images, :thumbnails end + def register_companion_routes + if CONFIG.invidious_companion.present? + get "/companion/*", Routes::Companion, :get_companion + options "/companion/*", Routes::Companion, :options_companion + end + end + # ------------------- # API routes # ------------------- diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr index 0daed46c..97ce7c40 100644 --- a/src/invidious/yt_backend/connection_pool.cr +++ b/src/invidious/yt_backend/connection_pool.cr @@ -46,8 +46,22 @@ struct YoutubeConnectionPool end end +class CompanionWrapper + property client : HTTP::Client + property companion : Config::CompanionConfig + + def initialize(companion : Config::CompanionConfig) + @companion = companion + @client = HTTP::Client.new(companion.private_url) + end + + def close + @client.close + end +end + struct CompanionConnectionPool - property pool : DB::Pool(HTTP::Client) + property pool : DB::Pool(CompanionWrapper) def initialize(capacity = 5, timeout = 5.0) options = DB::Pool::Options.new( @@ -57,26 +71,28 @@ struct CompanionConnectionPool checkout_timeout: timeout ) - @pool = DB::Pool(HTTP::Client).new(options) do + @pool = DB::Pool(CompanionWrapper).new(options) do companion = CONFIG.invidious_companion.sample - next make_client(companion.private_url, use_http_proxy: false) + client = make_client(companion.private_url, use_http_proxy: false) + CompanionWrapper.new(companion: companion) end end def client(&) - conn = pool.checkout + wrapper = pool.checkout begin - response = yield conn + response = yield wrapper rescue ex - conn.close + wrapper.client.close companion = CONFIG.invidious_companion.sample - conn = make_client(companion.private_url, use_http_proxy: false) + client = make_client(companion.private_url, use_http_proxy: false) + wrapper = CompanionWrapper.new(companion: companion) - response = yield conn + response = yield wrapper ensure - pool.release(conn) + pool.release(wrapper) end response diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr index d287a42f..f87e3091 100644 --- a/src/invidious/yt_backend/youtube_api.cr +++ b/src/invidious/yt_backend/youtube_api.cr @@ -701,22 +701,28 @@ module YoutubeAPI # Send the POST request begin - response = COMPANION_POOL.client &.post(endpoint, headers: headers, body: data.to_json) - body = response.body - if (response.status_code != 200) - raise Exception.new( - "Error while communicating with Invidious companion: \ - status code: #{response.status_code} and body: #{body.dump}" - ) + response_body = "" + + COMPANION_POOL.client do |wrapper| + companion_base_url = wrapper.companion.private_url.path + puts "Using companion: #{wrapper.companion.private_url}" + + response = wrapper.client.post(companion_base_url + endpoint, headers: headers, body: data.to_json) + response_body = response.body + + if response.status_code != 200 + raise Exception.new( + "Error while communicating with Invidious companion: " \ + "status code: #{response.status_code} and body: #{response_body.dump}" + ) + end end + + # Convert result to Hash + return JSON.parse(response_body).as_h rescue ex raise InfoException.new("Error while communicating with Invidious companion: " + (ex.message || "no extra info found")) end - - # Convert result to Hash - initial_data = JSON.parse(body).as_h - - return initial_data end #################################################################### From 42b955d713c54632f1886641dcedac27ee625c8a Mon Sep 17 00:00:00 2001 From: Emilien <4016501+unixfox@users.noreply.github.com> Date: Sat, 14 Jun 2025 18:01:44 +0200 Subject: [PATCH 03/12] chore: add the suggestions --- src/invidious/routes/before_all.cr | 1 + src/invidious/routes/companion.cr | 6 +----- src/invidious/yt_backend/connection_pool.cr | 15 ++++++++++----- src/invidious/yt_backend/youtube_api.cr | 16 ++++------------ 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr index b5269668..63b935ec 100644 --- a/src/invidious/routes/before_all.cr +++ b/src/invidious/routes/before_all.cr @@ -63,6 +63,7 @@ module Invidious::Routes::BeforeAll "/videoplayback", "/latest_version", "/download", + "/companion/", }.any? { |r| env.request.resource.starts_with? r } if env.request.cookies.has_key? "SID" diff --git a/src/invidious/routes/companion.cr b/src/invidious/routes/companion.cr index 23b62e9d..cd7ed422 100644 --- a/src/invidious/routes/companion.cr +++ b/src/invidious/routes/companion.cr @@ -28,10 +28,6 @@ module Invidious::Routes::Companion env.response.headers[key] = value end - if response.status_code >= 300 - return env.response.headers.delete("Transfer-Encoding") - end - - return proxy_file(response, env) + return IO.copy response.body_io, env.response end end diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr index 97ce7c40..45455a8a 100644 --- a/src/invidious/yt_backend/connection_pool.cr +++ b/src/invidious/yt_backend/connection_pool.cr @@ -46,13 +46,18 @@ struct YoutubeConnectionPool end end -class CompanionWrapper +# Packages a `HTTP::Client` to an Invidious companion instance alongside the configuration for that instance. +# +# This is used as the resource for the `CompanionPool` as to allow the ability to +# proxy the requests to Invidious companion from Invidious directly. +# Instead of setting up routes in a reverse proxy. +struct CompanionWrapper property client : HTTP::Client property companion : Config::CompanionConfig def initialize(companion : Config::CompanionConfig) @companion = companion - @client = HTTP::Client.new(companion.private_url) + @client = make_client(companion.private_url, use_http_proxy: false) end def close @@ -73,7 +78,7 @@ struct CompanionConnectionPool @pool = DB::Pool(CompanionWrapper).new(options) do companion = CONFIG.invidious_companion.sample - client = make_client(companion.private_url, use_http_proxy: false) + make_client(companion.private_url, use_http_proxy: false) CompanionWrapper.new(companion: companion) end end @@ -84,10 +89,10 @@ struct CompanionConnectionPool begin response = yield wrapper rescue ex - wrapper.client.close + wrapper.close companion = CONFIG.invidious_companion.sample - client = make_client(companion.private_url, use_http_proxy: false) + make_client(companion.private_url, use_http_proxy: false) wrapper = CompanionWrapper.new(companion: companion) response = yield wrapper diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr index f87e3091..4b39acd7 100644 --- a/src/invidious/yt_backend/youtube_api.cr +++ b/src/invidious/yt_backend/youtube_api.cr @@ -701,25 +701,17 @@ module YoutubeAPI # Send the POST request begin - response_body = "" + response_body = Hash(String, JSON::Any).new COMPANION_POOL.client do |wrapper| companion_base_url = wrapper.companion.private_url.path - puts "Using companion: #{wrapper.companion.private_url}" - response = wrapper.client.post(companion_base_url + endpoint, headers: headers, body: data.to_json) - response_body = response.body - - if response.status_code != 200 - raise Exception.new( - "Error while communicating with Invidious companion: " \ - "status code: #{response.status_code} and body: #{response_body.dump}" - ) + wrapper.client.post("#{companion_base_url}#{endpoint}", headers: headers, body: data.to_json) do | response | + response_body = JSON.parse(response.body_io).as_h end end - # Convert result to Hash - return JSON.parse(response_body).as_h + return response_body rescue ex raise InfoException.new("Error while communicating with Invidious companion: " + (ex.message || "no extra info found")) end From cba2adc6ef19d52736356a84af21fa513c7b3f74 Mon Sep 17 00:00:00 2001 From: Emilien <4016501+unixfox@users.noreply.github.com> Date: Sat, 14 Jun 2025 19:10:52 +0200 Subject: [PATCH 04/12] fix csp + progress proxy + allow omit public_url --- config/config.example.yml | 8 ++++++++ src/invidious/config.cr | 11 +++++++++++ src/invidious/routes/companion.cr | 23 +++++++++++++++++------ src/invidious/routes/embed.cr | 13 ++++++++----- src/invidious/routes/watch.cr | 13 ++++++++----- src/invidious/routing.cr | 1 + 6 files changed, 53 insertions(+), 16 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 60fbc825..cabbecfd 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -80,12 +80,20 @@ db: ## Both parameter can have identical URL when Invidious is hosted in ## an internal network or at home or locally (localhost). ## +## NOTE: If public_url is omitted, Invidious will use its built-in proxy +## to route companion requests through /companion, which is useful for +## simple setups where companion runs on the same network. When using +## the built-in proxy, CSP headers are not modified since requests +## stay within the same domain. +## ## Accepted values: "http(s)://:" ## Default: ## #invidious_companion: # - private_url: "http://localhost:8282/companion" # public_url: "http://localhost:8282/companion" +# # Example with built-in proxy (omit public_url): +# # - private_url: "http://localhost:8282/companion" ## ## API key for Invidious companion, used for securing the communication diff --git a/src/invidious/config.cr b/src/invidious/config.cr index 4d69854c..e47e405c 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -82,6 +82,9 @@ class Config @[YAML::Field(converter: Preferences::URIConverter)] property public_url : URI = URI.parse("") + + # Indicates if this companion instance uses the built-in proxy + property builtin_proxy : Bool = false end # Number of threads to use for crawling videos from channels (for updating subscriptions) @@ -271,6 +274,14 @@ class Config puts "Config: The value of 'invidious_companion_key' needs to be a size of 16 characters." exit(1) end + + # Set public_url to built-in proxy path when omitted + config.invidious_companion.each do |companion| + if companion.public_url.to_s.empty? + companion.public_url = URI.parse("/companion") + companion.builtin_proxy = true + end + end elsif config.signature_server puts("WARNING: inv-sig-helper is deprecated. Please switch to Invidious companion: https://docs.invidious.io/companion-installation/") else diff --git a/src/invidious/routes/companion.cr b/src/invidious/routes/companion.cr index cd7ed422..bcfbad6b 100644 --- a/src/invidious/routes/companion.cr +++ b/src/invidious/routes/companion.cr @@ -1,22 +1,33 @@ module Invidious::Routes::Companion # /companion def self.get_companion(env) - url = env.request.path.lchop("/companion") + url = env.request.path + if env.request.query + url += "?#{env.request.query}" + end begin - COMPANION_POOL.client &.get(url, env.request.header) do |resp| - return self.proxy_companion(env, resp) + COMPANION_POOL.client do |wrapper| + puts env.request.headers + wrapper.client.get(url, env.request.headers) do |resp| + return self.proxy_companion(env, resp) + end end rescue ex end end def self.options_companion(env) - url = env.request.path.lchop("/companion") + url = env.request.path + if env.request.query + url += "?#{env.request.query}" + end begin - COMPANION_POOL.client &.options(url, env.request.header) do |resp| - return self.proxy_companion(env, resp) + COMPANION_POOL.client do |wrapper| + wrapper.client.options(url, env.request.headers) do |resp| + return self.proxy_companion(env, resp) + end end rescue ex end diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr index 2fb7bebf..1318b290 100644 --- a/src/invidious/routes/embed.cr +++ b/src/invidious/routes/embed.cr @@ -209,14 +209,17 @@ module Invidious::Routes::Embed if CONFIG.invidious_companion.present? invidious_companion = CONFIG.invidious_companion.sample - invidious_companion_urls = CONFIG.invidious_companion.map do |companion| + invidious_companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion| uri = "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" end.join(" ") - env.response.headers["Content-Security-Policy"] = - env.response.headers["Content-Security-Policy"] - .gsub("media-src", "media-src #{invidious_companion_urls}") - .gsub("connect-src", "connect-src #{invidious_companion_urls}") + + if !invidious_companion_urls.empty? + env.response.headers["Content-Security-Policy"] = + env.response.headers["Content-Security-Policy"] + .gsub("media-src", "media-src #{invidious_companion_urls}") + .gsub("connect-src", "connect-src #{invidious_companion_urls}") + end end rendered "embed" diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index a50a146d..83893457 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -194,14 +194,17 @@ module Invidious::Routes::Watch if CONFIG.invidious_companion.present? invidious_companion = CONFIG.invidious_companion.sample - invidious_companion_urls = CONFIG.invidious_companion.map do |companion| + invidious_companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion| uri = "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" end.join(" ") - env.response.headers["Content-Security-Policy"] = - env.response.headers["Content-Security-Policy"] - .gsub("media-src", "media-src #{invidious_companion_urls}") - .gsub("connect-src", "connect-src #{invidious_companion_urls}") + + if !invidious_companion_urls.empty? + env.response.headers["Content-Security-Policy"] = + env.response.headers["Content-Security-Policy"] + .gsub("media-src", "media-src #{invidious_companion_urls}") + .gsub("connect-src", "connect-src #{invidious_companion_urls}") + end end templated "watch" diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index b95ac706..a51bb4b6 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -46,6 +46,7 @@ module Invidious::Routing self.register_api_v1_routes self.register_api_manifest_routes self.register_video_playback_routes + self.register_companion_routes end # ------------------- From 1653dd629e34bf4e5dc081ac15c070607e7d2908 Mon Sep 17 00:00:00 2001 From: Emilien <4016501+unixfox@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:35:11 +0200 Subject: [PATCH 05/12] fix formatting --- src/invidious/routes/embed.cr | 2 +- src/invidious/routes/watch.cr | 2 +- src/invidious/yt_backend/connection_pool.cr | 2 +- src/invidious/yt_backend/youtube_api.cr | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr index 1318b290..6b0887d5 100644 --- a/src/invidious/routes/embed.cr +++ b/src/invidious/routes/embed.cr @@ -213,7 +213,7 @@ module Invidious::Routes::Embed uri = "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" end.join(" ") - + if !invidious_companion_urls.empty? env.response.headers["Content-Security-Policy"] = env.response.headers["Content-Security-Policy"] diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 83893457..8a4fa246 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -198,7 +198,7 @@ module Invidious::Routes::Watch uri = "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}" end.join(" ") - + if !invidious_companion_urls.empty? env.response.headers["Content-Security-Policy"] = env.response.headers["Content-Security-Policy"] diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr index 45455a8a..42241d15 100644 --- a/src/invidious/yt_backend/connection_pool.cr +++ b/src/invidious/yt_backend/connection_pool.cr @@ -47,7 +47,7 @@ struct YoutubeConnectionPool end # Packages a `HTTP::Client` to an Invidious companion instance alongside the configuration for that instance. -# +# # This is used as the resource for the `CompanionPool` as to allow the ability to # proxy the requests to Invidious companion from Invidious directly. # Instead of setting up routes in a reverse proxy. diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr index 4b39acd7..6fa8ae0e 100644 --- a/src/invidious/yt_backend/youtube_api.cr +++ b/src/invidious/yt_backend/youtube_api.cr @@ -706,7 +706,7 @@ module YoutubeAPI COMPANION_POOL.client do |wrapper| companion_base_url = wrapper.companion.private_url.path - wrapper.client.post("#{companion_base_url}#{endpoint}", headers: headers, body: data.to_json) do | response | + wrapper.client.post("#{companion_base_url}#{endpoint}", headers: headers, body: data.to_json) do |response| response_body = JSON.parse(response.body_io).as_h end end From 5e9d51c06e387ea38108234406cfa0aa5e6fc12c Mon Sep 17 00:00:00 2001 From: syeopite Date: Wed, 28 May 2025 15:38:49 -0700 Subject: [PATCH 06/12] Refactor `FilteredCompressHandler` to inherit from stdlib This changes its behavior to align with the stdlib variant in that compression is now delayed till the moment that the server begins to send a response. This allows the handler to avoid compressing empty responses,and safeguards against any double compression of content that may occur if another handler decides to compressi ts response. This does however come at the drawback(?) of it now removing `content-length` headers on requests if it exists; since compression makes the value inaccurate anyway. See: https://github.com/crystal-lang/crystal/pull/9625 --- src/invidious/helpers/handlers.cr | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr index 13ea9fe9..7c5ef118 100644 --- a/src/invidious/helpers/handlers.cr +++ b/src/invidious/helpers/handlers.cr @@ -61,28 +61,13 @@ class Kemal::ExceptionHandler end end -class FilteredCompressHandler < Kemal::Handler +class FilteredCompressHandler < HTTP::CompressHandler exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/sb/*", "/ggpht/*", "/api/v1/auth/notifications"] exclude ["/api/v1/auth/notifications", "/data_control"], "POST" - def call(env) - return call_next env if exclude_match? env - - {% if flag?(:without_zlib) %} - call_next env - {% else %} - request_headers = env.request.headers - - if request_headers.includes_word?("Accept-Encoding", "gzip") - env.response.headers["Content-Encoding"] = "gzip" - env.response.output = Compress::Gzip::Writer.new(env.response.output, sync_close: true) - elsif request_headers.includes_word?("Accept-Encoding", "deflate") - env.response.headers["Content-Encoding"] = "deflate" - env.response.output = Compress::Deflate::Writer.new(env.response.output, sync_close: true) - end - - call_next env - {% end %} + def call(context) + return call_next context if exclude_match? context + super end end From 21c13bba9ddee5db02b7584efe5ae5912cbeabd6 Mon Sep 17 00:00:00 2001 From: Emilien <4016501+unixfox@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:57:48 +0200 Subject: [PATCH 07/12] chore: use api captions from companion when available --- src/invidious/views/components/player.ecr | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index af352102..85fa4373 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -65,12 +65,18 @@ <% end %> <% end %> - <% preferred_captions.each do |caption| %> - + <% preferred_captions.each do |caption| + api_captions_url = "/api/v1/captions/" + api_captions_url = invidious_companion.public_url.to_s + api_captions_url if (invidious_companion) + %> + <% end %> - <% captions.each do |caption| %> - + <% captions.each do |caption| + api_captions_url = "/api/v1/captions/" + api_captions_url = invidious_companion.public_url.to_s + api_captions_url if (invidious_companion) + %> + <% end %> <% end %> From cf2dfbb75d51a645fd7440d45a48d24fd8b15676 Mon Sep 17 00:00:00 2001 From: Emilien <4016501+unixfox@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:57:58 +0200 Subject: [PATCH 08/12] chore: remove debug --- src/invidious/routes/companion.cr | 1 - 1 file changed, 1 deletion(-) diff --git a/src/invidious/routes/companion.cr b/src/invidious/routes/companion.cr index bcfbad6b..11c2e3f5 100644 --- a/src/invidious/routes/companion.cr +++ b/src/invidious/routes/companion.cr @@ -8,7 +8,6 @@ module Invidious::Routes::Companion begin COMPANION_POOL.client do |wrapper| - puts env.request.headers wrapper.client.get(url, env.request.headers) do |resp| return self.proxy_companion(env, resp) end From ba02a4cdf5f266b28c4f6ebe7b58b615330d3ee5 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Mon, 8 Sep 2025 17:16:22 -0300 Subject: [PATCH 09/12] Prevent player microformat from being overwritten by the next microformat (#5453) * Prevent player microformat from being overwritten by the next microformat Closes https://github.com/iv-org/invidious/issues/5443 The player microformat is what we need to get the published date, premiere timestamp, allowed regions and more information of the video. Youtube introduced a new `microformat.microformatDataRenderer` in the next endpoint which overwrote the player microformat `microformat.playerMicroformatRenderer` when merged * Update src/invidious/videos/parser.cr Co-authored-by: syeopite <70992037+syeopite@users.noreply.github.com> --------- Co-authored-by: syeopite <70992037+syeopite@users.noreply.github.com> --- src/invidious/videos/parser.cr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index 5335aa79..6b1dedd6 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -102,6 +102,9 @@ def extract_video_info(video_id : String) # Don't fetch the next endpoint if the video is unavailable. if {"OK", "LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) next_response = YoutubeAPI.next({"videoId": video_id, "params": ""}) + # Remove the microformat returned by the /next endpoint on some videos + # to prevent player_response microformat from being overwritten. + next_response.delete("microformat") player_response = player_response.merge(next_response) end From 9e160d45d33e25f4faeaf4bfcde9a6b450426aba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:45:44 +0000 Subject: [PATCH 10/12] Bump actions/stale from 9 to 10 Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v9...v10) --- updated-dependencies: - dependency-name: actions/stale dependency-version: '10' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 65340d14..ab45ce12 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 730 From 14a629a4e8103aa1483cfc70bf68d31ecebc64a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20=28perso=29?= <4016501+unixfox@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:30:18 +0200 Subject: [PATCH 11/12] Better documentation for the specific case public_url with companion --- config/config.example.yml | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index cabbecfd..2b99345b 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -61,39 +61,32 @@ db: ## When this setting is commented out, Invidious companion is not used. ## Otherwise, Invidious will proxy the requests to Invidious companion. ## -## Note: multiple URL can be configured. In this case, invidious will +## Note: multiple URL can be configured. In this case, Invidious will ## randomly pick one every time video data needs to be retrieved. This ## URL is then kept in the video metadata cache to allow video playback ## to work. Once said cache has expired, requesting that video's data ## again will cause a new companion URL to be picked. ## -## The parameter private_url needs to be configured for the internal -## communication between the companion and Invidious. -## And public_url is the public URL from which companion is listening -## to the requests from the user(s). +## The parameter private_url is required for the internal communication +## between Invidious companion and Invidious. ## -## If you are using a reverse proxy then you will probably need to -## configure the public_url to be the same as the domain used for Invidious. -## Also apply when used from an external IP address (without a domain). -## Examples: https://MYINVIDIOUSDOMAIN/companion or http://192.168.1.100:8282/companion -## -## Both parameter can have identical URL when Invidious is hosted in -## an internal network or at home or locally (localhost). -## -## NOTE: If public_url is omitted, Invidious will use its built-in proxy -## to route companion requests through /companion, which is useful for -## simple setups where companion runs on the same network. When using -## the built-in proxy, CSP headers are not modified since requests -## stay within the same domain. +## The optional parameter public_url is the public URL from which +## Invidious companion is listening to the requests from the user(s). +## When this setting is commented out, Invidious proxy all requests to +## Invidious companion. Useful for simple setups. +## Otherwise, requests from the user(s) will reach Invidious companion directly. +## And you will need to configure a reverse proxy with separate routes +## for Invidious and Invidious companion. +## Read the post-install documentation for advanced reverse proxy +## documentation: https://docs.invidious.io/installation/#post-install-configuration ## ## Accepted values: "http(s)://:" ## Default: ## #invidious_companion: # - private_url: "http://localhost:8282/companion" -# public_url: "http://localhost:8282/companion" -# # Example with built-in proxy (omit public_url): -# # - private_url: "http://localhost:8282/companion" +# # Uncomment for advanced reverse proxy configuration (see above). +# # public_url: "http://localhost:8282/companion" ## ## API key for Invidious companion, used for securing the communication From f9cf70f9d7cf54e2d774a8bcf336dd1a142e71b3 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Thu, 11 Sep 2025 11:05:09 -0300 Subject: [PATCH 12/12] Add default playlist preference (#5449) * Add default playlist preference Closes https://github.com/iv-org/invidious/issues/5421 * Add option to set default playlist to none * Move it to player preferences --- locales/en-US.json | 2 ++ locales/es.json | 2 ++ src/invidious/config.cr | 2 ++ src/invidious/routes/preferences.cr | 3 +++ src/invidious/user/preferences.cr | 1 + src/invidious/views/user/preferences.ecr | 13 +++++++++++++ src/invidious/views/watch.ecr | 2 +- 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/locales/en-US.json b/locales/en-US.json index 3f42a509..fa28e7f8 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -122,6 +122,8 @@ "Redirect homepage to feed: ": "Redirect homepage to feed: ", "preferences_max_results_label": "Number of videos shown in feed: ", "preferences_sort_label": "Sort videos by: ", + "preferences_default_playlist": "Default playlist: ", + "preferences_default_playlist_none": "No default playlist set", "published": "published", "published - reverse": "published - reverse", "alphabetically": "alphabetically", diff --git a/locales/es.json b/locales/es.json index 46217943..686e13f9 100644 --- a/locales/es.json +++ b/locales/es.json @@ -78,6 +78,8 @@ "Redirect homepage to feed: ": "Redirigir la página de inicio a la fuente: ", "preferences_max_results_label": "Número de videos mostrados en la fuente: ", "preferences_sort_label": "Ordenar los videos por: ", + "preferences_default_playlist": "Lista de reproducción por defecto: ", + "preferences_default_playlist_none": "Ninguna lista de reproducción por defecto establecida", "published": "fecha de publicación", "published - reverse": "fecha de publicación: orden inverso", "alphabetically": "alfabéticamente", diff --git a/src/invidious/config.cr b/src/invidious/config.cr index e47e405c..36f09d28 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -52,6 +52,8 @@ struct ConfigPreferences property vr_mode : Bool = true property show_nick : Bool = true property save_player_pos : Bool = false + @[YAML::Field(ignore: true)] + property default_playlist : String? = nil def to_tuple {% begin %} diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index 39ca77c0..9936e523 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -144,6 +144,8 @@ module Invidious::Routes::PreferencesRoute notifications_only ||= "off" notifications_only = notifications_only == "on" + default_playlist = env.params.body["default_playlist"]?.try &.as(String) + # Convert to JSON and back again to take advantage of converters used for compatibility preferences = Preferences.from_json({ annotations: annotations, @@ -180,6 +182,7 @@ module Invidious::Routes::PreferencesRoute vr_mode: vr_mode, show_nick: show_nick, save_player_pos: save_player_pos, + default_playlist: default_playlist, }.to_json) if user = env.get? "user" diff --git a/src/invidious/user/preferences.cr b/src/invidious/user/preferences.cr index 0a8525f3..df195dd6 100644 --- a/src/invidious/user/preferences.cr +++ b/src/invidious/user/preferences.cr @@ -56,6 +56,7 @@ struct Preferences property extend_desc : Bool = CONFIG.default_user_preferences.extend_desc property volume : Int32 = CONFIG.default_user_preferences.volume property save_player_pos : Bool = CONFIG.default_user_preferences.save_player_pos + property default_playlist : String? = nil module BoolToString def self.to_json(value : String, json : JSON::Builder) diff --git a/src/invidious/views/user/preferences.ecr b/src/invidious/views/user/preferences.ecr index cf8b5593..23cb89f6 100644 --- a/src/invidious/views/user/preferences.ecr +++ b/src/invidious/views/user/preferences.ecr @@ -126,6 +126,19 @@ checked<% end %>> + <% if user = env.get?("user").try &.as(User) %> + <% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %> +
+ + +
+ <% end %> + <%= translate(locale, "preferences_category_visual") %>
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 6f9ced6f..fada6361 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -163,7 +163,7 @@ we're going to need to do it here in order to allow for translations.