mirror of
https://github.com/iv-org/invidious
synced 2025-11-06 23:40:07 +01:00
Compare commits
9 Commits
89c8b1b901
...
9e160d45d3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e160d45d3 | ||
|
|
ba02a4cdf5 | ||
|
|
cf2dfbb75d | ||
|
|
21c13bba9d | ||
|
|
5e9d51c06e | ||
|
|
1653dd629e | ||
|
|
cba2adc6ef | ||
|
|
42b955d713 | ||
|
|
324a416fd4 |
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 730
|
days-before-stale: 730
|
||||||
|
|||||||
@ -75,17 +75,25 @@ 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 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
|
## 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"
|
# - private_url: "http://localhost:8282/companion"
|
||||||
# public_url: "http://localhost:8282"
|
# 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
|
## API key for Invidious companion, used for securing the communication
|
||||||
|
|||||||
@ -82,6 +82,9 @@ 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)
|
||||||
@ -271,6 +274,14 @@ 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
|
||||||
|
|||||||
@ -61,28 +61,13 @@ class Kemal::ExceptionHandler
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class FilteredCompressHandler < Kemal::Handler
|
class FilteredCompressHandler < HTTP::CompressHandler
|
||||||
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(env)
|
def call(context)
|
||||||
return call_next env if exclude_match? env
|
return call_next context if exclude_match? context
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
@ -63,6 +63,7 @@ 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"
|
||||||
|
|||||||
43
src/invidious/routes/companion.cr
Normal file
43
src/invidious/routes/companion.cr
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
@ -209,10 +209,17 @@ 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.public_url}")
|
.gsub("media-src", "media-src #{invidious_companion_urls}")
|
||||||
.gsub("connect-src", "connect-src #{invidious_companion.public_url}")
|
.gsub("connect-src", "connect-src #{invidious_companion_urls}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
rendered "embed"
|
rendered "embed"
|
||||||
|
|||||||
@ -194,10 +194,17 @@ 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.public_url}")
|
.gsub("media-src", "media-src #{invidious_companion_urls}")
|
||||||
.gsub("connect-src", "connect-src #{invidious_companion.public_url}")
|
.gsub("connect-src", "connect-src #{invidious_companion_urls}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
templated "watch"
|
templated "watch"
|
||||||
|
|||||||
@ -46,6 +46,7 @@ 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
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
@ -188,7 +189,7 @@ module Invidious::Routing
|
|||||||
end
|
end
|
||||||
|
|
||||||
# -------------------
|
# -------------------
|
||||||
# Media proxy routes
|
# Proxy routes
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|
||||||
def register_api_manifest_routes
|
def register_api_manifest_routes
|
||||||
@ -223,6 +224,13 @@ 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
|
||||||
# -------------------
|
# -------------------
|
||||||
|
|||||||
@ -102,6 +102,9 @@ 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
|
||||||
|
|
||||||
|
|||||||
@ -65,12 +65,18 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% preferred_captions.each do |caption| %>
|
<% preferred_captions.each do |caption|
|
||||||
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
|
api_captions_url = "/api/v1/captions/"
|
||||||
|
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|
|
||||||
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name %>" label="<%= caption.name %>">
|
api_captions_url = "/api/v1/captions/"
|
||||||
|
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>
|
||||||
|
|||||||
@ -46,8 +46,27 @@ 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(HTTP::Client)
|
property pool : DB::Pool(CompanionWrapper)
|
||||||
|
|
||||||
def initialize(capacity = 5, timeout = 5.0)
|
def initialize(capacity = 5, timeout = 5.0)
|
||||||
options = DB::Pool::Options.new(
|
options = DB::Pool::Options.new(
|
||||||
@ -57,26 +76,28 @@ struct CompanionConnectionPool
|
|||||||
checkout_timeout: timeout
|
checkout_timeout: timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
@pool = DB::Pool(HTTP::Client).new(options) do
|
@pool = DB::Pool(CompanionWrapper).new(options) do
|
||||||
companion = CONFIG.invidious_companion.sample
|
companion = CONFIG.invidious_companion.sample
|
||||||
next make_client(companion.private_url, use_http_proxy: false)
|
make_client(companion.private_url, use_http_proxy: false)
|
||||||
|
CompanionWrapper.new(companion: companion)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def client(&)
|
def client(&)
|
||||||
conn = pool.checkout
|
wrapper = pool.checkout
|
||||||
|
|
||||||
begin
|
begin
|
||||||
response = yield conn
|
response = yield wrapper
|
||||||
rescue ex
|
rescue ex
|
||||||
conn.close
|
wrapper.close
|
||||||
|
|
||||||
companion = CONFIG.invidious_companion.sample
|
companion = CONFIG.invidious_companion.sample
|
||||||
conn = 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 conn
|
response = yield wrapper
|
||||||
ensure
|
ensure
|
||||||
pool.release(conn)
|
pool.release(wrapper)
|
||||||
end
|
end
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|||||||
@ -701,22 +701,20 @@ module YoutubeAPI
|
|||||||
# Send the POST request
|
# Send the POST request
|
||||||
|
|
||||||
begin
|
begin
|
||||||
response = COMPANION_POOL.client &.post(endpoint, headers: headers, body: data.to_json)
|
response_body = Hash(String, JSON::Any).new
|
||||||
body = response.body
|
|
||||||
if (response.status_code != 200)
|
COMPANION_POOL.client do |wrapper|
|
||||||
raise Exception.new(
|
companion_base_url = wrapper.companion.private_url.path
|
||||||
"Error while communicating with Invidious companion: \
|
|
||||||
status code: #{response.status_code} and body: #{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
|
||||||
|
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
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user