|
|
@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
require "../helpers/serialized_yt_data"
|
|
|
|
|
|
|
|
|
|
|
|
# This file contains helper methods to parse the Youtube API json data into
|
|
|
|
# This file contains helper methods to parse the Youtube API json data into
|
|
|
|
# neat little packages we can use
|
|
|
|
# neat little packages we can use
|
|
|
|
|
|
|
|
|
|
|
@ -14,6 +16,7 @@ private ITEM_PARSERS = {
|
|
|
|
Parsers::GridPlaylistRendererParser,
|
|
|
|
Parsers::GridPlaylistRendererParser,
|
|
|
|
Parsers::PlaylistRendererParser,
|
|
|
|
Parsers::PlaylistRendererParser,
|
|
|
|
Parsers::CategoryRendererParser,
|
|
|
|
Parsers::CategoryRendererParser,
|
|
|
|
|
|
|
|
Parsers::RichItemRendererParser,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
record AuthorFallback, name : String, id : String
|
|
|
|
record AuthorFallback, name : String, id : String
|
|
|
@ -57,6 +60,8 @@ private module Parsers
|
|
|
|
author_id = author_fallback.id
|
|
|
|
author_id = author_fallback.id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
author_verified = has_verified_badge?(item_contents["ownerBadges"]?)
|
|
|
|
|
|
|
|
|
|
|
|
# For live videos (and possibly recently premiered videos) there is no published information.
|
|
|
|
# For live videos (and possibly recently premiered videos) there is no published information.
|
|
|
|
# Instead, in its place is the amount of people currently watching. This behavior should be replicated
|
|
|
|
# Instead, in its place is the amount of people currently watching. This behavior should be replicated
|
|
|
|
# on Invidious once all features of livestreams are supported. On an unrelated note, defaulting to the current
|
|
|
|
# on Invidious once all features of livestreams are supported. On an unrelated note, defaulting to the current
|
|
|
@ -102,11 +107,7 @@ private module Parsers
|
|
|
|
premium = false
|
|
|
|
premium = false
|
|
|
|
|
|
|
|
|
|
|
|
premiere_timestamp = item_contents.dig?("upcomingEventData", "startTime").try { |t| Time.unix(t.as_s.to_i64) }
|
|
|
|
premiere_timestamp = item_contents.dig?("upcomingEventData", "startTime").try { |t| Time.unix(t.as_s.to_i64) }
|
|
|
|
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
|
|
|
|
|
|
|
|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
author_verified = (author_verified_badge && author_verified_badge.size > 0)
|
|
|
|
|
|
|
|
item_contents["badges"]?.try &.as_a.each do |badge|
|
|
|
|
item_contents["badges"]?.try &.as_a.each do |badge|
|
|
|
|
b = badge["metadataBadgeRenderer"]
|
|
|
|
b = badge["metadataBadgeRenderer"]
|
|
|
|
case b["label"].as_s
|
|
|
|
case b["label"].as_s
|
|
|
@ -133,7 +134,7 @@ private module Parsers
|
|
|
|
live_now: live_now,
|
|
|
|
live_now: live_now,
|
|
|
|
premium: premium,
|
|
|
|
premium: premium,
|
|
|
|
premiere_timestamp: premiere_timestamp,
|
|
|
|
premiere_timestamp: premiere_timestamp,
|
|
|
|
author_verified: author_verified || false,
|
|
|
|
author_verified: author_verified,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
@ -161,12 +162,9 @@ private module Parsers
|
|
|
|
private def self.parse(item_contents, author_fallback)
|
|
|
|
private def self.parse(item_contents, author_fallback)
|
|
|
|
author = extract_text(item_contents["title"]) || author_fallback.name
|
|
|
|
author = extract_text(item_contents["title"]) || author_fallback.name
|
|
|
|
author_id = item_contents["channelId"]?.try &.as_s || author_fallback.id
|
|
|
|
author_id = item_contents["channelId"]?.try &.as_s || author_fallback.id
|
|
|
|
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
|
|
|
|
author_verified = has_verified_badge?(item_contents["ownerBadges"]?)
|
|
|
|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
author_verified = (author_verified_badge && author_verified_badge.size > 0)
|
|
|
|
|
|
|
|
author_thumbnail = HelperExtractors.get_thumbnails(item_contents)
|
|
|
|
author_thumbnail = HelperExtractors.get_thumbnails(item_contents)
|
|
|
|
|
|
|
|
|
|
|
|
# When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube.
|
|
|
|
# When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube.
|
|
|
|
# Always simpleText
|
|
|
|
# Always simpleText
|
|
|
|
# TODO change default value to nil
|
|
|
|
# TODO change default value to nil
|
|
|
@ -188,7 +186,7 @@ private module Parsers
|
|
|
|
video_count: video_count,
|
|
|
|
video_count: video_count,
|
|
|
|
description_html: description_html,
|
|
|
|
description_html: description_html,
|
|
|
|
auto_generated: auto_generated,
|
|
|
|
auto_generated: auto_generated,
|
|
|
|
author_verified: author_verified || false,
|
|
|
|
author_verified: author_verified,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
@ -216,11 +214,9 @@ private module Parsers
|
|
|
|
private def self.parse(item_contents, author_fallback)
|
|
|
|
private def self.parse(item_contents, author_fallback)
|
|
|
|
title = extract_text(item_contents["title"]) || ""
|
|
|
|
title = extract_text(item_contents["title"]) || ""
|
|
|
|
plid = item_contents["playlistId"]?.try &.as_s || ""
|
|
|
|
plid = item_contents["playlistId"]?.try &.as_s || ""
|
|
|
|
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
|
|
|
|
|
|
|
|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
author_verified = (author_verified_badge && author_verified_badge.size > 0)
|
|
|
|
author_verified = has_verified_badge?(item_contents["ownerBadges"]?)
|
|
|
|
|
|
|
|
|
|
|
|
video_count = HelperExtractors.get_video_count(item_contents)
|
|
|
|
video_count = HelperExtractors.get_video_count(item_contents)
|
|
|
|
playlist_thumbnail = HelperExtractors.get_thumbnails(item_contents)
|
|
|
|
playlist_thumbnail = HelperExtractors.get_thumbnails(item_contents)
|
|
|
|
|
|
|
|
|
|
|
@ -232,7 +228,7 @@ private module Parsers
|
|
|
|
video_count: video_count,
|
|
|
|
video_count: video_count,
|
|
|
|
videos: [] of SearchPlaylistVideo,
|
|
|
|
videos: [] of SearchPlaylistVideo,
|
|
|
|
thumbnail: playlist_thumbnail,
|
|
|
|
thumbnail: playlist_thumbnail,
|
|
|
|
author_verified: author_verified || false,
|
|
|
|
author_verified: author_verified,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
@ -266,11 +262,8 @@ private module Parsers
|
|
|
|
author_info = item_contents.dig?("shortBylineText", "runs", 0)
|
|
|
|
author_info = item_contents.dig?("shortBylineText", "runs", 0)
|
|
|
|
author = author_info.try &.["text"].as_s || author_fallback.name
|
|
|
|
author = author_info.try &.["text"].as_s || author_fallback.name
|
|
|
|
author_id = author_info.try { |x| HelperExtractors.get_browse_id(x) } || author_fallback.id
|
|
|
|
author_id = author_info.try { |x| HelperExtractors.get_browse_id(x) } || author_fallback.id
|
|
|
|
author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
|
|
|
|
author_verified = has_verified_badge?(item_contents["ownerBadges"]?)
|
|
|
|
badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
author_verified = (author_verified_badge && author_verified_badge.size > 0)
|
|
|
|
|
|
|
|
videos = item_contents["videos"]?.try &.as_a.map do |v|
|
|
|
|
videos = item_contents["videos"]?.try &.as_a.map do |v|
|
|
|
|
v = v["childVideoRenderer"]
|
|
|
|
v = v["childVideoRenderer"]
|
|
|
|
v_title = v.dig?("title", "simpleText").try &.as_s || ""
|
|
|
|
v_title = v.dig?("title", "simpleText").try &.as_s || ""
|
|
|
@ -293,7 +286,7 @@ private module Parsers
|
|
|
|
video_count: video_count,
|
|
|
|
video_count: video_count,
|
|
|
|
videos: videos,
|
|
|
|
videos: videos,
|
|
|
|
thumbnail: playlist_thumbnail,
|
|
|
|
thumbnail: playlist_thumbnail,
|
|
|
|
author_verified: author_verified || false,
|
|
|
|
author_verified: author_verified,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
@ -374,6 +367,29 @@ private module Parsers
|
|
|
|
return {{@type.name}}
|
|
|
|
return {{@type.name}}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Parses an InnerTube richItemRenderer into a SearchVideo.
|
|
|
|
|
|
|
|
# Returns nil when the given object isn't a shelfRenderer
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
# A richItemRenderer seems to be a simple wrapper for a videoRenderer, used
|
|
|
|
|
|
|
|
# by the result page for hashtags. It is located inside a continuationItems
|
|
|
|
|
|
|
|
# container.
|
|
|
|
|
|
|
|
#
|
|
|
|
|
|
|
|
module RichItemRendererParser
|
|
|
|
|
|
|
|
def self.process(item : JSON::Any, author_fallback : AuthorFallback)
|
|
|
|
|
|
|
|
if item_contents = item.dig?("richItemRenderer", "content")
|
|
|
|
|
|
|
|
return self.parse(item_contents, author_fallback)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private def self.parse(item_contents, author_fallback)
|
|
|
|
|
|
|
|
return VideoRendererParser.process(item_contents, author_fallback)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def self.parser_name
|
|
|
|
|
|
|
|
return {{@type.name}}
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# The following are the extractors for extracting an array of items from
|
|
|
|
# The following are the extractors for extracting an array of items from
|
|
|
@ -501,6 +517,8 @@ private module Extractors
|
|
|
|
self.extract(target)
|
|
|
|
self.extract(target)
|
|
|
|
elsif target = initial_data["appendContinuationItemsAction"]?
|
|
|
|
elsif target = initial_data["appendContinuationItemsAction"]?
|
|
|
|
self.extract(target)
|
|
|
|
self.extract(target)
|
|
|
|
|
|
|
|
elsif target = initial_data["reloadContinuationItemsCommand"]?
|
|
|
|
|
|
|
|
self.extract(target)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|