fix: accept music album playlist response without header
This commit is contained in:
parent
8097873fe1
commit
38bc12f695
3 changed files with 74 additions and 37 deletions
|
|
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, ExtractionError},
|
error::{Error, ExtractionError},
|
||||||
model::{AlbumId, ChannelId, MusicAlbum, MusicPlaylist, Paginator},
|
model::{AlbumId, ChannelId, MusicAlbum, MusicPlaylist, Paginator, TrackItem},
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
util::{self, TryRemove},
|
util::{self, TryRemove},
|
||||||
};
|
};
|
||||||
|
|
@ -65,8 +65,6 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
||||||
) -> Result<MapResult<MusicPlaylist>, ExtractionError> {
|
) -> Result<MapResult<MusicPlaylist>, ExtractionError> {
|
||||||
// dbg!(&self);
|
// dbg!(&self);
|
||||||
|
|
||||||
let header = self.header.music_detail_header_renderer;
|
|
||||||
|
|
||||||
let mut content = self.contents.single_column_browse_results_renderer.contents;
|
let mut content = self.contents.single_column_browse_results_renderer.contents;
|
||||||
let mut music_contents = content
|
let mut music_contents = content
|
||||||
.try_swap_remove(0)
|
.try_swap_remove(0)
|
||||||
|
|
@ -85,31 +83,15 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
||||||
"no sectionListRenderer content",
|
"no sectionListRenderer content",
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
let playlist_id = shelf
|
if let Some(playlist_id) = shelf.playlist_id {
|
||||||
.playlist_id
|
if playlist_id != id {
|
||||||
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
return Err(ExtractionError::WrongResult(format!(
|
||||||
"no playlist id",
|
"got wrong playlist id {}, expected {}",
|
||||||
)))?;
|
playlist_id, id
|
||||||
|
)));
|
||||||
if playlist_id != id {
|
}
|
||||||
return Err(ExtractionError::WrongResult(format!(
|
|
||||||
"got wrong playlist id {}, expected {}",
|
|
||||||
playlist_id, id
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let from_ytm = header
|
|
||||||
.subtitle
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.any(|c| c.as_str() == util::YT_MUSIC_NAME);
|
|
||||||
|
|
||||||
let channel = header
|
|
||||||
.subtitle
|
|
||||||
.0
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|c| ChannelId::try_from(c).ok());
|
|
||||||
|
|
||||||
let mut mapper = MusicListMapper::new(lang);
|
let mut mapper = MusicListMapper::new(lang);
|
||||||
mapper.map_response(shelf.contents);
|
mapper.map_response(shelf.contents);
|
||||||
let map_res = mapper.conv_items();
|
let map_res = mapper.conv_items();
|
||||||
|
|
@ -120,10 +102,12 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
||||||
.map(|cont| cont.next_continuation_data.continuation);
|
.map(|cont| cont.next_continuation_data.continuation);
|
||||||
|
|
||||||
let track_count = match ctoken {
|
let track_count = match ctoken {
|
||||||
Some(_) => header
|
Some(_) => self.header.as_ref().and_then(|h| {
|
||||||
.second_subtitle
|
h.music_detail_header_renderer
|
||||||
.first()
|
.second_subtitle
|
||||||
.and_then(|txt| util::parse_numeric::<u64>(txt).ok()),
|
.first()
|
||||||
|
.and_then(|txt| util::parse_numeric::<u64>(txt).ok())
|
||||||
|
}),
|
||||||
None => Some(map_res.c.len() as u64),
|
None => Some(map_res.c.len() as u64),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -132,13 +116,63 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
||||||
.try_swap_remove(0)
|
.try_swap_remove(0)
|
||||||
.map(|c| c.next_continuation_data.continuation);
|
.map(|c| c.next_continuation_data.continuation);
|
||||||
|
|
||||||
|
let (from_ytm, channel, name, thumbnail, description) = match self.header {
|
||||||
|
Some(header) => {
|
||||||
|
let h = header.music_detail_header_renderer;
|
||||||
|
|
||||||
|
let from_ytm = h
|
||||||
|
.subtitle
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.any(|c| c.as_str() == util::YT_MUSIC_NAME);
|
||||||
|
let channel = h
|
||||||
|
.subtitle
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|c| ChannelId::try_from(c).ok());
|
||||||
|
|
||||||
|
(
|
||||||
|
from_ytm,
|
||||||
|
channel,
|
||||||
|
h.title,
|
||||||
|
h.thumbnail.into(),
|
||||||
|
h.description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Album playlists fetched via the playlist method dont include a header
|
||||||
|
let (album, cover) = map_res
|
||||||
|
.c
|
||||||
|
.first()
|
||||||
|
.and_then(|t: &TrackItem| {
|
||||||
|
t.album.as_ref().map(|a| (a.clone(), t.cover.clone()))
|
||||||
|
})
|
||||||
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"playlist without header or album items",
|
||||||
|
)))?;
|
||||||
|
|
||||||
|
if !map_res.c.iter().all(|t| {
|
||||||
|
t.album
|
||||||
|
.as_ref()
|
||||||
|
.map(|a| a.id == album.id)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}) {
|
||||||
|
return Err(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"album playlist containing items from different albums",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
(true, None, album.name, cover, None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
c: MusicPlaylist {
|
c: MusicPlaylist {
|
||||||
id: playlist_id,
|
id: id.to_owned(),
|
||||||
name: header.title,
|
name,
|
||||||
thumbnail: header.thumbnail.into(),
|
thumbnail,
|
||||||
channel,
|
channel,
|
||||||
description: header.description,
|
description,
|
||||||
track_count,
|
track_count,
|
||||||
from_ytm,
|
from_ytm,
|
||||||
tracks: Paginator::new_ext(
|
tracks: Paginator::new_ext(
|
||||||
|
|
@ -170,7 +204,10 @@ impl MapResponse<MusicAlbum> for response::MusicPlaylist {
|
||||||
) -> Result<MapResult<MusicAlbum>, ExtractionError> {
|
) -> Result<MapResult<MusicAlbum>, ExtractionError> {
|
||||||
// dbg!(&self);
|
// dbg!(&self);
|
||||||
|
|
||||||
let header = self.header.music_detail_header_renderer;
|
let header = self
|
||||||
|
.header
|
||||||
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no header")))?
|
||||||
|
.music_detail_header_renderer;
|
||||||
|
|
||||||
let mut content = self.contents.single_column_browse_results_renderer.contents;
|
let mut content = self.contents.single_column_browse_results_renderer.contents;
|
||||||
let sections = content
|
let sections = content
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use super::{ContentsRenderer, Tab};
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct MusicPlaylist {
|
pub(crate) struct MusicPlaylist {
|
||||||
pub contents: Contents,
|
pub contents: Contents,
|
||||||
pub header: Header,
|
pub header: Option<Header>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -1031,7 +1031,7 @@ pub struct ArtistId {
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct AlbumItem {
|
pub struct AlbumItem {
|
||||||
/// Unique YouTube album ID (e.g. `OLAK5uy_nZpcQys48R0aNb046hV-n1OAHGE4reftQ`)
|
/// Unique YouTube album ID (e.g. `MPREb_T5s950Swfdy`)
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// Album name
|
/// Album name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
|
||||||
Reference in a new issue