using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using System.Web; using InnerTube.Models; using Newtonsoft.Json.Linq; namespace InnerTube { public class Youtube { internal readonly HttpClient Client = new(); public readonly Dictionary> PlayerCache = new(); private readonly Dictionary ChannelTabParams = new() { [ChannelTabs.Home] = @"EghmZWF0dXJlZA%3D%3D", [ChannelTabs.Videos] = @"EgZ2aWRlb3M%3D", [ChannelTabs.Playlists] = @"EglwbGF5bGlzdHM%3D", [ChannelTabs.Community] = @"Egljb21tdW5pdHk%3D", [ChannelTabs.Channels] = @"EghjaGFubmVscw%3D%3D", [ChannelTabs.About] = @"EgVhYm91dA%3D%3D" }; private async Task MakeRequest(string endpoint, Dictionary postData, string language, string region, string clientName = "WEB", string clientId = "1", string clientVersion = "2.20220405", bool authorized = false) { HttpRequestMessage hrm = new(HttpMethod.Post, @$"https://www.youtube.com/youtubei/v1/{endpoint}?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"); byte[] buffer = Encoding.UTF8.GetBytes(RequestContext.BuildRequestContextJson(postData, language, region, clientName, clientVersion)); ByteArrayContent byteContent = new(buffer); if (authorized) { hrm.Headers.Add("Cookie", Utils.GenerateAuthCookie()); hrm.Headers.Add("Authorization", Utils.GenerateAuthHeader()); hrm.Headers.Add("X-Youtube-Client-Name", clientId); hrm.Headers.Add("X-Youtube-Client-Version", clientVersion); hrm.Headers.Add("Origin", "https://www.youtube.com"); } byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); hrm.Content = byteContent; HttpResponseMessage ytPlayerRequest = await Client.SendAsync(hrm); return JObject.Parse(await ytPlayerRequest.Content.ReadAsStringAsync()); } public async Task GetPlayerAsync(string videoId, string language = "en", string region = "US", bool iOS = false) { if (PlayerCache.Any(x => x.Key == videoId && x.Value.ExpireTime > DateTimeOffset.Now)) { CacheItem item = PlayerCache[videoId]; item.Item.ExpiresInSeconds = ((int)(item.ExpireTime - DateTimeOffset.Now).TotalSeconds).ToString(); return item.Item; } JObject player = await MakeRequest("player", new Dictionary { ["videoId"] = videoId, ["contentCheckOk"] = true, ["racyCheckOk"] = true }, language, region, iOS ? "IOS" : "ANDROID", iOS ? "5" : "3", "17.13.3", true); switch (player["playabilityStatus"]?["status"]?.ToString()) { case "OK": YoutubeStoryboardSpec storyboardSpec = new(player["storyboards"]?["playerStoryboardSpecRenderer"]?["spec"]?.ToString(), player["videoDetails"]?["lengthSeconds"]?.ToObject() ?? 0); YoutubePlayer video = new() { Id = player["videoDetails"]?["videoId"]?.ToString(), Title = player["videoDetails"]?["title"]?.ToString(), Description = player["videoDetails"]?["shortDescription"]?.ToString(), Tags = player["videoDetails"]?["keywords"]?.ToObject(), Channel = new Channel { Name = player["videoDetails"]?["author"]?.ToString(), Id = player["videoDetails"]?["channelId"]?.ToString(), Avatars = Array.Empty() }, Duration = player["videoDetails"]?["lengthSeconds"]?.ToObject(), IsLive = player["videoDetails"]?["isLiveContent"]?.ToObject() ?? false, Chapters = Array.Empty(), Thumbnails = player["videoDetails"]?["thumbnail"]?["thumbnails"]?.Select(x => new Thumbnail { Height = x["height"]?.ToObject() ?? -1, Url = x["url"]?.ToString(), Width = x["width"]?.ToObject() ?? -1 }).ToArray(), Formats = player["streamingData"]?["formats"]?.Select(x => new Format { FormatName = Utils.GetFormatName(x), FormatId = x["itag"]?.ToString(), FormatNote = x["quality"]?.ToString(), Filesize = x["contentLength"]?.ToObject(), Bitrate = x["bitrate"]?.ToObject() ?? 0, AudioCodec = Utils.GetCodec(x["mimeType"]?.ToString(), true), VideoCodec = Utils.GetCodec(x["mimeType"]?.ToString(), false), AudioSampleRate = x["audioSampleRate"]?.ToObject(), Resolution = $"{x["width"] ?? "0"}x{x["height"] ?? "0"}", Url = x["url"]?.ToString() }).ToArray() ?? Array.Empty(), AdaptiveFormats = player["streamingData"]?["adaptiveFormats"]?.Select(x => new Format { FormatName = Utils.GetFormatName(x), FormatId = x["itag"]?.ToString(), FormatNote = x["quality"]?.ToString(), Filesize = x["contentLength"]?.ToObject(), Bitrate = x["bitrate"]?.ToObject() ?? 0, AudioCodec = Utils.GetCodec(x["mimeType"].ToString(), true), VideoCodec = Utils.GetCodec(x["mimeType"].ToString(), false), AudioSampleRate = x["audioSampleRate"]?.ToObject(), Resolution = $"{x["width"] ?? "0"}x{x["height"] ?? "0"}", Url = x["url"]?.ToString(), InitRange = x["initRange"]?.ToObject(), IndexRange = x["indexRange"]?.ToObject() }).ToArray() ?? Array.Empty(), HlsManifestUrl = player["streamingData"]?["hlsManifestUrl"]?.ToString(), Subtitles = player["captions"]?["playerCaptionsTracklistRenderer"]?["captionTracks"]?.Select( x => new Subtitle { Ext = HttpUtility.ParseQueryString(x["baseUrl"].ToString()).Get("fmt"), Language = Utils.ReadRuns(x["name"]?["runs"]?.ToObject()), Url = x["baseUrl"].ToString() }).ToArray(), Storyboards = storyboardSpec.Urls.TryGetValue("L0", out string sb) ? new[] { sb } : Array.Empty(), ExpiresInSeconds = player["streamingData"]?["expiresInSeconds"]?.ToString(), ErrorMessage = null }; PlayerCache.Remove(videoId); PlayerCache.Add(videoId, new CacheItem(video, TimeSpan.FromSeconds(int.Parse(video.ExpiresInSeconds ?? "21600")) .Subtract(TimeSpan.FromHours(1)))); return video; case "LOGIN_REQUIRED": return new YoutubePlayer { Id = "", Title = "", Description = "", Tags = Array.Empty(), Channel = new Channel { Name = "", Id = "", SubscriberCount = "", Avatars = Array.Empty() }, Duration = 0, IsLive = false, Chapters = Array.Empty(), Thumbnails = Array.Empty(), Formats = Array.Empty(), AdaptiveFormats = Array.Empty(), Subtitles = Array.Empty(), Storyboards = Array.Empty(), ExpiresInSeconds = "0", ErrorMessage = "This video is age-restricted. Please contact this instances authors to update their configuration" }; default: return new YoutubePlayer { Id = "", Title = "", Description = "", Tags = Array.Empty(), Channel = new Channel { Name = "", Id = "", SubscriberCount = "", Avatars = Array.Empty() }, Duration = 0, IsLive = false, Chapters = Array.Empty(), Thumbnails = Array.Empty(), Formats = Array.Empty(), AdaptiveFormats = Array.Empty(), Subtitles = Array.Empty(), Storyboards = Array.Empty(), ExpiresInSeconds = "0", ErrorMessage = player["playabilityStatus"]?["reason"]?.ToString() ?? "Something has gone *really* wrong" }; } } public async Task GetVideoAsync(string videoId, string language = "en", string region = "US") { JObject player = await MakeRequest("next", new Dictionary { ["videoId"] = videoId }, language, region); JToken[] contents = (player?["contents"]?["twoColumnWatchNextResults"]?["results"]?["results"]?["contents"] ?.ToObject() ?? new JArray()) .SkipWhile(x => !x.First.Path.EndsWith("videoPrimaryInfoRenderer")).ToArray(); YoutubeVideo video = new(); video.Id = player["currentVideoEndpoint"]?["watchEndpoint"]?["videoId"]?.ToString(); try { video.Title = Utils.ReadRuns( contents[0] ["videoPrimaryInfoRenderer"]?["title"]?["runs"]?.ToObject()); video.Description = Utils.ReadRuns( contents[1] ["videoSecondaryInfoRenderer"]?["description"]?["runs"]?.ToObject()); video.Views = contents[0] ["videoPrimaryInfoRenderer"]?["viewCount"]?["videoViewCountRenderer"]?["viewCount"]?["simpleText"]?.ToString(); video.Channel = new Channel { Name = contents[1] ["videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["title"]?["runs"]?[0]?[ "text"]?.ToString(), Id = contents[1] ["videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["title"]?["runs"]?[0]? ["navigationEndpoint"]?["browseEndpoint"]?["browseId"]?.ToString(), SubscriberCount = contents[1] ["videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["subscriberCountText"]?[ "simpleText"]?.ToString(), Avatars = (contents[1][ "videoSecondaryInfoRenderer"]?["owner"]?["videoOwnerRenderer"]?["thumbnail"]?[ "thumbnails"] ?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray() }; video.UploadDate = contents[0][ "videoPrimaryInfoRenderer"]?["dateText"]?["simpleText"]?.ToString(); } catch { video.Title ??= ""; video.Description ??= ""; video.Channel ??= new Channel { Name = "", Id = "", SubscriberCount = "", Avatars = Array.Empty() }; video.UploadDate ??= ""; } video.Recommended = ParseRenderers( player?["contents"]?["twoColumnWatchNextResults"]?["secondaryResults"]?["secondaryResults"]? ["results"]?.ToObject() ?? new JArray()); return video; } public async Task SearchAsync(string query, string continuation = null, string language = "en", string region = "US") { Dictionary data = new(); if (string.IsNullOrWhiteSpace(continuation)) data.Add("query", query); else data.Add("continuation", continuation); JObject search = await MakeRequest("search", data, language, region); return new YoutubeSearchResults { Refinements = search?["refinements"]?.ToObject() ?? Array.Empty(), EstimatedResults = search?["estimatedResults"]?.ToObject() ?? 0, Results = ParseRenderers( search?["contents"]?["twoColumnSearchResultsRenderer"]?["primaryContents"]?["sectionListRenderer"]? ["contents"]?[0]?["itemSectionRenderer"]?["contents"]?.ToObject() ?? search?["onResponseReceivedCommands"]?[0]?["appendContinuationItemsAction"]?["continuationItems"]? [0]?["itemSectionRenderer"]?["contents"]?.ToObject() ?? new JArray()), ContinuationKey = search?["contents"]?["twoColumnSearchResultsRenderer"]?["primaryContents"]?["sectionListRenderer"]? ["contents"]?[1]?["continuationItemRenderer"]?["continuationEndpoint"]?["continuationCommand"]? ["token"]?.ToString() ?? search?["onResponseReceivedCommands"]?[0]?["appendContinuationItemsAction"]?["continuationItems"]? [1]?["continuationItemRenderer"]?["continuationEndpoint"]?["continuationCommand"]?["token"] ?.ToString() ?? "" }; } public async Task GetPlaylistAsync(string id, string continuation = null, string language = "en", string region = "US") { Dictionary data = new(); if (string.IsNullOrWhiteSpace(continuation)) data.Add("browseId", "VL" + id); else data.Add("continuation", continuation); JObject playlist = await MakeRequest("browse", data, language, region); DynamicItem[] renderers = ParseRenderers( playlist?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?[0]?["tabRenderer"]?["content"]? ["sectionListRenderer"]?["contents"]?[0]?["itemSectionRenderer"]?["contents"]?[0]? ["playlistVideoListRenderer"]?["contents"]?.ToObject() ?? playlist?["onResponseReceivedActions"]?[0]?["appendContinuationItemsAction"]?["continuationItems"] ?.ToObject() ?? new JArray()); return new YoutubePlaylist { Id = id, Title = playlist?["metadata"]?["playlistMetadataRenderer"]?["title"]?.ToString(), Description = playlist?["metadata"]?["playlistMetadataRenderer"]?["description"]?.ToString(), VideoCount = playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[0]?[ "playlistSidebarPrimaryInfoRenderer"]?["stats"]?[0]?["runs"]?[0]?["text"]?.ToString(), ViewCount = playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[0]?[ "playlistSidebarPrimaryInfoRenderer"]?["stats"]?[1]?["simpleText"]?.ToString(), LastUpdated = Utils.ReadRuns(playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[0]?[ "playlistSidebarPrimaryInfoRenderer"]?["stats"]?[2]?["runs"]?.ToObject() ?? new JArray()), Thumbnail = (playlist?["microformat"]?["microformatDataRenderer"]?["thumbnail"]?["thumbnails"] ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), Channel = new Channel { Name = playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[1]? ["playlistSidebarSecondaryInfoRenderer"]?["videoOwner"]?["videoOwnerRenderer"]?["title"]? ["runs"]?[0]?["text"]?.ToString(), Id = playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[1]? ["playlistSidebarSecondaryInfoRenderer"]?["videoOwner"]?["videoOwnerRenderer"]? ["navigationEndpoint"]?["browseEndpoint"]?["browseId"]?.ToString(), SubscriberCount = "", Avatars = (playlist?["sidebar"]?["playlistSidebarRenderer"]?["items"]?[1]? ["playlistSidebarSecondaryInfoRenderer"]?["videoOwner"]?["videoOwnerRenderer"]?["thumbnail"] ?["thumbnails"] ?? new JArray()).Select(Utils.ParseThumbnails).ToArray() }, Videos = renderers.Where(x => x is not ContinuationItem).ToArray(), ContinuationKey = renderers.FirstOrDefault(x => x is ContinuationItem)?.Id }; } public async Task GetChannelAsync(string id, ChannelTabs tab = ChannelTabs.Home, string continuation = null, string language = "en", string region = "US") { Dictionary data = new(); if (string.IsNullOrWhiteSpace(continuation)) { data.Add("browseId", id); if (string.IsNullOrWhiteSpace(continuation)) data.Add("params", ChannelTabParams[tab]); } else { data.Add("continuation", continuation); } JObject channel = await MakeRequest("browse", data, language, region); JArray mainArray = (channel?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?.ToObject() ?? new JArray()) .FirstOrDefault(x => x?["tabRenderer"]?["selected"]?.ToObject() ?? false)?["tabRenderer"]?[ "content"]? ["sectionListRenderer"]?["contents"]?.ToObject(); return new YoutubeChannel { Id = channel?["metadata"]?["channelMetadataRenderer"]?["externalId"]?.ToString(), Name = channel?["metadata"]?["channelMetadataRenderer"]?["title"]?.ToString(), Url = channel?["metadata"]?["channelMetadataRenderer"]?["externalId"]?.ToString(), Avatars = (channel?["metadata"]?["channelMetadataRenderer"]?["avatar"]?["thumbnails"] ?? new JArray()) .Select(Utils.ParseThumbnails).ToArray(), Banners = (channel?["header"]?["c4TabbedHeaderRenderer"]?["banner"]?["thumbnails"] ?? new JArray()) .Select(Utils.ParseThumbnails).ToArray(), Description = channel?["metadata"]?["channelMetadataRenderer"]?["description"]?.ToString(), Videos = ParseRenderers(mainArray ?? channel?["onResponseReceivedActions"]?[0]?["appendContinuationItemsAction"]? ["continuationItems"]?.ToObject() ?? new JArray()), Subscribers = channel?["header"]?["c4TabbedHeaderRenderer"]?["subscriberCountText"]?["simpleText"] ?.ToString() }; } public async Task GetExploreAsync(string browseId = null, string continuation = null, string language = "en", string region = "US") { Dictionary data = new(); if (string.IsNullOrWhiteSpace(continuation)) { data.Add("browseId", browseId ?? "FEexplore"); } else { data.Add("continuation", continuation); } JObject explore = await MakeRequest("browse", data, language, region); JToken[] token = (explore?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?[0]?["tabRenderer"]?["content"]? ["sectionListRenderer"]?["contents"]?.ToObject() ?? new JArray()).Skip(1).ToArray(); JArray mainArray = new(token.Select(x => x is JObject obj ? obj : null).Where(x => x is not null)); return new YoutubeTrends { Categories = explore?["contents"]?["twoColumnBrowseResultsRenderer"]?["tabs"]?[0]?["tabRenderer"]?["content"]?["sectionListRenderer"]?["contents"]?[0]?["itemSectionRenderer"]?["contents"]?[0]?["destinationShelfRenderer"]?["destinationButtons"]?.Select( x => { JToken rendererObject = x?["destinationButtonRenderer"]; TrendCategory category = new() { Label = rendererObject?["label"]?["simpleText"]?.ToString(), BackgroundImage = (rendererObject?["backgroundImage"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), Icon = (rendererObject?["iconImage"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), Id = $"{rendererObject?["onTap"]?["browseEndpoint"]?["browseId"]}" }; return category; }).ToArray(), Videos = ParseRenderers(mainArray) }; } public async Task GetLocalsAsync(string language = "en", string region = "US") { JObject locals = await MakeRequest("account/account_menu", new Dictionary(), language, region); return new YoutubeLocals { Languages = locals["actions"]?[0]?["openPopupAction"]?["popup"]?["multiPageMenuRenderer"]?["sections"]?[0]? ["multiPageMenuSectionRenderer"]?["items"]?[1]?["compactLinkRenderer"]?["serviceEndpoint"]? ["signalServiceEndpoint"]?["actions"]?[0]?["getMultiPageMenuAction"]?["menu"]? ["multiPageMenuRenderer"]?["sections"]?[0]?["multiPageMenuSectionRenderer"]?["items"]? .ToObject()?.ToDictionary( x => x?["compactLinkRenderer"]?["serviceEndpoint"]?["signalServiceEndpoint"]? ["actions"]?[0]?["selectLanguageCommand"]?["hl"]?.ToString(), x => x?["compactLinkRenderer"]?["title"]?["simpleText"]?.ToString()), Regions = locals["actions"]?[0]?["openPopupAction"]?["popup"]?["multiPageMenuRenderer"]?["sections"]?[0]? ["multiPageMenuSectionRenderer"]?["items"]?[2]?["compactLinkRenderer"]?["serviceEndpoint"]? ["signalServiceEndpoint"]?["actions"]?[0]?["getMultiPageMenuAction"]?["menu"]? ["multiPageMenuRenderer"]?["sections"]?[0]?["multiPageMenuSectionRenderer"]?["items"]? .ToObject()?.ToDictionary( x => x?["compactLinkRenderer"]?["serviceEndpoint"]?["signalServiceEndpoint"]? ["actions"]?[0]?["selectCountryCommand"]?["gl"]?.ToString(), x => x?["compactLinkRenderer"]?["title"]?["simpleText"]?.ToString()) }; } private DynamicItem[] ParseRenderers(JArray renderersArray) { List items = new(); foreach (JToken jToken in renderersArray) { JObject recommendationContainer = jToken as JObject; string rendererName = recommendationContainer?.First?.Path.Split(".").Last() ?? ""; JObject rendererItem = recommendationContainer?[rendererName]?.ToObject(); switch (rendererName) { case "videoRenderer": items.Add(new VideoItem { Id = rendererItem?["videoId"]?.ToString(), Title = Utils.ReadRuns(rendererItem?["title"]?["runs"]?.ToObject() ?? new JArray()), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), UploadedAt = rendererItem?["publishedTimeText"]?["simpleText"]?.ToString(), Views = long.TryParse( rendererItem?["viewCountText"]?["simpleText"]?.ToString().Split(" ")[0] .Replace(",", "").Replace(".", "") ?? "0", out long vV) ? vV : 0, Channel = new Channel { Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"]?.ToString(), Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[ "browseEndpoint"]?["browseId"]?.ToString(), SubscriberCount = null, Avatars = (rendererItem?["channelThumbnailSupportedRenderers"]?[ "channelThumbnailWithLinkRenderer"]?["thumbnail"]?["thumbnails"] ?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails) .ToArray() }, Duration = rendererItem?["thumbnailOverlays"]?[0]?[ "thumbnailOverlayTimeStatusRenderer"]?["text"]?["simpleText"]?.ToString(), Description = Utils.ReadRuns(rendererItem?["detailedMetadataSnippets"]?[0]?[ "snippetText"]?["runs"]?.ToObject() ?? new JArray()) }); break; case "gridVideoRenderer": items.Add(new VideoItem { Id = rendererItem?["videoId"]?.ToString(), Title = rendererItem?["title"]?["simpleText"]?.ToString() ?? Utils.ReadRuns( rendererItem?["title"]?["runs"]?.ToObject() ?? new JArray()), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), UploadedAt = rendererItem?["publishedTimeText"]?["simpleText"]?.ToString(), Views = long.TryParse( rendererItem?["viewCountText"]?["simpleText"]?.ToString().Split(" ")[0] .Replace(",", "").Replace(".", "") ?? "0", out long gVV) ? gVV : 0, Channel = null, Duration = rendererItem?["thumbnailOverlays"]?[0]?[ "thumbnailOverlayTimeStatusRenderer"]?["text"]?["simpleText"]?.ToString() }); break; case "playlistRenderer": items.Add(new PlaylistItem { Id = rendererItem?["playlistId"] ?.ToString(), Title = rendererItem?["title"]?["simpleText"] ?.ToString(), Thumbnails = (rendererItem?["thumbnails"]?[0]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), VideoCount = int.TryParse( rendererItem?["videoCountText"]?["runs"]?[0]?["text"]?.ToString().Replace(",", "") .Replace(".", "") ?? "0", out int pVC) ? pVC : 0, FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"] ?.ToString(), Channel = new Channel { Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"] ?.ToString(), Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[ "browseEndpoint"]?["browseId"] ?.ToString(), SubscriberCount = null, Avatars = null } }); break; case "channelRenderer": items.Add(new ChannelItem { Id = rendererItem?["channelId"]?.ToString(), Title = rendererItem?["title"]?["simpleText"]?.ToString(), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"] ?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails) .ToArray(), // Url = rendererItem?["navigationEndpoint"]?["commandMetadata"]?["webCommandMetadata"]?["url"] ?.ToString(), Description = Utils.ReadRuns(rendererItem?["descriptionSnippet"]?["runs"]?.ToObject() ?? new JArray()), VideoCount = long.TryParse( rendererItem?["videoCountText"]?["runs"]?[0]?["text"] ?.ToString() .Replace(",", "") .Replace(".", "") ?? "0", out long cVC) ? cVC : 0, Subscribers = rendererItem?["subscriberCountText"]?["simpleText"]?.ToString() }); break; case "radioRenderer": items.Add(new RadioItem { Id = rendererItem?["playlistId"] ?.ToString(), Title = rendererItem?["title"]?["simpleText"] ?.ToString(), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"] ?.ToString(), Channel = new Channel { Name = rendererItem?["longBylineText"]?["simpleText"]?.ToString(), Id = "", SubscriberCount = null, Avatars = null } }); break; case "shelfRenderer": items.Add(new ShelfItem { Title = rendererItem?["title"]?["simpleText"] ?.ToString() ?? rendererItem?["title"]?["runs"]?[0]?["text"] ?.ToString(), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), Items = ParseRenderers( rendererItem?["content"]?["verticalListRenderer"]?["items"] ?.ToObject() ?? rendererItem?["content"]?["horizontalListRenderer"]?["items"] ?.ToObject() ?? rendererItem?["content"]?["expandedShelfContentsRenderer"]?["items"] ?.ToObject() ?? new JArray()), CollapsedItemCount = rendererItem?["content"]?["verticalListRenderer"]?["collapsedItemCount"] ?.ToObject() ?? 0, Badges = ParseRenderers(rendererItem?["badges"]?.ToObject() ?? new JArray()) .Where(x => x is BadgeItem).Cast().ToArray(), }); break; case "horizontalCardListRenderer": items.Add(new HorizontalCardListItem { Title = rendererItem?["header"]?["richListHeaderRenderer"]?["title"]?["simpleText"] ?.ToString(), Items = ParseRenderers(rendererItem?["cards"]?.ToObject() ?? new JArray()) }); break; case "searchRefinementCardRenderer": items.Add(new CardItem { Title = Utils.ReadRuns(rendererItem?["query"]?["runs"]?.ToObject() ?? new JArray()), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray() }); break; case "compactVideoRenderer": items.Add(new VideoItem { Id = rendererItem?["videoId"]?.ToString(), Title = rendererItem?["title"]?["simpleText"]?.ToString(), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), UploadedAt = rendererItem?["publishedTimeText"]?["simpleText"]?.ToString(), Views = long.TryParse( rendererItem?["viewCountText"]?["simpleText"]?.ToString().Split(" ")[0] .Replace(",", "").Replace(".", "") ?? "0", out long cVV) ? cVV : 0, Channel = new Channel { Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"]?.ToString(), Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[ "browseEndpoint"]?["browseId"]?.ToString(), SubscriberCount = null, Avatars = null }, Duration = rendererItem?["thumbnailOverlays"]?[0]?[ "thumbnailOverlayTimeStatusRenderer"]?["text"]?["simpleText"]?.ToString() }); break; case "compactPlaylistRenderer": items.Add(new PlaylistItem { Id = rendererItem?["playlistId"] ?.ToString(), Title = rendererItem?["title"]?["simpleText"] ?.ToString(), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"] ?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails) .ToArray(), VideoCount = int.TryParse( rendererItem?["videoCountText"]?["runs"]?[0]?["text"]?.ToString().Replace(",", "") .Replace(".", "") ?? "0", out int cPVC) ? cPVC : 0, FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"] ?.ToString(), Channel = new Channel { Name = rendererItem?["longBylineText"]?["runs"]?[0]?["text"] ?.ToString(), Id = rendererItem?["longBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[ "browseEndpoint"]?["browseId"] ?.ToString(), SubscriberCount = null, Avatars = null } }); break; case "compactRadioRenderer": items.Add(new RadioItem { Id = rendererItem?["playlistId"] ?.ToString(), Title = rendererItem?["title"]?["simpleText"] ?.ToString(), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"] ?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails) .ToArray(), FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"] ?.ToString(), Channel = new Channel { Name = rendererItem?["longBylineText"]?["simpleText"]?.ToString(), Id = "", SubscriberCount = null, Avatars = null } }); break; case "continuationItemRenderer": items.Add(new ContinuationItem { Id = rendererItem?["continuationEndpoint"]?["continuationCommand"]?["token"]?.ToString() }); break; case "playlistVideoRenderer": items.Add(new PlaylistVideoItem { Id = rendererItem?["videoId"]?.ToString(), Index = rendererItem?["index"]?["simpleText"]?.ToObject() ?? 0, Title = Utils.ReadRuns(rendererItem?["title"]?["runs"]?.ToObject() ?? new JArray()), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), Channel = new Channel { Name = rendererItem?["shortBylineText"]?["runs"]?[0]?["text"]?.ToString(), Id = rendererItem?["shortBylineText"]?["runs"]?[0]?["navigationEndpoint"]?[ "browseEndpoint"]?["browseId"]?.ToString(), SubscriberCount = null, Avatars = null }, Duration = rendererItem?["lengthText"]?["simpleText"]?.ToString() }); break; case "itemSectionRenderer": items.Add(new ItemSectionItem { Contents = ParseRenderers(rendererItem?["contents"]?.ToObject() ?? new JArray()) }); break; case "gridRenderer": items.Add(new ItemSectionItem { Contents = ParseRenderers(rendererItem?["items"]?.ToObject() ?? new JArray()) }); break; case "messageRenderer": items.Add(new MessageItem { Title = rendererItem?["text"]?["simpleText"]?.ToString() }); break; case "channelAboutFullMetadataRenderer": items.Add(new ChannelAboutItem { Description = rendererItem?["description"]?["simpleText"]?.ToString(), Country = rendererItem?["country"]?["simpleText"]?.ToString(), Joined = Utils.ReadRuns(rendererItem?["joinedDateText"]?["runs"]?.ToObject() ?? new JArray()), ViewCount = rendererItem?["viewCountText"]?["simpleText"]?.ToString() }); break; case "compactStationRenderer": items.Add(new StationItem { Id = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["playlistId"]?.ToString(), Title = rendererItem?["title"]?["simpleText"]?.ToString(), Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject() ?? new JArray()).Select(Utils.ParseThumbnails).ToArray(), VideoCount = rendererItem?["videoCountText"]?["runs"]?[0]?["text"].ToObject() ?? 0, FirstVideoId = rendererItem?["navigationEndpoint"]?["watchEndpoint"]?["videoId"]?.ToString(), Description = rendererItem?["description"]?["simpleText"]?.ToString() }); break; case "metadataBadgeRenderer": items.Add(new BadgeItem { Title = rendererItem?["label"]?.ToString(), Style = rendererItem?["style"]?.ToString() }); break; case "promotedSparklesWebRenderer": // this is an ad // no one likes ads break; default: items.Add(new DynamicItem { Id = rendererName, Title = rendererItem?.ToString() }); break; } } return items.ToArray(); } } }