From e147a01ba5d5ca95607587ef97b07c05a6924d82 Mon Sep 17 00:00:00 2001 From: LuanRT Date: Sun, 16 Jul 2023 20:58:15 +0000 Subject: [PATCH] chore: v5.5.0 release --- deno/package.json | 4 +-- .../classes/EngagementPanelSectionList.ts | 5 ++-- deno/src/parser/classes/HashtagTile.ts | 28 +++++++++++++++++++ .../parser/classes/MacroMarkersInfoItem.ts | 17 +++++++++++ deno/src/parser/classes/MacroMarkersList.ts | 18 ++++++++++++ .../parser/classes/PlayerCaptionsTracklist.ts | 4 +-- .../classes/StructuredDescriptionContent.ts | 5 ++-- .../comments/CommentsEntryPointHeader.ts | 7 +++-- .../classes/comments/CommentsSimplebox.ts | 17 +++++++++++ deno/src/parser/nodes.ts | 4 +++ deno/src/parser/parser.ts | 15 ++++++++++ deno/src/parser/youtube/VideoInfo.ts | 22 +++++++++++++++ deno/src/utils/FormatUtils.ts | 7 ++++- 13 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 deno/src/parser/classes/HashtagTile.ts create mode 100644 deno/src/parser/classes/MacroMarkersInfoItem.ts create mode 100644 deno/src/parser/classes/MacroMarkersList.ts create mode 100644 deno/src/parser/classes/comments/CommentsSimplebox.ts diff --git a/deno/package.json b/deno/package.json index e01477be..7d185149 100644 --- a/deno/package.json +++ b/deno/package.json @@ -1,6 +1,6 @@ { "name": "youtubei.js", - "version": "5.4.0", + "version": "5.5.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", @@ -84,7 +84,7 @@ }, "license": "MIT", "dependencies": { - "jintr": "^1.0.0", + "jintr": "^1.1.0", "linkedom": "^0.14.12", "tslib": "^2.5.0", "undici": "^5.19.1" diff --git a/deno/src/parser/classes/EngagementPanelSectionList.ts b/deno/src/parser/classes/EngagementPanelSectionList.ts index fc47a851..3e08ea0f 100644 --- a/deno/src/parser/classes/EngagementPanelSectionList.ts +++ b/deno/src/parser/classes/EngagementPanelSectionList.ts @@ -2,6 +2,7 @@ import { YTNode } from '../helpers.ts'; import Parser, { type RawNode } from '../index.ts'; import ContinuationItem from './ContinuationItem.ts'; import EngagementPanelTitleHeader from './EngagementPanelTitleHeader.ts'; +import MacroMarkersList from './MacroMarkersList.ts'; import SectionList from './SectionList.ts'; import StructuredDescriptionContent from './StructuredDescriptionContent.ts'; @@ -9,7 +10,7 @@ export default class EngagementPanelSectionList extends YTNode { static type = 'EngagementPanelSectionList'; header: EngagementPanelTitleHeader | null; - content: SectionList | ContinuationItem | StructuredDescriptionContent | null; + content: SectionList | ContinuationItem | StructuredDescriptionContent | MacroMarkersList | null; target_id?: string; panel_identifier?: string; visibility?: string; @@ -17,7 +18,7 @@ export default class EngagementPanelSectionList extends YTNode { constructor(data: RawNode) { super(); this.header = Parser.parseItem(data.header, EngagementPanelTitleHeader); - this.content = Parser.parseItem(data.content, [ SectionList, ContinuationItem, StructuredDescriptionContent ]); + this.content = Parser.parseItem(data.content, [ SectionList, ContinuationItem, StructuredDescriptionContent, MacroMarkersList ]); this.panel_identifier = data.panelIdentifier; this.target_id = data.targetId; this.visibility = data.visibility; diff --git a/deno/src/parser/classes/HashtagTile.ts b/deno/src/parser/classes/HashtagTile.ts new file mode 100644 index 00000000..a2ab4dcc --- /dev/null +++ b/deno/src/parser/classes/HashtagTile.ts @@ -0,0 +1,28 @@ +import { YTNode } from '../helpers.ts'; +import type { RawNode } from '../index.ts'; +import { Thumbnail } from '../misc.ts'; +import NavigationEndpoint from './NavigationEndpoint.ts'; +import Text from './misc/Text.ts'; + +export default class HashtagTile extends YTNode { + static type = 'HashtagTile'; + + hashtag: Text; + hashtag_info_text: Text; + hashtag_thumbnail: Thumbnail[]; + endpoint: NavigationEndpoint; + hashtag_background_color: number; + hashtag_video_count: Text; + hashtag_channel_count: Text; + + constructor(data: RawNode) { + super(); + this.hashtag = new Text(data.hashtag); + this.hashtag_info_text = new Text(data.hashtagInfoText); + this.hashtag_thumbnail = Thumbnail.fromResponse(data.hashtagThumbnail); + this.endpoint = new NavigationEndpoint(data.onTapCommand); + this.hashtag_background_color = data.hashtagBackgroundColor; + this.hashtag_video_count = new Text(data.hashtagVideoCount); + this.hashtag_channel_count = new Text(data.hashtagChannelCount); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/MacroMarkersInfoItem.ts b/deno/src/parser/classes/MacroMarkersInfoItem.ts new file mode 100644 index 00000000..f1448fa1 --- /dev/null +++ b/deno/src/parser/classes/MacroMarkersInfoItem.ts @@ -0,0 +1,17 @@ +import { YTNode } from '../helpers.ts'; +import { Parser, type RawNode } from '../index.ts'; +import Menu from './menus/Menu.ts'; +import Text from './misc/Text.ts'; + +export default class MacroMarkersInfoItem extends YTNode { + static type = 'MacroMarkersInfoItem'; + + info_text: Text; + menu: Menu | null; + + constructor(data: RawNode) { + super(); + this.info_text = new Text(data.infoText); + this.menu = Parser.parseItem(data.menu, Menu); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/MacroMarkersList.ts b/deno/src/parser/classes/MacroMarkersList.ts new file mode 100644 index 00000000..9e7622ca --- /dev/null +++ b/deno/src/parser/classes/MacroMarkersList.ts @@ -0,0 +1,18 @@ +import { YTNode, type ObservedArray } from '../helpers.ts'; +import { Parser, type RawNode } from '../index.ts'; +import { Text } from '../misc.ts'; +import MacroMarkersInfoItem from './MacroMarkersInfoItem.ts'; +import MacroMarkersListItem from './MacroMarkersListItem.ts'; + +export default class MacroMarkersList extends YTNode { + static type = 'MacroMarkersList'; + + contents: ObservedArray; + sync_button_label: Text; + + constructor(data: RawNode) { + super(); + this.contents = Parser.parseArray(data.contents, [ MacroMarkersInfoItem, MacroMarkersListItem ]); + this.sync_button_label = new Text(data.syncButtonLabel); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/PlayerCaptionsTracklist.ts b/deno/src/parser/classes/PlayerCaptionsTracklist.ts index 72c48f18..96868da2 100644 --- a/deno/src/parser/classes/PlayerCaptionsTracklist.ts +++ b/deno/src/parser/classes/PlayerCaptionsTracklist.ts @@ -17,10 +17,10 @@ export default class PlayerCaptionsTracklist extends YTNode { audio_tracks?: { audio_track_id: string; captions_initial_state: string; - default_caption_track_index: number; + default_caption_track_index?: number; has_default_track: boolean; visibility: string; - caption_track_indices: number; + caption_track_indices: number[]; }[]; default_audio_track_index?: number; diff --git a/deno/src/parser/classes/StructuredDescriptionContent.ts b/deno/src/parser/classes/StructuredDescriptionContent.ts index e953c95c..10264e3c 100644 --- a/deno/src/parser/classes/StructuredDescriptionContent.ts +++ b/deno/src/parser/classes/StructuredDescriptionContent.ts @@ -1,6 +1,7 @@ import { YTNode, type ObservedArray } from '../helpers.ts'; import Parser, { type RawNode } from '../index.ts'; import ExpandableVideoDescriptionBody from './ExpandableVideoDescriptionBody.ts'; +import HorizontalCardList from './HorizontalCardList.ts'; import VideoDescriptionHeader from './VideoDescriptionHeader.ts'; import VideoDescriptionInfocardsSection from './VideoDescriptionInfocardsSection.ts'; import VideoDescriptionMusicSection from './VideoDescriptionMusicSection.ts'; @@ -8,10 +9,10 @@ import VideoDescriptionMusicSection from './VideoDescriptionMusicSection.ts'; export default class StructuredDescriptionContent extends YTNode { static type = 'StructuredDescriptionContent'; - items: ObservedArray; + items: ObservedArray; constructor(data: RawNode) { super(); - this.items = Parser.parseArray(data.items, [ VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection, VideoDescriptionInfocardsSection ]); + this.items = Parser.parseArray(data.items, [ VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection, VideoDescriptionInfocardsSection, HorizontalCardList ]); } } \ No newline at end of file diff --git a/deno/src/parser/classes/comments/CommentsEntryPointHeader.ts b/deno/src/parser/classes/comments/CommentsEntryPointHeader.ts index 53c3231a..7e09068e 100644 --- a/deno/src/parser/classes/comments/CommentsEntryPointHeader.ts +++ b/deno/src/parser/classes/comments/CommentsEntryPointHeader.ts @@ -1,9 +1,10 @@ import Text from '../misc/Text.ts'; import Thumbnail from '../misc/Thumbnail.ts'; +import CommentsSimplebox from './CommentsSimplebox.ts'; +import CommentsEntryPointTeaser from './CommentsEntryPointTeaser.ts'; import { YTNode } from '../../helpers.ts'; import type { RawNode } from '../../index.ts'; import { Parser } from '../../index.ts'; -import CommentsEntryPointTeaser from './CommentsEntryPointTeaser.ts'; export default class CommentsEntryPointHeader extends YTNode { static type = 'CommentsEntryPointHeader'; @@ -12,7 +13,7 @@ export default class CommentsEntryPointHeader extends YTNode { comment_count?: Text; teaser_avatar?: Thumbnail[]; teaser_content?: Text; - content_renderer?: CommentsEntryPointTeaser | null; + content_renderer?: CommentsEntryPointTeaser | CommentsSimplebox | null; simplebox_placeholder?: Text; constructor(data: RawNode) { @@ -35,7 +36,7 @@ export default class CommentsEntryPointHeader extends YTNode { } if (Reflect.has(data, 'contentRenderer')) { - this.content_renderer = Parser.parseItem(data.contentRenderer, CommentsEntryPointTeaser); + this.content_renderer = Parser.parseItem(data.contentRenderer, [ CommentsEntryPointTeaser, CommentsSimplebox ]); } if (Reflect.has(data, 'simpleboxPlaceholder')) { diff --git a/deno/src/parser/classes/comments/CommentsSimplebox.ts b/deno/src/parser/classes/comments/CommentsSimplebox.ts new file mode 100644 index 00000000..c7aa4660 --- /dev/null +++ b/deno/src/parser/classes/comments/CommentsSimplebox.ts @@ -0,0 +1,17 @@ +import { YTNode } from '../../helpers.ts'; +import Text from '../misc/Text.ts'; +import Thumbnail from '../misc/Thumbnail.ts'; +import type { RawNode } from '../../index.ts'; + +export default class CommentsSimplebox extends YTNode { + static type = 'CommentsSimplebox'; + + simplebox_avatar: Thumbnail[]; + simplebox_placeholder: Text; + + constructor(data: RawNode) { + super(); + this.simplebox_avatar = Thumbnail.fromResponse(data.simpleboxAvatar); + this.simplebox_placeholder = new Text(data.simpleboxPlaceholder); + } +} \ No newline at end of file diff --git a/deno/src/parser/nodes.ts b/deno/src/parser/nodes.ts index e8e6c368..a2820d1c 100644 --- a/deno/src/parser/nodes.ts +++ b/deno/src/parser/nodes.ts @@ -58,6 +58,7 @@ export { default as CommentsEntryPointHeader } from './classes/comments/Comments export { default as CommentsEntryPointTeaser } from './classes/comments/CommentsEntryPointTeaser.ts'; export { default as CommentsHeader } from './classes/comments/CommentsHeader.ts'; export { default as CommentSimplebox } from './classes/comments/CommentSimplebox.ts'; +export { default as CommentsSimplebox } from './classes/comments/CommentsSimplebox.ts'; export { default as CommentThread } from './classes/comments/CommentThread.ts'; export { default as CreatorHeart } from './classes/comments/CreatorHeart.ts'; export { default as EmojiPicker } from './classes/comments/EmojiPicker.ts'; @@ -115,6 +116,7 @@ export { default as GuideEntry } from './classes/GuideEntry.ts'; export { default as GuideSection } from './classes/GuideSection.ts'; export { default as GuideSubscriptionsSection } from './classes/GuideSubscriptionsSection.ts'; export { default as HashtagHeader } from './classes/HashtagHeader.ts'; +export { default as HashtagTile } from './classes/HashtagTile.ts'; export { default as Heatmap } from './classes/Heatmap.ts'; export { default as HeatMarker } from './classes/HeatMarker.ts'; export { default as HeroPlaylistThumbnail } from './classes/HeroPlaylistThumbnail.ts'; @@ -178,6 +180,8 @@ export { default as LiveChatItemList } from './classes/LiveChatItemList.ts'; export { default as LiveChatMessageInput } from './classes/LiveChatMessageInput.ts'; export { default as LiveChatParticipant } from './classes/LiveChatParticipant.ts'; export { default as LiveChatParticipantsList } from './classes/LiveChatParticipantsList.ts'; +export { default as MacroMarkersInfoItem } from './classes/MacroMarkersInfoItem.ts'; +export { default as MacroMarkersList } from './classes/MacroMarkersList.ts'; export { default as MacroMarkersListItem } from './classes/MacroMarkersListItem.ts'; export { default as Menu } from './classes/menus/Menu.ts'; export { default as MenuNavigationItem } from './classes/menus/MenuNavigationItem.ts'; diff --git a/deno/src/parser/parser.ts b/deno/src/parser/parser.ts index 70497a1f..d2f9dd82 100644 --- a/deno/src/parser/parser.ts +++ b/deno/src/parser/parser.ts @@ -413,6 +413,8 @@ export default class Parser { return observe(actions.map((action: any) => { if (action.navigateAction) return new NavigateAction(action.navigateAction); + if (action.showMiniplayerCommand) + return new ShowMiniplayerCommand(action.showMiniplayerCommand); if (action.reloadContinuationItemsCommand) return new ReloadContinuationItemsCommand(action.reloadContinuationItemsCommand); if (action.appendContinuationItemsAction) @@ -574,6 +576,19 @@ export class NavigateAction extends YTNode { } } +export class ShowMiniplayerCommand extends YTNode { + static readonly type = 'showMiniplayerCommand'; + + miniplayer_command: NavigationEndpoint; + show_premium_branding: boolean; + + constructor(data: RawNode) { + super(); + this.miniplayer_command = new NavigationEndpoint(data.miniplayerCommand); + this.show_premium_branding = data.showPremiumBranding; + } +} + export class AppendContinuationItemsAction extends YTNode { static readonly type = 'appendContinuationItemsAction'; diff --git a/deno/src/parser/youtube/VideoInfo.ts b/deno/src/parser/youtube/VideoInfo.ts index 90befdaf..c81bb3b4 100644 --- a/deno/src/parser/youtube/VideoInfo.ts +++ b/deno/src/parser/youtube/VideoInfo.ts @@ -99,6 +99,28 @@ class VideoInfo extends MediaInfo { this.captions = info.captions; this.cards = info.cards; + if (this.streaming_data) { + const default_audio_track = this.streaming_data.adaptive_formats.find((format) => format.audio_track?.audio_is_default); + if (default_audio_track) { + // The combined formats only exist for the default language, even for videos with multiple audio tracks + // So we can copy the language from the default audio track to the combined formats + this.streaming_data.formats.forEach((format) => format.language = default_audio_track.language); + } else if (typeof this.captions?.default_audio_track_index !== 'undefined' && this.captions?.audio_tracks && this.captions.caption_tracks) { + // For videos with a single audio track and captions, we can use the captions to figure out the language of the audio and combined formats + const audioTrack = this.captions.audio_tracks[this.captions.default_audio_track_index]; + const defaultCaptionTrackIndex = audioTrack.default_caption_track_index; + const index = audioTrack.caption_track_indices[defaultCaptionTrackIndex ? defaultCaptionTrackIndex : 0]; + const language_code = this.captions.caption_tracks[index].language_code; + + this.streaming_data.adaptive_formats.forEach((format) => { + if (format.has_audio) { + format.language = language_code; + } + }); + this.streaming_data.formats.forEach((format) => format.language = language_code); + } + } + const two_col = next?.contents?.item().as(TwoColumnWatchNextResults); const results = two_col?.results; diff --git a/deno/src/utils/FormatUtils.ts b/deno/src/utils/FormatUtils.ts index 64371398..f0163eb8 100644 --- a/deno/src/utils/FormatUtils.ts +++ b/deno/src/utils/FormatUtils.ts @@ -146,7 +146,7 @@ class FormatUtils { chunk_end += chunk_size; resolve(); - return; + } catch (e: any) { reject(e); } @@ -491,6 +491,11 @@ class FormatUtils { this.#hoistNumberAttributeIfPossible(set, mime_objects[i], 'audioSamplingRate', 'audio_sample_rate', hoisted); this.#hoistAudioChannelsIfPossible(document, set, mime_objects[i], hoisted); + + const language = mime_objects[i][0].language; + if (language) { + set.setAttribute('lang', language); + } } else { set.setAttribute('maxPlayoutRate', '1');