Skip to main content

ytmapi_rs/
simplified_queries.rs

1//! This module contains the implementation for more convenient ways to call the
2//! API, in many cases without the need of building Query structs.
3//! This module contains purely additional implementations for YtMusic. To see
4//! the documentation, refer to the [`YtMusic`] documentation itself.
5//! # Optional
6//! To enable this module, feature `simplified-queries` must be enabled (enabled
7//! by default)
8use crate::auth::{AuthToken, LoggedIn};
9use crate::common::{
10    AlbumID, ApiOutcome, ArtistChannelID, BrowseParams, EpisodeID, FeedbackTokenRemoveFromHistory,
11    LikeStatus, LyricsID, MoodCategoryParams, PlaylistID, PodcastChannelID, PodcastChannelParams,
12    PodcastID, SearchSuggestion, SetVideoID, SongTrackingUrl, TasteToken, UploadAlbumID,
13    UploadArtistID, UploadEntityID, UserChannelID, UserPlaylistsParams, UserVideosParams, VideoID,
14};
15use crate::parse::{
16    AddPlaylistItem, GetAlbum, GetArtist, GetArtistAlbumsAlbum, GetPlaylistDetails, GetUser,
17    HistoryPeriod, LibraryArtist, LibraryArtistSubscription, LibraryPlaylist, Lyrics, PlaylistItem,
18    SearchResultAlbum, SearchResultArtist, SearchResultEpisode, SearchResultFeaturedPlaylist,
19    SearchResultPlaylist, SearchResultPodcast, SearchResultProfile, SearchResultSong,
20    SearchResultVideo, SearchResults, UserPlaylist, UserVideo, WatchPlaylistTrack,
21};
22use crate::query::playlist::{CreatePlaylistType, DuplicateHandlingMode, GetPlaylistDetailsQuery};
23use crate::query::rate::{RatePlaylistQuery, RateSongQuery};
24use crate::query::search::BasicSearch;
25use crate::query::search::filteredsearch::{
26    AlbumsFilter, ArtistsFilter, CommunityPlaylistsFilter, EpisodesFilter, FeaturedPlaylistsFilter,
27    FilteredSearch, PlaylistsFilter, PodcastsFilter, ProfilesFilter, SongsFilter, VideosFilter,
28};
29use crate::query::song::{GetLyricsQuery, GetSongTrackingUrlQuery};
30use crate::query::{
31    AddHistoryItemQuery, AddPlaylistItemsQuery, CreatePlaylistQuery, DeletePlaylistQuery,
32    DeleteUploadEntityQuery, EditPlaylistQuery, EditSongLibraryStatusQuery, GetAlbumQuery,
33    GetArtistAlbumsQuery, GetArtistQuery, GetChannelEpisodesQuery, GetChannelQuery,
34    GetEpisodeQuery, GetHistoryQuery, GetLibraryAlbumsQuery, GetLibraryArtistSubscriptionsQuery,
35    GetLibraryArtistsQuery, GetLibraryChannelsQuery, GetLibraryPlaylistsQuery,
36    GetLibraryPodcastsQuery, GetLibrarySongsQuery, GetLibraryUploadAlbumQuery,
37    GetLibraryUploadAlbumsQuery, GetLibraryUploadArtistQuery, GetLibraryUploadArtistsQuery,
38    GetLibraryUploadSongsQuery, GetLyricsIDQuery, GetMoodCategoriesQuery, GetMoodPlaylistsQuery,
39    GetNewEpisodesQuery, GetPlaylistTracksQuery, GetPodcastQuery, GetSearchSuggestionsQuery,
40    GetTasteProfileQuery, GetUserPlaylistsQuery, GetUserQuery, GetUserVideosQuery,
41    GetWatchPlaylistQuery, Query, RemoveHistoryItemsQuery, RemovePlaylistItemsQuery, SearchQuery,
42    SetTasteProfileQuery, SubscribeArtistQuery, UnsubscribeArtistsQuery,
43};
44use crate::{Result, YtMusic};
45
46impl<A: AuthToken> YtMusic<A> {
47    /// API Search Query that returns results for each category if available.
48    /// # Usage
49    /// ```no_run
50    /// # async {
51    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
52    ///     .await
53    ///     .unwrap();
54    /// yt.search("Beatles").await
55    /// # };
56    /// ```
57    #[deprecated = "To be removed in future release - see issue #353"]
58    pub async fn search<'a, Q: Into<SearchQuery<'a, BasicSearch>>>(
59        &self,
60        query: Q,
61    ) -> Result<SearchResults> {
62        let query = query.into();
63        self.query(query).await
64    }
65    /// API Search Query for Artists only.
66    /// ```no_run
67    /// # async {
68    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
69    ///     .await
70    ///     .unwrap();
71    /// yt.search_artists("Beatles").await
72    /// # };
73    /// ```
74    pub async fn search_artists<'a, Q: Into<SearchQuery<'a, FilteredSearch<ArtistsFilter>>>>(
75        &self,
76        query: Q,
77    ) -> Result<Vec<SearchResultArtist>> {
78        let query = query.into();
79        self.query(query).await
80    }
81    /// API Search Query for Albums only.
82    /// ```no_run
83    /// # async {
84    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
85    /// yt.search_albums("Beatles").await
86    /// # };
87    pub async fn search_albums<'a, Q: Into<SearchQuery<'a, FilteredSearch<AlbumsFilter>>>>(
88        &self,
89        query: Q,
90    ) -> Result<Vec<SearchResultAlbum>> {
91        let query = query.into();
92        self.query(query).await
93    }
94    /// API Search Query for Songs only.
95    /// ```no_run
96    /// # async {
97    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
98    ///     .await
99    ///     .unwrap();
100    /// yt.search_songs("Beatles").await
101    /// # };
102    /// ```
103    pub async fn search_songs<'a, Q: Into<SearchQuery<'a, FilteredSearch<SongsFilter>>>>(
104        &self,
105        query: Q,
106    ) -> Result<Vec<SearchResultSong>> {
107        let query = query.into();
108        self.query(query).await
109    }
110    /// API Search Query for Playlists only.
111    /// ```no_run
112    /// # async {
113    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
114    ///     .await
115    ///     .unwrap();
116    /// yt.search_playlists("Beatles").await
117    /// # };
118    /// ```
119    pub async fn search_playlists<'a, Q: Into<SearchQuery<'a, FilteredSearch<PlaylistsFilter>>>>(
120        &self,
121        query: Q,
122    ) -> Result<Vec<SearchResultPlaylist>> {
123        let query = query.into();
124        self.query(query).await
125    }
126    /// API Search Query for Community Playlists only.
127    /// ```no_run
128    /// # async {
129    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
130    ///     .await
131    ///     .unwrap();
132    /// yt.search_community_playlists("Beatles").await
133    /// # };
134    /// ```
135    pub async fn search_community_playlists<
136        'a,
137        Q: Into<SearchQuery<'a, FilteredSearch<CommunityPlaylistsFilter>>>,
138    >(
139        &self,
140        query: Q,
141    ) -> Result<Vec<SearchResultPlaylist>> {
142        let query = query.into();
143        self.query(query).await
144    }
145    /// API Search Query for Featured Playlists only.
146    /// ```no_run
147    /// # async {
148    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
149    ///     .await
150    ///     .unwrap();
151    /// yt.search_featured_playlists("Beatles").await
152    /// # };
153    /// ```
154    pub async fn search_featured_playlists<
155        'a,
156        Q: Into<SearchQuery<'a, FilteredSearch<FeaturedPlaylistsFilter>>>,
157    >(
158        &self,
159        query: Q,
160    ) -> Result<Vec<SearchResultFeaturedPlaylist>> {
161        let query = query.into();
162        self.query(query).await
163    }
164    /// API Search Query for Episodes only.
165    /// ```no_run
166    /// # async {
167    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
168    ///     .await
169    ///     .unwrap();
170    /// yt.search_episodes("Beatles").await
171    /// # };
172    /// ```
173    pub async fn search_episodes<'a, Q: Into<SearchQuery<'a, FilteredSearch<EpisodesFilter>>>>(
174        &self,
175        query: Q,
176    ) -> Result<Vec<SearchResultEpisode>> {
177        let query = query.into();
178        self.query(query).await
179    }
180    /// API Search Query for Podcasts only.
181    /// ```no_run
182    /// # async {
183    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
184    ///     .await
185    ///     .unwrap();
186    /// yt.search_podcasts("Beatles").await
187    /// # };
188    /// ```
189    pub async fn search_podcasts<'a, Q: Into<SearchQuery<'a, FilteredSearch<PodcastsFilter>>>>(
190        &self,
191        query: Q,
192    ) -> Result<Vec<SearchResultPodcast>> {
193        let query = query.into();
194        self.query(query).await
195    }
196    /// API Search Query for Videos only.
197    /// ```no_run
198    /// # async {
199    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
200    ///     .await
201    ///     .unwrap();
202    /// yt.search_videos("Beatles").await
203    /// # };
204    /// ```
205    pub async fn search_videos<'a, Q: Into<SearchQuery<'a, FilteredSearch<VideosFilter>>>>(
206        &self,
207        query: Q,
208    ) -> Result<Vec<SearchResultVideo>> {
209        let query = query.into();
210        self.query(query).await
211    }
212    /// API Search Query for Profiles only.
213    /// ```no_run
214    /// # async {
215    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
216    ///     .await
217    ///     .unwrap();
218    /// yt.search_profiles("Beatles").await
219    /// # };
220    /// ```
221    pub async fn search_profiles<'a, Q: Into<SearchQuery<'a, FilteredSearch<ProfilesFilter>>>>(
222        &self,
223        query: Q,
224    ) -> Result<Vec<SearchResultProfile>> {
225        let query = query.into();
226        self.query(query).await
227    }
228    /// Gets information about an artist and their top releases.
229    /// ```no_run
230    /// # async {
231    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
232    ///     .await
233    ///     .unwrap();
234    /// let results = yt.search_artists("Beatles").await.unwrap();
235    /// yt.get_artist(&results[0].browse_id).await
236    /// # };
237    /// ```
238    pub async fn get_artist<'a>(&self, query: impl Into<GetArtistQuery<'a>>) -> Result<GetArtist> {
239        self.query(query.into()).await
240    }
241    /// Gets a full list albums for an artist.
242    /// ```no_run
243    /// # async {
244    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
245    ///     .await
246    ///     .unwrap();
247    /// let results = yt.search_artists("Beatles").await.unwrap();
248    /// let artist_top_albums = yt
249    ///     .get_artist(&results[0].browse_id)
250    ///     .await
251    ///     .unwrap()
252    ///     .top_releases
253    ///     .albums
254    ///     .unwrap();
255    /// yt.get_artist_albums(
256    ///     artist_top_albums.browse_id.unwrap(),
257    ///     artist_top_albums.params.unwrap(),
258    /// )
259    /// .await
260    /// # };
261    /// ```
262    pub async fn get_artist_albums<'a, T: Into<ArtistChannelID<'a>>, U: Into<BrowseParams<'a>>>(
263        &self,
264        channel_id: T,
265        browse_params: U,
266    ) -> Result<Vec<GetArtistAlbumsAlbum>> {
267        let query = GetArtistAlbumsQuery::new(channel_id.into(), browse_params.into());
268        self.query(query).await
269    }
270    /// Gets information about an album and its tracks.
271    /// ```no_run
272    /// # async {
273    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
274    ///     .await
275    ///     .unwrap();
276    /// let results = yt.search_albums("Dark Side Of The Moon").await.unwrap();
277    /// yt.get_album(&results[0].album_id).await
278    /// # };
279    /// ```
280    pub async fn get_album<'a, T: Into<AlbumID<'a>>>(&self, album_id: T) -> Result<GetAlbum> {
281        let query = GetAlbumQuery::new(album_id);
282        self.query(query).await
283    }
284    /// Gets the information that's available when playing a song or playlist;
285    /// upcoming tracks and lyrics.
286    /// # Partially implemented
287    /// Tracks are not implemented - empty vector always returned.
288    /// See [`GetWatchPlaylistQuery`] and [`YtMusic.query()`]
289    /// for more ways to construct and run
290    /// a GetWatchPlaylistQuery.
291    ///
292    /// [`YtMusic.query()`]: crate::YtMusic::query
293    /// [GetWatchPlaylistQuery]: crate::query::watch::GetWatchPlaylistQuery
294    ///
295    /// ```no_run
296    /// # async {
297    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
298    ///     .await
299    ///     .unwrap();
300    /// let results = yt
301    ///     .search_songs("While My Guitar Gently Weeps")
302    ///     .await
303    ///     .unwrap();
304    /// yt.get_watch_playlist_from_video_id(&results[0].video_id)
305    ///     .await
306    /// # };
307    /// ```
308    // NOTE: Could be generic across PlaylistID or VideoID using
309    // Into<GetWatchPlaylistQuery>
310    pub async fn get_watch_playlist_from_video_id<'a, S: Into<VideoID<'a>>>(
311        &self,
312        video_id: S,
313    ) -> Result<Vec<WatchPlaylistTrack>> {
314        let query = GetWatchPlaylistQuery::new_from_video_id(video_id.into());
315        self.query(query).await
316    }
317    /// Gets the `LyricsID` required to get lyrics.
318    /// ```no_run
319    /// # async {
320    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
321    ///     .await
322    ///     .unwrap();
323    /// let results = yt
324    ///     .search_songs("While My Guitar Gently Weeps")
325    ///     .await
326    ///     .unwrap();
327    /// yt.get_lyrics_id(&results[0].video_id).await
328    /// # };
329    /// ```
330    pub async fn get_lyrics_id<'a, T: Into<VideoID<'a>>>(
331        &self,
332        video_id: T,
333    ) -> Result<LyricsID<'static>> {
334        let query = GetLyricsIDQuery::new(video_id.into());
335        self.query(query).await
336    }
337    /// Gets song lyrics and the source.
338    /// ```no_run
339    /// # async {
340    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
341    ///     .await
342    ///     .unwrap();
343    /// let results = yt
344    ///     .search_songs("While My Guitar Gently Weeps")
345    ///     .await
346    ///     .unwrap();
347    /// let lyrics_id = yt.get_lyrics_id(&results[0].video_id).await.unwrap();
348    /// yt.get_lyrics(lyrics_id).await
349    /// # };
350    /// ```
351    pub async fn get_lyrics<'a, T: Into<LyricsID<'a>>>(&self, lyrics_id: T) -> Result<Lyrics> {
352        let query = GetLyricsQuery::new(lyrics_id.into());
353        self.query(query).await
354    }
355    /// Gets a playlists tracks.
356    /// ```no_run
357    /// # async {
358    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
359    ///     .await
360    ///     .unwrap();
361    /// let results = yt.search_featured_playlists("Heavy metal").await.unwrap();
362    /// yt.get_playlist_tracks(&results[0].playlist_id).await
363    /// # };
364    /// ```
365    pub async fn get_playlist_tracks<'a, T: Into<PlaylistID<'a>>>(
366        &self,
367        playlist_id: T,
368    ) -> Result<Vec<PlaylistItem>> {
369        let query = GetPlaylistTracksQuery::new(playlist_id.into());
370        self.query(query).await
371    }
372    /// Gets information about a playlist.
373    /// ```no_run
374    /// # async {
375    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
376    ///     .await
377    ///     .unwrap();
378    /// let results = yt.search_featured_playlists("Heavy metal").await.unwrap();
379    /// yt.get_playlist_details(&results[0].playlist_id).await
380    /// # };
381    /// ```
382    pub async fn get_playlist_details<'a, T: Into<PlaylistID<'a>>>(
383        &self,
384        playlist_id: T,
385    ) -> Result<GetPlaylistDetails> {
386        let query = GetPlaylistDetailsQuery::new(playlist_id.into());
387        self.query(query).await
388    }
389    /// Gets search suggestions
390    /// ```no_run
391    /// # async {
392    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
393    ///     .await
394    ///     .unwrap();
395    /// yt.get_search_suggestions("The Beat").await;
396    /// # };
397    /// ```
398    pub async fn get_search_suggestions<'a, S: Into<GetSearchSuggestionsQuery<'a>>>(
399        &self,
400        query: S,
401    ) -> Result<Vec<SearchSuggestion>> {
402        let query = query.into();
403        self.query(query).await
404    }
405    /// Fetches suggested artists from taste profile
406    /// <https://music.youtube.com/tasteprofile>.
407    /// Tasteprofile allows users to pick artists to update their
408    /// recommendations.
409    /// ```no_run
410    /// # async {
411    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
412    ///     .await
413    ///     .unwrap();
414    /// yt.get_taste_profile().await
415    /// # };
416    /// ```
417    pub async fn get_taste_profile(&self) -> Result<<GetTasteProfileQuery as Query<A>>::Output> {
418        self.query(GetTasteProfileQuery).await
419    }
420    /// Sets artists as favourites to influence your recommendations.
421    /// ```no_run
422    /// # async {
423    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
424    ///     .await
425    ///     .unwrap();
426    /// let results = yt.get_taste_profile().await.unwrap();
427    /// yt.set_taste_profile(results.into_iter().take(5).map(|r| r.taste_tokens))
428    ///     .await
429    /// # };
430    /// ```
431    pub async fn set_taste_profile<'a>(
432        &self,
433        taste_tokens: impl IntoIterator<Item = TasteToken<'a>>,
434    ) -> Result<<SetTasteProfileQuery<'a> as Query<A>>::Output> {
435        self.query(SetTasteProfileQuery::new(taste_tokens)).await
436    }
437    /// Fetches 'Moods & Genres' categories.
438    /// ```no_run
439    /// # async {
440    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
441    ///     .await
442    ///     .unwrap();
443    /// yt.get_mood_categories().await
444    /// # };
445    /// ```
446    pub async fn get_mood_categories(
447        &self,
448    ) -> Result<<GetMoodCategoriesQuery as Query<A>>::Output> {
449        self.query(GetMoodCategoriesQuery).await
450    }
451    /// Returns a list of playlists for a given mood category.
452    /// ```no_run
453    /// # async {
454    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
455    ///     .await
456    ///     .unwrap();
457    /// let results = yt.get_mood_categories().await.unwrap();
458    /// yt.get_mood_playlists(&results[0].mood_categories[0].params)
459    ///     .await
460    /// # };
461    /// ```
462    pub async fn get_mood_playlists<'a, T: Into<MoodCategoryParams<'a>>>(
463        &self,
464        mood_params: T,
465    ) -> Result<<GetMoodPlaylistsQuery<'_> as Query<A>>::Output> {
466        self.query(GetMoodPlaylistsQuery::new(mood_params.into()))
467            .await
468    }
469    /// Get the 'SongTrackingUrl' for a song. This is used to add items to
470    /// history using `add_history_item()`.
471    /// ```no_run
472    /// # async {
473    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
474    ///     .await
475    ///     .unwrap();
476    /// let song = yt
477    ///     .search_songs("While My Guitar Gently Weeps")
478    ///     .await
479    ///     .unwrap()
480    ///     .into_iter()
481    ///     .next()
482    ///     .unwrap();
483    /// yt.get_song_tracking_url(song.video_id).await
484    /// # };
485    /// ```
486    pub async fn get_song_tracking_url<'a, T: Into<VideoID<'a>>>(
487        &self,
488        video_id: T,
489    ) -> Result<SongTrackingUrl<'static>> {
490        let query = GetSongTrackingUrlQuery::new(video_id.into())?;
491        self.query(query).await
492    }
493    /// Gets information about a Channel of Podcasts.
494    /// ```no_run
495    /// # async {
496    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
497    ///     .await
498    ///     .unwrap();
499    /// let podcasts = yt.search_podcasts("Rustacean").await.unwrap();
500    /// let podcast = yt.get_podcast(&podcasts[0].podcast_id).await.unwrap();
501    /// yt.get_channel(podcast.channels[0].id.as_ref().unwrap())
502    ///     .await
503    /// # };
504    /// ```
505    pub async fn get_channel(
506        &self,
507        channel_id: impl Into<PodcastChannelID<'_>>,
508    ) -> Result<<GetChannelQuery<'_> as Query<A>>::Output> {
509        self.query(GetChannelQuery::new(channel_id)).await
510    }
511    /// Gets a list of all Episodes for a Channel. Note, if GetPodcastChannel
512    /// doesn't contain `episode_params`, you can be sure that all episodes are
513    /// already included at `episodes` key.
514    /// ```no_run
515    /// # async {
516    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
517    ///     .await
518    ///     .unwrap();
519    /// let podcasts = yt.search_podcasts("Rustacean").await.unwrap();
520    /// let podcast = yt.get_podcast(&podcasts[0].podcast_id).await.unwrap();
521    /// let channel_id = podcast.channels[0].id.as_ref().unwrap();
522    /// let channel = yt.get_channel(channel_id).await.unwrap();
523    /// match channel.episode_params {
524    ///     Some(p) => yt.get_channel_episodes(channel_id, p).await,
525    ///     None => Ok(channel.episodes),
526    /// }
527    /// # };
528    /// ```
529    pub async fn get_channel_episodes<'a>(
530        &self,
531        channel_id: impl Into<PodcastChannelID<'a>>,
532        podcast_channel_params: impl Into<PodcastChannelParams<'a>>,
533    ) -> Result<<GetChannelEpisodesQuery<'_> as Query<A>>::Output> {
534        self.query(GetChannelEpisodesQuery::new(
535            channel_id,
536            podcast_channel_params,
537        ))
538        .await
539    }
540    /// Gets information about a Podcast, including Episodes.
541    /// ```no_run
542    /// # async {
543    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
544    ///     .await
545    ///     .unwrap();
546    /// let podcasts = yt.search_podcasts("Rustacean").await.unwrap();
547    /// yt.get_podcast(&podcasts[0].podcast_id).await
548    /// # };
549    /// ```
550    pub async fn get_podcast(
551        &self,
552        podcast_id: impl Into<PodcastID<'_>>,
553    ) -> Result<<GetPodcastQuery<'_> as Query<A>>::Output> {
554        self.query(GetPodcastQuery::new(podcast_id)).await
555    }
556    /// ```no_run
557    /// # async {
558    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
559    ///     .await
560    ///     .unwrap();
561    /// let episodes = yt.search_episodes("Ratatui").await.unwrap();
562    /// yt.get_episode(&episodes[0].episode_id).await
563    /// # };
564    /// ```
565    pub async fn get_episode(
566        &self,
567        episode_id: impl Into<EpisodeID<'_>>,
568    ) -> Result<<GetEpisodeQuery<'_> as Query<A>>::Output> {
569        self.query(GetEpisodeQuery::new(episode_id)).await
570    }
571    /// Gets the special 'New Episodes' playlist.
572    /// ```no_run
573    /// # async {
574    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
575    ///     .await
576    ///     .unwrap();
577    /// yt.get_new_episodes().await
578    /// # };
579    /// ```
580    pub async fn get_new_episodes(&self) -> Result<<GetNewEpisodesQuery as Query<A>>::Output> {
581        self.query(GetNewEpisodesQuery).await
582    }
583    /// Gets information about an user and their videos and playlists.
584    /// ```no_run
585    /// # async {
586    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
587    ///     .await
588    ///     .unwrap();
589    /// let results = yt.search_profiles("PewDiePie").await.unwrap();
590    /// yt.get_user(&results[0].profile_id).await
591    /// # };
592    /// ```
593    pub async fn get_user<'a>(&self, id: impl Into<UserChannelID<'a>>) -> Result<GetUser> {
594        self.query(GetUserQuery::new(id.into())).await
595    }
596    /// Gets a full list of videos for a user.
597    /// ```no_run
598    /// # async {
599    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
600    ///     .await
601    ///     .unwrap();
602    /// let user_id = &yt.search_profiles("PewDiePie").await.unwrap()[0].profile_id;
603    /// let user = yt.get_user(&*user_id).await.unwrap();
604    /// yt.get_user_videos(&*user_id, user.all_videos_params.unwrap())
605    ///     .await
606    /// # };
607    /// ```
608    pub async fn get_user_videos<'a, T: Into<UserChannelID<'a>>, U: Into<UserVideosParams<'a>>>(
609        &self,
610        channel_id: T,
611        browse_params: U,
612    ) -> Result<Vec<UserVideo>> {
613        let query = GetUserVideosQuery::new(channel_id.into(), browse_params.into());
614        self.query(query).await
615    }
616    /// Gets a full list of playlists for a user.
617    /// ```no_run
618    /// # async {
619    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
620    ///     .await
621    ///     .unwrap();
622    /// let user_id = &yt.search_profiles("PewDiePie").await.unwrap()[0].profile_id;
623    /// let user = yt.get_user(&*user_id).await.unwrap();
624    /// yt.get_user_playlists(&*user_id, user.all_playlists_params.unwrap())
625    ///     .await
626    /// # };
627    /// ```
628    pub async fn get_user_playlists<
629        'a,
630        T: Into<UserChannelID<'a>>,
631        U: Into<UserPlaylistsParams<'a>>,
632    >(
633        &self,
634        channel_id: T,
635        browse_params: U,
636    ) -> Result<Vec<UserPlaylist>> {
637        let query = GetUserPlaylistsQuery::new(channel_id.into(), browse_params.into());
638        self.query(query).await
639    }
640}
641
642impl<A: LoggedIn> YtMusic<A> {
643    /// Removes items from a playlist you own.
644    ///  ```no_run
645    /// # async {
646    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
647    /// let ytmapi_rs::parse::LibraryPlaylist { playlist_id, .. } =
648    ///     yt.get_library_playlists().await.unwrap().pop().unwrap();
649    /// let source_playlist = yt.search_featured_playlists("Heavy metal")
650    ///     .await
651    ///     .unwrap();
652    /// let outcome = yt.add_playlist_to_playlist(
653    ///     &playlist_id,
654    ///     &source_playlist[0].playlist_id
655    /// ).await.unwrap();
656    /// yt.remove_playlist_items(
657    ///     playlist_id,
658    ///     outcome.iter().map(|o| (&o.set_video_id).into()),
659    /// ).await
660    /// # };
661    /// ```
662    pub async fn remove_playlist_items<'a, T: Into<PlaylistID<'a>>>(
663        &self,
664        playlist_id: T,
665        video_items: impl IntoIterator<Item = SetVideoID<'a>>,
666    ) -> Result<()> {
667        let query = RemovePlaylistItemsQuery::new(playlist_id.into(), video_items);
668        self.query(query).await
669    }
670    /// Makes changes to a playlist.
671    ///  ```no_run
672    /// # async {
673    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
674    /// let playlists = yt.get_library_playlists()
675    ///     .await
676    ///     .unwrap();
677    /// let query = ytmapi_rs::query::EditPlaylistQuery::new_title(
678    ///     &playlists[0].playlist_id,
679    ///     "Better playlist title",
680    /// )
681    ///     .with_new_description("Edited description");
682    /// yt.edit_playlist(query).await
683    /// # };
684    /// ```
685    pub async fn edit_playlist(&self, query: EditPlaylistQuery<'_>) -> Result<ApiOutcome> {
686        self.query(query).await
687    }
688    /// Gets a list of all uploaded songs in your Library.
689    /// # Additional functionality
690    /// See [`GetLibraryUploadSongsQuery`] and [`YtMusic.query()`]
691    /// for more ways to construct and run.
692    ///
693    /// [`YtMusic.query()`]: crate::YtMusic::query
694    /// [GetLibraryUploadSongsQuery]: crate::query::GetLibraryUploadSongsQuery
695    ///
696    /// ```no_run
697    /// # async {
698    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
699    ///     .await
700    ///     .unwrap();
701    /// yt.get_library_upload_songs().await
702    /// # };
703    /// ```
704    pub async fn get_library_upload_songs(
705        &self,
706    ) -> Result<<GetLibraryUploadSongsQuery as Query<A>>::Output> {
707        let query = GetLibraryUploadSongsQuery::default();
708        self.query(query).await
709    }
710    /// Gets a list of all uploaded artists in your Library.
711    /// # Additional functionality
712    /// See [`GetLibraryUploadArtistsQuery`] and [`YtMusic.query()`]
713    /// for more ways to construct and run.
714    ///
715    /// [`YtMusic.query()`]: crate::YtMusic::query
716    /// [GetLibraryUploadArtistsQuery]: crate::query::GetLibraryUploadArtistsQuery
717    ///
718    /// ```no_run
719    /// # async {
720    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
721    ///     .await
722    ///     .unwrap();
723    /// yt.get_library_upload_artists().await
724    /// # };
725    /// ```
726    pub async fn get_library_upload_artists(
727        &self,
728    ) -> Result<<GetLibraryUploadArtistsQuery as Query<A>>::Output> {
729        let query = GetLibraryUploadArtistsQuery::default();
730        self.query(query).await
731    }
732    /// Gets a list of all uploaded albums in your Library.
733    /// # Additional functionality
734    /// See [`GetLibraryUploadAlbumsQuery`] and [`YtMusic.query()`]
735    /// for more ways to construct and run.
736    ///
737    /// [`YtMusic.query()`]: crate::YtMusic::query
738    /// [GetLibraryUploadAlbumsQuery]: crate::query::GetLibraryUploadAlbumsQuery
739    ///
740    /// ```no_run
741    /// # async {
742    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
743    ///     .await
744    ///     .unwrap();
745    /// yt.get_library_upload_albums().await
746    /// # };
747    /// ```
748    pub async fn get_library_upload_albums(
749        &self,
750    ) -> Result<<GetLibraryUploadAlbumsQuery as Query<A>>::Output> {
751        let query = GetLibraryUploadAlbumsQuery::default();
752        self.query(query).await
753    }
754    /// Gets information and tracks for an uploaded album in your Library.
755    /// ```no_run
756    /// # async {
757    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
758    ///     .await
759    ///     .unwrap();
760    /// let albums = yt.get_library_upload_albums().await.unwrap();
761    /// yt.get_library_upload_album(&albums[0].album_id).await
762    /// # };
763    /// ```
764    pub async fn get_library_upload_album<'a, T: Into<UploadAlbumID<'a>>>(
765        &self,
766        upload_album_id: T,
767    ) -> Result<<GetLibraryUploadAlbumQuery<'_> as Query<A>>::Output> {
768        let query = GetLibraryUploadAlbumQuery::new(upload_album_id.into());
769        self.query(query).await
770    }
771    /// Gets all tracks for an uploaded artist in your Library.
772    /// ```no_run
773    /// # async {
774    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
775    ///     .await
776    ///     .unwrap();
777    /// let artists = yt.get_library_upload_artists().await.unwrap();
778    /// yt.get_library_upload_artist(&artists[0].artist_id).await
779    /// # };
780    /// ```
781    pub async fn get_library_upload_artist<'a, T: Into<UploadArtistID<'a>>>(
782        &self,
783        upload_artist_id: T,
784    ) -> Result<<GetLibraryUploadArtistQuery<'_> as Query<A>>::Output> {
785        let query = GetLibraryUploadArtistQuery::new(upload_artist_id.into());
786        self.query(query).await
787    }
788    /// Deletes an upload entity from your library - this is either a song or an
789    /// album.
790    /// ```no_run
791    /// # async {
792    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
793    ///     .await
794    ///     .unwrap();
795    /// let albums = yt.get_library_upload_albums().await.unwrap();
796    /// yt.delete_upload_entity(&albums[0].entity_id).await
797    /// # };
798    /// ```
799    pub async fn delete_upload_entity<'a, T: Into<UploadEntityID<'a>>>(
800        &self,
801        upload_entity_id: T,
802    ) -> Result<<DeleteUploadEntityQuery<'_> as Query<A>>::Output> {
803        let query = DeleteUploadEntityQuery::new(upload_entity_id.into());
804        self.query(query).await
805    }
806    /// Removes a list of items from your recently played history.
807    /// ```no_run
808    /// # async {
809    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
810    ///     .await
811    ///     .unwrap();
812    /// let history = yt.get_history().await.unwrap();
813    /// let first_history_token = match history.first().unwrap().items.first().unwrap() {
814    ///     ytmapi_rs::parse::HistoryItem::Song(i) => &i.feedback_token_remove,
815    ///     ytmapi_rs::parse::HistoryItem::Video(i) => &i.feedback_token_remove,
816    ///     ytmapi_rs::parse::HistoryItem::Episode(i) => &i.feedback_token_remove,
817    ///     ytmapi_rs::parse::HistoryItem::UploadSong(i) => &i.feedback_token_remove,
818    /// }
819    /// .into();
820    /// yt.remove_history_items(vec![first_history_token]).await
821    /// # };
822    /// ```
823    pub async fn remove_history_items(
824        &self,
825        feedback_tokens: impl IntoIterator<Item = FeedbackTokenRemoveFromHistory<'_>>,
826    ) -> Result<Vec<ApiOutcome>> {
827        let query = RemoveHistoryItemsQuery::new(feedback_tokens);
828        self.query(query).await
829    }
830    // TODO: Docs / alternative constructors.
831    pub async fn edit_song_library_status(
832        &self,
833        query: EditSongLibraryStatusQuery<'_>,
834    ) -> Result<Vec<ApiOutcome>> {
835        self.query(query).await
836    }
837    /// Sets the like status for a song.
838    /// ```no_run
839    /// # async {
840    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
841    ///     .await
842    ///     .unwrap();
843    /// let results = yt
844    ///     .search_songs("While My Guitar Gently Weeps")
845    ///     .await
846    ///     .unwrap();
847    /// yt.rate_song(&results[0].video_id, ytmapi_rs::common::LikeStatus::Liked)
848    ///     .await
849    /// # };
850    /// ```
851    pub async fn rate_song<'a, T: Into<VideoID<'a>>>(
852        &self,
853        video_id: T,
854        rating: LikeStatus,
855    ) -> Result<()> {
856        let query = RateSongQuery::new(video_id.into(), rating);
857        self.query(query).await
858    }
859    /// Sets the like status for a playlist.
860    /// A 'Liked' playlist will be added to your library, an 'Indifferent' will
861    /// be removed, and a 'Disliked' will reduce the chance of it appearing in
862    /// your recommendations.
863    ///  ```no_run
864    /// # async {
865    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
866    /// let results = yt.search_featured_playlists("Heavy metal")
867    ///     .await
868    ///     .unwrap();
869    /// yt.rate_playlist(
870    ///     &results[0].playlist_id,
871    ///     ytmapi_rs::common::LikeStatus::Liked,
872    /// ).await
873    /// # };
874    /// ```
875    pub async fn rate_playlist<'a, T: Into<PlaylistID<'a>>>(
876        &self,
877        playlist_id: T,
878        rating: LikeStatus,
879    ) -> Result<()> {
880        let query = RatePlaylistQuery::new(playlist_id.into(), rating);
881        self.query(query).await
882    }
883    /// Deletes a playlist you own.
884    ///  ```no_run
885    /// # async {
886    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
887    /// let results = yt.get_library_playlists().await.unwrap();
888    /// yt.delete_playlist(&results[0].playlist_id).await
889    /// # };
890    /// ```
891    pub async fn delete_playlist<'a, T: Into<PlaylistID<'a>>>(&self, playlist_id: T) -> Result<()> {
892        let query = DeletePlaylistQuery::new(playlist_id.into());
893        self.query(query).await
894    }
895    /// Creates a new playlist.
896    ///  ```no_run
897    /// # async {
898    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
899    /// let playlists = yt.search_featured_playlists("Heavy metal")
900    ///     .await
901    ///     .unwrap();
902    /// let query = ytmapi_rs::query::CreatePlaylistQuery::new(
903    ///     "My heavy metal playlist",
904    ///     None,
905    ///     ytmapi_rs::query::playlist::PrivacyStatus::Public,
906    /// )
907    ///     .with_source(&playlists[0].playlist_id);
908    /// yt.create_playlist(query).await
909    /// # };
910    /// ```
911    pub async fn create_playlist<T: CreatePlaylistType>(
912        &self,
913        query: CreatePlaylistQuery<'_, T>,
914    ) -> Result<PlaylistID<'static>> {
915        self.query(query).await
916    }
917    /// Adds video items to a playlist you own.
918    ///  ```no_run
919    /// # async {
920    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
921    /// let ytmapi_rs::parse::LibraryPlaylist { playlist_id, .. } =
922    ///     yt.get_library_playlists().await.unwrap().pop().unwrap();
923    /// let songs = yt.search_songs("Master of puppets").await.unwrap();
924    /// yt.add_video_items_to_playlist(
925    ///     playlist_id,
926    ///     songs.iter().map(|s| (&s.video_id).into())
927    /// ).await
928    /// # };
929    /// ```
930    pub async fn add_video_items_to_playlist<'a, T: Into<PlaylistID<'a>>>(
931        &self,
932        playlist_id: T,
933        video_ids: impl IntoIterator<Item = VideoID<'a>>,
934    ) -> Result<Vec<AddPlaylistItem>> {
935        let query = AddPlaylistItemsQuery::new_from_videos(
936            playlist_id.into(),
937            video_ids,
938            DuplicateHandlingMode::default(),
939        );
940        self.query(query).await
941    }
942    /// Appends another playlist to a playlist you own.
943    ///  ```no_run
944    /// # async {
945    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE").await.unwrap();
946    /// let ytmapi_rs::parse::LibraryPlaylist { playlist_id, .. } =
947    ///     yt.get_library_playlists().await.unwrap().pop().unwrap();
948    /// let source_playlist = yt.search_featured_playlists("Heavy metal")
949    ///     .await
950    ///     .unwrap();
951    /// yt.add_playlist_to_playlist(
952    ///     playlist_id,
953    ///     &source_playlist[0].playlist_id
954    /// ).await
955    /// # };
956    /// ```
957    pub async fn add_playlist_to_playlist<'a, T: Into<PlaylistID<'a>>, U: Into<PlaylistID<'a>>>(
958        &self,
959        destination_playlist: T,
960        source_playlist: U,
961    ) -> Result<Vec<AddPlaylistItem>> {
962        let query = AddPlaylistItemsQuery::new_from_playlist(
963            destination_playlist.into(),
964            source_playlist.into(),
965        );
966        self.query(query).await
967    }
968    /// Gets a list of all playlists in your Library.
969    /// ```no_run
970    /// # async {
971    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
972    ///     .await
973    ///     .unwrap();
974    /// yt.get_library_playlists().await;
975    /// # };
976    /// ```
977    pub async fn get_library_playlists(&self) -> Result<Vec<LibraryPlaylist>> {
978        let query = GetLibraryPlaylistsQuery;
979        self.query(query).await
980    }
981    /// Gets a list of all artists in your Library.
982    /// # Additional functionality
983    /// See [`GetLibraryArtistsQuery`] and [`YtMusic.query()`]
984    /// for more ways to construct and run.
985    ///
986    /// [`YtMusic.query()`]: crate::YtMusic::query
987    /// [GetLibraryArtistsQuery]: crate::query::GetLibraryArtistsQuery
988    ///
989    /// ```no_run
990    /// # async {
991    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
992    ///     .await
993    ///     .unwrap();
994    /// let results = yt.get_library_artists().await;
995    /// # };
996    /// ```
997    pub async fn get_library_artists(&self) -> Result<Vec<LibraryArtist>> {
998        let query = GetLibraryArtistsQuery::default();
999        self.query(query).await
1000    }
1001    /// Gets a list of all songs in your Library.
1002    /// # Additional functionality
1003    /// See [`GetLibrarySongsQuery`] and [`YtMusic.query()`]
1004    /// for more ways to construct and run.
1005    ///
1006    /// [`YtMusic.query()`]: crate::YtMusic::query
1007    /// [GetLibrarySongsQuery]: crate::query::GetLibrarySongsQuery
1008    ///
1009    /// ```no_run
1010    /// # async {
1011    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1012    ///     .await
1013    ///     .unwrap();
1014    /// let results = yt.get_library_songs().await;
1015    /// # };
1016    /// ```
1017    pub async fn get_library_songs(&self) -> Result<<GetLibrarySongsQuery as Query<A>>::Output> {
1018        let query = GetLibrarySongsQuery::default();
1019        self.query(query).await
1020    }
1021    /// Gets a list of all albums in your Library.
1022    /// # Additional functionality
1023    /// See [`GetLibraryAlbumsQuery`] and [`YtMusic.query()`]
1024    /// for more ways to construct and run.
1025    ///
1026    /// [`YtMusic.query()`]: crate::YtMusic::query
1027    /// [GetLibraryAlbumsQuery]: crate::query::GetLibraryAlbumsQuery
1028    ///
1029    /// ```no_run
1030    /// # async {
1031    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1032    ///     .await
1033    ///     .unwrap();
1034    /// let results = yt.get_library_albums().await;
1035    /// # };
1036    /// ```
1037    pub async fn get_library_albums(&self) -> Result<Vec<SearchResultAlbum>> {
1038        let query = GetLibraryAlbumsQuery::default();
1039        self.query(query).await
1040    }
1041    /// Gets a list of all artist subscriptions in your Library.
1042    /// # Additional functionality
1043    /// See [`GetLibraryArtistSubscriptionsQuery`] and [`YtMusic.query()`]
1044    /// for more ways to construct and run.
1045    ///
1046    /// [`YtMusic.query()`]: crate::YtMusic::query
1047    /// [GetLibraryArtistSubscriptionsQuery]: crate::query::GetLibraryArtistSubscriptionsQuery
1048    ///
1049    /// ```no_run
1050    /// # async {
1051    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1052    ///     .await
1053    ///     .unwrap();
1054    /// let results = yt.get_library_artist_subscriptions().await;
1055    /// # };
1056    /// ```
1057    pub async fn get_library_artist_subscriptions(&self) -> Result<Vec<LibraryArtistSubscription>> {
1058        let query = GetLibraryArtistSubscriptionsQuery::default();
1059        self.query(query).await
1060    }
1061    /// Gets a list of all podcasts in your Library.
1062    /// # Additional functionality
1063    /// See [`GetLibraryPodcastsQuery`] and [`YtMusic.query()`]
1064    /// for more ways to construct and run.
1065    ///
1066    /// [`YtMusic.query()`]: crate::YtMusic::query
1067    /// [GetLibraryPodcastsQuery]: crate::query::GetLibraryPodcastsQuery
1068    ///
1069    /// ```no_run
1070    /// # async {
1071    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1072    ///     .await
1073    ///     .unwrap();
1074    /// let results = yt.get_library_podcasts().await;
1075    /// # };
1076    /// ```
1077    pub async fn get_library_podcasts(
1078        &self,
1079    ) -> Result<<GetLibraryPodcastsQuery as Query<A>>::Output> {
1080        let query = GetLibraryPodcastsQuery::default();
1081        self.query(query).await
1082    }
1083    /// Gets a list of all channels in your Library.
1084    /// # Additional functionality
1085    /// See [`GetLibraryChannelsQuery`] and [`YtMusic.query()`]
1086    /// for more ways to construct and run.
1087    ///
1088    /// [`YtMusic.query()`]: crate::YtMusic::query
1089    /// [GetLibraryChannelsQuery]: crate::query::GetLibraryChannelsQuery
1090    ///
1091    /// ```no_run
1092    /// # async {
1093    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1094    ///     .await
1095    ///     .unwrap();
1096    /// let results = yt.get_library_channels().await;
1097    /// # };
1098    /// ```
1099    pub async fn get_library_channels(
1100        &self,
1101    ) -> Result<<GetLibraryChannelsQuery as Query<A>>::Output> {
1102        let query = GetLibraryChannelsQuery::default();
1103        self.query(query).await
1104    }
1105    /// Gets your recently played history.
1106    /// ```no_run
1107    /// # async {
1108    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1109    ///     .await
1110    ///     .unwrap();
1111    /// let results = yt.get_history().await;
1112    /// # };
1113    /// ```
1114    pub async fn get_history(&self) -> Result<Vec<HistoryPeriod>> {
1115        let query = GetHistoryQuery;
1116        self.query(query).await
1117    }
1118    /// Adds an item to the accounts history.
1119    /// ```no_run
1120    /// # async {
1121    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1122    ///     .await
1123    ///     .unwrap();
1124    /// let song = yt
1125    ///     .search_songs("While My Guitar Gently Weeps")
1126    ///     .await
1127    ///     .unwrap()
1128    ///     .into_iter()
1129    ///     .next()
1130    ///     .unwrap();
1131    /// let url = yt.get_song_tracking_url(song.video_id).await.unwrap();
1132    /// yt.add_history_item(url).await
1133    /// # };
1134    /// ```
1135    pub async fn add_history_item<'a, T: Into<SongTrackingUrl<'a>>>(
1136        &self,
1137        song_url: T,
1138    ) -> Result<<AddHistoryItemQuery<'a> as Query<A>>::Output> {
1139        self.query(AddHistoryItemQuery::new(song_url.into())).await
1140    }
1141    /// Subscribe to an artist.
1142    /// This does not error if the artist was already subscribed to.
1143    /// ```no_run
1144    /// # async {
1145    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1146    ///     .await
1147    ///     .unwrap();
1148    /// let the_beatles = &yt.search_artists("The Beatles").await.unwrap()[0].browse_id;
1149    /// yt.subscribe_artist(the_beatles).await
1150    /// # };
1151    /// ```
1152    pub async fn subscribe_artist(&self, channel_id: impl Into<ArtistChannelID<'_>>) -> Result<()> {
1153        self.query(SubscribeArtistQuery::new(channel_id.into()))
1154            .await
1155    }
1156    /// Unsubscribe to one or more artists.
1157    /// This does not error if the artists were not subscribed.
1158    /// ```no_run
1159    /// # async {
1160    /// let yt = ytmapi_rs::YtMusic::from_cookie("FAKE COOKIE")
1161    ///     .await
1162    ///     .unwrap();
1163    /// let some_beatles = yt
1164    ///     .search_artists("The Beatles")
1165    ///     .await
1166    ///     .unwrap()
1167    ///     .into_iter()
1168    ///     .map(|artist| artist.browse_id)
1169    ///     .take(2);
1170    /// yt.unsubscribe_artists(some_beatles).await
1171    /// # };
1172    /// ```
1173    pub async fn unsubscribe_artists<'a>(
1174        &self,
1175        channels: impl IntoIterator<Item = impl Into<ArtistChannelID<'a>>>,
1176    ) -> Result<()> {
1177        self.query(UnsubscribeArtistsQuery::new(
1178            channels.into_iter().map(Into::into),
1179        ))
1180        .await
1181    }
1182}