mirror of
https://git.nadeko.net/Fijxu/invidious.git
synced 2026-01-26 16:51:35 +00:00
Rework companion switcher
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
138
src/invidious/helpers/companion_status.cr
Normal file
138
src/invidious/helpers/companion_status.cr
Normal 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
|
||||
@@ -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
|
||||
17
src/invidious/jobs/companion_checker.cr
Normal file
17
src/invidious/jobs/companion_checker.cr
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 %>">
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user