mirror of
https://github.com/iv-org/invidious.git
synced 2026-09-06 00:52:45 -05:00
channels: handle null topic metadata safely
This commit is contained in:
parent
e86cd8a777
commit
9432cd048f
@ -254,6 +254,23 @@ Spectator.describe "extract_auto_generated_channel_header" do
|
||||
expect(header[:is_family_friendly]).to be_false
|
||||
end
|
||||
|
||||
it "defaults an explicit familySafe: null to true" do
|
||||
initdata = JSON.parse(<<-JSON).as_h
|
||||
{
|
||||
"header": {
|
||||
"pageHeaderRenderer": {"pageTitle": "Gaming"}
|
||||
},
|
||||
"microformat": {
|
||||
"microformatDataRenderer": {"familySafe": null}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
header = extract_auto_generated_channel_header(initdata, "UCNullFamilySafe")
|
||||
|
||||
expect(header[:is_family_friendly]).to be_true
|
||||
end
|
||||
|
||||
it "raises when the header shape is unknown" do
|
||||
initdata = JSON.parse(<<-JSON).as_h
|
||||
{
|
||||
@ -298,6 +315,40 @@ Spectator.describe "extract_topic_channel_details" do
|
||||
expect(extract_topic_channel_details(initdata)).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil when carousel contents are explicitly null" do
|
||||
initdata = JSON.parse(<<-JSON).as_h
|
||||
{
|
||||
"header": {
|
||||
"carouselHeaderRenderer": {
|
||||
"contents": null
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
expect(extract_topic_channel_details(initdata)).to be_nil
|
||||
end
|
||||
|
||||
it "skips an explicitly null topic renderer before a valid one" do
|
||||
initdata = JSON.parse(<<-JSON).as_h
|
||||
{
|
||||
"header": {
|
||||
"carouselHeaderRenderer": {
|
||||
"contents": [
|
||||
{"topicChannelDetailsRenderer": null},
|
||||
{"topicChannelDetailsRenderer": {"title": {"simpleText": "Sports"}}}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
details = extract_topic_channel_details(initdata)
|
||||
|
||||
expect(details).not_to be_nil
|
||||
expect(details.not_nil!.dig("title", "simpleText").as_s).to eq("Sports")
|
||||
end
|
||||
|
||||
it "exposes the subscriber count carried by the subtitle" do
|
||||
# `subscriberCountText` is part of the renderer but comes back null, so the
|
||||
# count is only available as free text in the subtitle.
|
||||
@ -322,11 +373,6 @@ Spectator.describe "extract_topic_channel_details" do
|
||||
}
|
||||
JSON
|
||||
|
||||
details = extract_topic_channel_details(initdata)
|
||||
|
||||
expect(details).not_to be_nil
|
||||
sub_text = details.not_nil!.dig("subtitle", "simpleText").as_s
|
||||
expect(sub_text).to eq("74.3M subscribers")
|
||||
expect(short_text_to_number(sub_text.split(" ")[0])).to eq(74_300_000_i64)
|
||||
expect(extract_topic_channel_subscriber_count(initdata)).to eq(74_300_000)
|
||||
end
|
||||
end
|
||||
|
||||
@ -24,12 +24,29 @@ record AboutChannel,
|
||||
# rather than by index.
|
||||
# ex: https://www.youtube.com/channel/UCEgdi0XIXXZ-qJOFPf4JSKw
|
||||
def extract_topic_channel_details(initdata : Hash(String, JSON::Any)) : JSON::Any?
|
||||
contents = initdata.dig?("header", "carouselHeaderRenderer", "contents")
|
||||
return nil if contents.nil?
|
||||
contents = initdata.dig?("header", "carouselHeaderRenderer", "contents").try &.as_a?
|
||||
return nil unless contents
|
||||
|
||||
contents.as_a
|
||||
.find { |content| !content.dig?("topicChannelDetailsRenderer").nil? }
|
||||
.try &.dig?("topicChannelDetailsRenderer")
|
||||
contents.each do |content|
|
||||
details = content.dig?("topicChannelDetailsRenderer")
|
||||
return details if details.try &.as_h?
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def extract_topic_channel_subscriber_count(initdata : Hash(String, JSON::Any)) : Int32
|
||||
details = extract_topic_channel_details(initdata)
|
||||
return 0 unless details
|
||||
|
||||
sub_text = details.dig?("subscriberCountText", "simpleText").try &.as_s?
|
||||
unless sub_text.try &.includes?("subscriber")
|
||||
sub_text = details.dig?("subtitle", "simpleText").try &.as_s?
|
||||
end
|
||||
|
||||
return 0 unless sub_text.try &.includes?("subscriber")
|
||||
|
||||
short_text_to_number(sub_text.split(" ")[0]).to_i32
|
||||
end
|
||||
|
||||
# Auto-generated channels come with one of three header shapes. This is only
|
||||
@ -96,7 +113,7 @@ def extract_auto_generated_channel_header(initdata : Hash(String, JSON::Any), uc
|
||||
|
||||
# `microformat` is absent from these payloads, so a missing flag defaults to
|
||||
# safe. An explicit `false` is still preserved.
|
||||
family_safe = initdata.dig?("microformat", "microformatDataRenderer", "familySafe").try(&.as_bool)
|
||||
family_safe = initdata.dig?("microformat", "microformatDataRenderer", "familySafe").try &.as_bool?
|
||||
|
||||
{
|
||||
author: author,
|
||||
@ -265,18 +282,8 @@ def get_about_info(ucid) : AboutChannel
|
||||
|
||||
break if sub_count != 0 && !pronouns.nil?
|
||||
end
|
||||
elsif (topic_details = extract_topic_channel_details(initdata))
|
||||
# Topic channels carry the subscriber count as free text in the subtitle,
|
||||
# ex: "74.3M subscribers". `subscriberCountText` is part of the same
|
||||
# renderer but comes back null, so it is only used as a first choice.
|
||||
sub_text = topic_details.dig?("subscriberCountText", "simpleText").try &.as_s
|
||||
unless sub_text.try &.includes?("subscriber")
|
||||
sub_text = topic_details.dig?("subtitle", "simpleText").try &.as_s
|
||||
end
|
||||
|
||||
if sub_text && sub_text.includes?("subscriber")
|
||||
sub_count = short_text_to_number(sub_text.split(" ")[0]).to_i32
|
||||
end
|
||||
elsif initdata.dig?("header", "carouselHeaderRenderer")
|
||||
sub_count = extract_topic_channel_subscriber_count(initdata)
|
||||
end
|
||||
|
||||
AboutChannel.new(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user