From ff9aeeedceb92176be297ed66afcaf6ca1835a60 Mon Sep 17 00:00:00 2001 From: LuanRT Date: Fri, 29 Jul 2022 16:09:11 -0300 Subject: [PATCH] refactor: rewrite `Library` to TypeScript --- src/parser/classes/Shelf.js | 2 +- src/parser/youtube/Library.js | 80 ------------------------- src/parser/youtube/Library.ts | 106 ++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 81 deletions(-) delete mode 100644 src/parser/youtube/Library.js create mode 100644 src/parser/youtube/Library.ts diff --git a/src/parser/classes/Shelf.js b/src/parser/classes/Shelf.js index 7a21af30..12e41b50 100644 --- a/src/parser/classes/Shelf.js +++ b/src/parser/classes/Shelf.js @@ -14,7 +14,7 @@ class Shelf extends YTNode { this.endpoint = new NavigationEndpoint(data.endpoint); } - this.content = Parser.parse(data.content) || []; + this.content = Parser.parse(data.content) || null; if (data.icon?.iconType) { this.icon_type = data.icon?.iconType; diff --git a/src/parser/youtube/Library.js b/src/parser/youtube/Library.js deleted file mode 100644 index 701a1f9b..00000000 --- a/src/parser/youtube/Library.js +++ /dev/null @@ -1,80 +0,0 @@ -import Parser from '../index'; -import History from './History'; -import Playlist from './Playlist'; -import Feed from '../../core/Feed'; -import { observe } from '../helpers'; - -class Library { - #actions; - #page; - - /** - * @param {object} response - API response. - * @param {import('../../core/Actions').default} actions - */ - constructor(response, actions) { - this.#actions = actions; - this.#page = Parser.parseResponse(response); - - const tab = this.#page.contents.tabs.get({ selected: true }); - const shelves = tab.content.contents.map((section) => section.contents[0]); - - const stats = this.#page.contents.secondary_contents.items.get({ type: 'ProfileColumnStats' }).items; - const user_info = this.#page.contents.secondary_contents.items.get({ type: 'ProfileColumnUserInfo' }); - - this.profile = { stats, user_info }; - - /** @type {{ type: string, title: import('../classes/misc/Text'), contents: object[], getAll: Promise. }[] } */ - this.sections = observe(shelves.map((shelf) => ({ - type: shelf.icon_type, - title: shelf.title, - contents: shelf.content.items, - getAll: () => this.#getAll(shelf) - }))); - } - - async #getAll(shelf) { - if (!shelf.menu?.top_level_buttons) - throw new Error(`The ${shelf.title.text} section doesn't have more items`); - - const button = await shelf.menu.top_level_buttons.get({ text: 'See all' }); - const page = await button.endpoint.call(this.#actions); - - switch (shelf.icon_type) { - case 'LIKE': - case 'WATCH_LATER': - return new Playlist(this.#actions, page, true); - case 'WATCH_HISTORY': - return new History(this.#actions, page, true); - case 'CONTENT_CUT': - return new Feed(this.#actions, page, true); - default: - } - } - - get history() { - return this.sections.get({ type: 'WATCH_HISTORY' }); - } - - get watch_later() { - return this.sections.get({ type: 'WATCH_LATER' }); - } - - get liked_videos() { - return this.sections.get({ type: 'LIKE' }); - } - - get playlists() { - return this.sections.get({ type: 'PLAYLISTS' }); - } - - get clips() { - return this.sections.get({ type: 'CONTENT_CUT' }); - } - - get page() { - return this.#page; - } -} - -export default Library; \ No newline at end of file diff --git a/src/parser/youtube/Library.ts b/src/parser/youtube/Library.ts new file mode 100644 index 00000000..e83f40df --- /dev/null +++ b/src/parser/youtube/Library.ts @@ -0,0 +1,106 @@ +import Parser, { ParsedResponse } from '..'; +import Actions, { AxioslikeResponse } from '../../core/Actions'; +import { InnertubeError } from '../../utils/Utils'; + +import Feed from '../../core/Feed'; +import History from './History'; +import Playlist from './Playlist'; + +import Tab from '../classes/Tab'; +import Menu from '../classes/menus/Menu'; +import Shelf from '../classes/Shelf'; +import Button from '../classes/Button'; +import SectionList from '../classes/SectionList'; +import ItemSection from '../classes/ItemSection'; +import TwoColumnBrowseResults from '../classes/TwoColumnBrowseResults'; + +import ProfileColumn from '../classes/ProfileColumn'; +import ProfileColumnStats from '../classes/ProfileColumnStats'; +import ProfileColumnUserInfo from '../classes/ProfileColumnUserInfo'; + +class Library { + #actions; + #page; + + profile; + sections; + + constructor(response: AxioslikeResponse, actions: Actions) { + this.#actions = actions; + this.#page = Parser.parseResponse(response); + + const two_col = this.#page.contents.item().as(TwoColumnBrowseResults); + + if (!two_col) + throw new InnertubeError('Response did not have a TwoColumnBrowseResults.'); + + const tab = two_col.tabs.array().as(Tab).get({ selected: true }); + + if (!tab) + throw new InnertubeError('Could not find target tab.'); + + const stats = two_col.secondary_contents.item().as(ProfileColumn).items.array().get({ type: 'ProfileColumnStats' })?.as(ProfileColumnStats) || null; + const user_info = two_col.secondary_contents.item().as(ProfileColumn).items.array().get({ type: 'ProfileColumnUserInfo' })?.as(ProfileColumnUserInfo) || null; + + this.profile = { stats, user_info }; + + const shelves = tab.content.item().as(SectionList).contents.array().as(ItemSection).map((is: ItemSection) => is.contents?.firstOfType(Shelf)); + + this.sections = shelves.map((shelf: any) => ({ + type: shelf.icon_type, + title: shelf.title, + contents: shelf.content?.item().items.array() || [], + getAll: () => this.#getAll(shelf) + })); + } + + async #getAll(shelf: Shelf): Promise { + if (!shelf.menu?.item().as(Menu).hasKey('top_level_buttons')) + throw new InnertubeError(`The ${shelf.title.text} shelf doesn't have more items`); + + const button = await shelf.menu.item().as(Menu).top_level_buttons.get({ text: 'See all' }); + + if (!button) + throw new InnertubeError('Did not find target button.'); + + const page = await button.as(Button).endpoint.callTest(this.#actions, { parse: true }); + + switch (shelf.icon_type) { + case 'LIKE': + case 'WATCH_LATER': + return new Playlist(this.#actions, page, true); + case 'WATCH_HISTORY': + return new History(this.#actions, page, true); + case 'CONTENT_CUT': + return new Feed(this.#actions, page, true); + default: + throw new InnertubeError('Target shelf not implemented.'); + } + } + + get history() { + return this.sections.find((section) => section.type === 'WATCH_HISTORY'); + } + + get watch_later() { + return this.sections.find((section) => section.type === 'WATCH_LATER'); + } + + get liked_videos() { + return this.sections.find((section) => section.type === 'LIKE'); + } + + get playlists() { + return this.sections.find((section) => section.type === 'PLAYLISTS'); + } + + get clips() { + return this.sections.find((section) => section.type === 'CONTENT_CUT'); + } + + get page(): ParsedResponse { + return this.#page; + } +} + +export default Library; \ No newline at end of file