diff --git a/assets/css/channel.css b/assets/css/channel.css index d4aefca8c..85d66a685 100644 --- a/assets/css/channel.css +++ b/assets/css/channel.css @@ -50,3 +50,70 @@ #link-widget-primary a:hover { color: #e1e1e1 !important; } + +/* Featured channels page */ + +.channel-section details { + margin: 20px; +} + +.channel-section details summary { + margin-bottom: 20px; +} + +.channel-section details summary::marker { + font-size: 1.3em; +} + +.category-heading { + font-size: 1.2em; + user-select: none; + display: inline; +} + +.section-contents .channel-profile { + text-align: center; +} + +/* Due to space constraints we'll make the special large featured channel display +only show up when the screen is wide enough */ + +@media screen and (min-width: 600px) { + .large-featured-channel.channel-profile { + /* We don't want the following attribute for large featured channels*/ + text-align: initial; + margin: initial; + + display: flex; + } + + .large-featured-channel.channel-profile img { + margin: 20% 0; + } + + .large-featured-channel .featured-channel-about { + margin-left: 2em; + } + + .large-featured-channel .featured-channel-title { + font-size: 1.2em; + margin-bottom: 10px + } + + .large-featured-channel .featured-channel-description { + margin-top: 10px; + font-weight: normal; + } +} + +/* Replicate the look for a normal featured channel */ +@media screen and (max-width: 600px) { + .large-featured-channel .seperator, .large-featured-channel .featured-channel-description { + display: none + } + + .large-featured-channel .featured-channel-metadata:last-child { + display: block; + margin-top: 1em; + } +} diff --git a/assets/css/default.css b/assets/css/default.css index 1c49a5d1d..79489c8cd 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -344,7 +344,7 @@ span > select { .light-theme a:hover, .light-theme a:active, -.light-theme summary:hover { +.light-theme .simulated_a:hover { color: #075A9E !important; } @@ -371,7 +371,7 @@ span > select { @media (prefers-color-scheme: light) { .no-theme a:hover, .no-theme a:active, - .no-theme summary:hover { + .no-theme .simulated_a:hover { color: #075A9E !important; } @@ -402,7 +402,7 @@ span > select { .dark-theme a:hover, .dark-theme a:active, -.dark-theme summary:hover { +.dark-theme .simulated_a:hover { color: rgb(0, 182, 240); } diff --git a/locales/ar.json b/locales/ar.json index 4655d3e67..18ed9f4c1 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/bn_BD.json b/locales/bn_BD.json index 66f8c1328..82fa94f36 100644 --- a/locales/bn_BD.json +++ b/locales/bn_BD.json @@ -367,5 +367,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/cs.json b/locales/cs.json index 415b1be73..85afb68bb 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -428,5 +428,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" -} \ No newline at end of file + "Links": "", + "This channel doesn't feature any other channels.": "" +} diff --git a/locales/da.json b/locales/da.json index 8084f5b0b..e3251a664 100644 --- a/locales/da.json +++ b/locales/da.json @@ -428,5 +428,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } \ No newline at end of file diff --git a/locales/de.json b/locales/de.json index 74f13dbde..3ed329f51 100644 --- a/locales/de.json +++ b/locales/de.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/el.json b/locales/el.json index 41db92560..a8c9310ce 100644 --- a/locales/el.json +++ b/locales/el.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/en-US.json b/locales/en-US.json index f3a2b4aff..020c22ee5 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -429,5 +429,6 @@ "Country: ": "Country: ", "Stats": "Stats", "Joined": "Joined", - "Links": "Links" + "Links": "Links", + "This channel doesn't feature any other channels.": "This channel doesn't feature any other channels." } diff --git a/locales/eo.json b/locales/eo.json index 3ad3b6884..7674c8e86 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/es.json b/locales/es.json index 8c2bfe12a..43f2e0a9d 100644 --- a/locales/es.json +++ b/locales/es.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/eu.json b/locales/eu.json index 2bc9d72ae..41d8bb1bf 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -350,5 +350,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/fa.json b/locales/fa.json index 2b982d6a7..0c7733a1b 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/fi.json b/locales/fi.json index 34ef090aa..fd763dfd5 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/fr.json b/locales/fr.json index ea63ce075..73c5aa1b2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/he.json b/locales/he.json index f0fff3991..adb1e00c7 100644 --- a/locales/he.json +++ b/locales/he.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/hr.json b/locales/hr.json index ef93856d3..620461291 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/hu-HU.json b/locales/hu-HU.json index 80ef3a71f..36804534c 100644 --- a/locales/hu-HU.json +++ b/locales/hu-HU.json @@ -349,5 +349,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/id.json b/locales/id.json index 7dc6a1cf1..b5aa4fa77 100644 --- a/locales/id.json +++ b/locales/id.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/is.json b/locales/is.json index 6794a8610..42e725175 100644 --- a/locales/is.json +++ b/locales/is.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/it.json b/locales/it.json index 6a32816b8..68910c4ca 100644 --- a/locales/it.json +++ b/locales/it.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/ja.json b/locales/ja.json index 6581472dc..31c818981 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/nb-NO.json b/locales/nb-NO.json index 7fe36918d..b8d8e1c37 100644 --- a/locales/nb-NO.json +++ b/locales/nb-NO.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/nl.json b/locales/nl.json index 0fbab8d51..851e686c9 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/pl.json b/locales/pl.json index fa540ea6e..4e045b1f5 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/pt-BR.json b/locales/pt-BR.json index 76f6f0cb5..438ad9401 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/pt-PT.json b/locales/pt-PT.json index ff3560e04..8d5840d99 100644 --- a/locales/pt-PT.json +++ b/locales/pt-PT.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/ro.json b/locales/ro.json index ba1a579e3..26500b3e2 100644 --- a/locales/ro.json +++ b/locales/ro.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/ru.json b/locales/ru.json index 2ae54d10a..fc89e6d0c 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/si.json b/locales/si.json index 4731ce46f..8d2e4d7c5 100644 --- a/locales/si.json +++ b/locales/si.json @@ -428,5 +428,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/sk.json b/locales/sk.json index 99229b219..89ddc2326 100644 --- a/locales/sk.json +++ b/locales/sk.json @@ -367,5 +367,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/sr.json b/locales/sr.json index 9e474cc89..70a3522fa 100644 --- a/locales/sr.json +++ b/locales/sr.json @@ -426,5 +426,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json index cc208c743..5253992ff 100644 --- a/locales/sr_Cyrl.json +++ b/locales/sr_Cyrl.json @@ -350,5 +350,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/sv-SE.json b/locales/sv-SE.json index 495c15036..8e60c2ac7 100644 --- a/locales/sv-SE.json +++ b/locales/sv-SE.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/tr.json b/locales/tr.json index f7687457f..7f84db235 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/uk.json b/locales/uk.json index f53698145..77b25e3df 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 19a6711c1..f1ef71b6f 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 3bbb04a45..8b8ac5495 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -429,5 +429,6 @@ "Country: ": "", "Stats": "", "Joined": "", - "Links": "" + "Links": "", + "This channel doesn't feature any other channels.": "" } diff --git a/src/invidious.cr b/src/invidious.cr index 135f5c8c7..b74490b34 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -312,6 +312,8 @@ Invidious::Routing.get "/channel/:ucid", Invidious::Routes::Channels, :home Invidious::Routing.get "/channel/:ucid/videos", Invidious::Routes::Channels, :videos 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 636d5a310..8b03a6f2c 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -135,7 +135,7 @@ struct AboutChannel property is_family_friendly : Bool property allowed_regions : Array(String) property related_channels : Array(AboutRelatedChannel) - property tabs : Hash(String, String) + property tabs : Hash(String, Tuple(Int32, String)) # TabName => {TabiZZndex, browseEndpoint params} property links : Array(Tuple(String, String, String)) end @@ -380,6 +380,27 @@ 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, title = nil ) + if continuation.is_a?(String) + initial_data = request_youtube_api_browse(continuation) + channels_tab_content = initial_data["onResponseReceivedActions"][0]["appendContinuationItemsAction"]["continuationItems"] + + return process_featured_channels([channels_tab_content,], nil, title, continuation_items=true) + else + if params.is_a?(String) + initial_data = request_youtube_api_browse(ucid, params) + else + initial_data = request_youtube_api_browse(ucid, tab_data[1]) + end + + channels_tab = initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"][tab_data[0]]["tabRenderer"] + channels_tab_content = channels_tab["content"]["sectionListRenderer"]["contents"].as_a + submenu_data = channels_tab["content"]["sectionListRenderer"]["subMenu"]?.try &.["channelSubMenuRenderer"]["contentTypeSubMenuItems"] || false + + return process_featured_channels(channels_tab_content, submenu_data) + end +end + def produce_channel_videos_continuation(ucid, page = 1, auto_generated = nil, sort_by = "newest", v2 = false) object = { "80226972:embedded" => { @@ -887,13 +908,16 @@ def get_about_info(ucid, locale) country = "" total_views = 0_i64 joined = Time.unix(0) - tabs = {} of String => String # TabName => browseEndpoint params + tabs = {} of String => Tuple(Int32, String) # TabName => {TabiZZndex, browseEndpoint params} links = [] of {String, String, String} tabs_json = initdata["contents"]["twoColumnBrowseResultsRenderer"]["tabs"]?.try &.as_a? + tab_names = [] of String + tab_data = [] of Tuple(Int32, String) + if !tabs_json.nil? # Retrieve information from the tabs array. The index we are looking for varies between channels. - tabs_json.each do |node| + tabs_json.each_with_index do |node, i| # Try to find the about section which is located in only one of the tabs. channel_about_meta = node["tabRenderer"]?.try &.["content"]?.try &.["sectionListRenderer"]? .try &.["contents"]?.try &.[0]?.try &.["itemSectionRenderer"]?.try &.["contents"]? @@ -935,10 +959,14 @@ def get_about_info(ucid, locale) auto_generated = true end end + + if node["tabRenderer"]? + tab_names << node["tabRenderer"]["title"].as_s.downcase + tab_data << {i, node["tabRenderer"]["endpoint"]["browseEndpoint"]["params"].as_s} + end + end - tab_names = tabs_json.reject { |node| node["tabRenderer"]?.nil? }.map { |node| node["tabRenderer"]["title"].as_s.downcase } - browse_endpoint_param = tabs_json.reject { |node| node["tabRenderer"]?.nil? }.map { |node| node["tabRenderer"]["endpoint"]["browseEndpoint"]["params"].as_s } - tabs = Hash.zip(tab_names, browse_endpoint_param) + tabs = Hash.zip(tab_names, tab_data) end sub_count = initdata["header"]["c4TabbedHeaderRenderer"]?.try &.["subscriberCountText"]?.try &.["simpleText"]?.try &.as_s? diff --git a/src/invidious/featured_channels.cr b/src/invidious/featured_channels.cr new file mode 100644 index 000000000..0b44aeaef --- /dev/null +++ b/src/invidious/featured_channels.cr @@ -0,0 +1,170 @@ +struct FeaturedChannel + include DB::Serializable + + property author : String + property ucid : String + property author_thumbnail : String + property subscriber_count : Int32 + property video_count : Int32 + property description_html : String? + + def to_json(locale, json : JSON::Builder) + json.object do + json.field "author", self.author + json.field "authorId", self.ucid + json.field "authorUrl", "/channel/#{self.ucid}" + json.field "authorThumbnails" do + json.array do + qualities = {32, 48, 76, 100, 176, 512} + + qualities.each do |quality| + json.object do + json.field "url", self.author_thumbnail.gsub(/=\d+/, "=s#{quality}") + json.field "width", quality + json.field "height", quality + end + end + end + end + + json.field "description", html_to_content(self.description_html) + json.field "descriptionHtml", self.description_html + json.field "subCount", self.subscriber_count + json.field "videoCount", self.video_count + json.field "badges", self.badges + end + end + + def to_json(locale, json : JSON::Builder | Nil = nil) + if json + to_json(locale, json) + else + JSON.build do |json| + to_json(locale, json) + end + end + end +end + +struct Category + include DB::Serializable + + property title : String + property contents : Array(FeaturedChannel) | FeaturedChannel + property browse_endpoint_param : String? + property continuation_token : String? + + def to_json(locale, json : JSON::Builder) + json.object do + json.field "title", self.title + json.field "contents", self.contents + end + end + + def to_json(locale, json : JSON::Builder | Nil = nil) + if json + to_json(locale, json) + else + JSON.build do |json| + to_json(locale, json) + end + end + end +end + +def _extract_channel_data(channel) + ucid = channel["channelId"].as_s + author = channel["title"]["simpleText"].as_s + author_thumbnail = channel["thumbnail"]["thumbnails"].as_a[0]["url"].as_s + subscriber_count = channel["subscriberCountText"]?.try &.["simpleText"]?.try &.as_s? + .try { |text| short_text_to_number(text.split(" ")[0]) } || 0 + + video_count = channel["videoCountText"]?.try &.["runs"][0]["text"].as_s.gsub(/\D/, "").to_i || 0 + + if channel["descriptionSnippet"]? + description = channel["descriptionSnippet"]["runs"][0]["text"].as_s + description_html = HTML.escape(description).gsub("\n", "") + else + description_html = nil + end + + FeaturedChannel.new({ + author: author, + ucid: ucid, + author_thumbnail: author_thumbnail, + subscriber_count: subscriber_count, + video_count: video_count, + description_html: description_html + }) +end + +def process_featured_channels(data, submenu_data, title=nil, continuation_items=false) + all_categories = [] of Category + + if submenu_data.is_a?(Bool) + return all_categories + end + + # Extraction process differs when there's more than one category + if data.size > 1 + data.each do |raw_category| + raw_category = raw_category["itemSectionRenderer"]["contents"].as_a[0]["shelfRenderer"] + + category_title = raw_category["title"]["runs"][0]["text"].as_s + browse_endpoint_param = raw_category["endpoint"]["browseEndpoint"]["params"].as_s + + # Category has multiple channels + if raw_category["content"].as_h.has_key?("horizontalListRenderer") + contents = [] of FeaturedChannel + raw_category["content"]["horizontalListRenderer"]["items"].as_a.each do |channel| + contents << _extract_channel_data(channel["gridChannelRenderer"]) + end + # Single channel + else + channel = raw_category["content"]["expandedShelfContentsRenderer"]["items"][0]["channelRenderer"] + contents = _extract_channel_data(channel) + end + + all_categories << Category.new({ + title: category_title, + contents: contents, + browse_endpoint_param: browse_endpoint_param, + continuation_token: nil + }) + end + else + if !continuation_items + raw_category_contents = data[0]["itemSectionRenderer"]["contents"].as_a[0]["gridRenderer"]["items"].as_a + else + raw_category_contents = data[0].as_a + end + + category_title = submenu_data.try &.[0]["title"].as_s || title || "" + + browse_endpoint_param = nil # Not needed + continuation_token = nil + + # If a continuation token is needed it'll always be after at least twelve channels + if raw_category_contents.size > 12 + continuation_token = raw_category_contents[-1]["continuationItemRenderer"]?.try &.["continuationEndpoint"]["continuationCommand"]["token"].as_s || nil + + if !continuation_token.nil? + raw_category_contents = raw_category_contents[0..-2] + end + end + + contents = [] of FeaturedChannel + raw_category_contents.each do |channel| + contents << _extract_channel_data(channel["gridChannelRenderer"]) + end + + all_categories << Category.new({ + title: category_title, + contents: contents, + browse_endpoint_param: browse_endpoint_param, + continuation_token: continuation_token + }) + end + + return all_categories +end diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index 7fa5a5e46..bf38b12e8 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -91,6 +91,69 @@ class Invidious::Routes::Channels < Invidious::Routes::BaseRoute templated "community" end + def channels(env) + data = self.fetch_basic_information(env) + if !data.is_a?(Tuple) + return data + end + locale, user, subscriptions, continuation, ucid, channel = data + + if !channel.tabs.has_key?("channels") + return env.redirect "/channel/#{channel.ucid}" + end + + # When a channel only has a single category it lacks the category param option so we'll handle it here. + if continuation + offset = env.params.query["offset"]? + if offset + offset = offset.to_i + else + offset = 0 + end + + # 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! + 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! + end + + templated "channels" + 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 "channels" + end + def about(env) data = self.fetch_basic_information(env) if !data.is_a?(Tuple) diff --git a/src/invidious/views/channel_about.ecr b/src/invidious/views/channel_about.ecr index 6912244f1..69e276489 100644 --- a/src/invidious/views/channel_about.ecr +++ b/src/invidious/views/channel_about.ecr @@ -3,7 +3,7 @@ <% end %> -<% content_type = 4 %> +<% content_type = 5 %> <% sort_options = Tuple.new %> <%= rendered "components/channel-information" %> diff --git a/src/invidious/views/channels.ecr b/src/invidious/views/channels.ecr new file mode 100644 index 000000000..6aeedc03f --- /dev/null +++ b/src/invidious/views/channels.ecr @@ -0,0 +1,115 @@ +<% content_for "header" do %> +<%= channel.author %> - Invidious + +<% end %> + +<% content_type = 4 %> +<% sort_options = Tuple.new %> +<%= rendered "components/channel-information" %> + +
+ <% if !featured_channel_categories.empty? %> + <% featured_channel_categories.each do | category | %> +
+
+ +

+ <% if (category_request_param = category.browse_endpoint_param).is_a?(String) %> + + <%= category.title %> + + <%else%> + <%= category.title %> + <%end%> +

+
+ <% contents = category.contents%> +
+ <% if contents.is_a?(Array(FeaturedChannel)) %> + <% contents.each do |item|%> +
+ + <% if !env.get("preferences").as(Preferences).thin_mode %> + "/> + <% end %> + + +
+ <%end%> + <% elsif contents.is_a?(FeaturedChannel) %> + <%item = contents %> + +
+
+ <% end %> + <% else %> +

+ <%= translate(locale, "This channel doesn't feature any other channels.")%> +

+ <% end %> + + +
+ +
+
+ <% if previous_continuation %> + + <%= translate(locale, "Previous page") %> + + <% elsif (offset - 1) == 0 %> + + <%= translate(locale, "Previous page") %> + + <% end %> +
+
+
+ <% if (next_cont_token = featured_channel_categories[0].continuation_token) %> + <% additional_url_param = ""%> + <% if continuation %> + <% additional_url_param = "&previous=#{HTML.escape(continuation)}"%> + <%end %> + <% if !title %> + <% title = featured_channel_categories[0].title %> + <%end %> + + + + <%= translate(locale, "Next page") %> + + <% end %> +
+
diff --git a/src/invidious/views/components/channel-information.ecr b/src/invidious/views/components/channel-information.ecr index 501896da1..1e484ec5b 100644 --- a/src/invidious/views/components/channel-information.ecr +++ b/src/invidious/views/components/channel-information.ecr @@ -107,6 +107,20 @@ <% end %> <% if content_type == 4 %> +
  • + + <%= translate(locale, "Channels") %> + +
  • + <% else %> +
  • + + <%= translate(locale, "Channels") %> + +
  • + <% end %> + + <% if content_type == 5 %>
  • <%= translate(locale, "About") %> diff --git a/src/invidious/views/search.ecr b/src/invidious/views/search.ecr index 38d677aba..6afbee953 100644 --- a/src/invidious/views/search.ecr +++ b/src/invidious/views/search.ecr @@ -9,7 +9,7 @@ <% else %>
    - +

    <%= translate(locale, "filter") %>