mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-19 20:41:17 +00:00
* feat: add core comments section classes * chore: update type declarations * chore: fix linter warnings * style: fix linter * chore: update tests * chore(tests): fix typo * chore(tests): fix typo x2 * fix(tests): `getReplies()` method is only present in `CommentThread` and not `Comment` * chore(tests): fix comment id path * chore(tests): remove outdated code * chore(tests): fix results path * chore: enforce code style * chore: update type declarations * docs: add examples and documentation * chore(docs): fix paths * chore(docs): fix more paths * chore(docs): fix `Comments.js` path * chore(docs): fix typo * chore(docs): mention example file * chore(examples): fix imports * chore(examples): fix typo
147 lines
4.0 KiB
JavaScript
147 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
const Parser = require('../contents');
|
|
const { observe, InnertubeError } = require('../../utils/Utils');
|
|
|
|
/** @namespace */
|
|
class Search {
|
|
#page;
|
|
#actions;
|
|
#continuation;
|
|
#header;
|
|
|
|
/**
|
|
* @param {object} response - API response.
|
|
* @param {import('../../core/Actions')} actions
|
|
* @param {object} args
|
|
* @param {boolean} args.is_continuation
|
|
* @param {boolean} args.is_filtered
|
|
*/
|
|
constructor(response, actions, args = {}) {
|
|
this.#actions = actions;
|
|
|
|
this.#page = args.is_continuation
|
|
&& response
|
|
|| Parser.parseResponse(response.data);
|
|
|
|
const tab = this.#page.contents.tabs.get({ selected: true });
|
|
const shelves = tab.content.contents;
|
|
|
|
const item_section = shelves.get({ type: 'ItemSection' });
|
|
|
|
this.#header = tab.content.header;
|
|
|
|
/**
|
|
* @type {import('../contents/classes/DidYouMean')}
|
|
*/
|
|
this.did_you_mean = item_section?.contents.get({ type: 'DidYouMean' }) || null;
|
|
|
|
/**
|
|
* @type {import('../contents/classes/ShowingResultsFor')}
|
|
*/
|
|
this.showing_results_for = item_section?.contents.get({ type: 'ShowingResultsFor' }) || null;
|
|
|
|
(!!this.did_you_mean || !!this.showing_results_for) && shelves.shift();
|
|
|
|
if (args.is_continuation || args.is_filtered) {
|
|
const shelf = shelves.get({ type: 'MusicShelf' });
|
|
|
|
this.results = shelf.contents;
|
|
this.#continuation = shelf.continuation;
|
|
|
|
return;
|
|
}
|
|
|
|
/** @type {{ title: string; items: object[]; getMore: Promise.<Search>; }} */
|
|
this.sections = observe(shelves.map((shelf) => ({
|
|
title: shelf.title,
|
|
contents: shelf.contents,
|
|
getMore: () => this.#getMore(shelf)
|
|
})));
|
|
}
|
|
|
|
async #getMore(shelf) {
|
|
if (!shelf.endpoint)
|
|
throw new InnertubeError(`${shelf.title} doesn't have more items`);
|
|
|
|
const response = await shelf.endpoint.call(this.#actions, 'YTMUSIC');
|
|
return new Search(response, this.#actions, { is_continuation: true });
|
|
}
|
|
|
|
/**
|
|
* Retrieves continuation, only works for individual sections or filtered results.
|
|
*
|
|
* @returns {Promise.<Search>}
|
|
*/
|
|
async getContinuation() {
|
|
if (!this.#continuation)
|
|
throw new InnertubeError('Looks like you\'ve reached the end');
|
|
|
|
const response = await this.#actions.search({ ctoken: this.#continuation, client: 'YTMUSIC' });
|
|
const data = response.data.continuationContents.musicShelfContinuation;
|
|
|
|
this.results = Parser.parse(data.contents);
|
|
this.#continuation = data?.continuations?.[0]?.nextContinuationData?.continuation;
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Applies given filter to the search.
|
|
*
|
|
* @param {string} name
|
|
* @returns {Promise.<Search>}
|
|
*/
|
|
async selectFilter(name) {
|
|
if (!this.filters.includes(name))
|
|
throw new InnertubeError('Invalid filter', { available_filters: this.filters });
|
|
|
|
const filter = this.#header.chips.get({ text: name });
|
|
if (filter.is_selected) return this;
|
|
|
|
const response = await filter.endpoint.call(this.#actions, 'YTMUSIC');
|
|
|
|
return new Search(response, this.#actions, { is_continuation: true });
|
|
}
|
|
|
|
/** @type {boolean} */
|
|
get has_continuation() {
|
|
return !!this.#continuation;
|
|
}
|
|
|
|
/** @type {string[]} */
|
|
get filters() {
|
|
return this.#header.chips.map((chip) => chip.text);
|
|
}
|
|
|
|
/** @type {import('../contents/classes/MusicResponsiveListItem')[]} */
|
|
get songs() {
|
|
return this.sections.get({ title: 'Songs' });
|
|
}
|
|
|
|
/** @type {import('../contents/classes/MusicResponsiveListItem')[]} */
|
|
get videos() {
|
|
return this.sections.get({ title: 'Videos' });
|
|
}
|
|
|
|
/** @type {import('../contents/classes/MusicResponsiveListItem')[]} */
|
|
get albums() {
|
|
return this.sections.get({ title: 'Albums' });
|
|
}
|
|
|
|
/** @type {import('../contents/classes/MusicResponsiveListItem')[]} */
|
|
get artists() {
|
|
return this.sections.get({ title: 'Artists' });
|
|
}
|
|
|
|
/** @type {import('../contents/classes/MusicResponsiveListItem')[]} */
|
|
get playlists() {
|
|
return this.sections.get({ title: 'Community playlists' });
|
|
}
|
|
|
|
get page() {
|
|
return this.#page;
|
|
}
|
|
}
|
|
|
|
module.exports = Search; |