Compare commits

...

3 Commits

Author SHA1 Message Date
LuanRT
09321408e5 chore: v6.1.0 release 2023-08-27 19:44:15 +00:00
LuanRT
848c7e68e2 chore: v6.0.2 release 2023-08-24 20:07:16 +00:00
LuanRT
bc437015e2 chore: v6.0.1 release 2023-08-22 12:09:04 +00:00
13 changed files with 188 additions and 84 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "youtubei.js",
"version": "6.0.0",
"version": "6.1.0",
"description": "A wrapper around YouTube's private API. Supports YouTube, YouTube Music, YouTube Kids and YouTube Studio (WIP).",
"type": "module",
"types": "./dist/src/platform/lib.d.ts",

View File

@@ -0,0 +1,19 @@
import Button from './Button.ts';
import Text from './misc/Text.ts';
import { YTNode } from '../helpers.ts';
import { Parser, type RawNode } from '../index.ts';
export default class AlertWithButton extends YTNode {
static type = 'AlertWithButton';
text: Text;
alert_type: string;
dismiss_button: Button | null;
constructor(data: RawNode) {
super();
this.text = new Text(data.text);
this.alert_type = data.type;
this.dismiss_button = Parser.parseItem(data.dismissButton, Button);
}
}

View File

@@ -2,6 +2,7 @@ import { YTNode } from '../helpers.ts';
import Parser, { type RawNode } from '../index.ts';
import Button from './Button.ts';
import ChannelHeaderLinks from './ChannelHeaderLinks.ts';
import ChannelHeaderLinksView from './ChannelHeaderLinksView.ts';
import SubscribeButton from './SubscribeButton.ts';
import Author from './misc/Author.ts';
import Text from './misc/Text.ts';
@@ -18,7 +19,7 @@ export default class C4TabbedHeader extends YTNode {
videos_count?: Text;
sponsor_button?: Button | null;
subscribe_button?: SubscribeButton | Button | null;
header_links?: ChannelHeaderLinks | null;
header_links?: ChannelHeaderLinks | ChannelHeaderLinksView | null;
channel_handle?: Text;
channel_id?: string;
@@ -58,7 +59,7 @@ export default class C4TabbedHeader extends YTNode {
}
if (Reflect.has(data, 'headerLinks')) {
this.header_links = Parser.parseItem(data.headerLinks, ChannelHeaderLinks);
this.header_links = Parser.parseItem(data.headerLinks, [ ChannelHeaderLinks, ChannelHeaderLinksView ]);
}
if (Reflect.has(data, 'channelHandleText')) {

View File

@@ -0,0 +1,22 @@
import { YTNode } from '../helpers.ts';
import type { RawNode } from '../index.ts';
import Text from './misc/Text.ts';
export default class ChannelHeaderLinksView extends YTNode {
static type = 'ChannelHeaderLinksView';
first_link?: Text;
more?: Text;
constructor(data: RawNode) {
super();
if (Reflect.has(data, 'firstLink')) {
this.first_link = Text.fromAttributed(data.firstLink);
}
if (Reflect.has(data, 'more')) {
this.more = Text.fromAttributed(data.more);
}
}
}

View File

@@ -0,0 +1,57 @@
import { YTNode, type ObservedArray } from '../helpers.ts';
import type { RawNode } from '../index.ts';
import Parser from '../index.ts';
import Author from './misc/Author.ts';
import NavigationEndpoint from './NavigationEndpoint.ts';
import Thumbnail from './misc/Thumbnail.ts';
import Menu from './menus/Menu.ts';
import { timeToSeconds } from '../../utils/Utils.ts';
export default class CompactMovie extends YTNode {
static type = 'CompactMovie';
id: string;
title: Text;
top_metadata_items: Text;
thumbnails: Thumbnail[];
thumbnail_overlays: ObservedArray<YTNode>;
author: Author;
duration: {
text: string;
seconds: number;
};
endpoint: NavigationEndpoint;
badges: ObservedArray<YTNode>;
use_vertical_poster: boolean;
menu: Menu | null;
constructor(data: RawNode) {
super();
console.log(data);
const overlay_time_status = data.thumbnailOverlays
.find((overlay: RawNode) => overlay.thumbnailOverlayTimeStatusRenderer)
?.thumbnailOverlayTimeStatusRenderer.text || 'N/A';
this.id = data.videoId;
this.title = new Text(data.title);
this.top_metadata_items = new Text(data.topMetadataItems);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
this.author = new Author(data.shortBylineText);
const durationText = data.lengthText ? new Text(data.lengthText).toString() : new Text(overlay_time_status).toString();
this.duration = {
text: durationText,
seconds: timeToSeconds(durationText)
};
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.badges = Parser.parseArray(data.badges);
this.use_vertical_poster = data.useVerticalPoster;
this.menu = Parser.parseItem(data.menu, Menu);
}
}

View File

@@ -17,7 +17,7 @@ export default class SearchSubMenu extends YTNode {
if (Reflect.has(data, 'title'))
this.title = new Text(data.title);
if (!Reflect.has(data, 'groups'))
if (Reflect.has(data, 'groups'))
this.groups = Parser.parseArray(data.groups, SearchFilterGroup);
if (Reflect.has(data, 'button'))

View File

@@ -24,7 +24,7 @@ export default class VideoSecondaryInfo extends YTNode {
this.description = new Text(data.description);
if (Reflect.has(data, 'attributedDescription')) {
this.description = new Text(this.#convertAttributedDescriptionToRuns(data.attributedDescription));
this.description = Text.fromAttributed(data.attributedDescription);
}
this.subscribe_button = Parser.parseItem(data.subscribeButton, [ SubscribeButton, Button ]);
@@ -34,72 +34,4 @@ export default class VideoSecondaryInfo extends YTNode {
this.default_expanded = data.defaultExpanded;
this.description_collapsed_lines = data.descriptionCollapsedLines;
}
#convertAttributedDescriptionToRuns(description: RawNode) {
const runs: {
text: string,
navigationEndpoint?: RawNode,
attachment?: RawNode
}[] = [];
const content = description.content;
const command_runs = description.commandRuns;
let last_end_index = 0;
if (command_runs) {
for (const item of command_runs) {
const length: number = item.length;
const start_index: number = item.startIndex;
if (start_index > last_end_index) {
runs.push({
text: content.slice(last_end_index, start_index)
});
}
if (Reflect.has(item, 'onTap')) {
let attachment = null;
if (Reflect.has(description, 'attachmentRuns')) {
const attachment_runs = description.attachmentRuns;
for (const attatchment_run of attachment_runs) {
if ((attatchment_run.startIndex - 2) == start_index) {
attachment = attatchment_run;
break;
}
}
}
if (attachment) {
runs.push({
text: content.slice(start_index, start_index + length),
navigationEndpoint: item.onTap,
attachment
});
} else {
runs.push({
text: content.slice(start_index, start_index + length),
navigationEndpoint: item.onTap
});
}
}
last_end_index = start_index + length;
}
if (last_end_index < content.length) {
runs.push({
text: content.slice(last_end_index)
});
}
} else {
runs.push({
text: content
});
}
return { runs };
}
}

View File

@@ -46,6 +46,74 @@ export default class Text {
}
}
static fromAttributed(data: RawNode): Text {
const runs: {
text: string,
navigationEndpoint?: RawNode,
attachment?: RawNode
}[] = [];
const content = data.content;
const command_runs = data.commandRuns;
let last_end_index = 0;
if (command_runs) {
for (const item of command_runs) {
const length: number = item.length;
const start_index: number = item.startIndex;
if (start_index > last_end_index) {
runs.push({
text: content.slice(last_end_index, start_index)
});
}
if (Reflect.has(item, 'onTap')) {
let attachment = null;
if (Reflect.has(data, 'attachmentRuns')) {
const attachment_runs = data.attachmentRuns;
for (const attatchment_run of attachment_runs) {
if ((attatchment_run.startIndex - 2) == start_index) {
attachment = attatchment_run;
break;
}
}
}
if (attachment) {
runs.push({
text: content.slice(start_index, start_index + length),
navigationEndpoint: item.onTap,
attachment
});
} else {
runs.push({
text: content.slice(start_index, start_index + length),
navigationEndpoint: item.onTap
});
}
}
last_end_index = start_index + length;
}
if (last_end_index < content.length) {
runs.push({
text: content.slice(last_end_index)
});
}
} else {
runs.push({
text: content
});
}
return new Text({ runs });
}
/**
* Converts the text to HTML.
* @returns The HTML.

View File

@@ -8,6 +8,7 @@ export { default as AccountSectionList } from './classes/AccountSectionList.ts';
export { default as AppendContinuationItemsAction } from './classes/actions/AppendContinuationItemsAction.ts';
export { default as OpenPopupAction } from './classes/actions/OpenPopupAction.ts';
export { default as Alert } from './classes/Alert.ts';
export { default as AlertWithButton } from './classes/AlertWithButton.ts';
export { default as AnalyticsMainAppKeyMetrics } from './classes/analytics/AnalyticsMainAppKeyMetrics.ts';
export { default as AnalyticsRoot } from './classes/analytics/AnalyticsRoot.ts';
export { default as AnalyticsShortsCarouselCard } from './classes/analytics/AnalyticsShortsCarouselCard.ts';
@@ -36,6 +37,7 @@ export { default as ChannelAboutFullMetadata } from './classes/ChannelAboutFullM
export { default as ChannelAgeGate } from './classes/ChannelAgeGate.ts';
export { default as ChannelFeaturedContent } from './classes/ChannelFeaturedContent.ts';
export { default as ChannelHeaderLinks } from './classes/ChannelHeaderLinks.ts';
export { default as ChannelHeaderLinksView } from './classes/ChannelHeaderLinksView.ts';
export { default as ChannelMetadata } from './classes/ChannelMetadata.ts';
export { default as ChannelMobileHeader } from './classes/ChannelMobileHeader.ts';
export { default as ChannelOptions } from './classes/ChannelOptions.ts';
@@ -67,6 +69,7 @@ export { default as SponsorCommentBadge } from './classes/comments/SponsorCommen
export { default as CompactChannel } from './classes/CompactChannel.ts';
export { default as CompactLink } from './classes/CompactLink.ts';
export { default as CompactMix } from './classes/CompactMix.ts';
export { default as CompactMovie } from './classes/CompactMovie.ts';
export { default as CompactPlaylist } from './classes/CompactPlaylist.ts';
export { default as CompactStation } from './classes/CompactStation.ts';
export { default as CompactVideo } from './classes/CompactVideo.ts';

View File

@@ -6,6 +6,7 @@ import PlayerCaptionsTracklist from './classes/PlayerCaptionsTracklist.ts';
import PlayerLiveStoryboardSpec from './classes/PlayerLiveStoryboardSpec.ts';
import PlayerStoryboardSpec from './classes/PlayerStoryboardSpec.ts';
import Alert from './classes/Alert.ts';
import AlertWithButton from './classes/AlertWithButton.ts';
import type { IParsedResponse, IRawResponse, RawData, RawNode } from './types/index.ts';
@@ -310,7 +311,7 @@ export function parseResponse<T extends IParsedResponse = IParsedResponse>(data:
parsed_data.overlay = overlay;
}
const alerts = parseArray(data.alerts, Alert);
const alerts = parseArray(data.alerts, [ Alert, AlertWithButton ]);
if (alerts.length) {
parsed_data.alerts = alerts;
}

View File

@@ -15,6 +15,7 @@ import type PlayerLiveStoryboardSpec from '../classes/PlayerLiveStoryboardSpec.t
import type PlayerStoryboardSpec from '../classes/PlayerStoryboardSpec.ts';
import type VideoDetails from '../classes/misc/VideoDetails.ts';
import type Alert from '../classes/Alert.ts';
import type AlertWithButton from '../classes/AlertWithButton.ts';
import type NavigationEndpoint from '../classes/NavigationEndpoint.ts';
import type PlayerAnnotationsExpanded from '../classes/PlayerAnnotationsExpanded.ts';
import type EngagementPanelSectionList from '../classes/EngagementPanelSectionList.ts';
@@ -44,7 +45,7 @@ export interface IParsedResponse {
metadata?: SuperParsedResult<YTNode>;
microformat?: YTNode;
overlay?: YTNode;
alerts?: ObservedArray<Alert>;
alerts?: ObservedArray<Alert | AlertWithButton>;
refinements?: string[];
estimated_results?: number;
player_overlays?: SuperParsedResult<YTNode>;
@@ -122,7 +123,7 @@ export interface IBrowseResponse {
header_memo?: Memo;
metadata?: SuperParsedResult<YTNode>;
microformat?: YTNode;
alerts?: ObservedArray<Alert>;
alerts?: ObservedArray<Alert | AlertWithButton>;
sidebar?: YTNode;
sidebar_memo?: Memo;
}

View File

@@ -55,7 +55,7 @@ function DashManifest({
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation": "urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
}, /* @__PURE__ */ DashUtils.createElement("period", null, audio_sets.map((set, index) => /* @__PURE__ */ DashUtils.createElement("adaptation-set", {
id: `audio_${index}`,
id: index,
mimeType: set.mime_type,
startWithSAP: "1",
subsegmentAlignment: "true",
@@ -66,7 +66,7 @@ function DashManifest({
schemeIdUri: "urn:mpeg:dash:role:2011",
value: set.track_role
}), set.track_name && /* @__PURE__ */ DashUtils.createElement("label", {
id: `audio_${index}`
id: index
}, set.track_name), set.channels && /* @__PURE__ */ DashUtils.createElement("audio-channel-configuration", {
schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
value: set.channels
@@ -81,7 +81,7 @@ function DashManifest({
}), /* @__PURE__ */ DashUtils.createElement(SegmentInfo, {
info: rep.segment_info
}))))), video_sets.map((set, index) => /* @__PURE__ */ DashUtils.createElement("adaptation-set", {
id: `video_${index}`,
id: index + audio_sets.length,
mimeType: set.mime_type,
startWithSAP: "1",
subsegmentAlignment: "true",
@@ -108,7 +108,7 @@ function DashManifest({
info: rep.segment_info
}))))), image_sets.map(async (set, index) => {
return /* @__PURE__ */ DashUtils.createElement("adaptation-set", {
id: `thumbs_${index}`,
id: index + audio_sets.length + video_sets.length,
mimeType: await set.getMimeType(),
contentType: "image"
}, set.representations.map(async (rep) => /* @__PURE__ */ DashUtils.createElement("representation", {

View File

@@ -90,7 +90,7 @@ function DashManifest({
{
audio_sets.map((set, index) => (
<adaptation-set
id={`audio_${index}`}
id={index}
mimeType={set.mime_type}
startWithSAP="1"
subsegmentAlignment="true"
@@ -107,7 +107,7 @@ function DashManifest({
}
{
set.track_name &&
<label id={`audio_${index}`}>
<label id={index}>
{set.track_name}
</label>
}
@@ -143,7 +143,7 @@ function DashManifest({
{
video_sets.map((set, index) => (
<adaptation-set
id={`video_${index}`}
id={index + audio_sets.length}
mimeType={set.mime_type}
startWithSAP="1"
subsegmentAlignment="true"
@@ -192,7 +192,7 @@ function DashManifest({
{
image_sets.map(async (set, index) => {
return <adaptation-set
id={`thumbs_${index}`}
id={index + audio_sets.length + video_sets.length}
mimeType={await set.getMimeType()}
contentType="image"
>