mirror of
https://codeberg.org/ashley/poke.git
synced 2025-06-22 19:47:01 -04:00
owo
This commit is contained in:
parent
f431111611
commit
72143fede3
100 changed files with 12438 additions and 0 deletions
790
core/InnerTube/Youtube.cs
Normal file
790
core/InnerTube/Youtube.cs
Normal file
|
@ -0,0 +1,790 @@
|
|||
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<string, CacheItem<YoutubePlayer>> PlayerCache = new();
|
||||
|
||||
private readonly Dictionary<ChannelTabs, string> 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<JObject> MakeRequest(string endpoint, Dictionary<string, object> 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<YoutubePlayer> 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<YoutubePlayer> item = PlayerCache[videoId];
|
||||
item.Item.ExpiresInSeconds = ((int)(item.ExpireTime - DateTimeOffset.Now).TotalSeconds).ToString();
|
||||
return item.Item;
|
||||
}
|
||||
|
||||
JObject player = await MakeRequest("player", new Dictionary<string, object>
|
||||
{
|
||||
["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<long>() ?? 0);
|
||||
YoutubePlayer video = new()
|
||||
{
|
||||
Id = player["videoDetails"]?["videoId"]?.ToString(),
|
||||
Title = player["videoDetails"]?["title"]?.ToString(),
|
||||
Description = player["videoDetails"]?["shortDescription"]?.ToString(),
|
||||
Tags = player["videoDetails"]?["keywords"]?.ToObject<string[]>(),
|
||||
Channel = new Channel
|
||||
{
|
||||
Name = player["videoDetails"]?["author"]?.ToString(),
|
||||
Id = player["videoDetails"]?["channelId"]?.ToString(),
|
||||
Avatars = Array.Empty<Thumbnail>()
|
||||
},
|
||||
Duration = player["videoDetails"]?["lengthSeconds"]?.ToObject<long>(),
|
||||
IsLive = player["videoDetails"]?["isLiveContent"]?.ToObject<bool>() ?? false,
|
||||
Chapters = Array.Empty<Chapter>(),
|
||||
Thumbnails = player["videoDetails"]?["thumbnail"]?["thumbnails"]?.Select(x => new Thumbnail
|
||||
{
|
||||
Height = x["height"]?.ToObject<int>() ?? -1,
|
||||
Url = x["url"]?.ToString(),
|
||||
Width = x["width"]?.ToObject<int>() ?? -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<long>(),
|
||||
Bitrate = x["bitrate"]?.ToObject<long>() ?? 0,
|
||||
AudioCodec = Utils.GetCodec(x["mimeType"]?.ToString(), true),
|
||||
VideoCodec = Utils.GetCodec(x["mimeType"]?.ToString(), false),
|
||||
AudioSampleRate = x["audioSampleRate"]?.ToObject<long>(),
|
||||
Resolution = $"{x["width"] ?? "0"}x{x["height"] ?? "0"}",
|
||||
Url = x["url"]?.ToString()
|
||||
}).ToArray() ?? Array.Empty<Format>(),
|
||||
AdaptiveFormats = player["streamingData"]?["adaptiveFormats"]?.Select(x => new Format
|
||||
{
|
||||
FormatName = Utils.GetFormatName(x),
|
||||
FormatId = x["itag"]?.ToString(),
|
||||
FormatNote = x["quality"]?.ToString(),
|
||||
Filesize = x["contentLength"]?.ToObject<long>(),
|
||||
Bitrate = x["bitrate"]?.ToObject<long>() ?? 0,
|
||||
AudioCodec = Utils.GetCodec(x["mimeType"].ToString(), true),
|
||||
VideoCodec = Utils.GetCodec(x["mimeType"].ToString(), false),
|
||||
AudioSampleRate = x["audioSampleRate"]?.ToObject<long>(),
|
||||
Resolution = $"{x["width"] ?? "0"}x{x["height"] ?? "0"}",
|
||||
Url = x["url"]?.ToString(),
|
||||
InitRange = x["initRange"]?.ToObject<Models.Range>(),
|
||||
IndexRange = x["indexRange"]?.ToObject<Models.Range>()
|
||||
}).ToArray() ?? Array.Empty<Format>(),
|
||||
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<JArray>()),
|
||||
Url = x["baseUrl"].ToString()
|
||||
}).ToArray(),
|
||||
Storyboards = storyboardSpec.Urls.TryGetValue("L0", out string sb) ? new[] { sb } : Array.Empty<string>(),
|
||||
ExpiresInSeconds = player["streamingData"]?["expiresInSeconds"]?.ToString(),
|
||||
ErrorMessage = null
|
||||
};
|
||||
PlayerCache.Remove(videoId);
|
||||
PlayerCache.Add(videoId,
|
||||
new CacheItem<YoutubePlayer>(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<string>(),
|
||||
Channel = new Channel
|
||||
{
|
||||
Name = "",
|
||||
Id = "",
|
||||
SubscriberCount = "",
|
||||
Avatars = Array.Empty<Thumbnail>()
|
||||
},
|
||||
Duration = 0,
|
||||
IsLive = false,
|
||||
Chapters = Array.Empty<Chapter>(),
|
||||
Thumbnails = Array.Empty<Thumbnail>(),
|
||||
Formats = Array.Empty<Format>(),
|
||||
AdaptiveFormats = Array.Empty<Format>(),
|
||||
Subtitles = Array.Empty<Subtitle>(),
|
||||
Storyboards = Array.Empty<string>(),
|
||||
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<string>(),
|
||||
Channel = new Channel
|
||||
{
|
||||
Name = "",
|
||||
Id = "",
|
||||
SubscriberCount = "",
|
||||
Avatars = Array.Empty<Thumbnail>()
|
||||
},
|
||||
Duration = 0,
|
||||
IsLive = false,
|
||||
Chapters = Array.Empty<Chapter>(),
|
||||
Thumbnails = Array.Empty<Thumbnail>(),
|
||||
Formats = Array.Empty<Format>(),
|
||||
AdaptiveFormats = Array.Empty<Format>(),
|
||||
Subtitles = Array.Empty<Subtitle>(),
|
||||
Storyboards = Array.Empty<string>(),
|
||||
ExpiresInSeconds = "0",
|
||||
ErrorMessage = player["playabilityStatus"]?["reason"]?.ToString() ?? "Something has gone *really* wrong"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<YoutubeVideo> GetVideoAsync(string videoId, string language = "en", string region = "US")
|
||||
{
|
||||
JObject player = await MakeRequest("next", new Dictionary<string, object>
|
||||
{
|
||||
["videoId"] = videoId
|
||||
}, language, region);
|
||||
|
||||
JToken[] contents =
|
||||
(player?["contents"]?["twoColumnWatchNextResults"]?["results"]?["results"]?["contents"]
|
||||
?.ToObject<JArray>() ?? 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<JArray>());
|
||||
video.Description = Utils.ReadRuns(
|
||||
contents[1]
|
||||
["videoSecondaryInfoRenderer"]?["description"]?["runs"]?.ToObject<JArray>());
|
||||
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<JArray>() ?? 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<Thumbnail>()
|
||||
};
|
||||
video.UploadDate ??= "";
|
||||
}
|
||||
video.Recommended = ParseRenderers(
|
||||
player?["contents"]?["twoColumnWatchNextResults"]?["secondaryResults"]?["secondaryResults"]?
|
||||
["results"]?.ToObject<JArray>() ?? new JArray());
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
public async Task<YoutubeSearchResults> SearchAsync(string query, string continuation = null,
|
||||
string language = "en", string region = "US")
|
||||
{
|
||||
Dictionary<string, object> 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<string[]>() ?? Array.Empty<string>(),
|
||||
EstimatedResults = search?["estimatedResults"]?.ToObject<long>() ?? 0,
|
||||
Results = ParseRenderers(
|
||||
search?["contents"]?["twoColumnSearchResultsRenderer"]?["primaryContents"]?["sectionListRenderer"]?
|
||||
["contents"]?[0]?["itemSectionRenderer"]?["contents"]?.ToObject<JArray>() ??
|
||||
search?["onResponseReceivedCommands"]?[0]?["appendContinuationItemsAction"]?["continuationItems"]?
|
||||
[0]?["itemSectionRenderer"]?["contents"]?.ToObject<JArray>() ?? 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<YoutubePlaylist> GetPlaylistAsync(string id, string continuation = null,
|
||||
string language = "en", string region = "US")
|
||||
{
|
||||
Dictionary<string, object> 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<JArray>() ??
|
||||
playlist?["onResponseReceivedActions"]?[0]?["appendContinuationItemsAction"]?["continuationItems"]
|
||||
?.ToObject<JArray>() ?? 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<JArray>() ?? 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<YoutubeChannel> GetChannelAsync(string id, ChannelTabs tab = ChannelTabs.Home,
|
||||
string continuation = null, string language = "en", string region = "US")
|
||||
{
|
||||
Dictionary<string, object> 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<JArray>() ?? new JArray())
|
||||
.FirstOrDefault(x => x?["tabRenderer"]?["selected"]?.ToObject<bool>() ?? false)?["tabRenderer"]?[
|
||||
"content"]?
|
||||
["sectionListRenderer"]?["contents"]?.ToObject<JArray>();
|
||||
|
||||
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<JArray>() ?? new JArray()),
|
||||
Subscribers = channel?["header"]?["c4TabbedHeaderRenderer"]?["subscriberCountText"]?["simpleText"]
|
||||
?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<YoutubeTrends> GetExploreAsync(string browseId = null, string continuation = null, string language = "en", string region = "US")
|
||||
{
|
||||
Dictionary<string, object> 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<JArray>() ?? 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<JArray>() ??
|
||||
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
|
||||
Icon = (rendererObject?["iconImage"]?["thumbnails"]?.ToObject<JArray>() ??
|
||||
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
|
||||
Id = $"{rendererObject?["onTap"]?["browseEndpoint"]?["browseId"]}"
|
||||
};
|
||||
return category;
|
||||
}).ToArray(),
|
||||
Videos = ParseRenderers(mainArray)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<YoutubeLocals> GetLocalsAsync(string language = "en", string region = "US")
|
||||
{
|
||||
JObject locals = await MakeRequest("account/account_menu", new Dictionary<string, object>(), 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<JArray>()?.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<JArray>()?.ToDictionary(
|
||||
x => x?["compactLinkRenderer"]?["serviceEndpoint"]?["signalServiceEndpoint"]?
|
||||
["actions"]?[0]?["selectCountryCommand"]?["gl"]?.ToString(),
|
||||
x => x?["compactLinkRenderer"]?["title"]?["simpleText"]?.ToString())
|
||||
};
|
||||
}
|
||||
|
||||
private DynamicItem[] ParseRenderers(JArray renderersArray)
|
||||
{
|
||||
List<DynamicItem> items = new();
|
||||
|
||||
foreach (JToken jToken in renderersArray)
|
||||
{
|
||||
JObject recommendationContainer = jToken as JObject;
|
||||
string rendererName = recommendationContainer?.First?.Path.Split(".").Last() ?? "";
|
||||
JObject rendererItem = recommendationContainer?[rendererName]?.ToObject<JObject>();
|
||||
switch (rendererName)
|
||||
{
|
||||
case "videoRenderer":
|
||||
items.Add(new VideoItem
|
||||
{
|
||||
Id = rendererItem?["videoId"]?.ToString(),
|
||||
Title = Utils.ReadRuns(rendererItem?["title"]?["runs"]?.ToObject<JArray>() ??
|
||||
new JArray()),
|
||||
Thumbnails =
|
||||
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
|
||||
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<JArray>() ?? new JArray()).Select(Utils.ParseThumbnails)
|
||||
.ToArray()
|
||||
},
|
||||
Duration = rendererItem?["thumbnailOverlays"]?[0]?[
|
||||
"thumbnailOverlayTimeStatusRenderer"]?["text"]?["simpleText"]?.ToString(),
|
||||
Description = Utils.ReadRuns(rendererItem?["detailedMetadataSnippets"]?[0]?[
|
||||
"snippetText"]?["runs"]?.ToObject<JArray>() ?? new JArray())
|
||||
});
|
||||
break;
|
||||
case "gridVideoRenderer":
|
||||
items.Add(new VideoItem
|
||||
{
|
||||
Id = rendererItem?["videoId"]?.ToString(),
|
||||
Title = rendererItem?["title"]?["simpleText"]?.ToString() ?? Utils.ReadRuns(
|
||||
rendererItem?["title"]?["runs"]?.ToObject<JArray>() ?? new JArray()),
|
||||
Thumbnails =
|
||||
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
|
||||
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<JArray>() ??
|
||||
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<JArray>() ??
|
||||
new JArray()).Select(Utils.ParseThumbnails)
|
||||
.ToArray(), //
|
||||
Url = rendererItem?["navigationEndpoint"]?["commandMetadata"]?["webCommandMetadata"]?["url"]
|
||||
?.ToString(),
|
||||
Description =
|
||||
Utils.ReadRuns(rendererItem?["descriptionSnippet"]?["runs"]?.ToObject<JArray>() ??
|
||||
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<JArray>() ??
|
||||
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<JArray>() ??
|
||||
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
|
||||
Items = ParseRenderers(
|
||||
rendererItem?["content"]?["verticalListRenderer"]?["items"]
|
||||
?.ToObject<JArray>() ??
|
||||
rendererItem?["content"]?["horizontalListRenderer"]?["items"]
|
||||
?.ToObject<JArray>() ??
|
||||
rendererItem?["content"]?["expandedShelfContentsRenderer"]?["items"]
|
||||
?.ToObject<JArray>() ??
|
||||
new JArray()),
|
||||
CollapsedItemCount =
|
||||
rendererItem?["content"]?["verticalListRenderer"]?["collapsedItemCount"]
|
||||
?.ToObject<int>() ?? 0,
|
||||
Badges = ParseRenderers(rendererItem?["badges"]?.ToObject<JArray>() ?? new JArray())
|
||||
.Where(x => x is BadgeItem).Cast<BadgeItem>().ToArray(),
|
||||
});
|
||||
break;
|
||||
case "horizontalCardListRenderer":
|
||||
items.Add(new HorizontalCardListItem
|
||||
{
|
||||
Title = rendererItem?["header"]?["richListHeaderRenderer"]?["title"]?["simpleText"]
|
||||
?.ToString(),
|
||||
Items = ParseRenderers(rendererItem?["cards"]?.ToObject<JArray>() ?? new JArray())
|
||||
});
|
||||
break;
|
||||
case "searchRefinementCardRenderer":
|
||||
items.Add(new CardItem
|
||||
{
|
||||
Title = Utils.ReadRuns(rendererItem?["query"]?["runs"]?.ToObject<JArray>() ??
|
||||
new JArray()),
|
||||
Thumbnails = (rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
|
||||
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<JArray>() ??
|
||||
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<JArray>() ?? 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<JArray>() ?? 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<long>() ?? 0,
|
||||
Title = Utils.ReadRuns(rendererItem?["title"]?["runs"]?.ToObject<JArray>() ??
|
||||
new JArray()),
|
||||
Thumbnails =
|
||||
(rendererItem?["thumbnail"]?["thumbnails"]?.ToObject<JArray>() ??
|
||||
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<JArray>() ?? new JArray())
|
||||
});
|
||||
break;
|
||||
case "gridRenderer":
|
||||
items.Add(new ItemSectionItem
|
||||
{
|
||||
Contents = ParseRenderers(rendererItem?["items"]?.ToObject<JArray>() ?? 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<JArray>() ??
|
||||
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<JArray>() ??
|
||||
new JArray()).Select(Utils.ParseThumbnails).ToArray(),
|
||||
VideoCount = rendererItem?["videoCountText"]?["runs"]?[0]?["text"].ToObject<int>() ?? 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue