diff --git a/src/invidious.cr b/src/invidious.cr index b74490b3..0977f3a7 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -313,7 +313,6 @@ Invidious::Routing.get "/channel/:ucid/videos", Invidious::Routes::Channels, :vi Invidious::Routing.get "/channel/:ucid/playlists", Invidious::Routes::Channels, :playlists Invidious::Routing.get "/channel/:ucid/community", Invidious::Routes::Channels, :community Invidious::Routing.get "/channel/:ucid/channels", Invidious::Routes::Channels, :channels -Invidious::Routing.get "/channel/:ucid/channels/:param", Invidious::Routes::Channels, :featured_channel_category Invidious::Routing.get "/channel/:ucid/about", Invidious::Routes::Channels, :about Invidious::Routing.get "/watch", Invidious::Routes::Watch, :handle diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index f32d457d..b271b492 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -380,7 +380,9 @@ def fetch_channel_playlists(ucid, author, continuation, sort_by) return items, continuation end -def fetch_channel_featured_channels(ucid, tab_data, params = nil, continuation = nil, query_title = nil) +def fetch_channel_featured_channels(ucid, tab_data, view=nil, shelf_id=nil, continuation = nil, query_title = nil) + auxiliary_data = {} of String => String + if continuation.is_a?(String) initial_data = request_youtube_api_browse(continuation) items = extract_items(initial_data) @@ -392,9 +394,14 @@ def fetch_channel_featured_channels(ucid, tab_data, params = nil, continuation = browse_endpoint_data: nil, continuation_token: continuation_token, badges: nil, + auxiliary_data: auxiliary_data, })] else - if params.is_a?(String) + if view && shelf_id + auxiliary_data["view"] = view + auxiliary_data["shelf_id"] = shelf_id + + params = produce_featured_channel_browse_param(view.to_i64, shelf_id.to_i64) initial_data = request_youtube_api_browse(ucid, params) continuation_token = fetch_continuation_token(initial_data) else @@ -432,17 +439,19 @@ def fetch_channel_featured_channels(ucid, tab_data, params = nil, continuation = browse_endpoint_data: category.browse_endpoint_data, continuation_token: continuation_token, badges: nil, + auxiliary_data: category.auxiliary_data, }) end # If we don't have any categories we'll create one. if category_array.empty? return [Category.new({ - title: fallback_title, # If continuation contents is requested then the query_title has to be passed along. + title: fallback_title, contents: items, browse_endpoint_data: nil, continuation_token: continuation_token, badges: nil, + auxiliary_data: auxiliary_data, })] end @@ -450,6 +459,21 @@ def fetch_channel_featured_channels(ucid, tab_data, params = nil, continuation = end end +def produce_featured_channel_browse_param(view : Int64, shelf_id : Int64) + object = { + "2:string" => "channels", + "4:varint" => view, + "14:varint" => shelf_id + } + + browse_params = object.try { |i| Protodec::Any.cast_json(object) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } + + return browse_params +end + def produce_channel_videos_continuation(ucid, page = 1, auto_generated = nil, sort_by = "newest", v2 = false) object = { "80226972:embedded" => { diff --git a/src/invidious/helpers/extractors.cr b/src/invidious/helpers/extractors.cr index a9523eb8..a6177031 100644 --- a/src/invidious/helpers/extractors.cr +++ b/src/invidious/helpers/extractors.cr @@ -208,7 +208,6 @@ private class CategoryParser < ItemParser def parse(item_contents, author_fallback) # Title extraction is a bit complicated. There are two possible routes for it # as well as times when the title attribute just isn't sent by YT. - title_container = item_contents["title"]? || "" if !title_container.is_a? String if title = title_container["simpleText"]? @@ -220,6 +219,7 @@ private class CategoryParser < ItemParser title = "" end + auxiliary_data = {} of String => String browse_endpoint = item_contents["endpoint"]?.try &.["browseEndpoint"] || nil browse_endpoint_data = "" category_type = 0 # 0: Video, 1: Channels, 2: Playlist/feed, 3: trending @@ -236,7 +236,14 @@ private class CategoryParser < ItemParser # instead it uses the browseId parameter. So if there isn't a params value we can assume the # category is a playlist/feed if browse_endpoint["params"]? - browse_endpoint_data = browse_endpoint["params"].as_s + # However, even though the channel category type returns the browse endpoint param + # we're not going to be using it in order to preserve compatablity with Youtube. + # and for an URL that looks cleaner + url = item_contents["endpoint"]["commandMetadata"]["webCommandMetadata"]["url"] + url = URI.parse(url.as_s) + auxiliary_data["view"] = url.query_params["view"] + auxiliary_data["shelf_id"] = url.query_params["shelf_id"] + category_type = 1 else browse_endpoint_data = browse_endpoint["browseId"].as_s @@ -276,6 +283,7 @@ private class CategoryParser < ItemParser browse_endpoint_data: browse_endpoint_data, continuation_token: nil, badges: badges, + auxiliary_data: auxiliary_data, }) end end diff --git a/src/invidious/helpers/invidiousitems.cr b/src/invidious/helpers/invidiousitems.cr index 8694ae97..2b753911 100644 --- a/src/invidious/helpers/invidiousitems.cr +++ b/src/invidious/helpers/invidiousitems.cr @@ -237,6 +237,9 @@ class Category property continuation_token : String? property badges : Array(Tuple(String, String))? + # Data unique to only specific types of categories. + property auxiliary_data : Hash(String, String) + def to_json(locale, json : JSON::Builder) json.object do json.field "title", self.title diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index 3d64d796..6d298355 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -102,6 +102,12 @@ class Invidious::Routes::Channels < Invidious::Routes::BaseRoute return env.redirect "/channel/#{channel.ucid}" end + view = env.params.query["view"]? + shelf_id = env.params.query["shelf_id"]? + # Category title isn't returned when requesting a specific category or continuation data + # so we have it in through a url param + current_category_title = env.params.query["title"]? + if continuation offset = env.params.query["offset"]? if offset @@ -112,47 +118,27 @@ class Invidious::Routes::Channels < Invidious::Routes::BaseRoute # Previous continuation previous_continuation = env.params.query["previous"]? - # Category title is not returned when using a continuation token. - title = env.params.query["title"]? - featured_channel_categories = fetch_channel_featured_channels(ucid, channel.tabs["channels"], nil, continuation, title).not_nil! + featured_channel_categories = fetch_channel_featured_channels(ucid, channel.tabs["channels"], nil, nil, continuation, current_category_title).not_nil! + elsif view && shelf_id + offset = env.params.query["offset"]? + if offset + offset = offset.to_i + else + offset = 0 + end + + featured_channel_categories = fetch_channel_featured_channels(ucid, channel.tabs["channels"], view, shelf_id, continuation, current_category_title).not_nil! else previous_continuation = nil - category_param = nil offset = 0 - title = nil - featured_channel_categories = fetch_channel_featured_channels(ucid, channel.tabs["channels"], nil, nil).not_nil! + featured_channel_categories = fetch_channel_featured_channels(ucid, channel.tabs["channels"], nil, nil, current_category_title).not_nil! end templated "channel/featured_channels", buffer_footer: true end - def featured_channel_category(env) - # Used to check when the initial page is reached and redirect to /channel/:ucid/channels/:param when zero - offset = env.params.query["offset"]? - category_param = env.params.url["param"] - if offset - offset = offset.to_i - else - offset = 0 - end - - data = self.fetch_basic_information(env) - if !data.is_a?(Tuple) - return data - end - locale, user, subscriptions, continuation, ucid, channel = data - - # Previous continuation - previous_continuation = env.params.query["previous"]? - # Category title is not returned when using a continuation token. - title = env.params.query["title"]? - - featured_channel_categories = fetch_channel_featured_channels(ucid, channel.tabs["channels"], category_param, continuation, title).not_nil! - templated "channel/featured_channels" - end - def about(env) data = self.fetch_basic_information(env) if !data.is_a?(Tuple) diff --git a/src/invidious/views/channel/featured_channels.ecr b/src/invidious/views/channel/featured_channels.ecr index e2e49d24..a936a01a 100644 --- a/src/invidious/views/channel/featured_channels.ecr +++ b/src/invidious/views/channel/featured_channels.ecr @@ -14,8 +14,9 @@

- <% if (category_request_param = category.browse_endpoint_data).is_a?(String) %> - + <% if category.auxiliary_data.has_key?("view") %> + <% category_url_param = "?view=#{category.auxiliary_data["view"]}&shelf_id=#{category.auxiliary_data["shelf_id"]}" %> + <%= category.title %> <%else%> @@ -90,14 +91,15 @@ <% if !featured_channel_categories.empty? %> + <% base_url = "/channel/#{channel.ucid}/channels?view=#{view}&shelf_id=#{shelf_id}" %>
<% if previous_continuation %> - + <%= translate(locale, "Previous page") %> <% elsif (offset - 1) == 0 %> - + <%= translate(locale, "Previous page") %> <% end %> @@ -105,15 +107,15 @@
<% if (next_cont_token = featured_channel_categories[0].continuation_token) %> - <% additional_url_param = ""%> + <% previous = ""%> <% if continuation %> - <% additional_url_param = "&previous=#{HTML.escape(continuation)}"%> + <% previous = "&previous=#{HTML.escape(continuation)}"%> <% end %> - <% if !title %> - <% title = featured_channel_categories[0].title %> + <% if !current_category_title %> + <% current_category_title = featured_channel_categories[0].title %> <% end %> - + <%= translate(locale, "Next page") %> <% end %>