mirror of
https://github.com/iv-org/invidious.git
synced 2024-11-26 15:37:23 +00:00
Document and reorganize code 4 chan's feat. chans
This commit is contained in:
parent
cc10675610
commit
a6f3c80b6b
@ -1,82 +1,58 @@
|
|||||||
def fetch_channel_featured_channels(ucid, params, view = nil, shelf_id = nil, continuation = nil, query_title = nil) : {Array(Category), (String | Nil)}
|
# Fetches the featured channel categories of a channel
|
||||||
# Continuation to load more channel catagories
|
#
|
||||||
if continuation.is_a?(String)
|
# Returned as an array of Category objects containing different channels.
|
||||||
initial_data = request_youtube_api_browse(continuation)
|
def fetch_channel_featured_channels(ucid) : Array(Category)
|
||||||
items = extract_items(initial_data)
|
initial_data = request_youtube_api_browse(ucid, "EghjaGFubmVscw%3D%3D")
|
||||||
continuation_token = fetch_continuation_token(initial_data)
|
|
||||||
|
|
||||||
return [Category.new({
|
channels_tab = extract_selected_tab(initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"])
|
||||||
title: query_title.not_nil!, # If continuation contents is requested then the query_title has to be passed along.
|
|
||||||
|
# The submenu is the content type menu, and is used to select which categories to view fully.
|
||||||
|
# As a result, it contains the category title which we'll use as a fallback, since Innertube doesn't
|
||||||
|
# return the title when the channel only has one featured channel category.
|
||||||
|
submenu = channels_tab["content"]["sectionListRenderer"]["subMenu"]?
|
||||||
|
|
||||||
|
# If the featured channel tabs lacks categories then that means the channel doesn't feature any other channels.
|
||||||
|
if !submenu
|
||||||
|
return [] of Category
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetches the fallback title.
|
||||||
|
submenu_data = submenu["channelSubMenuRenderer"]["contentTypeSubMenuItems"]
|
||||||
|
fallback_title = submenu_data.as_a.select(&.["selected"].as_bool)[0]["title"].as_s
|
||||||
|
|
||||||
|
items = extract_items(initial_data)
|
||||||
|
|
||||||
|
category_array = [] of Category
|
||||||
|
items.each do |category|
|
||||||
|
# The items can either be an Array of Categories or an Array of channels.
|
||||||
|
if !category.is_a?(Category)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
# category.title = category.title.empty? ? fallback_title : category.title
|
||||||
|
category_array << category
|
||||||
|
end
|
||||||
|
|
||||||
|
# If the returned data is only an array of channels then it means that the featured channel tab only has one category.
|
||||||
|
# This is due to the fact that InnerTube uses a "gridRenderer" (an array of items) when only one category is present.
|
||||||
|
# However, as the InnerTube result is a "gridRenderer" and not an "shelfRenderer", an object representing an
|
||||||
|
# category or section on youtube, we'll lack the category title. But, the good news is that the title of the category is still stored within the submenu
|
||||||
|
# which we fetched above. We can then use all of these values together to produce a Category object.
|
||||||
|
if category_array.empty?
|
||||||
|
category_array << Category.new({
|
||||||
|
title: fallback_title,
|
||||||
contents: items,
|
contents: items,
|
||||||
description_html: "",
|
description_html: "",
|
||||||
url: nil,
|
url: nil,
|
||||||
badges: nil,
|
badges: nil,
|
||||||
})], continuation_token
|
})
|
||||||
else
|
|
||||||
url = nil
|
|
||||||
if view && shelf_id
|
|
||||||
url = "/channel/#{ucid}/channels?view=#{view}&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
|
|
||||||
initial_data = request_youtube_api_browse(ucid, params)
|
|
||||||
continuation_token = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
channels_tab = extract_selected_tab(initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"])
|
|
||||||
submenu = channels_tab["content"]["sectionListRenderer"]["subMenu"]?
|
|
||||||
|
|
||||||
# There's no submenu data if the channel doesn't feature any channels.
|
|
||||||
if !submenu
|
|
||||||
return {[] of Category, continuation_token}
|
|
||||||
end
|
|
||||||
|
|
||||||
submenu_data = submenu["channelSubMenuRenderer"]["contentTypeSubMenuItems"]
|
|
||||||
|
|
||||||
items = extract_items(initial_data)
|
|
||||||
fallback_title = submenu_data.as_a.select(&.["selected"].as_bool)[0]["title"].as_s
|
|
||||||
|
|
||||||
# Although extract_items parsed everything into the right structs, we still have
|
|
||||||
# to fill in the title (if missing) attribute since Youtube doesn't return it when requesting
|
|
||||||
# a full category (initial)
|
|
||||||
category_array = [] of Category
|
|
||||||
items.each do |category|
|
|
||||||
# Tell compiler that the result from extract_items has to be an array of Categories
|
|
||||||
if !category.is_a?(Category)
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
category_array << Category.new({
|
|
||||||
title: category.title.empty? ? fallback_title : category.title,
|
|
||||||
contents: category.contents,
|
|
||||||
description_html: category.description_html,
|
|
||||||
url: category.url,
|
|
||||||
badges: nil,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
# If no categories has been parsed then it means two things.
|
|
||||||
# - We're currently viewing a specific channel category
|
|
||||||
# - We're currently requesting the continuation contents for that specific category.
|
|
||||||
# And since Youtube dyanmically loads more channels onto the page via JS, the data returned from InnerTube will only contains
|
|
||||||
# channels. Thus, we'll just go ahead and create one for the template to use.
|
|
||||||
if category_array.empty?
|
|
||||||
category_array << Category.new({
|
|
||||||
title: fallback_title,
|
|
||||||
contents: items,
|
|
||||||
description_html: "",
|
|
||||||
url: url,
|
|
||||||
badges: nil,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
return category_array, continuation_token
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return category_array
|
||||||
end
|
end
|
||||||
|
|
||||||
def produce_featured_channel_browse_param(view : Int64, shelf_id : Int64)
|
# Produces the InnerTube parameter for requesting the contents of a specific channel featuring category
|
||||||
|
private def produce_featured_channel_browse_param(view : Int64, shelf_id : Int64)
|
||||||
object = {
|
object = {
|
||||||
"2:string" => "channels",
|
"2:string" => "channels",
|
||||||
"4:varint" => view,
|
"4:varint" => view,
|
||||||
@ -90,3 +66,50 @@ def produce_featured_channel_browse_param(view : Int64, shelf_id : Int64)
|
|||||||
|
|
||||||
return browse_params
|
return browse_params
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Fetches the first set of channels from a selected channel featuring category
|
||||||
|
def fetch_selected_channel_featuring_category(ucid, view, shelf_id) : Tuple(Category, String | Nil)
|
||||||
|
category_url = "/channel/#{ucid}/channels?view=#{view}&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)
|
||||||
|
channels_tab = extract_selected_tab(initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"])
|
||||||
|
continuation_token = fetch_continuation_token(initial_data)
|
||||||
|
|
||||||
|
# Fetches the fallback title
|
||||||
|
submenu = channels_tab["content"]["sectionListRenderer"]["subMenu"]
|
||||||
|
submenu_data = submenu["channelSubMenuRenderer"]["contentTypeSubMenuItems"]
|
||||||
|
fallback_title = submenu_data.as_a.select(&.["selected"].as_bool)[0]["title"].as_s
|
||||||
|
|
||||||
|
items = extract_items(initial_data)
|
||||||
|
|
||||||
|
# Since the returned items from InnerTube is an array of channels, (See explanation at the end of the fetch_channel_featured_channels function)
|
||||||
|
# we lack the category title attribute. However, it is still stored as a submenu data which we fetched above. We can then use all of these
|
||||||
|
# values together to produce a Category object.
|
||||||
|
return Category.new({
|
||||||
|
title: fallback_title,
|
||||||
|
contents: items,
|
||||||
|
description_html: "",
|
||||||
|
url: category_url,
|
||||||
|
badges: nil,
|
||||||
|
}), continuation_token
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetches the next set of channels within the selected channel featuring category.
|
||||||
|
# Requires the continuation token and the query_title.
|
||||||
|
#
|
||||||
|
# TODO: The query_title here is really only used for frontend rendering.
|
||||||
|
# And since it's a URL parameter we should be able to just request it directly within the template files.
|
||||||
|
def fetch_channel_featured_channels_category_continuation(continuation, query_title) : Tuple(Category, String | Nil)
|
||||||
|
initial_data = request_youtube_api_browse(continuation)
|
||||||
|
items = extract_items(initial_data)
|
||||||
|
continuation_token = fetch_continuation_token(initial_data)
|
||||||
|
|
||||||
|
return Category.new({
|
||||||
|
title: query_title.not_nil!, # If continuation contents is requested then the query_title has to be passed along.
|
||||||
|
contents: items,
|
||||||
|
description_html: "",
|
||||||
|
url: nil,
|
||||||
|
badges: nil,
|
||||||
|
}), continuation_token
|
||||||
|
end
|
||||||
|
@ -116,36 +116,33 @@ class Invidious::Routes::Channels < Invidious::Routes::BaseRoute
|
|||||||
|
|
||||||
view = env.params.query["view"]?
|
view = env.params.query["view"]?
|
||||||
shelf_id = env.params.query["shelf_id"]?
|
shelf_id = env.params.query["shelf_id"]?
|
||||||
|
|
||||||
|
# The offset is mainly to check if we're at the first page or not and in turn whether we should have a "previous page" button or not.
|
||||||
|
offset = env.params.query["offset"]?
|
||||||
|
if offset
|
||||||
|
offset = offset.to_i
|
||||||
|
else
|
||||||
|
offset = 0
|
||||||
|
end
|
||||||
|
|
||||||
# Category title isn't returned when requesting a specific category or continuation data
|
# Category title isn't returned when requesting a specific category or continuation data
|
||||||
# so we have it in through a url param
|
# so we have it in through a url param
|
||||||
current_category_title = env.params.query["title"]?
|
current_category_title = env.params.query["title"]?
|
||||||
|
|
||||||
|
previous_continuation = env.params.query["previous"]?
|
||||||
|
|
||||||
if continuation
|
if continuation
|
||||||
offset = env.params.query["offset"]?
|
featured_channel_categories, continuation_token = fetch_channel_featured_channels_category_continuation(continuation, current_category_title)
|
||||||
if offset
|
|
||||||
offset = offset.to_i
|
|
||||||
else
|
|
||||||
offset = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# Previous continuation
|
|
||||||
previous_continuation = env.params.query["previous"]?
|
|
||||||
|
|
||||||
featured_channel_categories, continuation_token = fetch_channel_featured_channels(ucid, "EghjaGFubmVscw%3D%3D", nil, nil, continuation, current_category_title).not_nil!
|
|
||||||
elsif view && shelf_id
|
elsif view && shelf_id
|
||||||
offset = env.params.query["offset"]?
|
featured_channel_categories, continuation_token = fetch_selected_channel_featuring_category(ucid, view, shelf_id)
|
||||||
if offset
|
|
||||||
offset = offset.to_i
|
|
||||||
else
|
|
||||||
offset = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
featured_channel_categories, continuation_token = fetch_channel_featured_channels(ucid, "EghjaGFubmVscw%3D%3D", view, shelf_id, continuation, current_category_title).not_nil!
|
|
||||||
else
|
else
|
||||||
previous_continuation = nil
|
continuation_token = nil
|
||||||
offset = 0
|
featured_channel_categories = fetch_channel_featured_channels(ucid)
|
||||||
|
end
|
||||||
|
|
||||||
featured_channel_categories, continuation_token = fetch_channel_featured_channels(ucid, "EghjaGFubmVscw%3D%3D", nil, nil, current_category_title).not_nil!
|
# If we only got a single category we'll go ahead and wrap it within an array for easier processing in the template.
|
||||||
|
if featured_channel_categories.is_a? Category
|
||||||
|
featured_channel_categories = [featured_channel_categories]
|
||||||
end
|
end
|
||||||
|
|
||||||
templated "channel/featured_channels", buffer_footer: true
|
templated "channel/featured_channels", buffer_footer: true
|
||||||
|
@ -91,6 +91,7 @@
|
|||||||
|
|
||||||
<% if !featured_channel_categories.empty? %>
|
<% if !featured_channel_categories.empty? %>
|
||||||
<% base_url = "/channel/#{channel.ucid}/channels?view=#{view}&shelf_id=#{shelf_id}" %>
|
<% base_url = "/channel/#{channel.ucid}/channels?view=#{view}&shelf_id=#{shelf_id}" %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="pure-g h-box">
|
||||||
<div class="pure-u-1 pure-u-lg-1-5">
|
<div class="pure-u-1 pure-u-lg-1-5">
|
||||||
<% if previous_continuation %>
|
<% if previous_continuation %>
|
||||||
|
Loading…
Reference in New Issue
Block a user