channels: handle incomplete auto-generated payloads

This commit is contained in:
EazyHood 2026-08-02 13:10:56 -05:00
parent 30b9870cb9
commit c81452f2a4
4 changed files with 57 additions and 7 deletions

View File

@ -69,7 +69,7 @@ Spectator.describe "extract_auto_generated_channel_header" do
expect(header[:author_thumbnail]).to eq("//yt3.example/first") expect(header[:author_thumbnail]).to eq("//yt3.example/first")
end end
it "falls back to the ucid when the carousel carries no topic details" do it "raises when the carousel carries no topic details" do
initdata = JSON.parse(<<-JSON).as_h initdata = JSON.parse(<<-JSON).as_h
{ {
"header": { "header": {
@ -82,10 +82,31 @@ Spectator.describe "extract_auto_generated_channel_header" do
} }
JSON JSON
header = extract_auto_generated_channel_header(initdata, "UCEgdi0XIXXZ-qJOFPf4JSKw") expect do
extract_auto_generated_channel_header(initdata, "UCEgdi0XIXXZ-qJOFPf4JSKw")
end.to raise_error(InfoException)
end
expect(header[:author]).to eq("UCEgdi0XIXXZ-qJOFPf4JSKw") it "raises when the carousel topic details have no title" do
expect(header[:author_thumbnail]).to eq("") initdata = JSON.parse(<<-JSON).as_h
{
"header": {
"carouselHeaderRenderer": {
"contents": [
{
"topicChannelDetailsRenderer": {
"avatar": {"thumbnails": [{"url": "//yt3.example/avatar"}]}
}
}
]
}
}
}
JSON
expect do
extract_auto_generated_channel_header(initdata, "UCEgdi0XIXXZ-qJOFPf4JSKw")
end.to raise_error(InfoException)
end end
it "parses the current pageHeaderRenderer shape" do it "parses the current pageHeaderRenderer shape" do

View File

@ -0,0 +1,22 @@
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

View File

@ -75,9 +75,15 @@ def extract_auto_generated_channel_header(initdata : Hash(String, JSON::Any), uc
# This shape carries neither a banner nor a description. # This shape carries neither a banner nor a description.
details = extract_topic_channel_details(initdata) details = extract_topic_channel_details(initdata)
author = details.try &.dig?("title", "simpleText").try &.as_s || ucid unless details
raise InfoException.new("Could not extract the carousel header of channel #{ucid}")
end
author = details.dig?("title", "simpleText").try &.as_s
author ||= raise InfoException.new("Could not extract the carousel title of channel #{ucid}")
author_url = "https://www.youtube.com/channel/#{ucid}" author_url = "https://www.youtube.com/channel/#{ucid}"
author_thumbnail = details.try &.dig?("avatar", "thumbnails", 0, "url").try &.as_s || "" author_thumbnail = details.dig?("avatar", "thumbnails", 0, "url").try &.as_s
author_thumbnail ||= raise InfoException.new("Could not extract the carousel thumbnail of channel #{ucid}")
else else
raise InfoException.new("Could not extract the header of channel #{ucid}") raise InfoException.new("Could not extract the header of channel #{ucid}")
end end

View File

@ -964,7 +964,8 @@ private module Extractors
private def self.extract(target) private def self.extract(target)
raw_items = [] of JSON::Any raw_items = [] of JSON::Any
content = extract_selected_tab(target["tabs"])["content"] content = extract_selected_tab(target["tabs"])["content"]?
return raw_items if content.nil?
if section_list_contents = content.dig?("sectionListRenderer", "contents") if section_list_contents = content.dig?("sectionListRenderer", "contents")
raw_items = unpack_section_list(section_list_contents) raw_items = unpack_section_list(section_list_contents)