refactor: generic search API

This commit is contained in:
ThetaDev 2023-11-18 01:19:47 +01:00
parent 48ccfc5c06
commit 8458d878e7
21 changed files with 2185 additions and 2262 deletions

View file

@ -9,7 +9,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use reqwest::{Client, ClientBuilder}; use reqwest::{Client, ClientBuilder};
use rustypipe::{ use rustypipe::{
client::RustyPipe, client::RustyPipe,
model::{UrlTarget, VideoId}, model::{UrlTarget, VideoId, YouTubeItem},
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter}, param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
}; };
use serde::Serialize; use serde::Serialize;
@ -178,7 +178,7 @@ enum SearchOrder {
Views, Views,
} }
#[derive(Copy, Clone, ValueEnum)] #[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum MusicSearchCategory { enum MusicSearchCategory {
All, All,
Tracks, Tracks,
@ -667,13 +667,17 @@ async fn main() {
.length_opt(length.map(search_filter::Length::from)) .length_opt(length.map(search_filter::Length::from))
.date_opt(date.map(search_filter::UploadDate::from)) .date_opt(date.map(search_filter::UploadDate::from))
.sort_opt(order.map(search_filter::Order::from)); .sort_opt(order.map(search_filter::Order::from));
let mut res = rp.query().search_filter(&query, &filter).await.unwrap(); let mut res = rp
.query()
.search_filter::<YouTubeItem, _>(&query, &filter)
.await
.unwrap();
res.items.extend_limit(rp.query(), limit).await.unwrap(); res.items.extend_limit(rp.query(), limit).await.unwrap();
print_data(&res, format, pretty); print_data(&res, format, pretty);
} }
}, },
Some(MusicSearchCategory::All) => { Some(MusicSearchCategory::All) => {
let res = rp.query().music_search(&query).await.unwrap(); let res = rp.query().music_search_main(&query).await.unwrap();
print_data(&res, format, pretty); print_data(&res, format, pretty);
} }
Some(MusicSearchCategory::Tracks) => { Some(MusicSearchCategory::Tracks) => {
@ -696,19 +700,13 @@ async fn main() {
res.items.extend_limit(rp.query(), limit).await.unwrap(); res.items.extend_limit(rp.query(), limit).await.unwrap();
print_data(&res, format, pretty); print_data(&res, format, pretty);
} }
Some(MusicSearchCategory::PlaylistsYtm) => { Some(MusicSearchCategory::PlaylistsYtm | MusicSearchCategory::PlaylistsCommunity) => {
let mut res = rp let mut res = rp
.query() .query()
.music_search_playlists(&query, false) .music_search_playlists(
.await &query,
.unwrap(); music == Some(MusicSearchCategory::PlaylistsCommunity),
res.items.extend_limit(rp.query(), limit).await.unwrap(); )
print_data(&res, format, pretty);
}
Some(MusicSearchCategory::PlaylistsCommunity) => {
let mut res = rp
.query()
.music_search_playlists(&query, true)
.await .await
.unwrap(); .unwrap();
res.items.extend_limit(rp.query(), limit).await.unwrap(); res.items.extend_limit(rp.query(), limit).await.unwrap();

View file

@ -7,7 +7,7 @@ use num_enum::TryFromPrimitive;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use rustypipe::client::{ClientType, RustyPipe, RustyPipeQuery, YTContext}; use rustypipe::client::{ClientType, RustyPipe, RustyPipeQuery, YTContext};
use rustypipe::model::YouTubeItem; use rustypipe::model::{MusicItem, YouTubeItem};
use rustypipe::param::search_filter::{ItemType, SearchFilter}; use rustypipe::param::search_filter::{ItemType, SearchFilter};
use serde::de::IgnoredAny; use serde::de::IgnoredAny;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -274,14 +274,25 @@ pub async fn playlists_for_shorts(rp: &RustyPipeQuery) -> Result<bool> {
} }
pub async fn track_viewcount(rp: &RustyPipeQuery) -> Result<bool> { pub async fn track_viewcount(rp: &RustyPipeQuery) -> Result<bool> {
let res = rp.music_search("lieblingsmensch namika").await?; let res = rp.music_search_main("lieblingsmensch namika").await?;
let track = &res let track = &res
.tracks .items
.items
.iter() .iter()
.find(|a| a.id == "6485PhOtHzY") .find_map(|itm| {
if let MusicItem::Track(track) = itm {
if track.id == "6485PhOtHzY" {
Some(track)
} else {
None
}
} else {
None
}
})
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!("could not find track, got {:#?}", &res.tracks); panic!("could not find track, got {:#?}", &res.items.items);
}); });
Ok(track.view_count.is_some()) Ok(track.view_count.is_some())

View file

@ -8,6 +8,7 @@ use std::{
use path_macro::path; use path_macro::path;
use rustypipe::{ use rustypipe::{
client::{ClientType, RustyPipe}, client::{ClientType, RustyPipe},
model::YouTubeItem,
param::{ param::{
search_filter::{self, ItemType, SearchFilter}, search_filter::{self, ItemType, SearchFilter},
ChannelVideoTab, Country, ChannelVideoTab, Country,
@ -392,7 +393,10 @@ async fn search() {
} }
let rp = rp_testfile(&json_path); let rp = rp_testfile(&json_path);
rp.query().search("doobydoobap").await.unwrap(); rp.query()
.search::<YouTubeItem, _>("doobydoobap")
.await
.unwrap();
} }
async fn search_cont() { async fn search_cont() {
@ -402,7 +406,11 @@ async fn search_cont() {
} }
let rp = RustyPipe::new(); let rp = RustyPipe::new();
let search = rp.query().search("doobydoobap").await.unwrap(); let search = rp
.query()
.search::<YouTubeItem, _>("doobydoobap")
.await
.unwrap();
let rp = rp_testfile(&json_path); let rp = rp_testfile(&json_path);
search.items.next(rp.query()).await.unwrap().unwrap(); search.items.next(rp.query()).await.unwrap().unwrap();
@ -416,7 +424,7 @@ async fn search_playlists() {
let rp = rp_testfile(&json_path); let rp = rp_testfile(&json_path);
rp.query() rp.query()
.search_filter("pop", &SearchFilter::new().item_type(ItemType::Playlist)) .search_filter::<YouTubeItem, _>("pop", &SearchFilter::new().item_type(ItemType::Playlist))
.await .await
.unwrap(); .unwrap();
} }
@ -429,7 +437,7 @@ async fn search_empty() {
let rp = rp_testfile(&json_path); let rp = rp_testfile(&json_path);
rp.query() rp.query()
.search_filter( .search_filter::<YouTubeItem, _>(
"test", "test",
&SearchFilter::new() &SearchFilter::new()
.feature(search_filter::Feature::IsLive) .feature(search_filter::Feature::IsLive)
@ -558,7 +566,7 @@ async fn music_search() {
} }
let rp = rp_testfile(&json_path); let rp = rp_testfile(&json_path);
rp.query().music_search(query).await.unwrap(); rp.query().music_search_main(query).await.unwrap();
} }
} }

View file

@ -8,9 +8,10 @@ use crate::{
model::{ model::{
paginator::{ContinuationEndpoint, Paginator}, paginator::{ContinuationEndpoint, Paginator},
traits::FromYtItem, traits::FromYtItem,
AlbumItem, ArtistItem, MusicPlaylistItem, MusicSearchFiltered, MusicSearchResult, AlbumItem, ArtistItem, MusicItem, MusicPlaylistItem, MusicSearchResult,
MusicSearchSuggestion, TrackItem, MusicSearchSuggestion, TrackItem,
}, },
param::search_filter::MusicSearchFilter,
serializer::MapResult, serializer::MapResult,
}; };
@ -22,7 +23,7 @@ struct QSearch<'a> {
context: YTContext<'a>, context: YTContext<'a>,
query: &'a str, query: &'a str,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
params: Option<Params>, params: Option<&'a str>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@ -32,76 +33,22 @@ struct QSearchSuggestion<'a> {
input: &'a str, input: &'a str,
} }
#[derive(Debug, Serialize)]
enum Params {
#[serde(rename = "EgWKAQIIAWoMEAMQBBAJEA4QChAF")]
Tracks,
#[serde(rename = "EgWKAQIQAWoMEAMQBBAJEA4QChAF")]
Videos,
#[serde(rename = "EgWKAQIYAWoMEAMQBBAJEA4QChAF")]
Albums,
#[serde(rename = "EgWKAQIgAWoMEAMQBBAJEA4QChAF")]
Artists,
#[serde(rename = "EgeKAQQoADgBagwQAxAEEAkQDhAKEAU%3D")]
YtmPlaylists,
#[serde(rename = "EgeKAQQoAEABagwQAxAEEAkQDhAKEAU%3D")]
CommunityPlaylists,
}
impl RustyPipeQuery { impl RustyPipeQuery {
/// Search YouTube Music. Returns items from any type. /// Search YouTube Music.
#[tracing::instrument(skip(self))] ///
pub async fn music_search<S: AsRef<str> + Debug>( /// This is a generic implementation which casts items to the given type or filters
/// them out.
pub async fn music_search<T: FromYtItem, S: AsRef<str>>(
&self, &self,
query: S, query: S,
) -> Result<MusicSearchResult, Error> { filter: Option<MusicSearchFilter>,
) -> Result<MusicSearchResult<T>, Error> {
let query = query.as_ref(); let query = query.as_ref();
let context = self.get_context(ClientType::DesktopMusic, true, None).await; let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QSearch { let request_body = QSearch {
context, context,
query, query,
params: None, params: filter.map(MusicSearchFilter::params),
};
self.execute_request::<response::MusicSearch, _, _>(
ClientType::DesktopMusic,
"music_search",
query,
"search",
&request_body,
)
.await
}
/// Search YouTube Music tracks
#[tracing::instrument(skip(self))]
pub async fn music_search_tracks<S: AsRef<str> + Debug>(
&self,
query: S,
) -> Result<MusicSearchFiltered<TrackItem>, Error> {
self._music_search_tracks(query, Params::Tracks).await
}
/// Search YouTube Music videos
#[tracing::instrument(skip(self))]
pub async fn music_search_videos<S: AsRef<str> + Debug>(
&self,
query: S,
) -> Result<MusicSearchFiltered<TrackItem>, Error> {
self._music_search_tracks(query, Params::Videos).await
}
async fn _music_search_tracks<S: AsRef<str>>(
&self,
query: S,
params: Params,
) -> Result<MusicSearchFiltered<TrackItem>, Error> {
let query = query.as_ref();
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QSearch {
context,
query,
params: Some(params),
}; };
self.execute_request::<response::MusicSearch, _, _>( self.execute_request::<response::MusicSearch, _, _>(
@ -114,81 +61,66 @@ impl RustyPipeQuery {
.await .await
} }
/// Search YouTube Music albums /// Search YouTube music and return items of all types
#[tracing::instrument(skip(self))] pub async fn music_search_main<S: AsRef<str>>(
pub async fn music_search_albums<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
) -> Result<MusicSearchFiltered<AlbumItem>, Error> { ) -> Result<MusicSearchResult<MusicItem>, Error> {
let query = query.as_ref(); self.music_search(query, None).await
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QSearch {
context,
query,
params: Some(Params::Albums),
};
self.execute_request::<response::MusicSearch, _, _>(
ClientType::DesktopMusic,
"music_search_albums",
query,
"search",
&request_body,
)
.await
} }
/// Search YouTube Music artists /// Search YouTube Music artists
#[tracing::instrument(skip(self))] pub async fn music_search_artists<S: AsRef<str>>(
pub async fn music_search_artists<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
) -> Result<MusicSearchFiltered<ArtistItem>, Error> { ) -> Result<MusicSearchResult<ArtistItem>, Error> {
let query = query.as_ref(); self.music_search(query, Some(MusicSearchFilter::Artists))
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QSearch {
context,
query,
params: Some(Params::Artists),
};
self.execute_request::<response::MusicSearch, _, _>(
ClientType::DesktopMusic,
"music_search_albums",
query,
"search",
&request_body,
)
.await .await
} }
/// Search YouTube Music albums
pub async fn music_search_albums<S: AsRef<str>>(
&self,
query: S,
) -> Result<MusicSearchResult<AlbumItem>, Error> {
self.music_search(query, Some(MusicSearchFilter::Albums))
.await
}
/// Search YouTube Music tracks
pub async fn music_search_tracks<S: AsRef<str>>(
&self,
query: S,
) -> Result<MusicSearchResult<TrackItem>, Error> {
self.music_search(query, Some(MusicSearchFilter::Tracks))
.await
}
/// Search YouTube Music videos
pub async fn music_search_videos<S: AsRef<str>>(
&self,
query: S,
) -> Result<MusicSearchResult<TrackItem>, Error> {
self.music_search(query, Some(MusicSearchFilter::Videos))
.await
}
/// Search YouTube Music playlists /// Search YouTube Music playlists
/// ///
/// Playlists are filtered whether they are created by users /// Playlists are filtered whether they are created by users
/// (`community=true`) or by YouTube Music (`community=false`) /// (`community=true`) or by YouTube Music (`community=false`)
#[tracing::instrument(skip(self))]
pub async fn music_search_playlists<S: AsRef<str> + Debug>( pub async fn music_search_playlists<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
community: bool, community: bool,
) -> Result<MusicSearchFiltered<MusicPlaylistItem>, Error> { ) -> Result<MusicSearchResult<MusicPlaylistItem>, Error> {
let query = query.as_ref(); self.music_search(
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QSearch {
context,
query, query,
params: Some(if community { Some(if community {
Params::CommunityPlaylists MusicSearchFilter::CommunityPlaylists
} else { } else {
Params::YtmPlaylists MusicSearchFilter::YtmPlaylists
}), }),
};
self.execute_request::<response::MusicSearch, _, _>(
ClientType::DesktopMusic,
"music_search_playlists",
query,
"search",
&request_body,
) )
.await .await
} }
@ -217,79 +149,14 @@ impl RustyPipeQuery {
} }
} }
impl MapResponse<MusicSearchResult> for response::MusicSearch { impl<T: FromYtItem> MapResponse<MusicSearchResult<T>> for response::MusicSearch {
fn map_response(
self,
_id: &str,
lang: crate::param::Language,
_deobf: Option<&crate::deobfuscate::DeobfData>,
_vdata: Option<&str>,
) -> Result<MapResult<MusicSearchResult>, crate::error::ExtractionError> {
// dbg!(&self);
let sections = self
.contents
.tabbed_search_results_renderer
.contents
.into_iter()
.next()
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no tab")))?
.tab_renderer
.content
.section_list_renderer
.contents;
let mut corrected_query = None;
let mut order = Vec::new();
let mut mapper = MusicListMapper::new(lang);
sections.into_iter().for_each(|section| match section {
response::music_search::ItemSection::MusicShelfRenderer(shelf) => {
if let Some(etype) = mapper.map_response(shelf.contents) {
if !order.contains(&etype) {
order.push(etype);
}
}
}
response::music_search::ItemSection::MusicCardShelfRenderer(card) => {
if let Some(etype) = mapper.map_card(card) {
if !order.contains(&etype) {
order.push(etype);
}
}
}
response::music_search::ItemSection::ItemSectionRenderer { contents } => {
if let Some(corrected) = contents.into_iter().next() {
corrected_query = Some(corrected.showing_results_for_renderer.corrected_query);
}
}
response::music_search::ItemSection::None => {}
});
let map_res = mapper.group_items();
Ok(MapResult {
c: MusicSearchResult {
tracks: map_res.c.tracks,
albums: map_res.c.albums,
artists: map_res.c.artists,
playlists: map_res.c.playlists,
corrected_query,
order,
},
warnings: map_res.warnings,
})
}
}
impl<T: FromYtItem> MapResponse<MusicSearchFiltered<T>> for response::MusicSearch {
fn map_response( fn map_response(
self, self,
_id: &str, _id: &str,
lang: crate::param::Language, lang: crate::param::Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
vdata: Option<&str>, vdata: Option<&str>,
) -> Result<MapResult<MusicSearchFiltered<T>>, ExtractionError> { ) -> Result<MapResult<MusicSearchResult<T>>, ExtractionError> {
// dbg!(&self); // dbg!(&self);
let tabs = self.contents.tabbed_search_results_renderer.contents; let tabs = self.contents.tabbed_search_results_renderer.contents;
@ -327,7 +194,7 @@ impl<T: FromYtItem> MapResponse<MusicSearchFiltered<T>> for response::MusicSearc
let map_res = mapper.conv_items(); let map_res = mapper.conv_items();
Ok(MapResult { Ok(MapResult {
c: MusicSearchFiltered { c: MusicSearchResult {
items: Paginator::new_ext( items: Paginator::new_ext(
None, None,
map_res.c, map_res.c,
@ -391,7 +258,7 @@ mod tests {
use crate::{ use crate::{
client::{response, MapResponse}, client::{response, MapResponse},
model::{ model::{
AlbumItem, ArtistItem, MusicPlaylistItem, MusicSearchFiltered, MusicSearchResult, AlbumItem, ArtistItem, MusicItem, MusicPlaylistItem, MusicSearchResult,
MusicSearchSuggestion, TrackItem, MusicSearchSuggestion, TrackItem,
}, },
param::Language, param::Language,
@ -410,7 +277,7 @@ mod tests {
let search: response::MusicSearch = let search: response::MusicSearch =
serde_json::from_reader(BufReader::new(json_file)).unwrap(); serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<MusicSearchResult> = let map_res: MapResult<MusicSearchResult<MusicItem>> =
search.map_response("", Language::En, None, None).unwrap(); search.map_response("", Language::En, None, None).unwrap();
assert!( assert!(
@ -433,7 +300,7 @@ mod tests {
let search: response::MusicSearch = let search: response::MusicSearch =
serde_json::from_reader(BufReader::new(json_file)).unwrap(); serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<MusicSearchFiltered<TrackItem>> = let map_res: MapResult<MusicSearchResult<TrackItem>> =
search.map_response("", Language::En, None, None).unwrap(); search.map_response("", Language::En, None, None).unwrap();
assert!( assert!(
@ -452,7 +319,7 @@ mod tests {
let search: response::MusicSearch = let search: response::MusicSearch =
serde_json::from_reader(BufReader::new(json_file)).unwrap(); serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<MusicSearchFiltered<AlbumItem>> = let map_res: MapResult<MusicSearchResult<AlbumItem>> =
search.map_response("", Language::En, None, None).unwrap(); search.map_response("", Language::En, None, None).unwrap();
assert!( assert!(
@ -471,7 +338,7 @@ mod tests {
let search: response::MusicSearch = let search: response::MusicSearch =
serde_json::from_reader(BufReader::new(json_file)).unwrap(); serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<MusicSearchFiltered<ArtistItem>> = let map_res: MapResult<MusicSearchResult<ArtistItem>> =
search.map_response("", Language::En, None, None).unwrap(); search.map_response("", Language::En, None, None).unwrap();
assert!( assert!(
@ -492,7 +359,7 @@ mod tests {
let search: response::MusicSearch = let search: response::MusicSearch =
serde_json::from_reader(BufReader::new(json_file)).unwrap(); serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<MusicSearchFiltered<MusicPlaylistItem>> = let map_res: MapResult<MusicSearchResult<MusicPlaylistItem>> =
search.map_response("", Language::En, None, None).unwrap(); search.map_response("", Language::En, None, None).unwrap();
assert!( assert!(

View file

@ -6,6 +6,7 @@ use crate::{
error::{Error, ExtractionError}, error::{Error, ExtractionError},
model::{ model::{
paginator::{ContinuationEndpoint, Paginator}, paginator::{ContinuationEndpoint, Paginator},
traits::FromYtItem,
SearchResult, YouTubeItem, SearchResult, YouTubeItem,
}, },
param::search_filter::SearchFilter, param::search_filter::SearchFilter,
@ -25,7 +26,10 @@ struct QSearch<'a> {
impl RustyPipeQuery { impl RustyPipeQuery {
/// Search YouTube /// Search YouTube
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn search<S: AsRef<str> + Debug>(&self, query: S) -> Result<SearchResult, Error> { pub async fn search<T: FromYtItem, S: AsRef<str> + Debug>(
&self,
query: S,
) -> Result<SearchResult<T>, Error> {
let query = query.as_ref(); let query = query.as_ref();
let context = self.get_context(ClientType::Desktop, true, None).await; let context = self.get_context(ClientType::Desktop, true, None).await;
let request_body = QSearch { let request_body = QSearch {
@ -46,11 +50,11 @@ impl RustyPipeQuery {
/// Search YouTube using the given [`SearchFilter`] /// Search YouTube using the given [`SearchFilter`]
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub async fn search_filter<S: AsRef<str> + Debug>( pub async fn search_filter<T: FromYtItem, S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
filter: &SearchFilter, filter: &SearchFilter,
) -> Result<SearchResult, Error> { ) -> Result<SearchResult<T>, Error> {
let query = query.as_ref(); let query = query.as_ref();
let context = self.get_context(ClientType::Desktop, true, None).await; let context = self.get_context(ClientType::Desktop, true, None).await;
let request_body = QSearch { let request_body = QSearch {
@ -97,14 +101,14 @@ impl RustyPipeQuery {
} }
} }
impl MapResponse<SearchResult> for response::Search { impl<T: FromYtItem> MapResponse<SearchResult<T>> for response::Search {
fn map_response( fn map_response(
self, self,
_id: &str, _id: &str,
lang: crate::param::Language, lang: crate::param::Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
vdata: Option<&str>, vdata: Option<&str>,
) -> Result<MapResult<SearchResult>, ExtractionError> { ) -> Result<MapResult<SearchResult<T>>, ExtractionError> {
let items = self let items = self
.contents .contents
.two_column_search_results_renderer .two_column_search_results_renderer
@ -119,7 +123,11 @@ impl MapResponse<SearchResult> for response::Search {
c: SearchResult { c: SearchResult {
items: Paginator::new_ext( items: Paginator::new_ext(
self.estimated_results, self.estimated_results,
mapper.items, mapper
.items
.into_iter()
.filter_map(T::from_yt_item)
.collect(),
mapper.ctoken, mapper.ctoken,
None, None,
ContinuationEndpoint::Search, ContinuationEndpoint::Search,
@ -144,7 +152,7 @@ mod tests {
use crate::{ use crate::{
client::{response, MapResponse}, client::{response, MapResponse},
model::SearchResult, model::{SearchResult, YouTubeItem},
param::Language, param::Language,
serializer::MapResult, serializer::MapResult,
util::tests::TESTFILES, util::tests::TESTFILES,
@ -160,7 +168,7 @@ mod tests {
let json_file = File::open(json_path).unwrap(); let json_file = File::open(json_path).unwrap();
let search: response::Search = serde_json::from_reader(BufReader::new(json_file)).unwrap(); let search: response::Search = serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<SearchResult> = let map_res: MapResult<SearchResult<YouTubeItem>> =
search.map_response("", Language::En, None, None).unwrap(); search.map_response("", Language::En, None, None).unwrap();
assert!( assert!(

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: None, count: None,
items: [ items: [

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: None, count: None,
items: [ items: [

View file

@ -3,8 +3,27 @@ source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchResult( MusicSearchResult(
tracks: [ items: Paginator(
TrackItem( count: Some(20),
items: [
Artist(ArtistItem(
id: "UCPC0L1d253x-KuMNwa05TpA",
name: "Taylor Swift",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/U1cI80giSCUuNYx3zkRPt_AWytN1qFMlQoL5F7kTZeFzfIMmfHJYLJchX3BxeDLglE9MeVYp4OlN5Xc=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/U1cI80giSCUuNYx3zkRPt_AWytN1qFMlQoL5F7kTZeFzfIMmfHJYLJchX3BxeDLglE9MeVYp4OlN5Xc=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(51400000),
)),
Track(TrackItem(
id: "3YgtjHZyCIQ", id: "3YgtjHZyCIQ",
name: "Anti-Hero", name: "Anti-Hero",
duration: Some(201), duration: Some(201),
@ -35,8 +54,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "aZ1hziFhj1o", id: "aZ1hziFhj1o",
name: "Love Story", name: "Love Story",
duration: Some(235), duration: Some(235),
@ -67,178 +86,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
),
TrackItem(
id: "VmBoTeLsKfs",
name: "I Knew You Were Trouble.",
duration: Some(220),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: Some(AlbumId(
id: "MPREb_6FbAboRIFSX",
name: "Red",
)), )),
view_count: None, Album(AlbumItem(
is_video: false,
track_nr: None,
by_va: false,
),
TrackItem(
id: "65Q7EdWnjqM",
name: "We Are Never Ever Getting Back Together",
duration: Some(194),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: Some(AlbumId(
id: "MPREb_6FbAboRIFSX",
name: "Red",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
),
TrackItem(
id: "EL72UcDZLSk",
name: "Midnight Rain",
duration: Some(175),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/omCs21jqwK4Ss_VZxPFKwQP5z0UY0vi_8gXu4XNxHKDgE-GHYHWkIw80XR1uzFgdyhM3PvVUZeZ8iAfF=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/omCs21jqwK4Ss_VZxPFKwQP5z0UY0vi_8gXu4XNxHKDgE-GHYHWkIw80XR1uzFgdyhM3PvVUZeZ8iAfF=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: Some(AlbumId(
id: "MPREb_z0ABWl3jaT0",
name: "Midnights",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
),
TrackItem(
id: "b1kbLwvqugk",
name: "Anti-Hero",
duration: Some(310),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/b1kbLwvqugk/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3mTWxIS76F_mpp6z63jfh_wtth9RA",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: None,
view_count: Some(123000000),
is_video: true,
track_nr: None,
by_va: false,
),
TrackItem(
id: "3tmd-ClpJxA",
name: "Look What You Made Me Do",
duration: Some(256),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/3tmd-ClpJxA/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nNkqC6yuXrIsm7St2zBSUzvuji-Q",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: None,
view_count: Some(1300000000),
is_video: true,
track_nr: None,
by_va: false,
),
TrackItem(
id: "FuXNumBwDOM",
name: "ME! (feat. Brendon Urie)",
duration: Some(249),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/FuXNumBwDOM/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3lT7i_HDpIpUM2j9RoGdVL7NBDh4A",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: None,
view_count: Some(402000000),
is_video: true,
track_nr: None,
by_va: false,
),
],
albums: [
AlbumItem(
id: "MPREb_l2IU1O3l6QK", id: "MPREb_l2IU1O3l6QK",
name: "Lavender Haze (Acoustic Version)", name: "Lavender Haze (Acoustic Version)",
cover: [ cover: [
@ -273,8 +122,176 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2023), year: Some(2023),
by_va: false, by_va: false,
)),
Track(TrackItem(
id: "VmBoTeLsKfs",
name: "I Knew You Were Trouble.",
duration: Some(220),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w60-h60-l90-rj",
width: 60,
height: 60,
), ),
AlbumItem( Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: Some(AlbumId(
id: "MPREb_6FbAboRIFSX",
name: "Red",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "65Q7EdWnjqM",
name: "We Are Never Ever Getting Back Together",
duration: Some(194),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/_HJASc9B_QXn2yoUBn4ZLkn6ka8o_CYX4hNT3HQSeIRcqStiJU15VVtOwlaoFV4KqhJK3d-0W0sw3yQ=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: Some(AlbumId(
id: "MPREb_6FbAboRIFSX",
name: "Red",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "EL72UcDZLSk",
name: "Midnight Rain",
duration: Some(175),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/omCs21jqwK4Ss_VZxPFKwQP5z0UY0vi_8gXu4XNxHKDgE-GHYHWkIw80XR1uzFgdyhM3PvVUZeZ8iAfF=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/omCs21jqwK4Ss_VZxPFKwQP5z0UY0vi_8gXu4XNxHKDgE-GHYHWkIw80XR1uzFgdyhM3PvVUZeZ8iAfF=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: Some(AlbumId(
id: "MPREb_z0ABWl3jaT0",
name: "Midnights",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "b1kbLwvqugk",
name: "Anti-Hero",
duration: Some(310),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/b1kbLwvqugk/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3mTWxIS76F_mpp6z63jfh_wtth9RA",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: None,
view_count: Some(123000000),
is_video: true,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "3tmd-ClpJxA",
name: "Look What You Made Me Do",
duration: Some(256),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/3tmd-ClpJxA/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nNkqC6yuXrIsm7St2zBSUzvuji-Q",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: None,
view_count: Some(1300000000),
is_video: true,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "FuXNumBwDOM",
name: "ME! (feat. Brendon Urie)",
duration: Some(249),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/FuXNumBwDOM/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3lT7i_HDpIpUM2j9RoGdVL7NBDh4A",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCPC0L1d253x-KuMNwa05TpA"),
name: "Taylor Swift",
),
],
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
album: None,
view_count: Some(402000000),
is_video: true,
track_nr: None,
by_va: false,
)),
Album(AlbumItem(
id: "MPREb_BiCQyyofUtj", id: "MPREb_BiCQyyofUtj",
name: "Eyes Open (Taylor\'s Version)", name: "Eyes Open (Taylor\'s Version)",
cover: [ cover: [
@ -309,8 +326,8 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2021), year: Some(2021),
by_va: false, by_va: false,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_aTnRAnnwbvO", id: "MPREb_aTnRAnnwbvO",
name: "The More Lover Chapter", name: "The More Lover Chapter",
cover: [ cover: [
@ -345,8 +362,8 @@ MusicSearchResult(
album_type: Ep, album_type: Ep,
year: Some(2019), year: Some(2019),
by_va: false, by_va: false,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_UGeXP8tVTb5", id: "MPREb_UGeXP8tVTb5",
name: "All Of The Girls You Loved Before", name: "All Of The Girls You Loved Before",
cover: [ cover: [
@ -381,80 +398,8 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2019), year: Some(2019),
by_va: false, by_va: false,
), )),
], Playlist(MusicPlaylistItem(
artists: [
ArtistItem(
id: "UCPC0L1d253x-KuMNwa05TpA",
name: "Taylor Swift",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/U1cI80giSCUuNYx3zkRPt_AWytN1qFMlQoL5F7kTZeFzfIMmfHJYLJchX3BxeDLglE9MeVYp4OlN5Xc=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/U1cI80giSCUuNYx3zkRPt_AWytN1qFMlQoL5F7kTZeFzfIMmfHJYLJchX3BxeDLglE9MeVYp4OlN5Xc=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(51400000),
),
ArtistItem(
id: "UCprAFmT0C6O4X0ToEXpeFTQ",
name: "Kendrick Lamar",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/hMjzHmIuTV0XPlvRSjl3wMR6NP-uF-fqF6kkandkFX-hEVp6d3tw-FQG9_smAq0tFwNBT6QLQR-Hkwge=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/hMjzHmIuTV0XPlvRSjl3wMR6NP-uF-fqF6kkandkFX-hEVp6d3tw-FQG9_smAq0tFwNBT6QLQR-Hkwge=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(11900000),
),
ArtistItem(
id: "UCYR9erHSNBPjjNswR4FrMaw",
name: "ZAYN",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/x_OLwEjNh74QScfVbc3ejrbxjjG3WFe5CfVrO9KlIT_W2VvHpVC-orFzg-LF2kVYsBGK2YOA5JvUsVE=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/x_OLwEjNh74QScfVbc3ejrbxjjG3WFe5CfVrO9KlIT_W2VvHpVC-orFzg-LF2kVYsBGK2YOA5JvUsVE=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(15300000),
),
ArtistItem(
id: "UCH9AZnGOJebPl3RiVGgpkKA",
name: "I Prevail",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/BsYJ-FyJ4DuXe3yLZ_C3gZREpGAOJ4g1s779ZOF5XqccwJ0ahXfiFujd8dIBkaPjsC6RhW1A6iNgVw=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/BsYJ-FyJ4DuXe3yLZ_C3gZREpGAOJ4g1s779ZOF5XqccwJ0ahXfiFujd8dIBkaPjsC6RhW1A6iNgVw=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(847000),
),
],
playlists: [
MusicPlaylistItem(
id: "RDCLAK5uy_k1272v-yXtLJm7gmMiAxjOl-vh5aEC11A", id: "RDCLAK5uy_k1272v-yXtLJm7gmMiAxjOl-vh5aEC11A",
name: "Presenting Taylor Swift", name: "Presenting Taylor Swift",
thumbnail: [ thumbnail: [
@ -482,8 +427,8 @@ MusicSearchResult(
channel: None, channel: None,
track_count: Some(72), track_count: Some(72),
from_ytm: true, from_ytm: true,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PLMtpqbF01MkVhY0_Z4Afg1m3xCKa2kNBf", id: "PLMtpqbF01MkVhY0_Z4Afg1m3xCKa2kNBf",
name: "All Taylor Swift Songs", name: "All Taylor Swift Songs",
thumbnail: [ thumbnail: [
@ -509,8 +454,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PLsK_PMdOWOamZphWwqin4A1vw9IJQ0bxC", id: "PLsK_PMdOWOamZphWwqin4A1vw9IJQ0bxC",
name: "Taylor Swift The Eras Tour Setlist (Lyrics and Music Videos)", name: "Taylor Swift The Eras Tour Setlist (Lyrics and Music Videos)",
thumbnail: [ thumbnail: [
@ -536,8 +481,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PL_MyZJz846m0QstXAZMmDUJAEHw6je6-_", id: "PL_MyZJz846m0QstXAZMmDUJAEHw6je6-_",
name: "Taylor Swift - Top Tracks 2022 Playlist", name: "Taylor Swift - Top Tracks 2022 Playlist",
thumbnail: [ thumbnail: [
@ -563,13 +508,61 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
)),
Artist(ArtistItem(
id: "UCprAFmT0C6O4X0ToEXpeFTQ",
name: "Kendrick Lamar",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/hMjzHmIuTV0XPlvRSjl3wMR6NP-uF-fqF6kkandkFX-hEVp6d3tw-FQG9_smAq0tFwNBT6QLQR-Hkwge=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/hMjzHmIuTV0XPlvRSjl3wMR6NP-uF-fqF6kkandkFX-hEVp6d3tw-FQG9_smAq0tFwNBT6QLQR-Hkwge=w120-h120-p-l90-rj",
width: 120,
height: 120,
), ),
], ],
corrected_query: None, subscriber_count: Some(11900000),
order: [ )),
Artist, Artist(ArtistItem(
Track, id: "UCYR9erHSNBPjjNswR4FrMaw",
Album, name: "ZAYN",
Playlist, avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/x_OLwEjNh74QScfVbc3ejrbxjjG3WFe5CfVrO9KlIT_W2VvHpVC-orFzg-LF2kVYsBGK2YOA5JvUsVE=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/x_OLwEjNh74QScfVbc3ejrbxjjG3WFe5CfVrO9KlIT_W2VvHpVC-orFzg-LF2kVYsBGK2YOA5JvUsVE=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
], ],
subscriber_count: Some(15300000),
)),
Artist(ArtistItem(
id: "UCH9AZnGOJebPl3RiVGgpkKA",
name: "I Prevail",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/BsYJ-FyJ4DuXe3yLZ_C3gZREpGAOJ4g1s779ZOF5XqccwJ0ahXfiFujd8dIBkaPjsC6RhW1A6iNgVw=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/BsYJ-FyJ4DuXe3yLZ_C3gZREpGAOJ4g1s779ZOF5XqccwJ0ahXfiFujd8dIBkaPjsC6RhW1A6iNgVw=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(847000),
)),
],
ctoken: None,
endpoint: music_search,
),
corrected_query: None,
) )

View file

@ -3,8 +3,10 @@ source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchResult( MusicSearchResult(
tracks: [ items: Paginator(
TrackItem( count: Some(16),
items: [
Track(TrackItem(
id: "ZeerrnuLi5E", id: "ZeerrnuLi5E",
name: "Black Mamba", name: "Black Mamba",
duration: Some(230), duration: Some(230),
@ -27,8 +29,8 @@ MusicSearchResult(
is_video: true, is_video: true,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "BL-aIpCLWnU", id: "BL-aIpCLWnU",
name: "Black Mamba", name: "Black Mamba",
duration: Some(175), duration: Some(175),
@ -59,8 +61,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "cATe8Toht70", id: "cATe8Toht70",
name: "Black Mamba", name: "Black Mamba",
duration: Some(74), duration: Some(74),
@ -95,8 +97,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "WwNKyoizf8k", id: "WwNKyoizf8k",
name: "BLACK MAMBA", name: "BLACK MAMBA",
duration: Some(182), duration: Some(182),
@ -127,8 +129,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "yQUU29NwNF4", id: "yQUU29NwNF4",
name: "aespa(에스파) - Black Mamba @인기가요 inkigayo 20201122", name: "aespa(에스파) - Black Mamba @인기가요 inkigayo 20201122",
duration: Some(213), duration: Some(213),
@ -151,8 +153,8 @@ MusicSearchResult(
is_video: true, is_video: true,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "Ky5RT5oGg0w", id: "Ky5RT5oGg0w",
name: "Black Mamba", name: "Black Mamba",
duration: Some(287), duration: Some(287),
@ -175,8 +177,8 @@ MusicSearchResult(
is_video: true, is_video: true,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "dz9bieeSVRw", id: "dz9bieeSVRw",
name: "aespa - Black Mamba (Music Bank) | KBS WORLD TV 201127", name: "aespa - Black Mamba (Music Bank) | KBS WORLD TV 201127",
duration: Some(192), duration: Some(192),
@ -199,10 +201,8 @@ MusicSearchResult(
is_video: true, is_video: true,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
], Album(AlbumItem(
albums: [
AlbumItem(
id: "MPREb_OpHWHwyNOuY", id: "MPREb_OpHWHwyNOuY",
name: "Black Mamba", name: "Black Mamba",
cover: [ cover: [
@ -237,8 +237,8 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2020), year: Some(2020),
by_va: false, by_va: false,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_pvdHyqvGjbI", id: "MPREb_pvdHyqvGjbI",
name: "Girls - The 2nd Mini Album", name: "Girls - The 2nd Mini Album",
cover: [ cover: [
@ -273,8 +273,8 @@ MusicSearchResult(
album_type: Album, album_type: Album,
year: Some(2022), year: Some(2022),
by_va: false, by_va: false,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_CznUTKnATw6", id: "MPREb_CznUTKnATw6",
name: "Black Mamba (feat. Foolio)", name: "Black Mamba (feat. Foolio)",
cover: [ cover: [
@ -309,10 +309,8 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2020), year: Some(2020),
by_va: false, by_va: false,
), )),
], Artist(ArtistItem(
artists: [
ArtistItem(
id: "UCEdZAdnnKqbaHOlv8nM6OtA", id: "UCEdZAdnnKqbaHOlv8nM6OtA",
name: "aespa", name: "aespa",
avatar: [ avatar: [
@ -328,8 +326,8 @@ MusicSearchResult(
), ),
], ],
subscriber_count: Some(4120000), subscriber_count: Some(4120000),
), )),
ArtistItem( Artist(ArtistItem(
id: "UCaDT20-B3U8h-tPg_VMvntw", id: "UCaDT20-B3U8h-tPg_VMvntw",
name: "The Black Mamba", name: "The Black Mamba",
avatar: [ avatar: [
@ -345,8 +343,8 @@ MusicSearchResult(
), ),
], ],
subscriber_count: Some(2640), subscriber_count: Some(2640),
), )),
ArtistItem( Artist(ArtistItem(
id: "UCLcwLJIGBDDvbfq8JERV6Ag", id: "UCLcwLJIGBDDvbfq8JERV6Ag",
name: "Black Mamba", name: "Black Mamba",
avatar: [ avatar: [
@ -362,10 +360,8 @@ MusicSearchResult(
), ),
], ],
subscriber_count: Some(9), subscriber_count: Some(9),
), )),
], Playlist(MusicPlaylistItem(
playlists: [
MusicPlaylistItem(
id: "PLk76iSbFqNJsu_Gozn9SkEXxQ7t-bpXid", id: "PLk76iSbFqNJsu_Gozn9SkEXxQ7t-bpXid",
name: "IRMA MIRTILLA Black Mamba", name: "IRMA MIRTILLA Black Mamba",
thumbnail: [ thumbnail: [
@ -391,8 +387,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PLIL9Q2jz6euDEJZKHd4QaG4iic944_vKY", id: "PLIL9Q2jz6euDEJZKHd4QaG4iic944_vKY",
name: "Black Mamba", name: "Black Mamba",
thumbnail: [ thumbnail: [
@ -418,8 +414,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PLinm7-cvTdN7RqadpfNrncUGqkdyKNpn6", id: "PLinm7-cvTdN7RqadpfNrncUGqkdyKNpn6",
name: "Black Mamba", name: "Black Mamba",
thumbnail: [ thumbnail: [
@ -445,13 +441,10 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
)),
],
ctoken: None,
endpoint: music_search,
), ),
],
corrected_query: None, corrected_query: None,
order: [
Track,
Album,
Artist,
Playlist,
],
) )

View file

@ -3,8 +3,10 @@ source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchResult( MusicSearchResult(
tracks: [ items: Paginator(
TrackItem( count: Some(18),
items: [
Track(TrackItem(
id: "ITdJEc_81h4", id: "ITdJEc_81h4",
name: "Pop (Radio Version)", name: "Pop (Radio Version)",
duration: Some(176), duration: Some(176),
@ -35,8 +37,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "VHLPvrlclmQ", id: "VHLPvrlclmQ",
name: "Pop im Radio", name: "Pop im Radio",
duration: Some(224), duration: Some(224),
@ -67,8 +69,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "R9TPed_ohKM", id: "R9TPed_ohKM",
name: "POP!", name: "POP!",
duration: Some(169), duration: Some(169),
@ -99,8 +101,8 @@ MusicSearchResult(
is_video: false, is_video: false,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "Ej1nxBxFSKc", id: "Ej1nxBxFSKc",
name: "Non-Stop-Pop FM (Hosted by Cara Delevingne) [Grand Theft Auto V] | Pop, R&B, Dance-pop Music Mix", name: "Non-Stop-Pop FM (Hosted by Cara Delevingne) [Grand Theft Auto V] | Pop, R&B, Dance-pop Music Mix",
duration: Some(8752), duration: Some(8752),
@ -123,8 +125,8 @@ MusicSearchResult(
is_video: true, is_video: true,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "26OrUhkRa3c", id: "26OrUhkRa3c",
name: "Top Hits 2020 Video Mix (CLEAN) | Hip Hop 2020 - (POP HITS 2020, TOP 40 HITS, BEST POP HITS,TOP 40)", name: "Top Hits 2020 Video Mix (CLEAN) | Hip Hop 2020 - (POP HITS 2020, TOP 40 HITS, BEST POP HITS,TOP 40)",
duration: Some(10012), duration: Some(10012),
@ -147,8 +149,8 @@ MusicSearchResult(
is_video: true, is_video: true,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
TrackItem( Track(TrackItem(
id: "Idk-oFqn3kM", id: "Idk-oFqn3kM",
name: "THE BEST CHARTS POP HITS 2021 I THE BEST MUSIC RADIO CHARTS I", name: "THE BEST CHARTS POP HITS 2021 I THE BEST MUSIC RADIO CHARTS I",
duration: Some(8795), duration: Some(8795),
@ -171,10 +173,8 @@ MusicSearchResult(
is_video: true, is_video: true,
track_nr: None, track_nr: None,
by_va: false, by_va: false,
), )),
], Album(AlbumItem(
albums: [
AlbumItem(
id: "MPREb_CYbQPbuAWrt", id: "MPREb_CYbQPbuAWrt",
name: "Pop Radio", name: "Pop Radio",
cover: [ cover: [
@ -204,8 +204,8 @@ MusicSearchResult(
album_type: Album, album_type: Album,
year: Some(2016), year: Some(2016),
by_va: true, by_va: true,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_Cmf1lWfv0dV", id: "MPREb_Cmf1lWfv0dV",
name: "Pop Radio", name: "Pop Radio",
cover: [ cover: [
@ -235,8 +235,8 @@ MusicSearchResult(
album_type: Album, album_type: Album,
year: Some(2022), year: Some(2022),
by_va: true, by_va: true,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_Ic1ZUsaeuRv", id: "MPREb_Ic1ZUsaeuRv",
name: "Pop Radio", name: "Pop Radio",
cover: [ cover: [
@ -271,63 +271,8 @@ MusicSearchResult(
album_type: Album, album_type: Album,
year: Some(2002), year: Some(2002),
by_va: false, by_va: false,
), )),
], Playlist(MusicPlaylistItem(
artists: [
ArtistItem(
id: "UCSZJrhZ2_ILCpyk3Z3AZVTA",
name: "Icona Pop",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/sw3ilLK-equKu_EtKG1ehnbNqbmo55ZqS_LjOlu4SuYRQrGyWoxIMF9OSw4ORpVtgYlKoeJGD4thG7k=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/sw3ilLK-equKu_EtKG1ehnbNqbmo55ZqS_LjOlu4SuYRQrGyWoxIMF9OSw4ORpVtgYlKoeJGD4thG7k=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(713000),
),
ArtistItem(
id: "UCOk0CLydqB-B0UH7UaZrVqw",
name: "Bacilos",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/HtQH-8U0IvzGUjkEzOZjpLEBdqcEBaSRAmpneHhtXbiZHL1rJsoq8iJFwcCSMY7PlM-UuzVGDkoJn6k=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/HtQH-8U0IvzGUjkEzOZjpLEBdqcEBaSRAmpneHhtXbiZHL1rJsoq8iJFwcCSMY7PlM-UuzVGDkoJn6k=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(339000),
),
ArtistItem(
id: "UC-Unifbw_ADqgIeMq4AdvvA",
name: "Death Pop Radio",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/P8DRNrrBP_x4GmPXXKJkWKroLuMCpGW4DJTgxFPYFI-MlLk3pI6xOYMpMnzyb49md-8VVn9L3RHNTfMq=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/P8DRNrrBP_x4GmPXXKJkWKroLuMCpGW4DJTgxFPYFI-MlLk3pI6xOYMpMnzyb49md-8VVn9L3RHNTfMq=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(11),
),
],
playlists: [
MusicPlaylistItem(
id: "RDCLAK5uy_l8kJfTElp2zFMop7IboOXetbbKU3a9VeQ", id: "RDCLAK5uy_l8kJfTElp2zFMop7IboOXetbbKU3a9VeQ",
name: "REST Turkish Rap", name: "REST Turkish Rap",
thumbnail: [ thumbnail: [
@ -355,8 +300,8 @@ MusicSearchResult(
channel: None, channel: None,
track_count: Some(50), track_count: Some(50),
from_ytm: true, from_ytm: true,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "RDCLAK5uy_kLB769E3eFSzgy4fbpu6-1YPLh90b0JAY", id: "RDCLAK5uy_kLB769E3eFSzgy4fbpu6-1YPLh90b0JAY",
name: "Pop Hotlist", name: "Pop Hotlist",
thumbnail: [ thumbnail: [
@ -384,8 +329,8 @@ MusicSearchResult(
channel: None, channel: None,
track_count: Some(54), track_count: Some(54),
from_ytm: true, from_ytm: true,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "RDCLAK5uy_mCvOm3kQy1RTBwDOGYkNhtHwMO89ffquk", id: "RDCLAK5uy_mCvOm3kQy1RTBwDOGYkNhtHwMO89ffquk",
name: "Crème French Pop", name: "Crème French Pop",
thumbnail: [ thumbnail: [
@ -413,8 +358,8 @@ MusicSearchResult(
channel: None, channel: None,
track_count: Some(50), track_count: Some(50),
from_ytm: true, from_ytm: true,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PL47aILYuQXEKiHdqMfNCHat1Gck3XQrrK", id: "PL47aILYuQXEKiHdqMfNCHat1Gck3XQrrK",
name: "Today\'s Pop Hits Playlist 2022 ♫ Best Radio Hits 2022", name: "Today\'s Pop Hits Playlist 2022 ♫ Best Radio Hits 2022",
thumbnail: [ thumbnail: [
@ -440,8 +385,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PL5ITQ2Yq_HLpidRR3wAio-YRBnG7-FeLd", id: "PL5ITQ2Yq_HLpidRR3wAio-YRBnG7-FeLd",
name: "Radio Swiss Pop", name: "Radio Swiss Pop",
thumbnail: [ thumbnail: [
@ -467,8 +412,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PLX6L4t7t6ZanfCJ1wBxRdGZ_mk9ygmKqo", id: "PLX6L4t7t6ZanfCJ1wBxRdGZ_mk9ygmKqo",
name: "Deutsch Pop Hits NEU 2022", name: "Deutsch Pop Hits NEU 2022",
thumbnail: [ thumbnail: [
@ -494,13 +439,61 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
)),
Artist(ArtistItem(
id: "UCSZJrhZ2_ILCpyk3Z3AZVTA",
name: "Icona Pop",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/sw3ilLK-equKu_EtKG1ehnbNqbmo55ZqS_LjOlu4SuYRQrGyWoxIMF9OSw4ORpVtgYlKoeJGD4thG7k=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/sw3ilLK-equKu_EtKG1ehnbNqbmo55ZqS_LjOlu4SuYRQrGyWoxIMF9OSw4ORpVtgYlKoeJGD4thG7k=w120-h120-p-l90-rj",
width: 120,
height: 120,
), ),
], ],
corrected_query: None, subscriber_count: Some(713000),
order: [ )),
Track, Artist(ArtistItem(
Album, id: "UCOk0CLydqB-B0UH7UaZrVqw",
Playlist, name: "Bacilos",
Artist, avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/HtQH-8U0IvzGUjkEzOZjpLEBdqcEBaSRAmpneHhtXbiZHL1rJsoq8iJFwcCSMY7PlM-UuzVGDkoJn6k=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/HtQH-8U0IvzGUjkEzOZjpLEBdqcEBaSRAmpneHhtXbiZHL1rJsoq8iJFwcCSMY7PlM-UuzVGDkoJn6k=w120-h120-l90-rj",
width: 120,
height: 120,
),
], ],
subscriber_count: Some(339000),
)),
Artist(ArtistItem(
id: "UC-Unifbw_ADqgIeMq4AdvvA",
name: "Death Pop Radio",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/P8DRNrrBP_x4GmPXXKJkWKroLuMCpGW4DJTgxFPYFI-MlLk3pI6xOYMpMnzyb49md-8VVn9L3RHNTfMq=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/P8DRNrrBP_x4GmPXXKJkWKroLuMCpGW4DJTgxFPYFI-MlLk3pI6xOYMpMnzyb49md-8VVn9L3RHNTfMq=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(11),
)),
],
ctoken: None,
endpoint: music_search,
),
corrected_query: None,
) )

View file

@ -3,178 +3,10 @@ source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchResult( MusicSearchResult(
tracks: [ items: Paginator(
TrackItem( count: Some(16),
id: "6485PhOtHzY", items: [
name: "Lieblingsmensch", Album(AlbumItem(
duration: Some(191),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/dwrJ5NnlZU7CBziLRlTm1uizuolakRAX7g34-eKeqEZQGZgwmvhqcs3TiZClfm7v6a-KYHieitdakpPo=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/dwrJ5NnlZU7CBziLRlTm1uizuolakRAX7g34-eKeqEZQGZgwmvhqcs3TiZClfm7v6a-KYHieitdakpPo=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
name: "Namika",
),
],
artist_id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
album: Some(AlbumId(
id: "MPREb_RXHxrUFfrvQ",
name: "Lieblingsmensch",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
),
TrackItem(
id: "pt0YvfnhGgI",
name: "Lieblingsmensch",
duration: Some(524),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/38mrm1ntm4MQfmB4Pl2EdY7o6McbLq06sC7qqLxDMqOfL-eqySheDnfl3IOpZYIE_ozt6Bywlmjj2DCe=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/38mrm1ntm4MQfmB4Pl2EdY7o6McbLq06sC7qqLxDMqOfL-eqySheDnfl3IOpZYIE_ozt6Bywlmjj2DCe=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCCpID8TTjkkjLCwBybAfHSg"),
name: "Boris Brejcha",
),
],
artist_id: Some("UCCpID8TTjkkjLCwBybAfHSg"),
album: Some(AlbumId(
id: "MPREb_VFqQlfPhsFW",
name: "Lieblingsmensch",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
),
TrackItem(
id: "0yPnvetCm-U",
name: "Lieblingsmensch",
duration: Some(174),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/RORTouE503-ZZqgAov66r2cK-YyTJbZIwbM1Hz58ja7jNgWSG_xjTLxK41nwAT8ejRvY7U35dMm4OOYhYg=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/RORTouE503-ZZqgAov66r2cK-YyTJbZIwbM1Hz58ja7jNgWSG_xjTLxK41nwAT8ejRvY7U35dMm4OOYhYg=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCvfUKCnUBfsZAVHgF-pYmJg"),
name: "Voyce",
),
],
artist_id: Some("UCvfUKCnUBfsZAVHgF-pYmJg"),
album: Some(AlbumId(
id: "MPREb_SpT32xAd4YR",
name: "Gegenstück EP",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
),
TrackItem(
id: "3ryohiCVq3M",
name: "Namika - Lieblingsmensch (Official Video)",
duration: Some(191),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/3ryohiCVq3M/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nsa7wZ13z3ie49RzcBHKQAmjlU9w",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
name: "Namika",
),
],
artist_id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
album: None,
view_count: Some(108000000),
is_video: true,
track_nr: None,
by_va: false,
),
TrackItem(
id: "6Bt1KeMNqvc",
name: "Lieblingsmensch Namika Lyrics",
duration: Some(188),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/6Bt1KeMNqvc/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3n9IkVmxE5YqesHGPS0xtJemxrNzQ",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCgoJMRKimbxB374QjHgE6kA"),
name: "jessika adam",
),
],
artist_id: Some("UCgoJMRKimbxB374QjHgE6kA"),
album: None,
view_count: Some(10000000),
is_video: true,
track_nr: None,
by_va: false,
),
TrackItem(
id: "5YQRHUItXTI",
name: "Namika - Lieblingsmensch 1 Hour Version",
duration: Some(3801),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/5YQRHUItXTI/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kP0NYgZ5YbsGdULVIXOu08eoHGrw",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCiQjRO2m3dBBlg7sqTaFA_A"),
name: "ZockerAlarm",
),
],
artist_id: Some("UCiQjRO2m3dBBlg7sqTaFA_A"),
album: None,
view_count: Some(56000),
is_video: true,
track_nr: None,
by_va: false,
),
],
albums: [
AlbumItem(
id: "MPREb_RXHxrUFfrvQ", id: "MPREb_RXHxrUFfrvQ",
name: "Lieblingsmensch", name: "Lieblingsmensch",
cover: [ cover: [
@ -209,8 +41,176 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2015), year: Some(2015),
by_va: false, by_va: false,
)),
Track(TrackItem(
id: "6485PhOtHzY",
name: "Lieblingsmensch",
duration: Some(191),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/dwrJ5NnlZU7CBziLRlTm1uizuolakRAX7g34-eKeqEZQGZgwmvhqcs3TiZClfm7v6a-KYHieitdakpPo=w60-h60-l90-rj",
width: 60,
height: 60,
), ),
AlbumItem( Thumbnail(
url: "https://lh3.googleusercontent.com/dwrJ5NnlZU7CBziLRlTm1uizuolakRAX7g34-eKeqEZQGZgwmvhqcs3TiZClfm7v6a-KYHieitdakpPo=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
name: "Namika",
),
],
artist_id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
album: Some(AlbumId(
id: "MPREb_RXHxrUFfrvQ",
name: "Lieblingsmensch",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "pt0YvfnhGgI",
name: "Lieblingsmensch",
duration: Some(524),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/38mrm1ntm4MQfmB4Pl2EdY7o6McbLq06sC7qqLxDMqOfL-eqySheDnfl3IOpZYIE_ozt6Bywlmjj2DCe=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/38mrm1ntm4MQfmB4Pl2EdY7o6McbLq06sC7qqLxDMqOfL-eqySheDnfl3IOpZYIE_ozt6Bywlmjj2DCe=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCCpID8TTjkkjLCwBybAfHSg"),
name: "Boris Brejcha",
),
],
artist_id: Some("UCCpID8TTjkkjLCwBybAfHSg"),
album: Some(AlbumId(
id: "MPREb_VFqQlfPhsFW",
name: "Lieblingsmensch",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "0yPnvetCm-U",
name: "Lieblingsmensch",
duration: Some(174),
cover: [
Thumbnail(
url: "https://lh3.googleusercontent.com/RORTouE503-ZZqgAov66r2cK-YyTJbZIwbM1Hz58ja7jNgWSG_xjTLxK41nwAT8ejRvY7U35dMm4OOYhYg=w60-h60-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/RORTouE503-ZZqgAov66r2cK-YyTJbZIwbM1Hz58ja7jNgWSG_xjTLxK41nwAT8ejRvY7U35dMm4OOYhYg=w120-h120-l90-rj",
width: 120,
height: 120,
),
],
artists: [
ArtistId(
id: Some("UCvfUKCnUBfsZAVHgF-pYmJg"),
name: "Voyce",
),
],
artist_id: Some("UCvfUKCnUBfsZAVHgF-pYmJg"),
album: Some(AlbumId(
id: "MPREb_SpT32xAd4YR",
name: "Gegenstück EP",
)),
view_count: None,
is_video: false,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "3ryohiCVq3M",
name: "Namika - Lieblingsmensch (Official Video)",
duration: Some(191),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/3ryohiCVq3M/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nsa7wZ13z3ie49RzcBHKQAmjlU9w",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
name: "Namika",
),
],
artist_id: Some("UCIh4j8fXWf2U0ro0qnGU8Mg"),
album: None,
view_count: Some(108000000),
is_video: true,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "6Bt1KeMNqvc",
name: "Lieblingsmensch Namika Lyrics",
duration: Some(188),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/6Bt1KeMNqvc/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3n9IkVmxE5YqesHGPS0xtJemxrNzQ",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCgoJMRKimbxB374QjHgE6kA"),
name: "jessika adam",
),
],
artist_id: Some("UCgoJMRKimbxB374QjHgE6kA"),
album: None,
view_count: Some(10000000),
is_video: true,
track_nr: None,
by_va: false,
)),
Track(TrackItem(
id: "5YQRHUItXTI",
name: "Namika - Lieblingsmensch 1 Hour Version",
duration: Some(3801),
cover: [
Thumbnail(
url: "https://i.ytimg.com/vi/5YQRHUItXTI/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kP0NYgZ5YbsGdULVIXOu08eoHGrw",
width: 400,
height: 225,
),
],
artists: [
ArtistId(
id: Some("UCiQjRO2m3dBBlg7sqTaFA_A"),
name: "ZockerAlarm",
),
],
artist_id: Some("UCiQjRO2m3dBBlg7sqTaFA_A"),
album: None,
view_count: Some(56000),
is_video: true,
track_nr: None,
by_va: false,
)),
Album(AlbumItem(
id: "MPREb_V5f8YfHKp2j", id: "MPREb_V5f8YfHKp2j",
name: "Lieblingsmensch", name: "Lieblingsmensch",
cover: [ cover: [
@ -245,8 +245,8 @@ MusicSearchResult(
album_type: Ep, album_type: Ep,
year: Some(2015), year: Some(2015),
by_va: false, by_va: false,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_AlIjxpnBKtn", id: "MPREb_AlIjxpnBKtn",
name: "Lieblingsmensch (Edit)", name: "Lieblingsmensch (Edit)",
cover: [ cover: [
@ -281,8 +281,8 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2019), year: Some(2019),
by_va: false, by_va: false,
), )),
AlbumItem( Album(AlbumItem(
id: "MPREb_VFqQlfPhsFW", id: "MPREb_VFqQlfPhsFW",
name: "Lieblingsmensch", name: "Lieblingsmensch",
cover: [ cover: [
@ -317,63 +317,8 @@ MusicSearchResult(
album_type: Single, album_type: Single,
year: Some(2019), year: Some(2019),
by_va: false, by_va: false,
), )),
], Playlist(MusicPlaylistItem(
artists: [
ArtistItem(
id: "UCIh4j8fXWf2U0ro0qnGU8Mg",
name: "Namika",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/iY5H0k9sMP6hevj7ttwx2WibgxmJ9OMoK9TuVHwUMvdA8ZrrJCdGYT_BG-HhgYcVDihVJMQqSKbOcpk=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/iY5H0k9sMP6hevj7ttwx2WibgxmJ9OMoK9TuVHwUMvdA8ZrrJCdGYT_BG-HhgYcVDihVJMQqSKbOcpk=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(737000),
),
ArtistItem(
id: "UCCpID8TTjkkjLCwBybAfHSg",
name: "Boris Brejcha",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/2aw3EVIIy1zbuvkl0txoqPBGUjvkv056NUzc6Qdz5ZdmknsJr28AQig7HTy_q9xqYC4LjVsyffl-9shZ=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/2aw3EVIIy1zbuvkl0txoqPBGUjvkv056NUzc6Qdz5ZdmknsJr28AQig7HTy_q9xqYC4LjVsyffl-9shZ=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(988000),
),
ArtistItem(
id: "UCZnutiGgJ2LrrwzDH_ElSDg",
name: "Dendemann",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/G_nI16FH_wiRKj1HAhmltOA-vTifD8UVwsNcJGKu40c6Y2A6Pg2S6o6f5EajkIZguv8JAt1mU9V66dw=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/G_nI16FH_wiRKj1HAhmltOA-vTifD8UVwsNcJGKu40c6Y2A6Pg2S6o6f5EajkIZguv8JAt1mU9V66dw=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(22700),
),
],
playlists: [
MusicPlaylistItem(
id: "PL46jG0sBz4EabjYqv0tVEd1RvrZRjfJ8m", id: "PL46jG0sBz4EabjYqv0tVEd1RvrZRjfJ8m",
name: "Lieblingsmensch", name: "Lieblingsmensch",
thumbnail: [ thumbnail: [
@ -399,8 +344,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PL-2Z-8cOvEixQ2EKumbXXW8XivJG6U7-D", id: "PL-2Z-8cOvEixQ2EKumbXXW8XivJG6U7-D",
name: "Namika - Lieblingsmensch", name: "Namika - Lieblingsmensch",
thumbnail: [ thumbnail: [
@ -426,8 +371,8 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
), )),
MusicPlaylistItem( Playlist(MusicPlaylistItem(
id: "PLDnJKuRrOR5sx_jZ8-dsweq1OdayL_kpz", id: "PLDnJKuRrOR5sx_jZ8-dsweq1OdayL_kpz",
name: "Lieblingsmensch", name: "Lieblingsmensch",
thumbnail: [ thumbnail: [
@ -453,13 +398,61 @@ MusicSearchResult(
)), )),
track_count: None, track_count: None,
from_ytm: false, from_ytm: false,
)),
Artist(ArtistItem(
id: "UCIh4j8fXWf2U0ro0qnGU8Mg",
name: "Namika",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/iY5H0k9sMP6hevj7ttwx2WibgxmJ9OMoK9TuVHwUMvdA8ZrrJCdGYT_BG-HhgYcVDihVJMQqSKbOcpk=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/iY5H0k9sMP6hevj7ttwx2WibgxmJ9OMoK9TuVHwUMvdA8ZrrJCdGYT_BG-HhgYcVDihVJMQqSKbOcpk=w120-h120-p-l90-rj",
width: 120,
height: 120,
), ),
], ],
corrected_query: Some("lieblingsmensch"), subscriber_count: Some(737000),
order: [ )),
Album, Artist(ArtistItem(
Track, id: "UCCpID8TTjkkjLCwBybAfHSg",
Playlist, name: "Boris Brejcha",
Artist, avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/2aw3EVIIy1zbuvkl0txoqPBGUjvkv056NUzc6Qdz5ZdmknsJr28AQig7HTy_q9xqYC4LjVsyffl-9shZ=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/2aw3EVIIy1zbuvkl0txoqPBGUjvkv056NUzc6Qdz5ZdmknsJr28AQig7HTy_q9xqYC4LjVsyffl-9shZ=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
], ],
subscriber_count: Some(988000),
)),
Artist(ArtistItem(
id: "UCZnutiGgJ2LrrwzDH_ElSDg",
name: "Dendemann",
avatar: [
Thumbnail(
url: "https://lh3.googleusercontent.com/G_nI16FH_wiRKj1HAhmltOA-vTifD8UVwsNcJGKu40c6Y2A6Pg2S6o6f5EajkIZguv8JAt1mU9V66dw=w60-h60-p-l90-rj",
width: 60,
height: 60,
),
Thumbnail(
url: "https://lh3.googleusercontent.com/G_nI16FH_wiRKj1HAhmltOA-vTifD8UVwsNcJGKu40c6Y2A6Pg2S6o6f5EajkIZguv8JAt1mU9V66dw=w120-h120-p-l90-rj",
width: 120,
height: 120,
),
],
subscriber_count: Some(22700),
)),
],
ctoken: None,
endpoint: music_search,
),
corrected_query: Some("lieblingsmensch"),
) )

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: None, count: None,
items: [ items: [

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: None, count: None,
items: [ items: [

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: None, count: None,
items: [ items: [

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: Some(7), count: Some(7),
items: [ items: [

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: None, count: None,
items: [ items: [

View file

@ -2,7 +2,7 @@
source: src/client/music_search.rs source: src/client/music_search.rs
expression: map_res.c expression: map_res.c
--- ---
MusicSearchFiltered( MusicSearchResult(
items: Paginator( items: Paginator(
count: None, count: None,
items: [ items: [

View file

@ -810,9 +810,9 @@ pub struct ChannelRssVideo {
/// YouTube search result /// YouTube search result
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SearchResult { pub struct SearchResult<T> {
/// Search result items /// Search result items
pub items: Paginator<YouTubeItem>, pub items: Paginator<T>,
/// Corrected search query /// Corrected search query
/// ///
/// If the search term containes a typo, YouTube instead searches /// If the search term containes a typo, YouTube instead searches
@ -1143,29 +1143,6 @@ pub struct MusicArtist {
pub radio_id: Option<String>, pub radio_id: Option<String>,
} }
/// YouTube Music search result
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct MusicSearchResult {
/// Found tracks
pub tracks: Vec<TrackItem>,
/// Found albums
pub albums: Vec<AlbumItem>,
/// Found artists
pub artists: Vec<ArtistItem>,
/// Found playlists
pub playlists: Vec<MusicPlaylistItem>,
/// Corrected search query
///
/// If the search term containes a typo, YouTube instead searches
/// for the corrected search term and displays it on top of the
/// search results page.
pub corrected_query: Option<String>,
/// Order of the item sections of the search page, starting with
/// the most relevant.
pub order: Vec<MusicItemType>,
}
/// Generic YouTube Music item /// Generic YouTube Music item
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[allow(missing_docs)] #[allow(missing_docs)]
@ -1186,10 +1163,10 @@ pub enum MusicItemType {
Playlist, Playlist,
} }
/// Filtered YouTube Music search result (one item type) /// YouTube Music search result
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
pub struct MusicSearchFiltered<T> { pub struct MusicSearchResult<T> {
/// Search items /// Search items
pub items: Paginator<T>, pub items: Paginator<T>,
/// Corrected search query /// Corrected search query

View file

@ -1,7 +1,5 @@
//! Wrapper model for progressively fetched items //! Wrapper model for progressively fetched items
use std::convert::TryInto;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Wrapper around progressively fetched items /// Wrapper around progressively fetched items

View file

@ -217,6 +217,38 @@ impl SearchFilter {
} }
} }
/// YouTube Music search filter
///
/// Allows you to filter YTM search results by item type.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MusicSearchFilter {
/// YouTube Music tracks
Tracks,
/// YouTube videos
Videos,
/// Albums
Albums,
/// Artists
Artists,
/// Playlists created by YouTube Music
YtmPlaylists,
/// Playlists created by YouTube users
CommunityPlaylists,
}
impl MusicSearchFilter {
pub(crate) fn params(self) -> &'static str {
match self {
MusicSearchFilter::Tracks => "EgWKAQIIAWoMEAMQBBAJEA4QChAF",
MusicSearchFilter::Videos => "EgWKAQIQAWoMEAMQBBAJEA4QChAF",
MusicSearchFilter::Albums => "EgWKAQIYAWoMEAMQBBAJEA4QChAF",
MusicSearchFilter::Artists => "EgWKAQIgAWoMEAMQBBAJEA4QChAF",
MusicSearchFilter::YtmPlaylists => "EgeKAQQoADgBagwQAxAEEAkQDhAKEAU%3D",
MusicSearchFilter::CommunityPlaylists => "EgeKAQQoAEABagwQAxAEEAkQDhAKEAU%3D",
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use rstest::rstest; use rstest::rstest;

View file

@ -5,25 +5,22 @@ use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
use rstest::{fixture, rstest}; use rstest::{fixture, rstest};
use rustypipe::model::paginator::ContinuationEndpoint; use time::{macros::date, OffsetDateTime};
use rustypipe::param::{ChannelOrder, ChannelVideoTab, Language};
use rustypipe::validate;
use time::macros::date;
use time::OffsetDateTime;
use rustypipe::client::{ClientType, RustyPipe, RustyPipeQuery}; use rustypipe::client::{ClientType, RustyPipe, RustyPipeQuery};
use rustypipe::error::{Error, ExtractionError, UnavailabilityReason}; use rustypipe::error::{Error, ExtractionError, UnavailabilityReason};
use rustypipe::model::{ use rustypipe::model::{
paginator::Paginator, paginator::{ContinuationEndpoint, Paginator},
richtext::ToPlaintext, richtext::ToPlaintext,
traits::{FromYtItem, YtStream}, traits::{FromYtItem, YtStream},
AlbumType, AudioCodec, AudioFormat, AudioTrackType, Channel, Frameset, MusicGenre, AlbumType, AudioCodec, AudioFormat, AudioTrackType, Channel, Frameset, MusicGenre, MusicItem,
MusicItemType, UrlTarget, Verification, VideoCodec, VideoFormat, YouTubeItem, UrlTarget, Verification, VideoCodec, VideoFormat, YouTubeItem,
}; };
use rustypipe::param::{ use rustypipe::param::{
search_filter::{self, SearchFilter}, search_filter::{self, SearchFilter},
Country, ChannelOrder, ChannelVideoTab, Country, Language,
}; };
use rustypipe::validate;
//#PLAYER //#PLAYER
@ -1162,7 +1159,7 @@ mod channel_rss {
#[rstest] #[rstest]
fn search(rp: RustyPipe, unlocalized: bool) { fn search(rp: RustyPipe, unlocalized: bool) {
let result = tokio_test::block_on(rp.query().search("doobydoobap")).unwrap(); let result = tokio_test::block_on(rp.query().search::<YouTubeItem, _>("doobydoobap")).unwrap();
assert_gte( assert_gte(
result.items.count.unwrap(), result.items.count.unwrap(),
@ -1182,10 +1179,10 @@ fn search(rp: RustyPipe, unlocalized: bool) {
#[case::channel(search_filter::ItemType::Channel)] #[case::channel(search_filter::ItemType::Channel)]
#[case::playlist(search_filter::ItemType::Playlist)] #[case::playlist(search_filter::ItemType::Playlist)]
fn search_filter_item_type(#[case] item_type: search_filter::ItemType, rp: RustyPipe) { fn search_filter_item_type(#[case] item_type: search_filter::ItemType, rp: RustyPipe) {
let mut result = tokio_test::block_on( let mut result = tokio_test::block_on(rp.query().search_filter::<YouTubeItem, _>(
rp.query() "with no videos",
.search_filter("with no videos", &SearchFilter::new().item_type(item_type)), &SearchFilter::new().item_type(item_type),
) ))
.unwrap(); .unwrap();
tokio_test::block_on(result.items.extend(rp.query())).unwrap(); tokio_test::block_on(result.items.extend(rp.query())).unwrap();
@ -1207,7 +1204,7 @@ fn search_filter_item_type(#[case] item_type: search_filter::ItemType, rp: Rusty
#[rstest] #[rstest]
fn search_empty(rp: RustyPipe) { fn search_empty(rp: RustyPipe) {
let result = tokio_test::block_on( let result = tokio_test::block_on(
rp.query().search_filter( rp.query().search_filter::<YouTubeItem, _>(
"3gig84hgi34gu8vj34gj489", "3gig84hgi34gu8vj34gj489",
&search_filter::SearchFilter::new() &search_filter::SearchFilter::new()
.feature(search_filter::Feature::IsLive) .feature(search_filter::Feature::IsLive)
@ -1602,18 +1599,15 @@ fn music_artist_albums_not_found(rp: RustyPipe) {
#[rstest] #[rstest]
#[case::default(false)] #[case::default(false)]
#[case::typo(true)] #[case::typo(true)]
fn music_search(#[case] typo: bool, rp: RustyPipe, unlocalized: bool) { fn music_search_main(#[case] typo: bool, rp: RustyPipe, unlocalized: bool) {
let res = tokio_test::block_on(rp.query().music_search(match typo { let res = tokio_test::block_on(rp.query().music_search_main(match typo {
false => "lieblingsmensch namika", false => "lieblingsmensch namika",
true => "lieblingsmesch namika", true => "lieblingsmesch namika",
})) }))
.unwrap(); .unwrap();
let items = res.items.items;
assert!(!res.tracks.is_empty(), "no tracks"); check_search_result(&items);
assert!(!res.albums.is_empty(), "no albums");
assert!(!res.artists.is_empty(), "no artists");
assert!(!res.playlists.is_empty(), "no playlists");
assert_eq!(res.order[0], MusicItemType::Track);
if typo { if typo {
if unlocalized { if unlocalized {
@ -1623,12 +1617,21 @@ fn music_search(#[case] typo: bool, rp: RustyPipe, unlocalized: bool) {
assert_eq!(res.corrected_query, None); assert_eq!(res.corrected_query, None);
} }
let track = &res let track = items
.tracks
.iter() .iter()
.find(|a| a.id == "6485PhOtHzY") .find_map(|itm| {
if let MusicItem::Track(track) = itm {
if track.id == "6485PhOtHzY" {
Some(track)
} else {
None
}
} else {
None
}
})
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!("could not find track, got {:#?}", &res.tracks); panic!("could not find track, got {:#?}", &items);
}); });
assert_eq!(track.name, "Lieblingsmensch"); assert_eq!(track.name, "Lieblingsmensch");
@ -1654,21 +1657,27 @@ fn music_search(#[case] typo: bool, rp: RustyPipe, unlocalized: bool) {
} }
#[rstest] #[rstest]
fn music_search2(rp: RustyPipe, unlocalized: bool) { fn music_search_main2(rp: RustyPipe, unlocalized: bool) {
let res = tokio_test::block_on(rp.query().music_search("taylor swift")).unwrap(); let res = tokio_test::block_on(rp.query().music_search_main("taylor swift")).unwrap();
let items = res.items.items;
assert!(!res.tracks.is_empty(), "no tracks"); check_search_result(&items);
assert!(!res.albums.is_empty(), "no albums");
assert!(!res.artists.is_empty(), "no artists");
assert!(!res.playlists.is_empty(), "no playlists");
assert_eq!(res.order[0], MusicItemType::Artist);
let artist = &res let artist = items
.artists
.iter() .iter()
.find(|a| a.id == "UCPC0L1d253x-KuMNwa05TpA") .find_map(|itm| {
if let MusicItem::Artist(artist) = itm {
if artist.id == "UCPC0L1d253x-KuMNwa05TpA" {
Some(artist)
} else {
None
}
} else {
None
}
})
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!("could not find artist, got {:#?}", &res.artists); panic!("could not find artist, got {:#?}", &items);
}); });
if unlocalized { if unlocalized {
@ -1677,6 +1686,37 @@ fn music_search2(rp: RustyPipe, unlocalized: bool) {
assert!(!artist.avatar.is_empty(), "got no avatar"); assert!(!artist.avatar.is_empty(), "got no avatar");
} }
fn check_search_result(items: &[MusicItem]) {
assert_gte(items.len(), 10, "search results");
let mut has_tracks = false;
let mut has_videos = false;
let mut has_albums = false;
let mut has_artists = false;
let mut has_playlists = false;
for itm in items {
match itm {
MusicItem::Track(t) => {
if t.is_video {
has_videos = true
} else {
has_tracks = true
}
}
MusicItem::Album(_) => has_albums = true,
MusicItem::Artist(_) => has_artists = true,
MusicItem::Playlist(_) => has_playlists = true,
}
}
assert!(has_tracks, "no tracks");
assert!(has_videos, "no videos");
assert!(has_albums, "no albums");
assert!(has_artists, "no artists");
assert!(has_playlists, "no playlists");
}
#[rstest] #[rstest]
fn music_search_tracks(rp: RustyPipe, unlocalized: bool) { fn music_search_tracks(rp: RustyPipe, unlocalized: bool) {
let res = tokio_test::block_on(rp.query().music_search_tracks("black mamba")).unwrap(); let res = tokio_test::block_on(rp.query().music_search_tracks("black mamba")).unwrap();
@ -1754,23 +1794,35 @@ fn music_search_videos(rp: RustyPipe, unlocalized: bool) {
#[case::videos(true)] #[case::videos(true)]
fn music_search_episode(rp: RustyPipe, #[case] videos: bool) { fn music_search_episode(rp: RustyPipe, #[case] videos: bool) {
let query = "Blond - Da muss man dabei gewesen sein: Das Hörspiel - Fall #1"; let query = "Blond - Da muss man dabei gewesen sein: Das Hörspiel - Fall #1";
let tracks = if videos { let track_id = "Zq_-LDy7AgE";
tokio_test::block_on(rp.query().music_search_videos(query))
.unwrap()
.items
.items
} else {
tokio_test::block_on(rp.query().music_search(query))
.unwrap()
.tracks
};
let track = &tracks let track = if videos {
let items = tokio_test::block_on(rp.query().music_search_videos(query))
.unwrap()
.items
.items;
items.iter().find(|a| a.id == track_id).cloned()
} else {
let items = tokio_test::block_on(rp.query().music_search_main(query))
.unwrap()
.items
.items;
items
.iter() .iter()
.find(|a| a.id == "Zq_-LDy7AgE") .find_map(|itm| {
.unwrap_or_else(|| { if let MusicItem::Track(track) = itm {
panic!("could not find episode, got {:#?}", &tracks); if track.id == track_id {
}); Some(track)
} else {
None
}
} else {
None
}
})
.cloned()
}
.expect("could not find episode");
assert_eq!(track.artists.len(), 1); assert_eq!(track.artists.len(), 1);
let track_artist = &track.artists[0]; let track_artist = &track.artists[0];
@ -1967,7 +2019,7 @@ fn music_search_playlists_community(rp: RustyPipe) {
/// The YouTube Music search sometimes shows genre radio items. They should be skipped. /// The YouTube Music search sometimes shows genre radio items. They should be skipped.
#[rstest] #[rstest]
fn music_search_genre_radio(rp: RustyPipe) { fn music_search_genre_radio(rp: RustyPipe) {
tokio_test::block_on(rp.query().music_search("pop radio")).unwrap(); tokio_test::block_on(rp.query().music_search_main("pop radio")).unwrap();
} }
#[rstest] #[rstest]
@ -2372,7 +2424,7 @@ const VISITOR_DATA_SEARCH_CHANNEL_HANDLES: &str = "CgszYlc1Yk1WZGRCSSjrwOSbBg%3D
fn ab3_search_channel_handles() { fn ab3_search_channel_handles() {
let rp = rp_visitor_data(VISITOR_DATA_SEARCH_CHANNEL_HANDLES); let rp = rp_visitor_data(VISITOR_DATA_SEARCH_CHANNEL_HANDLES);
tokio_test::block_on(rp.query().search_filter( tokio_test::block_on(rp.query().search_filter::<YouTubeItem, _>(
"test", "test",
&SearchFilter::new().item_type(search_filter::ItemType::Channel), &SearchFilter::new().item_type(search_filter::ItemType::Channel),
)) ))