Compare commits
No commits in common. "9b6c952fd3eaf0771636b137cd0d82cec3ebb08e" and "220e335314329a9994045dc2755c58b943e3ec1f" have entirely different histories.
9b6c952fd3
...
220e335314
4 changed files with 23 additions and 58 deletions
|
@ -345,9 +345,8 @@ impl MusicListMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_item(&mut self, item: MusicResponseItem) -> Result<Option<MusicEntityType>, String> {
|
fn map_item(&mut self, item: MusicResponseItem) -> Result<MusicEntityType, String> {
|
||||||
match item {
|
match item {
|
||||||
// List item
|
|
||||||
MusicResponseItem::MusicResponsiveListItemRenderer(item) => {
|
MusicResponseItem::MusicResponsiveListItemRenderer(item) => {
|
||||||
let mut columns = item.flex_columns.into_iter();
|
let mut columns = item.flex_columns.into_iter();
|
||||||
let title = columns.next().map(|col| col.renderer.text.to_string());
|
let title = columns.next().map(|col| col.renderer.text.to_string());
|
||||||
|
@ -357,27 +356,19 @@ impl MusicListMapper {
|
||||||
match item.navigation_endpoint {
|
match item.navigation_endpoint {
|
||||||
// Artist / Album / Playlist
|
// Artist / Album / Playlist
|
||||||
Some(ne) => {
|
Some(ne) => {
|
||||||
let mut subtitle_parts = c2
|
let (page_type, id) = ne
|
||||||
.ok_or_else(|| "could not get subtitle".to_owned())?
|
.music_page()
|
||||||
.renderer
|
.ok_or_else(|| "could not get navigation endpoint".to_owned())?;
|
||||||
.text
|
|
||||||
.split(util::DOT_SEPARATOR)
|
|
||||||
.into_iter();
|
|
||||||
|
|
||||||
let (page_type, id) = match ne.music_page() {
|
|
||||||
Some(music_page) => music_page,
|
|
||||||
None => {
|
|
||||||
// Ignore radio items
|
|
||||||
if subtitle_parts.len() == 1 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
return Err("invalid navigation endpoint".to_string());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let title =
|
let title =
|
||||||
title.ok_or_else(|| format!("track {}: could not get title", id))?;
|
title.ok_or_else(|| format!("track {}: could not get title", id))?;
|
||||||
|
|
||||||
|
let mut subtitle_parts = c2
|
||||||
|
.ok_or_else(|| format!("item {}: could not get subtitle", id))?
|
||||||
|
.renderer
|
||||||
|
.text
|
||||||
|
.split(util::DOT_SEPARATOR)
|
||||||
|
.into_iter();
|
||||||
let subtitle_p1 = subtitle_parts.next();
|
let subtitle_p1 = subtitle_parts.next();
|
||||||
let subtitle_p2 = subtitle_parts.next();
|
let subtitle_p2 = subtitle_parts.next();
|
||||||
let subtitle_p3 = subtitle_parts.next();
|
let subtitle_p3 = subtitle_parts.next();
|
||||||
|
@ -394,7 +385,7 @@ impl MusicListMapper {
|
||||||
avatar: item.thumbnail.into(),
|
avatar: item.thumbnail.into(),
|
||||||
subscriber_count,
|
subscriber_count,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Artist))
|
Ok(MusicEntityType::Artist)
|
||||||
}
|
}
|
||||||
PageType::Album => {
|
PageType::Album => {
|
||||||
let album_type = subtitle_p1
|
let album_type = subtitle_p1
|
||||||
|
@ -415,7 +406,7 @@ impl MusicListMapper {
|
||||||
year,
|
year,
|
||||||
by_va,
|
by_va,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Album))
|
Ok(MusicEntityType::Album)
|
||||||
}
|
}
|
||||||
PageType::Playlist => {
|
PageType::Playlist => {
|
||||||
// Part 1 may be the "Playlist" label
|
// Part 1 may be the "Playlist" label
|
||||||
|
@ -442,11 +433,11 @@ impl MusicListMapper {
|
||||||
track_count,
|
track_count,
|
||||||
from_ytm,
|
from_ytm,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Playlist))
|
Ok(MusicEntityType::Playlist)
|
||||||
}
|
}
|
||||||
PageType::Channel => {
|
PageType::Channel => {
|
||||||
// There may be broken YT channels from the artist search. They can be skipped.
|
// There may be broken YT channels from the artist search. They can be skipped.
|
||||||
Ok(None)
|
Ok(MusicEntityType::Artist)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -562,11 +553,10 @@ impl MusicListMapper {
|
||||||
is_video,
|
is_video,
|
||||||
track_nr,
|
track_nr,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Track))
|
Ok(MusicEntityType::Track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tile
|
|
||||||
MusicResponseItem::MusicTwoRowItemRenderer(item) => {
|
MusicResponseItem::MusicTwoRowItemRenderer(item) => {
|
||||||
let mut subtitle_parts = item.subtitle.split(util::DOT_SEPARATOR).into_iter();
|
let mut subtitle_parts = item.subtitle.split(util::DOT_SEPARATOR).into_iter();
|
||||||
let subtitle_p1 = subtitle_parts.next();
|
let subtitle_p1 = subtitle_parts.next();
|
||||||
|
@ -591,7 +581,7 @@ impl MusicListMapper {
|
||||||
is_video: true,
|
is_video: true,
|
||||||
track_nr: None,
|
track_nr: None,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Track))
|
Ok(MusicEntityType::Track)
|
||||||
}
|
}
|
||||||
// Artist / Album / Playlist
|
// Artist / Album / Playlist
|
||||||
None => {
|
None => {
|
||||||
|
@ -646,7 +636,7 @@ impl MusicListMapper {
|
||||||
year,
|
year,
|
||||||
by_va,
|
by_va,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Album))
|
Ok(MusicEntityType::Album)
|
||||||
}
|
}
|
||||||
PageType::Playlist => {
|
PageType::Playlist => {
|
||||||
let from_ytm = subtitle_p2
|
let from_ytm = subtitle_p2
|
||||||
|
@ -667,7 +657,7 @@ impl MusicListMapper {
|
||||||
track_count,
|
track_count,
|
||||||
from_ytm,
|
from_ytm,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Playlist))
|
Ok(MusicEntityType::Playlist)
|
||||||
}
|
}
|
||||||
PageType::Artist => {
|
PageType::Artist => {
|
||||||
let subscriber_count = subtitle_p1.and_then(|p| {
|
let subscriber_count = subtitle_p1.and_then(|p| {
|
||||||
|
@ -680,7 +670,7 @@ impl MusicListMapper {
|
||||||
avatar: item.thumbnail_renderer.into(),
|
avatar: item.thumbnail_renderer.into(),
|
||||||
subscriber_count,
|
subscriber_count,
|
||||||
}));
|
}));
|
||||||
Ok(Some(MusicEntityType::Artist))
|
Ok(MusicEntityType::Artist)
|
||||||
}
|
}
|
||||||
PageType::Channel => {
|
PageType::Channel => {
|
||||||
Err(format!("channel items unsupported. id: {}", id))
|
Err(format!("channel items unsupported. id: {}", id))
|
||||||
|
@ -701,12 +691,7 @@ impl MusicListMapper {
|
||||||
res.c
|
res.c
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|item| match self.map_item(item) {
|
.for_each(|item| match self.map_item(item) {
|
||||||
Ok(Some(et)) => {
|
Ok(t) => etype = Some(t),
|
||||||
if etype.is_none() {
|
|
||||||
etype = Some(et);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(e) => self.warnings.push(e),
|
Err(e) => self.warnings.push(e),
|
||||||
});
|
});
|
||||||
etype
|
etype
|
||||||
|
|
|
@ -72,19 +72,6 @@ impl RustyPipeQuery {
|
||||||
Ok(UrlTarget::Playlist { id })
|
Ok(UrlTarget::Playlist { id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Album or channel
|
|
||||||
Some("browse") => match path_split.next() {
|
|
||||||
Some(id) => {
|
|
||||||
if util::CHANNEL_ID_REGEX.is_match(id).unwrap_or_default() {
|
|
||||||
Ok(UrlTarget::Channel { id: id.to_owned() })
|
|
||||||
} else if util::ALBUM_ID_REGEX.is_match(id).unwrap_or_default() {
|
|
||||||
Ok(UrlTarget::Album { id: id.to_owned() })
|
|
||||||
} else {
|
|
||||||
Err(Error::Other("invalid url: no browse id".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Err(Error::Other("invalid url: invalid browse id".into())),
|
|
||||||
},
|
|
||||||
// Channel vanity URL or youtu.be shortlink
|
// Channel vanity URL or youtu.be shortlink
|
||||||
Some(mut id) => {
|
Some(mut id) => {
|
||||||
if id == "c" || id == "user" {
|
if id == "c" || id == "user" {
|
||||||
|
|
|
@ -68,7 +68,9 @@ impl UrlTarget {
|
||||||
format!("{}/playlist?list={}", yt_host, id)
|
format!("{}/playlist?list={}", yt_host, id)
|
||||||
}
|
}
|
||||||
UrlTarget::Album { id } => {
|
UrlTarget::Album { id } => {
|
||||||
format!("https://music.youtube.com/browse/{}", id)
|
// The official album URLs use the playlist ID
|
||||||
|
// This looks weird, but it works
|
||||||
|
format!("{}/channel/{}", yt_host, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1189,8 +1189,6 @@ async fn search_suggestion_empty() {
|
||||||
// Both a video ID and a channel name + video time param => returns video
|
// Both a video ID and a channel name + video time param => returns video
|
||||||
#[case("https://piped.mha.fi/dQw4w9WgXcQ?t=0", UrlTarget::Video {id: "dQw4w9WgXcQ".to_owned(), start_time: 0})]
|
#[case("https://piped.mha.fi/dQw4w9WgXcQ?t=0", UrlTarget::Video {id: "dQw4w9WgXcQ".to_owned(), start_time: 0})]
|
||||||
#[case("https://music.youtube.com/playlist?list=OLAK5uy_k0yFrZlFRgCf3rLPza-lkRmCrtLPbK9pE", UrlTarget::Album {id: "MPREb_GyH43gCvdM5".to_owned()})]
|
#[case("https://music.youtube.com/playlist?list=OLAK5uy_k0yFrZlFRgCf3rLPza-lkRmCrtLPbK9pE", UrlTarget::Album {id: "MPREb_GyH43gCvdM5".to_owned()})]
|
||||||
#[case("https://music.youtube.com/browse/MPREb_GyH43gCvdM5", UrlTarget::Album {id: "MPREb_GyH43gCvdM5".to_owned()})]
|
|
||||||
#[case("https://music.youtube.com/browse/UC5I2hjZYiW9gZPVkvzM8_Cw", UrlTarget::Channel {id: "UC5I2hjZYiW9gZPVkvzM8_Cw".to_owned()})]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn resolve_url(#[case] url: &str, #[case] expect: UrlTarget) {
|
async fn resolve_url(#[case] url: &str, #[case] expect: UrlTarget) {
|
||||||
let rp = RustyPipe::builder().strict().build();
|
let rp = RustyPipe::builder().strict().build();
|
||||||
|
@ -1748,13 +1746,6 @@ async fn music_search_playlists_community() {
|
||||||
assert!(!playlist.from_ytm);
|
assert!(!playlist.from_ytm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The YouTube Music search sometimes shows genre radio items. They should be skipped.
|
|
||||||
#[tokio::test]
|
|
||||||
async fn music_search_genre_radio() {
|
|
||||||
let rp = RustyPipe::builder().strict().build();
|
|
||||||
rp.query().music_search("pop radio").await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
//#TESTUTIL
|
//#TESTUTIL
|
||||||
|
|
||||||
/// Assert equality within 10% margin
|
/// Assert equality within 10% margin
|
||||||
|
|
Loading…
Reference in a new issue