use crate::*;
use serde::Deserialize;
pub async fn user_follows_artists(
token: &AccessToken,
ids: &[&str],
) -> Result<Vec<bool>, EndpointError<Error>> {
if ids.is_empty() {
return Ok(Vec::new());
}
Ok(request!(
token,
GET "/v1/me/following/contains",
query_params = {"type": "artist", "ids": &&ids.join(",")},
ret = Vec<bool>
))
}
pub async fn user_follows_users(
token: &AccessToken,
ids: &[&str],
) -> Result<Vec<bool>, EndpointError<Error>> {
if ids.is_empty() {
return Ok(Vec::new());
}
Ok(request!(
token,
GET "/v1/me/following/contains",
query_params = {"type": "user", "ids": &&ids.join(",")},
ret = Vec<bool>
))
}
pub async fn users_follow_playlist(
token: &AccessToken,
id: &str,
user_ids: &[&str],
) -> Result<Vec<bool>, EndpointError<Error>> {
if user_ids.is_empty() {
return Ok(Vec::new());
}
Ok(request!(
token,
GET "/v1/playlists/{}/followers/contains",
path_params = [id],
query_params = {"ids": &&user_ids.join(",")},
ret = Vec<bool>
))
}
pub async fn follow_artists(token: &AccessToken, ids: &[&str]) -> Result<(), EndpointError<Error>> {
if ids.is_empty() {
return Ok(());
}
request!(
token,
PUT "/v1/me/following",
query_params = {"type": "artist", "ids": &ids.join(",")},
body = "{}"
);
Ok(())
}
pub async fn follow_users(token: &AccessToken, ids: &[&str]) -> Result<(), EndpointError<Error>> {
if ids.is_empty() {
return Ok(());
}
request!(
token,
PUT "/v1/me/following",
query_params = {"type": "user", "ids": &ids.join(",")},
body = "{}"
);
Ok(())
}
pub async fn follow_playlist_public(
token: &AccessToken,
id: &str,
) -> Result<(), EndpointError<Error>> {
request!(
token,
PUT "/v1/playlists/{}/followers",
path_params = [id],
header_params = {"Content-Type": "application/json"},
body = "{\"public\": true}"
);
Ok(())
}
pub async fn follow_playlist_private(
token: &AccessToken,
id: &str,
) -> Result<(), EndpointError<Error>> {
request!(
token,
PUT "/v1/playlists/{}/followers",
path_params = [id],
header_params = {"Content-Type": "application/json"},
body = "{\"public\": false}"
);
Ok(())
}
pub async fn get_followed_artists(
token: &AccessToken,
limit: usize,
after: Option<&str>,
) -> Result<CursorPage<Artist>, EndpointError<Error>> {
#[derive(Deserialize)]
struct Response {
artists: CursorPage<Artist>,
};
Ok(request!(
token,
GET "/v1/me/following",
query_params = {"type": "artist", "limit": &limit.to_string()},
optional_query_params = {"after": after},
ret = Response
)
.artists)
}
pub async fn unfollow_artists(
token: &AccessToken,
ids: &[&str],
) -> Result<(), EndpointError<Error>> {
if ids.is_empty() {
return Ok(());
}
request!(
token,
DELETE "/v1/me/following",
query_params = {"type": "artist", "ids": &ids.join(",")},
body = "{}"
);
Ok(())
}
pub async fn unfollow_users(token: &AccessToken, ids: &[&str]) -> Result<(), EndpointError<Error>> {
if ids.is_empty() {
return Ok(());
}
request!(
token,
DELETE "/v1/me/following",
query_params = {"type": "user", "ids": &ids.join(",")},
body = "{}"
);
Ok(())
}
pub async fn unfollow_playlist(token: &AccessToken, id: &str) -> Result<(), EndpointError<Error>> {
request!(
token,
DELETE "/v1/playlists/{}/followers",
path_params = [id],
body = "{}"
);
Ok(())
}
#[cfg(test)]
mod tests {
use crate::endpoints::token;
use crate::*;
#[tokio::test]
async fn test_follow_artists() {
let token = token().await;
let artists = &[
"0PFtn5NtBbbUNbU9EAmIWF",
"7dGJo4pcD2V6oG8kP0tJRR",
"4llAOeA6kEF4ytaB2fsmcW",
];
let split = 2;
let (followed_artists, unfollowed_artists) = artists.split_at(split);
let old = user_follows_artists(&token, artists).await.unwrap();
follow_artists(&token, followed_artists).await.unwrap();
unfollow_artists(&token, unfollowed_artists).await.unwrap();
let check = user_follows_artists(&token, artists).await.unwrap();
let (follow_check, unfollow_check) = check.split_at(split);
assert!(follow_check.into_iter().all(|&followed| followed));
assert!(unfollow_check.into_iter().all(|&followed| !followed));
let followed = get_followed_artists(&token, 50, None).await.unwrap();
if followed.total <= 50 {
for followed_artist in followed_artists {
assert!(followed
.items
.iter()
.any(|artist| artist.id == *followed_artist));
}
for unfollowed_artist in unfollowed_artists {
assert!(followed
.items
.iter()
.all(|artist| artist.id != *unfollowed_artist));
}
}
let mut old_followed = Vec::with_capacity(artists.len());
let mut old_unfollowed = Vec::with_capacity(artists.len());
for i in 0..artists.len() {
if old[i] {
&mut old_followed
} else {
&mut old_unfollowed
}
.push(artists[i]);
}
follow_artists(&token, &old_followed).await.unwrap();
unfollow_artists(&token, &old_unfollowed).await.unwrap();
}
#[tokio::test]
async fn test_follow_playlists() {
let token = token().await;
follow_playlist_public(&token, "37i9dQZF1DWYBF1dYDPlHw")
.await
.unwrap();
let id = get_current_user(&token).await.unwrap().id;
let followers = users_follow_playlist(&token, "37i9dQZF1DWYBF1dYDPlHw", &["spotify", &id])
.await
.unwrap();
assert_eq!(followers, &[false, true]);
unfollow_playlist(&token, "37i9dQZF1DWYBF1dYDPlHw")
.await
.unwrap();
}
}