From 08f862292ac9963a50be1d8417b0a03971db733f Mon Sep 17 00:00:00 2001 From: Fijxu Date: Wed, 24 Jun 2026 23:54:34 -0400 Subject: [PATCH] fix: fix playlists not showing any videos due to outdated playlist parsing (#5774) * TODO, CHANGE COMMIT MESSAGE WHEN DONE * save * fix: fix playlists videos parsing * revert text * do not shadow outer item variable --- src/invidious/playlists.cr | 57 ++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 231d4bbc..e6a06633 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -454,6 +454,10 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32, end end +# TODO (2026-06-24): Migrate this function to use parsers instead, as it uses, +# the same LockupViewModel used in Channel videos and Youtube playlists that +# appears on searches (Invidious /search endpoint). +# Related to https://github.com/iv-org/invidious/pull/5736 def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JSON::Any)) videos = [] of PlaylistVideo | ProblematicTimelineItem @@ -467,8 +471,7 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS tabs_contents = tabs_renderer.["contents"]? || tabs_renderer.["content"] list_renderer = tabs_contents.["sectionListRenderer"]["contents"][0] - item_renderer = list_renderer.["itemSectionRenderer"]["contents"][0] - contents = item_renderer.["playlistVideoListRenderer"]["contents"].as_a + contents = list_renderer.["itemSectionRenderer"]["contents"].as_a else # Continuation data contents = initial_data["onResponseReceivedActions"][0]? @@ -479,15 +482,39 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS end contents.try &.each do |item| - if i = item["playlistVideoRenderer"]? - video_id = i.dig?("navigationEndpoint", "watchEndpoint", "videoId").try &.as_s || i.dig("videoId").as_s - plid = i.dig?("navigationEndpoint", "watchEndpoint", "playlistId").try &.as_s || playlist_id - index = i.dig?("navigationEndpoint", "watchEndpoint", "index").try &.as_i64 || i.dig("index", "simpleText").as_s.to_i64 + if i = item["lockupViewModel"]? + thumbnail_view_model = i.dig?( + "contentImage", "thumbnailViewModel" + ) + + watch_endpoint = i.dig?("rendererContext", "commandContext", "onTap", "innertubeCommand", "watchEndpoint") + video_id = watch_endpoint.try &.["videoId"]?.try &.as_s + plid = watch_endpoint.try &.["playlistId"]?.try &.as_s || playlist_id + index = watch_endpoint.try &.["index"]?.try &.as_i64 + + metadata = i["metadata"]? + lockup_metadata_view_model = metadata.try &.dig?("lockupMetadataViewModel") + title = lockup_metadata_view_model.try &.dig?("title", "content").try &.as_s + lockup_metadata = lockup_metadata_view_model.try &.dig?("metadata") + metadata_rows = lockup_metadata.try &.dig?("contentMetadataViewModel", "metadataRows").try &.as_a + + # Find the metadataParts with commandRuns inside, which contains author + # information. + metadata_parts = metadata_rows.try &.find { |row| + parts = row["metadataParts"]?.try &.as_a + parts && parts.any? { |item2| item2.dig?("text", "commandRuns").try &.as_a } + }.try &.["metadataParts"].as_a + + if author_info = metadata_parts.try &.find(&.dig?("text", "commandRuns")) + .try &.["text"] + author = author_info["content"].as_s + ucid = author_info.dig?("commandRuns", 0, "onTap", "innertubeCommand", "browseEndpoint", "browseId") + .try &.as_s + end + + length = thumbnail_view_model.try &.dig?("overlays", 0, "thumbnailBottomOverlayViewModel", "badges", 0, "thumbnailBadgeViewModel", "text").try &.as_s + length_seconds = decode_length_seconds(length) if length - title = i["title"].try { |t| t["simpleText"]? || t["runs"]?.try &.[0]["text"]? }.try &.as_s || "" - author = i["shortBylineText"]?.try &.["runs"][0]["text"].as_s || "" - ucid = i["shortBylineText"]?.try &.["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s || "" - length_seconds = i["lengthSeconds"]?.try &.as_s.to_i live = false if !length_seconds @@ -496,15 +523,15 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS end videos << PlaylistVideo.new({ - title: title, - id: video_id, - author: author, - ucid: ucid, + title: title || "", + id: video_id || "", + author: author || "", + ucid: ucid || "", length_seconds: length_seconds, published: Time.utc, plid: plid, live_now: live, - index: index, + index: index || -1_i64, }) end rescue ex