Newer
Older
commentsToken = continueRenderer.continuationEndpoint.continuationCommand.token;
}
}
});
}
});
}
Kelvin
committed
const commentSectionPanel = engagementPanels?.find(x=>x?.engagementPanelSectionListRenderer?.panelIdentifier == "engagement-panel-comments-section");
const altContinuation = commentSectionPanel?.engagementPanelSectionListRenderer?.content?.sectionListRenderer?.contents
?.find(y=>true)?.itemSectionRenderer;
if(altContinuation != null && !commentsToken && altContinuation.sectionIdentifier == "comment-item-section") {
const continuationRenderer = altContinuation?.contents?.find(y=>true)?.continuationItemRenderer;
const altToken = continuationRenderer?.continuationEndpoint?.continuationCommand?.token;
if(altToken)
commentsToken = altToken;
}
Kelvin
committed
return requestCommentPager(contextUrl, commentsToken, useLogin, useLogin) ?? new CommentPager([], false);
Kelvin
committed
function requestCommentPager(contextUrl, continuationToken, useLogin, useMobile) {
Kelvin
committed
}, useLogin, useMobile);
if(IS_TESTING)
console.log("data", data);
const endpoints = data?.onResponseReceivedCommands ?? data?.onResponseReceivedActions ?? data?.onResponseReceivedEndpoints;
if(!endpoints) {
log("Comment object:\n" + JSON.stringify(data, null, " "));
if(bridge.devSubmit) bridge.devSubmit("requestCommentPager - No comment endpoints", JSON.stringify(data));
throw new ScriptException("No comment endpoints provided by Youtube");
}
for(let i = 0; i < endpoints.length; i++) {
const endpoint = endpoints[i];
const continuationItems = endpoint.reloadContinuationItemsCommand?.continuationItems ??
endpoint.appendContinuationItemsAction?.continuationItems;
if(continuationItems && continuationItems.length > 0) {
let comments = [];
if(continuationItems && continuationItems.length > 0) {
for(let continuationItem of continuationItems) {
switchKey(continuationItem, {
commentThreadRenderer(renderer) {
const commentRenderer = renderer?.comment?.commentRenderer;
if(!commentRenderer)
return;
const replyCount = (commentRenderer.replyCount ? commentRenderer?.replyCount : 0);
let replyContinuation = renderer.replies?.commentRepliesRenderer?.contents?.length == 1 ?
(renderer.replies.commentRepliesRenderer.contents[0]?.continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token
?? renderer.replies.commentRepliesRenderer.contents[0]?.continuationItemRenderer?.button?.buttonRenderer?.command?.continuationCommand?.token) :
comments.push(extractCommentRenderer_Comment(contextUrl, commentRenderer, replyCount, replyContinuation, useLogin, useMobile));
comments.push(extractCommentRenderer_Comment(contextUrl, renderer, 0, null, useLogin, useMobile));
},
continuationItemRenderer(renderer) {
if(renderer?.continuationEndpoint?.continuationCommand?.token)
commentsContinuation = renderer?.continuationEndpoint?.continuationCommand?.token;
else if(renderer?.button?.buttonRenderer?.command?.continuationCommand?.token)
commentsContinuation = renderer.button.buttonRenderer.command.continuationCommand.token
}
});
}
if(comments.length > 0) {
return new YTCommentPager(comments, commentsContinuation, contextUrl, useLogin, useMobile);
if(data?.frameworkUpdates?.entityBatchUpdate?.mutations) {
log("New comments model");
const mutations = data.frameworkUpdates.entityBatchUpdate.mutations;
if(mutations.length > 0) {
const comments = [];
let parentItems = [];
for(let i = 0; i < endpoints.length; i++)
parentItems.push(...(endpoints[i].reloadContinuationItemsCommand?.continuationItems ??
endpoints[i].appendContinuationItemsAction?.continuationItems ??
[]));
parentItems = parentItems.filter(x=>x.commentThreadRenderer);
const commentObjects = mutations.filter(x=>x?.payload?.commentEntityPayload);
for(let commentObject of commentObjects) {
const cobj = commentObject?.payload?.commentEntityPayload ?? {};
const parent = parentItems.find(x=>x.commentThreadRenderer?.commentViewModel?.commentViewModel?.commentKey == commentObject.entityKey);
const replyContents = parent?.commentThreadRenderer?.replies?.commentRepliesRenderer?.contents;
const replyContinuation = ((replyContents?.length ?? 0) > 0) ?
(replyContents[0].continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token ??
replyContents[0].continuationItemRenderer?.button?.buttonRenderer?.command?.continuationCommand?.token) :
null;
const authorEndpoint = cobj.author?.channelCommand?.innertubeCommand?.commandMetadata?.webCommandMetadata?.url;
comments.push(new YTComment({
contextUrl: contextUrl,
author: new PlatformAuthorLink(new PlatformID(PLATFORM, cobj?.author?.displayName, config.id, PLATFORM_CLAIMTYPE), cobj.author.displayName, (authorEndpoint) ? URL_BASE + authorEndpoint : "", cobj.author.avatarThumbnailUrl),
message: cobj.properties?.content?.content ?? "",
rating: new RatingLikes(extractHumanNumber_Integer(cobj.toolbar?.likeCountLiked) ?? 0),
date: (extractAgoTextRuns_Timestamp(cobj?.properties?.publishedTime) ?? 0),
replyCount: extractFirstNumber_Integer(cobj?.toolbar?.replyCount) ?? 0,
context: { replyContinuation: replyContinuation, useLogin: useLogin + "", useMobile: useMobile + "" }
}));
}
if(comments.length > 0) {
return new YTCommentPager(comments, commentsContinuation, contextUrl, useLogin, useMobile);
log("Comment object:\n" + JSON.stringify(data, null, " "));
if(bridge.devSubmit) bridge.devSubmit("requestCommentPager - No comment endpoints", JSON.stringify(data));
throw new ScriptException("No valid comment endpoint provided by Youtube");
}
function extractSingleColumnBrowseResultsRenderer_Tabs(renderer, contextData) {
const tabs = [];
if(!renderer.tabs) {
if(bridge.devSubmit) bridge.devSubmit("extractSingleColumnBrowseResultsRenderer_Tabs - No tabs found", JSON.stringify(renderer));
throw new ScriptException("No tabs found");
}
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
for(let i = 0; i < renderer.tabs.length; i++) {
const tab = renderer.tabs[i];
if(!tab.tabRenderer)
continue;
const tabRenderer = tab.tabRenderer; //TODO: Check if this is ever null?
const isDefault = tabRenderer.selected;
const title = tabRenderer.title;
const content = tabRenderer.content;
if(!content)
continue; //.endpoint
const tabContentRendererName = Object.keys(content)[0];
if(!tabContentRendererName) continue;
let tabResult = undefined;
switchKey(content, {
richGridRenderer(renderer) {
tabResult = extractRichGridRenderer_Shelves(content[tabContentRendererName], contextData);
},
sectionListRenderer(renderer) {
if(!renderer.contents)
return;
tabResult = extractSectionListRenderer_Sections(renderer, contextData);
},
default() {
if(bridge.devSubmit) bridge.devSubmit("extractSingleColumnBrowseResultsRenderer_Tabs - Unknown tab renderer: " + tabContentRendererName, JSON.stringify(content));
throw new ScriptException("Unknown tab renderer: " + tabContentRendererName);
}
});
if(tabResult) {
tabResult.isDefault = !!isDefault;
tabResult.title = title;
tabs.push(tabResult);
}
}
return tabs;
}
function extractTwoColumnBrowseResultsRenderer_Tabs(renderer, contextData) {
const tabs = [];
if(!renderer.tabs)
{
if(bridge.devSubmit) bridge.devSubmit("extractTwoColumnBrowseResultsRenderer_Tabs - No tabs found", JSON.stringify(renderer));
throw new ScriptException("No tabs found");
}
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
for(let i = 0; i < renderer.tabs.length; i++) {
const tab = renderer.tabs[i];
if(!tab.tabRenderer && !tab.expandableTabRenderer)
continue;
const tabRenderer = tab.tabRenderer ?? tab.expandableTabRenderer;
const isDefault = tabRenderer.selected;
const title = tabRenderer.title;
const content = tabRenderer.content;
if(!content)
continue; //.endpoint
const tabContentRendererName = Object.keys(content)[0];
if(!tabContentRendererName) continue;
let tabResult = undefined;
switchKey(content, {
richGridRenderer(renderer) {
tabResult = extractRichGridRenderer_Shelves(renderer, contextData);
},
sectionListRenderer(renderer) {
//Channel sectioned tabs..
tabResult = extractSectionListRenderer_Sections(renderer, contextData)
},
default() {
if(bridge.devSubmit) bridge.devSubmit("extractTwoColumnBrowseResultsRenderer_Tabs - Unknown tab renderer: " + tabContentRendererName, JSON.stringify(renderer));
throw new ScriptException("Unknown tab renderer: " + tabContentRendererName);
}
});
if(tabResult) {
tabResult.isDefault = !!isDefault;
tabResult.title = title;
tabs.push(tabResult);
}
}
return tabs;
}
function extractRichGridRenderer_Shelves(richGridRenderer, contextData) {
const contents = richGridRenderer.contents;
let continuation = null;
for(let ci = 0; ci < contents.length; ci++) {
const content = contents[ci];
switchKey(content, {
richSectionRenderer(renderer) {
shelves.push(extractRichSectionRenderer_Shelf(renderer, contextData));
},
richItemRenderer(renderer) {
videos.push(extractRichItemRenderer_Video(renderer, contextData));
},
continuationItemRenderer(renderer) {
continuation = extractContinuationItemRenderer_Continuation(renderer, contextData);
},
const items = extractItemSectionRenderer_Shelves(renderer, contextData);
if(items.shelves)
shelves = shelves.concat(items.shelves);
if(items.videos)
videos = videos.concat(items.videos);
},
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
default(name) {
log("Unknown shelf/section renderer in extractRichGridRenderer_Shelves: " + name);
}
});
}
return {
shelves: shelves.filter(x=>x != null),
videos: videos.filter(x=>x != null),
continuation: continuation
};
}
function extractSectionListRenderer_Sections(sectionListRenderer, contextData) {
const contents = sectionListRenderer.contents;
let shelves = [];
let videos = [];
let channels = [];
let playlists = [];
let continuation = null;
for(let i = 0; i < contents.length; i++) {
const item = contents[i];
switchKey(item, {
itemSectionRenderer(renderer) {
const items = extractItemSectionRenderer_Shelves(renderer, contextData);
if(items.videos.length > 0)
videos.push(...items.videos);
if(items.channels.length > 0)
channels.push(...items.channels);
if(items.playlists.length > 0)
playlists.push(...items.playlists);
if(items.shelves)
shelves.push(...items.shelves);
if(items.continuation)
subContinuations.push(items.continuation);
},
continuationItemRenderer(renderer) {
continuation = extractContinuationItemRenderer_Continuation(renderer, contextData);
}
});
}
return {
shelves: shelves,
videos: videos,
channels: channels,
playlists: playlists,
continuation: continuation,
subContinuations: subContinuations
};
}
function extractItemSectionRenderer_Shelves(itemSectionRenderer, contextData) {
const contents = itemSectionRenderer.contents ?? itemSectionRenderer.results;
let shelves = [];
let videos = [];
let channels = [];
let playlists = [];
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
contents.forEach((item)=>{
switchKey(item, {
channelFeaturedContentRenderer(renderer) {
if(renderer.items) {
let videos = switchKeyVideos(renderer.items, contextData);
if(videos && videos.length > 0) {
shelves.push({
name: "Featured",
type: "Shelf",
videos: videos
});
}
}
},
channelRenderer(renderer) {
const channel = extractChannelRenderer_AuthorLink(renderer);
if(channel)
channels.push(channel);
},
playlistRenderer(renderer) {
const playlist = extractPlaylistRenderer_Playlist(renderer);
if(playlist)
playlists.push(playlist);
},
shelfRenderer(renderer) {
const shelf = extractShelfRenderer_Shelf(renderer);
if(shelf)
shelves.push(shelf);
},
gridRenderer(renderer) {
const shelf = extractGridRenderer_Shelf(renderer, contextData);
if(shelf.playlists.length > 0)
playlists.push(...shelf.playlists);
if(shelf.continuation)
continuationToken = shelf.continuation;
continuationItemRenderer(renderer) {
const token = renderer?.continuationEndpoint?.continuationCommand?.token
if(token)
continuationToken = token;
},
default() {
const video = switchKeyVideo(item, contextData);
if(video)
videos.push(video);
}
});
});
return {
shelves: shelves.filter(x=>x != null),
videos: videos.filter(x=>x != null),
channels: channels.filter(x=>x != null),
playlists: playlists.filter(x=>x != null),
continuation: continuationToken
function extractGridRenderer_Shelf(gridRenderer, contextData) {
const contents = gridRenderer.items;
let shelves = [];
let videos = [];
let channels = [];
let playlists = [];
contents.forEach((item)=>{
switchKey(item, {
gridPlaylistRenderer(renderer) {
const playlist = extractPlaylistRenderer_Playlist(renderer, contextData);
if(playlist)
playlists.push(playlist);
},
lockupViewModel(renderer) {
const playlist = extractPlaylistLockupViewModel_Playlist(renderer, contextData);
if(playlist)
playlists.push(playlist);
},
continuationItemRenderer(renderer) {
if(renderer?.continuationEndpoint?.continuationCommand?.token)
continuation = renderer.continuationEndpoint.continuationCommand.token;
},
default() {
const video = switchKeyVideo(item, contextData);
if(video)
videos.push(video);
}
});
});
return {
videos: videos.filter(x=>x != null),
channels: channels.filter(x=>x != null),
playlists: playlists.filter(x=>x != null),
continuation: continuation
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
function switchKeyVideos(contents, contextData) {
let videos = [];
for(let content of contents) {
const video = switchKeyVideo(content, contextData);
if(video)
videos.push(video);
}
return videos;
}
function switchKeyVideo(content, contextData) {
return switchKey(content, {
channelFeaturedContentRenderer(renderer) {
return extractVideoRenderer_Video(renderer, contextData);
},
videoRenderer(renderer) {
return extractVideoRenderer_Video(renderer, contextData);
},
compactVideoRenderer(renderer) {
return extractVideoWithContextRenderer_Video(renderer, contextData);
},
videoWithContextRenderer(renderer) {
return extractVideoWithContextRenderer_Video(renderer, contextData);
},
reelItemRenderer(renderer) {
return extractReelItemRenderer_Video(renderer, contextData);
},
adSlotRenderer(adSlot) {
return null;
},
default(name) {
return null;
}
});
}
//#endregion
//#region Element Extractors
function extractShelfRenderer_Shelf(shelfRenderer, contextData) {
let name = extractText_String(shelfRenderer.title);
return switchKey(shelfRenderer.content, {
expandedShelfContentsRenderer(renderer) {
return {
name: name,
type: "Shelf",
videos: switchKeyVideos(renderer.items)
};
},
default() {
return null;
}
});
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
}
function extractContinuationItemRenderer_Continuation(continuationItemRenderer) {
return {
url: continuationItemRenderer.continuationEndpoint.commandMetadata.apiUrl, //TODO: See if this is useful at all
token: continuationItemRenderer.continuationEndpoint.continuationCommand.token
};
}
function extractRichSectionRenderer_Shelf(sectionRenderer, contextData) {
const content = sectionRenderer.content;
return switchKey(content, {
richShelfRenderer(renderer) {
return extractRichShelfRenderer_Shelf(renderer, contextData);
},
default(name) {
log("Unknown shelf renderer in extractRichSectionRenderer_Shelf: " + name);
return null;
//throw new ScriptException("Unknown shelf renderer: " + name);
}
});
}
function extractRichShelfRenderer_Shelf(shelfRenderer, contextData) {
const shelf = {
name: extractRuns_String(shelfRenderer.title?.runs),
type: "Shelf",
videos: []
};
for(let itemi = 0; itemi < shelfRenderer.contents.length; itemi++) {
const item = shelfRenderer.contents[itemi];
switchKey(item, {
richItemRenderer(renderer) {
shelf.videos.push(extractRichItemRenderer_Video(renderer), contextData);
},
default(name) {
log("Unknown shelf renderer in extractRichShelfRenderer_Shelf: " + name);
//throw new ScriptException("Unknown item renderer: " + name);
}
});
}
shelf.videos = shelf.videos.filter(x=>x != null);
return shelf;
}
//#endregion
//#region Item Extractor
function extractRichItemRenderer_Video(itemRenderer, contextData) {
const content = itemRenderer.content;
return switchKeyVideo(content, contextData);
}
function extractVideoWithContextRenderer_Video(videoRenderer, contextData) {
const liveBadges = videoRenderer.thumbnailOverlays?.filter(x=>
x.thumbnailOverlayTimeStatusRenderer?.accessibility?.accessibilityData?.label == "LIVE");
let isLive = liveBadges != null && liveBadges.length > 0;
isLive = isLive || ((videoRenderer.badges?.filter(x=>x.metadataBadgeRenderer?.style == "BADGE_STYLE_TYPE_LIVE_NOW")?.length ?? 0) > 0)
let plannedDate = null;
if(videoRenderer.upcomingEventData?.startTime)
plannedDate = parseInt(videoRenderer.upcomingEventData.startTime);
isLive = isLive || !!plannedDate
//if(!isLive && !videoRenderer.publishedTimeText?.simpleText)
// return null; //Not a normal video
const author = (contextData && contextData.authorLink) ?
contextData.authorLink : extractVideoWithContextRenderer_AuthorLink(videoRenderer);
if(IS_TESTING)
//if(!videoRenderer?.lengthText?.runs || !videoRenderer.publishedTimeText?.runs)
// isLive = true; //If no length, live after all?
if(videoRenderer?.shortViewCountText)
viewCount = extractHumanNumber_Integer(extractText_String(videoRenderer.shortViewCountText));
else log("No viewcount found on video " + videoRenderer.videoId);
const title = (videoRenderer.headline) ? extractText_String(videoRenderer.headline) : extractText_String(videoRenderer.title);
return new PlatformVideo({
id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id),
name: escapeUnicode(title),
thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail),
author: author,
uploadDate: plannedDate ?? parseInt(new Date().getTime() / 1000),
duration: 0,
viewCount: viewCount,
url: URL_BASE + "/watch?v=" + videoRenderer.videoId,
isLive: true,
extractType: "VideoWithContext"
});
return new PlatformVideo({
id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id),
name: escapeUnicode(title),
thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail),
author: author,
uploadDate: parseInt(extractAgoText_Timestamp(extractText_String(videoRenderer.publishedTimeText))),
duration: extractHumanTime_Seconds(extractText_String(videoRenderer.lengthText)),
viewCount: viewCount,
url: URL_BASE + "/watch?v=" + videoRenderer.videoId,
isLive: false,
extractType: "VideoWithContext"
});
}
function extractVideoRenderer_Video(videoRenderer, contextData) {
const liveBadges = videoRenderer.badges?.filter(x=>x.metadataBadgeRenderer?.label == "LIVE");
const liveOverlays = videoRenderer.thumbnailOverlays?.filter(x=>
x.thumbnailOverlayTimeStatusRenderer?.style == "LIVE" ||
x.thumbnailOverlayTimeStatusRenderer?.accessibility?.accessibilityData?.label == "LIVE");
let isLive = (liveBadges != null && liveBadges.length > 0) ||
(liveOverlays != null && liveOverlays.length > 0);
let plannedDate = null;
if(videoRenderer.upcomingEventData?.startTime)
plannedDate = parseInt(videoRenderer.upcomingEventData.startTime);
if(plannedDate)
isLive = true;
isLive = isLive || ((videoRenderer.badges?.filter(x=>x.metadataBadgeRenderer?.style == "BADGE_STYLE_TYPE_LIVE_NOW")?.length ?? 0) > 0)
if(!isLive && !videoRenderer.publishedTimeText?.simpleText)
return null; //Not a normal video
const author = (contextData && contextData.authorLink) ?
contextData.authorLink : extractVideoRenderer_AuthorLink(videoRenderer);
if(IS_TESTING)
console.log(videoRenderer);
// if(!videoRenderer?.lengthText?.simpleText)
// isLive = true; //If no length, live after all?
if(isLive)
return new PlatformVideo({
id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id),
name: escapeUnicode(extractRuns_String(videoRenderer.title.runs)),
thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail),
author: author,
uploadDate: plannedDate ?? parseInt(new Date().getTime()/1000),
duration: 0,
viewCount: !videoRenderer.viewCountText ? 0 : extractRuns_ViewerCount(videoRenderer.viewCountText.runs),
url: URL_BASE + "/watch?v=" + videoRenderer.videoId,
isLive: true,
extractType: "Video"
});
else
return new PlatformVideo({
id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id),
name: escapeUnicode(extractRuns_String(videoRenderer.title.runs)),
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail),
author: author,
uploadDate: parseInt(extractAgoText_Timestamp(videoRenderer.publishedTimeText.simpleText)),
duration: extractHumanTime_Seconds(videoRenderer.lengthText.simpleText),
viewCount: extractFirstNumber_Integer(extractText_String(videoRenderer.viewCountText)),
url: URL_BASE + "/watch?v=" + videoRenderer.videoId,
isLive: false,
extractType: "Video"
});
}
function extractReelItemRenderer_Video(reelItemRenderer) {
//We don't do shorts for now..
return null;
}
function extractPlaylistVideoRenderer_Video(videoRenderer, contextData) {
if(!videoRenderer.lengthText)
return null;
const author = (contextData && contextData.authorLink) ?
contextData.authorLink : extractRuns_AuthorLink(videoRenderer.shortBylineText?.runs);
if(IS_TESTING)
console.log(videoRenderer);
let date = 0;
if(videoRenderer?.publishedTimeText?.simpleText)
date = parseInt(extractAgoText_Timestamp(videoRenderer.publishedTimeText.simpleText));
return new PlatformVideo({
id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id),
name: escapeUnicode(extractRuns_String(videoRenderer.title.runs)),
thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail),
author: author,
uploadDate: date,
duration: extractHumanTime_Seconds(extractText_String(videoRenderer.lengthText)),
viewCount: 0,//extractFirstNumber_Integer(videoRenderer.viewCountText.simpleText),
url: URL_BASE + "/watch?v=" + videoRenderer.videoId,
isLive: false,
extractType: "Video"
});
}
function extractPlaylistRenderer_Playlist(playlistRenderer, contextData) {
const author = (contextData && contextData.authorLink) ?
contextData.authorLink : extractRuns_AuthorLink(playlistRenderer.shortBylineText?.runs);
let thumbnail = (playlistRenderer.thumbnails && playlistRenderer.thumbnails.length > 0) ? extractThumbnail_BestUrl(playlistRenderer.thumbnails[0]) : null;
if(!thumbnail && playlistRenderer.thumbnail)
thumbnail = extractThumbnail_BestUrl(playlistRenderer.thumbnail);
return new PlatformPlaylist({
id: new PlatformID(PLATFORM, playlistRenderer.playlistId, config.id),
author: author,
name: extractText_String(playlistRenderer.title),
url: URL_PLAYLIST + playlistRenderer.playlistId,
videoCount: extractFirstNumber_Integer(extractText_String(playlistRenderer.videoCountText)),
});
}
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
const REGEX_VIDEO_COUNT = /[0-9]+ videos/;
function extractPlaylistLockupViewModel_Playlist(playlistRenderer, contextData) {
const author = (contextData && contextData.authorLink) ?
contextData.authorLink : null;
const thumbnailViewModel = playlistRenderer?.contentImage?.collectionThumbnailViewModel?.primaryThumbnail?.thumbnailViewModel;
let thumbnail = (thumbnailViewModel?.image?.sources?.length > 0) ?
thumbnailViewModel.image.sources[0].url :
null;
thumbnail = thumbnail ?? "";
let videoCount = -1;
if(thumbnailViewModel?.overlays?.length > 0) {
for(let overlay of thumbnailViewModel?.overlays) {
if(overlay.thumbnailOverlayBadgeViewModel?.thumbnailBadges) {
for(let subOverlay of overlay.thumbnailOverlayBadgeViewModel.thumbnailBadges) {
if(subOverlay.thumbnailBadgeViewModel?.text) {
if(REGEX_VIDEO_COUNT.test(subOverlay.thumbnailBadgeViewModel.text)) {
videoCount = extractFirstNumber_Integer(subOverlay.thumbnailBadgeViewModel.text);
break;
}
}
}
}
if(videoCount >= 0)
break;
}
}
let id = playlistRenderer?.rendererContext?.commandContext?.onTap?.innertubeCommand?.watchEndpoint?.playlistId;
if(IS_TESTING) {
console.log("New playlist model: ", [
thumbnail, author, id, videoCount
]);
}
return new PlatformPlaylist({
id: new PlatformID(PLATFORM, playlistRenderer.playlistId, config.id),
author: author,
name: extractText_String(playlistRenderer.metadata?.lockupMetadataViewModel?.title),
thumbnail: thumbnail,
url: URL_PLAYLIST + id,
videoCount: videoCount,
});
}
function extractChannelRenderer_AuthorLink(channelRenderer) {
const id = channelRenderer.channelId;
const name = extractText_String(channelRenderer.title);
const channelUrl = extractNavigationEndpoint_Url(channelRenderer.navigationEndpoint);
let thumbUrl = extractThumbnail_BestUrl(channelRenderer.thumbnail);
if(thumbUrl.startsWith("//"))
thumbUrl = "https:" + thumbUrl;
const subscribers = extractHumanNumber_Integer(extractText_String(channelRenderer.videoCountText));
return new PlatformAuthorLink(new PlatformID(PLATFORM, id, config.id, PLATFORM_CLAIMTYPE), name, channelUrl, thumbUrl, subscribers);
}
function extractRuns_AuthorLink(runs) {
if(!runs || runs.length == 0)
return null;
const id = runs[0]?.navigationEndpoint?.browseEndpoint?.browseId;
const name = extractRuns_String(runs);
const channelUrl = extractNavigationEndpoint_Url(runs[0]?.navigationEndpoint);
const thumbUrl = null;
return new PlatformAuthorLink(new PlatformID(PLATFORM, id, config.id, PLATFORM_CLAIMTYPE), name, channelUrl, thumbUrl ?? "");
return new Thumbnails(thumbnail.thumbnails.map(x=>new Thumbnail(escapeUnicode(x.url), x.height)));
}
function extractThumbnail_BestUrl(thumbnail) {
if(!thumbnail?.thumbnails || thumbnail.thumbnails.length <= 0)
return null;
let bestUrl = thumbnail.thumbnails[0].url;
let bestHeight = thumbnail.thumbnails[0].height;
for(let thumb of thumbnail.thumbnails)
if(thumb.height > bestHeight) {
bestUrl = thumb.url;
bestHeight = thumb.height;
}
return bestUrl;
}
function extractVideoWithContextRenderer_AuthorLink(videoRenderer) {
let id = videoRenderer.channelThumbnail?.channelThumbnailWithLinkRenderer?.navigationEndpoint?.browseEndpoint?.browseId;
const name = extractRuns_String(videoRenderer.shortBylineText.runs);
const channelThumbs = videoRenderer.channelThumbnail.channelThumbnailWithLinkRenderer?.thumbnail?.thumbnails;
const thumbUrl = channelThumbs && channelThumbs.length > 0 ? channelThumbs[0].url : null;
let channelUrl = videoRenderer.channelThumbnail?.channelThumbnailWithLinkRenderer?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl;
if(channelUrl) channelUrl = URL_BASE + channelUrl;
return new PlatformAuthorLink(new PlatformID(PLATFORM, id, config.id, PLATFORM_CLAIMTYPE), name, channelUrl, thumbUrl);
}
function extractVideoRenderer_AuthorLink(videoRenderer) {
const id = videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer?.navigationEndpoint?.browseEndpoint?.browseId;
const name = extractText_String(videoRenderer.ownerText)//extractRuns_String(videoRenderer.ownerText.runs);
const channelIcon = videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer;
const thumbUrl = channelIcon.thumbnail.thumbnails[0].url;
const channelUrl = (!id) ? extractRuns_Url(videoRenderer.ownerText.runs) : URL_BASE + "/channel/" + id;
return new PlatformAuthorLink(new PlatformID(PLATFORM, id, config.id, PLATFORM_CLAIMTYPE), name, channelUrl, thumbUrl);
function extractCommentRenderer_Comment(contextUrl, commentRenderer, replyCount, replyContinuation, useLogin, useMobile) {
const authorName = extractText_String(commentRenderer.authorText) ?? "";
const authorEndpoint = commentRenderer.authorEndpoint?.commandMetadata?.webCommandMetadata?.url ?? "";
const authorThumbnail = (commentRenderer.authorThumbnail?.thumbnails ?
commentRenderer.authorThumbnail.thumbnails[commentRenderer.authorThumbnail.thumbnails.length - 1].url :
""
);
return new YTComment({
contextUrl: contextUrl,
author: new PlatformAuthorLink(new PlatformID(PLATFORM, null, config.id, PLATFORM_CLAIMTYPE), authorName, URL_BASE + authorEndpoint, authorThumbnail),
message: extractRuns_String(commentRenderer.contentText?.runs) ?? "",
rating: new RatingLikes(commentRenderer?.voteCount?.simpleText ? extractHumanNumber_Integer(commentRenderer.voteCount.simpleText) : 0),
date: (commentRenderer.publishedTimeText?.runs ? extractAgoTextRuns_Timestamp(commentRenderer.publishedTimeText.runs) : 0),
replyCount: replyCount ?? 0,
context: { replyContinuation: replyContinuation, useLogin: useLogin + "", useMobile: useMobile + "" }
function convertIfOtherUrl(url) {
url = convertIfShortUrl(url);
url = convertIfEmbedUrl(url);
Kelvin
committed
url = convertIfMusicUrl(url);
return url;
}
function convertIfMusicUrl(url) {
const musicMatch = url.match(REGEX_VIDEO_URL_DESKTOP);
if(musicMatch && musicMatch.length == 3 && musicMatch[1]?.toLowerCase() == "music")
url = URL_BASE + "/watch?v=" + musicMatch[1];
return url;
}
function convertIfEmbedUrl(url) {
const embedMatch = url.match(REGEX_VIDEO_URL_EMBED);
if(embedMatch && embedMatch.length == 3) {
let id = embedMatch[2];
if(id.indexOf("?") > 0)
id = id.substring(0, id.indexOf("?"));
url = URL_BASE + "/watch?v=" + id;
}
return url;
}
function convertIfShortUrl(url) {
const shortMatch = url.match(REGEX_VIDEO_URL_SHORT);
if(shortMatch && shortMatch.length == 3) {
let id = shortMatch[2];
if(id.indexOf("?") > 0)
id = id.substring(0, id.indexOf("?"));
url = URL_BASE + "/watch?v=" + id;
}
return url;
}
//#region Basic Extractors
function extractText_String(item) {
if(typeof item === 'string')
return item;
if(item?.simpleText)
return item.simpleText;
if(item?.runs)
return extractRuns_String(item.runs);
if(item?.content && typeof item.content == 'string')
return item.content;
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
if(item)
log("Unknown string object: " + JSON.stringify(item, null, " "));
return null;
}
function extractRuns_String(runs) {
if(!runs)
return null;
let str = "";
for(let runi = 0; runi < runs.length; runi++) {
const run = runs[runi];
if(run.text)
str += run.text;
else if(run.emoji?.image?.accessibility?.accessibilityData?.label)
str += "__" + run.emoji?.image?.accessibility?.accessibilityData?.label + "__"
}
return str;
}
function extractRuns_Html(runs) {
if(!runs)
return null;
let str = "";
for(let runi = 0; runi < runs.length; runi++) {
const run = runs[runi];
if(run.text)
str += run.text;
}
return str;
}
function extractRuns_Url(runs) {
for(let runi = 0; runi < runs.length; runi++) {
const run = runs[runi];
if(run.navigationEndpoint && run.navigationEndpoint.browseEndpoint && run.navigationEndpoint.browseEndpoint.canonicalBaseUrl)
return URL_BASE + run.navigationEndpoint.browseEndpoint.canonicalBaseUrl;
}
}
function extractNavigationEndpoint_Url(navEndpoint, baseUrl) {
if(!baseUrl)
baseUrl = URL_BASE;
if(!navEndpoint)
return null;
if(navEndpoint?.browseEndpoint?.browseId && navEndpoint?.browseEndpoint?.canonicalBaseUrl && navEndpoint.browseEndpoint.canonicalBaseUrl.startsWith("/@"))
return baseUrl + "/channel/" + navEndpoint?.browseEndpoint?.browseId;
if(navEndpoint?.browseEndpoint?.canonicalBaseUrl)
return baseUrl + navEndpoint?.browseEndpoint?.canonicalBaseUrl;
if(navEndpoint.commandMetadata?.webCommandMetadata?.url)
return baseUrl + navEndpoint.commandMetadata?.webCommandMetadata?.url;
return null;
}
function extractAgoTextRuns_Timestamp(runs) {
const runStr = (typeof runs === "string") ? runs : extractRuns_String(runs);
return extractAgoText_Timestamp(runStr);
}
function extractAgoText_Timestamp(str) {
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
const match = str.match(REGEX_HUMAN_AGO);
if(!match)
return 0;
const value = parseInt(match[1]);
const now = parseInt(new Date().getTime() / 1000);
switch(match[2]) {
case "second":
case "seconds":
return now - value;
case "minute":
case "minutes":
return now - value * 60;
case "hour":
case "hours":
return now - value * 60 * 60;
case "day":
case "days":
return now - value * 60 * 60 * 24;
case "week":
case "weeks":
return now - value * 60 * 60 * 24 * 7;
case "month":
case "months":
return now - value * 60 * 60 * 24 * 30; //For now it will suffice
case "year":
case "years":
return now - value * 60 * 60 * 24 * 365;
default:
if(bridge.devSubmit) bridge.devSubmit("extractAgoText_Timestamp - Unknown time type: " + match[2], match[2]);
throw new ScriptException("Unknown time type: " + match[2]);
}
}
function extractRuns_ViewerCount(runs) {
if(runs && runs.length > 0) {
const item = runs[0].text.replaceAll(".", "").replaceAll(",", "");
if(isNaN(item))
return -1;
return parseInt(item);
}
return -1;
}
function extractHumanTime_Seconds(str) {
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
if(str.indexOf(" ") >= 0)
str = str.split(" ")[0];
const parts = str.split(":");
let scale = 1;
let seconds = 0;
for(let i = parts.length-1; i >= 0; i--) {
if(isNaN(parts[i]))
return seconds;
seconds += parseInt(parts[i]) * scale;
scale *= 60;
}
return parseInt(seconds);
}
function extractFirstNumber_Integer(str) {
if(str) {
const parts = str.split(' ');
if(parts && parts.length > 0) {
const num = parts[0].replaceAll(".","").replaceAll(",","");
if(isNaN(num))
return -1;
return parseInt(num);
}
}
return -1;
}
function extractHumanNumber_Integer(str) {
if(!str)
return -1;
const match = str.match(REGEX_HUMAN_NUMBER);
if(!match)
return extractFirstNumber_Integer(str);
const value = parseFloat(match[1]);
switch(match[2]) {
case "T":
return parseInt(1000000000000 * value);
case "B":
return parseInt(1000000000 * value);
case "M":
return parseInt(1000000 * value);
case "K":
return parseInt(1000 * value);
default:
return parseInt(value);
}
}
function extractDate_Timestamp(dateStr) {
if(!dateStr)
return -1;
if(dateStr.indexOf("ago") > 0)
return extractAgoText_Timestamp(dateStr);