Compare commits

...

12 Commits

Author SHA1 Message Date
github-actions[bot]
2e5f076fd7 chore(main): release 6.2.0 (#491)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-29 14:25:32 -03:00
absidue
0412fa05ff fix(Format): Fix is_original always being true (#492) 2023-08-29 14:23:08 -03:00
Luan
10c15bfb9f feat(Session): Add fallback for session data retrieval (#490)
* feat(Session): Add fallback for session data retrieval

Should have added this when we first implemented session data retrieval to be honest. It makes a request to YouTube's service worker and the data there can change or the request can just fail.

* chore: lint
2023-08-28 07:18:27 -03:00
github-actions[bot]
4862c35cee chore(main): release 6.1.0 (#489)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-27 16:43:10 -03:00
absidue
2eed1726d5 feat(parser): Add CompactMovie (#487) 2023-08-27 16:29:50 -03:00
absidue
8b69587787 feat(parser): Add AlertWithButton (#486) 2023-08-27 16:29:23 -03:00
absidue
ed7be2a675 feat(parser): Add ChannelHeaderLinksView (#484) 2023-08-27 16:28:49 -03:00
github-actions[bot]
361fb4a9f1 chore(main): release 6.0.2 (#481)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-24 17:06:07 -03:00
Daniel Wykerd
1c3ea2acd3 fix: invalid set ids in dash manifest (#480) 2023-08-24 15:59:10 -03:00
github-actions[bot]
859c4585d9 chore(main): release 6.0.1 (#476)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-08-22 09:07:49 -03:00
LuanRT
751f2b90fd Merge branch 'main' of https://github.com/LuanRT/YouTube.js 2023-08-22 09:06:23 -03:00
LuanRT
90be877d28 fix(SearchSubMenu): Groups not being parsed due to a typo 2023-08-22 09:06:06 -03:00
16 changed files with 231 additions and 85 deletions

View File

@@ -1,5 +1,40 @@
# Changelog
## [6.2.0](https://github.com/LuanRT/YouTube.js/compare/v6.1.0...v6.2.0) (2023-08-29)
### Features
* **Session:** Add fallback for session data retrieval ([#490](https://github.com/LuanRT/YouTube.js/issues/490)) ([10c15bf](https://github.com/LuanRT/YouTube.js/commit/10c15bfb9f131a2acea2f26ff3328993d8d8f4aa))
### Bug Fixes
* **Format:** Fix `is_original` always being `true` ([#492](https://github.com/LuanRT/YouTube.js/issues/492)) ([0412fa0](https://github.com/LuanRT/YouTube.js/commit/0412fa05ff1f00960b398c2f18d5ce39ce0cb864))
## [6.1.0](https://github.com/LuanRT/YouTube.js/compare/v6.0.2...v6.1.0) (2023-08-27)
### Features
* **parser:** Add `AlertWithButton` ([#486](https://github.com/LuanRT/YouTube.js/issues/486)) ([8b69587](https://github.com/LuanRT/YouTube.js/commit/8b6958778721ba274283f641779fb60bc6f42cd2))
* **parser:** Add `ChannelHeaderLinksView` ([#484](https://github.com/LuanRT/YouTube.js/issues/484)) ([ed7be2a](https://github.com/LuanRT/YouTube.js/commit/ed7be2a675cf1ec663e743e90db6260c97546739))
* **parser:** Add `CompactMovie` ([#487](https://github.com/LuanRT/YouTube.js/issues/487)) ([2eed172](https://github.com/LuanRT/YouTube.js/commit/2eed1726d5bde7648af09273cc14ab4a315cb23e))
## [6.0.2](https://github.com/LuanRT/YouTube.js/compare/v6.0.1...v6.0.2) (2023-08-24)
### Bug Fixes
* invalid set ids in dash manifest ([#480](https://github.com/LuanRT/YouTube.js/issues/480)) ([1c3ea2a](https://github.com/LuanRT/YouTube.js/commit/1c3ea2acd38652c6b40a0817a7836c672a776c4e))
## [6.0.1](https://github.com/LuanRT/YouTube.js/compare/v6.0.0...v6.0.1) (2023-08-22)
### Bug Fixes
* **SearchSubMenu:** Groups not being parsed due to a typo ([90be877](https://github.com/LuanRT/YouTube.js/commit/90be877d28e0ef013056eaeaa4f2765c91addd61))
## [6.0.0](https://github.com/LuanRT/YouTube.js/compare/v5.8.0...v6.0.0) (2023-08-18)

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "youtubei.js",
"version": "6.0.0",
"version": "6.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "youtubei.js",
"version": "6.0.0",
"version": "6.2.0",
"funding": [
"https://github.com/sponsors/LuanRT"
],

View File

@@ -1,6 +1,6 @@
{
"name": "youtubei.js",
"version": "6.0.0",
"version": "6.2.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

@@ -217,10 +217,17 @@ export default class Session extends EventEmitterLike {
) {
let session_data: SessionData;
const session_args = { lang, location, time_zone: tz, device_category, client_name, enable_safety_mode, visitor_data };
if (generate_session_locally) {
session_data = this.#generateSessionData({ lang, location, time_zone: tz, device_category, client_name, enable_safety_mode, visitor_data });
session_data = this.#generateSessionData(session_args);
} else {
session_data = await this.#retrieveSessionData({ lang, location, time_zone: tz, device_category, client_name, enable_safety_mode, visitor_data }, fetch);
try {
// This can fail if the data changes or the request is blocked for some reason.
session_data = await this.#retrieveSessionData(session_args, fetch);
} catch (err) {
session_data = this.#generateSessionData(session_args);
}
}
return { ...session_data, account_index };

View File

@@ -0,0 +1,19 @@
import Button from './Button.js';
import Text from './misc/Text.js';
import { YTNode } from '../helpers.js';
import { Parser, type RawNode } from '../index.js';
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.js';
import Parser, { type RawNode } from '../index.js';
import Button from './Button.js';
import ChannelHeaderLinks from './ChannelHeaderLinks.js';
import ChannelHeaderLinksView from './ChannelHeaderLinksView.js';
import SubscribeButton from './SubscribeButton.js';
import Author from './misc/Author.js';
import Text from './misc/Text.js';
@@ -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.js';
import type { RawNode } from '../index.js';
import Text from './misc/Text.js';
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.js';
import type { RawNode } from '../index.js';
import Parser from '../index.js';
import Author from './misc/Author.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import Thumbnail from './misc/Thumbnail.js';
import Menu from './menus/Menu.js';
import { timeToSeconds } from '../../utils/Utils.js';
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

@@ -104,7 +104,7 @@ export default class Format {
this.language = xtags?.find((x: string) => x.startsWith('lang='))?.split('=')[1] || null;
this.is_dubbed = audio_content === 'dubbed';
this.is_descriptive = audio_content === 'descriptive';
this.is_original = audio_content === 'original' || !this.is_dubbed || !this.is_descriptive;
this.is_original = audio_content === 'original' || (!this.is_dubbed && !this.is_descriptive);
if (Reflect.has(data, 'audioTrack')) {
this.audio_track = {

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

View File

@@ -6,6 +6,7 @@ import PlayerCaptionsTracklist from './classes/PlayerCaptionsTracklist.js';
import PlayerLiveStoryboardSpec from './classes/PlayerLiveStoryboardSpec.js';
import PlayerStoryboardSpec from './classes/PlayerStoryboardSpec.js';
import Alert from './classes/Alert.js';
import AlertWithButton from './classes/AlertWithButton.js';
import type { IParsedResponse, IRawResponse, RawData, RawNode } from './types/index.js';
@@ -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.j
import type PlayerStoryboardSpec from '../classes/PlayerStoryboardSpec.js';
import type VideoDetails from '../classes/misc/VideoDetails.js';
import type Alert from '../classes/Alert.js';
import type AlertWithButton from '../classes/AlertWithButton.js';
import type NavigationEndpoint from '../classes/NavigationEndpoint.js';
import type PlayerAnnotationsExpanded from '../classes/PlayerAnnotationsExpanded.js';
import type EngagementPanelSectionList from '../classes/EngagementPanelSectionList.js';
@@ -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

@@ -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"
>