From 0fc29f0bbf965215146a6ae192494c74e6cefcbb Mon Sep 17 00:00:00 2001 From: LuanRT Date: Mon, 23 Jan 2023 05:38:53 -0300 Subject: [PATCH] feat(ytkids): add `getChannel()` (#292) --- docs/API/kids.md | 28 +++++++++++++++++++++++++ src/core/Feed.ts | 1 + src/core/Kids.ts | 11 ++++++++++ src/parser/classes/ItemSection.ts | 7 ++++++- src/parser/index.ts | 19 ++++++++++++++++- src/parser/ytkids/Channel.ts | 34 +++++++++++++++++++++++++++++++ test/constants.ts | 4 ++++ test/main.test.ts | 5 +++++ 8 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/parser/ytkids/Channel.ts diff --git a/docs/API/kids.md b/docs/API/kids.md index ec1ad7825..42913ea18 100644 --- a/docs/API/kids.md +++ b/docs/API/kids.md @@ -7,6 +7,7 @@ YouTube Kids is a modified version of the YouTube app, with a simplified interfa * Kids * [.search(query)](#search) * [.getInfo(video_id)](#getinfo) + * [.getChannel(channel_id)](#getchannel) * [.getHomeFeed()](#gethomefeed) @@ -64,6 +65,33 @@ Retrieves video info.

+ +### getChannel(channel_id) + +Retrieves channel info. + +**Returns:** `Promise.` + +| Param | Type | Description | +| --- | --- | --- | +| channel_id | `string` | The channel id | + +
+Methods & Getters +

+ +- `#getContinuation()` + - Retrieves next batch of videos. + +- `#has_continuation` + - Returns whether there are more videos to retrieve. + +- `#page` + - Returns the original InnerTube response(s), parsed and sanitized. + +

+
+ ### getHomeFeed() diff --git a/src/core/Feed.ts b/src/core/Feed.ts index 7e56c315e..12d34757d 100644 --- a/src/core/Feed.ts +++ b/src/core/Feed.ts @@ -45,6 +45,7 @@ class Feed { const memo = concatMemos( this.#page.contents_memo, + this.#page.continuation_contents_memo, this.#page.on_response_received_commands_memo, this.#page.on_response_received_endpoints_memo, this.#page.on_response_received_actions_memo, diff --git a/src/core/Kids.ts b/src/core/Kids.ts index f1c8f7d90..a5a2962a3 100644 --- a/src/core/Kids.ts +++ b/src/core/Kids.ts @@ -1,7 +1,9 @@ import Search from '../parser/ytkids/Search'; import HomeFeed from '../parser/ytkids/HomeFeed'; import VideoInfo from '../parser/ytkids/VideoInfo'; +import Channel from '../parser/ytkids/Channel'; import type Session from './Session'; + import { generateRandomString } from '../utils/Utils'; class Kids { @@ -45,6 +47,15 @@ class Kids { return new VideoInfo(response, this.#session.actions, cpn); } + /** + * Retrieves the contents of the given channel. + * @param channel_id - The channel id. + */ + async getChannel(channel_id: string): Promise { + const response = await this.#session.actions.execute('/browse', { browseId: channel_id, client: 'YTKIDS' }); + return new Channel(this.#session.actions, response.data); + } + /** * Retrieves the home feed. */ diff --git a/src/parser/classes/ItemSection.ts b/src/parser/classes/ItemSection.ts index d111057ce..1e5f3fde1 100644 --- a/src/parser/classes/ItemSection.ts +++ b/src/parser/classes/ItemSection.ts @@ -10,7 +10,8 @@ class ItemSection extends YTNode { header: CommentsHeader | ItemSectionHeader | ItemSectionTabbedHeader | null; contents; - target_id; + target_id?: string; + continuation?: string; constructor(data: any) { super(); @@ -20,6 +21,10 @@ class ItemSection extends YTNode { if (data.targetId || data.sectionIdentifier) { this.target_id = data?.target_id || data?.sectionIdentifier; } + + if (data.continuations) { + this.continuation = data.continuations?.at(0)?.nextContinuationData?.continuation; + } } } diff --git a/src/parser/index.ts b/src/parser/index.ts index 87b010d23..fb57f95f6 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -269,6 +269,8 @@ export default class Parser { } static parseLC(data: any) { + if (data.itemSectionContinuation) + return new ItemSectionContinuation(data.itemSectionContinuation); if (data.sectionListContinuation) return new SectionListContinuation(data.sectionListContinuation); if (data.liveChatContinuation) @@ -387,7 +389,22 @@ export default class Parser { export type ParsedResponse = ReturnType; -// Continuation nodes +// Continuation + +export class ItemSectionContinuation extends YTNode { + static readonly type = 'itemSectionContinuation'; + + contents: ObservedArray | null; + continuation?: string; + + constructor(data: any) { + super(); + this.contents = Parser.parseArray(data.contents); + if (data.continuations) { + this.continuation = data.continuations?.at(0)?.nextContinuationData?.continuation; + } + } +} export class AppendContinuationItemsAction extends YTNode { static readonly type = 'appendContinuationItemsAction'; diff --git a/src/parser/ytkids/Channel.ts b/src/parser/ytkids/Channel.ts new file mode 100644 index 000000000..4ece04685 --- /dev/null +++ b/src/parser/ytkids/Channel.ts @@ -0,0 +1,34 @@ +import Feed from '../../core/Feed'; +import Actions from '../../core/Actions'; +import C4TabbedHeader from '../classes/C4TabbedHeader'; +import ItemSection from '../classes/ItemSection'; +import { ItemSectionContinuation } from '..'; + +class Channel extends Feed { + header?: C4TabbedHeader; + contents?: ItemSection | ItemSectionContinuation; + + constructor(actions: Actions, data: any, already_parsed = false) { + super(actions, data, already_parsed); + this.header = this.page.header?.item().as(C4TabbedHeader); + this.contents = this.memo.getType(ItemSection).first() || this.page.continuation_contents?.as(ItemSectionContinuation); + } + + /** + * Retrieves next batch of videos. + */ + async getContinuation(): Promise { + const response = await this.actions.execute('/browse', { + continuation: this.contents?.continuation, + client: 'YTKIDS' + }); + + return new Channel(this.actions, response.data); + } + + get has_continuation(): boolean { + return !!this.contents?.continuation; + } +} + +export default Channel; \ No newline at end of file diff --git a/test/constants.ts b/test/constants.ts index 4e43962a5..4e138b622 100644 --- a/test/constants.ts +++ b/test/constants.ts @@ -33,5 +33,9 @@ export const CHANNELS = [ { ID: 'UCXuqSBlHAE6Xw-yeJA0Tunw', NAME: 'Linus Tech Tips' + }, + { + ID: 'UCpbpfcZfo-hoDAx2m1blFhg', + NAME: 'Learning Blocks' } ]; \ No newline at end of file diff --git a/test/main.test.ts b/test/main.test.ts index 1e726eb43..bc1d41e58 100644 --- a/test/main.test.ts +++ b/test/main.test.ts @@ -238,6 +238,11 @@ describe('YouTube.js Tests', () => { const info = await yt.kids.getInfo(VIDEOS[6].ID); expect(info.basic_info?.id).toBe(VIDEOS[6].ID); }); + + it('should retrieve a channel', async () => { + const channel = await yt.kids.getChannel(CHANNELS[1].ID); + expect(channel.videos.length).toBeGreaterThan(0); + }); }); });