mirror of
https://github.com/iv-org/invidious.git
synced 2026-07-07 06:06:48 -05:00
fix(playlists): fall back to playlistHeaderRenderer when sidebar is absent
YouTube stopped returning sidebar.playlistSidebarRenderer for many playlists; extract the same fields from header.playlistHeaderRenderer instead to prevent 500 errors on /playlist pages. Also fix the -1 video count on lockupViewModel playlist cards: make badge text matching case-insensitive and add a numeric-prefix fallback so counts are recovered even when YouTube changes the badge wording.
This commit is contained in:
parent
347f84c276
commit
b426f74ba5
@ -345,36 +345,20 @@ def fetch_playlist(plid : String)
|
||||
initial_data = YoutubeAPI.browse("VL" + plid, params: "")
|
||||
|
||||
playlist_sidebar_renderer = initial_data.dig?("sidebar", "playlistSidebarRenderer", "items")
|
||||
raise InfoException.new("Could not extract playlistSidebarRenderer.") if !playlist_sidebar_renderer
|
||||
header_renderer = initial_data.dig?("header", "playlistHeaderRenderer")
|
||||
|
||||
playlist_info = playlist_sidebar_renderer.dig?(0, "playlistSidebarPrimaryInfoRenderer")
|
||||
raise InfoException.new("Could not extract playlist info") if !playlist_info
|
||||
|
||||
title = playlist_info.dig?("title", "runs", 0, "text").try &.as_s || ""
|
||||
|
||||
desc_item = playlist_info["description"]?
|
||||
|
||||
description_txt = desc_item.try &.["runs"]?.try &.as_a
|
||||
.map(&.["text"].as_s).join("") || desc_item.try &.["simpleText"]?.try &.as_s || ""
|
||||
|
||||
description_html = desc_item.try &.["runs"]?.try &.as_a
|
||||
.try { |run| content_to_comment_html(run).try &.to_s } || "<p></p>"
|
||||
|
||||
thumbnail = playlist_info.dig?(
|
||||
"thumbnailRenderer", "playlistVideoThumbnailRenderer",
|
||||
"thumbnail", "thumbnails", 0, "url"
|
||||
).try &.as_s || playlist_info.dig?(
|
||||
"thumbnailRenderer", "playlistCustomThumbnailRenderer",
|
||||
"thumbnail", "thumbnails", 0, "url"
|
||||
).try &.as_s
|
||||
raise InfoException.new("Could not extract playlist data.") if playlist_sidebar_renderer.nil? && header_renderer.nil?
|
||||
|
||||
views = 0_i64
|
||||
updated = Time.utc
|
||||
video_count = 0
|
||||
subtitle = extract_text(header_renderer.try &.["subtitle"]?)
|
||||
|
||||
subtitle = extract_text(initial_data.dig?("header", "playlistHeaderRenderer", "subtitle"))
|
||||
# stats parsing is the same format in both sidebar and header renderers
|
||||
stats_source = playlist_sidebar_renderer.try(&.dig?(0, "playlistSidebarPrimaryInfoRenderer", "stats")) ||
|
||||
header_renderer.try(&.["stats"]?)
|
||||
|
||||
playlist_info["stats"]?.try &.as_a.each do |stat|
|
||||
stats_source.try &.as_a.each do |stat|
|
||||
text = stat["runs"]?.try &.as_a.map(&.["text"].as_s).join("") || stat["simpleText"]?.try &.as_s
|
||||
next if !text
|
||||
|
||||
@ -389,20 +373,63 @@ def fetch_playlist(plid : String)
|
||||
end
|
||||
end
|
||||
|
||||
if playlist_sidebar_renderer.size < 2
|
||||
author = ""
|
||||
author_thumbnail = ""
|
||||
ucid = ""
|
||||
if playlist_sidebar_renderer
|
||||
playlist_info = playlist_sidebar_renderer.dig?(0, "playlistSidebarPrimaryInfoRenderer")
|
||||
raise InfoException.new("Could not extract playlist info") if !playlist_info
|
||||
|
||||
title = playlist_info.dig?("title", "runs", 0, "text").try &.as_s || ""
|
||||
|
||||
desc_item = playlist_info["description"]?
|
||||
|
||||
description_txt = desc_item.try &.["runs"]?.try &.as_a
|
||||
.map(&.["text"].as_s).join("") || desc_item.try &.["simpleText"]?.try &.as_s || ""
|
||||
|
||||
description_html = desc_item.try &.["runs"]?.try &.as_a
|
||||
.try { |run| content_to_comment_html(run).try &.to_s } || "<p></p>"
|
||||
|
||||
thumbnail = playlist_info.dig?(
|
||||
"thumbnailRenderer", "playlistVideoThumbnailRenderer",
|
||||
"thumbnail", "thumbnails", 0, "url"
|
||||
).try &.as_s || playlist_info.dig?(
|
||||
"thumbnailRenderer", "playlistCustomThumbnailRenderer",
|
||||
"thumbnail", "thumbnails", 0, "url"
|
||||
).try &.as_s
|
||||
|
||||
if playlist_sidebar_renderer.size < 2
|
||||
author = ""
|
||||
author_thumbnail = ""
|
||||
ucid = ""
|
||||
else
|
||||
author_info = playlist_sidebar_renderer[1].dig?(
|
||||
"playlistSidebarSecondaryInfoRenderer", "videoOwner", "videoOwnerRenderer"
|
||||
)
|
||||
|
||||
raise InfoException.new("Could not extract author info") if !author_info
|
||||
|
||||
author = author_info.dig?("title", "runs", 0, "text").try &.as_s || ""
|
||||
author_thumbnail = author_info.dig?("thumbnail", "thumbnails", 0, "url").try &.as_s || ""
|
||||
ucid = author_info.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
|
||||
end
|
||||
else
|
||||
author_info = playlist_sidebar_renderer[1].dig?(
|
||||
"playlistSidebarSecondaryInfoRenderer", "videoOwner", "videoOwnerRenderer"
|
||||
)
|
||||
# Sidebar gone — fall back to playlistHeaderRenderer (YouTube changed response format)
|
||||
hr = header_renderer.not_nil!
|
||||
|
||||
raise InfoException.new("Could not extract author info") if !author_info
|
||||
title = extract_text(hr["title"]?) || ""
|
||||
|
||||
author = author_info.dig?("title", "runs", 0, "text").try &.as_s || ""
|
||||
author_thumbnail = author_info.dig?("thumbnail", "thumbnails", 0, "url").try &.as_s || ""
|
||||
ucid = author_info.dig?("title", "runs", 0, "navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
|
||||
desc_item = hr["descriptionText"]? || hr["description"]?
|
||||
description_txt = desc_item.try &.["runs"]?.try &.as_a
|
||||
.map(&.["text"].as_s).join("") || desc_item.try &.["simpleText"]?.try &.as_s || ""
|
||||
description_html = desc_item.try &.["runs"]?.try &.as_a
|
||||
.try { |run| content_to_comment_html(run).try &.to_s } || "<p></p>"
|
||||
|
||||
thumbnail = hr.dig?("thumbnail", "thumbnails", 0, "url").try &.as_s
|
||||
|
||||
owner_run = hr.dig?("ownerText", "runs", 0)
|
||||
author = owner_run.try &.["text"]?.try &.as_s || ""
|
||||
ucid = owner_run.try &.dig?("navigationEndpoint", "browseEndpoint", "browseId").try &.as_s || ""
|
||||
author_thumbnail = hr.dig?("channelAvatar", "decoratedAvatarViewModel", "avatar",
|
||||
"avatarViewModel", "image", "sources", 0, "url").try &.as_s ||
|
||||
hr.dig?("avatar", "thumbnails", 0, "url").try &.as_s || ""
|
||||
end
|
||||
|
||||
return Playlist.new({
|
||||
|
||||
@ -675,13 +675,20 @@ private module Parsers
|
||||
#
|
||||
# NOTE: this simplistic `.to_i` conversion might not work on larger
|
||||
# playlists and hasn't been tested.
|
||||
video_count = thumbnail_view_model.dig("overlays").as_a
|
||||
all_badge_texts = thumbnail_view_model.dig("overlays").as_a
|
||||
.compact_map(&.dig?("thumbnailOverlayBadgeViewModel", "thumbnailBadges").try &.as_a)
|
||||
.flatten
|
||||
.find(nil, &.dig?("thumbnailBadgeViewModel", "text").try { |node|
|
||||
{"episodes", "videos"}.any? { |str| node.as_s.ends_with?(str) }
|
||||
})
|
||||
.try &.dig("thumbnailBadgeViewModel", "text").as_s.to_i(strict: false)
|
||||
.compact_map(&.dig?("thumbnailBadgeViewModel", "text").try &.as_s)
|
||||
|
||||
# Prefer badges explicitly labeled "videos" or "episodes" (case-insensitive)
|
||||
video_count = all_badge_texts
|
||||
.find { |t| {"episodes", "videos"}.any? { |s| t.downcase.ends_with?(s) } }
|
||||
.try &.to_i(strict: false)
|
||||
|
||||
# Fallback: any badge whose text starts with a digit
|
||||
video_count ||= all_badge_texts
|
||||
.find { |t| t[0]?.try &.ascii_number? }
|
||||
.try &.to_i(strict: false)
|
||||
|
||||
metadata = item_contents.dig("metadata", "lockupMetadataViewModel")
|
||||
title = metadata.dig("title", "content").as_s
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user