diff --git a/continuations.ts b/continuations.ts index 4454da8..b7d3a07 100644 --- a/continuations.ts +++ b/continuations.ts @@ -5,7 +5,7 @@ export async function get_continuations( continuation_type: string, limit: number | null, request: (additional_params: Record) => Promise, - parse: (data: any) => any[], + parse: (data: any, continuation?: any) => any[], _ctoken_path = "", reloadable = false, ) { @@ -42,6 +42,28 @@ export async function get_continuations( return { items, continuation }; } +export async function get_sort_continuations( + results: any, + continuation_type: string, + request: (additional_params: Record) => Promise, + parse: (data: any, continuation?: any) => Return, +) { + const get_params = () => get_reloadable_continuation_params(results); + + let params = get_params(); + + const response = await request(params); + + if ("continuationContents" in response) { + results = response.continuationContents[continuation_type]; + params = get_params(); + } else { + return null; + } + + return get_continuation_contents(results, parse); +} + export async function get_validated_continuations( results: any, continuation_type: string, @@ -119,15 +141,15 @@ function get_continuation_object(ctoken: string) { export function get_continuation_contents( continuation: any, - parse: (data: any) => T[], + parse: (data: any, continuation?: any) => T, ) { for (const term of ["contents", "items"]) { if (term in continuation) { - return parse(continuation[term]); + return parse(continuation[term], continuation); } } - return [] as T[]; + return [] as T; } export async function resend_request_until_valid( diff --git a/mixins/browsing.ts b/mixins/browsing.ts index 6551da1..551138f 100644 --- a/mixins/browsing.ts +++ b/mixins/browsing.ts @@ -1,6 +1,6 @@ import CONSTANTS2 from "../constants-ng.json" assert { type: "json" }; -import { get_continuations } from "../continuations.ts"; +import { get_continuations, get_sort_continuations } from "../continuations.ts"; import { CAROUSEL, CONTENT, @@ -8,6 +8,7 @@ import { DESCRIPTION_SHELF, find_object_by_key, GRID, + GRID_ITEMS, MRLIR, MRLITFC, MUSIC_SHELF, @@ -48,7 +49,12 @@ import { import { ArtistRun, Format, parse_format } from "../parsers/songs.ts"; import { j, jo, sum_total_duration } from "../util.ts"; import { Thumbnail } from "./playlist.ts"; -import { AbortOptions, PaginationOptions } from "./utils.ts"; +import { + AbortOptions, + get_sort_options, + PaginationOptions, + SortOptions, +} from "./utils.ts"; import { request_json } from "./_request.ts"; export { is_ranked } from "../parsers/browsing.ts"; @@ -531,21 +537,69 @@ export async function get_lyrics( } export interface ArtistAlbums { - artist: string; + artist: string | null; title: string; results: ParsedAlbum[]; + sort: SortOptions; } export async function get_artist_albums( channelId: string, params: string, - options: AbortOptions = {}, + options: Omit = {}, ): Promise { + const data = { + browseId: channelId, + params, + }; + + function get_chips(renderer: any) { + const header = j(renderer, "header.musicSideAlignedItemRenderer"); + + const chips = j(header, "startItems.0.chipCloudRenderer.chips"); + + const selected_chip = j( + chips + .find((chip: any) => chip.chipCloudChipRenderer.isSelected == true), + "chipCloudChipRenderer", + TEXT_RUN_TEXT, + ); + + return { + selected_chip: selected_chip as string, + sort_options: get_sort_options(header.endItems), + }; + } + + if (options.continuation) { + return get_sort_continuations( + options.continuation, + "sectionListContinuation", + (params) => { + return request_json("browse", { + data, + params, + signal: options.signal, + }); + }, + (contents, continuation) => { + const chips = get_chips(continuation); + + return { + artist: null, + title: chips.selected_chip, + results: parse_content_list( + j(contents[0], GRID_ITEMS), + parse_album, + ), + sort: chips.sort_options, + }; + }, + )! as Promise; + } + const json = await request_json("browse", { - data: { - browseId: channelId, - params, - }, + data, signal: options.signal, }); @@ -553,19 +607,13 @@ export async function get_artist_albums( const grid = j(columnTab, SECTION_LIST_ITEM, GRID); - const chips = j( - columnTab, - "sectionListRenderer.header.musicSideAlignedItemRenderer.startItems.0.chipCloudRenderer.chips", - ); - - const selected_chip = chips - .filter((chip: any) => chip.chipCloudChipRenderer.isSelected == true) - .map((chip: any) => j(chip.chipCloudChipRenderer, TEXT_RUN_TEXT))[0]; + const chips = get_chips(j(columnTab, "sectionListRenderer")); return { artist: j(json, "header.musicHeaderRenderer", TITLE_TEXT), - title: selected_chip, + title: chips.selected_chip, results: parse_content_list(grid.items, parse_album), + sort: chips.sort_options, }; } diff --git a/mixins/utils.ts b/mixins/utils.ts index 65da1a0..7976608 100644 --- a/mixins/utils.ts +++ b/mixins/utils.ts @@ -1,6 +1,8 @@ import { ERROR_CODE, MuseError } from "../errors.ts"; +import { TITLE_TEXT } from "../nav.ts"; import { LikeStatus } from "../parsers/songs.ts"; import { get_option } from "../setup.ts"; +import { j } from "../util.ts"; export { get_option }; export function prepare_like_endpoint(status: LikeStatus) { @@ -128,3 +130,41 @@ export interface PaginationOptions extends AbortOptions { export interface PaginationAndOrderOptions extends PaginationOptions { order?: Order; } + +export interface SortOptions { + selected: string; + options: { title: string; continuation: string }[]; +} + +export function get_sort_options(chips: any): SortOptions { + const sort = j( + chips.find((chip: any) => "musicSortFilterButtonRenderer" in chip), + "musicSortFilterButtonRenderer", + ); + + const selected = j(sort, TITLE_TEXT); + const options = j(sort, "menu.musicMultiSelectMenuRenderer.options") + .map((option: any) => { + const renderer = j(option, "musicMultiSelectMenuItemRenderer"); + + return { + title: j(renderer, TITLE_TEXT), + continuation: j( + renderer, + "selectedCommand.commandExecutorCommand.commands", + ) + .filter((option: any) => + option.browseSectionListReloadEndpoint != null + ) + .map((option: any) => + option.browseSectionListReloadEndpoint.continuation + .reloadContinuationData.continuation + )[0], + }; + }); + + return { + selected, + options, + }; +}