feat: do all the backend balancing on the invidious side

This will make invidious easier to maintain and escalate without the need of an overcomplicated reverse proxy configuration and multiple invidious instances with each one with a different configuration (in this case, invidious companion)
This commit is contained in:
Fijxu 2025-03-30 20:08:15 -03:00
parent ddf6802d76
commit d47aa3dd6a
No known key found for this signature in database
GPG Key ID: 32C1DDF333EDA6A4
13 changed files with 341 additions and 265 deletions

View File

@ -1,45 +1,49 @@
module BackendInfo module BackendInfo
extend self extend self
@@exvpp_url : String = "" @@exvpp_url : Array(String) = Array.new(CONFIG.invidious_companion.size, "")
@@status : Int32 = 0 @@status : Array(Int32) = Array.new(CONFIG.invidious_companion.size, 0)
def check_backends def check_backends
check_companion() check_companion()
end end
def check_companion def check_companion
begin CONFIG.invidious_companion.each_with_index do |companion, index|
response = HTTP::Client.get "#{CONFIG.invidious_companion.sample.private_url}/healthz" spawn do
if response.status_code == 200 begin
check_videoplayback_proxy() response = HTTP::Client.get "#{companion.private_url}/healthz"
else if response.status_code == 200
@@status = 0 check_videoplayback_proxy(companion, index)
else
@@status[index] = 0
end
rescue
@@status[index] = 0
end
end end
rescue
@@status = 0
end end
end end
def check_videoplayback_proxy private def check_videoplayback_proxy(companion : Config::CompanionConfig, index : Int32)
begin begin
info = HTTP::Client.get "#{CONFIG.invidious_companion.sample.private_url}/info" info = HTTP::Client.get "#{companion.private_url}/info"
exvpp_url = JSON.parse(info.body)["external_videoplayback_proxy"]?.try &.to_s exvpp_url = JSON.parse(info.body)["external_videoplayback_proxy"]?.try &.to_s
exvpp_url = "" if exvpp_url.nil? exvpp_url = "" if exvpp_url.nil?
@@exvpp_url = exvpp_url @@exvpp_url[index] = exvpp_url
if exvpp_url.empty? if exvpp_url.empty?
@@status = 2 @@status[index] = 2
return return
else else
begin begin
exvpp_health = HTTP::Client.get "#{exvpp_url}/health" exvpp_health = HTTP::Client.get "#{exvpp_url}/health"
if exvpp_health.status_code == 200 if exvpp_health.status_code == 200
@@status = 2 @@status[index] = 2
return return
else else
@@status = 1 @@status[index] = 1
end end
rescue rescue
@@status = 1 @@status[index] = 1
end end
end end
rescue rescue

View File

@ -9,7 +9,8 @@ module Invidious::Routes::API::Manifest
region = env.params.query["region"]? region = env.params.query["region"]?
if CONFIG.invidious_companion.present? if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample current_companion = env.get("current_companion").as(Int32)
invidious_companion = CONFIG.invidious_companion[current_companion]
return env.redirect "#{invidious_companion.public_url}/api/manifest/dash/id/#{id}?#{env.params.query}" return env.redirect "#{invidious_companion.public_url}/api/manifest/dash/id/#{id}?#{env.params.query}"
end end

View File

@ -0,0 +1,16 @@
{% skip_file if flag?(:api_only) %}
module Invidious::Routes::BackendSwitcher
def self.switch(env)
referer = get_referer(env)
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

View File

@ -24,12 +24,33 @@ module Invidious::Routes::BeforeAll
extra_connect_csp = "" extra_connect_csp = ""
if CONFIG.invidious_companion.present? if CONFIG.invidious_companion.present?
extra_media_csp = " #{CONFIG.invidious_companion.sample.public_url}" if env.request.cookies[CONFIG.server_id_cookie_name]?.nil?
extra_connect_csp = " #{CONFIG.invidious_companion.sample.public_url}" env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(env.request.headers["Host"])
exvpp_url = BackendInfo.get_exvpp end
if !exvpp_url.empty?
extra_media_csp += " #{exvpp_url}" begin
extra_connect_csp += " #{exvpp_url}" current_companion = env.request.cookies[CONFIG.server_id_cookie_name].value.try &.to_i
rescue
current_companion = rand(CONFIG.invidious_companion.size)
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(env.request.headers["Host"], current_companion)
end
env.set "current_companion", current_companion
CONFIG.invidious_companion.each do |companion|
extra_media_csp += " #{companion.public_url}"
extra_connect_csp += " #{companion.public_url}"
end
exvpp_urls = BackendInfo.get_exvpp
exvpp_urls.each do |exvpp_url|
if !exvpp_url.empty?
extra_media_csp += " #{exvpp_url}"
extra_connect_csp += " #{exvpp_url}"
end
end end
end end

View File

@ -267,7 +267,8 @@ module Invidious::Routes::VideoPlayback
# so we have a mechanism here to redirect to the latest version # so we have a mechanism here to redirect to the latest version
def self.latest_version(env) def self.latest_version(env)
if CONFIG.invidious_companion.present? if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample current_companion = env.get("current_companion").as(Int32)
invidious_companion = CONFIG.invidious_companion[current_companion]
return env.redirect "#{invidious_companion.public_url}/latest_version?#{env.params.query}" return env.redirect "#{invidious_companion.public_url}/latest_version?#{env.params.query}"
end end

View File

@ -52,7 +52,7 @@ module Invidious::Routes::Watch
env.params.query.delete_all("listen") env.params.query.delete_all("listen")
begin begin
video = get_video(id, region: params.region) video = get_video(id, region: params.region, env: env)
rescue ex : NotFoundException rescue ex : NotFoundException
LOGGER.error("get_video not found: #{id} : #{ex.message}") LOGGER.error("get_video not found: #{id} : #{ex.message}")
return error_template(404, ex) return error_template(404, ex)
@ -214,7 +214,8 @@ module Invidious::Routes::Watch
end end
if CONFIG.invidious_companion.present? if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample current_companion = env.get("current_companion").as(Int32)
invidious_companion = CONFIG.invidious_companion[current_companion]
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.public_url}")
@ -350,8 +351,9 @@ module Invidious::Routes::Watch
env.params.query["local"] = "true" env.params.query["local"] = "true"
if (CONFIG.invidious_companion.present?) if (CONFIG.invidious_companion.present?)
video = get_video(video_id) video = get_video(video_id, env: env)
invidious_companion = CONFIG.invidious_companion.sample current_companion = env.get("current_companion").as(Int32)
invidious_companion = CONFIG.invidious_companion[current_companion]
return env.redirect "#{invidious_companion.public_url}/latest_version?#{env.params.query}" return env.redirect "#{invidious_companion.public_url}/latest_version?#{env.params.query}"
else else
return Invidious::Routes::VideoPlayback.latest_version(env) return Invidious::Routes::VideoPlayback.latest_version(env)

View File

@ -21,6 +21,7 @@ module Invidious::Routing
get "/privacy", Routes::Misc, :privacy get "/privacy", Routes::Misc, :privacy
get "/licenses", Routes::Misc, :licenses get "/licenses", Routes::Misc, :licenses
get "/redirect", Routes::Misc, :cross_instance_redirect get "/redirect", Routes::Misc, :cross_instance_redirect
get "/switchbackend", Routes::BackendSwitcher, :switch
self.register_channel_routes self.register_channel_routes
self.register_watch_routes self.register_watch_routes

View File

@ -45,5 +45,29 @@ struct Invidious::User
samesite: HTTP::Cookie::SameSite::Lax samesite: HTTP::Cookie::SameSite::Lax
) )
end 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
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
end end

View File

@ -298,7 +298,7 @@ struct Video
predicate_bool upcoming, isUpcoming predicate_bool upcoming, isUpcoming
end 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 (video = Invidious::Database::Videos.select(id)) && !region
# If record was last updated over 10 minutes ago, or video has since premiered, # If record was last updated over 10 minutes ago, or video has since premiered,
# refresh (expire param in response lasts for 6 hours) # refresh (expire param in response lasts for 6 hours)
@ -308,7 +308,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
force_refresh || force_refresh ||
video.schema_version != Video::SCHEMA_VERSION # cache control video.schema_version != Video::SCHEMA_VERSION # cache control
begin begin
video = fetch_video(id, region) video = fetch_video(id, region, env)
Invidious::Database::Videos.insert(video) Invidious::Database::Videos.insert(video)
rescue ex rescue ex
Invidious::Database::Videos.delete(id) Invidious::Database::Videos.delete(id)
@ -316,7 +316,7 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
end end
end end
else else
video = fetch_video(id, region) video = fetch_video(id, region, env)
Invidious::Database::Videos.insert(video) if !region Invidious::Database::Videos.insert(video) if !region
end end
@ -324,11 +324,11 @@ def get_video(id, refresh = true, region = nil, force_refresh = false)
rescue DB::Error rescue DB::Error
# Avoid common `DB::PoolRetryAttemptsExceeded` error and friends # Avoid common `DB::PoolRetryAttemptsExceeded` error and friends
# Note: All DB errors inherit from `DB::Error` # Note: All DB errors inherit from `DB::Error`
return fetch_video(id, region) return fetch_video(id, region, env)
end end
def fetch_video(id, region) def fetch_video(id, region, env)
info = extract_video_info(video_id: id) info = extract_video_info(video_id: id, env: env)
if reason = info["reason"]? if reason = info["reason"]?
if reason == "Video unavailable" if reason == "Video unavailable"

View File

@ -58,12 +58,12 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
} }
end 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 # Init client config for the API
client_config = YoutubeAPI::ClientConfig.new client_config = YoutubeAPI::ClientConfig.new
# Fetch data from the player endpoint # Fetch data from the player endpoint
player_response = YoutubeAPI.player(video_id: video_id, params: "2AMB", client_config: client_config) player_response = YoutubeAPI.player(env: env, video_id: video_id, params: "2AMB", client_config: client_config)
playability_status = player_response.dig?("playabilityStatus", "status").try &.as_s playability_status = player_response.dig?("playabilityStatus", "status").try &.as_s
@ -119,7 +119,7 @@ def extract_video_info(video_id : String)
# following issue for an explanation about decrypted URLs: # following issue for an explanation about decrypted URLs:
# https://github.com/TeamNewPipe/NewPipeExtractor/issues/562 # https://github.com/TeamNewPipe/NewPipeExtractor/issues/562
client_config.client_type = YoutubeAPI::ClientType::AndroidTestSuite client_config.client_type = YoutubeAPI::ClientType::AndroidTestSuite
new_player_response = try_fetch_streaming_data(video_id, client_config) new_player_response = try_fetch_streaming_data(video_id, client_config, env)
end end
# Replace player response and reset reason # Replace player response and reset reason
@ -154,9 +154,9 @@ def extract_video_info(video_id : String)
return params return params
end 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.") 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"] playability_status = response["playabilityStatus"]["status"]
LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.") LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.")

View File

@ -1,9 +1,7 @@
<% <%
locale = env.get("preferences").as(Preferences).locale locale = env.get("preferences").as(Preferences).locale
dark_mode = env.get("preferences").as(Preferences).dark_mode dark_mode = env.get("preferences").as(Preferences).dark_mode
current_backend = env.request.cookies[CONFIG.server_id_cookie_name]?.try &.value || env.request.headers["Host"]
current_external_videoplayback_proxy = Invidious::HttpServer::Utils.get_external_proxy() current_external_videoplayback_proxy = Invidious::HttpServer::Utils.get_external_proxy()
status = BackendInfo.get_status
%> %>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<%= locale %>"> <html lang="<%= locale %>">
@ -37,15 +35,6 @@
<div class="pure-u-1 pure-u-md-4-24"> <div class="pure-u-1 pure-u-md-4-24">
<a href="/" class="index-link pure-menu-heading"> <a href="/" class="index-link pure-menu-heading">
Invidious Invidious
<% if status == 0 %>
<span style="color: #fd4848;">•</span>
<% end %>
<% if status == 1 %>
<span style="color: #d06925;">•</span>
<% end %>
<% if status == 2 %>
<span style="color: #42ae3c;">•</span>
<% end %>
</a> </a>
</div> </div>
<div class="pure-u-1 pure-u-md-12-24 searchbar"> <div class="pure-u-1 pure-u-md-12-24 searchbar">
@ -118,31 +107,35 @@
</div> </div>
</div> </div>
<% if !CONFIG.backends.empty? %> <%
<% if !CONFIG.backend_domains.includes?(env.request.headers["Host"]) %> if CONFIG.invidious_companion.present?
current_backend = env.get("current_companion").as(Int32)
status = BackendInfo.get_status
%>
<div class="h-box" style="margin-bottom: 10px;"> <div class="h-box" style="margin-bottom: 10px;">
<b>Switch Backend:</b> <b>Switch Backend:</b>
<% CONFIG.backends.each do | backend | %> <% CONFIG.invidious_companion.each_with_index do | backend, index | %>
<% backend = backend.split(CONFIG.backends_delimiter) %> <% if current_backend == index %>
<% if current_backend == backend[0] %> <a href="/switchbackend?backend_id=<%= index.to_s %>" style="text-decoration-line: underline; display: inline-block;">
<a href="/switchbackend?backend_id=<%= backend[0] %>" style="text-decoration-line: underline; display: inline-block;"> Backend<%= HTML.escape((index+1).to_s) %>
Backend<%= HTML.escape(backend[0]) %> <span style="color:
<% if backend.size == 2 %> <% if status[index] == 0 %> #fd4848; <% end %>
<%= HTML.escape(backend[1]) %> <% if status[index] == 1 %> #d06925; <% end %>
<% end %> <% if status[index] == 2 %> #42ae3c; <% end %>
">•</span>
<% else %>
<a href="/switchbackend?backend_id=<%= index.to_s %>" style="display: inline-block;">
Backend<%= HTML.escape((index+1).to_s) %>
<span style="color:
<% if status[index] == 0 %> #fd4848; <% end %>
<% if status[index] == 1 %> #d06925; <% end %>
<% if status[index] == 2 %> #42ae3c; <% end %>
">•</span>
<% end %>
</a> <span> | </span> </a> <span> | </span>
<% else %> <% end %>
<a href="/switchbackend?backend_id=<%= backend[0] %>" style="display: inline-block;">
Backend<%= HTML.escape(backend[0]) %>
<% if backend.size == 2 %>
<%= HTML.escape(backend[1]) %>
<% end %>
</a> <span> | </span>
<% end %>
<% end %>
</div> </div>
<% end %> <% end %>
<% end %>
<% if CONFIG.banner %> <% if CONFIG.banner %>
<div class="h-box"> <div class="h-box">
@ -152,202 +145,207 @@
<%= content %> <%= content %>
<% if buffer_footer %> <% if buffer_footer %>
<div id="footer_buffer"></div> <div id="footer_buffer"></div>
<% end %> <% end %>
</div> </div>
</div> </div>
<script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script>
<script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script>
<% if env.get? "user" %> <% if env.get? "user" %>
<script src="/js/sse.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/sse.js?v=<%= ASSET_COMMIT %>"></script>
<script id="notification_data" type="application/json"> <script id="notification_data" type="application/json">
<%= <%=
{ {
"upload_text" => HTML.escape(translate(locale, "`x` uploaded a video")), "upload_text" => HTML.escape(translate(locale, "`x` uploaded a video")),
"live_upload_text" => HTML.escape(translate(locale, "`x` is live")) "live_upload_text" => HTML.escape(translate(locale, "`x` is live"))
}.to_pretty_json }.to_pretty_json
%> %>
</script> </script>
<% if CONFIG.enable_user_notifications %> <% if CONFIG.enable_user_notifications %>
<script src="/js/notifications.js?v=<%= ASSET_COMMIT %>"></script> <script src="/js/notifications.js?v=<%= ASSET_COMMIT %>"></script>
<% end %> <% end %>
<% end %> <% end %>
<footer class="pure-g"> <footer class="pure-g">
<div class="pure-u-1 pure-u-md-2-24"></div> <div class="pure-u-1 pure-u-md-2-24"></div>
<div class="h-box pure-u-1 pure-u-md-20-24" id="footer-content-container"> <div class="h-box pure-u-1 pure-u-md-20-24" id="footer-content-container">
<div class="pure-u-1 footer-content"> <div class="pure-u-1 footer-content">
<div class="footer-section pure-u-1-4" id="footer-custom-text"> <div class="footer-section pure-u-1-4" id="footer-custom-text">
<b>Invidious</b> <b>Invidious</b>
<% if CONFIG.footer %> <% if CONFIG.footer %>
<p><%=CONFIG.footer%></p> <p><%=CONFIG.footer%></p>
<% else %> <% else %>
<p><%=translate(locale, "default_invidious_footer_text")%></p> <p><%=translate(locale, "default_invidious_footer_text")%></p>
<% end %> <% end %>
</div> </div>
<div class="footer-section"> <div class="footer-section">
<b class="footer-section-header"><%= translate(locale, "footer_navigation_section_header")%></b> <b class="footer-section-header"><%= translate(locale, "footer_navigation_section_header")%></b>
<ul class="pure-menu-list footer-section-list"> <ul class="pure-menu-list footer-section-list">
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="/" title="<%= translate(locale, "footer_home_link")%>"> <a href="/" title="<%= translate(locale, "footer_home_link")%>">
<%= translate(locale, "footer_home_link") %> <%= translate(locale, "footer_home_link") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="/feed/popular" title="<%= translate(locale, "Popular")%>"> <a href="/feed/popular" title="<%= translate(locale, "Popular")%>">
<%= translate(locale, "Popular") %> <%= translate(locale, "Popular") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="/feed/trending" title="<%= translate(locale, "Trending")%>" style=""> <a href="/feed/trending" title="<%= translate(locale, "Trending")%>" style="">
<%= translate(locale, "Trending") %> <%= translate(locale, "Trending") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="/search" title="<%= translate(locale, "Search")%>"> <a href="/search" title="<%= translate(locale, "Search")%>">
<%= translate(locale, "Search") %> <%= translate(locale, "Search") %>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="footer-section"> <div class="footer-section">
<b class="footer-section-header">Invidious</b> <b class="footer-section-header">Invidious</b>
<ul class="pure-menu-list footer-section-list"> <ul class="pure-menu-list footer-section-list">
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="https://invidious.io" title="<%= translate(locale, "footer_project_homepage_link")%>"> <a href="https://invidious.io" title="<%= translate(locale, "footer_project_homepage_link")%>">
<%= translate(locale, "footer_project_homepage_link") %> <%= translate(locale, "footer_project_homepage_link") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="https://github.com/iv-org/invidious" title="<%= translate(locale, "footer_source_code_link")%>"> <a href="https://github.com/iv-org/invidious" title="<%= translate(locale, "footer_source_code_link")%>">
<%= translate(locale, "footer_source_code_link") %> <%= translate(locale, "footer_source_code_link") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="https://github.com/iv-org/invidious/issues" title="<%= translate(locale, "footer_issue_tracker_link")%>" style=""> <a href="https://github.com/iv-org/invidious/issues" title="<%= translate(locale, "footer_issue_tracker_link")%>" style="">
<%= translate(locale, "footer_issue_tracker_link") %> <%= translate(locale, "footer_issue_tracker_link") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="https://instances.invidious.io" title="<%= translate(locale, "footer_public_instances_link")%>"> <a href="https://instances.invidious.io" title="<%= translate(locale, "footer_public_instances_link")%>">
<%= translate(locale, "footer_public_instances_link") %> <%= translate(locale, "footer_public_instances_link") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="https://invidious.io/donate" title="<%= translate(locale, "footer_donate_link")%>"> <a href="https://invidious.io/donate" title="<%= translate(locale, "footer_donate_link")%>">
<%= translate(locale, "footer_donate_link") %> <%= translate(locale, "footer_donate_link") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="https://matrix.to/#/#invidious:matrix.org" title="<%= translate(locale, "footer_matrix_link")%>"> <a href="https://matrix.to/#/#invidious:matrix.org" title="<%= translate(locale, "footer_matrix_link")%>">
<%= translate(locale, "footer_matrix_link") %> <%= translate(locale, "footer_matrix_link") %>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<% if CONFIG.instance_maintainer_email || CONFIG.modified_source_code_url || CONFIG.footer_instance_tos_link || CONFIG.footer_instance_privacy_policy_link %> <% if CONFIG.instance_maintainer_email || CONFIG.modified_source_code_url || CONFIG.footer_instance_tos_link || CONFIG.footer_instance_privacy_policy_link %>
<div class="footer-section"> <div class="footer-section">
<b class="footer-section-header"> <b class="footer-section-header">
<% if CONFIG.modified_source_code_url %> <% if CONFIG.modified_source_code_url %>
<%= translate(locale, "footer_instance_section_header_modified_source")%> <%= translate(locale, "footer_instance_section_header_modified_source")%>
<% else %> <% else %>
<%= translate(locale, "footer_instance_section_header")%> <%= translate(locale, "footer_instance_section_header")%>
<% end %> <% end %>
</b> </b>
<ul class="pure-menu-list footer-section-list"> <ul class="pure-menu-list footer-section-list">
<% if CONFIG.instance_maintainer_email %> <% if CONFIG.instance_maintainer_email %>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="<%=HTML.escape("mailto:#{CONFIG.instance_maintainer_email.not_nil!}")%>" title="<%= translate(locale, "footer_contact_link")%>"> <a href="<%=HTML.escape("mailto:#{CONFIG.instance_maintainer_email.not_nil!}")%>" title="<%= translate(locale, "footer_contact_link")%>">
<%= translate(locale, "footer_contact_link") %> <%= translate(locale, "footer_contact_link") %>
</a> </a>
</li> </li>
<% end %> <% end %>
<% if CONFIG.modified_source_code_url %> <% if CONFIG.modified_source_code_url %>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="<%=HTML.escape(CONFIG.modified_source_code_url.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_modified_source_code")%>"> <a href="<%=HTML.escape(CONFIG.modified_source_code_url.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_modified_source_code")%>">
<%= translate(locale, "footer_instance_section_modified_source_code") %> <%= translate(locale, "footer_instance_section_modified_source_code") %>
</a> </a>
</li> </li>
<% end %> <% end %>
<% if CONFIG.footer_instance_tos_link %> <% if CONFIG.footer_instance_tos_link %>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="<%=HTML.escape(CONFIG.footer_instance_tos_link.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_tos")%>"> <a href="<%=HTML.escape(CONFIG.footer_instance_tos_link.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_tos")%>">
<%= translate(locale, "footer_instance_section_tos") %> <%= translate(locale, "footer_instance_section_tos") %>
</a> </a>
</li> </li>
<% end %> <% end %>
<% if CONFIG.footer_instance_privacy_policy_link %> <% if CONFIG.footer_instance_privacy_policy_link %>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="<%=HTML.escape(CONFIG.footer_instance_privacy_policy_link.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_privacy_policy")%>"> <a href="<%=HTML.escape(CONFIG.footer_instance_privacy_policy_link.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_privacy_policy")%>">
<%= translate(locale, "footer_instance_section_privacy_policy") %> <%= translate(locale, "footer_instance_section_privacy_policy") %>
</a> </a>
</li> </li>
<% end %> <% end %>
<% if CONFIG.footer_instance_donate_link %> <% if CONFIG.footer_instance_donate_link %>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="<%=HTML.escape(CONFIG.footer_instance_donate_link.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_donate")%>"> <a href="<%=HTML.escape(CONFIG.footer_instance_donate_link.not_nil!)%>" title="<%= translate(locale, "footer_instance_section_donate")%>">
<%= translate(locale, "footer_instance_section_donate") %> <%= translate(locale, "footer_instance_section_donate") %>
</a> </a>
</li> </li>
<% end %> <% end %>
<% CONFIG.footer_instance_section_custom_fields.each do | field | %> <% CONFIG.footer_instance_section_custom_fields.each do | field | %>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="<%=HTML.escape(field[1])%>" title="<%= HTML.escape(field[0]) %>"> <a href="<%=HTML.escape(field[1])%>" title="<%= HTML.escape(field[0]) %>">
<%= HTML.escape(field[0]) %> <%= HTML.escape(field[0]) %>
</a> </a>
</li> </li>
<% end %> <% end %>
</ul> </ul>
</div> </div>
<% end %> <% end %>
<div class="footer-section"> <div class="footer-section">
<b class="footer-section-header"><%= translate(locale, "footer_support_section_header")%></b> <b class="footer-section-header"><%= translate(locale, "footer_support_section_header")%></b>
<ul class="pure-menu-list footer-section-list"> <ul class="pure-menu-list footer-section-list">
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="https://github.com/iv-org/invidious/issues/new" title="<%= translate(locale, "footer_report_bug_link")%>"> <a href="https://github.com/iv-org/invidious/issues/new" title="<%= translate(locale, "footer_report_bug_link")%>">
<%= translate(locale, "footer_report_bug_link") %> <%= translate(locale, "footer_report_bug_link") %>
</a> </a>
</li> </li>
<li class="pure-menu-item footer-section-item"> <li class="pure-menu-item footer-section-item">
<a href="#" title="<%= translate(locale, "footer_faq_link")%>" style=""> <a href="#" title="<%= translate(locale, "footer_faq_link")%>" style="">
<%= translate(locale, "footer_faq_link") %> <%= translate(locale, "footer_faq_link") %>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<hr/> <hr/>
<div class="footer-footer"> <div class="footer-footer">
<div class="box">You are currently using Backend: <%= current_backend %></div> <%
<% if !current_external_videoplayback_proxy.empty? %> if CONFIG.invidious_companion.present?
<div class="box">External Videoplayback Proxy: <%= current_external_videoplayback_proxy %></div> current_backend = env.get("current_companion").as(Int32)
<% end %> %>
<span class="left"> <div class="box">You are currently using Backend: <%= CONFIG.invidious_companion[current_backend].public_url %></div>
<% if CONFIG.modified_source_code_url %> <% end %>
<%= translate(locale, "footer_current_version_modified") %> <% if !current_external_videoplayback_proxy.empty? %>
<% else %> <div class="box">External Videoplayback Proxy: <%= current_external_videoplayback_proxy %></div>
<%= translate(locale, "Current version: ") %> <% end %>
<% end %> <span class="left">
<% if CONFIG.modified_source_code_url %>
<%= translate(locale, "footer_current_version_modified") %>
<% else %>
<%= translate(locale, "Current version: ") %>
<% end %>
<%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %>
</span> </span>
<div class="right"> <div class="right">
<a href="/privacy" title="<%= translate(locale, "footer_privacy_policy_link")%>"><%= translate(locale, "footer_privacy_policy_link") %></a> <a href="/privacy" title="<%= translate(locale, "footer_privacy_policy_link")%>"><%= translate(locale, "footer_privacy_policy_link") %></a>
<span> | </span> <span> | </span>
<a href="/licenses" title="<%= translate(locale, "footer_licences_link")%>"><%= translate(locale, "footer_licences_link") %></a> <a href="/licenses" title="<%= translate(locale, "footer_licences_link")%>"><%= translate(locale, "footer_licences_link") %></a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="pure-u-1 pure-u-md-2-24"></div> <div class="pure-u-1 pure-u-md-2-24"></div>
</footer> </footer>
</body> </body>
</html> </html>

View File

@ -63,7 +63,7 @@ struct CompanionConnectionPool
end end
end end
def client(&) def client(env : HTTP::Server::Context | Nil, &)
conn = pool.checkout conn = pool.checkout
begin begin
@ -71,7 +71,13 @@ struct CompanionConnectionPool
rescue ex rescue ex
conn.close conn.close
companion = CONFIG.invidious_companion.sample if env.nil?
companion = CONFIG.invidious_companion.sample
else
current_companion = env.get("current_companion").as(Int32)
companion = CONFIG.invidious_companion[current_companion]
end
conn = make_client(companion.private_url, use_http_proxy: false) conn = make_client(companion.private_url, use_http_proxy: false)
response = yield conn response = yield conn

View File

@ -456,6 +456,7 @@ module YoutubeAPI
*, # Force the following parameters to be passed by name *, # Force the following parameters to be passed by name
params : String, params : String,
client_config : ClientConfig | Nil = nil, client_config : ClientConfig | Nil = nil,
env : HTTP::Server::Context | Nil = nil,
) )
# Playback context, separate because it can be different between clients # Playback context, separate because it can be different between clients
playback_ctx = { playback_ctx = {
@ -492,7 +493,7 @@ module YoutubeAPI
end end
if CONFIG.invidious_companion.present? 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 else
return self._post_json("/youtubei/v1/player", data, client_config) return self._post_json("/youtubei/v1/player", data, client_config)
end end
@ -673,6 +674,7 @@ module YoutubeAPI
def _post_invidious_companion( def _post_invidious_companion(
endpoint : String, endpoint : String,
data : Hash, data : Hash,
env : HTTP::Server::Context | Nil,
) : Hash(String, JSON::Any) ) : Hash(String, JSON::Any)
headers = HTTP::Headers{ headers = HTTP::Headers{
"Content-Type" => "application/json; charset=UTF-8", "Content-Type" => "application/json; charset=UTF-8",
@ -686,7 +688,7 @@ 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 = COMPANION_POOL.client(env, &.post(endpoint, headers: headers, body: data.to_json))
body = response.body body = response.body
if (response.status_code != 200) if (response.status_code != 200)
raise Exception.new( raise Exception.new(