feat: add music_new_albums/videos

This commit is contained in:
ThetaDev 2022-11-28 00:56:17 +01:00
parent bd936a8c42
commit 8f82694eaf
15 changed files with 103714 additions and 19 deletions

View file

@ -14,7 +14,6 @@ members = [".", "codegen", "cli"]
[features]
default = ["default-tls"]
all = ["rss"]
rss = ["quick-xml"]

View file

@ -1,5 +1,5 @@
test:
cargo test -F all
cargo test --all-features
testfiles:
cargo run -p rustypipe-codegen -- -d . download-testfiles

View file

@ -28,4 +28,4 @@ inspired by [NewPipe](https://github.com/TeamNewPipe/NewPipeExtractor).
- [X] **Track details** (lyrics, recommendations)
- [ ] **Moods**
- [ ] **Charts**
- [ ] **New**
- [X] **New**

View file

@ -55,6 +55,8 @@ pub async fn download_testfiles(project_root: &Path) {
music_related(&testfiles).await;
music_radio(&testfiles).await;
music_radio_cont(&testfiles).await;
music_new_albums(&testfiles).await;
music_new_videos(&testfiles).await;
}
const CLIENT_TYPES: [ClientType; 5] = [
@ -801,3 +803,27 @@ async fn music_radio_cont(testfiles: &Path) {
let rp = rp_testfile(&json_path);
res.next(&rp.query()).await.unwrap().unwrap();
}
async fn music_new_albums(testfiles: &Path) {
let mut json_path = testfiles.to_path_buf();
json_path.push("music_new");
json_path.push("albums_default.json");
if json_path.exists() {
return;
}
let rp = rp_testfile(&json_path);
rp.query().music_new_albums().await.unwrap();
}
async fn music_new_videos(testfiles: &Path) {
let mut json_path = testfiles.to_path_buf();
json_path.push("music_new");
json_path.push("videos_default.json");
if json_path.exists() {
return;
}
let rp = rp_testfile(&json_path);
rp.query().music_new_videos().await.unwrap();
}

View file

@ -5,6 +5,7 @@ pub(crate) mod response;
mod channel;
mod music_artist;
mod music_details;
mod music_new;
mod music_playlist;
mod music_search;
mod pagination;

126
src/client/music_new.rs Normal file
View file

@ -0,0 +1,126 @@
use std::borrow::Cow;
use crate::{
client::response::music_item::MusicListMapper,
error::{Error, ExtractionError},
model::{AlbumItem, FromYtItem, TrackItem},
};
use super::{response, ClientType, MapResponse, QBrowse, RustyPipeQuery};
impl RustyPipeQuery {
pub async fn music_new_albums(&self) -> Result<Vec<AlbumItem>, Error> {
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QBrowse {
context,
browse_id: "FEmusic_new_releases_albums",
};
self.execute_request::<response::MusicNew, _, _>(
ClientType::DesktopMusic,
"music_new_albums",
"",
"browse",
&request_body,
)
.await
}
pub async fn music_new_videos(&self) -> Result<Vec<TrackItem>, Error> {
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QBrowse {
context,
browse_id: "FEmusic_new_releases_videos",
};
self.execute_request::<response::MusicNew, _, _>(
ClientType::DesktopMusic,
"music_new_videos",
"",
"browse",
&request_body,
)
.await
}
}
impl<T: FromYtItem> MapResponse<Vec<T>> for response::MusicNew {
fn map_response(
self,
_id: &str,
lang: crate::param::Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<crate::serializer::MapResult<Vec<T>>, ExtractionError> {
let items = self
.contents
.single_column_browse_results_renderer
.contents
.into_iter()
.next()
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))?
.tab_renderer
.content
.section_list_renderer
.contents
.into_iter()
.next()
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))?
.grid_renderer
.items;
let mut mapper = MusicListMapper::new(lang);
mapper.map_response(items);
Ok(mapper.conv_items())
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::BufReader, path::Path};
use rstest::rstest;
use super::*;
use crate::{param::Language, serializer::MapResult};
#[rstest]
#[case::default("default")]
fn map_music_new_albums(#[case] name: &str) {
let filename = format!("testfiles/music_new/albums_{}.json", name);
let json_path = Path::new(&filename);
let json_file = File::open(json_path).unwrap();
let new_albums: response::MusicNew =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Vec<AlbumItem>> =
new_albums.map_response("", Language::En, None).unwrap();
assert!(
map_res.warnings.is_empty(),
"deserialization/mapping warnings: {:?}",
map_res.warnings
);
insta::assert_ron_snapshot!(format!("map_music_new_albums_{}", name), map_res.c);
}
#[rstest]
#[case::default("default")]
fn map_music_new_videos(#[case] name: &str) {
let filename = format!("testfiles/music_new/videos_{}.json", name);
let json_path = Path::new(&filename);
let json_file = File::open(json_path).unwrap();
let new_albums: response::MusicNew =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Vec<TrackItem>> =
new_albums.map_response("", Language::En, None).unwrap();
assert!(
map_res.warnings.is_empty(),
"deserialization/mapping warnings: {:?}",
map_res.warnings
);
insta::assert_ron_snapshot!(format!("map_music_new_videos_{}", name), map_res.c);
}
}

View file

@ -2,6 +2,7 @@ pub(crate) mod channel;
pub(crate) mod music_artist;
pub(crate) mod music_details;
pub(crate) mod music_item;
pub(crate) mod music_new;
pub(crate) mod music_playlist;
pub(crate) mod music_search;
pub(crate) mod player;
@ -19,6 +20,7 @@ pub(crate) use music_details::MusicDetails;
pub(crate) use music_details::MusicLyrics;
pub(crate) use music_details::MusicRelated;
pub(crate) use music_item::MusicContinuation;
pub(crate) use music_new::MusicNew;
pub(crate) use music_playlist::MusicPlaylist;
pub(crate) use music_search::MusicSearch;
pub(crate) use music_search::MusicSearchSuggestion;

View file

@ -1,10 +1,10 @@
use serde::Deserialize;
use serde_with::{serde_as, DefaultOnError};
use crate::serializer::{text::Text, MapResult, VecLogError};
use crate::serializer::text::Text;
use super::{
music_item::{ItemSection, MusicResponseItem, MusicThumbnailRenderer},
music_item::{Grid, ItemSection, MusicThumbnailRenderer},
ContentsRenderer, SectionList, Tab,
};
@ -67,20 +67,6 @@ pub(crate) struct MusicArtistAlbums {
pub contents: Contents<Grid>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Grid {
pub grid_renderer: GridRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct GridRenderer {
#[serde_as(as = "VecLogError<_>")]
pub items: MapResult<Vec<MusicResponseItem>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SimpleHeader {

View file

@ -340,6 +340,20 @@ pub(crate) struct MusicItemMenuEntry {
pub menu_navigation_item_renderer: ButtonRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Grid {
pub grid_renderer: GridRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct GridRenderer {
#[serde_as(as = "VecLogError<_>")]
pub items: MapResult<Vec<MusicResponseItem>>,
}
/*
#MAPPER
*/

View file

@ -0,0 +1,15 @@
use serde::Deserialize;
use super::{music_item::Grid, ContentsRenderer, SectionList, Tab};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MusicNew {
pub contents: Contents,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Contents {
pub single_column_browse_results_renderer: ContentsRenderer<Tab<SectionList<Grid>>>,
}

View file

@ -0,0 +1,299 @@
---
source: src/client/music_new.rs
assertion_line: 125
expression: map_res.c
---
[
TrackItem(
id: "skl6N3zGv-s",
title: "Adieu",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/skl6N3zGv-s/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3ktEUMuvlRUDZBMXDRt-Cx7Nz3EWg",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/skl6N3zGv-s/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3nyQxZBIheGvsVXeL2EBknUqHPJow",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCs6GGpd9zvsYghuYe0VDFUQ"),
name: "Rammstein",
),
],
artist_id: Some("UCs6GGpd9zvsYghuYe0VDFUQ"),
album: None,
view_count: Some(8600000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "gFERoNpcnFU",
title: "Marteria - Wald (Official Video)",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/gFERoNpcnFU/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3mLZl-GbssOYbd_1gOgbN0VeeD1Wg",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/gFERoNpcnFU/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3locAMTqlz38oUE3SFLrg2Z4-g1Aw",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCicJjripVxiTXbUfociVZwQ"),
name: "MARTERIA",
),
],
artist_id: Some("UCicJjripVxiTXbUfociVZwQ"),
album: None,
view_count: Some(244000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "bmEzom5sfCI",
title: "ELIF X PA SPORTS - ICH DENK AN DICH (Official Video)",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/bmEzom5sfCI/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nFPXYLNpvXAYJwNc8cDZv2zbTQ_w",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/bmEzom5sfCI/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3mkd-afVklbhPQSmdCLjpMEIj6zow",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCuO5r4J0jTrgRYnmfEOd3UQ"),
name: "ELIF",
),
],
artist_id: Some("UCuO5r4J0jTrgRYnmfEOd3UQ"),
album: None,
view_count: Some(314000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "QHY2pm7uT3k",
title: "HOME RUN",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/QHY2pm7uT3k/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nYr8bEDDfJfpHsCldOvYscc_nmfQ",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/QHY2pm7uT3k/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3lc6FFxwsEUKprtbtZT8Ug8u6ckaA",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UC4gi504gkSyoXt9vfaGUr9A"),
name: "Farid Bang",
),
],
artist_id: Some("UC4gi504gkSyoXt9vfaGUr9A"),
album: None,
view_count: Some(265000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "Su42LK7I4NM",
title: "R3HAB, Timmy Trumpet, W&W - Poison [Tomorrowland Music]",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/Su42LK7I4NM/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3naagfviBJEDib3cquUWBcc0nYEsA",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/Su42LK7I4NM/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3mf75m2WZU_U9AnZVC5Pry9sDodWA",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCsN8M73DMWa8SPp5o_0IAQQ"),
name: "Tomorrowland",
),
],
artist_id: Some("UCsN8M73DMWa8SPp5o_0IAQQ"),
album: None,
view_count: Some(47000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "mly7ha04bEE",
title: "SAMRA x JOEL BRANDENSTEIN - REGEN (prod. by Lukas Lulou Loules) [Official Video]",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/mly7ha04bEE/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3mZWz0zJVHnKmHxB8bGuGan56w_3g",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/mly7ha04bEE/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3nWJJNvExgCsjQHgohAaNJYaP-Dtg",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCc_iRXEpehN-dDTkPLoBjZg"),
name: "Samra",
),
],
artist_id: Some("UCc_iRXEpehN-dDTkPLoBjZg"),
album: None,
view_count: Some(225000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "c91bmLbGt-g",
title: "Der Berg ruft",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/c91bmLbGt-g/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kh7cuvBMYZNVVh9Qci2GKLdEUJTg",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/c91bmLbGt-g/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3kPm3joE3rQMh_NJfvrWm4c4VaTpA",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCsPz48w0M3QUEGAiDP1x17w"),
name: "Bausa",
),
ArtistId(
id: Some("UCqG3KFVWvCPCtKzEYifV5Ew"),
name: "Maxwell",
),
ArtistId(
id: Some("UC6EFoBjPEanrpj3ua_khs5w"),
name: "Joshi Mizu",
),
],
artist_id: Some("UCsPz48w0M3QUEGAiDP1x17w"),
album: None,
view_count: Some(124000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "IwzkfMmNMpM",
title: "정국 Jung Kook (of BTS) featuring Fahad Al Kubaisi - Dreamers | FIFA World Cup 2022 Soundtrack",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/IwzkfMmNMpM/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kCDSVTXecskVcEeDKgRZQTG8x4Yg",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/IwzkfMmNMpM/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3nrwn3QHyDAiR6IPr9haIxEBUclYg",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCpcTrCXblq78GZrTUTLWeBw"),
name: "FIFA",
),
],
artist_id: Some("UCpcTrCXblq78GZrTUTLWeBw"),
album: None,
view_count: Some(34000000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "_-spkuonX2k",
title: "Blau zu Grau",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/_-spkuonX2k/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kyiowlRzYlkTqpip9Wvvb0XRbHSg",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/_-spkuonX2k/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3mKFbAX7nNg-FHcEpWwN5B23HXE-Q",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCeCsfhcY09c-UvX7DkQDcUw"),
name: "IMMI",
),
ArtistId(
id: Some("UCqjwECZanh3FGxXSEgFNjkw"),
name: "The Cratez",
),
],
artist_id: Some("UCeCsfhcY09c-UvX7DkQDcUw"),
album: None,
view_count: Some(39000),
is_video: true,
track_nr: None,
),
TrackItem(
id: "48pBUciAbRY",
title: "CAPITAL BRA - BALLERLAIKA (prod. by LUCRY & CESA)",
duration: None,
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/48pBUciAbRY/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nNFQ7wq5X4pqoZMPDDsB0xg7BrGA",
width: 400,
height: 225,
),
Thumbnail(
url: "https://i.ytimg.com/vi/48pBUciAbRY/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3kUpn8ZUgkIR6v8n5jcJ6lxx-WJJg",
width: 800,
height: 450,
),
],
artists: [
ArtistId(
id: Some("UCa7FGSUsN2wNRUclibmicMg"),
name: "Capital Bra",
),
],
artist_id: Some("UCa7FGSUsN2wNRUclibmicMg"),
album: None,
view_count: Some(400000),
is_video: true,
track_nr: None,
),
]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2054,6 +2054,34 @@ async fn music_radio_playlist_not_found() {
}
}
#[tokio::test]
async fn music_new_albums() {
let rp = RustyPipe::builder().strict().build();
let albums = rp.query().music_new_albums().await.unwrap();
assert_gte(albums.len(), 10, "albums");
for album in albums {
assert_album_id(&album.id);
assert!(!album.name.is_empty());
assert!(!album.cover.is_empty(), "got no cover");
}
}
#[tokio::test]
async fn music_new_videos() {
let rp = RustyPipe::builder().strict().build();
let videos = rp.query().music_new_videos().await.unwrap();
assert_gte(videos.len(), 10, "videos");
for video in videos {
assert_video_id(&video.id);
assert!(!video.title.is_empty());
assert!(!video.cover.is_empty(), "got no cover");
assert_gte(video.view_count.unwrap(), 1000, "views");
assert!(video.is_video);
}
}
//#AB TESTS
const VISITOR_DATA_SEARCH_CHANNEL_HANDLES: &str = "CgszYlc1Yk1WZGRCSSjrwOSbBg%3D%3D";