feat(ytkids): add getChannel() (#292)

This commit is contained in:
LuanRT
2023-01-23 05:38:53 -03:00
committed by GitHub
parent 2bbefefbb7
commit 0fc29f0bbf
8 changed files with 107 additions and 2 deletions

View File

@@ -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)
<a name="search"></a>
@@ -64,6 +65,33 @@ Retrieves video info.
</p>
</details>
<a name="getchannel"></a>
### getChannel(channel_id)
Retrieves channel info.
**Returns:** `Promise.<Channel>`
| Param | Type | Description |
| --- | --- | --- |
| channel_id | `string` | The channel id |
<details>
<summary>Methods & Getters</summary>
<p>
- `<channel>#getContinuation()`
- Retrieves next batch of videos.
- `<channel>#has_continuation`
- Returns whether there are more videos to retrieve.
- `<channel>#page`
- Returns the original InnerTube response(s), parsed and sanitized.
</p>
</details>
<a name="gethomefeed"></a>
### getHomeFeed()

View File

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

View File

@@ -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<Channel> {
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.
*/

View File

@@ -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;
}
}
}

View File

@@ -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<typeof Parser.parseResponse>;
// Continuation nodes
// Continuation
export class ItemSectionContinuation extends YTNode {
static readonly type = 'itemSectionContinuation';
contents: ObservedArray<YTNode> | 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';

View File

@@ -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<Channel> {
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;

View File

@@ -33,5 +33,9 @@ export const CHANNELS = [
{
ID: 'UCXuqSBlHAE6Xw-yeJA0Tunw',
NAME: 'Linus Tech Tips'
},
{
ID: 'UCpbpfcZfo-hoDAx2m1blFhg',
NAME: 'Learning Blocks'
}
];

View File

@@ -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);
});
});
});