mirror of
https://github.com/iv-org/invidious.git
synced 2026-09-06 17:12: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
|
expect(header[:is_family_friendly]).to be_false
|
||||||
end
|
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
|
it "raises when the header shape is unknown" do
|
||||||
initdata = JSON.parse(<<-JSON).as_h
|
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
|
expect(extract_topic_channel_details(initdata)).to be_nil
|
||||||
end
|
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
|
it "exposes the subscriber count carried by the subtitle" do
|
||||||
# `subscriberCountText` is part of the renderer but comes back null, so the
|
# `subscriberCountText` is part of the renderer but comes back null, so the
|
||||||
# count is only available as free text in the subtitle.
|
# count is only available as free text in the subtitle.
|
||||||
@ -322,11 +373,6 @@ Spectator.describe "extract_topic_channel_details" do
|
|||||||
}
|
}
|
||||||
JSON
|
JSON
|
||||||
|
|
||||||
details = extract_topic_channel_details(initdata)
|
expect(extract_topic_channel_subscriber_count(initdata)).to eq(74_300_000)
|
||||||
|
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -24,12 +24,29 @@ record AboutChannel,
|
|||||||
# rather than by index.
|
# rather than by index.
|
||||||
# ex: https://www.youtube.com/channel/UCEgdi0XIXXZ-qJOFPf4JSKw
|
# ex: https://www.youtube.com/channel/UCEgdi0XIXXZ-qJOFPf4JSKw
|
||||||
def extract_topic_channel_details(initdata : Hash(String, JSON::Any)) : JSON::Any?
|
def extract_topic_channel_details(initdata : Hash(String, JSON::Any)) : JSON::Any?
|
||||||
contents = initdata.dig?("header", "carouselHeaderRenderer", "contents")
|
contents = initdata.dig?("header", "carouselHeaderRenderer", "contents").try &.as_a?
|
||||||
return nil if contents.nil?
|
return nil unless contents
|
||||||
|
|
||||||
contents.as_a
|
contents.each do |content|
|
||||||
.find { |content| !content.dig?("topicChannelDetailsRenderer").nil? }
|
details = content.dig?("topicChannelDetailsRenderer")
|
||||||
.try &.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
|
end
|
||||||
|
|
||||||
# Auto-generated channels come with one of three header shapes. This is only
|
# 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
|
# `microformat` is absent from these payloads, so a missing flag defaults to
|
||||||
# safe. An explicit `false` is still preserved.
|
# 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,
|
author: author,
|
||||||
@ -265,18 +282,8 @@ def get_about_info(ucid) : AboutChannel
|
|||||||
|
|
||||||
break if sub_count != 0 && !pronouns.nil?
|
break if sub_count != 0 && !pronouns.nil?
|
||||||
end
|
end
|
||||||
elsif (topic_details = extract_topic_channel_details(initdata))
|
elsif initdata.dig?("header", "carouselHeaderRenderer")
|
||||||
# Topic channels carry the subscriber count as free text in the subtitle,
|
sub_count = extract_topic_channel_subscriber_count(initdata)
|
||||||
# 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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
AboutChannel.new(
|
AboutChannel.new(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user