Skip to content

Commit 77b39c7

Browse files
authored
feat(ytmusic): Add support for YouTube Music mood filters (#404)
* add filters property to ytmusic HomeFeed * remove implied type * add applyFilter to HomeFeed * add test * remove section_list var
1 parent 7c530d3 commit 77b39c7

File tree

2 files changed

+38
-0
lines changed

2 files changed

+38
-0
lines changed

src/parser/ytmusic/HomeFeed.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ import type { ApiResponse } from '../../core/Actions.js';
99
import type { ObservedArray } from '../helpers.js';
1010
import type { IBrowseResponse } from '../types/ParsedResponse.js';
1111
import { InnertubeError } from '../../utils/Utils.js';
12+
import ChipCloud from '../classes/ChipCloud.js';
13+
import ChipCloudChip from '../classes/ChipCloudChip.js';
1214

1315
class HomeFeed {
1416
#page: IBrowseResponse;
1517
#actions: Actions;
1618
#continuation?: string;
1719

1820
sections?: ObservedArray<MusicCarouselShelf | MusicTastebuilderShelf>;
21+
header?: ChipCloud;
1922

2023
constructor(response: ApiResponse, actions: Actions) {
2124
this.#actions = actions;
@@ -36,6 +39,7 @@ class HomeFeed {
3639
return;
3740
}
3841

42+
this.header = tab.content?.as(SectionList).header?.as(ChipCloud);
3943
this.#continuation = tab.content?.as(SectionList).continuation;
4044
this.sections = tab.content?.as(SectionList).contents.as(MusicCarouselShelf, MusicTastebuilderShelf);
4145
}
@@ -55,6 +59,33 @@ class HomeFeed {
5559
return new HomeFeed(response, this.#actions);
5660
}
5761

62+
async applyFilter(target_filter: string | ChipCloudChip): Promise<HomeFeed> {
63+
let cloud_chip: ChipCloudChip | undefined;
64+
65+
if (typeof target_filter === 'string') {
66+
cloud_chip = this.header?.chips?.as(ChipCloudChip).get({ text: target_filter });
67+
if (!cloud_chip)
68+
throw new InnertubeError('Could not find filter with given name.', { available_filters: this.filters });
69+
} else if (target_filter?.is(ChipCloudChip)) {
70+
cloud_chip = target_filter;
71+
}
72+
73+
if (!cloud_chip)
74+
throw new InnertubeError('Invalid filter', { available_filters: this.filters });
75+
76+
if (cloud_chip?.is_selected) return this;
77+
78+
if (!cloud_chip.endpoint)
79+
throw new InnertubeError('Selected filter does not have an endpoint.');
80+
81+
const response = await cloud_chip.endpoint.call(this.#actions, { client: 'YTMUSIC' });
82+
return new HomeFeed(response, this.#actions);
83+
}
84+
85+
get filters(): string[] {
86+
return this.header?.chips?.as(ChipCloudChip).map((chip) => chip.text) || [];
87+
}
88+
5889
get has_continuation(): boolean {
5990
return !!this.#continuation;
6091
}

test/main.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,13 @@ describe('YouTube.js Tests', () => {
296296
expect(incremental_continuation.sections).toBeDefined();
297297
expect(incremental_continuation.sections?.length).toBeGreaterThan(0);
298298
});
299+
300+
test('HomeFeed#applyFilter', async () => {
301+
home = await home.applyFilter(home.filters[1]);
302+
expect(home).toBeDefined();
303+
expect(home.sections).toBeDefined();
304+
expect(home.sections?.length).toBeGreaterThan(0);
305+
});
299306
});
300307

301308
test('Innertube#music.getExplore', async () => {

0 commit comments

Comments
 (0)