diff --git a/config/config.example.yml b/config/config.example.yml index e925a5e3..79936b94 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -345,6 +345,16 @@ feed_threads: 1 ## #decrypt_polling: false +# +# Time interval between two executions of the job that checks +# whether or not Invidious has been blocked by YouTube +# +# Accepted values: a valid time interval (like 1h30m or 90m) +# Default: 30m +# +#blockage_check_interval: 30m + + jobs: diff --git a/src/invidious.cr b/src/invidious.cr index e0bd0101..d6329472 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -185,6 +185,8 @@ Invidious::Jobs.register Invidious::Jobs::NotificationJob.new(CONNECTION_CHANNEL Invidious::Jobs.register Invidious::Jobs::ClearExpiredItemsJob.new +Invidious::Jobs.register Invidious::Jobs::CheckBlockageStatusJob.new(PG_DB) + Invidious::Jobs.start_all def popular_videos diff --git a/src/invidious/config.cr b/src/invidious/config.cr index cee33ce1..921b4299 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -138,6 +138,9 @@ class Config # Playlist length limit property playlist_length_limit : Int32 = 500 + @[YAML::Field(converter: Preferences::TimeSpanConverter)] + property blockage_check_interval : Time::Span = 30.minutes + def disabled?(option) case disabled = CONFIG.disable_proxy when Bool diff --git a/src/invidious/jobs/blockage_status_job.cr b/src/invidious/jobs/blockage_status_job.cr new file mode 100644 index 00000000..ea7c56be --- /dev/null +++ b/src/invidious/jobs/blockage_status_job.cr @@ -0,0 +1,51 @@ +class Invidious::Jobs::CheckBlockageStatusJob < Invidious::Jobs::BaseJob + private getter db : DB::Database + + BLOCKAGE_STATUS = { + "version" => "1.0", + "blocked" => false, + } + + def initialize(@db) + end + + def begin + # Logic mostly taken from bypass_captcha_job.cr + loop do + begin + # TODO find performant way of fetching a random video from the videos table. + video = fetch_video("zj82_v2R6ts", nil) + + if !video.nil? + # Assume unblocked + BLOCKAGE_STATUS["blocked"] = false + + if video.reason.try &.includes?("YouTube is currently trying to block Invidious instances") + BLOCKAGE_STATUS["blocked"] = true + else + # Fetch a random fetch stream. If it returns a 403 then the instance has been blocked. + random_stream = video.video_streams.sample(1) + if !random_stream.empty? + url = URI.parse(random_stream[0]["url"].as_s) + client = make_client(URI.parse("https://#{url.host.not_nil!}")) + + client.get(url.request_target) do |resp| + if resp.status_code == 403 + BLOCKAGE_STATUS["blocked"] = true + end + + break + end + end + end + end + rescue ex + LOGGER.error("CheckBlockageStatusJob: #{ex.message}") + ensure + LOGGER.debug("CheckBlockageStatusJob: Done, sleeping for #{CONFIG.blockage_check_interval}") + sleep CONFIG.blockage_check_interval + Fiber.yield + end + end + end +end diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr index e499f4d6..1dd0fcc4 100644 --- a/src/invidious/routes/api/v1/misc.cr +++ b/src/invidious/routes/api/v1/misc.cr @@ -10,6 +10,12 @@ module Invidious::Routes::API::V1::Misc end end + # Endpoint for checking whether or not a specific instance has been blocked + def self.blockage(env) + env.response.content_type = "application/json" + return Invidious::Jobs::CheckBlockageStatusJob::BLOCKAGE_STATUS.to_json + end + # APIv1 currently uses the same logic for both # user playlists and Invidious playlists. This means that we can't # reasonably split them yet. This should be addressed in APIv2 diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index 9c43171c..c509526d 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -291,6 +291,7 @@ module Invidious::Routing # Misc get "/api/v1/stats", {{namespace}}::Misc, :stats + get "/api/v1/information/status/blocked", {{namespace}}::Misc, :blockage get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes