Compare commits

..

No commits in common. "9e160d45d33e25f4faeaf4bfcde9a6b450426aba" and "89c8b1b901062c729370370116aa9127a39cd214" have entirely different histories.

13 changed files with 57 additions and 155 deletions

View File

@ -10,7 +10,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v10 - uses: actions/stale@v9
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 730 days-before-stale: 730

View File

@ -75,25 +75,17 @@ db:
## If you are using a reverse proxy then you will probably need to ## 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. ## 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). ## Also apply when used from an external IP address (without a domain).
## Examples: https://MYINVIDIOUSDOMAIN/companion or http://192.168.1.100:8282/companion ## Examples: https://MYINVIDIOUSDOMAIN or http://192.168.1.100:8282
## ##
## Both parameter can have identical URL when Invidious is hosted in ## Both parameter can have identical URL when Invidious is hosted in
## an internal network or at home or locally (localhost). ## 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)://<IP-HOSTNAME>:<Port>" ## Accepted values: "http(s)://<IP-HOSTNAME>:<Port>"
## Default: <none> ## Default: <none>
## ##
#invidious_companion: #invidious_companion:
# - private_url: "http://localhost:8282/companion" # - private_url: "http://localhost:8282"
# public_url: "http://localhost:8282/companion" # public_url: "http://localhost:8282"
# # Example with built-in proxy (omit public_url):
# # - private_url: "http://localhost:8282/companion"
## ##
## API key for Invidious companion, used for securing the communication ## API key for Invidious companion, used for securing the communication

View File

@ -82,9 +82,6 @@ class Config
@[YAML::Field(converter: Preferences::URIConverter)] @[YAML::Field(converter: Preferences::URIConverter)]
property public_url : URI = URI.parse("") property public_url : URI = URI.parse("")
# Indicates if this companion instance uses the built-in proxy
property builtin_proxy : Bool = false
end end
# Number of threads to use for crawling videos from channels (for updating subscriptions) # Number of threads to use for crawling videos from channels (for updating subscriptions)
@ -274,14 +271,6 @@ class Config
puts "Config: The value of 'invidious_companion_key' needs to be a size of 16 characters." puts "Config: The value of 'invidious_companion_key' needs to be a size of 16 characters."
exit(1) exit(1)
end 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 elsif config.signature_server
puts("WARNING: inv-sig-helper is deprecated. Please switch to Invidious companion: https://docs.invidious.io/companion-installation/") puts("WARNING: inv-sig-helper is deprecated. Please switch to Invidious companion: https://docs.invidious.io/companion-installation/")
else else

View File

@ -61,13 +61,28 @@ class Kemal::ExceptionHandler
end end
end end
class FilteredCompressHandler < HTTP::CompressHandler class FilteredCompressHandler < Kemal::Handler
exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/sb/*", "/ggpht/*", "/api/v1/auth/notifications"] exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/sb/*", "/ggpht/*", "/api/v1/auth/notifications"]
exclude ["/api/v1/auth/notifications", "/data_control"], "POST" exclude ["/api/v1/auth/notifications", "/data_control"], "POST"
def call(context) def call(env)
return call_next context if exclude_match? context return call_next env if exclude_match? env
super
{% 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 %}
end end
end end

View File

@ -63,7 +63,6 @@ module Invidious::Routes::BeforeAll
"/videoplayback", "/videoplayback",
"/latest_version", "/latest_version",
"/download", "/download",
"/companion/",
}.any? { |r| env.request.resource.starts_with? r } }.any? { |r| env.request.resource.starts_with? r }
if env.request.cookies.has_key? "SID" if env.request.cookies.has_key? "SID"

View File

@ -1,43 +0,0 @@
module Invidious::Routes::Companion
# /companion
def self.get_companion(env)
url = env.request.path
if env.request.query
url += "?#{env.request.query}"
end
begin
COMPANION_POOL.client do |wrapper|
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
if env.request.query
url += "?#{env.request.query}"
end
begin
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
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
return IO.copy response.body_io, env.response
end
end

View File

@ -209,17 +209,10 @@ module Invidious::Routes::Embed
if CONFIG.invidious_companion.present? if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample invidious_companion = CONFIG.invidious_companion.sample
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(" ")
if !invidious_companion_urls.empty?
env.response.headers["Content-Security-Policy"] = env.response.headers["Content-Security-Policy"] =
env.response.headers["Content-Security-Policy"] env.response.headers["Content-Security-Policy"]
.gsub("media-src", "media-src #{invidious_companion_urls}") .gsub("media-src", "media-src #{invidious_companion.public_url}")
.gsub("connect-src", "connect-src #{invidious_companion_urls}") .gsub("connect-src", "connect-src #{invidious_companion.public_url}")
end
end end
rendered "embed" rendered "embed"

View File

@ -194,17 +194,10 @@ module Invidious::Routes::Watch
if CONFIG.invidious_companion.present? if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample invidious_companion = CONFIG.invidious_companion.sample
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(" ")
if !invidious_companion_urls.empty?
env.response.headers["Content-Security-Policy"] = env.response.headers["Content-Security-Policy"] =
env.response.headers["Content-Security-Policy"] env.response.headers["Content-Security-Policy"]
.gsub("media-src", "media-src #{invidious_companion_urls}") .gsub("media-src", "media-src #{invidious_companion.public_url}")
.gsub("connect-src", "connect-src #{invidious_companion_urls}") .gsub("connect-src", "connect-src #{invidious_companion.public_url}")
end
end end
templated "watch" templated "watch"

View File

@ -46,7 +46,6 @@ module Invidious::Routing
self.register_api_v1_routes self.register_api_v1_routes
self.register_api_manifest_routes self.register_api_manifest_routes
self.register_video_playback_routes self.register_video_playback_routes
self.register_companion_routes
end end
# ------------------- # -------------------
@ -189,7 +188,7 @@ module Invidious::Routing
end end
# ------------------- # -------------------
# Proxy routes # Media proxy routes
# ------------------- # -------------------
def register_api_manifest_routes def register_api_manifest_routes
@ -224,13 +223,6 @@ module Invidious::Routing
get "/vi/:id/:name", Routes::Images, :thumbnails get "/vi/:id/:name", Routes::Images, :thumbnails
end 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 # API routes
# ------------------- # -------------------

View File

@ -102,9 +102,6 @@ def extract_video_info(video_id : String)
# Don't fetch the next endpoint if the video is unavailable. # Don't fetch the next endpoint if the video is unavailable.
if {"OK", "LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) if {"OK", "LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status)
next_response = YoutubeAPI.next({"videoId": video_id, "params": ""}) 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) player_response = player_response.merge(next_response)
end end

View File

@ -65,18 +65,12 @@
<% end %> <% end %>
<% end %> <% end %>
<% preferred_captions.each do |caption| <% preferred_captions.each do |caption| %>
api_captions_url = "/api/v1/captions/" <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
api_captions_url = invidious_companion.public_url.to_s + api_captions_url if (invidious_companion)
%>
<track kind="captions" src="<%= api_captions_url %><%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
<% end %> <% end %>
<% captions.each do |caption| <% captions.each do |caption| %>
api_captions_url = "/api/v1/captions/" <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
api_captions_url = invidious_companion.public_url.to_s + api_captions_url if (invidious_companion)
%>
<track kind="captions" src="<%= api_captions_url %><%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
<% end %> <% end %>
<% end %> <% end %>
</video> </video>

View File

@ -46,27 +46,8 @@ struct YoutubeConnectionPool
end end
end 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.
struct CompanionWrapper
property client : HTTP::Client
property companion : Config::CompanionConfig
def initialize(companion : Config::CompanionConfig)
@companion = companion
@client = make_client(companion.private_url, use_http_proxy: false)
end
def close
@client.close
end
end
struct CompanionConnectionPool struct CompanionConnectionPool
property pool : DB::Pool(CompanionWrapper) property pool : DB::Pool(HTTP::Client)
def initialize(capacity = 5, timeout = 5.0) def initialize(capacity = 5, timeout = 5.0)
options = DB::Pool::Options.new( options = DB::Pool::Options.new(
@ -76,28 +57,26 @@ struct CompanionConnectionPool
checkout_timeout: timeout checkout_timeout: timeout
) )
@pool = DB::Pool(CompanionWrapper).new(options) do @pool = DB::Pool(HTTP::Client).new(options) do
companion = CONFIG.invidious_companion.sample companion = CONFIG.invidious_companion.sample
make_client(companion.private_url, use_http_proxy: false) next make_client(companion.private_url, use_http_proxy: false)
CompanionWrapper.new(companion: companion)
end end
end end
def client(&) def client(&)
wrapper = pool.checkout conn = pool.checkout
begin begin
response = yield wrapper response = yield conn
rescue ex rescue ex
wrapper.close conn.close
companion = CONFIG.invidious_companion.sample companion = CONFIG.invidious_companion.sample
make_client(companion.private_url, use_http_proxy: false) conn = make_client(companion.private_url, use_http_proxy: false)
wrapper = CompanionWrapper.new(companion: companion)
response = yield wrapper response = yield conn
ensure ensure
pool.release(wrapper) pool.release(conn)
end end
response response

View File

@ -701,20 +701,22 @@ module YoutubeAPI
# Send the POST request # Send the POST request
begin begin
response_body = Hash(String, JSON::Any).new response = COMPANION_POOL.client &.post(endpoint, headers: headers, body: data.to_json)
body = response.body
COMPANION_POOL.client do |wrapper| if (response.status_code != 200)
companion_base_url = wrapper.companion.private_url.path raise Exception.new(
"Error while communicating with Invidious companion: \
wrapper.client.post("#{companion_base_url}#{endpoint}", headers: headers, body: data.to_json) do |response| status code: #{response.status_code} and body: #{body.dump}"
response_body = JSON.parse(response.body_io).as_h )
end end
end
return response_body
rescue ex rescue ex
raise InfoException.new("Error while communicating with Invidious companion: " + (ex.message || "no extra info found")) raise InfoException.new("Error while communicating with Invidious companion: " + (ex.message || "no extra info found"))
end end
# Convert result to Hash
initial_data = JSON.parse(body).as_h
return initial_data
end end
#################################################################### ####################################################################