Rework companion switcher

This commit is contained in:
Fijxu
2026-01-24 19:47:37 -03:00
parent bc5d8d0b9b
commit 47237d21db
13 changed files with 423 additions and 263 deletions

View File

@@ -202,10 +202,16 @@ Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new
Invidious::Jobs.register Invidious::Jobs::InstanceListRefreshJob.new
if CONFIG.invidious_companion.present?
Invidious::Jobs.register Invidious::Jobs::CheckBackend.new
COMPANION_STATUS = begin
CompanionStatus.new if CONFIG.invidious_companion.present?
rescue
nil
end
if companion_status = COMPANION_STATUS
Invidious::Jobs.register Invidious::Jobs::CompanionChecker.new(companion_status)
else
LOGGER.info("jobs: Disabling CheckBackend job. invidious-companion and their respective external video playback proxies (if set on invidious-companion) will not be checked")
LOGGER.info("jobs: Disabling CompanionChecker job. invidious-companion and their respective external video playback proxies (if set on invidious-companion) will not be checked")
end
Invidious::Jobs.start_all

View File

@@ -209,7 +209,7 @@ class Config
property invidious_companion_key : String = ""
# Invidious companion prefix for numbered domains
property invidious_companion_prefix : String = ""
property invidious_companion_prefix : String? = nil
# Saved cookies in "name1=value1; name2=value2..." format
@[YAML::Field(converter: Preferences::StringToCookies)]

View File

@@ -1,110 +0,0 @@
module BackendInfo
extend self
enum Status
Dead = 0
Blocked = 1
Working = 2
end
struct CompanionData
include JSON::Serializable
property blocked : Bool = false
@[JSON::Field(key: "blockedCount")]
property blocked_count : Int64 = 0
end
@@status : Array(Int32) = Array.new(CONFIG.invidious_companion.size, Status::Dead.to_i)
@@csp : Array(String) = Array.new(CONFIG.invidious_companion.size, "")
@@working_ends : Array(Int32) = Array(Int32).new(0)
@@csp_mutex : Mutex = Mutex.new
@@check_mutex : Mutex = Mutex.new
def check_backends
check_companion()
LOGGER.debug("Invidious companion: New working_ends \"#{@@working_ends}\"")
LOGGER.debug("Invidious companion: New status \"#{@@status}\"")
end
private def check_companion
# Create Channels the size of CONFIG.invidious_companion
comp_size = CONFIG.invidious_companion.size
channels = Channel(Nil).new(comp_size)
updated_ends = Array(Int32).new(0)
updated_status = Array(Int32).new(CONFIG.invidious_companion.size, 0)
LOGGER.debug("Invidious companion: comp_size \"#{comp_size}\"")
CONFIG.invidious_companion.each_with_index do |companion, index|
spawn do
begin
client = HTTP::Client.new(companion.private_url)
client.connect_timeout = 10.seconds
response = client.get(CONFIG.check_backends_path)
if response.status_code == 200
if response.content_type == "application/json"
body = response.body
status_json = CompanionData.from_json(body)
if status_json.blocked
updated_ends, updated_status = self.set_status(index, updated_ends, updated_status, Status::Blocked, true)
else
updated_ends, updated_status = self.set_status(index, updated_ends, updated_status, Status::Working, true)
end
else
updated_ends, updated_status = self.set_status(index, updated_ends, updated_status, Status::Working, true)
end
self.generate_csp([companion.public_url, companion.i2p_public_url], index)
else
_, updated_status = self.set_status(index, updated_ends, updated_status, Status::Dead, false)
end
rescue
_, updated_status = self.set_status(index, updated_ends, updated_status, Status::Dead, false)
ensure
LOGGER.trace("Invidious companion: Done Index: \"#{index}\"")
channels.send(nil)
end
end
end
# Wait until we receive a signal from them all
LOGGER.debug("Invidious companion: Updating working_ends")
comp_size.times { channels.receive }
@@working_ends = updated_ends.sort!
@@status = updated_status
end
private def generate_csp(companion_url : Array(URI), index : Int32? = nil)
@@csp_mutex.synchronize do
@@csp[index] = ""
companion_url.each do |url|
fixed_url = "#{url.scheme}://#{url.host}#{url.port ? ":#{url.port}" : ""}"
@@csp[index] += " #{fixed_url}"
end
end
end
private def set_status(index, updated_ends, updated_status, status, push = false)
@@check_mutex.synchronize do
updated_status[index] = status.to_i
updated_ends.push(index) if push
end
return {updated_ends, updated_status}
end
def get_status
# Shouldn't need to lock since we never edit this array, only change the pointer.
return @@status
end
def get_working_ends
# Shouldn't need to lock since we never edit this array, only change the pointer.
return @@working_ends
end
def get_csp(index : Int32)
# A little mutex to prevent sending a partial CSP header
# Not sure if this is necessary. But if the @@csp[index] is being assigned
# at the same time when it's being accessed, a data race will appear
@@csp_mutex.synchronize do
return @@csp[index], @@csp[index]
end
end
end

View File

@@ -0,0 +1,138 @@
require "wait_group"
class CompanionStatus
enum Status
# Color in the backend switcher: Red
Down = 0
# Color in the backend switcher: Yellow
Blocked = 1
# Color in the backend switcher: Green
Working = 2
end
struct CompanionHealthData
include JSON::Serializable
property blocked : Bool = false
@[JSON::Field(key: "blockedCount")]
property blocked_count : Int64 = 0
end
class CompanionInfo
property companion : Config::CompanionConfig
property status : Status
property csp : String
def initialize(companion)
@companion = companion
@status = Status::Down
@csp = ""
end
end
class WorkingCompanions
property all : Array(Int32)
property community : Array(Int32)
def initialize
@all = Array(Int32).new
@community = Array(Int32).new
end
end
getter companions : Array(CompanionInfo)
getter working_companions : WorkingCompanions
# Reusable TLS Context for HTTP Client
# https://github.com/crystal-lang/crystal/issues/15419
@tlscontext : OpenSSL::SSL::Context::Client
def initialize
@companions = Array(CompanionInfo).new(CONFIG.invidious_companion.size) do |index|
CompanionInfo.new(CONFIG.invidious_companion[index])
end
@working_companions = WorkingCompanions.new
@tlscontext = OpenSSL::SSL::Context::Client.new
end
def check_companions
wg = WaitGroup.new(@companions.size)
@companions.each_with_index do |companion, index|
c = companion.companion
spawn do
begin
self.healthcheck(c, index)
if @companions[index].status == Status::Working
LOGGER.trace("Companion checker: generating CSP for #{c.private_url}")
self.generate_csp(
[c.public_url,
c.i2p_public_url], index)
end
rescue
@companions[index].status == Status::Down
ensure
wg.done
end
end
end
wg.wait
self.generate_working_companions
end
private def generate_csp(companion_urls : Array(URI), index : Int32)
local_csp = ""
companion_urls.each do |url|
host = url.host
next if !host.presence
scheme = url.scheme
port = url.port ? ":#{url.port}" : ""
local_csp += "#{scheme}://#{host}#{port} "
end
@companions[index].csp = local_csp
end
private def generate_working_companions
# Aux variable to temporarily store the alive companions
# If we were to empty the `@working_companions`, some requests in the
# timespan of the `@info` iteration to find the working companions could be
# displayed as there was not working companions
local_working_companions = WorkingCompanions.new
@companions.each_with_index do |companion, index|
if companion.status == Status::Working
local_working_companions.community << index
if !companion.companion.community
local_working_companions.all << index
end
end
end
@working_companions = local_working_companions
end
private def healthcheck(companion : Config::CompanionConfig, index : Int32)
client = HTTP::Client.new(companion.private_url, tls: @tlscontext)
client.connect_timeout = 10.seconds
response = client.get(CONFIG.check_backends_path)
if response.status_code == 200
if response.content_type == "application/json"
body = response.body
status_json = CompanionHealthData.from_json(body)
if status_json.blocked
@companions[index].status = Status::Blocked
else
@companions[index].status = Status::Working
end
else
@companions[index].status = Status::Working
end
else
@companions[index].status = Status::Down
end
end
end

View File

@@ -1,14 +0,0 @@
class Invidious::Jobs::CheckBackend < Invidious::Jobs::BaseJob
def initialize
end
def begin
loop do
LOGGER.info("Backend Checker: Starting")
BackendInfo.check_backends
LOGGER.info("Backend Checker: Done, sleeping for #{CONFIG.check_backends_interval} seconds")
sleep CONFIG.check_backends_interval.seconds
Fiber.yield
end
end
end

View File

@@ -0,0 +1,17 @@
class Invidious::Jobs::CompanionChecker < Invidious::Jobs::BaseJob
@companion_status : CompanionStatus
def initialize(companion_status)
@companion_status = companion_status
end
def begin
loop do
LOGGER.info("Companion checker: Starting")
@companion_status.check_companions
LOGGER.info("Companion checker: Done, sleeping for #{CONFIG.check_backends_interval} seconds")
sleep CONFIG.check_backends_interval.seconds
Fiber.yield
end
end
end

View File

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

View File

@@ -1,12 +1,7 @@
module Invidious::Routes::BeforeAll
private COMPANION_PREFIXES = [] of String
extend self
CONFIG.invidious_companion.each_with_index do |_, i|
prefix = CONFIG.invidious_companion_prefix + "#{i + 1}"
COMPANION_PREFIXES << prefix
end
def self.handle(env)
def handle(env)
preferences = Preferences.from_json("{}")
host = env.request.headers["Host"]
@@ -28,75 +23,6 @@ module Invidious::Routes::BeforeAll
env.response.headers["X-XSS-Protection"] = "1; mode=block"
env.response.headers["X-Content-Type-Options"] = "nosniff"
extra_media_csp = ""
extra_connect_csp = ""
if CONFIG.invidious_companion.present?
if !{
"/sb/",
"/vi/",
"/s_p/",
"/yts/",
"/ggpht/",
}.any? { |r| env.request.resource.starts_with? r }
current_companion_d = host.split(":")[0].split(".")[0]
if index = COMPANION_PREFIXES.index(current_companion_d)
env.set "using_domain", true
env.set "current_companion", index
env.set "companion_public_url", CONFIG.invidious_companion[index].public_url.to_s
else
if !env.request.cookies[CONFIG.server_id_cookie_name]?
env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host)
end
begin
current_companion = env.request.cookies[CONFIG.server_id_cookie_name].value.try &.to_i
rescue
working_ends = BackendInfo.get_working_ends
if !working_ends.empty?
current_companion = working_ends.sample
else
current_companion = rand(CONFIG.invidious_companion.size)
end
end
if current_companion < 0
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(host, current_companion)
end
companion_status = BackendInfo.get_status
if companion_status[current_companion] != BackendInfo::Status::Working.to_i
current_companion = 0 if current_companion == companion_status.size - 1
alive_companion = companion_status.index(BackendInfo::Status::Working.to_i, offset: current_companion)
if alive_companion
env.set "companion_switched", true
current_companion = alive_companion
env.response.cookies[CONFIG.server_id_cookie_name] = Invidious::User::Cookies.server_id(host, current_companion)
end
end
env.set "current_companion", current_companion
if host.split(".").last == "i2p"
env.set "using_i2p", true
env.set "companion_public_url", CONFIG.invidious_companion[current_companion].i2p_public_url.to_s
else
env.set "using_i2p", false
env.set "companion_public_url", CONFIG.invidious_companion[current_companion].public_url.to_s
end
end
extra_media_csp, extra_connect_csp = BackendInfo.get_csp(env.get("current_companion").as(Int32))
end
end
# Only allow the pages at /embed/* to be embedded
if env.request.resource.starts_with?("/embed")
frame_ancestors = "'self' file: http: https:"
@@ -107,22 +33,6 @@ module Invidious::Routes::BeforeAll
scheme = env.request.headers["X-Forwarded-Proto"]? || ("https" if CONFIG.https_only) || "http"
env.set "scheme", scheme
# TODO: Remove style-src's 'unsafe-inline', requires to remove all
# inline styles (<style> [..] </style>, style=" [..] ")
env.response.headers["Content-Security-Policy"] = {
"default-src 'none'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: " + "#{scheme}://#{env.request.headers["Host"]?}",
"font-src 'self' data:",
"connect-src 'self'" + extra_connect_csp,
"manifest-src 'self'",
"media-src 'self' blob:" + extra_media_csp,
"child-src 'self' blob:",
"frame-src 'self'",
"frame-ancestors " + frame_ancestors,
}.join("; ") if CONFIG.csp
env.response.headers["Referrer-Policy"] = "same-origin"
# Ask the chrom*-based browsers to disable FLoC
@@ -183,6 +93,32 @@ module Invidious::Routes::BeforeAll
preferences.locale = locale
env.set "preferences", preferences
companion_csp = ""
if companion_status = COMPANION_STATUS
companion_csp = Invidious::Routes::BeforeAll::Companion.process_companion(
env,
host,
companion_status,
preferences
)
end
# TODO: Remove style-src's 'unsafe-inline', requires to remove all
# inline styles (<style> [..] </style>, style=" [..] ")
env.response.headers["Content-Security-Policy"] = {
"default-src 'none'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: " + "#{scheme}://#{env.request.headers["Host"]?}",
"font-src 'self' data:",
"connect-src 'self' " + companion_csp,
"manifest-src 'self'",
"media-src 'self' blob: " + companion_csp,
"child-src 'self' blob:",
"frame-src 'self'",
"frame-ancestors " + frame_ancestors,
}.join("; ") if CONFIG.csp
# Allow media resources to be loaded from google servers
# TODO: check if *.youtube.com can be removed
#
@@ -212,3 +148,182 @@ module Invidious::Routes::BeforeAll
env.set "current_page", URI.encode_www_form(current_page)
end
end
#
# Invidious companion processing
#
module Invidious::Routes::BeforeAll::Companion
extend self
private COMPANION_PREFIXES = [] of String
if c_prefix = CONFIG.invidious_companion_prefix
CONFIG.invidious_companion.each_with_index do |_, i|
prefix = c_prefix + "#{i + 1}"
COMPANION_PREFIXES << prefix
end
end
def process_companion(
env : HTTP::Server::Context,
host : String,
companion_status : CompanionStatus,
preferences : Preferences,
)
cookie_name = CONFIG.server_id_cookie_name
c_size = CONFIG.invidious_companion.size
current_companion = 0
# When accessing via domain we assume the user explicitely wants to access
# that domain.
if CONFIG.invidious_companion_prefix.presence && (index = self.using_invidious_domain?(host))
env.set "companion_using_domain", true
env.set "companion_companion_public_url", CONFIG.invidious_companion[index].public_url.to_s
current_companion = index
else
# Set cookie if there is no cookie
if !env.request.cookies.has_key?(cookie_name)
current_companion = self.find_available_companion(env, host, nil, companion_status, preferences)
if current_companion
self.set_cookie(env, host, current_companion)
else
return ""
end
else
begin
current_companion = get_cookie(env)
current_companion = self.find_available_companion(env, host, current_companion, companion_status, preferences)
rescue
current_companion = rand(c_size)
self.set_cookie(env, host, current_companion)
end
end
if current_companion.nil?
return ""
end
# Set I2P public URL when it's being accessed via I2P.
# I2P is not like Tor, therefore I2P users can't connect to "clearnet" sites
# like it would work in Tor.
if host.split(".").last == "i2p"
env.set "companion_using_i2p", true
env.set "companion_companion_public_url", CONFIG.invidious_companion[current_companion].i2p_public_url.to_s
else
env.set "companion_using_i2p", false
env.set "companion_companion_public_url", CONFIG.invidious_companion[current_companion].public_url.to_s
end
end
env.set "current_companion", current_companion
companion_csp = companion_status.companions[current_companion].csp
return companion_csp
end
private def set_cookie(
env : HTTP::Server::Context,
host : String,
current_companion : Int32,
)
cookie_name = CONFIG.server_id_cookie_name
env.response.cookies[cookie_name] = Invidious::User::Cookies.server_id(host, current_companion)
end
private def get_cookie(env : HTTP::Server::Context)
cookie_name = CONFIG.server_id_cookie_name
return env.request.cookies[cookie_name].value.try &.to_i
end
private def find_available_companion(
env : HTTP::Server::Context,
host : String,
current_companion : Int32?,
companion_status : CompanionStatus,
preferences : Preferences,
)
companions = companion_status.companions
working_companions = companion_status.working_companions
c_size = companions.size
if !preferences.show_community_backends
working_companions = working_companions.all
else
working_companions = working_companions.community
end
if current_companion.nil?
available_companion = self.get_available_companion(c_size, working_companions)
if available_companion
current_companion = available_companion
return current_companion
else
return nil
end
end
current_companion = self.wrap_current_companion(env, host, current_companion, c_size, working_companions)
if current_companion.nil?
return nil
end
status = companions[current_companion].status
if status != CompanionStatus::Status::Working
alive_companion = self.get_available_companion(c_size, working_companions)
if alive_companion
current_companion = alive_companion
env.set "companion_switched", true
self.set_cookie(env, host, current_companion)
end
end
return current_companion
end
private def using_invidious_domain?(host : String)
current_companion_domain = host.split(":")[0].split(".")[0]
if index = COMPANION_PREFIXES.index(current_companion_domain)
return index
else
return nil
end
end
# Checks if the current_companion does not match any companion
private def wrap_current_companion(
env : HTTP::Server::Context,
host : String,
current_companion : Int32,
invidious_companion_size : Int32,
working_companions : Array(Int32),
)
if (current_companion < 0) || current_companion >= invidious_companion_size
current_companion = self.get_available_companion(invidious_companion_size, working_companions)
if current_companion
self.set_cookie(env, host, current_companion)
else
current_companion = rand(invidious_companion_size)
end
end
return current_companion
end
private def check_community(env, current_companion, companions)
companion = companions[current_companion].companion
if companion.community
end
end
private def get_available_companion(
invidious_companion_size : Int32,
working_companions : Array(Int32),
)
if !working_companions.empty?
# Choose a random working companion
current_companion = working_companions.sample
return current_companion
else
return nil
end
end
end

View File

@@ -268,7 +268,7 @@ module Invidious::Routes::VideoPlayback
# so we have a mechanism here to redirect to the latest version
def self.latest_version(env)
if CONFIG.invidious_companion.present?
companion_public_url = env.get("companion_public_url").as(String)
companion_public_url = env.get("companion_companion_public_url").as(String)
return env.redirect "#{companion_public_url}/latest_version?#{env.params.query}"
end

View File

@@ -56,10 +56,7 @@ struct Invidious::User
# 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
def server_id(domain : String?, server_id : Int32) : HTTP::Cookie
# Strip the port from the domain if it's being accessed from another port
# Browsers will reject the cookie if it contains the port number. This is
# because `example.com:3000` is not the same as `example.com` on a cookie.

View File

@@ -25,8 +25,8 @@
audio_streams.each_with_index do |fmt, i|
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
src_url += "&local=true" if params.local
companion_public_url = env.get("companion_public_url").as(String)
src_url = companion_public_url + src_url +
companion_public_url = env.get("companion_companion_public_url").as(String)
src_url = companion_public_url + src_url +
"&check=#{invidious_companion_check_id}" if (invidious_companion)
bitrate = fmt["bitrate"]
@@ -42,7 +42,7 @@
<% else %>
<% if params.quality == "dash"
src_url = "/api/manifest/dash/id/" + video.id + "?local=true&unique_res=1"
companion_public_url = env.get("companion_public_url").as(String)
companion_public_url = env.get("companion_companion_public_url").as(String)
src_url = companion_public_url + src_url +
"&check=#{invidious_companion_check_id}" if (invidious_companion)
%>
@@ -55,7 +55,7 @@
fmt_stream.each_with_index do |fmt, i|
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
src_url += "&local=true" if params.local
companion_public_url = env.get("companion_public_url").as(String)
companion_public_url = env.get("companion_companion_public_url").as(String)
src_url = companion_public_url + src_url +
"&check=#{invidious_companion_check_id}" if (invidious_companion)
@@ -73,7 +73,8 @@
<% 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)
companion_public_url = env.get("companion_companion_public_url").as(String)
api_captions_url = companion_public_url + api_captions_url if (invidious_companion)
api_captions_check_id = "&check=#{invidious_companion_check_id}"
%>
<track kind="captions" src="<%= api_captions_url %><%= video.id %>?label=<%= caption.name %><%= api_captions_check_id %>" label="<%= caption.name %>">
@@ -81,7 +82,8 @@
<% 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)
companion_public_url = env.get("companion_companion_public_url").as(String)
api_captions_url = companion_public_url + api_captions_url if (invidious_companion)
api_captions_check_id = "&check=#{invidious_companion_check_id}"
%>
<track kind="captions" src="<%= api_captions_url %><%= video.id %>?label=<%= caption.name %><%= api_captions_check_id %>" label="<%= caption.name %>">

View File

@@ -108,28 +108,30 @@
</div>
<%
if CONFIG.invidious_companion.present?
if companion_status = COMPANION_STATUS
current_backend = env.get?("current_companion").try &.as(Int32)
domain = env.get?("using_domain")
domain = env.get?("companion_using_domain")
scheme = env.get("scheme")
status = BackendInfo.get_status
companion_switched = env.get?("companion_switched")
using_i2p = env.get?("using_i2p")
using_i2p = env.get?("companion_using_i2p")
companions = companion_status.companions
%>
<div class="h-box" style="margin-bottom: 10px;">
<b><%= translate(locale, "companion_switch_backend") %></b>
<% if domain %>
<% CONFIG.invidious_companion.each_with_index do | companion, index | %>
<% next if companion.community && !preferences.show_community_backends %>
<% next if companion.i2p_public_url.host.nil? && using_i2p %>
<% host_backend = env.request.headers["Host"].sub(/([^.]+)(\d+)/, "\\1#{index+1}") %>
<% is_current_backend_host = host_backend == env.request.headers["Host"] %>
<% CONFIG.invidious_companion.each_with_index do | companion, index |
next if companion.community && !preferences.show_community_backends
next if companion.i2p_public_url.host.nil? && using_i2p
host_backend = env.request.headers["Host"].sub(/([^.]+)(\d+)/, "\\1#{index+1}")
is_current_backend_host = host_backend == env.request.headers["Host"]
backend_name_prefix = CONFIG.backend_name_prefix + (index + 1).to_s
%>
<a href="<%= scheme %>://<%= host_backend %><%= env.request.resource %>" style="<%= is_current_backend_host ? "text-decoration-line: underline;" : "" %> display: inline-block;">
<%= HTML.escape(CONFIG.backend_name_prefix + (index + 1).to_s) %> <%= HTML.escape(companion.note) %>
<%= HTML.escape(backend_name_prefix) %> <%= HTML.escape(companion.note) %>
<span style="color:
<% if status[index] == BackendInfo::Status::Dead.to_i %> #fd4848; <% end %>
<% if status[index] == BackendInfo::Status::Blocked.to_i %> #ddc338; <% end %>
<% if status[index] == BackendInfo::Status::Working.to_i %> #42ae3c; <% end %>
<% if companions[index].status == CompanionStatus::Status::Down %> #fd4848; <% end %>
<% if companions[index].status == CompanionStatus::Status::Blocked %> #ddc338; <% end %>
<% if companions[index].status == CompanionStatus::Status::Working %> #42ae3c; <% end %>
">❚</span>
</a>
<% if !(index == CONFIG.invidious_companion.size-1) %>
@@ -137,16 +139,19 @@
<% end %>
<% end %>
<% else %>
<% current_page = env.get("current_page") %>
<% CONFIG.invidious_companion.each_with_index do | companion, index | %>
<% next if companion.community && !preferences.show_community_backends %>
<% next if companion.i2p_public_url.host.nil? && using_i2p %>
<%
current_page = env.get("current_page")
CONFIG.invidious_companion.each_with_index do | companion, index |
next if companion.community && !preferences.show_community_backends
next if companion.i2p_public_url.host.nil? && using_i2p
backend_name_prefix = CONFIG.backend_name_prefix + (index + 1).to_s
%>
<a href="/switchbackend?backend_id=<%= index.to_s %>&referer=<%= current_page %>" style="<%= current_backend == index ? "text-decoration-line: underline;" : "" %> display: inline-block;">
<%= HTML.escape(CONFIG.backend_name_prefix + (index + 1).to_s) %> <%= HTML.escape(companion.note) %>
<%= HTML.escape(backend_name_prefix) %> <%= HTML.escape(companion.note) %>
<span style="color:
<% if status[index] == BackendInfo::Status::Dead.to_i %> #fd4848; <% end %>
<% if status[index] == BackendInfo::Status::Blocked.to_i %> #ddc338; <% end %>
<% if status[index] == BackendInfo::Status::Working.to_i %> #42ae3c; <% end %>
<% if companions[index].status == CompanionStatus::Status::Down %> #fd4848; <% end %>
<% if companions[index].status == CompanionStatus::Status::Blocked %> #ddc338; <% end %>
<% if companions[index].status == CompanionStatus::Status::Working %> #42ae3c; <% end %>
">❚</span>
</a>
<% if !(index == CONFIG.invidious_companion.size-1) %>
@@ -342,7 +347,7 @@
<span class="left">
<%
if CONFIG.invidious_companion.present?
companion_public_url = env.get("companion_public_url").as(String)
companion_public_url = env.get("companion_companion_public_url").as(String)
%>
<div class="box">You are currently using Backend: <%= current_backend ? companion_public_url : "Unable to get backend, this is bug, please report it!" %></div>
<% end %>

View File

@@ -652,19 +652,23 @@ module YoutubeAPI
# Send the POST request
begin
if env.nil?
working_ends = BackendInfo.get_working_ends
current_companion = working_ends.sample
else
current_companion = env.get("current_companion").as(Int32)
if companion_status = COMPANION_STATUS
if env.nil?
working_ends = companion_status.working_companions.community
current_companion = working_ends.sample
else
current_companion = env.get("current_companion").as(Int32)
end
end
response_body = Hash(String, JSON::Any).new
COMPANION_POOL[current_companion].client do |wrapper|
companion_base_url = wrapper.companion.private_url.path
if current_companion
COMPANION_POOL[current_companion].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|
response_body = JSON.parse(response.body_io).as_h
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