From 52bddc774c373c1b110bdb0566475d1358a67d24 Mon Sep 17 00:00:00 2001 From: nick42d <133559267+nick42d@users.noreply.github.com> Date: Fri, 19 Jul 2024 22:36:09 +0800 Subject: [PATCH] feat! Move convenience functions behind feature gate and add documentation. Resolves #76 (#81) * Move simplified queries to own module * Cargo fix and one documentation change * Documentation - continued * Complete documentation for library * Additional documentation, and improve some of the simplified queries * Additional documentation * Add doctests to CI, fix a typo * Continue adding documentation * Continue adding documentation * Complete adding documentation BREAKING CHANGE: Simplified queries now feature gated (though enabled by default). Improved interface for some simplified queries - e.g get_library_artists. Removed impl for Lyrics. Fixed the AlbumID on SearchResultAlbum (was chowing ChannelID instead). YtMusic::process_json() moved out of impl to function. Removed erroneous public field from GetArtistAlbumsQuery. --- .github/workflows/rust.yml | 1 + README.md | 9 +- youtui/src/app/server/api.rs | 19 +- ytmapi-rs/Cargo.toml | 10 +- ytmapi-rs/src/auth/browser.rs | 9 + ytmapi-rs/src/auth/oauth.rs | 8 + ytmapi-rs/src/common.rs | 18 - ytmapi-rs/src/lib.rs | 400 +++------- ytmapi-rs/src/parse.rs | 10 +- ytmapi-rs/src/parse/artist.rs | 1 - ytmapi-rs/src/parse/library.rs | 11 +- ytmapi-rs/src/parse/search.rs | 2 +- ytmapi-rs/src/query.rs | 6 +- ytmapi-rs/src/query/artist.rs | 2 +- ytmapi-rs/src/query/playlist/create.rs | 10 +- ytmapi-rs/src/query/playlist/edit.rs | 27 +- ytmapi-rs/src/simplified_queries.rs | 683 ++++++++++++++++++ ytmapi-rs/src/utils.rs | 9 +- .../get_library_albums_20240701_output.txt | 6 +- ...y_artist_subscriptions_20240701_output.txt | 12 +- .../search_albums_20231226_output.txt | 42 +- ...sts_type_not_specified_20240612_output.txt | 8 +- ...odcasts_type_specified_20240612_output.txt | 8 +- ...highlighted_top_result_20240107_output.txt | 6 +- ytmapi-rs/tests/live_integration_tests.rs | 69 +- 25 files changed, 924 insertions(+), 462 deletions(-) create mode 100644 ytmapi-rs/src/simplified_queries.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fbc61065..fcb1e64e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -36,3 +36,4 @@ jobs: - name: Run tests run: | cargo test --verbose --bins --lib + cargo test --verbose --doc diff --git a/README.md b/README.md index 47abbe50..be8a74e0 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Chrome example (Select manually and paste): |Endpoint | Implemented: Query | Implemented: Continuations | |--- | --- | --- | |GetArtist | [x] || -|GetAlbum | [ ]* || +|GetAlbum | [x]* || |GetArtistAlbums | [x] || |Search | [x] |[ ]| |GetSearchSuggestions|[x]|| @@ -82,14 +82,14 @@ Chrome example (Select manually and paste): |GetMoodCategories|[ ]|| |GetMoodPlaylists|[ ]|| |GetCharts|[ ]|| -|GetWatchPlaylist|[ ]\*|[ ]| +|GetWatchPlaylist|[x]\*|[ ]| |GetLibraryPlaylists|[x]|[ ]| |GetLibrarySongs|[x]|[ ]| |GetLibraryAlbums|[x]|[ ]| |GetLibraryArtists|[x]|[ ]| |GetLibrarySubscriptions|[x]|[ ]| |GetLikedSongs|[ ]|| -|GetHistory|[x]|| +|GetHistory|[x]*|| |AddHistoryItem|[ ]|| |RemoveHistoryItem|[x]|| |RateSong|[x]|| @@ -117,6 +117,9 @@ Chrome example (Select manually and paste): \* get artist is partially implemented only - only returns albums and songs +\* get history is partially implemented only +- does not return a date, and remove from history feedback items are not generated. + # Additional information See the wiki for additional information https://github.com/nick42d/youtui/wiki diff --git a/youtui/src/app/server/api.rs b/youtui/src/app/server/api.rs index 49c38f5e..2c76a441 100644 --- a/youtui/src/app/server/api.rs +++ b/youtui/src/app/server/api.rs @@ -232,11 +232,7 @@ impl Api { // Should this be a ChannelID or BrowseID? Should take a trait?. // Should this actually take ChannelID::try_from(BrowseID::Artist) -> // ChannelID::Artist? - let artist = api - .get_artist(ytmapi_rs::query::GetArtistQuery::new( - ytmapi_rs::ChannelID::from_raw(browse_id.get_raw()), - )) - .await; + let artist = api.get_artist(browse_id).await; let artist = match artist { Ok(a) => a, Err(e) => { @@ -298,13 +294,7 @@ impl Api { unreachable!("Checked not none above") }; - let albums = match api - .get_artist_albums(ytmapi_rs::query::GetArtistAlbumsQuery::new( - ytmapi_rs::ChannelID::from_raw(temp_browse_id.get_raw()), - temp_params, - )) - .await - { + let albums = match api.get_artist_albums(temp_browse_id, temp_params).await { Ok(r) => r, Err(e) => { error!("Received error on get_artist_albums query \"{}\"", e); @@ -332,10 +322,7 @@ impl Api { "Spawning request for caller tracks for request ID {:?}", id ); - let album = match api - .get_album(ytmapi_rs::query::GetAlbumQuery::new(&b_id)) - .await - { + let album = match api.get_album(&b_id).await { Ok(album) => album, Err(e) => { error!("Error <{e}> getting album {:?}", b_id); diff --git a/ytmapi-rs/Cargo.toml b/ytmapi-rs/Cargo.toml index bc014148..49716e19 100644 --- a/ytmapi-rs/Cargo.toml +++ b/ytmapi-rs/Cargo.toml @@ -30,23 +30,25 @@ chrono = "0.4.38" rand = "0.8.5" [features] +default = ["default-tls", "simplified-queries"] # Provide alternative TLS options, but use reqwest's default by default. -# NOTE: To use an alternative TLS, you will need to specify default-features = false +# NOTE: To use an alternative TLS with the standard builders, +# you will need to specify default-features = false. # As reqwest preferentially uses default-tls when multiple TLS features are enabled. # See reqwest docs for more information. # https://docs.rs/reqwest/latest/reqwest/tls/index.html # TODO: Implement builder functions that allow us to ensure we use a specific TLS. -default = ["default-tls"] default-tls = ["reqwest/default-tls"] native-tls = ["reqwest/native-tls"] rustls-tls = ["reqwest/rustls-tls"] +# Enable the use of simplified queries such as YtMusic::search("xx") +simplified-queries = [] # Dev only section - [dev-dependencies] pretty_assertions = "1.4.0" -# docs.rs-specific configuration +# docs.rs-specific configuration required to enable nightly documentation features [package.metadata.docs.rs] # document all features all-features = true diff --git a/ytmapi-rs/src/auth/browser.rs b/ytmapi-rs/src/auth/browser.rs index 59229db0..72364b3a 100644 --- a/ytmapi-rs/src/auth/browser.rs +++ b/ytmapi-rs/src/auth/browser.rs @@ -11,6 +11,7 @@ use crate::{ use reqwest::Client; use serde::{Deserialize, Serialize}; use serde_json::json; +use std::fmt::Debug; use std::path::Path; #[derive(Clone, Serialize, Deserialize)] @@ -138,3 +139,11 @@ impl BrowserToken { BrowserToken::from_str(&contents, client).await } } + +// Don't use default Debug implementation for BrowserToken - contents are +// private +impl Debug for BrowserToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Private BrowserToken") + } +} diff --git a/ytmapi-rs/src/auth/oauth.rs b/ytmapi-rs/src/auth/oauth.rs index 0009ee72..119c9c03 100644 --- a/ytmapi-rs/src/auth/oauth.rs +++ b/ytmapi-rs/src/auth/oauth.rs @@ -260,3 +260,11 @@ impl OAuthTokenGenerator { serde_json::from_str(&result).map_err(|_| Error::response(&result)) } } +// Don't use default Debug implementation for BrowserToken - contents are +// private +// TODO: Display some fields, such as time. +impl std::fmt::Debug for OAuthToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Private BrowserToken") + } +} diff --git a/ytmapi-rs/src/common.rs b/ytmapi-rs/src/common.rs index a2d627b2..5388c906 100644 --- a/ytmapi-rs/src/common.rs +++ b/ytmapi-rs/src/common.rs @@ -155,13 +155,6 @@ impl_youtube_id!(FeedbackTokenAddToLibrary<'a>); impl<'a> BrowseID<'a> for PlaylistID<'a> {} impl<'a> BrowseID<'a> for ChannelID<'a> {} -impl<'a> From<&'a AlbumID<'a>> for AlbumID<'a> { - fn from(value: &'a AlbumID<'a>) -> Self { - let core = &value.0; - AlbumID(core.as_ref().into()) - } -} - impl<'a> BrowseParams<'a> { pub fn from_raw(raw_str: S) -> BrowseParams<'a> where @@ -244,15 +237,4 @@ pub mod browsing { pub lyrics: String, pub source: String, } - impl Lyrics { - pub fn get_lyrics(&self) -> &str { - self.lyrics.as_str() - } - pub fn get_source(&self) -> &str { - self.source.as_str() - } - pub fn new(lyrics: String, source: String) -> Self { - Self { lyrics, source } - } - } } diff --git a/ytmapi-rs/src/lib.rs b/ytmapi-rs/src/lib.rs index da0d76fe..7fa7ac28 100644 --- a/ytmapi-rs/src/lib.rs +++ b/ytmapi-rs/src/lib.rs @@ -18,7 +18,7 @@ //! #[tokio::main] //! pub async fn main() -> Result<(), ytmapi_rs::Error> { //! let (code, url) = ytmapi_rs::generate_oauth_code_and_url().await?; -//! println!("Go to {url}, fhe login flow, and press enter when done"); +//! println!("Go to {url}, finish the login flow, and press enter when done"); //! let mut _buf = String::new(); //! let _ = std::io::stdin().read_line(&mut _buf); //! let token = ytmapi_rs::generate_oauth_token(code).await?; @@ -53,44 +53,13 @@ compile_error!("One of the TLS features must be enabled for this crate"); use auth::{ browser::BrowserToken, oauth::OAuthDeviceCode, AuthToken, OAuthToken, OAuthTokenGenerator, }; -use common::{ - browsing::Lyrics, - library::{LibraryArtist, Playlist}, - watch::WatchPlaylist, - FeedbackTokenRemoveFromHistory, PlaylistID, SearchSuggestion, UploadAlbumID, UploadArtistID, -}; pub use common::{Album, BrowseID, ChannelID, Thumbnail, VideoID}; pub use error::{Error, Result}; -use parse::{ - AddPlaylistItem, AlbumParams, ApiSuccess, ArtistParams, GetLibraryArtistSubscription, - GetPlaylist, LikeStatus, ParseFrom, ProcessedResult, SearchResultAlbum, SearchResultArtist, - SearchResultEpisode, SearchResultFeaturedPlaylist, SearchResultPlaylist, SearchResultPodcast, - SearchResultProfile, SearchResultSong, SearchResultVideo, SearchResults, TableListItem, - TableListSong, -}; +use parse::{ParseFrom, ProcessedResult}; pub use process::RawResult; -use query::{ - filteredsearch::{ - AlbumsFilter, ArtistsFilter, CommunityPlaylistsFilter, EpisodesFilter, - FeaturedPlaylistsFilter, FilteredSearch, PlaylistsFilter, PodcastsFilter, ProfilesFilter, - SongsFilter, VideosFilter, - }, - lyrics::GetLyricsQuery, - rate::{RatePlaylistQuery, RateSongQuery}, - watch::GetWatchPlaylistQuery, - AddPlaylistItemsQuery, AddVideosToPlaylist, BasicSearch, CreatePlaylistQuery, - CreatePlaylistType, DeletePlaylistQuery, EditPlaylistQuery, EditSongLibraryStatusQuery, - GetAlbumQuery, GetArtistAlbumsQuery, GetArtistQuery, GetHistoryQuery, GetLibraryAlbumsQuery, - GetLibraryArtistSubscriptionsQuery, GetLibraryArtistsQuery, GetLibraryPlaylistsQuery, - GetLibrarySongsQuery, GetLibraryUploadAlbumQuery, GetLibraryUploadAlbumsQuery, - GetLibraryUploadArtistQuery, GetLibraryUploadArtistsQuery, GetLibraryUploadSongsQuery, - GetPlaylistQuery, GetSearchSuggestionsQuery, Query, RemoveHistoryItemsQuery, - RemovePlaylistItemsQuery, SearchQuery, -}; +use query::Query; use reqwest::Client; -use std::{path::Path}; - -use crate::{common::UploadEntityID, query::DeleteUploadEntityQuery}; +use std::path::Path; // TODO: Confirm if auth should be pub pub mod auth; @@ -105,6 +74,9 @@ pub mod error; pub mod parse; mod process; pub mod query; +#[cfg(feature = "simplified-queries")] +#[cfg_attr(docsrs, doc(cfg(feature = "simplified-queries")))] +pub mod simplified_queries; #[cfg(test)] mod tests; @@ -113,6 +85,10 @@ mod tests; /// A handle to the YouTube Music API, wrapping a reqwest::Client. /// Generic over AuthToken, as different AuthTokens may allow different queries /// to be executed. +/// # Documentation note +/// Examples given for methods on this struct will use fake or mock +/// constructors. When using in a real environment, you will need to construct +/// using a real token or cookie. pub struct YtMusic { // TODO: add language // TODO: add location @@ -302,293 +278,96 @@ impl YtMusic { } } impl YtMusic { - //TODO: Usage examples /// Return a raw result from YouTube music for query Q that requires further /// processing. + /// # Usage + /// ```no_run + /// use ytmapi_rs::parse::ParseFrom; + /// use ytmapi_rs::auth::BrowserToken; + /// + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await?; + /// let query = ytmapi_rs::query::SearchQuery::new("Beatles") + /// .with_filter(ytmapi_rs::query::ArtistsFilter); + /// let raw_result = yt.raw_query(query).await?; + /// let result = + /// as ParseFrom<_,BrowserToken>>::parse_from(raw_result.process()?)?; + /// assert_eq!(result[0].artist, "The Beatles"); + /// # Ok::<(), ytmapi_rs::Error>(()) + /// # }; + /// ``` pub async fn raw_query>(&self, query: Q) -> Result> { // TODO: Check for a response the reflects an expired Headers token self.token.raw_query(&self.client, query).await } /// Return a result from YouTube music that has had errors removed and been /// processed into parsable JSON. + /// # Usage + /// ```no_run + /// use ytmapi_rs::parse::ParseFrom; + /// use ytmapi_rs::auth::BrowserToken; + /// + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await?; + /// let query = ytmapi_rs::query::SearchQuery::new("Beatles") + /// .with_filter(ytmapi_rs::query::ArtistsFilter); + /// let processed_result = yt.processed_query(query).await?; + /// let result = + /// as ParseFrom<_,BrowserToken>>::parse_from(processed_result)?; + /// assert_eq!(result[0].artist, "The Beatles"); + /// # Ok::<(), ytmapi_rs::Error>(()) + /// # }; + /// ``` pub async fn processed_query>(&self, query: Q) -> Result> { // TODO: Check for a response the reflects an expired Headers token self.token.raw_query(&self.client, query).await?.process() } /// Return the raw JSON returned by YouTube music for Query Q. + /// Return a result from YouTube music that has had errors removed and been + /// processed into parsable JSON. + /// # Usage + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await?; + /// let query = ytmapi_rs::query::SearchQuery::new("Beatles") + /// .with_filter(ytmapi_rs::query::ArtistsFilter); + /// let json_string = yt.json_query(query).await?; + /// assert!(serde_json::from_str::(&json_string).is_ok()); + /// # Ok::<(), ytmapi_rs::Error>(()) + /// # }; + /// ``` pub async fn json_query>(&self, query: Q) -> Result { // TODO: Remove allocation let json = self.raw_query(query).await?.process()?.clone_json(); Ok(json) } + /// Return a result from YouTube music that has had errors removed and been + /// processed into parsable JSON. + /// # Usage + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("").await?; + /// let query = ytmapi_rs::query::SearchQuery::new("Beatles") + /// .with_filter(ytmapi_rs::query::ArtistsFilter); + /// let result = yt.query(query).await?; + /// assert_eq!(result[0].artist, "The Beatles"); + /// # Ok::<(), ytmapi_rs::Error>(()) + /// # }; + /// ``` pub async fn query>(&self, query: Q) -> Result { query.call(self).await } - /// API Search Query that returns results for each category if available. - pub async fn search<'a, Q: Into>>( - &self, - query: Q, - ) -> Result { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Artists only. - pub async fn search_artists<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Albums only. - pub async fn search_albums<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Songs only. - pub async fn search_songs<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Playlists only. - pub async fn search_playlists<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Community Playlists only. - pub async fn search_community_playlists< - 'a, - Q: Into>>, - >( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Featured Playlists only. - pub async fn search_featured_playlists< - 'a, - Q: Into>>, - >( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Episodes only. - pub async fn search_episodes<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Podcasts only. - pub async fn search_podcasts<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Videos only. - pub async fn search_videos<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - /// API Search Query for Profiles only. - pub async fn search_profiles<'a, Q: Into>>>( - &self, - query: Q, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - pub async fn get_artist(&self, query: GetArtistQuery<'_>) -> Result { - query.call(self).await - } - pub async fn get_artist_albums(&self, query: GetArtistAlbumsQuery<'_>) -> Result> { - query.call(self).await - } - pub async fn get_album(&self, query: GetAlbumQuery<'_>) -> Result { - query.call(self).await - } - pub async fn get_lyrics(&self, query: GetLyricsQuery<'_>) -> Result { - query.call(self).await - } - // TODO: Implement for other cases of query. - pub async fn get_watch_playlist<'a, S: Into>>>( - &self, - query: S, - ) -> Result { - let query = query.into(); - query.call(self).await - } - // TODO: Implement for other cases of query. - pub async fn get_playlist<'a, S: Into>>( - &self, - query: S, - ) -> Result { - let query = query.into(); - query.call(self).await - } - pub async fn get_search_suggestions<'a, S: Into>>( - &self, - query: S, - ) -> Result> { - let query = query.into(); - query.call(self).await - } - pub async fn get_library_playlists(&self) -> Result> { - // TODO: investigate why returning empty array - let query = GetLibraryPlaylistsQuery; - query.call(self).await - } - pub async fn get_library_artists( - // TODO: investigate why returning empty array - // TODO: Better constructor for query - &self, - query: GetLibraryArtistsQuery, - ) -> Result> { - query.call(self).await - } - pub async fn get_library_songs( - &self, - query: GetLibrarySongsQuery, - ) -> Result> { - query.call(self).await - } - pub async fn get_library_albums( - &self, - query: GetLibraryAlbumsQuery, - ) -> Result> { - query.call(self).await - } - pub async fn get_library_artist_subscriptions( - &self, - query: GetLibraryArtistSubscriptionsQuery, - ) -> Result> { - query.call(self).await - } - pub async fn get_history(&self) -> Result> { - let query = GetHistoryQuery; - query.call(self).await - } - pub async fn remove_history_items<'a>( - &self, - feedback_tokens: Vec>, - ) -> Result>> { - let query = RemoveHistoryItemsQuery::new(feedback_tokens); - query.call(self).await - } - pub async fn edit_song_library_status<'a>( - &self, - query: EditSongLibraryStatusQuery<'a>, - ) -> Result>> { - query.call(self).await - } - pub async fn rate_song(&self, video_id: VideoID<'_>, rating: LikeStatus) -> Result { - let query = RateSongQuery::new(video_id, rating); - query.call(self).await - } - pub async fn rate_playlist( - &self, - playlist_id: PlaylistID<'_>, - rating: LikeStatus, - ) -> Result { - let query = RatePlaylistQuery::new(playlist_id, rating); - query.call(self).await - } - pub async fn delete_playlist<'a, Q: Into>>( - &self, - query: Q, - ) -> Result { - query.into().call(self).await - } - pub async fn create_playlist<'a, Q: Into>, C: CreatePlaylistType>( - &self, - query: Q, - ) -> Result> { - query.into().call(self).await - } - pub async fn remove_playlist_items<'a, Q: Into>>( - &self, - query: Q, - ) -> Result { - query.into().call(self).await - } - pub async fn add_playlist_video_items< - 'a, - Q: Into>>, - >( - &self, - query: Q, - ) -> Result> { - query.into().call(self).await - } - pub async fn edit_playlist<'a, Q: Into>>( - &self, - query: Q, - ) -> Result { - query.into().call(self).await - } - pub async fn get_library_upload_songs( - &self, - ) -> Result<>::Output> { - let query = GetLibraryUploadSongsQuery::default(); - query.call(self).await - } - pub async fn get_library_upload_artists( - &self, - ) -> Result<>::Output> { - let query = GetLibraryUploadArtistsQuery::default(); - query.call(self).await - } - pub async fn get_library_upload_albums( - &self, - ) -> Result<>::Output> { - let query = GetLibraryUploadAlbumsQuery::default(); - query.call(self).await - } - pub async fn get_library_upload_album( - &self, - upload_album_id: UploadAlbumID<'_>, - ) -> Result<>::Output> { - let query = GetLibraryUploadAlbumQuery::new(upload_album_id); - query.call(self).await - } - pub async fn get_library_upload_artist( - &self, - upload_artist_id: UploadArtistID<'_>, - ) -> Result<>::Output> { - let query = GetLibraryUploadArtistQuery::new(upload_artist_id); - query.call(self).await - } - pub async fn delete_upload_entity( - &self, - upload_entity_id: UploadEntityID<'_>, - ) -> Result<>::Output> { - let query = DeleteUploadEntityQuery::new(upload_entity_id); - query.call(self).await - } } // TODO: Keep session alive after calling these methods. /// Generates a tuple containing fresh OAuthDeviceCode and corresponding url for /// you to authenticate yourself at. (OAuthDeviceCode, URL) +/// # Usage +/// ```no_run +/// # async { +/// let (code, url) = ytmapi_rs::generate_oauth_code_and_url().await?; +/// # Ok::<(), ytmapi_rs::Error>(()) +/// # }; +/// ``` pub async fn generate_oauth_code_and_url() -> Result<(OAuthDeviceCode, String)> { let client = Client::new(); let code = OAuthTokenGenerator::new(&client).await?; @@ -597,12 +376,32 @@ pub async fn generate_oauth_code_and_url() -> Result<(OAuthDeviceCode, String)> } // TODO: Keep session alive after calling these methods. /// Generates an OAuth Token when given an OAuthDeviceCode. +/// # Usage +/// ```no_run +/// # async { +/// let (code, url) = ytmapi_rs::generate_oauth_code_and_url().await?; +/// println!("Go to {url}, finish the login flow, and press enter when done"); +/// let mut buf = String::new(); +/// let _ = std::io::stdin().read_line(&mut buf); +/// let token = ytmapi_rs::generate_oauth_token(code).await; +/// assert!(token.is_ok()); +/// # Ok::<(), ytmapi_rs::Error>(()) +/// # }; +/// ``` pub async fn generate_oauth_token(code: OAuthDeviceCode) -> Result { let client = Client::new(); OAuthToken::from_code(&client, code).await } // TODO: Keep session alive after calling these methods. /// Generates a Browser Token when given a browser cookie. +/// # Usage +/// ```no_run +/// # async { +/// let cookie = "FAKE COOKIE"; +/// let token = ytmapi_rs::generate_browser_token(cookie).await; +/// assert!(matches!(token.unwrap_err().into_kind(),ytmapi_rs::error::ErrorKind::Header)); +/// # }; +/// ``` pub async fn generate_browser_token>(cookie: S) -> Result { let client = Client::new(); BrowserToken::from_str(cookie.as_ref(), &client).await @@ -610,6 +409,13 @@ pub async fn generate_browser_token>(cookie: S) -> Result(json, query); +/// assert!(result.is_err()); +/// ``` pub fn process_json, A: AuthToken>(json: String, query: Q) -> Result { Q::Output::parse_from(RawResult::from_raw(json, query).process()?) } diff --git a/ytmapi-rs/src/parse.rs b/ytmapi-rs/src/parse.rs index 6f8d48df..594cfc6c 100644 --- a/ytmapi-rs/src/parse.rs +++ b/ytmapi-rs/src/parse.rs @@ -231,7 +231,7 @@ pub struct SearchResultAlbum { pub artist: String, pub year: String, pub explicit: Explicit, - pub browse_id: ChannelID<'static>, + pub album_id: AlbumID<'static>, pub album_type: AlbumType, pub thumbnails: Vec, } @@ -386,10 +386,10 @@ mod lyrics { SECTION_LIST_ITEM, DESCRIPTION_SHELF ))?; - Ok(Lyrics::new( - description_shelf.take_value_pointer(DESCRIPTION)?, - description_shelf.take_value_pointer(concatcp!("/footer", RUN_TEXT))?, - )) + Ok(Lyrics { + lyrics: description_shelf.take_value_pointer(DESCRIPTION)?, + source: description_shelf.take_value_pointer(concatcp!("/footer", RUN_TEXT))?, + }) } } diff --git a/ytmapi-rs/src/parse/artist.rs b/ytmapi-rs/src/parse/artist.rs index 56b17a38..ba2298cb 100644 --- a/ytmapi-rs/src/parse/artist.rs +++ b/ytmapi-rs/src/parse/artist.rs @@ -640,7 +640,6 @@ mod tests { query::GetArtistAlbumsQuery, ChannelID, }; - #[tokio::test] async fn test_get_artist_albums_query() { diff --git a/ytmapi-rs/src/parse/library.rs b/ytmapi-rs/src/parse/library.rs index ac41cf37..3336ab77 100644 --- a/ytmapi-rs/src/parse/library.rs +++ b/ytmapi-rs/src/parse/library.rs @@ -18,7 +18,7 @@ use crate::query::{ EditSongLibraryStatusQuery, GetLibraryAlbumsQuery, GetLibraryArtistSubscriptionsQuery, GetLibraryArtistsQuery, GetLibraryPlaylistsQuery, GetLibrarySongsQuery, }; -use crate::{Error, Result, Thumbnail}; +use crate::{ChannelID, Error, Result, Thumbnail}; use const_format::concatcp; #[derive(Debug)] @@ -26,7 +26,7 @@ use const_format::concatcp; pub struct GetLibraryArtistSubscription { pub name: String, pub subscribers: String, - pub channel_id: String, + pub channel_id: ChannelID<'static>, pub thumbnails: Vec, } @@ -234,7 +234,7 @@ fn parse_item_list_album(mut json_crawler: JsonCrawler) -> Result Result GetWatchPlaylistQuery> { - pub fn new_from_video_id(id: VideoID<'a>) -> GetWatchPlaylistQuery> { - GetWatchPlaylistQuery { id } + pub fn new_from_video_id>>( + id: T, + ) -> GetWatchPlaylistQuery> { + GetWatchPlaylistQuery { id: id.into() } } pub fn with_playlist_id( self, diff --git a/ytmapi-rs/src/query/artist.rs b/ytmapi-rs/src/query/artist.rs index db6b5843..4b62dce3 100644 --- a/ytmapi-rs/src/query/artist.rs +++ b/ytmapi-rs/src/query/artist.rs @@ -16,7 +16,7 @@ pub struct GetArtistQuery<'a> { #[derive(Debug)] pub struct GetArtistAlbumsQuery<'a> { channel_id: ChannelID<'a>, - pub params: BrowseParams<'a>, + params: BrowseParams<'a>, } impl<'a> GetArtistQuery<'a> { pub fn new(channel_id: ChannelID<'a>) -> GetArtistQuery<'a> { diff --git a/ytmapi-rs/src/query/playlist/create.rs b/ytmapi-rs/src/query/playlist/create.rs index a91e562f..5ef1c95e 100644 --- a/ytmapi-rs/src/query/playlist/create.rs +++ b/ytmapi-rs/src/query/playlist/create.rs @@ -60,10 +60,10 @@ impl<'a> CreatePlaylistQuery<'a, BasicCreatePlaylist> { } impl<'a> CreatePlaylistQuery<'a, BasicCreatePlaylist> { - pub fn with_source( + pub fn with_source>>( self, - source_playlist: PlaylistID<'a>, - ) -> CreatePlaylistQuery<'a, CreatePlaylistFromPlaylist> { + source_playlist: T, + ) -> CreatePlaylistQuery<'a, CreatePlaylistFromPlaylist<'a>> { let CreatePlaylistQuery { title, description, @@ -74,7 +74,9 @@ impl<'a> CreatePlaylistQuery<'a, BasicCreatePlaylist> { title, description, privacy_status, - query_type: CreatePlaylistFromPlaylist { source_playlist }, + query_type: CreatePlaylistFromPlaylist { + source_playlist: source_playlist.into(), + }, } } } diff --git a/ytmapi-rs/src/query/playlist/edit.rs b/ytmapi-rs/src/query/playlist/edit.rs index 5c34c072..a925045b 100644 --- a/ytmapi-rs/src/query/playlist/edit.rs +++ b/ytmapi-rs/src/query/playlist/edit.rs @@ -8,7 +8,6 @@ use crate::{ use serde_json::json; use std::borrow::Cow; -// Is this really a query? It's more of an action/command. // TODO: Confirm if all options can be passed - or mutually exclusive. pub struct EditPlaylistQuery<'a> { id: PlaylistID<'a>, @@ -28,7 +27,8 @@ pub enum AddOrder { } impl<'a> EditPlaylistQuery<'a> { - pub fn new_title>>(id: PlaylistID<'a>, new_title: S) -> Self { + pub fn new_title>, S: Into>>(id: T, new_title: S) -> Self { + let id = id.into(); Self { id, new_title: Some(new_title.into()), @@ -39,7 +39,11 @@ impl<'a> EditPlaylistQuery<'a> { add_playlist: None, } } - pub fn new_description>>(id: PlaylistID<'a>, new_description: S) -> Self { + pub fn new_description>, S: Into>>( + id: T, + new_description: S, + ) -> Self { + let id = id.into(); Self { id, new_title: None, @@ -50,7 +54,11 @@ impl<'a> EditPlaylistQuery<'a> { add_playlist: None, } } - pub fn new_privacy_status(id: PlaylistID<'a>, new_privacy_status: PrivacyStatus) -> Self { + pub fn new_privacy_status>>( + id: T, + new_privacy_status: PrivacyStatus, + ) -> Self { + let id = id.into(); Self { id, new_title: None, @@ -61,11 +69,12 @@ impl<'a> EditPlaylistQuery<'a> { add_playlist: None, } } - pub fn swap_videos_order( - id: PlaylistID<'a>, + pub fn swap_videos_order>>( + id: T, video_1: SetVideoID<'a>, video_2: SetVideoID<'a>, ) -> Self { + let id = id.into(); Self { id, new_title: None, @@ -76,7 +85,8 @@ impl<'a> EditPlaylistQuery<'a> { add_playlist: None, } } - pub fn change_add_order(id: PlaylistID<'a>, change_add_order: AddOrder) -> Self { + pub fn change_add_order>>(id: T, change_add_order: AddOrder) -> Self { + let id = id.into(); Self { id, new_title: None, @@ -87,7 +97,8 @@ impl<'a> EditPlaylistQuery<'a> { add_playlist: None, } } - pub fn add_playlist(id: PlaylistID<'a>, add_playlist: PlaylistID<'a>) -> Self { + pub fn add_playlist>>(id: T, add_playlist: PlaylistID<'a>) -> Self { + let id = id.into(); Self { id, new_title: None, diff --git a/ytmapi-rs/src/simplified_queries.rs b/ytmapi-rs/src/simplified_queries.rs new file mode 100644 index 00000000..dee0a3bb --- /dev/null +++ b/ytmapi-rs/src/simplified_queries.rs @@ -0,0 +1,683 @@ +//! This module contains the implementation for more convenient ways to call the +//! API, in many cases without the need of building Query structs. +//! # Optional +//! To enable this module, feature `simplified-queries` must be enabled (enabled +//! by default) +use crate::auth::AuthToken; +use crate::common::{ + browsing::Lyrics, + library::{LibraryArtist, Playlist}, + watch::WatchPlaylist, + FeedbackTokenRemoveFromHistory, PlaylistID, SearchSuggestion, UploadAlbumID, UploadArtistID, +}; +use crate::common::{AlbumID, BrowseParams, LyricsID, SetVideoID}; +use crate::parse::{ + AddPlaylistItem, AlbumParams, ApiSuccess, ArtistParams, GetArtistAlbums, + GetLibraryArtistSubscription, GetPlaylist, LikeStatus, SearchResultAlbum, SearchResultArtist, + SearchResultEpisode, SearchResultFeaturedPlaylist, SearchResultPlaylist, SearchResultPodcast, + SearchResultProfile, SearchResultSong, SearchResultVideo, SearchResults, TableListItem, + TableListSong, +}; +use crate::process::RawResult; +use crate::query::DuplicateHandlingMode; +use crate::query::{ + filteredsearch::{ + AlbumsFilter, ArtistsFilter, CommunityPlaylistsFilter, EpisodesFilter, + FeaturedPlaylistsFilter, FilteredSearch, PlaylistsFilter, PodcastsFilter, ProfilesFilter, + SongsFilter, VideosFilter, + }, + lyrics::GetLyricsQuery, + rate::{RatePlaylistQuery, RateSongQuery}, + watch::GetWatchPlaylistQuery, + AddPlaylistItemsQuery, AddVideosToPlaylist, BasicSearch, CreatePlaylistQuery, + CreatePlaylistType, DeletePlaylistQuery, EditPlaylistQuery, EditSongLibraryStatusQuery, + GetAlbumQuery, GetArtistAlbumsQuery, GetArtistQuery, GetHistoryQuery, GetLibraryAlbumsQuery, + GetLibraryArtistSubscriptionsQuery, GetLibraryArtistsQuery, GetLibraryPlaylistsQuery, + GetLibrarySongsQuery, GetLibraryUploadAlbumQuery, GetLibraryUploadAlbumsQuery, + GetLibraryUploadArtistQuery, GetLibraryUploadArtistsQuery, GetLibraryUploadSongsQuery, + GetPlaylistQuery, GetSearchSuggestionsQuery, Query, RemoveHistoryItemsQuery, + RemovePlaylistItemsQuery, SearchQuery, +}; +use crate::{common::UploadEntityID, query::DeleteUploadEntityQuery}; +use crate::{Album, ChannelID, Result, VideoID, YtMusic}; + +impl YtMusic { + /// API Search Query that returns results for each category if available. + /// # Usage + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search("Beatles").await + /// # }; + pub async fn search<'a, Q: Into>>( + &self, + query: Q, + ) -> Result { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Artists only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_artists("Beatles").await + /// # }; + pub async fn search_artists<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Albums only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_albums("Beatles").await + /// # }; + pub async fn search_albums<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Songs only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_songs("Beatles").await + /// # }; + pub async fn search_songs<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Playlists only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_playlists("Beatles").await + /// # }; + pub async fn search_playlists<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Community Playlists only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_community_playlists("Beatles").await + /// # }; + pub async fn search_community_playlists< + 'a, + Q: Into>>, + >( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Featured Playlists only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_featured_playlists("Beatles").await + /// # }; + pub async fn search_featured_playlists< + 'a, + Q: Into>>, + >( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Episodes only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_episodes("Beatles").await + /// # }; + pub async fn search_episodes<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Podcasts only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_podcasts("Beatles").await + /// # }; + pub async fn search_podcasts<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Videos only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_videos("Beatles").await + /// # }; + pub async fn search_videos<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// API Search Query for Profiles only. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.search_profiles("Beatles").await + /// # }; + pub async fn search_profiles<'a, Q: Into>>>( + &self, + query: Q, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// Gets information about an artist and their top releases. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_artists("Beatles").await.unwrap(); + /// yt.get_artist(&results[0].browse_id).await + /// # }; + pub async fn get_artist<'a, T: Into>>( + &self, + channel_id: T, + ) -> Result { + let query = GetArtistQuery::new(channel_id.into()); + query.call(self).await + } + /// Gets a full list albums for an artist. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_artists("Beatles").await.unwrap(); + /// let artist_top_albums = yt.get_artist(&results[0].browse_id).await.unwrap().top_releases.albums.unwrap(); + /// yt.get_artist_albums( + /// artist_top_albums.browse_id.unwrap(), + /// artist_top_albums.params.unwrap(), + /// ).await + /// # }; + pub async fn get_artist_albums<'a, T: Into>, U: Into>>( + &self, + channel_id: T, + browse_params: U, + ) -> Result> { + let query = GetArtistAlbumsQuery::new(channel_id.into(), browse_params.into()); + query.call(self).await + } + /// Gets information about an album and its tracks. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_albums("Dark Side Of The Moon").await.unwrap(); + /// yt.get_album(&results[0].album_id).await + /// # }; + pub async fn get_album<'a, T: Into>>(&self, album_id: T) -> Result { + let query = GetAlbumQuery::new(album_id); + query.call(self).await + } + /// Gets the information that's available when playing a song or playlist; + /// upcoming tracks and lyrics. + /// # Partially implemented + /// Tracks are not implemented - empty vector always returned. + /// See [`GetWatchPlaylistQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run + /// a GetWatchPlaylistQuery. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetWatchPlaylistQuery]: crate::query::watch::GetWatchPlaylistQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_songs("While My Guitar Gently Weeps").await.unwrap(); + /// yt.get_watch_playlist_from_video_id(&results[0].video_id).await + /// # }; + // NOTE: Could be generic across PlaylistID or VideoID using + // Into + pub async fn get_watch_playlist_from_video_id<'a, S: Into>>( + &self, + video_id: S, + ) -> Result { + let query = GetWatchPlaylistQuery::new_from_video_id(video_id.into()); + query.call(self).await + } + /// Gets song lyrics and the source. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_songs("While My Guitar Gently Weeps").await.unwrap(); + /// let watch_playlist = yt.get_watch_playlist_from_video_id(&results[0].video_id).await.unwrap(); + /// yt.get_lyrics(watch_playlist.lyrics_id).await + /// # }; + pub async fn get_lyrics<'a, T: Into>>(&self, lyrics_id: T) -> Result { + let query = GetLyricsQuery::new(lyrics_id.into()); + query.call(self).await + } + /// Gets information about a playlist and its tracks. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_featured_playlists("Heavy metal").await.unwrap(); + /// yt.get_playlist(&results[0].playlist_id).await + /// # }; + pub async fn get_playlist<'a, T: Into>>( + &self, + playlist_id: T, + ) -> Result { + let query = GetPlaylistQuery::new(playlist_id.into()); + query.call(self).await + } + /// Gets search suggestions + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.get_search_suggestions("The Beat").await; + /// # }; + pub async fn get_search_suggestions<'a, S: Into>>( + &self, + query: S, + ) -> Result> { + let query = query.into(); + query.call(self).await + } + /// Gets a list of all playlists in your Library. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.get_library_playlists().await; + /// # }; + pub async fn get_library_playlists(&self) -> Result> { + let query = GetLibraryPlaylistsQuery; + query.call(self).await + } + /// Gets a list of all artists in your Library. + /// # Additional functionality + /// See [`GetLibraryArtistsQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetLibraryArtistsQuery]: crate::query::GetLibraryArtistsQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.get_library_artists().await; + /// # }; + pub async fn get_library_artists(&self) -> Result> { + let query = GetLibraryArtistsQuery::default(); + query.call(self).await + } + /// Gets a list of all songs in your Library. + /// # Additional functionality + /// See [`GetLibrarySongsQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetLibrarySongsQuery]: crate::query::GetLibrarySongsQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.get_library_songs().await; + /// # }; + pub async fn get_library_songs(&self) -> Result> { + let query = GetLibrarySongsQuery::default(); + query.call(self).await + } + /// Gets a list of all albums in your Library. + /// # Additional functionality + /// See [`GetLibraryAlbumsQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetLibraryAlbumsQuery]: crate::query::GetLibraryAlbumsQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.get_library_albums().await; + /// # }; + pub async fn get_library_albums(&self) -> Result> { + let query = GetLibraryAlbumsQuery::default(); + query.call(self).await + } + /// Gets a list of all artist subscriptions in your Library. + /// # Additional functionality + /// See [`GetLibraryArtistSubscriptionsQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetLibraryArtistSubscriptionsQuery]: crate::query::GetLibraryArtistSubscriptionsQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.get_library_artist_subscriptions().await; + /// # }; + pub async fn get_library_artist_subscriptions( + &self, + ) -> Result> { + let query = GetLibraryArtistSubscriptionsQuery::default(); + query.call(self).await + } + /// Gets your recently played history. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.get_history().await; + /// # }; + pub async fn get_history(&self) -> Result> { + let query = GetHistoryQuery; + query.call(self).await + } + /// Removes a list of items from your recently played history. + /// # Note + /// The feedback tokens required to call this query are currently not + /// generated by `get_history()`. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let history = yt.get_history().await.unwrap(); + /// todo!("FeedbackTokenRemoveFromHistory are not able to be accessed from history currently") + /// # }; + pub async fn remove_history_items<'a>( + &self, + feedback_tokens: Vec>, + ) -> Result>> { + let query = RemoveHistoryItemsQuery::new(feedback_tokens); + query.call(self).await + } + // TODO: Docs / alternative constructors. + pub async fn edit_song_library_status<'a>( + &self, + query: EditSongLibraryStatusQuery<'a>, + ) -> Result>> { + query.call(self).await + } + /// Sets the like status for a song. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_songs("While My Guitar Gently Weeps").await.unwrap(); + /// yt.rate_song(&results[0].video_id, ytmapi_rs::parse::LikeStatus::Liked).await + /// # }; + pub async fn rate_song<'a, T: Into>>( + &self, + video_id: T, + rating: LikeStatus, + ) -> Result { + let query = RateSongQuery::new(video_id.into(), rating); + query.call(self).await + } + /// Sets the like status for a playlist. + /// A 'Liked' playlist will be added to your library, an 'Indifferent' will + /// be removed, and a 'Disliked' will reduce the chance of it appearing in + /// your recommendations. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.search_featured_playlists("Heavy metal") + /// .await + /// .unwrap(); + /// yt.rate_playlist( + /// &results[0].playlist_id, + /// ytmapi_rs::parse::LikeStatus::Liked, + /// ).await + /// # }; + pub async fn rate_playlist<'a, T: Into>>( + &self, + playlist_id: T, + rating: LikeStatus, + ) -> Result { + let query = RatePlaylistQuery::new(playlist_id.into(), rating); + query.call(self).await + } + /// Deletes a playlist you own. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let results = yt.get_library_playlists().await.unwrap(); + /// yt.delete_playlist(&results[0].playlist_id).await + /// # }; + pub async fn delete_playlist<'a, T: Into>>( + &self, + playlist_id: T, + ) -> Result { + let query = DeletePlaylistQuery::new(playlist_id.into()); + query.call(self).await + } + /// Creates a new playlist. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let playlists = yt.search_featured_playlists("Heavy metal") + /// .await + /// .unwrap(); + /// let query = ytmapi_rs::query::CreatePlaylistQuery::new( + /// "My heavy metal playlist", + /// None, + /// ytmapi_rs::query::PrivacyStatus::Public, + /// ) + /// .with_source(&playlists[0].playlist_id); + /// yt.create_playlist(query).await + /// # }; + pub async fn create_playlist<'a, T: CreatePlaylistType>( + &self, + query: CreatePlaylistQuery<'a, T>, + ) -> Result> { + query.call(self).await + } + /// Adds video items to a playlist you own. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let ytmapi_rs::common::library::Playlist { playlist_id, .. } = + /// yt.get_library_playlists().await.unwrap().pop().unwrap(); + /// let songs = yt.search_songs("Master of puppets").await.unwrap(); + /// yt.add_video_items_to_playlist( + /// playlist_id, + /// songs.iter().map(|s| (&s.video_id).into()).collect() + /// ).await + /// # }; + pub async fn add_video_items_to_playlist<'a, T: Into>>( + &self, + playlist_id: T, + video_ids: Vec>, + ) -> Result> { + let query = AddPlaylistItemsQuery::new_from_videos( + playlist_id.into(), + video_ids, + DuplicateHandlingMode::default(), + ); + query.call(self).await + } + /// Appends another playlist to a playlist you own. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let ytmapi_rs::common::library::Playlist { playlist_id, .. } = + /// yt.get_library_playlists().await.unwrap().pop().unwrap(); + /// let source_playlist = yt.search_featured_playlists("Heavy metal") + /// .await + /// .unwrap(); + /// yt.add_playlist_to_playlist( + /// playlist_id, + /// &source_playlist[0].playlist_id + /// ).await + /// # }; + pub async fn add_playlist_to_playlist<'a, T: Into>, U: Into>>( + &self, + destination_playlist: T, + source_playlist: U, + ) -> Result> { + let query = AddPlaylistItemsQuery::new_from_playlist( + destination_playlist.into(), + source_playlist.into(), + ); + query.call(self).await + } + /// Removes items from a playlist you own. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let ytmapi_rs::common::library::Playlist { playlist_id, .. } = + /// yt.get_library_playlists().await.unwrap().pop().unwrap(); + /// let source_playlist = yt.search_featured_playlists("Heavy metal") + /// .await + /// .unwrap(); + /// let outcome = yt.add_playlist_to_playlist( + /// &playlist_id, + /// &source_playlist[0].playlist_id + /// ).await.unwrap(); + /// yt.remove_playlist_items( + /// playlist_id, + /// outcome.iter().map(|o| (&o.set_video_id).into()).collect(), + /// ).await + /// # }; + pub async fn remove_playlist_items<'a, T: Into>>( + &self, + playlist_id: T, + video_items: Vec>, + ) -> Result { + let query = RemovePlaylistItemsQuery::new(playlist_id.into(), video_items); + query.call(self).await + } + /// Makes changes to a playlist. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let playlists = yt.get_library_playlists() + /// .await + /// .unwrap(); + /// let query = ytmapi_rs::query::EditPlaylistQuery::new_title( + /// &playlists[0].playlist_id, + /// "Better playlist title", + /// ) + /// .with_new_description("Edited description"); + /// yt.edit_playlist(query).await + /// # }; + pub async fn edit_playlist(&self, query: EditPlaylistQuery<'_>) -> Result { + query.call(self).await + } + /// Gets a list of all uploaded songs in your Library. + /// # Additional functionality + /// See [`GetLibraryUploadSongsQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetLibraryUploadSongsQuery]: crate::query::GetLibraryUploadSongsQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.get_library_upload_songs().await + /// # }; + pub async fn get_library_upload_songs( + &self, + ) -> Result<>::Output> { + let query = GetLibraryUploadSongsQuery::default(); + query.call(self).await + } + /// Gets a list of all uploaded artists in your Library. + /// # Additional functionality + /// See [`GetLibraryUploadArtistsQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetLibraryUploadArtistsQuery]: crate::query::GetLibraryUploadArtistsQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.get_library_upload_artists().await + /// # }; + pub async fn get_library_upload_artists( + &self, + ) -> Result<>::Output> { + let query = GetLibraryUploadArtistsQuery::default(); + query.call(self).await + } + /// Gets a list of all uploaded albums in your Library. + /// # Additional functionality + /// See [`GetLibraryUploadAlbumsQuery`] and [`YtMusic.query()`] + /// for more ways to construct and run. + /// + /// [`YtMusic.query()`]: crate::YtMusic::query + /// [GetLibraryUploadAlbumsQuery]: crate::query::GetLibraryUploadAlbumsQuery + /// + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// yt.get_library_upload_albums().await + /// # }; + pub async fn get_library_upload_albums( + &self, + ) -> Result<>::Output> { + let query = GetLibraryUploadAlbumsQuery::default(); + query.call(self).await + } + /// Gets information and tracks for an uploaded album in your Library. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let albums = yt.get_library_upload_albums().await.unwrap(); + /// yt.get_library_upload_album(&albums[0].album_id).await + /// # }; + pub async fn get_library_upload_album<'a, T: Into>>( + &self, + upload_album_id: T, + ) -> Result<>::Output> { + let query = GetLibraryUploadAlbumQuery::new(upload_album_id.into()); + query.call(self).await + } + /// Gets all tracks for an uploaded artist in your Library. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let artists = yt.get_library_upload_artists().await.unwrap(); + /// yt.get_library_upload_artist(&artists[0].artist_id).await + /// # }; + pub async fn get_library_upload_artist<'a, T: Into>>( + &self, + upload_artist_id: T, + ) -> Result<>::Output> { + let query = GetLibraryUploadArtistQuery::new(upload_artist_id.into()); + query.call(self).await + } + /// Deletes an upload entity from your library - this is either a song or an + /// album. + /// ```no_run + /// # async { + /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap(); + /// let albums = yt.get_library_upload_albums().await.unwrap(); + /// yt.delete_upload_entity(&albums[0].entity_id).await + /// # }; + pub async fn delete_upload_entity<'a, T: Into>>( + &self, + upload_entity_id: T, + ) -> Result<>::Output> { + let query = DeleteUploadEntityQuery::new(upload_entity_id.into()); + query.call(self).await + } +} diff --git a/ytmapi-rs/src/utils.rs b/ytmapi-rs/src/utils.rs index 5baaef30..8ed1aa36 100644 --- a/ytmapi-rs/src/utils.rs +++ b/ytmapi-rs/src/utils.rs @@ -42,7 +42,8 @@ pub fn hash_sapisid(sapisid: &str) -> String { } /// Macro to generate the boilerplate code that allows implementation of -/// YoutubeID for a simple struct. +/// YoutubeID for a simple struct. In addition implements a convenient From +/// implementation. macro_rules! impl_youtube_id { ($t:ty) => { impl<'a> YoutubeID<'a> for $t { @@ -53,6 +54,12 @@ macro_rules! impl_youtube_id { Self(raw_str.into()) } } + impl<'a> From<&'a $t> for $t { + fn from(value: &'a $t) -> Self { + let core = &value.0; + Self(core.as_ref().into()) + } + } }; } diff --git a/ytmapi-rs/test_json/get_library_albums_20240701_output.txt b/ytmapi-rs/test_json/get_library_albums_20240701_output.txt index 713807c4..ef5971b0 100644 --- a/ytmapi-rs/test_json/get_library_albums_20240701_output.txt +++ b/ytmapi-rs/test_json/get_library_albums_20240701_output.txt @@ -4,7 +4,7 @@ artist: "Radiohead", year: "2021", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_E7NpKw54NIT", ), album_type: Album, @@ -26,7 +26,7 @@ artist: "Aj Lee & Blue Summit", year: "2021", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_tlzBJv4t1DX", ), album_type: Album, @@ -48,7 +48,7 @@ artist: "Kendrick Lamar", year: "2015", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_3jGuGO44ziy", ), album_type: Album, diff --git a/ytmapi-rs/test_json/get_library_artist_subscriptions_20240701_output.txt b/ytmapi-rs/test_json/get_library_artist_subscriptions_20240701_output.txt index 54a254d2..fabab417 100644 --- a/ytmapi-rs/test_json/get_library_artist_subscriptions_20240701_output.txt +++ b/ytmapi-rs/test_json/get_library_artist_subscriptions_20240701_output.txt @@ -2,7 +2,9 @@ GetLibraryArtistSubscription { name: "Calibre", subscribers: "19.2K subscribers", - channel_id: "UC48r7vjqthkREf6NdoNEzww", + channel_id: ChannelID( + "UC48r7vjqthkREf6NdoNEzww", + ), thumbnails: [ Thumbnail { height: 60, @@ -19,7 +21,9 @@ GetLibraryArtistSubscription { name: "Have a Nice Life", subscribers: "22.3K subscribers", - channel_id: "UCmc-p5a2CHyCNpyAiI47Mzg", + channel_id: ChannelID( + "UCmc-p5a2CHyCNpyAiI47Mzg", + ), thumbnails: [ Thumbnail { height: 60, @@ -36,7 +40,9 @@ GetLibraryArtistSubscription { name: "The Smile", subscribers: "152K subscribers", - channel_id: "UChy8y7EpURH4K_LXIHENROA", + channel_id: ChannelID( + "UChy8y7EpURH4K_LXIHENROA", + ), thumbnails: [ Thumbnail { height: 60, diff --git a/ytmapi-rs/test_json/search_albums_20231226_output.txt b/ytmapi-rs/test_json/search_albums_20231226_output.txt index 49ba2f70..1d51c05e 100644 --- a/ytmapi-rs/test_json/search_albums_20231226_output.txt +++ b/ytmapi-rs/test_json/search_albums_20231226_output.txt @@ -4,7 +4,7 @@ artist: "Chocolate Starfish And The Hot Dog Flavored Water", year: "2000", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_isdHayivN53", ), album_type: Album, @@ -36,7 +36,7 @@ artist: "Significant Other", year: "1999", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_hNaIPnuYrtX", ), album_type: Album, @@ -68,7 +68,7 @@ artist: "Results May Vary", year: "2003", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_nMiTt4IbiwD", ), album_type: Album, @@ -100,7 +100,7 @@ artist: "STILL SUCKS", year: "2021", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_KWbcdJIIFRM", ), album_type: Album, @@ -132,7 +132,7 @@ artist: "Three Dollar Bill, Y'All$", year: "1997", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_kxKpExr2AHi", ), album_type: Album, @@ -164,7 +164,7 @@ artist: "Who's Next (Remastered 2022)", year: "1971", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_gq8S7Z17pMW", ), album_type: Album, @@ -196,7 +196,7 @@ artist: "Gold Cobra", year: "2011", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_5Xr7fwdmKIH", ), album_type: Album, @@ -228,7 +228,7 @@ artist: "Follow The Leader", year: "1998", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_juYedhgZbpn", ), album_type: Album, @@ -260,7 +260,7 @@ artist: "New Old Songs", year: "2001", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_J0MosCf7S5B", ), album_type: Album, @@ -292,7 +292,7 @@ artist: "Greatest Hitz", year: "2005", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_IZ7nwJGwbVb", ), album_type: Album, @@ -324,7 +324,7 @@ artist: "Master of Puppets", year: "1986", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_EvUaRykfAC1", ), album_type: Album, @@ -356,7 +356,7 @@ artist: "Hybrid Theory", year: "2000", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_bivSECiIj20", ), album_type: Album, @@ -388,7 +388,7 @@ artist: "The Unquestionable Truth (Pt. 1)", year: "2005", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_EHrFamOULZJ", ), album_type: EP, @@ -420,7 +420,7 @@ artist: "Endless Slaughter", year: "2014", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_eL3tjEUrmJx", ), album_type: Single, @@ -452,7 +452,7 @@ artist: "Devils Night", year: "2001", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_fIx7ZejmLQd", ), album_type: Album, @@ -484,7 +484,7 @@ artist: "Limp Bizkit", year: "2021", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_yNFQ4XTCyPz", ), album_type: Single, @@ -516,7 +516,7 @@ artist: "Counterfeit Countdown", year: "1997", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_edHAO1rYneW", ), album_type: Single, @@ -548,7 +548,7 @@ artist: "Meteora", year: "2003", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_qMlbe7gLeuH", ), album_type: Album, @@ -580,7 +580,7 @@ artist: "Family Values Tour '98", year: "1999", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_nVZDkecPblM", ), album_type: Album, @@ -612,7 +612,7 @@ artist: "Eat You Alive", year: "2003", explicit: IsExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_YI3tr47oA4L", ), album_type: Single, @@ -639,4 +639,4 @@ }, ], }, -] \ No newline at end of file +] diff --git a/ytmapi-rs/test_json/search_basic_with_vodcasts_type_not_specified_20240612_output.txt b/ytmapi-rs/test_json/search_basic_with_vodcasts_type_not_specified_20240612_output.txt index cd0fa4c0..a7a4c3c9 100644 --- a/ytmapi-rs/test_json/search_basic_with_vodcasts_type_not_specified_20240612_output.txt +++ b/ytmapi-rs/test_json/search_basic_with_vodcasts_type_not_specified_20240612_output.txt @@ -198,7 +198,7 @@ SearchResults { artist: "The Beatles", year: "1968", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_S5TiUIYvI78", ), album_type: Album, @@ -230,7 +230,7 @@ SearchResults { artist: "Abbey Road (Super Deluxe Edition)", year: "1969", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_tQfaWH32ovE", ), album_type: Album, @@ -262,7 +262,7 @@ SearchResults { artist: "The Beatles 1967 - 1970", year: "1973", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_LAgCGKzQToD", ), album_type: Album, @@ -584,4 +584,4 @@ SearchResults { podcasts: [], episodes: [], profiles: [], -} \ No newline at end of file +} diff --git a/ytmapi-rs/test_json/search_basic_with_vodcasts_type_specified_20240612_output.txt b/ytmapi-rs/test_json/search_basic_with_vodcasts_type_specified_20240612_output.txt index 605f1619..0c42940f 100644 --- a/ytmapi-rs/test_json/search_basic_with_vodcasts_type_specified_20240612_output.txt +++ b/ytmapi-rs/test_json/search_basic_with_vodcasts_type_specified_20240612_output.txt @@ -202,7 +202,7 @@ SearchResults { artist: "The Beatles", year: "1968", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_S5TiUIYvI78", ), album_type: Album, @@ -234,7 +234,7 @@ SearchResults { artist: "Abbey Road (Super Deluxe Edition)", year: "1969", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_tQfaWH32ovE", ), album_type: Album, @@ -266,7 +266,7 @@ SearchResults { artist: "The Beatles 1967 - 1970", year: "1973", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_LAgCGKzQToD", ), album_type: Album, @@ -771,4 +771,4 @@ SearchResults { ], }, ], -} \ No newline at end of file +} diff --git a/ytmapi-rs/test_json/search_highlighted_top_result_20240107_output.txt b/ytmapi-rs/test_json/search_highlighted_top_result_20240107_output.txt index 4b3d2628..fa906f73 100644 --- a/ytmapi-rs/test_json/search_highlighted_top_result_20240107_output.txt +++ b/ytmapi-rs/test_json/search_highlighted_top_result_20240107_output.txt @@ -198,7 +198,7 @@ SearchResults { artist: "The Beatles 1967 - 1970", year: "1973", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_LAgCGKzQToD", ), album_type: Album, @@ -230,7 +230,7 @@ SearchResults { artist: "Abbey Road (Super Deluxe Edition)", year: "1969", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_tQfaWH32ovE", ), album_type: Album, @@ -262,7 +262,7 @@ SearchResults { artist: "The Beatles", year: "1968", explicit: NotExplicit, - browse_id: ChannelID( + album_id: AlbumID( "MPREb_S5TiUIYvI78", ), album_type: Album, diff --git a/ytmapi-rs/tests/live_integration_tests.rs b/ytmapi-rs/tests/live_integration_tests.rs index cffe8e7a..c280f76e 100644 --- a/ytmapi-rs/tests/live_integration_tests.rs +++ b/ytmapi-rs/tests/live_integration_tests.rs @@ -89,6 +89,10 @@ async fn test_new() { } generate_query_test!(test_get_history, GetHistoryQuery); +generate_query_test!( + test_get_playlist, + GetPlaylistQuery::new(PlaylistID::from_raw("VLPL0jp-uZ7a4g9FQWW5R_u0pz4yzV4RiOXu")) +); generate_query_test!( test_get_library_upload_songs, GetLibraryUploadSongsQuery::default() @@ -345,19 +349,13 @@ async fn test_add_remove_playlist_items() { .await .unwrap(); let set_video_ids = api - .add_playlist_video_items(AddPlaylistItemsQuery::new_from_videos( - id.clone(), - vec![VideoID::from_raw("kfSQkZuIx84")], - Default::default(), - )) + .add_video_items_to_playlist(&id, vec![VideoID::from_raw("kfSQkZuIx84")]) .await .unwrap() .into_iter() .map(|item| item.set_video_id) .collect(); - api.remove_playlist_items(RemovePlaylistItemsQuery::new(id.clone(), set_video_ids)) - .await - .unwrap(); + api.remove_playlist_items(&id, set_video_ids).await.unwrap(); api.delete_playlist(id).await.unwrap(); } #[tokio::test] @@ -380,36 +378,6 @@ async fn test_edit_playlist() { api.delete_playlist(id).await.unwrap(); } #[tokio::test] -async fn test_get_playlist_oauth() { - let mut api = new_standard_oauth_api().await.unwrap(); - // Don't stuff around trying the keep the local OAuth secret up to date, just - // refresh it each time. - api.refresh_token().await.unwrap(); - api.get_playlist(GetPlaylistQuery::new(PlaylistID::from_raw( - "VLPL0jp-uZ7a4g9FQWW5R_u0pz4yzV4RiOXu", - ))) - .await - .unwrap(); -} -#[tokio::test] -async fn test_get_playlist() { - // TODO: Add siginficantly more queries. - let api = new_standard_api().await.unwrap(); - api.get_playlist(GetPlaylistQuery::new(PlaylistID::from_raw( - "VLPL0jp-uZ7a4g9FQWW5R_u0pz4yzV4RiOXu", - ))) - .await - .unwrap(); -} -#[tokio::test] -async fn test_search_songs_oauth() { - let mut api = new_standard_oauth_api().await.unwrap(); - // Don't stuff around trying the keep the local OAuth secret up to date, just - // refresh it each time. - api.refresh_token().await.unwrap(); - let _res = api.search_songs("Beatles").await.unwrap(); -} -#[tokio::test] async fn test_get_library_playlists_oauth() { let mut api = new_standard_oauth_api().await.unwrap(); // Don't stuff around trying the keep the local OAuth secret up to date, just @@ -430,15 +398,13 @@ async fn test_get_library_artists_oauth() { // Don't stuff around trying the keep the local OAuth secret up to date, just // refresh it each time. api.refresh_token().await.unwrap(); - let query = GetLibraryArtistsQuery::default(); - let res = api.get_library_artists(query).await.unwrap(); + let res = api.get_library_artists().await.unwrap(); assert!(!res.is_empty()); } #[tokio::test] async fn test_get_library_artists() { let api = new_standard_api().await.unwrap(); - let query = GetLibraryArtistsQuery::default(); - let res = api.get_library_artists(query).await.unwrap(); + let res = api.get_library_artists().await.unwrap(); assert!(!res.is_empty()); } #[tokio::test] @@ -446,9 +412,7 @@ async fn test_watch_playlist() { // TODO: Make more generic let api = new_standard_api().await.unwrap(); let res = api - .get_watch_playlist(GetWatchPlaylistQuery::new_from_video_id(VideoID::from_raw( - "9mWr4c_ig54", - ))) + .get_watch_playlist_from_video_id(VideoID::from_raw("9mWr4c_ig54")) .await .unwrap(); let example = WatchPlaylist { @@ -463,15 +427,10 @@ async fn test_get_lyrics() { // TODO: Make more generic let api = new_standard_api().await.unwrap(); let res = api - .get_watch_playlist(GetWatchPlaylistQuery::new_from_video_id(VideoID::from_raw( - "9mWr4c_ig54", - ))) - .await - .unwrap(); - let res = api - .get_lyrics(GetLyricsQuery::new(res.lyrics_id)) + .get_watch_playlist_from_video_id(VideoID::from_raw("9mWr4c_ig54")) .await .unwrap(); + let res = api.get_lyrics(res.lyrics_id).await.unwrap(); let example = Lyrics { lyrics: "You're my lesson I had to learn\nAnother page I'll have to turn\nI got one more message, always tryna be heard\nBut you never listen to a word\n\nHeaven knows we came so close\nBut this ain't real, it's just a dream\nWake me up, I've been fast asleep\nLetting go of fantasies\nBeen caught up in who I needed you to be\nHow foolish of me\n\nFoolish of me\nFoolish of me\nFoolish of me\nFoolish of me\n\nJust give me one second and I'll be fine\nJust let me catch my breath and come back to life\nI finally get the message, you were never meant to be mine\nCouldn't see the truth, I was blind (meant to be mine)\n\nWhoa, heaven knows we came so close\nBut this ain't real, it's just a dream\nWake me up, I've been fast asleep\nLetting go of fantasies\nBeen caught up in who I needed you to be\nHow foolish of me\n\nFoolish of me\nFoolish of me\nFoolish of me\nFoolish of me\n\nLetting go, we came so close (how foolish of me)\nOh, I'm letting go of fantasies\nBeen caught up in who I needed you to be\nHow foolish of me".into(), source: "Source: Musixmatch".into(), @@ -538,10 +497,8 @@ async fn test_get_artist_albums() { let _now = std::time::Instant::now(); let albums = res.top_releases.albums.unwrap(); let params = albums.params.unwrap(); - // For some reason the params is wrong. needs investigation. - let channel_id = &albums.browse_id.unwrap(); - let q = GetArtistAlbumsQuery::new(ChannelID::from_raw(channel_id.get_raw()), params); - api.get_artist_albums(q).await.unwrap(); + let channel_id = albums.browse_id.unwrap(); + api.get_artist_albums(channel_id, params).await.unwrap(); let now = std::time::Instant::now(); println!("Get albums took {} ms", now.elapsed().as_millis()); }