mirror of
https://github.com/iv-org/invidious.git
synced 2026-07-08 22:56:47 -05:00
fix: fix channel videos and playlists on searches
Channel videos are now encapsulated in a `lockupViewModel`. There is 3 types of content that can be inside a `lockupViewModel`: - LOCKUP_CONTENT_TYPE_VIDEO - LOCKUP_CONTENT_TYPE_PLAYLIST - LOCKUP_CONTENT_TYPE_PODCAST This commit parses `LOCKUP_CONTENT_TYPE_VIDEO` and `LOCKUP_CONTENT_TYPE_PLAYLIST` types to fix videos in channels and playlists in channels There is another bugs to fix: - Since `LOCKUP_CONTENT_TYPE_PODCAST` needs to be handled differently compared to `LOCKUP_CONTENT_TYPE_PLAYLIST`, podcasts of a channel do not appear in the list of podcasts (Example channel with podcasts: https://www.youtube.com/@GrantSanderson/podcasts) - The playlists of a channel need a better parsing for it's channel hyperlink, since the channel hyperlink is not actually the channel, it's the updated date of the playlist or just "View full playlist" PRs to fix those issues are welcome!
This commit is contained in:
parent
99390d065d
commit
07b5ed8972
@ -630,13 +630,14 @@ private module Parsers
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parses an InnerTube lockupViewModel into a SearchPlaylist.
|
# Parses an InnerTube lockupViewModel into a SearchPlaylist or a SearchVideo
|
||||||
# Returns nil when the given object is not a lockupViewModel.
|
# Returns nil when the given object is not a lockupViewModel.
|
||||||
#
|
#
|
||||||
# This structure is present since November 2024 on the "podcasts" and
|
# This structure is present since November 2024 on the "podcasts" and
|
||||||
# "playlists" tabs of the channel page. It is usually encapsulated in either
|
# "playlists" tabs of the channel page. It is usually encapsulated in either
|
||||||
# a richItemRenderer or a richGridRenderer.
|
# a richItemRenderer or a richGridRenderer.
|
||||||
#
|
#
|
||||||
|
# Since 2026-05-21, now channel videos are encapsulated in a lockupViewModel.
|
||||||
module LockupViewModelParser
|
module LockupViewModelParser
|
||||||
extend self
|
extend self
|
||||||
include BaseParser
|
include BaseParser
|
||||||
@ -648,14 +649,52 @@ private module Parsers
|
|||||||
end
|
end
|
||||||
|
|
||||||
private def parse_internal(item_contents, author_fallback)
|
private def parse_internal(item_contents, author_fallback)
|
||||||
playlist_id = item_contents["contentId"].as_s
|
content_type = item_contents["contentType"].as_s
|
||||||
|
|
||||||
|
if content_type == "LOCKUP_CONTENT_TYPE_VIDEO"
|
||||||
thumbnail_view_model = item_contents.dig(
|
thumbnail_view_model = item_contents.dig(
|
||||||
"contentImage", "thumbnailViewModel"
|
"contentImage", "thumbnailViewModel"
|
||||||
)
|
)
|
||||||
|
|
||||||
thumbnail = thumbnail_view_model.dig("image", "sources", 0, "url").as_s
|
thumbnail = thumbnail_view_model.dig("image", "sources", 0, "url").as_s
|
||||||
|
|
||||||
|
video_id = item_contents["contentId"].as_s
|
||||||
|
|
||||||
|
metadata = item_contents.dig("metadata", "lockupMetadataViewModel")
|
||||||
|
title = metadata.dig("title", "content").as_s
|
||||||
|
# Contains the views of the video and the published time of the video.
|
||||||
|
metadata_parts = metadata.dig("metadata", "contentMetadataViewModel", "metadataRows", 0, "metadataParts")
|
||||||
|
|
||||||
|
view_count_text = metadata_parts.dig(0, "text", "content").as_s
|
||||||
|
view_count = short_text_to_number(view_count_text || "0")
|
||||||
|
published = metadata_parts.dig?(1, "text", "content").try { |t| decode_date(t.as_s) } || Time.local
|
||||||
|
|
||||||
|
author_verified = has_verified_badge?(item_contents["ownerBadges"]?)
|
||||||
|
|
||||||
|
return SearchVideo.new({
|
||||||
|
title: title,
|
||||||
|
id: video_id,
|
||||||
|
author: author_fallback.name,
|
||||||
|
ucid: author_fallback.id,
|
||||||
|
published: published,
|
||||||
|
views: view_count,
|
||||||
|
description_html: "",
|
||||||
|
length_seconds: 0,
|
||||||
|
premiere_timestamp: Time.unix(0),
|
||||||
|
author_verified: false,
|
||||||
|
author_thumbnail: nil,
|
||||||
|
badges: VideoBadges::None,
|
||||||
|
})
|
||||||
|
elsif content_type == "LOCKUP_CONTENT_TYPE_PODCAST"
|
||||||
|
# TODO
|
||||||
|
else # If it's a podcast, it's content_type would be "LOCKUP_CONTENT_TYPE_PODCAST"
|
||||||
|
thumbnail_view_model = item_contents.dig(
|
||||||
|
"contentImage", "collectionThumbnailViewModel",
|
||||||
|
"primaryThumbnail", "thumbnailViewModel"
|
||||||
|
)
|
||||||
|
thumbnail = thumbnail_view_model.dig("image", "sources", 0, "url").as_s
|
||||||
|
|
||||||
|
playlist_id = item_contents["contentId"].as_s
|
||||||
|
|
||||||
# This complicated sequences tries to extract the following data structure:
|
# This complicated sequences tries to extract the following data structure:
|
||||||
# "overlays": [{
|
# "overlays": [{
|
||||||
# "thumbnailOverlayBadgeViewModel": {
|
# "thumbnailOverlayBadgeViewModel": {
|
||||||
@ -681,6 +720,17 @@ private module Parsers
|
|||||||
metadata = item_contents.dig("metadata", "lockupMetadataViewModel")
|
metadata = item_contents.dig("metadata", "lockupMetadataViewModel")
|
||||||
title = metadata.dig("title", "content").as_s
|
title = metadata.dig("title", "content").as_s
|
||||||
|
|
||||||
|
metadata_parts = metadata.dig("metadata", "contentMetadataViewModel", "metadataRows", 0, "metadataParts", 0)
|
||||||
|
|
||||||
|
if author_info = metadata_parts["text"]?
|
||||||
|
author = author_info["content"].as_s
|
||||||
|
author_id = author_info.dig?("commandRuns", 0, "onTap", "innertubeCommand", "browseEndpoint", "browseId")
|
||||||
|
.try &.as_s || author_fallback.id
|
||||||
|
else
|
||||||
|
author = author_fallback.name
|
||||||
|
author_id = author_fallback.id
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: Retrieve "updated" info from metadata parts
|
# TODO: Retrieve "updated" info from metadata parts
|
||||||
# rows = metadata.dig("metadata", "contentMetadataViewModel", "metadataRows").as_a
|
# rows = metadata.dig("metadata", "contentMetadataViewModel", "metadataRows").as_a
|
||||||
# parts_text = rows.map(&.dig?("metadataParts", "text", "content").try &.as_s)
|
# parts_text = rows.map(&.dig?("metadataParts", "text", "content").try &.as_s)
|
||||||
@ -693,14 +743,15 @@ private module Parsers
|
|||||||
return SearchPlaylist.new({
|
return SearchPlaylist.new({
|
||||||
title: title,
|
title: title,
|
||||||
id: playlist_id,
|
id: playlist_id,
|
||||||
author: author_fallback.name,
|
author: author,
|
||||||
ucid: author_fallback.id,
|
ucid: author_id,
|
||||||
video_count: video_count || -1,
|
video_count: video_count || -1,
|
||||||
videos: [] of SearchPlaylistVideo,
|
videos: [] of SearchPlaylistVideo,
|
||||||
thumbnail: thumbnail,
|
thumbnail: thumbnail,
|
||||||
author_verified: false,
|
author_verified: false,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.parser_name
|
def self.parser_name
|
||||||
return {{@type.name}}
|
return {{@type.name}}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user