From d1962c9f216cdc820b3e95d836a8dfbf302cbb91 Mon Sep 17 00:00:00 2001 From: EazyHood Date: Wed, 2 Sep 2026 10:50:36 -0500 Subject: [PATCH] channels: complete carousel header review fixes Fall back to the ucid-based channel URL when a legacy auto-generated header omits urlCanonical, and cover it with a focused regression spec. Remove the unrelated selected-tab guard and its spec so this change remains scoped to auto-generated channel headers. --- spec/invidious/channels/about_spec.cr | 21 +++++++++++++++++++ spec/invidious/yt_backend/extractors_spec.cr | 22 -------------------- src/invidious/channels/about.cr | 3 ++- src/invidious/yt_backend/extractors.cr | 3 +-- 4 files changed, 24 insertions(+), 25 deletions(-) delete mode 100644 spec/invidious/yt_backend/extractors_spec.cr diff --git a/spec/invidious/channels/about_spec.cr b/spec/invidious/channels/about_spec.cr index 55936cbe4..abac6e2ad 100644 --- a/spec/invidious/channels/about_spec.cr +++ b/spec/invidious/channels/about_spec.cr @@ -200,6 +200,27 @@ Spectator.describe "extract_auto_generated_channel_header" do expect(header[:tags]).to eq(["Gaming"]) end + it "falls back to the channel URL when the canonical URL is missing" do + initdata = JSON.parse(<<-JSON).as_h + { + "header": { + "interactiveTabbedHeaderRenderer": { + "title": {"simpleText": "Legacy gaming"} + } + }, + "microformat": { + "microformatDataRenderer": { + "familySafe": true + } + } + } + JSON + + header = extract_auto_generated_channel_header(initdata, "UCMissingCanonical") + + expect(header[:author_url]).to eq("https://www.youtube.com/channel/UCMissingCanonical") + end + it "keeps an explicit familySafe: false" do initdata = JSON.parse(<<-JSON).as_h { diff --git a/spec/invidious/yt_backend/extractors_spec.cr b/spec/invidious/yt_backend/extractors_spec.cr deleted file mode 100644 index a4c2dc0aa..000000000 --- a/spec/invidious/yt_backend/extractors_spec.cr +++ /dev/null @@ -1,22 +0,0 @@ -require "../../parsers_helper" - -Spectator.describe "YouTubeTabs" do - it "treats a selected tab without content as empty" do - initdata = JSON.parse(<<-JSON).as_h - { - "contents": { - "twoColumnBrowseResultsRenderer": { - "tabs": [ - {"tabRenderer": {"selected": true}} - ] - } - } - } - JSON - - items, continuation = extract_items(initdata) - - expect(items).to be_empty - expect(continuation).to be_nil - end -end diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr index ece26b393..4677f08d5 100644 --- a/src/invidious/channels/about.cr +++ b/src/invidious/channels/about.cr @@ -42,7 +42,8 @@ def extract_auto_generated_channel_header(initdata : Hash(String, JSON::Any), uc if header = initdata.dig?("header", "interactiveTabbedHeaderRenderer") author = header.dig("title", "simpleText").as_s - author_url = initdata.dig("microformat", "microformatDataRenderer", "urlCanonical").as_s + author_url = initdata.dig?("microformat", "microformatDataRenderer", "urlCanonical").try &.as_s? + author_url ||= "https://www.youtube.com/channel/#{ucid}" author_thumbnail = header.dig?("boxArt", "thumbnails", 0, "url").try &.as_s || "" banner = header.dig?("banner", "thumbnails").try &.[-1]?.try &.["url"].as_s? diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index 72c3e1441..b2226e74d 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -964,8 +964,7 @@ private module Extractors private def self.extract(target) raw_items = [] of JSON::Any - content = extract_selected_tab(target["tabs"])["content"]? - return raw_items if content.nil? + content = extract_selected_tab(target["tabs"])["content"] if section_list_contents = content.dig?("sectionListRenderer", "contents") raw_items = unpack_section_list(section_list_contents)