Compare commits

...

3 commits

Author SHA1 Message Date
127596687b chore: update quick-xml
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-09-22 03:25:39 +02:00
1d1dcd622f feat: add tracing 2023-09-22 03:22:38 +02:00
ab599206c5 tests: expect album for artist top tracks 2023-09-22 02:50:44 +02:00
24 changed files with 156 additions and 73 deletions

View file

@ -35,7 +35,6 @@ regex = "1.6.0"
fancy-regex = "0.11.0" fancy-regex = "0.11.0"
thiserror = "1.0.36" thiserror = "1.0.36"
url = "2.2.2" url = "2.2.2"
log = "0.4.17"
reqwest = { version = "0.11.11", default-features = false, features = [ reqwest = { version = "0.11.11", default-features = false, features = [
"json", "json",
"gzip", "gzip",
@ -60,11 +59,10 @@ ress = "0.11.4"
phf = "0.11.1" phf = "0.11.1"
base64 = "0.21.0" base64 = "0.21.0"
urlencoding = "2.1.2" urlencoding = "2.1.2"
quick-xml = { version = "0.29.0", features = ["serialize"], optional = true } quick-xml = { version = "0.30.0", features = ["serialize"], optional = true }
tracing = { version = "0.1.37", features = ["log"] }
[dev-dependencies] [dev-dependencies]
env_logger = "0.10.0"
test-log = "0.2.11"
rstest = "0.18.1" rstest = "0.18.1"
tokio-test = "0.4.2" tokio-test = "0.4.2"
insta = { version = "1.17.1", features = ["ron", "redactions"] } insta = { version = "1.17.1", features = ["ron", "redactions"] }

View file

@ -47,7 +47,7 @@ indicatif = "0.17.0"
futures = "0.3.21" futures = "0.3.21"
anyhow = "1.0" anyhow = "1.0"
clap = { version = "4.0.29", features = ["derive"] } clap = { version = "4.0.29", features = ["derive"] }
env_logger = "0.10.0" tracing-subscriber = "0.3.17"
serde = "1.0" serde = "1.0"
serde_json = "1.0.82" serde_json = "1.0.82"
serde_yaml = "0.9.19" serde_yaml = "0.9.19"

View file

@ -390,7 +390,8 @@ async fn download_videos(
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
env_logger::builder().format_timestamp_micros().init(); // env_logger::builder().format_timestamp_micros().init();
tracing_subscriber::fmt::init();
let cli = Cli::parse(); let cli = Cli::parse();

View file

@ -20,7 +20,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use log::error; use tracing::error;
pub(crate) const DEFAULT_CACHE_FILE: &str = "rustypipe_cache.json"; pub(crate) const DEFAULT_CACHE_FILE: &str = "rustypipe_cache.json";

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use serde::Serialize; use serde::Serialize;
use url::Url; use url::Url;
@ -78,7 +80,8 @@ impl RustyPipeQuery {
} }
/// Get the videos from a YouTube channel /// Get the videos from a YouTube channel
pub async fn channel_videos<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn channel_videos<S: AsRef<str> + Debug>(
&self, &self,
channel_id: S, channel_id: S,
) -> Result<Channel<Paginator<VideoItem>>, Error> { ) -> Result<Channel<Paginator<VideoItem>>, Error> {
@ -89,7 +92,8 @@ impl RustyPipeQuery {
/// Get a ordered list of videos from a YouTube channel /// Get a ordered list of videos from a YouTube channel
/// ///
/// This function does not return channel metadata. /// This function does not return channel metadata.
pub async fn channel_videos_order<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn channel_videos_order<S: AsRef<str> + Debug>(
&self, &self,
channel_id: S, channel_id: S,
order: ChannelOrder, order: ChannelOrder,
@ -99,7 +103,8 @@ impl RustyPipeQuery {
} }
/// Get the videos of the given tab (Shorts, Livestreams) from a YouTube channel /// Get the videos of the given tab (Shorts, Livestreams) from a YouTube channel
pub async fn channel_videos_tab<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn channel_videos_tab<S: AsRef<str> + Debug>(
&self, &self,
channel_id: S, channel_id: S,
tab: ChannelVideoTab, tab: ChannelVideoTab,
@ -111,7 +116,8 @@ impl RustyPipeQuery {
/// Get a ordered list of videos from the given tab (Shorts, Livestreams) of a YouTube channel /// Get a ordered list of videos from the given tab (Shorts, Livestreams) of a YouTube channel
/// ///
/// This function does not return channel metadata. /// This function does not return channel metadata.
pub async fn channel_videos_tab_order<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn channel_videos_tab_order<S: AsRef<str> + Debug>(
&self, &self,
channel_id: S, channel_id: S,
tab: ChannelVideoTab, tab: ChannelVideoTab,
@ -128,7 +134,8 @@ impl RustyPipeQuery {
} }
/// Search the videos of a channel /// Search the videos of a channel
pub async fn channel_search<S: AsRef<str>, S2: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn channel_search<S: AsRef<str> + Debug, S2: AsRef<str> + Debug>(
&self, &self,
channel_id: S, channel_id: S,
query: S2, query: S2,
@ -143,7 +150,8 @@ impl RustyPipeQuery {
} }
/// Get the playlists of a channel /// Get the playlists of a channel
pub async fn channel_playlists<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn channel_playlists<S: AsRef<str> + Debug>(
&self, &self,
channel_id: S, channel_id: S,
) -> Result<Channel<Paginator<PlaylistItem>>, Error> { ) -> Result<Channel<Paginator<PlaylistItem>>, Error> {
@ -167,7 +175,8 @@ impl RustyPipeQuery {
} }
/// Get additional metadata from the *About* tab of a channel /// Get additional metadata from the *About* tab of a channel
pub async fn channel_info<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn channel_info<S: AsRef<str> + Debug>(
&self, &self,
channel_id: S, channel_id: S,
) -> Result<Channel<ChannelInfo>, Error> { ) -> Result<Channel<ChannelInfo>, Error> {

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use crate::{ use crate::{
error::{Error, ExtractionError}, error::{Error, ExtractionError},
model::ChannelRss, model::ChannelRss,
@ -15,7 +17,11 @@ impl RustyPipeQuery {
/// for checking a lot of channels or implementing a subscription feed. /// for checking a lot of channels or implementing a subscription feed.
/// ///
/// The downside of using the RSS feed is that it does not provide video durations. /// The downside of using the RSS feed is that it does not provide video durations.
pub async fn channel_rss<S: AsRef<str>>(&self, channel_id: S) -> Result<ChannelRss, Error> { #[tracing::instrument(skip(self))]
pub async fn channel_rss<S: AsRef<str> + Debug>(
&self,
channel_id: S,
) -> Result<ChannelRss, Error> {
let channel_id = channel_id.as_ref(); let channel_id = channel_id.as_ref();
let url = format!("https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}"); let url = format!("https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}");
let xml = self let xml = self

View file

@ -489,7 +489,7 @@ impl RustyPipeBuilder {
.and_then(|data| match serde_json::from_str::<CacheData>(&data) { .and_then(|data| match serde_json::from_str::<CacheData>(&data) {
Ok(data) => Some(data), Ok(data) => Some(data),
Err(e) => { Err(e) => {
log::error!("Could not deserialize cache. Error: {}", e); tracing::error!("Could not deserialize cache. Error: {}", e);
None None
} }
}) })
@ -745,7 +745,7 @@ impl RustyPipe {
// Retry in case of a recoverable status code (server err, too many requests) // Retry in case of a recoverable status code (server err, too many requests)
if n != self.inner.n_http_retries { if n != self.inner.n_http_retries {
let ms = util::retry_delay(n, 1000, 60000, 3); let ms = util::retry_delay(n, 1000, 60000, 3);
log::warn!( tracing::warn!(
"Retry attempt #{}. Error: {}. Waiting {} ms", "Retry attempt #{}. Error: {}. Waiting {} ms",
n + 1, n + 1,
status, status,
@ -866,7 +866,7 @@ impl RustyPipe {
match desktop_client.get() { match desktop_client.get() {
Some(cdata) => cdata.version.clone(), Some(cdata) => cdata.version.clone(),
None => { None => {
log::debug!("getting desktop client version"); tracing::debug!("getting desktop client version");
match self.extract_desktop_client_version().await { match self.extract_desktop_client_version().await {
Ok(version) => { Ok(version) => {
*desktop_client = CacheEntry::from(ClientData { *desktop_client = CacheEntry::from(ClientData {
@ -877,7 +877,7 @@ impl RustyPipe {
version version
} }
Err(e) => { Err(e) => {
log::warn!("{}, falling back to hardcoded desktop client version", e); tracing::warn!("{}, falling back to hardcoded desktop client version", e);
DESKTOP_CLIENT_VERSION.to_owned() DESKTOP_CLIENT_VERSION.to_owned()
} }
} }
@ -898,7 +898,7 @@ impl RustyPipe {
match music_client.get() { match music_client.get() {
Some(cdata) => cdata.version.clone(), Some(cdata) => cdata.version.clone(),
None => { None => {
log::debug!("getting music client version"); tracing::debug!("getting music client version");
match self.extract_music_client_version().await { match self.extract_music_client_version().await {
Ok(version) => { Ok(version) => {
*music_client = CacheEntry::from(ClientData { *music_client = CacheEntry::from(ClientData {
@ -909,7 +909,7 @@ impl RustyPipe {
version version
} }
Err(e) => { Err(e) => {
log::warn!("{}, falling back to hardcoded music client version", e); tracing::warn!("{}, falling back to hardcoded music client version", e);
DESKTOP_MUSIC_CLIENT_VERSION.to_owned() DESKTOP_MUSIC_CLIENT_VERSION.to_owned()
} }
} }
@ -925,7 +925,7 @@ impl RustyPipe {
match deobf_data.get() { match deobf_data.get() {
Some(deobf_data) => Ok(deobf_data.clone()), Some(deobf_data) => Ok(deobf_data.clone()),
None => { None => {
log::debug!("getting deobf data"); tracing::debug!("getting deobf data");
match DeobfData::extract(self.inner.http.clone(), self.inner.reporter.as_deref()) match DeobfData::extract(self.inner.http.clone(), self.inner.reporter.as_deref())
.await .await
@ -941,7 +941,7 @@ impl RustyPipe {
// Try to fall back to expired cache data if available, otherwise return error // Try to fall back to expired cache data if available, otherwise return error
match deobf_data.get_expired() { match deobf_data.get_expired() {
Some(d) => { Some(d) => {
log::warn!("could not get new deobf data ({e}), falling back to expired cache"); tracing::warn!("could not get new deobf data ({e}), falling back to expired cache");
Ok(d.clone()) Ok(d.clone())
} }
None => Err(e), None => Err(e),
@ -963,7 +963,7 @@ impl RustyPipe {
match serde_json::to_string(&cdata) { match serde_json::to_string(&cdata) {
Ok(data) => storage.write(&data), Ok(data) => storage.write(&data),
Err(e) => log::error!("Could not serialize cache. Error: {}", e), Err(e) => tracing::error!("Could not serialize cache. Error: {}", e),
} }
} }
} }
@ -976,7 +976,7 @@ impl RustyPipe {
/// Sometimes YouTube does not set the `__Secure-YEC` cookie. In this case, the /// Sometimes YouTube does not set the `__Secure-YEC` cookie. In this case, the
/// visitor data is extracted from the html page. /// visitor data is extracted from the html page.
async fn get_visitor_data(&self) -> Result<String, Error> { async fn get_visitor_data(&self) -> Result<String, Error> {
log::debug!("getting YT visitor data"); tracing::debug!("getting YT visitor data");
let resp = self.inner.http.get(YOUTUBE_MUSIC_HOME_URL).send().await?; let resp = self.inner.http.get(YOUTUBE_MUSIC_HOME_URL).send().await?;
let vdata = resp let vdata = resp
@ -1284,6 +1284,7 @@ impl RustyPipeQuery {
let status = response.status(); let status = response.status();
let body = response.text().await?; let body = response.text().await?;
tracing::debug!("fetched {} bytes from YT", body.len());
let res = if status.is_client_error() || status.is_server_error() { let res = if status.is_client_error() || status.is_server_error() {
let error_msg = serde_json::from_str::<response::ErrorResponse>(&body) let error_msg = serde_json::from_str::<response::ErrorResponse>(&body)
@ -1314,9 +1315,11 @@ impl RustyPipeQuery {
} }
}; };
tracing::debug!("mapped response");
Ok(RequestResult { res, status, body }) Ok(RequestResult { res, status, body })
} }
#[tracing::instrument(skip_all)]
async fn yt_request<R: DeserializeOwned + MapResponse<M> + Debug, M>( async fn yt_request<R: DeserializeOwned + MapResponse<M> + Debug, M>(
&self, &self,
request: &Request, request: &Request,
@ -1339,7 +1342,7 @@ impl RustyPipeQuery {
if n != self.client.inner.n_http_retries { if n != self.client.inner.n_http_retries {
let ms = util::retry_delay(n, 1000, 60000, 3); let ms = util::retry_delay(n, 1000, 60000, 3);
log::warn!( tracing::warn!(
"Retry attempt #{}. Error: {}. Waiting {} ms", "Retry attempt #{}. Error: {}. Waiting {} ms",
n + 1, n + 1,
err, err,
@ -1380,7 +1383,7 @@ impl RustyPipeQuery {
body: &B, body: &B,
deobf: Option<&DeobfData>, deobf: Option<&DeobfData>,
) -> Result<M, Error> { ) -> Result<M, Error> {
log::debug!("getting {}({})", operation, id); tracing::debug!("getting {}({})", operation, id);
let request = self let request = self
.request_builder(ctype, endpoint) .request_builder(ctype, endpoint)
@ -1528,7 +1531,7 @@ trait MapResponse<T> {
fn validate_country(country: Country) -> Country { fn validate_country(country: Country) -> Country {
if country == Country::Zz { if country == Country::Zz {
log::warn!("Country:Zz (Global) can only be used for fetching music charts, falling back to Country:Us"); tracing::warn!("Country:Zz (Global) can only be used for fetching music charts, falling back to Country:Us");
Country::Us Country::Us
} else { } else {
country country

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use tracing::debug;
use crate::{ use crate::{
client::response::url_endpoint::NavigationEndpoint, client::response::url_endpoint::NavigationEndpoint,
@ -29,7 +30,7 @@ impl RustyPipeQuery {
let res = self._music_artist(artist_id, all_albums).await; let res = self._music_artist(artist_id, all_albums).await;
if let Err(Error::Extraction(ExtractionError::Redirect(id))) = res { if let Err(Error::Extraction(ExtractionError::Redirect(id))) = res {
log::debug!("music artist {} redirects to {}", artist_id, &id); debug!("music artist {} redirects to {}", artist_id, &id);
self._music_artist(&id, all_albums).await self._music_artist(&id, all_albums).await
} else { } else {
res res

View file

@ -32,6 +32,7 @@ struct FormData {
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get the YouTube Music charts for a given country /// Get the YouTube Music charts for a given country
#[tracing::instrument(skip(self))]
pub async fn music_charts(&self, country: Option<Country>) -> Result<MusicCharts, Error> { pub async fn music_charts(&self, country: Option<Country>) -> Result<MusicCharts, Error> {
let context = self.get_context(ClientType::DesktopMusic, true, None).await; let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QCharts { let request_body = QCharts {

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use std::{borrow::Cow, fmt::Debug};
use serde::Serialize; use serde::Serialize;
@ -38,7 +38,11 @@ struct QRadio<'a> {
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get the metadata of a YouTube music track /// Get the metadata of a YouTube music track
pub async fn music_details<S: AsRef<str>>(&self, video_id: S) -> Result<TrackDetails, Error> { #[tracing::instrument(skip(self))]
pub async fn music_details<S: AsRef<str> + Debug>(
&self,
video_id: S,
) -> Result<TrackDetails, Error> {
let video_id = video_id.as_ref(); let video_id = video_id.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 = QMusicDetails { let request_body = QMusicDetails {
@ -62,7 +66,8 @@ impl RustyPipeQuery {
/// Get the lyrics of a YouTube music track /// Get the lyrics of a YouTube music track
/// ///
/// The `lyrics_id` has to be obtained using [`RustyPipeQuery::music_details`]. /// The `lyrics_id` has to be obtained using [`RustyPipeQuery::music_details`].
pub async fn music_lyrics<S: AsRef<str>>(&self, lyrics_id: S) -> Result<Lyrics, Error> { #[tracing::instrument(skip(self))]
pub async fn music_lyrics<S: AsRef<str> + Debug>(&self, lyrics_id: S) -> Result<Lyrics, Error> {
let lyrics_id = lyrics_id.as_ref(); let lyrics_id = lyrics_id.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 = QBrowse { let request_body = QBrowse {
@ -83,7 +88,11 @@ impl RustyPipeQuery {
/// Get related items (tracks, playlists, artists) to a YouTube Music track /// Get related items (tracks, playlists, artists) to a YouTube Music track
/// ///
/// The `related_id` has to be obtained using [`RustyPipeQuery::music_details`]. /// The `related_id` has to be obtained using [`RustyPipeQuery::music_details`].
pub async fn music_related<S: AsRef<str>>(&self, related_id: S) -> Result<MusicRelated, Error> { #[tracing::instrument(skip(self))]
pub async fn music_related<S: AsRef<str> + Debug>(
&self,
related_id: S,
) -> Result<MusicRelated, Error> {
let related_id = related_id.as_ref(); let related_id = related_id.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 = QBrowse { let request_body = QBrowse {
@ -104,7 +113,8 @@ impl RustyPipeQuery {
/// Get a YouTube Music radio (a dynamically generated playlist) /// Get a YouTube Music radio (a dynamically generated playlist)
/// ///
/// The `radio_id` can be obtained using [`RustyPipeQuery::music_artist`] to get an artist's radio. /// The `radio_id` can be obtained using [`RustyPipeQuery::music_artist`] to get an artist's radio.
pub async fn music_radio<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_radio<S: AsRef<str> + Debug>(
&self, &self,
radio_id: S, radio_id: S,
) -> Result<Paginator<TrackItem>, Error> { ) -> Result<Paginator<TrackItem>, Error> {
@ -133,7 +143,8 @@ impl RustyPipeQuery {
} }
/// Get a YouTube Music radio (a dynamically generated playlist) for a track /// Get a YouTube Music radio (a dynamically generated playlist) for a track
pub async fn music_radio_track<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_radio_track<S: AsRef<str> + Debug>(
&self, &self,
video_id: S, video_id: S,
) -> Result<Paginator<TrackItem>, Error> { ) -> Result<Paginator<TrackItem>, Error> {
@ -142,7 +153,8 @@ impl RustyPipeQuery {
} }
/// Get a YouTube Music radio (a dynamically generated playlist) for a playlist /// Get a YouTube Music radio (a dynamically generated playlist) for a playlist
pub async fn music_radio_playlist<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_radio_playlist<S: AsRef<str> + Debug>(
&self, &self,
playlist_id: S, playlist_id: S,
) -> Result<Paginator<TrackItem>, Error> { ) -> Result<Paginator<TrackItem>, Error> {

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use std::{borrow::Cow, fmt::Debug};
use crate::{ use crate::{
error::{Error, ExtractionError}, error::{Error, ExtractionError},
@ -13,6 +13,7 @@ use super::{
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get a list of moods and genres from YouTube Music /// Get a list of moods and genres from YouTube Music
#[tracing::instrument(skip(self))]
pub async fn music_genres(&self) -> Result<Vec<MusicGenreItem>, Error> { pub async fn music_genres(&self) -> Result<Vec<MusicGenreItem>, Error> {
let context = self.get_context(ClientType::DesktopMusic, true, None).await; let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QBrowse { let request_body = QBrowse {
@ -31,7 +32,11 @@ impl RustyPipeQuery {
} }
/// Get the playlists from a YouTube Music genre /// Get the playlists from a YouTube Music genre
pub async fn music_genre<S: AsRef<str>>(&self, genre_id: S) -> Result<MusicGenre, Error> { #[tracing::instrument(skip(self))]
pub async fn music_genre<S: AsRef<str> + Debug>(
&self,
genre_id: S,
) -> Result<MusicGenre, Error> {
let genre_id = genre_id.as_ref(); let genre_id = genre_id.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 = QBrowseParams { let request_body = QBrowseParams {

View file

@ -10,6 +10,7 @@ use super::{response, ClientType, MapResponse, QBrowse, RustyPipeQuery};
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get the new albums that were released on YouTube Music /// Get the new albums that were released on YouTube Music
#[tracing::instrument(skip(self))]
pub async fn music_new_albums(&self) -> Result<Vec<AlbumItem>, Error> { pub async fn music_new_albums(&self) -> Result<Vec<AlbumItem>, Error> {
let context = self.get_context(ClientType::DesktopMusic, true, None).await; let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QBrowse { let request_body = QBrowse {
@ -28,6 +29,7 @@ impl RustyPipeQuery {
} }
/// Get the new music videos that were released on YouTube Music /// Get the new music videos that were released on YouTube Music
#[tracing::instrument(skip(self))]
pub async fn music_new_videos(&self) -> Result<Vec<TrackItem>, Error> { pub async fn music_new_videos(&self) -> Result<Vec<TrackItem>, Error> {
let context = self.get_context(ClientType::DesktopMusic, true, None).await; let context = self.get_context(ClientType::DesktopMusic, true, None).await;
let request_body = QBrowse { let request_body = QBrowse {

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use std::{borrow::Cow, fmt::Debug};
use crate::{ use crate::{
error::{Error, ExtractionError}, error::{Error, ExtractionError},
@ -17,7 +17,8 @@ use super::{
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get a playlist from YouTube Music /// Get a playlist from YouTube Music
pub async fn music_playlist<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_playlist<S: AsRef<str> + Debug>(
&self, &self,
playlist_id: S, playlist_id: S,
) -> Result<MusicPlaylist, Error> { ) -> Result<MusicPlaylist, Error> {
@ -39,7 +40,11 @@ impl RustyPipeQuery {
} }
/// Get an album from YouTube Music /// Get an album from YouTube Music
pub async fn music_album<S: AsRef<str>>(&self, album_id: S) -> Result<MusicAlbum, Error> { #[tracing::instrument(skip(self))]
pub async fn music_album<S: AsRef<str> + Debug>(
&self,
album_id: S,
) -> Result<MusicAlbum, Error> {
let album_id = album_id.as_ref(); let album_id = album_id.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 = QBrowse { let request_body = QBrowse {

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use std::{borrow::Cow, fmt::Debug};
use serde::Serialize; use serde::Serialize;
@ -48,7 +48,11 @@ enum Params {
impl RustyPipeQuery { impl RustyPipeQuery {
/// Search YouTube Music. Returns items from any type. /// Search YouTube Music. Returns items from any type.
pub async fn music_search<S: AsRef<str>>(&self, query: S) -> Result<MusicSearchResult, Error> { #[tracing::instrument(skip(self))]
pub async fn music_search<S: AsRef<str> + Debug>(
&self,
query: S,
) -> Result<MusicSearchResult, 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 {
@ -68,7 +72,8 @@ impl RustyPipeQuery {
} }
/// Search YouTube Music tracks /// Search YouTube Music tracks
pub async fn music_search_tracks<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_search_tracks<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
) -> Result<MusicSearchFiltered<TrackItem>, Error> { ) -> Result<MusicSearchFiltered<TrackItem>, Error> {
@ -76,7 +81,8 @@ impl RustyPipeQuery {
} }
/// Search YouTube Music videos /// Search YouTube Music videos
pub async fn music_search_videos<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_search_videos<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
) -> Result<MusicSearchFiltered<TrackItem>, Error> { ) -> Result<MusicSearchFiltered<TrackItem>, Error> {
@ -107,7 +113,8 @@ impl RustyPipeQuery {
} }
/// Search YouTube Music albums /// Search YouTube Music albums
pub async fn music_search_albums<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_search_albums<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
) -> Result<MusicSearchFiltered<AlbumItem>, Error> { ) -> Result<MusicSearchFiltered<AlbumItem>, Error> {
@ -130,10 +137,12 @@ impl RustyPipeQuery {
} }
/// Search YouTube Music artists /// Search YouTube Music artists
pub async fn music_search_artists( #[tracing::instrument(skip(self))]
pub async fn music_search_artists<S: AsRef<str> + Debug>(
&self, &self,
query: &str, query: S,
) -> Result<MusicSearchFiltered<ArtistItem>, Error> { ) -> Result<MusicSearchFiltered<ArtistItem>, Error> {
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,
@ -154,7 +163,8 @@ impl RustyPipeQuery {
/// ///
/// 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`)
pub async fn music_search_playlists<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_search_playlists<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
community: bool, community: bool,
@ -182,7 +192,8 @@ impl RustyPipeQuery {
} }
/// Get YouTube Music search suggestions /// Get YouTube Music search suggestions
pub async fn music_search_suggestion<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn music_search_suggestion<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
) -> Result<MusicSearchSuggestion, Error> { ) -> Result<MusicSearchSuggestion, Error> {

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use crate::error::{Error, ExtractionError}; use crate::error::{Error, ExtractionError};
use crate::model::{ use crate::model::{
paginator::{ContinuationEndpoint, Paginator}, paginator::{ContinuationEndpoint, Paginator},
@ -11,7 +13,8 @@ use super::{response, ClientType, MapResponse, QContinuation, RustyPipeQuery};
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get more YouTube items from the given continuation token and endpoint /// Get more YouTube items from the given continuation token and endpoint
pub async fn continuation<T: FromYtItem, S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn continuation<T: FromYtItem, S: AsRef<str> + Debug>(
&self, &self,
ctoken: S, ctoken: S,
endpoint: ContinuationEndpoint, endpoint: ContinuationEndpoint,

View file

@ -1,6 +1,7 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
fmt::Debug,
}; };
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -61,7 +62,8 @@ struct QContentPlaybackContext<'a> {
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get YouTube player data (video/audio streams + basic metadata) /// Get YouTube player data (video/audio streams + basic metadata)
pub async fn player<S: AsRef<str>>(&self, video_id: S) -> Result<VideoPlayer, Error> { #[tracing::instrument(skip(self))]
pub async fn player<S: AsRef<str> + Debug>(&self, video_id: S) -> Result<VideoPlayer, Error> {
let video_id = video_id.as_ref(); let video_id = video_id.as_ref();
let desktop_res = self.player_from_client(video_id, ClientType::Desktop).await; let desktop_res = self.player_from_client(video_id, ClientType::Desktop).await;
@ -90,7 +92,8 @@ impl RustyPipeQuery {
} }
/// Get YouTube player data (video/audio streams + basic metadata) using the specified client /// Get YouTube player data (video/audio streams + basic metadata) using the specified client
pub async fn player_from_client<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn player_from_client<S: AsRef<str> + Debug>(
&self, &self,
video_id: S, video_id: S,
client_type: ClientType, client_type: ClientType,

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, convert::TryFrom}; use std::{borrow::Cow, convert::TryFrom, fmt::Debug};
use time::OffsetDateTime; use time::OffsetDateTime;
@ -12,7 +12,8 @@ use super::{response, ClientType, MapResponse, MapResult, QBrowse, RustyPipeQuer
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get a YouTube playlist /// Get a YouTube playlist
pub async fn playlist<S: AsRef<str>>(&self, playlist_id: S) -> Result<Playlist, Error> { #[tracing::instrument(skip(self))]
pub async fn playlist<S: AsRef<str> + Debug>(&self, playlist_id: S) -> Result<Playlist, Error> {
let playlist_id = playlist_id.as_ref(); let playlist_id = playlist_id.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 = QBrowse { let request_body = QBrowse {

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
@ -19,7 +21,8 @@ struct QSearch<'a> {
impl RustyPipeQuery { impl RustyPipeQuery {
/// Search YouTube /// Search YouTube
pub async fn search<S: AsRef<str>>(&self, query: S) -> Result<SearchResult, Error> { #[tracing::instrument(skip(self))]
pub async fn search<S: AsRef<str> + Debug>(&self, query: S) -> Result<SearchResult, 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 {
@ -39,7 +42,8 @@ impl RustyPipeQuery {
} }
/// Search YouTube using the given [`SearchFilter`] /// Search YouTube using the given [`SearchFilter`]
pub async fn search_filter<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn search_filter<S: AsRef<str> + Debug>(
&self, &self,
query: S, query: S,
filter: &SearchFilter, filter: &SearchFilter,
@ -63,7 +67,11 @@ impl RustyPipeQuery {
} }
/// Get YouTube search suggestions /// Get YouTube search suggestions
pub async fn search_suggestion<S: AsRef<str>>(&self, query: S) -> Result<Vec<String>, Error> { #[tracing::instrument(skip(self))]
pub async fn search_suggestion<S: AsRef<str> + Debug>(
&self,
query: S,
) -> Result<Vec<String>, Error> {
let url = url::Url::parse_with_params( let url = url::Url::parse_with_params(
"https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&xhr=t", "https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&xhr=t",
&[ &[

View file

@ -11,6 +11,7 @@ use super::{response, ClientType, MapResponse, QBrowse, QBrowseParams, RustyPipe
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get the videos from the YouTube startpage /// Get the videos from the YouTube startpage
#[tracing::instrument(skip(self))]
pub async fn startpage(&self) -> Result<Paginator<VideoItem>, Error> { pub async fn startpage(&self) -> Result<Paginator<VideoItem>, Error> {
let context = self.get_context(ClientType::Desktop, true, None).await; let context = self.get_context(ClientType::Desktop, true, None).await;
let request_body = QBrowse { let request_body = QBrowse {
@ -29,6 +30,7 @@ impl RustyPipeQuery {
} }
/// Get the videos from the YouTube trending page /// Get the videos from the YouTube trending page
#[tracing::instrument(skip(self))]
pub async fn trending(&self) -> Result<Vec<VideoItem>, Error> { pub async fn trending(&self) -> Result<Vec<VideoItem>, Error> {
let context = self.get_context(ClientType::Desktop, true, None).await; let context = self.get_context(ClientType::Desktop, true, None).await;
let request_body = QBrowseParams { let request_body = QBrowseParams {

View file

@ -1,4 +1,4 @@
use std::borrow::Cow; use std::{borrow::Cow, fmt::Debug};
use serde::Serialize; use serde::Serialize;
@ -59,7 +59,8 @@ impl RustyPipeQuery {
/// ); /// );
/// # }); /// # });
/// ``` /// ```
pub async fn resolve_url<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn resolve_url<S: AsRef<str> + Debug>(
self, self,
url: S, url: S,
resolve_albums: bool, resolve_albums: bool,
@ -236,7 +237,8 @@ impl RustyPipeQuery {
/// ); /// );
/// # }); /// # });
/// ``` /// ```
pub async fn resolve_string<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn resolve_string<S: AsRef<str> + Debug>(
self, self,
s: S, s: S,
resolve_albums: bool, resolve_albums: bool,

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
@ -26,7 +28,11 @@ struct QVideo<'a> {
impl RustyPipeQuery { impl RustyPipeQuery {
/// Get the metadata for a video /// Get the metadata for a video
pub async fn video_details<S: AsRef<str>>(&self, video_id: S) -> Result<VideoDetails, Error> { #[tracing::instrument(skip(self))]
pub async fn video_details<S: AsRef<str> + Debug>(
&self,
video_id: S,
) -> Result<VideoDetails, Error> {
let video_id = video_id.as_ref(); let video_id = video_id.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 = QVideo { let request_body = QVideo {
@ -47,7 +53,8 @@ impl RustyPipeQuery {
} }
/// Get the comments for a video using the continuation token obtained from `rusty_pipe_query.video_details()` /// Get the comments for a video using the continuation token obtained from `rusty_pipe_query.video_details()`
pub async fn video_comments<S: AsRef<str>>( #[tracing::instrument(skip(self))]
pub async fn video_comments<S: AsRef<str> + Debug>(
&self, &self,
ctoken: S, ctoken: S,
visitor_data: Option<&str>, visitor_data: Option<&str>,

View file

@ -29,7 +29,7 @@ impl DeobfData {
pub async fn extract(http: Client, reporter: Option<&dyn Reporter>) -> Result<Self, Error> { pub async fn extract(http: Client, reporter: Option<&dyn Reporter>) -> Result<Self, Error> {
let js_url = get_player_js_url(&http).await?; let js_url = get_player_js_url(&http).await?;
let player_js = get_response(&http, &js_url).await?; let player_js = get_response(&http, &js_url).await?;
log::debug!("downloaded player.js from {}", js_url); tracing::debug!("downloaded player.js from {}", js_url);
let res = Self::extract_fns(&js_url, &player_js); let res = Self::extract_fns(&js_url, &player_js);
@ -89,7 +89,7 @@ impl Deobfuscator {
res.as_str().map_or( res.as_str().map_or(
Err(DeobfError::Other("sig deobfuscation func returned null")), Err(DeobfError::Other("sig deobfuscation func returned null")),
|res| { |res| {
log::debug!("deobfuscated sig"); tracing::debug!("deobfuscated sig");
Ok(res.to_owned()) Ok(res.to_owned())
}, },
) )
@ -102,7 +102,7 @@ impl Deobfuscator {
res.as_str().map_or( res.as_str().map_or(
Err(DeobfError::Other("nsig deobfuscation func returned null")), Err(DeobfError::Other("nsig deobfuscation func returned null")),
|res| { |res| {
log::debug!("deobfuscated nsig"); tracing::debug!("deobfuscated nsig");
Ok(res.to_owned()) Ok(res.to_owned())
}, },
) )

View file

@ -23,9 +23,9 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::{macros::format_description, OffsetDateTime}; use time::{macros::format_description, OffsetDateTime};
use tracing::error;
use crate::{deobfuscate::DeobfData, util}; use crate::{deobfuscate::DeobfData, util};

View file

@ -32,7 +32,6 @@ use rustypipe::param::{
#[case::tv_html5_embed(ClientType::TvHtml5Embed)] #[case::tv_html5_embed(ClientType::TvHtml5Embed)]
#[case::android(ClientType::Android)] #[case::android(ClientType::Android)]
#[case::ios(ClientType::Ios)] #[case::ios(ClientType::Ios)]
#[test_log::test]
fn get_player_from_client(#[case] client_type: ClientType, rp: RustyPipe) { fn get_player_from_client(#[case] client_type: ClientType, rp: RustyPipe) {
let player_data = let player_data =
tokio_test::block_on(rp.query().player_from_client("n4tK7LYFxI0", client_type)).unwrap(); tokio_test::block_on(rp.query().player_from_client("n4tK7LYFxI0", client_type)).unwrap();
@ -1469,7 +1468,6 @@ fn music_album_not_found(rp: RustyPipe) {
#[case::no_artist("no_artist", "UCh8gHdtzO2tXd593_bjErWg", false, 0, 2)] #[case::no_artist("no_artist", "UCh8gHdtzO2tXd593_bjErWg", false, 0, 2)]
// querying Trailerpark's secondary YouTube channel should result in the YTM channel being fetched // querying Trailerpark's secondary YouTube channel should result in the YTM channel being fetched
#[case::secondary_channel("no_more_albums", "UCC9192yGQD25eBZgFZ84MPw", true, 15, 0)] #[case::secondary_channel("no_more_albums", "UCC9192yGQD25eBZgFZ84MPw", true, 15, 0)]
#[test_log::test]
fn music_artist( fn music_artist(
#[case] name: &str, #[case] name: &str,
#[case] id: &str, #[case] id: &str,
@ -1491,12 +1489,17 @@ fn music_artist(
assert_gte(artist.subscriber_count.unwrap(), 30000, "subscribers"); assert_gte(artist.subscriber_count.unwrap(), 30000, "subscribers");
} }
artist.tracks.iter().for_each(|t| {
assert!(!t.cover.is_empty());
if t.is_video {
assert!(t.view_count.is_some());
} else {
assert!(t.album.is_some());
}
});
// Check images // Check images
assert!(!artist.header_image.is_empty(), "got no header image"); assert!(!artist.header_image.is_empty(), "got no header image");
artist
.tracks
.iter()
.for_each(|t| assert!(!t.cover.is_empty()));
artist artist
.albums .albums
.iter() .iter()