spotify_rs/endpoint/
user.rs

1use serde::Serialize;
2use serde_json::json;
3
4use crate::{
5    auth::{AuthFlow, Authorised},
6    client::Body,
7    error::Result,
8    model::{
9        artist::{Artist, PagedArtists},
10        track::Track,
11        user::{PrivateUser, TimeRange, User},
12        CursorPage, Page,
13    },
14    query_list, Nil,
15};
16
17use super::{Client, Endpoint, EndpointPrivate};
18
19pub async fn get_current_user_profile(
20    spotify: &Client<impl AuthFlow + Authorised>,
21) -> Result<PrivateUser> {
22    spotify.get::<(), _>("/me".to_owned(), None).await
23}
24
25pub fn current_user_top_artists() -> UserTopItemsEndpoint<ArtistsMarker> {
26    UserTopItemsEndpoint::default()
27}
28
29pub fn current_user_top_tracks() -> UserTopItemsEndpoint<TracksMarker> {
30    UserTopItemsEndpoint::default()
31}
32
33pub async fn get_user(id: impl Into<String>, spotify: &Client<impl AuthFlow>) -> Result<User> {
34    spotify
35        .get::<(), _>(format!("/users/{}", id.into()), None)
36        .await
37}
38
39pub fn follow_playlist(id: impl Into<String>) -> FollowPlaylistEndpoint {
40    FollowPlaylistEndpoint {
41        id: id.into(),
42        public: None,
43    }
44}
45
46pub async fn unfollow_playlist(
47    id: impl Into<String>,
48    spotify: &Client<impl AuthFlow + Authorised>,
49) -> Result<Nil> {
50    spotify
51        .delete::<(), _>(format!("/playlists/{}/followers", id.into()), None)
52        .await
53}
54
55pub fn followed_artists() -> FollowedArtistsEndpoint {
56    // Currently only the "artist" type is supported, so it's hardcoded.
57    FollowedArtistsEndpoint {
58        r#type: "artist".to_owned(),
59        ..Default::default()
60    }
61}
62
63pub async fn follow_artists<T: AsRef<str>>(
64    ids: &[T],
65    spotify: &Client<impl AuthFlow + Authorised>,
66) -> Result<Nil> {
67    let ids: Vec<String> = ids.iter().map(|i| i.as_ref().to_owned()).collect();
68
69    spotify
70        .put(
71            "/me/following?type=artist".to_owned(),
72            Body::Json(json!({ "ids": ids })),
73        )
74        .await
75}
76
77pub async fn unfollow_artists<T: AsRef<str>>(
78    ids: &[T],
79    spotify: &Client<impl AuthFlow + Authorised>,
80) -> Result<Nil> {
81    let ids: Vec<String> = ids.iter().map(|i| i.as_ref().to_owned()).collect();
82
83    spotify
84        .delete(
85            "/me/following?type=artist".to_owned(),
86            Body::Json(json!({ "ids": ids })),
87        )
88        .await
89}
90
91pub async fn check_if_user_follows_artists<T: AsRef<str>>(
92    ids: &[T],
93    spotify: &Client<impl AuthFlow + Authorised>,
94) -> Result<Vec<bool>> {
95    spotify
96        .get::<(), _>(
97            format!("/me/following/contains?type=artist&ids={}", query_list(ids)),
98            None,
99        )
100        .await
101}
102
103pub async fn follow_users<T: AsRef<str>>(
104    ids: &[T],
105    spotify: &Client<impl AuthFlow + Authorised>,
106) -> Result<Nil> {
107    let ids: Vec<String> = ids.iter().map(|i| i.as_ref().to_owned()).collect();
108
109    spotify
110        .put(
111            "/me/following?type=user".to_owned(),
112            Body::Json(json!({ "ids": ids })),
113        )
114        .await
115}
116
117pub async fn unfollow_users<T: AsRef<str>>(
118    ids: &[T],
119    spotify: &Client<impl AuthFlow + Authorised>,
120) -> Result<Nil> {
121    let ids: Vec<String> = ids.iter().map(|i| i.as_ref().to_owned()).collect();
122
123    spotify
124        .delete(
125            "/me/following?type=user".to_owned(),
126            Body::Json(json!({ "ids": ids })),
127        )
128        .await
129}
130
131pub async fn check_if_user_follows_users<T: AsRef<str>>(
132    ids: &[T],
133    spotify: &Client<impl AuthFlow + Authorised>,
134) -> Result<Vec<bool>> {
135    spotify
136        .get::<(), _>(
137            format!("/me/following/contains?type=user&ids={}", query_list(ids)),
138            None,
139        )
140        .await
141}
142
143pub async fn check_if_current_user_follow_playlist(
144    playlist_id: impl Into<String>,
145    spotify: &Client<impl AuthFlow + Authorised>,
146) -> Result<Vec<bool>> {
147    spotify
148        .get::<(), _>(
149            format!("/playlists/{}/followers/contains", playlist_id.into()),
150            None,
151        )
152        .await
153}
154
155pub trait ItemType: private::Sealed {}
156impl ItemType for ArtistsMarker {}
157impl ItemType for TracksMarker {}
158
159#[derive(Clone, Copy, Debug, Default)]
160pub struct ArtistsMarker;
161#[derive(Clone, Copy, Debug, Default)]
162pub struct TracksMarker;
163
164mod private {
165    pub trait Sealed {}
166
167    impl Sealed for super::ArtistsMarker {}
168    impl Sealed for super::TracksMarker {}
169}
170
171impl<I: ItemType> Endpoint for UserTopItemsEndpoint<I> {}
172impl Endpoint for FollowPlaylistEndpoint {}
173impl Endpoint for FollowedArtistsEndpoint {}
174
175#[derive(Clone, Debug, Default, Serialize)]
176pub struct UserTopItemsEndpoint<ItemType> {
177    pub(crate) time_range: Option<TimeRange>,
178    pub(crate) limit: Option<u32>,
179    pub(crate) offset: Option<u32>,
180    #[serde(skip)]
181    marker: std::marker::PhantomData<ItemType>,
182}
183
184impl<I: ItemType> UserTopItemsEndpoint<I> {
185    /// The time frame of the computed affinities.
186    pub fn time_range(mut self, time_range: TimeRange) -> Self {
187        self.time_range = Some(time_range);
188        self
189    }
190
191    #[doc = include_str!("../docs/limit.md")]
192    pub fn limit(mut self, limit: u32) -> Self {
193        self.limit = Some(limit);
194        self
195    }
196
197    #[doc = include_str!("../docs/offset.md")]
198    pub fn offset(mut self, offset: u32) -> Self {
199        self.offset = Some(offset);
200        self
201    }
202}
203
204impl UserTopItemsEndpoint<ArtistsMarker> {
205    #[doc = include_str!("../docs/send.md")]
206    pub async fn get(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Page<Artist>> {
207        spotify.get("/me/top/artists".to_owned(), self).await
208    }
209}
210
211impl UserTopItemsEndpoint<TracksMarker> {
212    #[doc = include_str!("../docs/send.md")]
213    pub async fn get(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Page<Track>> {
214        spotify.get("/me/top/tracks".to_owned(), self).await
215    }
216}
217
218#[derive(Clone, Debug, Default, Serialize)]
219pub struct FollowPlaylistEndpoint {
220    #[serde(skip)]
221    pub(crate) id: String,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub(crate) public: Option<bool>,
224}
225
226impl FollowPlaylistEndpoint {
227    /// If set to `true`, the playlist will be included in the user's
228    /// public playlists. Defaults to `true`.
229    pub fn public(mut self, public: bool) -> Self {
230        self.public = Some(public);
231        self
232    }
233
234    #[doc = include_str!("../docs/send.md")]
235    pub async fn send(self, spotify: &Client<impl AuthFlow + Authorised>) -> Result<Nil> {
236        spotify
237            .put(format!("/playlists/{}/followers", self.id), self.json())
238            .await
239    }
240}
241
242#[derive(Clone, Debug, Default, Serialize)]
243pub struct FollowedArtistsEndpoint {
244    pub(crate) r#type: String,
245    pub(crate) after: Option<String>,
246    pub(crate) limit: Option<u32>,
247}
248
249impl FollowedArtistsEndpoint {
250    /// The last artist ID retrieved from the previous request.
251    pub fn after(mut self, artist_id: impl Into<String>) -> Self {
252        self.after = Some(artist_id.into());
253        self
254    }
255
256    #[doc = include_str!("../docs/limit.md")]
257    pub fn limit(mut self, limit: u32) -> Self {
258        self.limit = Some(limit);
259        self
260    }
261
262    #[doc = include_str!("../docs/send.md")]
263    pub async fn get(
264        self,
265        spotify: &Client<impl AuthFlow + Authorised>,
266    ) -> Result<CursorPage<Artist, Self>> {
267        spotify
268            .get("/me/following".to_owned(), self)
269            .await
270            .map(|a: PagedArtists| a.artists)
271    }
272}