Skip to content

Commit

Permalink
Merge pull request #29 from InputUsername/feature/new-endpoints
Browse files Browse the repository at this point in the history
Add additional endpoints
  • Loading branch information
InputUsername committed May 18, 2024
2 parents 4f64862 + 467fa4b commit 6a78974
Show file tree
Hide file tree
Showing 7 changed files with 525 additions and 20 deletions.
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,37 @@
## v0.8.0 (unreleased)

- Implemented additional API endpoints:
- Core:
- `/1/user/(user_name)/similar-users` - `Client::user_similar_users`;
- `/1/user/(user_name)/similar-to/(other_user_name)` - `Client::user_similar_to`.
- Playlists:
- `/1/user/(playlist_user_name)/playlists` - `Client::user_playlists`;
- `/1/user/(playlist_user_name)/playlists/createdfor` - `Client::user_playlists_created_for`;
- `/1/user/(playlist_user_name)/playlists/collaborator` - `Client::user_playlists_collaborator`;
- `/1/playlist/(playlist_mbid)` - `Client::get_playlist` ([#19], [@Kernald]).
- `/1/playlist/create` - `Client::playlist_create`;
- `/1/playlist/(playlist_mbid)/delete` - `Client::playlist_delete`;
- `/1/playlist/(playlist_mbid)/copy` - `Client::playlist_copy`;
- Statistics:
- `/1/stats/release-group/(release_group_mbid)/listeners` - `Client::release_group_listeners` ([#23], [@RustyNova016]).
- Social:
- `/1/user/(user_name)/followers` - `Client::user_followers`;
- `/1/user/(user_name)/following` - `Client::user_following`;
- `/1/user/(user_name)/follow` - `Client::user_follow`;
- `/1/user/(user_name)/unfollow` - `Client::user_unfollow`.
- Added types for playlists in MusicBrainz's [JSPF format].
- Added MBID mapping to `UserListensResponse` models ([#24], [@RustyNova016]).
- Added `Clone`, `PartialEq` and `Eq` derives for `raw` models ([#26], [@RustyNova016]).
- Removed the `time_range` parameter from `Client::user_listens` ([#24]).
- Removed the `time_range` parameter from `Client::user_listens` ([#24], [@RustyNova016]).
- Updated attohttpc dependency from 0.24 to 0.28.
- Pinned the minimum supported Rust version (MSRV) to 1.58.

[JSPF format]: https://musicbrainz.org/doc/jspf
[#19]: https://github.com/InputUsername/listenbrainz-rs/pull/19
[#23]: https://github.com/InputUsername/listenbrainz-rs/pull/23
[#24]: https://github.com/InputUsername/listenbrainz-rs/pull/24
[#26]: https://github.com/InputUsername/listenbrainz-rs/pull/26
[@Kernald]: https://github.com/Kernald
[@RustyNova016]: https://github.com/RustyNova016

## v0.7.0 (2023-02-12)
Expand Down
1 change: 1 addition & 0 deletions src/raw/mod.rs → src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

mod client;
mod endpoint;
pub mod jspf;
pub mod request;
pub mod response;

Expand Down
164 changes: 163 additions & 1 deletion src/raw/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use attohttpc::header::AUTHORIZATION;
use serde::Serialize;

use super::endpoint::Endpoint;
use super::jspf;
use super::request::*;
use super::response::*;
use crate::Error;
Expand Down Expand Up @@ -131,6 +132,65 @@ impl Client {
self.post(Endpoint::DeleteListen, token, data)
}

/// Endpoint: [`user/{user_name}/playlists/collaborator`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(playlist_user_name)-playlists-collaborator)
pub fn user_playlists_collaborator(
&self,
token: Option<&str>,
user_name: &str,
count: Option<u64>,
offset: Option<u64>,
) -> Result<UserPlaylistsCollaboratorResponse, Error> {
let endpoint = format!("{}{}", self.api_root_url, Endpoint::UserPlaylistsCollaborator(user_name));

let mut request = attohttpc::get(endpoint);

if let Some(token) = token {
request = request.header("Authorization", format!("Token {}", token));
}
if let Some(count) = count {
request = request.param("count", count);
}
if let Some(offset) = offset {
request = request.param("offset", offset);
}

let response = request.send()?;

ResponseType::from_response(response)
}

/// Endpoint: [`user/{user_name}/playlists/createdfor`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(playlist_user_name)-playlists-createdfor)
pub fn user_playlists_created_for(
&self,
token: Option<&str>,
user_name: &str,
count: Option<u64>,
offset: Option<u64>,
) -> Result<UserPlaylistsCollaboratorResponse, Error> {
let endpoint = format!("{}{}", self.api_root_url, Endpoint::UserPlaylistsCreatedFor(user_name));

let mut request = attohttpc::get(endpoint);

if let Some(token) = token {
request = request.header("Authorization", format!("Token {}", token));
}
if let Some(count) = count {
request = request.param("count", count);
}
if let Some(offset) = offset {
request = request.param("offset", offset);
}

let response = request.send()?;

ResponseType::from_response(response)
}

/// Endpoint: [`user/{user_name}/similar-users`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(user_name)-similar-users)
pub fn user_similar_users(&self, user_name: &str) -> Result<UserSimilarUsersResponse, Error> {
self.get(Endpoint::UserSimilarUsers(user_name))
}

/// Endpoint: [`user/{user_name}/listen-count`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(user_name)-listen-count)
pub fn user_listen_count(&self, user_name: &str) -> Result<UserListenCountResponse, Error> {
self.get(Endpoint::UserListenCount(user_name))
Expand All @@ -141,7 +201,43 @@ impl Client {
self.get(Endpoint::UserPlayingNow(user_name))
}

/// Endpoint: [`user/{user_name}/listens`](https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-listens)
/// Endpoint: [`user/{user_name}/similar-to/{other_user_name}`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(user_name)-similar-to-(other_user_name))
pub fn user_similar_to(
&self,
user_name: &str,
other_user_name: &str,
) -> Result<UserSimilarToResponse, Error> {
self.get(Endpoint::UserSimilarTo(user_name, other_user_name))
}

/// Endpoint: [`user/{user_name}/playlists`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(playlist_user_name)-playlists)
pub fn user_playlists(
&self,
token: Option<&str>,
user_name: &str,
count: Option<u64>,
offset: Option<u64>,
) -> Result<UserPlaylistsResponse, Error> {
let endpoint = format!("{}{}", self.api_root_url, Endpoint::UserPlaylists(user_name));

let mut request = attohttpc::get(endpoint);

if let Some(token) = token {
request = request.header("Authorization", format!("Token {}", token));
}
if let Some(count) = count {
request = request.param("count", count);
}
if let Some(offset) = offset {
request = request.param("offset", offset);
}

let response = request.send()?;

ResponseType::from_response(response)
}

/// Endpoint: [`user/{user_name}/listens`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(user_name)-listens)
pub fn user_listens(
&self,
user_name: &str,
Expand Down Expand Up @@ -179,6 +275,13 @@ impl Client {
ResponseType::from_response(response)
}

/// Endpoint:
/// [`playlist`](https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-playlist-(playlist_mbid))
/// (`GET`)
pub fn get_playlist(&self, playlist: &str) -> Result<GetPlaylistResponse, Error> {
self.get(Endpoint::Playlist(playlist))
}

/// Endpoint: [`latest-import`](https://listenbrainz.readthedocs.io/en/production/dev/api/#post--1-latest-import) (`POST`)
pub fn update_latest_import(
&self,
Expand All @@ -188,6 +291,33 @@ impl Client {
self.post(Endpoint::LatestImport, token, data)
}

/// Endpoint: [`playlist/create`](https://listenbrainz.readthedocs.io/en/production/dev/api/#post--1-playlist-create)
pub fn playlist_create(
&self,
token: &str,
playlist: jspf::Playlist,
) -> Result<PlaylistCreateResponse, Error> {
self.post(Endpoint::PlaylistCreate, token, playlist)
}

/// Endpoint: [`playlist/{playlist_mbid}/delete`](https://listenbrainz.readthedocs.io/en/production/dev/api/#post--1-playlist-(playlist_mbid)-delete)
pub fn playlist_delete(
&self,
token: &str,
playlist_mbid: &str,
) -> Result<PlaylistDeleteResponse, Error> {
self.post(Endpoint::PlaylistDelete(playlist_mbid), token, ())
}

/// Endpoint: [`playlist/{playlist_mbid}/copy`](https://listenbrainz.readthedocs.io/en/production/dev/api/#post--1-playlist-(playlist_mbid)-copy)
pub fn playlist_copy(
&self,
token: &str,
playlist_mbid: &str,
) -> Result<PlaylistCopyResponse, Error> {
self.post(Endpoint::PlaylistCopy(playlist_mbid), token, ())
}

/// Endpoint: [`stats/sitewide/artists`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-stats-sitewide-artists)
pub fn stats_sitewide_artists(
&self,
Expand Down Expand Up @@ -357,6 +487,38 @@ impl Client {

ResponseType::from_response(response)
}

/// Endpoint: [`user/{user_name}/followers`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(user_name)-followers)
pub fn user_followers(&self, user_name: &str) -> Result<UserFollowersResponse, Error> {
self.get(Endpoint::UserFollowers(user_name))
}

/// Endpoint: [`user/{user_name}/following`](https://listenbrainz.readthedocs.io/en/production/dev/api/#get--1-user-(user_name)-following)
pub fn user_following(&self, user_name: &str) -> Result<UserFollowingResponse, Error> {
self.get(Endpoint::UserFollowing(user_name))
}

/// Endpoint: [`user/{user_name}/unfollow`](https://listenbrainz.readthedocs.io/en/production/dev/api/#post--1-user-(user_name)-unfollow)
pub fn user_unfollow(&self, token: &str, user_name: &str) -> Result<UserUnfollowResponse, Error> {
let endpoint = format!("{}{}", self.api_root_url, Endpoint::UserUnfollow(user_name));

let response = attohttpc::post(endpoint)
.header("Authorization", format!("Token {}", token))
.send()?;

ResponseType::from_response(response)
}

/// Endpoint: [`user/{user_name}/follow`](https://listenbrainz.readthedocs.io/en/production/dev/api/#post--1-user-(user_name)-follow)
pub fn user_follow(&self, token: &str, user_name: &str) -> Result<UserFollowResponse, Error> {
let endpoint = format!("{}{}", self.api_root_url, Endpoint::UserFollow(user_name));

let response = attohttpc::post(endpoint)
.header("Authorization", format!("Token {}", token))
.send()?;

ResponseType::from_response(response)
}
}

impl Default for Client {
Expand Down
65 changes: 47 additions & 18 deletions src/raw/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ pub enum Endpoint<'a> {
SubmitListens,
ValidateToken,
DeleteListen,
UserPlaylistsCollaborator(&'a str),
UserPlaylistsCreatedFor(&'a str),
UserSimilarUsers(&'a str),
UserListenCount(&'a str),
UserPlayingNow(&'a str),
UserSimilarTo(&'a str, &'a str),
UserPlaylists(&'a str),
UserListens(&'a str),
LatestImport,
Playlist(&'a str),
PlaylistCreate,
PlaylistDelete(&'a str),
PlaylistCopy(&'a str),
StatsSitewideArtists,
StatsUserListeningActivity(&'a str),
StatsUserDailyActivity(&'a str),
Expand All @@ -17,32 +26,52 @@ pub enum Endpoint<'a> {
StatsUserArtists(&'a str),
StatsReleaseGroupListeners(&'a str),
StatusGetDumpInfo,
UserFollowers(&'a str),
UserFollowing(&'a str),
UserUnfollow(&'a str),
UserFollow(&'a str),
}

impl<'a> fmt::Display for Endpoint<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Self::SubmitListens => "submit-listens",
Self::ValidateToken => "validate-token",
Self::DeleteListen => "delete-listen",
Self::UserListenCount(user) => return write!(f, "user/{user}/listen-count"),
Self::UserPlayingNow(user) => return write!(f, "user/{user}/playing-now"),
Self::UserListens(user) => return write!(f, "user/{user}/listens"),
Self::LatestImport => "latest-import",
Self::StatsSitewideArtists => "stats/sitewide/artists",
match self {
Self::SubmitListens => write!(f, "submit-listens"),
Self::ValidateToken => write!(f, "validate-token"),
Self::DeleteListen => write!(f, "delete-listen"),
Self::UserPlaylistsCollaborator(user) => {
write!(f, "user/{}/playlists/collaborator", user)
}
Self::UserPlaylistsCreatedFor(user) => write!(f, "user/{}/playlists/createdfor", user),
Self::UserSimilarUsers(user) => write!(f, "user/{}/similar-users", user),
Self::UserListenCount(user) => write!(f, "user/{}/listen-count", user),
Self::UserPlayingNow(user) => write!(f, "user/{}/playing-now", user),
Self::UserSimilarTo(user, other_user) => {
write!(f, "user/{}/similar-to/{}", user, other_user)
}
Self::UserPlaylists(user) => write!(f, "user/{}/playlists", user),
Self::UserListens(user) => write!(f, "user/{}/listens", user),
Self::LatestImport => write!(f, "latest-import"),
Self::Playlist(playlist) => write!(f, "playlist/{}", playlist),
Self::PlaylistCreate => write!(f, "playlist/create"),
Self::PlaylistDelete(playlist) => write!(f, "playlist/{}/delete", playlist),
Self::PlaylistCopy(playlist) => write!(f, "playlist/{}/copy", playlist),
Self::StatsSitewideArtists => write!(f, "stats/sitewide/artists"),
Self::StatsUserListeningActivity(user) => {
return write!(f, "stats/user/{user}/listening-activity")
write!(f, "stats/user/{}/listening-activity", user)
}
Self::StatsUserDailyActivity(user) => {
return write!(f, "stats/user/{user}/daily-activity")
write!(f, "stats/user/{}/daily-activity", user)
}
Self::StatsUserRecordings(user) => return write!(f, "stats/user/{user}/recordings"),
Self::StatsUserArtistMap(user) => return write!(f, "stats/user/{user}/artist-map"),
Self::StatsUserReleases(user) => return write!(f, "stats/user/{user}/releases"),
Self::StatsUserArtists(user) => return write!(f, "stats/user/{user}/artists"),
Self::StatsUserRecordings(user) => write!(f, "stats/user/{}/recordings", user),
Self::StatsUserArtistMap(user) => write!(f, "stats/user/{}/artist-map", user),
Self::StatsUserReleases(user) => write!(f, "stats/user/{}/releases", user),
Self::StatsUserArtists(user) => write!(f, "stats/user/{}/artists", user),
Self::StatsReleaseGroupListeners(release_group_mbid) => return write!(f, "stats/release-group/{release_group_mbid}/listeners"),
Self::StatusGetDumpInfo => "status/get-dump-info",
};
write!(f, "{s}")
Self::StatusGetDumpInfo => write!(f, "status/get-dump-info"),
Self::UserFollowers(user) => write!(f, "user/{}/followers", user),
Self::UserFollowing(user) => write!(f, "user/{}/following", user),
Self::UserUnfollow(user) => write!(f, "user/{}/unfollow", user),
Self::UserFollow(user) => write!(f, "user/{}/follow", user),
}
}
}
Loading

0 comments on commit 6a78974

Please sign in to comment.