mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-13 17:42:18 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c76f5f478d | ||
|
|
49d1432b5a | ||
|
|
be157ef016 | ||
|
|
9f703203b6 | ||
|
|
516eeeff45 |
38
README.md
38
README.md
@@ -1,5 +1,3 @@
|
||||
<!-- Hi there, fellow coder :) -->
|
||||
|
||||
<!-- BADGE LINKS -->
|
||||
[npm]: https://www.npmjs.com/package/youtubei.js
|
||||
[versions]: https://www.npmjs.com/package/youtubei.js?activeTab=versions
|
||||
@@ -10,37 +8,23 @@
|
||||
|
||||
<!-- OTHER LINKS -->
|
||||
[project]: https://github.com/LuanRT/YouTube.js
|
||||
[twitter]: https://twitter.com/lrt_nooneknows
|
||||
[twitter]: https://twitter.com/thesciencephile
|
||||
[discord]: https://discord.gg/syDu7Yks54
|
||||
[nodejs]: https://nodejs.org
|
||||
|
||||
<!-- INTRODUCTION -->
|
||||
<h1 align=center>
|
||||
YouTube.js
|
||||
</h1>
|
||||
<h1 align=center>YouTube.js</h1>
|
||||
|
||||
<p align=center>
|
||||
<i>
|
||||
A full-featured wrapper around the InnerTube API, which is what YouTube itself uses.
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/LuanRT/YouTube.js/issues">
|
||||
Report Bug
|
||||
</a>
|
||||
·
|
||||
<a href="https://github.com/LuanRT/YouTube.js/issues">
|
||||
Request Feature
|
||||
</a>
|
||||
</p>
|
||||
<p align=center>A full-featured wrapper around the InnerTube API, which is what YouTube itself uses.</p>
|
||||
|
||||
<!-- BADGES -->
|
||||
<div align="center">
|
||||
|
||||
[][actions]
|
||||
[][versions]
|
||||
[][versions]
|
||||
[][codefactor]
|
||||
[][npm]
|
||||
[][discord]
|
||||
[][say-thanks]
|
||||
<br>
|
||||
[][github-sponsors]
|
||||
@@ -57,7 +41,7 @@
|
||||
<body>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://serpapi.com/">
|
||||
<a href="https://serpapi.com/" target="_blank">
|
||||
<img width="80" alt="SerpApi" src="https://luanrt.is-a.dev/assets/img/serpapi.svg" />
|
||||
<br>
|
||||
<b>
|
||||
@@ -105,7 +89,7 @@ ___
|
||||
</details>
|
||||
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About
|
||||
## Description
|
||||
|
||||
InnerTube is an API used across all YouTube clients, it was created to simplify[^1] the internal structure of the platform in a way that updates, tweaks, and experiments can be easily made. This library handles all the low-level communication with InnerTube, providing a simple, fast, and efficient way to interact with YouTube programmatically.
|
||||
|
||||
@@ -245,7 +229,7 @@ const yt = await Innertube.create({
|
||||
* `Innertube`
|
||||
|
||||
<details>
|
||||
<summary>objects</summary>
|
||||
<summary>Objects</summary>
|
||||
<p>
|
||||
|
||||
* [.session](https://github.com/LuanRT/YouTube.js/blob/main/docs/API/session.md)
|
||||
@@ -260,7 +244,7 @@ const yt = await Innertube.create({
|
||||
|
||||
|
||||
<details>
|
||||
<summary>methods</summary>
|
||||
<summary>Methods</summary>
|
||||
<p>
|
||||
|
||||
* [.getInfo(video_id, client?)](#getinfo)
|
||||
@@ -690,7 +674,7 @@ Feel free to check the [issues page](https://github.com/LuanRT/YouTube.js/issues
|
||||
<!-- CONTACT -->
|
||||
## Contact
|
||||
|
||||
LuanRT - [@lrt_nooneknows][twitter] - luan.lrt4@gmail.com
|
||||
LuanRT - [@thesciencephile][twitter] - luan.lrt4@gmail.com
|
||||
|
||||
Project Link: [https://github.com/LuanRT/YouTube.js][project]
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "youtubei.js",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/LuanRT"
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"description": "Full-featured wrapper around YouTube's private API. Supports YouTube, YouTube Music and YouTube Studio (WIP).",
|
||||
"main": "./dist/index.js",
|
||||
"browser": "./bundle/browser.js",
|
||||
|
||||
@@ -167,13 +167,13 @@ export default class Player {
|
||||
|
||||
static extractSigSourceCode(data: string) {
|
||||
const calls = getStringBetweenStrings(data, 'function(a){a=a.split("")', 'return a.join("")}');
|
||||
const obj_name = calls?.split('.')?.[0]?.replace(';', '');
|
||||
const functions = getStringBetweenStrings(data, `var ${obj_name}=`, '};');
|
||||
const obj_name = calls?.split(/\.|\[/)?.[0]?.replace(';', '')?.trim();
|
||||
const functions = getStringBetweenStrings(data, `var ${obj_name}={`, '};');
|
||||
|
||||
if (!functions || !calls)
|
||||
console.warn(new PlayerError('Failed to extract signature decipher algorithm'));
|
||||
|
||||
return `function descramble_sig(a) { a = a.split(""); let ${obj_name}=${functions}}${calls} return a.join("") } descramble_sig(sig);`;
|
||||
return `function descramble_sig(a) { a = a.split(""); let ${obj_name}={${functions}}${calls} return a.join("") } descramble_sig(sig);`;
|
||||
}
|
||||
|
||||
static extractNSigSourceCode(data: string) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Parser from '..';
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
export default class CarouselHeader extends YTNode {
|
||||
class CarouselHeader extends YTNode {
|
||||
static type = 'CarouselHeader';
|
||||
|
||||
contents: YTNode[];
|
||||
@@ -10,4 +10,6 @@ export default class CarouselHeader extends YTNode {
|
||||
super();
|
||||
this.contents = Parser.parseArray(data.contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CarouselHeader;
|
||||
@@ -3,7 +3,7 @@ import { YTNode } from '../helpers';
|
||||
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
|
||||
export default class CarouselItem extends YTNode {
|
||||
class CarouselItem extends YTNode {
|
||||
static type = 'CarouselItem';
|
||||
|
||||
items: YTNode[];
|
||||
@@ -20,4 +20,6 @@ export default class CarouselItem extends YTNode {
|
||||
this.pagination_thumbnails = Thumbnail.fromResponse(data.paginationThumbnails);
|
||||
this.paginator_alignment = data.paginatorAlignment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CarouselItem;
|
||||
@@ -4,7 +4,7 @@ import Text from './misc/Text';
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
import NavigationEndpoint from './NavigationEndpoint';
|
||||
|
||||
export default class CompactStation extends YTNode {
|
||||
class CompactStation extends YTNode {
|
||||
static type = 'CompactStation';
|
||||
|
||||
title: Text;
|
||||
@@ -22,4 +22,6 @@ export default class CompactStation extends YTNode {
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CompactStation;
|
||||
@@ -4,7 +4,7 @@ import { YTNode } from '../helpers';
|
||||
import Text from './misc/Text';
|
||||
import NavigationEndpoint from './NavigationEndpoint';
|
||||
|
||||
export default class DefaultPromoPanel extends YTNode {
|
||||
class DefaultPromoPanel extends YTNode {
|
||||
static type = 'DefaultPromoPanel';
|
||||
|
||||
title: Text;
|
||||
@@ -33,4 +33,6 @@ export default class DefaultPromoPanel extends YTNode {
|
||||
this.metadata_order = data.metadataOrder;
|
||||
this.panel_layout = data.panelLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DefaultPromoPanel;
|
||||
@@ -2,7 +2,7 @@ import Parser from '../index';
|
||||
import { YTNode } from '../helpers';
|
||||
import ChipCloudChip from './ChipCloudChip';
|
||||
|
||||
export default class FeedFilterChipBar extends YTNode {
|
||||
class FeedFilterChipBar extends YTNode {
|
||||
static type = 'FeedFilterChipBar';
|
||||
|
||||
contents;
|
||||
@@ -11,4 +11,6 @@ export default class FeedFilterChipBar extends YTNode {
|
||||
super();
|
||||
this.contents = Parser.parseArray<ChipCloudChip>(data.contents, ChipCloudChip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FeedFilterChipBar;
|
||||
@@ -1,7 +1,7 @@
|
||||
import Parser from '..';
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
export default class GameCard extends YTNode {
|
||||
class GameCard extends YTNode {
|
||||
static type = 'GameCard';
|
||||
|
||||
game;
|
||||
@@ -10,4 +10,6 @@ export default class GameCard extends YTNode {
|
||||
super();
|
||||
this.game = Parser.parseItem(data.game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GameCard;
|
||||
@@ -4,7 +4,7 @@ import Text from './misc/Text';
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
import NavigationEndpoint from './NavigationEndpoint';
|
||||
|
||||
export default class GameDetails extends YTNode {
|
||||
class GameDetails extends YTNode {
|
||||
static type = 'GameDetails';
|
||||
|
||||
title: Text;
|
||||
@@ -21,4 +21,6 @@ export default class GameDetails extends YTNode {
|
||||
this.endpoint = new NavigationEndpoint(data.endpoint);
|
||||
this.is_official_box_art = data.isOfficialBoxArt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GameDetails;
|
||||
@@ -4,7 +4,7 @@ import { YTNode } from '../helpers';
|
||||
class Panel {
|
||||
static type = 'Panel';
|
||||
|
||||
thumbnail: {
|
||||
thumbnail?: {
|
||||
image: {
|
||||
url: string;
|
||||
width: number;
|
||||
@@ -43,13 +43,15 @@ class Panel {
|
||||
};
|
||||
|
||||
constructor(data: any) {
|
||||
this.thumbnail = {
|
||||
image: data.thumbnail.image.sources,
|
||||
endpoint: new NavigationEndpoint(data.thumbnail.onTap),
|
||||
on_long_press_endpoint: new NavigationEndpoint(data.thumbnail.onLongPress),
|
||||
content_mode: data.thumbnail.contentMode,
|
||||
crop_options: data.thumbnail.cropOptions
|
||||
};
|
||||
if (data.thumbnail) {
|
||||
this.thumbnail = {
|
||||
image: data.thumbnail.image.sources,
|
||||
endpoint: new NavigationEndpoint(data.thumbnail.onTap),
|
||||
on_long_press_endpoint: new NavigationEndpoint(data.thumbnail.onLongPress),
|
||||
content_mode: data.thumbnail.contentMode,
|
||||
crop_options: data.thumbnail.cropOptions
|
||||
};
|
||||
}
|
||||
|
||||
this.background_image = {
|
||||
image: data.backgroundImage.image.sources,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import Parser from '../index';
|
||||
import { YTNode } from '../helpers';
|
||||
import SearchRefinementCard from './SearchRefinementCard';
|
||||
import Button from './Button';
|
||||
|
||||
class HorizontalCardList extends YTNode {
|
||||
static type = 'HorizontalCardList';
|
||||
@@ -11,10 +13,10 @@ class HorizontalCardList extends YTNode {
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.cards = Parser.parse(data.cards);
|
||||
this.header = Parser.parse(data.header);
|
||||
this.previous_button = Parser.parse(data.previousButton);
|
||||
this.next_button = Parser.parse(data.nextButton);
|
||||
this.cards = Parser.parseArray<SearchRefinementCard>(data.cards, SearchRefinementCard);
|
||||
this.header = Parser.parseItem(data.header);
|
||||
this.previous_button = Parser.parseItem<Button>(data.previousButton, Button);
|
||||
this.next_button = Parser.parseItem<Button>(data.nextButton, Button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import SubscribeButton from './SubscribeButton';
|
||||
import MetadataBadge from './MetadataBadge';
|
||||
import Button from './Button';
|
||||
|
||||
export default class InteractiveTabbedHeader extends YTNode {
|
||||
class InteractiveTabbedHeader extends YTNode {
|
||||
static type = 'InteractiveTabbedHeader';
|
||||
|
||||
header_type: string;
|
||||
@@ -33,4 +33,6 @@ export default class InteractiveTabbedHeader extends YTNode {
|
||||
this.buttons = Parser.parseArray<SubscribeButton | Button>(data.buttons, [ SubscribeButton, Button ]);
|
||||
this.auto_generated = new Text(data.autoGenerated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default InteractiveTabbedHeader;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { YTNode } from '../helpers';
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
|
||||
export default class PlaylistCustomThumbnail extends YTNode {
|
||||
class PlaylistCustomThumbnail extends YTNode {
|
||||
static type = 'PlaylistCustomThumbnail';
|
||||
|
||||
thumbnail: Thumbnail[];
|
||||
@@ -10,4 +10,6 @@ export default class PlaylistCustomThumbnail extends YTNode {
|
||||
super();
|
||||
this.thumbnail = Thumbnail.fromResponse(data.thumbnail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PlaylistCustomThumbnail;
|
||||
@@ -5,7 +5,7 @@ import Button from './Button';
|
||||
import Text from './misc/Text';
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
|
||||
export default class RecognitionShelf extends YTNode {
|
||||
class RecognitionShelf extends YTNode {
|
||||
static type = 'RecognitionShelf';
|
||||
|
||||
title: Text;
|
||||
@@ -23,4 +23,6 @@ export default class RecognitionShelf extends YTNode {
|
||||
this.button = Parser.parseItem<Button>(data.button, Button);
|
||||
this.surface = data.surface;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RecognitionShelf;
|
||||
@@ -20,14 +20,14 @@ class Shelf extends YTNode {
|
||||
this.endpoint = new NavigationEndpoint(data.endpoint);
|
||||
}
|
||||
|
||||
this.content = Parser.parse(data.content) || null;
|
||||
this.content = Parser.parseItem(data.content) || null;
|
||||
|
||||
if (data.icon?.iconType) {
|
||||
this.icon_type = data.icon?.iconType;
|
||||
}
|
||||
|
||||
if (data.menu) {
|
||||
this.menu = Parser.parse(data.menu);
|
||||
this.menu = Parser.parseItem(data.menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { YTNode } from '../helpers';
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
|
||||
export default class ThumbnailLandscapePortrait extends YTNode {
|
||||
class ThumbnailLandscapePortrait extends YTNode {
|
||||
static type = 'ThumbnailLandscapePortrait';
|
||||
|
||||
landscape: Thumbnail[];
|
||||
@@ -12,4 +12,6 @@ export default class ThumbnailLandscapePortrait extends YTNode {
|
||||
this.landscape = Thumbnail.fromResponse(data.landscape);
|
||||
this.portrait = Thumbnail.fromResponse(data.portrait);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ThumbnailLandscapePortrait;
|
||||
@@ -6,7 +6,7 @@ import Thumbnail from './misc/Thumbnail';
|
||||
import NavigationEndpoint from './NavigationEndpoint';
|
||||
import SubscribeButton from './SubscribeButton';
|
||||
|
||||
export default class TopicChannelDetails extends YTNode {
|
||||
class TopicChannelDetails extends YTNode {
|
||||
static type = 'TopicChannelDetails';
|
||||
|
||||
title: Text;
|
||||
@@ -24,4 +24,6 @@ export default class TopicChannelDetails extends YTNode {
|
||||
this.subscribe_button = Parser.parseItem<SubscribeButton>(data.subscribeButton, SubscribeButton);
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TopicChannelDetails;
|
||||
@@ -1,4 +1,5 @@
|
||||
import Parser from '../index';
|
||||
import Text from './misc/Text';
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
class UniversalWatchCard extends YTNode {
|
||||
@@ -7,13 +8,17 @@ class UniversalWatchCard extends YTNode {
|
||||
header;
|
||||
call_to_action;
|
||||
sections;
|
||||
collapsed_label?: Text;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
// TODO: use parseItem / parseArray for these
|
||||
this.header = Parser.parse(data.header);
|
||||
this.call_to_action = Parser.parse(data.callToAction);
|
||||
this.sections = Parser.parse(data.sections);
|
||||
this.header = Parser.parseItem(data.header);
|
||||
this.call_to_action = Parser.parseItem(data.callToAction);
|
||||
this.sections = Parser.parseArray(data.sections);
|
||||
|
||||
if (data.collapsedLabel) {
|
||||
this.collapsed_label = new Text(data.collapsedLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class VerticalWatchCardList extends YTNode {
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.items = Parser.parse(data.items);
|
||||
this.items = Parser.parseArray(data.items);
|
||||
this.contents = this.items; // XXX: alias for consistency
|
||||
this.view_all_text = new Text(data.viewAllText);
|
||||
this.view_all_endpoint = new NavigationEndpoint(data.viewAllEndpoint);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import Parser from '../index';
|
||||
import Parser from '..';
|
||||
import Text from './misc/Text';
|
||||
import Author from './misc/Author';
|
||||
import Menu from './menus/Menu';
|
||||
import Thumbnail from './misc/Thumbnail';
|
||||
import NavigationEndpoint from './NavigationEndpoint';
|
||||
|
||||
import { timeToSeconds } from '../../utils/Utils';
|
||||
import { YTNode } from '../helpers';
|
||||
|
||||
@@ -55,7 +56,7 @@ class Video extends YTNode {
|
||||
|
||||
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
|
||||
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
|
||||
this.rich_thumbnail = data.richThumbnail ? Parser.parse(data.richThumbnail) : null;
|
||||
this.rich_thumbnail = data.richThumbnail ? Parser.parseItem(data.richThumbnail) : null;
|
||||
this.author = new Author(data.ownerText, data.ownerBadges, data.channelThumbnailSupportedRenderers?.channelThumbnailWithLinkRenderer?.thumbnail);
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
this.published = new Text(data.publishedTimeText);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Video from './Video';
|
||||
|
||||
export default class VideoCard extends Video {
|
||||
class VideoCard extends Video {
|
||||
static type = 'VideoCard';
|
||||
|
||||
constructor(data: any) {
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoCard;
|
||||
@@ -13,9 +13,9 @@ class WatchCardHeroVideo extends YTNode {
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
|
||||
this.call_to_action_button = Parser.parse(data.callToActionButton);
|
||||
this.hero_image = Parser.parse(data.heroImage);
|
||||
this.label = data.lengthText.accessibility.accessibilityData.label;
|
||||
this.call_to_action_button = Parser.parseItem(data.callToActionButton);
|
||||
this.hero_image = Parser.parseItem(data.heroImage);
|
||||
this.label = data.lengthText?.accessibility.accessibilityData.label || '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class WatchCardSectionSequence extends YTNode {
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.lists = Parser.parse(data.lists);
|
||||
this.lists = Parser.parseArray(data.lists);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,10 +39,10 @@ class Library {
|
||||
}
|
||||
|
||||
async #getAll(shelf: Shelf): Promise<Playlist | History | Feed> {
|
||||
if (!shelf.menu?.item().as(Menu).hasKey('top_level_buttons'))
|
||||
if (!shelf.menu?.as(Menu).hasKey('top_level_buttons'))
|
||||
throw new InnertubeError(`The ${shelf.title.text} shelf doesn't have more items`);
|
||||
|
||||
const button = shelf.menu.item().as(Menu).top_level_buttons.get({ text: 'See all' });
|
||||
const button = shelf.menu.as(Menu).top_level_buttons.get({ text: 'See all' });
|
||||
|
||||
if (!button)
|
||||
throw new InnertubeError('Did not find target button.');
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import Actions from '../../core/Actions';
|
||||
import { observe, ObservedArray, YTNode } from '../helpers';
|
||||
import { ObservedArray, YTNode } from '../helpers';
|
||||
import { InnertubeError } from '../../utils/Utils';
|
||||
|
||||
import Feed from '../../core/Feed';
|
||||
import SectionList from '../classes/SectionList';
|
||||
import ItemSection from '../classes/ItemSection';
|
||||
import HorizontalCardList from '../classes/HorizontalCardList';
|
||||
import RichListHeader from '../classes/RichListHeader';
|
||||
import SearchRefinementCard from '../classes/SearchRefinementCard';
|
||||
import TwoColumnSearchResults from '../classes/TwoColumnSearchResults';
|
||||
import UniversalWatchCard from '../classes/UniversalWatchCard';
|
||||
import WatchCardHeroVideo from '../classes/WatchCardHeroVideo';
|
||||
import WatchCardSectionSequence from '../classes/WatchCardSectionSequence';
|
||||
|
||||
class Search extends Feed {
|
||||
export default class Search extends Feed {
|
||||
results: ObservedArray<YTNode> | null | undefined;
|
||||
refinements;
|
||||
estimated_results;
|
||||
@@ -24,30 +20,16 @@ class Search extends Feed {
|
||||
super(actions, data, already_parsed);
|
||||
|
||||
const contents =
|
||||
this.page.contents?.item().as(TwoColumnSearchResults).primary_contents?.item().as(SectionList).contents.array() ||
|
||||
this.page.contents_memo.getType(SectionList)?.[0]?.contents?.array() ||
|
||||
this.page.on_response_received_commands?.[0].contents;
|
||||
|
||||
const secondary_contents_maybe = this.page.contents?.item().key('secondary_contents');
|
||||
const secondary_contents = secondary_contents_maybe?.isParsed() ? secondary_contents_maybe.parsed().item().key('contents').parsed().array() : undefined;
|
||||
|
||||
this.results = contents.firstOfType(ItemSection)?.contents;
|
||||
|
||||
const card_list = this.results?.get({ type: 'HorizontalCardList' }, true)?.as(HorizontalCardList);
|
||||
const universal_watch_card = secondary_contents?.firstOfType(UniversalWatchCard);
|
||||
|
||||
this.refinements = this.page.refinements || [];
|
||||
this.estimated_results = this.page.estimated_results;
|
||||
|
||||
this.watch_card = {
|
||||
header: universal_watch_card?.header.item() || null,
|
||||
call_to_action: universal_watch_card?.call_to_action?.item()?.as(WatchCardHeroVideo) || null,
|
||||
sections: universal_watch_card?.sections?.array()?.filterType(WatchCardSectionSequence) || []
|
||||
};
|
||||
|
||||
this.refinement_cards = {
|
||||
header: card_list?.header.item().as(RichListHeader) || null,
|
||||
cards: card_list?.cards.array().filterType(SearchRefinementCard) || observe([] as SearchRefinementCard[])
|
||||
};
|
||||
this.watch_card = this.page?.contents_memo.getType(UniversalWatchCard)?.[0];
|
||||
this.refinement_cards = this.results?.get({ type: 'HorizontalCardList' }, true)?.as(HorizontalCardList);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +39,8 @@ class Search extends Feed {
|
||||
let target_card: SearchRefinementCard | undefined;
|
||||
|
||||
if (typeof card === 'string') {
|
||||
target_card = this.refinement_cards.cards.get({ query: card });
|
||||
if (!this.refinement_cards) throw new InnertubeError('No refinement cards found.');
|
||||
target_card = this.refinement_cards?.cards.get({ query: card });
|
||||
if (!target_card)
|
||||
throw new InnertubeError(`Refinement card "${card}" not found`, { available_cards: this.refinement_card_queries });
|
||||
} else if (card.type === 'SearchRefinementCard') {
|
||||
@@ -71,8 +54,11 @@ class Search extends Feed {
|
||||
return new Search(this.actions, page, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of refinement card queries.
|
||||
*/
|
||||
get refinement_card_queries() {
|
||||
return this.refinement_cards.cards.map((card) => card.query);
|
||||
return this.refinement_cards?.cards.map((card) => card.query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +68,4 @@ class Search extends Feed {
|
||||
const continuation = await this.getContinuationData();
|
||||
return new Search(this.actions, continuation, true);
|
||||
}
|
||||
}
|
||||
|
||||
export default Search;
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export const CLIENTS = Object.freeze({
|
||||
},
|
||||
YTMUSIC_ANDROID: {
|
||||
NAME: 'ANDROID_MUSIC',
|
||||
VERSION: '5.17.51'
|
||||
VERSION: '5.34.51'
|
||||
},
|
||||
TV_EMBEDDED: {
|
||||
NAME: 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
|
||||
|
||||
Reference in New Issue
Block a user