Compare commits
3 commits
abd3317a10
...
127596687b
Author | SHA1 | Date | |
---|---|---|---|
127596687b | |||
1d1dcd622f | |||
ab599206c5 |
24 changed files with 156 additions and 73 deletions
|
@ -35,7 +35,6 @@ regex = "1.6.0"
|
|||
fancy-regex = "0.11.0"
|
||||
thiserror = "1.0.36"
|
||||
url = "2.2.2"
|
||||
log = "0.4.17"
|
||||
reqwest = { version = "0.11.11", default-features = false, features = [
|
||||
"json",
|
||||
"gzip",
|
||||
|
@ -60,11 +59,10 @@ ress = "0.11.4"
|
|||
phf = "0.11.1"
|
||||
base64 = "0.21.0"
|
||||
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]
|
||||
env_logger = "0.10.0"
|
||||
test-log = "0.2.11"
|
||||
rstest = "0.18.1"
|
||||
tokio-test = "0.4.2"
|
||||
insta = { version = "1.17.1", features = ["ron", "redactions"] }
|
||||
|
|
|
@ -47,7 +47,7 @@ indicatif = "0.17.0"
|
|||
futures = "0.3.21"
|
||||
anyhow = "1.0"
|
||||
clap = { version = "4.0.29", features = ["derive"] }
|
||||
env_logger = "0.10.0"
|
||||
tracing-subscriber = "0.3.17"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0.82"
|
||||
serde_yaml = "0.9.19"
|
||||
|
|
|
@ -390,7 +390,8 @@ async fn download_videos(
|
|||
|
||||
#[tokio::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();
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use log::error;
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) const DEFAULT_CACHE_FILE: &str = "rustypipe_cache.json";
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use serde::Serialize;
|
||||
use url::Url;
|
||||
|
||||
|
@ -78,7 +80,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
channel_id: S,
|
||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||
|
@ -89,7 +92,8 @@ impl RustyPipeQuery {
|
|||
/// Get a ordered list of videos from a YouTube channel
|
||||
///
|
||||
/// 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,
|
||||
channel_id: S,
|
||||
order: ChannelOrder,
|
||||
|
@ -99,7 +103,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
channel_id: S,
|
||||
tab: ChannelVideoTab,
|
||||
|
@ -111,7 +116,8 @@ impl RustyPipeQuery {
|
|||
/// Get a ordered list of videos from the given tab (Shorts, Livestreams) of a YouTube channel
|
||||
///
|
||||
/// 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,
|
||||
channel_id: S,
|
||||
tab: ChannelVideoTab,
|
||||
|
@ -128,7 +134,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
channel_id: S,
|
||||
query: S2,
|
||||
|
@ -143,7 +150,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
channel_id: S,
|
||||
) -> Result<Channel<Paginator<PlaylistItem>>, Error> {
|
||||
|
@ -167,7 +175,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
channel_id: S,
|
||||
) -> Result<Channel<ChannelInfo>, Error> {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
model::ChannelRss,
|
||||
|
@ -15,7 +17,11 @@ impl RustyPipeQuery {
|
|||
/// 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.
|
||||
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 url = format!("https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}");
|
||||
let xml = self
|
||||
|
|
|
@ -489,7 +489,7 @@ impl RustyPipeBuilder {
|
|||
.and_then(|data| match serde_json::from_str::<CacheData>(&data) {
|
||||
Ok(data) => Some(data),
|
||||
Err(e) => {
|
||||
log::error!("Could not deserialize cache. Error: {}", e);
|
||||
tracing::error!("Could not deserialize cache. Error: {}", e);
|
||||
None
|
||||
}
|
||||
})
|
||||
|
@ -745,7 +745,7 @@ impl RustyPipe {
|
|||
// Retry in case of a recoverable status code (server err, too many requests)
|
||||
if n != self.inner.n_http_retries {
|
||||
let ms = util::retry_delay(n, 1000, 60000, 3);
|
||||
log::warn!(
|
||||
tracing::warn!(
|
||||
"Retry attempt #{}. Error: {}. Waiting {} ms",
|
||||
n + 1,
|
||||
status,
|
||||
|
@ -866,7 +866,7 @@ impl RustyPipe {
|
|||
match desktop_client.get() {
|
||||
Some(cdata) => cdata.version.clone(),
|
||||
None => {
|
||||
log::debug!("getting desktop client version");
|
||||
tracing::debug!("getting desktop client version");
|
||||
match self.extract_desktop_client_version().await {
|
||||
Ok(version) => {
|
||||
*desktop_client = CacheEntry::from(ClientData {
|
||||
|
@ -877,7 +877,7 @@ impl RustyPipe {
|
|||
version
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -898,7 +898,7 @@ impl RustyPipe {
|
|||
match music_client.get() {
|
||||
Some(cdata) => cdata.version.clone(),
|
||||
None => {
|
||||
log::debug!("getting music client version");
|
||||
tracing::debug!("getting music client version");
|
||||
match self.extract_music_client_version().await {
|
||||
Ok(version) => {
|
||||
*music_client = CacheEntry::from(ClientData {
|
||||
|
@ -909,7 +909,7 @@ impl RustyPipe {
|
|||
version
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -925,7 +925,7 @@ impl RustyPipe {
|
|||
match deobf_data.get() {
|
||||
Some(deobf_data) => Ok(deobf_data.clone()),
|
||||
None => {
|
||||
log::debug!("getting deobf data");
|
||||
tracing::debug!("getting deobf data");
|
||||
|
||||
match DeobfData::extract(self.inner.http.clone(), self.inner.reporter.as_deref())
|
||||
.await
|
||||
|
@ -941,7 +941,7 @@ impl RustyPipe {
|
|||
// Try to fall back to expired cache data if available, otherwise return error
|
||||
match deobf_data.get_expired() {
|
||||
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())
|
||||
}
|
||||
None => Err(e),
|
||||
|
@ -963,7 +963,7 @@ impl RustyPipe {
|
|||
|
||||
match serde_json::to_string(&cdata) {
|
||||
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
|
||||
/// visitor data is extracted from the html page.
|
||||
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 vdata = resp
|
||||
|
@ -1284,6 +1284,7 @@ impl RustyPipeQuery {
|
|||
|
||||
let status = response.status();
|
||||
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 error_msg = serde_json::from_str::<response::ErrorResponse>(&body)
|
||||
|
@ -1314,9 +1315,11 @@ impl RustyPipeQuery {
|
|||
}
|
||||
};
|
||||
|
||||
tracing::debug!("mapped response");
|
||||
Ok(RequestResult { res, status, body })
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn yt_request<R: DeserializeOwned + MapResponse<M> + Debug, M>(
|
||||
&self,
|
||||
request: &Request,
|
||||
|
@ -1339,7 +1342,7 @@ impl RustyPipeQuery {
|
|||
|
||||
if n != self.client.inner.n_http_retries {
|
||||
let ms = util::retry_delay(n, 1000, 60000, 3);
|
||||
log::warn!(
|
||||
tracing::warn!(
|
||||
"Retry attempt #{}. Error: {}. Waiting {} ms",
|
||||
n + 1,
|
||||
err,
|
||||
|
@ -1380,7 +1383,7 @@ impl RustyPipeQuery {
|
|||
body: &B,
|
||||
deobf: Option<&DeobfData>,
|
||||
) -> Result<M, Error> {
|
||||
log::debug!("getting {}({})", operation, id);
|
||||
tracing::debug!("getting {}({})", operation, id);
|
||||
|
||||
let request = self
|
||||
.request_builder(ctype, endpoint)
|
||||
|
@ -1528,7 +1531,7 @@ trait MapResponse<T> {
|
|||
|
||||
fn validate_country(country: Country) -> Country {
|
||||
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
|
||||
} else {
|
||||
country
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::borrow::Cow;
|
|||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
client::response::url_endpoint::NavigationEndpoint,
|
||||
|
@ -29,7 +30,7 @@ impl RustyPipeQuery {
|
|||
let res = self._music_artist(artist_id, all_albums).await;
|
||||
|
||||
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
|
||||
} else {
|
||||
res
|
||||
|
|
|
@ -32,6 +32,7 @@ struct FormData {
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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> {
|
||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QCharts {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -38,7 +38,11 @@ struct QRadio<'a> {
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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 context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QMusicDetails {
|
||||
|
@ -62,7 +66,8 @@ impl RustyPipeQuery {
|
|||
/// Get the lyrics of a YouTube music track
|
||||
///
|
||||
/// 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 context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
@ -83,7 +88,11 @@ impl RustyPipeQuery {
|
|||
/// Get related items (tracks, playlists, artists) to a YouTube Music track
|
||||
///
|
||||
/// 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 context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
@ -104,7 +113,8 @@ impl RustyPipeQuery {
|
|||
/// 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.
|
||||
pub async fn music_radio<S: AsRef<str>>(
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn music_radio<S: AsRef<str> + Debug>(
|
||||
&self,
|
||||
radio_id: S,
|
||||
) -> Result<Paginator<TrackItem>, Error> {
|
||||
|
@ -133,7 +143,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
video_id: S,
|
||||
) -> Result<Paginator<TrackItem>, Error> {
|
||||
|
@ -142,7 +153,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
playlist_id: S,
|
||||
) -> Result<Paginator<TrackItem>, Error> {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
|
@ -13,6 +13,7 @@ use super::{
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// Get a list of moods and genres from YouTube Music
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn music_genres(&self) -> Result<Vec<MusicGenreItem>, Error> {
|
||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
@ -31,7 +32,11 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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 context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QBrowseParams {
|
||||
|
|
|
@ -10,6 +10,7 @@ use super::{response, ClientType, MapResponse, QBrowse, RustyPipeQuery};
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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> {
|
||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
@ -28,6 +29,7 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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> {
|
||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
|
@ -17,7 +17,8 @@ use super::{
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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,
|
||||
playlist_id: S,
|
||||
) -> Result<MusicPlaylist, Error> {
|
||||
|
@ -39,7 +40,11 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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 context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -48,7 +48,11 @@ enum Params {
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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 context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QSearch {
|
||||
|
@ -68,7 +72,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
query: S,
|
||||
) -> Result<MusicSearchFiltered<TrackItem>, Error> {
|
||||
|
@ -76,7 +81,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
query: S,
|
||||
) -> Result<MusicSearchFiltered<TrackItem>, Error> {
|
||||
|
@ -107,7 +113,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
query: S,
|
||||
) -> Result<MusicSearchFiltered<AlbumItem>, Error> {
|
||||
|
@ -130,10 +137,12 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
query: &str,
|
||||
query: S,
|
||||
) -> Result<MusicSearchFiltered<ArtistItem>, Error> {
|
||||
let query = query.as_ref();
|
||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QSearch {
|
||||
context,
|
||||
|
@ -154,7 +163,8 @@ impl RustyPipeQuery {
|
|||
///
|
||||
/// Playlists are filtered whether they are created by users
|
||||
/// (`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,
|
||||
query: S,
|
||||
community: bool,
|
||||
|
@ -182,7 +192,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
query: S,
|
||||
) -> Result<MusicSearchSuggestion, Error> {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use crate::error::{Error, ExtractionError};
|
||||
use crate::model::{
|
||||
paginator::{ContinuationEndpoint, Paginator},
|
||||
|
@ -11,7 +13,8 @@ use super::{response, ClientType, MapResponse, QContinuation, RustyPipeQuery};
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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,
|
||||
ctoken: S,
|
||||
endpoint: ContinuationEndpoint,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt::Debug,
|
||||
};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -61,7 +62,8 @@ struct QContentPlaybackContext<'a> {
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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 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
|
||||
pub async fn player_from_client<S: AsRef<str>>(
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn player_from_client<S: AsRef<str> + Debug>(
|
||||
&self,
|
||||
video_id: S,
|
||||
client_type: ClientType,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, convert::TryFrom};
|
||||
use std::{borrow::Cow, convert::TryFrom, fmt::Debug};
|
||||
|
||||
use time::OffsetDateTime;
|
||||
|
||||
|
@ -12,7 +12,8 @@ use super::{response, ClientType, MapResponse, MapResult, QBrowse, RustyPipeQuer
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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 context = self.get_context(ClientType::Desktop, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
|
@ -19,7 +21,8 @@ struct QSearch<'a> {
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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 context = self.get_context(ClientType::Desktop, true, None).await;
|
||||
let request_body = QSearch {
|
||||
|
@ -39,7 +42,8 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
query: S,
|
||||
filter: &SearchFilter,
|
||||
|
@ -63,7 +67,11 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// 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(
|
||||
"https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&xhr=t",
|
||||
&[
|
||||
|
|
|
@ -11,6 +11,7 @@ use super::{response, ClientType, MapResponse, QBrowse, QBrowseParams, RustyPipe
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// Get the videos from the YouTube startpage
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn startpage(&self) -> Result<Paginator<VideoItem>, Error> {
|
||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
||||
let request_body = QBrowse {
|
||||
|
@ -29,6 +30,7 @@ impl RustyPipeQuery {
|
|||
}
|
||||
|
||||
/// Get the videos from the YouTube trending page
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn trending(&self) -> Result<Vec<VideoItem>, Error> {
|
||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
||||
let request_body = QBrowseParams {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
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,
|
||||
url: S,
|
||||
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,
|
||||
s: S,
|
||||
resolve_albums: bool,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
|
@ -26,7 +28,11 @@ struct QVideo<'a> {
|
|||
|
||||
impl RustyPipeQuery {
|
||||
/// 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 context = self.get_context(ClientType::Desktop, true, None).await;
|
||||
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()`
|
||||
pub async fn video_comments<S: AsRef<str>>(
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn video_comments<S: AsRef<str> + Debug>(
|
||||
&self,
|
||||
ctoken: S,
|
||||
visitor_data: Option<&str>,
|
||||
|
|
|
@ -29,7 +29,7 @@ impl DeobfData {
|
|||
pub async fn extract(http: Client, reporter: Option<&dyn Reporter>) -> Result<Self, Error> {
|
||||
let js_url = get_player_js_url(&http).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);
|
||||
|
||||
|
@ -89,7 +89,7 @@ impl Deobfuscator {
|
|||
res.as_str().map_or(
|
||||
Err(DeobfError::Other("sig deobfuscation func returned null")),
|
||||
|res| {
|
||||
log::debug!("deobfuscated sig");
|
||||
tracing::debug!("deobfuscated sig");
|
||||
Ok(res.to_owned())
|
||||
},
|
||||
)
|
||||
|
@ -102,7 +102,7 @@ impl Deobfuscator {
|
|||
res.as_str().map_or(
|
||||
Err(DeobfError::Other("nsig deobfuscation func returned null")),
|
||||
|res| {
|
||||
log::debug!("deobfuscated nsig");
|
||||
tracing::debug!("deobfuscated nsig");
|
||||
Ok(res.to_owned())
|
||||
},
|
||||
)
|
||||
|
|
|
@ -23,9 +23,9 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::{macros::format_description, OffsetDateTime};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{deobfuscate::DeobfData, util};
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ use rustypipe::param::{
|
|||
#[case::tv_html5_embed(ClientType::TvHtml5Embed)]
|
||||
#[case::android(ClientType::Android)]
|
||||
#[case::ios(ClientType::Ios)]
|
||||
#[test_log::test]
|
||||
fn get_player_from_client(#[case] client_type: ClientType, rp: RustyPipe) {
|
||||
let player_data =
|
||||
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)]
|
||||
// querying Trailerpark's secondary YouTube channel should result in the YTM channel being fetched
|
||||
#[case::secondary_channel("no_more_albums", "UCC9192yGQD25eBZgFZ84MPw", true, 15, 0)]
|
||||
#[test_log::test]
|
||||
fn music_artist(
|
||||
#[case] name: &str,
|
||||
#[case] id: &str,
|
||||
|
@ -1491,12 +1489,17 @@ fn music_artist(
|
|||
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
|
||||
assert!(!artist.header_image.is_empty(), "got no header image");
|
||||
artist
|
||||
.tracks
|
||||
.iter()
|
||||
.for_each(|t| assert!(!t.cover.is_empty()));
|
||||
artist
|
||||
.albums
|
||||
.iter()
|
||||
|
|
Loading…
Reference in a new issue