- <%- if !env.get("preferences").as(Preferences).thin_mode -%>
+ <%- if !thin_mode -%>
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index 9275631c..498d57a1 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -311,6 +311,8 @@ we're going to need to do it here in order to allow for translations.
&listen=<%= params.listen %>">
/mqdefault.jpg" alt="" />
+ <%- else -%>
+
<%- end -%>
From f2fa3da9d2f8ffc1684997526ddd5b3357d88897 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Wed, 12 Jul 2023 11:06:34 -0700
Subject: [PATCH 21/28] Add support for releases and podcasts tabs
---
locales/en-US.json | 2 +
src/invidious/channels/playlists.cr | 18 +++++++
src/invidious/frontend/channel_page.cr | 2 +
src/invidious/routes/api/v1/channels.cr | 62 ++++++++++++++++++++++++-
src/invidious/routes/channels.cr | 44 +++++++++++++++++-
src/invidious/routing.cr | 5 ++
src/invidious/views/channel.ecr | 2 +
src/invidious/yt_backend/extractors.cr | 5 +-
8 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/locales/en-US.json b/locales/en-US.json
index e13ba968..29dd7a40 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -474,6 +474,8 @@
"channel_tab_videos_label": "Videos",
"channel_tab_shorts_label": "Shorts",
"channel_tab_streams_label": "Livestreams",
+ "channel_tab_podcasts_label": "Podcasts",
+ "channel_tab_releases_label": "Releases",
"channel_tab_playlists_label": "Playlists",
"channel_tab_community_label": "Community",
"channel_tab_channels_label": "Channels"
diff --git a/src/invidious/channels/playlists.cr b/src/invidious/channels/playlists.cr
index 8dc824b2..91029fe3 100644
--- a/src/invidious/channels/playlists.cr
+++ b/src/invidious/channels/playlists.cr
@@ -26,3 +26,21 @@ def fetch_channel_playlists(ucid, author, continuation, sort_by)
return extract_items(initial_data, author, ucid)
end
+
+def fetch_channel_podcasts(ucid, author, continuation)
+ if continuation
+ initial_data = YoutubeAPI.browse(continuation)
+ else
+ initial_data = YoutubeAPI.browse(ucid, params: "Eghwb2RjYXN0c_IGBQoDugEA")
+ end
+ return extract_items(initial_data, author, ucid)
+end
+
+def fetch_channel_releases(ucid, author, continuation)
+ if continuation
+ initial_data = YoutubeAPI.browse(continuation)
+ else
+ initial_data = YoutubeAPI.browse(ucid, params: "EghyZWxlYXNlc_IGBQoDsgEA")
+ end
+ return extract_items(initial_data, author, ucid)
+end
diff --git a/src/invidious/frontend/channel_page.cr b/src/invidious/frontend/channel_page.cr
index 53745dd5..fe7d6d6e 100644
--- a/src/invidious/frontend/channel_page.cr
+++ b/src/invidious/frontend/channel_page.cr
@@ -5,6 +5,8 @@ module Invidious::Frontend::ChannelPage
Videos
Shorts
Streams
+ Podcasts
+ Releases
Playlists
Community
Channels
diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr
index bcb4db2c..adf05d30 100644
--- a/src/invidious/routes/api/v1/channels.cr
+++ b/src/invidious/routes/api/v1/channels.cr
@@ -245,7 +245,7 @@ module Invidious::Routes::API::V1::Channels
channel = nil # Make the compiler happy
get_channel()
- items, continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
+ items, next_continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
JSON.build do |json|
json.object do
@@ -257,7 +257,65 @@ module Invidious::Routes::API::V1::Channels
end
end
- json.field "continuation", continuation
+ json.field "continuation", next_continuation if next_continuation
+ end
+ end
+ end
+
+ def self.podcasts(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ env.response.content_type = "application/json"
+
+ ucid = env.params.url["ucid"]
+ continuation = env.params.query["continuation"]?
+
+ # Use the macro defined above
+ channel = nil # Make the compiler happy
+ get_channel()
+
+ items, next_continuation = fetch_channel_podcasts(channel.ucid, channel.author, continuation)
+
+ JSON.build do |json|
+ json.object do
+ json.field "playlists" do
+ json.array do
+ items.each do |item|
+ item.to_json(locale, json) if item.is_a?(SearchPlaylist)
+ end
+ end
+ end
+
+ json.field "continuation", next_continuation if next_continuation
+ end
+ end
+ end
+
+ def self.releases(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ env.response.content_type = "application/json"
+
+ ucid = env.params.url["ucid"]
+ continuation = env.params.query["continuation"]?
+
+ # Use the macro defined above
+ channel = nil # Make the compiler happy
+ get_channel()
+
+ items, next_continuation = fetch_channel_releases(channel.ucid, channel.author, continuation)
+
+ JSON.build do |json|
+ json.object do
+ json.field "playlists" do
+ json.array do
+ items.each do |item|
+ item.to_json(locale, json) if item.is_a?(SearchPlaylist)
+ end
+ end
+ end
+
+ json.field "continuation", next_continuation if next_continuation
end
end
end
diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr
index 16621994..9892ae2a 100644
--- a/src/invidious/routes/channels.cr
+++ b/src/invidious/routes/channels.cr
@@ -27,7 +27,7 @@ module Invidious::Routes::Channels
item.author
end
end
- items = items.select(SearchPlaylist).map(&.as(SearchPlaylist))
+ items = items.select(SearchPlaylist)
items.each(&.author = "")
else
sort_options = {"newest", "oldest", "popular"}
@@ -105,13 +105,53 @@ module Invidious::Routes::Channels
channel.ucid, channel.author, continuation, (sort_by || "last")
)
- items = items.select(SearchPlaylist).map(&.as(SearchPlaylist))
+ items = items.select(SearchPlaylist)
items.each(&.author = "")
selected_tab = Frontend::ChannelPage::TabsAvailable::Playlists
templated "channel"
end
+ def self.podcasts(env)
+ data = self.fetch_basic_information(env)
+ return data if !data.is_a?(Tuple)
+
+ locale, user, subscriptions, continuation, ucid, channel = data
+
+ sort_by = ""
+ sort_options = [] of String
+
+ items, next_continuation = fetch_channel_podcasts(
+ channel.ucid, channel.author, continuation
+ )
+
+ items = items.select(SearchPlaylist)
+ items.each(&.author = "")
+
+ selected_tab = Frontend::ChannelPage::TabsAvailable::Podcasts
+ templated "channel"
+ end
+
+ def self.releases(env)
+ data = self.fetch_basic_information(env)
+ return data if !data.is_a?(Tuple)
+
+ locale, user, subscriptions, continuation, ucid, channel = data
+
+ sort_by = ""
+ sort_options = [] of String
+
+ items, next_continuation = fetch_channel_releases(
+ channel.ucid, channel.author, continuation
+ )
+
+ items = items.select(SearchPlaylist)
+ items.each(&.author = "")
+
+ selected_tab = Frontend::ChannelPage::TabsAvailable::Releases
+ templated "channel"
+ end
+
def self.community(env)
data = self.fetch_basic_information(env)
if !data.is_a?(Tuple)
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index daaf4d88..9c43171c 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -118,6 +118,8 @@ module Invidious::Routing
get "/channel/:ucid/videos", Routes::Channels, :videos
get "/channel/:ucid/shorts", Routes::Channels, :shorts
get "/channel/:ucid/streams", Routes::Channels, :streams
+ get "/channel/:ucid/podcasts", Routes::Channels, :podcasts
+ get "/channel/:ucid/releases", Routes::Channels, :releases
get "/channel/:ucid/playlists", Routes::Channels, :playlists
get "/channel/:ucid/community", Routes::Channels, :community
get "/channel/:ucid/channels", Routes::Channels, :channels
@@ -228,6 +230,9 @@ module Invidious::Routing
get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
get "/api/v1/channels/:ucid/shorts", {{namespace}}::Channels, :shorts
get "/api/v1/channels/:ucid/streams", {{namespace}}::Channels, :streams
+ get "/api/v1/channels/:ucid/podcasts", {{namespace}}::Channels, :podcasts
+ get "/api/v1/channels/:ucid/releases", {{namespace}}::Channels, :releases
+
get "/api/v1/channels/:ucid/channels", {{namespace}}::Channels, :channels
{% for route in {"videos", "latest", "playlists", "community", "search"} %}
diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr
index 6e62a471..066e25b5 100644
--- a/src/invidious/views/channel.ecr
+++ b/src/invidious/views/channel.ecr
@@ -9,6 +9,8 @@
when .streams? then "/channel/#{ucid}/streams"
when .playlists? then "/channel/#{ucid}/playlists"
when .channels? then "/channel/#{ucid}/channels"
+ when .podcasts? then "/channel/#{ucid}/podcasts"
+ when .releases? then "/channel/#{ucid}/releases"
else
"/channel/#{ucid}"
end
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr
index 6686e6e7..e5029dc5 100644
--- a/src/invidious/yt_backend/extractors.cr
+++ b/src/invidious/yt_backend/extractors.cr
@@ -408,8 +408,8 @@ private module Parsers
# Returns nil when the given object isn't a RichItemRenderer
#
# A richItemRenderer seems to be a simple wrapper for a videoRenderer, used
- # by the result page for hashtags. It is located inside a continuationItems
- # container.
+ # by the result page for hashtags and for the podcast tab on channels.
+ # It is located inside a continuationItems container for hashtags.
#
module RichItemRendererParser
def self.process(item : JSON::Any, author_fallback : AuthorFallback)
@@ -421,6 +421,7 @@ private module Parsers
private def self.parse(item_contents, author_fallback)
child = VideoRendererParser.process(item_contents, author_fallback)
child ||= ReelItemRendererParser.process(item_contents, author_fallback)
+ child ||= PlaylistRendererParser.process(item_contents, author_fallback)
return child
end
From 05cc5033910cabe7008832e8917b93ee3112a540 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Sat, 15 Jul 2023 12:57:26 +0000
Subject: [PATCH 22/28] Fix lint
---
src/invidious/views/channel.ecr | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr
index 066e25b5..4b50e7a0 100644
--- a/src/invidious/views/channel.ecr
+++ b/src/invidious/views/channel.ecr
@@ -9,8 +9,8 @@
when .streams? then "/channel/#{ucid}/streams"
when .playlists? then "/channel/#{ucid}/playlists"
when .channels? then "/channel/#{ucid}/channels"
- when .podcasts? then "/channel/#{ucid}/podcasts"
- when .releases? then "/channel/#{ucid}/releases"
+ when .podcasts? then "/channel/#{ucid}/podcasts"
+ when .releases? then "/channel/#{ucid}/releases"
else
"/channel/#{ucid}"
end
From 70145cba31fb7fa14dafa3493c9133c01f642116 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Tue, 11 Jul 2023 20:49:36 -0700
Subject: [PATCH 23/28] Community: Parse `Quiz` attachments
---
src/invidious/channels/community.cr | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index aac4bc8a..671f6dee 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -216,6 +216,22 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
parse_item(attachment)
.as(SearchPlaylist)
.to_json(locale, json)
+ when .has_key?("quizRenderer")
+ json.object do
+ attachment = attachment["quizRenderer"]
+ json.field "type", "quiz"
+ json.field "totalVotes", short_text_to_number(attachment["totalVotes"]["simpleText"].as_s.split(" ")[0])
+ json.field "choices" do
+ json.array do
+ attachment["choices"].as_a.each do |choice|
+ json.object do
+ json.field "text", choice.dig("text", "runs", 0, "text").as_s
+ json.field "isCorrect", choice["isCorrect"].as_bool
+ end
+ end
+ end
+ end
+ end
else
json.object do
json.field "type", "unknown"
From c1a69e4a4a8b581ec743b7b3f741097d6596cb3b Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Sun, 16 Jul 2023 17:23:23 +0200
Subject: [PATCH 24/28] Channels: Use innertube to fetch the community tab
---
src/invidious/channels/community.cr | 54 +++++++++-----------------
src/invidious/yt_backend/extractors.cr | 26 ++++++++-----
2 files changed, 34 insertions(+), 46 deletions(-)
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index aac4bc8a..1a54a946 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -1,49 +1,31 @@
private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000}
# TODO: Add "sort_by"
-def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
- response = YT_POOL.client &.get("/channel/#{ucid}/community?gl=US&hl=en")
- if response.status_code != 200
- response = YT_POOL.client &.get("/user/#{ucid}/community?gl=US&hl=en")
- end
+def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
+ if cursor.nil?
+ # Egljb21tdW5pdHk%3D is the protobuf object to load "community"
+ initial_data = YoutubeAPI.browse(ucid, params: "Egljb21tdW5pdHk%3D")
- if response.status_code != 200
- raise NotFoundException.new("This channel does not exist.")
- end
-
- ucid = response.body.match(/https:\/\/www.youtube.com\/channel\/(?UC[a-zA-Z0-9_-]{22})/).not_nil!["ucid"]
-
- if !continuation || continuation.empty?
- initial_data = extract_initial_data(response.body)
- body = extract_selected_tab(initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"])["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]
-
- if !body
- raise InfoException.new("Could not extract community tab.")
+ items = [] of JSON::Any
+ extract_items(initial_data) do |item|
+ items << item
end
else
- continuation = produce_channel_community_continuation(ucid, continuation)
+ continuation = produce_channel_community_continuation(ucid, cursor)
+ initial_data = YoutubeAPI.browse(continuation: continuation)
- headers = HTTP::Headers.new
- headers["cookie"] = response.cookies.add_request_headers(headers)["cookie"]
+ container = initial_data.dig?("continuationContents", "itemSectionContinuation", "contents")
- session_token = response.body.match(/"XSRF_TOKEN":"(?[^"]+)"/).try &.["session_token"]? || ""
- post_req = {
- session_token: session_token,
- }
+ raise InfoException.new("Can't extract community data") if container.nil?
- body = YoutubeAPI.browse(continuation)
-
- body = body.dig?("continuationContents", "itemSectionContinuation") ||
- body.dig?("continuationContents", "backstageCommentsContinuation")
-
- if !body
- raise InfoException.new("Could not extract continuation.")
- end
+ items = container.as_a
end
- posts = body["contents"].as_a
+ return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
+end
- if message = posts[0]["messageRenderer"]?
+def extract_channel_community(items, *, ucid, locale, format, thin_mode)
+ if message = items[0]["messageRenderer"]?
error_message = (message["text"]["simpleText"]? ||
message["text"]["runs"]?.try &.[0]?.try &.["text"]?)
.try &.as_s || ""
@@ -59,7 +41,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
json.field "authorId", ucid
json.field "comments" do
json.array do
- posts.each do |post|
+ items.each do |post|
comments = post["backstagePostThreadRenderer"]?.try &.["comments"]? ||
post["backstageCommentsContinuation"]?
@@ -242,7 +224,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
end
end
end
- if cont = posts.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token")
+ if cont = items.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token")
json.field "continuation", extract_channel_community_cursor(cont.as_s)
end
end
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr
index e5029dc5..8cf59d50 100644
--- a/src/invidious/yt_backend/extractors.cr
+++ b/src/invidious/yt_backend/extractors.cr
@@ -608,19 +608,25 @@ private module Extractors
private def self.unpack_section_list(contents)
raw_items = [] of JSON::Any
- contents.as_a.each do |renderer_container|
- renderer_container_contents = renderer_container["itemSectionRenderer"]["contents"][0]
-
- # Category extraction
- if items_container = renderer_container_contents["shelfRenderer"]?
- raw_items << renderer_container_contents
- next
- elsif items_container = renderer_container_contents["gridRenderer"]?
+ contents.as_a.each do |item|
+ if item_section_content = item.dig?("itemSectionRenderer", "contents")
+ raw_items += self.unpack_item_section(item_section_content)
else
- items_container = renderer_container_contents
+ raw_items << item
end
+ end
- items_container["items"]?.try &.as_a.each do |item|
+ return raw_items
+ end
+
+ private def self.unpack_item_section(contents)
+ raw_items = [] of JSON::Any
+
+ contents.as_a.each do |item|
+ # Category extraction
+ if container = item.dig?("gridRenderer", "items") || item.dig?("items")
+ raw_items += container.as_a
+ else
raw_items << item
end
end
From c5fe96e93603db58d6767928eedc658e8b58e59f Mon Sep 17 00:00:00 2001
From: syeopite
Date: Wed, 26 Jul 2023 07:19:12 -0700
Subject: [PATCH 25/28] Remove lsquic from codebase
---
config/config.example.yml | 21 ---
shard.lock | 4 -
shard.yml | 3 -
src/invidious.cr | 2 +-
src/invidious/config.cr | 2 -
src/invidious/routes/images.cr | 142 +++-----------------
src/invidious/yt_backend/connection_pool.cr | 37 +----
src/invidious/yt_backend/youtube_api.cr | 14 +-
8 files changed, 32 insertions(+), 193 deletions(-)
diff --git a/config/config.example.yml b/config/config.example.yml
index 34070fe5..e925a5e3 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -140,27 +140,6 @@ https_only: false
##
#pool_size: 100
-##
-## Enable/Disable the use of QUIC (HTTP/3) when connecting
-## to the youtube API and websites ('youtube.com', 'ytimg.com').
-## QUIC's main advantages are its lower latency and lower bandwidth
-## use, compared to its predecessors. However, the current version
-## of QUIC used in invidious is still based on the IETF draft 31,
-## meaning that the underlying library may still not be fully
-## optimized. You can read more about QUIC at the link below:
-## https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-31
-##
-## Note: you should try both options and see what is the best for your
-## instance. In general QUIC is recommended for public instances. Your
-## mileage may vary.
-##
-## Note 2: Using QUIC prevents some captcha challenges from appearing.
-## See: https://github.com/iv-org/invidious/issues/957#issuecomment-576424042
-##
-## Accepted values: true, false
-## Default: false
-##
-#use_quic: false
##
## Additional cookies to be sent when requesting the youtube API.
diff --git a/shard.lock b/shard.lock
index 235e4c25..55fcfe46 100644
--- a/shard.lock
+++ b/shard.lock
@@ -24,10 +24,6 @@ shards:
git: https://github.com/jeromegn/kilt.git
version: 0.6.1
- lsquic:
- git: https://github.com/iv-org/lsquic.cr.git
- version: 2.18.1-2
-
pg:
git: https://github.com/will/crystal-pg.git
version: 0.24.0
diff --git a/shard.yml b/shard.yml
index 7ee0bb2a..e929160d 100644
--- a/shard.yml
+++ b/shard.yml
@@ -25,9 +25,6 @@ dependencies:
protodec:
github: iv-org/protodec
version: ~> 0.1.5
- lsquic:
- github: iv-org/lsquic.cr
- version: ~> 2.18.1-2
athena-negotiation:
github: athena-framework/negotiation
version: ~> 0.1.1
diff --git a/src/invidious.cr b/src/invidious.cr
index 84e1895d..e0bd0101 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -90,7 +90,7 @@ SOFTWARE = {
"branch" => "#{CURRENT_BRANCH}",
}
-YT_POOL = YoutubeConnectionPool.new(YT_URL, capacity: CONFIG.pool_size, use_quic: CONFIG.use_quic)
+YT_POOL = YoutubeConnectionPool.new(YT_URL, capacity: CONFIG.pool_size)
# CLI
Kemal.config.extra_options do |parser|
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
index e5f1e822..cee33ce1 100644
--- a/src/invidious/config.cr
+++ b/src/invidious/config.cr
@@ -126,8 +126,6 @@ class Config
property host_binding : String = "0.0.0.0"
# Pool size for HTTP requests to youtube.com and ytimg.com (each domain has a separate pool of `pool_size`)
property pool_size : Int32 = 100
- # Use quic transport for youtube api
- property use_quic : Bool = false
# Saved cookies in "name1=value1; name2=value2..." format
@[YAML::Field(converter: Preferences::StringToCookies)]
diff --git a/src/invidious/routes/images.cr b/src/invidious/routes/images.cr
index 594a7869..b6a2e110 100644
--- a/src/invidious/routes/images.cr
+++ b/src/invidious/routes/images.cr
@@ -3,17 +3,7 @@ module Invidious::Routes::Images
def self.ggpht(env)
url = env.request.path.lchop("/ggpht")
- headers = (
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- HTTP::Headers{":authority" => "yt3.ggpht.com"}
- else
- HTTP::Headers.new
- end
- {% else %}
- HTTP::Headers.new
- {% end %}
- )
+ headers = HTTP::Headers.new
REQUEST_HEADERS_WHITELIST.each do |header|
if env.request.headers[header]?
@@ -42,22 +32,9 @@ module Invidious::Routes::Images
}
begin
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- YT_POOL.client &.get(url, headers) do |resp|
- return request_proc.call(resp)
- end
- else
- HTTP::Client.get("https://yt3.ggpht.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- end
- {% else %}
- # This can likely be optimized into a (small) pool sometime in the future.
- HTTP::Client.get("https://yt3.ggpht.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- {% end %}
+ HTTP::Client.get("https://yt3.ggpht.com#{url}") do |resp|
+ return request_proc.call(resp)
+ end
rescue ex
end
end
@@ -78,10 +55,6 @@ module Invidious::Routes::Images
headers = HTTP::Headers.new
- {% unless flag?(:disable_quic) %}
- headers[":authority"] = "#{authority}.ytimg.com"
- {% end %}
-
REQUEST_HEADERS_WHITELIST.each do |header|
if env.request.headers[header]?
headers[header] = env.request.headers[header]
@@ -107,22 +80,9 @@ module Invidious::Routes::Images
}
begin
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- YT_POOL.client &.get(url, headers) do |resp|
- return request_proc.call(resp)
- end
- else
- HTTP::Client.get("https://#{authority}.ytimg.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- end
- {% else %}
- # This can likely be optimized into a (small) pool sometime in the future.
- HTTP::Client.get("https://#{authority}.ytimg.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- {% end %}
+ HTTP::Client.get("https://#{authority}.ytimg.com#{url}") do |resp|
+ return request_proc.call(resp)
+ end
rescue ex
end
end
@@ -133,17 +93,7 @@ module Invidious::Routes::Images
name = env.params.url["name"]
url = env.request.resource
- headers = (
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- HTTP::Headers{":authority" => "i9.ytimg.com"}
- else
- HTTP::Headers.new
- end
- {% else %}
- HTTP::Headers.new
- {% end %}
- )
+ headers = HTTP::Headers.new
REQUEST_HEADERS_WHITELIST.each do |header|
if env.request.headers[header]?
@@ -169,22 +119,9 @@ module Invidious::Routes::Images
}
begin
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- YT_POOL.client &.get(url, headers) do |resp|
- return request_proc.call(resp)
- end
- else
- HTTP::Client.get("https://i9.ytimg.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- end
- {% else %}
- # This can likely be optimized into a (small) pool sometime in the future.
- HTTP::Client.get("https://i9.ytimg.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- {% end %}
+ HTTP::Client.get("https://i9.ytimg.com#{url}") do |resp|
+ return request_proc.call(resp)
+ end
rescue ex
end
end
@@ -223,41 +160,16 @@ module Invidious::Routes::Images
id = env.params.url["id"]
name = env.params.url["name"]
- headers = (
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- HTTP::Headers{":authority" => "i.ytimg.com"}
- else
- HTTP::Headers.new
- end
- {% else %}
- HTTP::Headers.new
- {% end %}
- )
+ headers = HTTP::Headers.new
if name == "maxres.jpg"
build_thumbnails(id).each do |thumb|
thumbnail_resource_path = "/vi/#{id}/#{thumb[:url]}.jpg"
- # Logic here is short enough that manually typing them out should be fine.
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- if YT_POOL.client &.head(thumbnail_resource_path, headers).status_code == 200
- name = thumb[:url] + ".jpg"
- break
- end
- else
- if HTTP::Client.head("https://i.ytimg.com#{thumbnail_resource_path}").status_code == 200
- name = thumb[:url] + ".jpg"
- break
- end
- end
- {% else %}
- # This can likely be optimized into a (small) pool sometime in the future.
- if HTTP::Client.head("https://i.ytimg.com#{thumbnail_resource_path}").status_code == 200
- name = thumb[:url] + ".jpg"
- break
- end
- {% end %}
+ # This can likely be optimized into a (small) pool sometime in the future.
+ if HTTP::Client.head("https://i.ytimg.com#{thumbnail_resource_path}").status_code == 200
+ name = thumb[:url] + ".jpg"
+ break
+ end
end
end
@@ -287,22 +199,10 @@ module Invidious::Routes::Images
}
begin
- {% unless flag?(:disable_quic) %}
- if CONFIG.use_quic
- YT_POOL.client &.get(url, headers) do |resp|
- return request_proc.call(resp)
- end
- else
- HTTP::Client.get("https://i.ytimg.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- end
- {% else %}
- # This can likely be optimized into a (small) pool sometime in the future.
- HTTP::Client.get("https://i.ytimg.com#{url}") do |resp|
- return request_proc.call(resp)
- end
- {% end %}
+ # This can likely be optimized into a (small) pool sometime in the future.
+ HTTP::Client.get("https://i.ytimg.com#{url}") do |resp|
+ return request_proc.call(resp)
+ end
rescue ex
end
end
diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr
index 658731cf..e9eb726c 100644
--- a/src/invidious/yt_backend/connection_pool.cr
+++ b/src/invidious/yt_backend/connection_pool.cr
@@ -1,11 +1,3 @@
-{% unless flag?(:disable_quic) %}
- require "lsquic"
-
- alias HTTPClientType = QUIC::Client | HTTP::Client
-{% else %}
- alias HTTPClientType = HTTP::Client
-{% end %}
-
def add_yt_headers(request)
if request.headers["User-Agent"] == "Crystal"
request.headers["User-Agent"] ||= "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
@@ -26,11 +18,11 @@ struct YoutubeConnectionPool
property! url : URI
property! capacity : Int32
property! timeout : Float64
- property pool : DB::Pool(HTTPClientType)
+ property pool : DB::Pool(HTTP::Client)
- def initialize(url : URI, @capacity = 5, @timeout = 5.0, use_quic = true)
+ def initialize(url : URI, @capacity = 5, @timeout = 5.0)
@url = url
- @pool = build_pool(use_quic)
+ @pool = build_pool()
end
def client(region = nil, &block)
@@ -43,11 +35,7 @@ struct YoutubeConnectionPool
response = yield conn
rescue ex
conn.close
- {% unless flag?(:disable_quic) %}
- conn = CONFIG.use_quic ? QUIC::Client.new(url) : HTTP::Client.new(url)
- {% else %}
- conn = HTTP::Client.new(url)
- {% end %}
+ conn = HTTP::Client.new(url)
conn.family = (url.host == "www.youtube.com") ? CONFIG.force_resolve : Socket::Family::INET
conn.family = Socket::Family::INET if conn.family == Socket::Family::UNSPEC
@@ -61,19 +49,9 @@ struct YoutubeConnectionPool
response
end
- private def build_pool(use_quic)
- DB::Pool(HTTPClientType).new(initial_pool_size: 0, max_pool_size: capacity, max_idle_pool_size: capacity, checkout_timeout: timeout) do
- conn = nil # Declare
- {% unless flag?(:disable_quic) %}
- if use_quic
- conn = QUIC::Client.new(url)
- else
- conn = HTTP::Client.new(url)
- end
- {% else %}
- conn = HTTP::Client.new(url)
- {% end %}
-
+ private def build_pool
+ DB::Pool(HTTP::Client).new(initial_pool_size: 0, max_pool_size: capacity, max_idle_pool_size: capacity, checkout_timeout: timeout) do
+ conn = HTTP::Client.new(url)
conn.family = (url.host == "www.youtube.com") ? CONFIG.force_resolve : Socket::Family::INET
conn.family = Socket::Family::INET if conn.family == Socket::Family::UNSPEC
conn.before_request { |r| add_yt_headers(r) } if url.host == "www.youtube.com"
@@ -83,7 +61,6 @@ struct YoutubeConnectionPool
end
def make_client(url : URI, region = nil)
- # TODO: Migrate any applicable endpoints to QUIC
client = HTTPClient.new(url, OpenSSL::SSL::Context::Client.insecure)
client.family = (url.host == "www.youtube.com") ? CONFIG.force_resolve : Socket::Family::UNSPEC
client.before_request { |r| add_yt_headers(r) } if url.host == "www.youtube.com"
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index 3dd9e9d8..aef9ddd9 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -595,17 +595,9 @@ module YoutubeAPI
LOGGER.trace("YoutubeAPI: POST data: #{data}")
# Send the POST request
- if {{ !flag?(:disable_quic) }} && CONFIG.use_quic
- # Using QUIC client
- body = YT_POOL.client(client_config.proxy_region,
- &.post(url, headers: headers, body: data.to_json)
- ).body
- else
- # Using HTTP client
- body = YT_POOL.client(client_config.proxy_region) do |client|
- client.post(url, headers: headers, body: data.to_json) do |response|
- self._decompress(response.body_io, response.headers["Content-Encoding"]?)
- end
+ body = YT_POOL.client(client_config.proxy_region) do |client|
+ client.post(url, headers: headers, body: data.to_json) do |response|
+ self._decompress(response.body_io, response.headers["Content-Encoding"]?)
end
end
From a8ba02051b261a634050ea7f621451d84ca61607 Mon Sep 17 00:00:00 2001
From: syeopite
Date: Wed, 26 Jul 2023 07:25:19 -0700
Subject: [PATCH 26/28] Remove(?) lsquic from make and docker files
---
.github/workflows/container-release.yml | 29 ++-----------------------
Makefile | 6 -----
docker/Dockerfile | 11 +---------
docker/Dockerfile.arm64 | 11 +---------
4 files changed, 4 insertions(+), 53 deletions(-)
diff --git a/.github/workflows/container-release.yml b/.github/workflows/container-release.yml
index 86aec94f..13bbf34c 100644
--- a/.github/workflows/container-release.yml
+++ b/.github/workflows/container-release.yml
@@ -52,7 +52,7 @@ jobs:
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- - name: Build and push Docker AMD64 image without QUIC for Push Event
+ - name: Build and push Docker AMD64 image for Push Event
if: github.ref == 'refs/heads/master'
uses: docker/build-push-action@v3
with:
@@ -64,9 +64,8 @@ jobs:
tags: quay.io/invidious/invidious:${{ github.sha }},quay.io/invidious/invidious:latest
build-args: |
"release=1"
- "disable_quic=1"
- - name: Build and push Docker ARM64 image without QUIC for Push Event
+ - name: Build and push Docker ARM64 image for Push Event
if: github.ref == 'refs/heads/master'
uses: docker/build-push-action@v3
with:
@@ -78,28 +77,4 @@ jobs:
tags: quay.io/invidious/invidious:${{ github.sha }}-arm64,quay.io/invidious/invidious:latest-arm64
build-args: |
"release=1"
- "disable_quic=1"
- - name: Build and push Docker AMD64 image with QUIC for Push Event
- if: github.ref == 'refs/heads/master'
- uses: docker/build-push-action@v3
- with:
- context: .
- file: docker/Dockerfile
- platforms: linux/amd64
- labels: quay.expires-after=12w
- push: true
- tags: quay.io/invidious/invidious:${{ github.sha }}-quic,quay.io/invidious/invidious:latest-quic
- build-args: release=1
-
- - name: Build and push Docker ARM64 image with QUIC for Push Event
- if: github.ref == 'refs/heads/master'
- uses: docker/build-push-action@v3
- with:
- context: .
- file: docker/Dockerfile.arm64
- platforms: linux/arm64/v8
- labels: quay.expires-after=12w
- push: true
- tags: quay.io/invidious/invidious:${{ github.sha }}-arm64-quic,quay.io/invidious/invidious:latest-arm64-quic
- build-args: release=1
diff --git a/Makefile b/Makefile
index d4657792..9eb195df 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,6 @@
RELEASE := 1
STATIC := 0
-DISABLE_QUIC := 1
NO_DBG_SYMBOLS := 0
@@ -27,10 +26,6 @@ else
FLAGS += --debug
endif
-ifeq ($(DISABLE_QUIC), 1)
- FLAGS += -Ddisable_quic
-endif
-
ifeq ($(API_ONLY), 1)
FLAGS += -Dapi_only
endif
@@ -115,7 +110,6 @@ help:
@echo " STATIC Link libraries statically (Default: 0)"
@echo ""
@echo " API_ONLY Build invidious without a GUI (Default: 0)"
- @echo " DISABLE_QUIC Disable support for QUIC (Default: 0)"
@echo " NO_DBG_SYMBOLS Strip debug symbols (Default: 0)"
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 57864883..761bbdca 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -2,15 +2,12 @@ FROM crystallang/crystal:1.4.1-alpine AS builder
RUN apk add --no-cache sqlite-static yaml-static
ARG release
-ARG disable_quic
WORKDIR /invidious
COPY ./shard.yml ./shard.yml
COPY ./shard.lock ./shard.lock
RUN shards install --production
-COPY --from=quay.io/invidious/lsquic-compiled /root/liblsquic.a ./lib/lsquic/src/lsquic/ext/liblsquic.a
-
COPY ./src/ ./src/
# TODO: .git folder is required for building – this is destructive.
# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION.
@@ -24,13 +21,7 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
RUN crystal spec --warnings all \
--link-flags "-lxml2 -llzma"
-RUN if [[ "${release}" == 1 && "${disable_quic}" == 1 ]] ; then \
- crystal build ./src/invidious.cr \
- --release \
- -Ddisable_quic \
- --static --warnings all \
- --link-flags "-lxml2 -llzma"; \
- elif [[ "${release}" == 1 ]] ; then \
+RUN if [[ "${release}" == 1 ]] ; then \
crystal build ./src/invidious.cr \
--release \
--static --warnings all \
diff --git a/docker/Dockerfile.arm64 b/docker/Dockerfile.arm64
index 10135efb..cf9231fb 100644
--- a/docker/Dockerfile.arm64
+++ b/docker/Dockerfile.arm64
@@ -2,15 +2,12 @@ FROM alpine:3.16 AS builder
RUN apk add --no-cache 'crystal=1.4.1-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev
ARG release
-ARG disable_quic
WORKDIR /invidious
COPY ./shard.yml ./shard.yml
COPY ./shard.lock ./shard.lock
RUN shards install --production
-COPY --from=quay.io/invidious/lsquic-compiled /root/liblsquic.a ./lib/lsquic/src/lsquic/ext/liblsquic.a
-
COPY ./src/ ./src/
# TODO: .git folder is required for building – this is destructive.
# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION.
@@ -24,13 +21,7 @@ COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
RUN crystal spec --warnings all \
--link-flags "-lxml2 -llzma"
-RUN if [[ "${release}" == 1 && "${disable_quic}" == 1 ]] ; then \
- crystal build ./src/invidious.cr \
- --release \
- -Ddisable_quic \
- --static --warnings all \
- --link-flags "-lxml2 -llzma"; \
- elif [[ "${release}" == 1 ]] ; then \
+RUN if [[ "${release}" == 1 ]] ; then \
crystal build ./src/invidious.cr \
--release \
--static --warnings all \
From 0d27eef047d24f8c7b3f9528502bc5828cad3c73 Mon Sep 17 00:00:00 2001
From: Fabio Henrique
Date: Sun, 6 Aug 2023 12:29:19 +0000
Subject: [PATCH 27/28] update ameba version
fix shard.yml authors typo
---
shard.lock | 7 ++++---
shard.yml | 4 ++--
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/shard.lock b/shard.lock
index 55fcfe46..efb60a59 100644
--- a/shard.lock
+++ b/shard.lock
@@ -1,5 +1,9 @@
version: 2.0
shards:
+ ameba:
+ git: https://github.com/crystal-ameba/ameba.git
+ version: 1.5.0
+
athena-negotiation:
git: https://github.com/athena-framework/negotiation.git
version: 0.1.1
@@ -44,6 +48,3 @@ shards:
git: https://github.com/crystal-lang/crystal-sqlite3.git
version: 0.18.0
- ameba:
- git: https://github.com/crystal-ameba/ameba.git
- version: 0.14.3
diff --git a/shard.yml b/shard.yml
index e929160d..be06a7df 100644
--- a/shard.yml
+++ b/shard.yml
@@ -3,7 +3,7 @@ version: 0.20.1
authors:
- Omar Roth
- - Invidous team
+ - Invidious team
targets:
invidious:
@@ -35,7 +35,7 @@ development_dependencies:
version: ~> 0.10.4
ameba:
github: crystal-ameba/ameba
- version: ~> 0.14.3
+ version: ~> 1.5.0
crystal: ">= 1.0.0, < 2.0.0"
From 2f6b2688bb8042c29942e46767dc78836f21fb57 Mon Sep 17 00:00:00 2001
From: syeopite
Date: Sun, 6 Aug 2023 12:20:05 -0700
Subject: [PATCH 28/28] Use workaround for fetching streaming URLs
YouTube appears to be A/B testing some new integrity checks. Adding the
parameter "CgIQBg" to InnerTube player requests appears to workaround
the problem
See https://github.com/TeamNewPipe/NewPipeExtractor/pull/1084
---
src/invidious/videos/parser.cr | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 9cc0ffdc..2a09d187 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -55,8 +55,9 @@ def extract_video_info(video_id : String, proxy_region : String? = nil)
client_config = YoutubeAPI::ClientConfig.new(proxy_region: proxy_region)
# Fetch data from the player endpoint
- # 8AEB param is used to fetch YouTube stories
- player_response = YoutubeAPI.player(video_id: video_id, params: "8AEB", client_config: client_config)
+ # CgIQBg is a workaround for streaming URLs that returns a 403.
+ # See https://github.com/iv-org/invidious/issues/4027#issuecomment-1666944520
+ player_response = YoutubeAPI.player(video_id: video_id, params: "CgIQBg", client_config: client_config)
playability_status = player_response.dig?("playabilityStatus", "status").try &.as_s
@@ -135,8 +136,9 @@ end
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
LOGGER.debug("try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client.")
- # 8AEB param is used to fetch YouTube stories
- response = YoutubeAPI.player(video_id: id, params: "8AEB", client_config: client_config)
+ # CgIQBg is a workaround for streaming URLs that returns a 403.
+ # See https://github.com/iv-org/invidious/issues/4027#issuecomment-1666944520
+ response = YoutubeAPI.player(video_id: id, params: "CgIQBg", client_config: client_config)
playability_status = response["playabilityStatus"]["status"]
LOGGER.debug("try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}.")