diff --git a/src/raw/response.rs b/src/raw/response.rs deleted file mode 100644 index 98df1d5..0000000 --- a/src/raw/response.rs +++ /dev/null @@ -1,700 +0,0 @@ -//! Low-level response data models. -//! -//! Every response type has the `rate_limit` field, which contains rate limiting -//! information. See the documentation of the [`RateLimit`] type for more -//! details. - -#![allow(missing_docs)] - -use std::collections::HashMap; - -use attohttpc::Response; -use serde::de::DeserializeOwned; -use serde::Deserialize; -use serde::Serialize; - -use super::jspf; -use crate::Error; - -/// Contains rate limiting information. -/// -/// ListenBrainz API rate limiting is described in the [API docs]. -/// Prefer using the [`RateLimit::reset_in`] field over [`RateLimit::reset`], -/// as the former is resilient against clients with incorrect clocks. -/// -/// [API docs]: https://listenbrainz.readthedocs.io/en/production/dev/api/#rate-limiting -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RateLimit { - pub limit: u64, - pub remaining: u64, - pub reset_in: u64, - pub reset: i64, -} - -impl RateLimit { - /// Extract rate limiting information from the `X-RateLimit-` headers. - /// Only returns `Some` if all fields are present and valid. - fn from_headers(response: &Response) -> Option { - let headers = response.headers(); - - let limit = headers - .get("X-RateLimit-Limit")? - .to_str() - .ok()? - .parse() - .ok()?; - let remaining = headers - .get("X-RateLimit-Remaining")? - .to_str() - .ok()? - .parse() - .ok()?; - let reset_in = headers - .get("X-RateLimit-Reset-In")? - .to_str() - .ok()? - .parse() - .ok()?; - let reset = headers - .get("X-RateLimit-Reset")? - .to_str() - .ok()? - .parse() - .ok()?; - - Some(Self { - limit, - remaining, - reset_in, - reset, - }) - } -} - -/// Internal trait for response types. -/// Allows converting the response type from an `attohttpc::Response`, -/// by deserializing the body into the response type and then -/// adding the `rate_limit` field from headers. -pub(crate) trait ResponseType: DeserializeOwned { - fn from_response(response: Response) -> Result; -} - -/// Internal macro for response types. -/// Wraps the definition of a response type, adds the `rate_limit` field, -/// and implements the `ResponseType` trait. -macro_rules! response_type { - ( - $(#[$meta:meta])* - pub struct $name:ident { - $(pub $field:ident: $field_ty:ty),* - $(,)? - } - ) => { - $(#[$meta])* - pub struct $name { - #[serde(skip)] - pub rate_limit: Option, - $(pub $field: $field_ty),* - } - - impl ResponseType for $name { - fn from_response(response: Response) -> Result { - let response = Error::try_from_error_response(response)?; - let rate_limit = RateLimit::from_headers(&response); - let mut result: Self = response.json()?; - result.rate_limit = rate_limit; - Ok(result) - } - } - } -} - -// --------- submit-listens - -response_type! { - /// Response type for [`Client::submit_listens`](super::Client::submit_listens). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct SubmitListensResponse { - pub status: String, - } -} - -// --------- validate-token - -response_type! { - /// Response type for [`Client::validate_token`](super::Client::validate_token). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct ValidateTokenResponse { - pub code: u16, - pub message: String, - - pub valid: bool, - pub user_name: Option, - } -} - -// --------- delete-listen - -response_type! { - /// Response type for [`Client::delete_listen`](super::Client::delete_listen). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct DeleteListenResponse { - pub status: String, - } -} - -// --------- user/{user_name}/playlists/collaborator - -response_type! { - /// Response type for [`Client::user_playlists_collaborator`](super::Client::user_playlists_collaborator). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserPlaylistsCollaboratorResponse { - pub count: u64, - pub offset: u64, - pub playlist_count: u64, - pub playlists: Vec, - } -} - -// --------- user/{user_name}/playlists/createdfor - -response_type! { - /// Response type for [`Client::user_playlists_created_for`](super::Client::user_playlists_created_for). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserPlaylistsCreatedForResponse { - pub count: u64, - pub offset: u64, - pub playlist_count: u64, - pub playlists: Vec, - } -} - -// --------- user/{user_name}/similar-users - -response_type! { - /// Response type for [`Client::user_similar_users`](super::Client::user_similar_users). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserSimilarUsersResponse { - pub payload: Vec, - } -} - -/// Type of the [`UserSimilarUsersResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize)] -pub struct UserSimilarUsersPayload { - pub user_name: String, - pub similarity: f64, -} - -// --------- user/{user_name}/listen-count - -response_type! { - /// Response type for [`Client::user_listen_count`](super::Client::user_listen_count). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct UserListenCountResponse { - pub payload: UserListenCountPayload, - } -} - -/// Type of the [`UserListenCountResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserListenCountPayload { - pub count: u64, -} - -// -------- user/{user_name}/playing-now - -response_type! { - /// Response type for [`Client::user_playing_now`](super::Client::user_playing_now). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct UserPlayingNowResponse { - pub payload: UserPlayingNowPayload, - } -} - -/// Type of the [`UserPlayingNowResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserPlayingNowPayload { - pub count: u8, - pub user_id: String, - pub listens: Vec, - pub playing_now: bool, -} - -/// Type of the [`UserPlayingNowPayload::listens`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserPlayingNowListen { - pub track_metadata: UserPlayingNowTrackMetadata, - pub playing_now: bool, -} - -/// Type of the [`UserPlayingNowListen::track_metadata`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserPlayingNowTrackMetadata { - pub artist_name: String, - pub track_name: String, - pub release_name: Option, - pub additional_info: HashMap, -} - -// -------- user/{user_name}/similar-to/{other_user_name} - -response_type! { - #[derive(Debug, Deserialize, Serialize)] - pub struct UserSimilarToResponse { - pub user_name: String, - pub similarity: f64, - } -} - -// -------- user/{user_name}/playlists - -response_type! { - /// Response type for [`Client::user_playlists`](super::Client::user_playlists). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserPlaylistsResponse { - pub count: u64, - pub offset: u64, - pub playlist_count: u64, - pub playlists: Vec, - } -} - -// -------- user/{user_name}/listens - -response_type! { - /// Response type for [`Client::user_listens`](super::Client::user_listens). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct UserListensResponse { - pub payload: UserListensPayload, - } -} - -/// Type of the [`UserListensResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserListensPayload { - pub count: u64, - pub latest_listen_ts: i64, - pub oldest_listen_ts: i64, - pub user_id: String, - pub listens: Vec, -} - -/// Type of the [`UserListensPayload::listens`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserListensListen { - pub user_name: String, - pub inserted_at: i64, - pub listened_at: i64, - pub recording_msid: String, - pub track_metadata: UserListensTrackMetadata, -} - -/// Type of the [`UserListensListen::track_metadata`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserListensTrackMetadata { - pub artist_name: String, - pub track_name: String, - pub release_name: Option, - pub additional_info: HashMap, - pub mbid_mapping: Option, -} - -/// Type of the [`UserListensTrackMetadata::mbid_mapping`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserListensMBIDMapping { - pub artist_mbids: Option>, - pub artists: Option>, - pub recording_mbid: String, - pub recording_name: Option, - pub caa_id: Option, - pub caa_release_mbid: Option, - pub release_mbid: Option, -} - -/// Type of the [`UserListensMBIDMapping::artists`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct UserListensMappingArtist { - pub artist_mbid: String, - pub artist_credit_name: String, - pub join_phrase: String, -} - -// --------- latest-import (GET) - -response_type! { - /// Response type for [`Client::get_latest_import`](super::Client::get_latest_import). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct GetLatestImportResponse { - pub latest_import: i64, - pub musicbrainz_id: String, - } -} - -// --------- latest-import (POST) - -response_type! { - /// Response type for [`Client::update_latest_import`](super::Client::update_latest_import). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct UpdateLatestImportResponse { - pub status: String, - } -} - -// --------- playlist - -response_type! { - /// Response type for [`Client::playlist`](super::Client::get_playlist). - #[derive(Debug, Deserialize, Serialize)] - pub struct GetPlaylistResponse { - pub playlist: jspf::PlaylistInfo, - } -} - -// --------- playlist/create - -response_type! { - /// Response type for [`Client::playlist_create`](super::Client::playlist_create). - #[derive(Debug, Deserialize, Serialize)] - pub struct PlaylistCreateResponse { - pub playlist_mbid: String, - pub status: String, - } -} - -// --------- playlist/{playlist_mbid}/delete - -response_type! { - /// Response type for [`Client::playlist_delete`](super::Client::playlist_delete). - #[derive(Debug, Deserialize, Serialize)] - pub struct PlaylistDeleteResponse { - pub status: String, - } -} - -// --------- playlist/{playlist_mbid}/copy - -response_type! { - /// Response type for [`Client::playlist_copy`](super::Client::playlist_copy). - #[derive(Debug, Deserialize, Serialize)] - pub struct PlaylistCopyResponse { - pub playlist_mbid: String, - pub status: String, - } -} - -// --------- stats/sitewide/artists - -response_type! { - /// Response type for [`Client::stats_sitewide_artists`](super::Client::stats_sitewide_artists). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatsSitewideArtistsResponse { - pub payload: StatsSitewideArtistsPayload, - } -} - -/// Type of the [`StatsSitewideArtistsResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsSitewideArtistsPayload { - pub artists: Vec, - pub offset: u64, - pub count: u64, - pub range: String, - pub last_updated: i64, - pub from_ts: i64, - pub to_ts: i64, -} - -/// Type of the [`StatsSitewideArtistsPayload::artists`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsSitewideArtistsArtist { - pub artist_mbids: Option>, - pub artist_name: String, - pub listen_count: u64, -} - -// --------- stats/user/{user_name}/listening-activity - -response_type! { - /// Response type for [`Client::stats_user_listening_activity`](super::Client::stats_user_listening_activity). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatsUserListeningActivityResponse { - pub payload: StatsUserListeningActivityPayload, - } -} - -/// Type of the [`StatsUserListeningActivityResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserListeningActivityPayload { - pub user_id: String, - pub listening_activity: Vec, - pub from_ts: i64, - pub to_ts: i64, - pub last_updated: i64, -} - -/// Type of the [`StatsUserListeningActivityPayload::listening_activity`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserListeningActivityListeningActivity { - pub listen_count: u64, - pub from_ts: i64, - pub to_ts: i64, - pub time_range: String, -} - -// --------- stats/user/{user_name}/daily-activity - -response_type! { - /// Response type for [`Client::stats_user_daily_activity`](super::Client::stats_user_daily_activity). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatsUserDailyActivityResponse { - pub payload: StatsUserDailyActivityPayload, - } -} - -/// Type of the [`StatsUserDailyActivityResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserDailyActivityPayload { - pub user_id: String, - pub daily_activity: StatsUserDailyActivityDailyActivity, - pub from_ts: i64, - pub to_ts: i64, - pub last_updated: i64, - pub stats_range: String, -} - -/// Type of the [`StatsUserDailyActivityPayload::daily_activity`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserDailyActivityDailyActivity { - pub days: HashMap>, -} - -/// Type of the [`StatsUserDailyActivityDailyActivity::days`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserDailyActivityHour { - pub hour: u8, - pub listen_count: u64, -} - -// --------- stats/user/{user_name}/recordings - -response_type! { - /// Response type of [`Client::stats_user_recordings`](super::Client::stats_user_recordings). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatsUserRecordingsResponse { - pub payload: StatsUserRecordingsPayload, - } -} - -/// Type of the [`StatsUserRecordingsResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserRecordingsPayload { - pub recordings: Vec, - pub count: u64, - pub total_recording_count: u64, - pub user_id: String, - pub from_ts: i64, - pub to_ts: i64, - pub last_updated: i64, - pub range: String, -} - -/// Type of the [`StatsUserRecordingsPayload::recordings`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserRecordingsRecording { - pub artist_mbids: Option>, - pub artist_msid: Option, - pub artist_name: String, - pub listen_count: u64, - pub recording_mbid: Option, - pub recording_msid: Option, - pub release_mbid: Option, - pub release_msid: Option, - pub release_name: Option, - pub track_name: Option, -} - -// --------- stats/user/{user_name}/artist-map - -response_type! { - /// Response type of [`Client::stats_user_artist_map`](super::Client::stats_user_artist_map). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatsUserArtistMapResponse { - pub payload: StatsUserArtistMapPayload, - } -} - -/// Type of the [`StatsUserArtistMapResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserArtistMapPayload { - pub artist_map: Vec, - pub user_id: String, - pub from_ts: i64, - pub to_ts: i64, - pub last_updated: i64, - pub range: String, -} - -/// Type of the [`StatsUserArtistMapPayload::artist_map`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserArtistMapCountry { - pub country: String, - pub artist_count: u64, -} - -// --------- stats/user/{user_name}/releases - -response_type! { - /// Response type for [`Client::stats_user_releases`](super::Client::stats_user_releases). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatsUserReleasesResponse { - pub payload: StatsUserReleasesPayload, - } -} - -/// Type of the [`StatsUserReleasesResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserReleasesPayload { - pub releases: Vec, - pub count: u64, - pub total_release_count: u64, - pub user_id: String, - pub from_ts: i64, - pub to_ts: i64, - pub last_updated: i64, - pub range: String, -} - -/// Type of the [`StatsUserReleasesPayload::releases`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserReleasesRelease { - pub artist_mbids: Option>, - pub artist_msid: Option, - pub artist_name: String, - pub listen_count: u64, - pub release_mbid: Option, - pub release_msid: Option, - pub release_name: String, -} - -// --------- stats/user/{user_name}/artists - -response_type! { - /// Response type of [`Client::stats_user_artists`](super::Client::stats_user_artists). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatsUserArtistsResponse { - pub payload: StatsUserArtistsPayload, - } -} - -/// Type of the [`StatsUserArtistsResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserArtistsPayload { - pub artists: Vec, - pub count: u64, - pub total_artist_count: u64, - pub user_id: String, - pub from_ts: i64, - pub to_ts: i64, - pub last_updated: i64, - pub range: String, -} - -/// Type of the [`StatsUserArtistsPayload::artists`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsUserArtistsArtist { - pub artist_mbids: Option>, - pub artist_msid: Option, - pub artist_name: String, - pub listen_count: u64, -} - -// --------- status/get-dump-info - -response_type! { - /// Response type for [`Client::status_get_dump_info`](super::Client::status_get_dump_info). - #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] - pub struct StatusGetDumpInfoResponse { - pub code: u16, - pub message: String, - - pub id: i64, - pub timestamp: String, - } -} - -// ---------- stats/release-group/(release_group_mbid)/listeners - -response_type! { - /// Response type for [`Client::stats_release_group_listeners`](super::Client::stats_release_group_listeners). - #[derive(Debug, Deserialize, Serialize)] - pub struct StatsReleaseGroupListenersResponse { - pub payload: StatsReleaseGroupListenersPayload - } -} - -/// Type of the [`StatsReleaseGroupListenersResponse::payload`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsReleaseGroupListenersPayload { - pub artist_mbids: Vec, - pub artist_name: String, - pub caa_id: Option, - pub caa_release_mbid: Option, - pub from_ts: i64, - pub last_updated: i64, - pub listeners: Vec, - pub release_group_mbid: String, - pub release_group_name: String, - pub stats_range: String, - pub to_ts: i64, - pub total_listen_count: i64, -} - -/// Type of the [`StatsReleaseGroupListenersPayload::listeners`] field. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] -pub struct StatsReleaseGroupListenersListeners { - pub listen_count: u64, - pub username_name: String, -} - -// --------- user/{user_name}/followers - -response_type! { - /// Response type for [`Client::user_followers`](super::Client::user_followers). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserFollowersResponse { - pub followers: Vec, - pub user: String, - } -} - -// --------- user/{user_name}/following - -response_type! { - /// Response type for [`Client::user_following`](super::Client::user_following). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserFollowingResponse { - pub following: Vec, - pub user: String, - } -} - -// --------- user/{user_name}/unfollow - -response_type! { - /// Response type for [`Client::user_unfollow`](super::Client::user_unfollow). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserUnfollowResponse { - pub status: String, - } -} - -// --------- user/{user_name}/follow - -response_type! { - /// Response type for [`Client::user_follow`](super::Client::user_follow). - #[derive(Debug, Deserialize, Serialize)] - pub struct UserFollowResponse { - pub status: String, - } -} diff --git a/src/raw/response/art/mod.rs b/src/raw/response/art/mod.rs new file mode 100644 index 0000000..784fa9b --- /dev/null +++ b/src/raw/response/art/mod.rs @@ -0,0 +1,3 @@ +//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/art.html +// Currently no api responses have been made for this page. +// The header comments will be made as needed diff --git a/src/raw/response/core/mod.rs b/src/raw/response/core/mod.rs new file mode 100644 index 0000000..63871c1 --- /dev/null +++ b/src/raw/response/core/mod.rs @@ -0,0 +1,234 @@ +use std::collections::HashMap; + +use serde::Serialize; + +use super::response_type; +use crate::raw::response::Deserialize; + +// --------- GET /1/search/users/ +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-search-users- + +// TODO + +// --------- POST /1/submit-listens +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#post--1-submit-listens + +response_type! { + /// Response type for [`Client::submit_listens`](super::Client::submit_listens). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct SubmitListensResponse { + pub status: String, + } +} + +// --------- GET /1/user/(user_name)/listens +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-listens + +response_type! { + /// Response type for [`Client::user_listens`](super::Client::user_listens). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct UserListensResponse { + pub payload: UserListensPayload, + } +} + +/// Type of the [`UserListensResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserListensPayload { + pub count: u64, + pub latest_listen_ts: i64, + pub oldest_listen_ts: i64, + pub user_id: String, + pub listens: Vec, +} + +/// Type of the [`UserListensPayload::listens`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserListensListen { + pub user_name: String, + pub inserted_at: i64, + pub listened_at: i64, + pub recording_msid: String, + pub track_metadata: UserListensTrackMetadata, +} + +/// Type of the [`UserListensListen::track_metadata`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserListensTrackMetadata { + pub artist_name: String, + pub track_name: String, + pub release_name: Option, + pub additional_info: HashMap, + pub mbid_mapping: Option, +} + +/// Type of the [`UserListensTrackMetadata::mbid_mapping`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserListensMBIDMapping { + pub artist_mbids: Option>, + pub artists: Option>, + pub recording_mbid: String, + pub recording_name: Option, + pub caa_id: Option, + pub caa_release_mbid: Option, + pub release_mbid: Option, +} + +/// Type of the [`UserListensMBIDMapping::artists`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserListensMappingArtist { + pub artist_mbid: String, + pub artist_credit_name: String, + pub join_phrase: String, +} + +// --------- GET /1/user/(user_name)/listen-count +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-listen-count + +response_type! { + /// Response type for [`Client::user_listen_count`](super::Client::user_listen_count). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct UserListenCountResponse { + pub payload: UserListenCountPayload, + } +} + +/// Type of the [`UserListenCountResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserListenCountPayload { + pub count: u64, +} + +// --------- GET /1/user/(user_name)/playing-now +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-playing-now + +response_type! { + /// Response type for [`Client::user_playing_now`](super::Client::user_playing_now). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct UserPlayingNowResponse { + pub payload: UserPlayingNowPayload, + } +} + +/// Type of the [`UserPlayingNowResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserPlayingNowPayload { + pub count: u8, + pub user_id: String, + pub listens: Vec, + pub playing_now: bool, +} + +/// Type of the [`UserPlayingNowPayload::listens`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserPlayingNowListen { + pub track_metadata: UserPlayingNowTrackMetadata, + pub playing_now: bool, +} + +/// Type of the [`UserPlayingNowListen::track_metadata`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct UserPlayingNowTrackMetadata { + pub artist_name: String, + pub track_name: String, + pub release_name: Option, + pub additional_info: HashMap, +} + +// --------- GET /1/user/(user_name)/similar-users +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-similar-users + +response_type! { + /// Response type for [`Client::user_similar_users`](super::Client::user_similar_users). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] + pub struct UserSimilarUsersResponse { + pub payload: Vec, + } +} + +/// Type of the [`UserSimilarUsersResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +pub struct UserSimilarUsersPayload { + pub user_name: String, + pub similarity: f64, +} + +// --------- GET /1/user/(user_name)/similar-to/(other_user_name) +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-similar-to-(other_user_name) + +response_type! { + #[derive(Debug, Deserialize, Serialize)] + pub struct UserSimilarToResponse { + pub user_name: String, + pub similarity: f64, + } +} + +// --------- GET /1/validate-token +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-validate-token + +response_type! { + /// Response type for [`Client::validate_token`](super::Client::validate_token). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct ValidateTokenResponse { + pub code: u16, + pub message: String, + + pub valid: bool, + pub user_name: Option, + } +} + +// --------- POST /1/delete-listen +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#post--1-delete-listen + +response_type! { + /// Response type for [`Client::delete_listen`](super::Client::delete_listen). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct DeleteListenResponse { + pub status: String, + } +} + +// --------- GET /1/user/(playlist_user_name)/playlists/recommendations +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(playlist_user_name)-playlists-recommendations + +// TODO + +// --------- GET /1/user/(user_name)/services +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-user-(user_name)-services + +// TODO + +// --------- GET /1/lb-radio/tags +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-lb-radio-tags + +// TODO + +// --------- GET /1/lb-radio/artist/(seed_artist_mbid) +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-lb-radio-artist-(seed_artist_mbid) + +// TODO + +// --------- GET /1/latest-import +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#get--1-latest-import + +response_type! { + /// Response type for [`Client::get_latest_import`](super::Client::get_latest_import). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct GetLatestImportResponse { + pub latest_import: i64, + pub musicbrainz_id: String, + } +} + +// --------- POST /1/latest-import +// https://listenbrainz.readthedocs.io/en/latest/users/api/core.html#post--1-latest-import + +response_type! { + /// Response type for [`Client::update_latest_import`](super::Client::update_latest_import). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct UpdateLatestImportResponse { + pub status: String, + } +} diff --git a/src/raw/response/metadata/mod.rs b/src/raw/response/metadata/mod.rs new file mode 100644 index 0000000..17fa84c --- /dev/null +++ b/src/raw/response/metadata/mod.rs @@ -0,0 +1,3 @@ +//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/metadata.html +// Currently no api responses have been made for this page. +// The header comments will be made as needed diff --git a/src/raw/response/misc/mod.rs b/src/raw/response/misc/mod.rs new file mode 100644 index 0000000..5b84ab4 --- /dev/null +++ b/src/raw/response/misc/mod.rs @@ -0,0 +1,19 @@ +use serde::Deserialize; +use serde::Serialize; + +use crate::raw::response::response_type; + +// --------- GET /1/status/get-dump-info +// https://listenbrainz.readthedocs.io/en/latest/users/api/misc.html#get--1-status-get-dump-info + +response_type! { + /// Response type for [`Client::status_get_dump_info`](super::Client::status_get_dump_info). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatusGetDumpInfoResponse { + pub code: u16, + pub message: String, + + pub id: i64, + pub timestamp: String, + } +} diff --git a/src/raw/response/mod.rs b/src/raw/response/mod.rs new file mode 100644 index 0000000..6f77ba6 --- /dev/null +++ b/src/raw/response/mod.rs @@ -0,0 +1,135 @@ +//! Low-level response data models. +//! +//! Every response type has the `rate_limit` field, which contains rate limiting +//! information. See the documentation of the [`RateLimit`] type for more +//! details. + +#![allow(missing_docs)] + +use attohttpc::Response; +use serde::de::DeserializeOwned; +use serde::Deserialize; + +use crate::Error; + +// Sub modules +pub mod art; +pub mod core; +pub mod metadata; +pub mod misc; +pub mod playlists; +pub mod popularity; +pub mod recommendations; +pub mod recordings; +pub mod settings; +pub mod social; +pub mod statistics; + +// Reexport of the sub modules +pub use crate::raw::response::art::*; +pub use crate::raw::response::core::*; +pub use crate::raw::response::metadata::*; +pub use crate::raw::response::misc::*; +pub use crate::raw::response::playlists::*; +pub use crate::raw::response::popularity::*; +pub use crate::raw::response::recommendations::*; +pub use crate::raw::response::recordings::*; +pub use crate::raw::response::settings::*; +pub use crate::raw::response::social::*; +pub use crate::raw::response::statistics::*; + +/// Contains rate limiting information. +/// +/// ListenBrainz API rate limiting is described in the [API docs]. +/// Prefer using the [`RateLimit::reset_in`] field over [`RateLimit::reset`], +/// as the former is resilient against clients with incorrect clocks. +/// +/// [API docs]: https://listenbrainz.readthedocs.io/en/production/dev/api/#rate-limiting +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RateLimit { + pub limit: u64, + pub remaining: u64, + pub reset_in: u64, + pub reset: i64, +} + +impl RateLimit { + /// Extract rate limiting information from the `X-RateLimit-` headers. + /// Only returns `Some` if all fields are present and valid. + fn from_headers(response: &Response) -> Option { + let headers = response.headers(); + + let limit = headers + .get("X-RateLimit-Limit")? + .to_str() + .ok()? + .parse() + .ok()?; + let remaining = headers + .get("X-RateLimit-Remaining")? + .to_str() + .ok()? + .parse() + .ok()?; + let reset_in = headers + .get("X-RateLimit-Reset-In")? + .to_str() + .ok()? + .parse() + .ok()?; + let reset = headers + .get("X-RateLimit-Reset")? + .to_str() + .ok()? + .parse() + .ok()?; + + Some(Self { + limit, + remaining, + reset_in, + reset, + }) + } +} + +/// Internal trait for response types. +/// Allows converting the response type from an `attohttpc::Response`, +/// by deserializing the body into the response type and then +/// adding the `rate_limit` field from headers. +pub(crate) trait ResponseType: DeserializeOwned { + fn from_response(response: Response) -> Result; +} + +/// Internal macro for response types. +/// Wraps the definition of a response type, adds the `rate_limit` field, +/// and implements the `ResponseType` trait. +macro_rules! response_type { + ( + $(#[$meta:meta])* + pub struct $name:ident { + $(pub $field:ident: $field_ty:ty),* + $(,)? + } + ) => { + $(#[$meta])* + pub struct $name { + #[serde(skip)] + pub rate_limit: Option, + $(pub $field: $field_ty),* + } + + impl crate::raw::response::ResponseType for $name { + fn from_response(response: crate::raw::response::Response) -> Result { + let response = crate::raw::response::Error::try_from_error_response(response)?; + let rate_limit = crate::raw::response::RateLimit::from_headers(&response); + let mut result: Self = response.json()?; + result.rate_limit = rate_limit; + Ok(result) + } + } + } +} + +// Let the childrens access the macro +pub(super) use response_type; diff --git a/src/raw/response/playlists/mod.rs b/src/raw/response/playlists/mod.rs new file mode 100644 index 0000000..8ff4379 --- /dev/null +++ b/src/raw/response/playlists/mod.rs @@ -0,0 +1,146 @@ +use serde::Deserialize; +use serde::Serialize; + +use crate::raw::jspf; +use crate::raw::response::response_type; + +// --------- GET /1/user/(playlist_user_name)/playlists +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-user-(playlist_user_name)-playlists + +response_type! { + /// Response type for [`Client::user_playlists`](super::Client::user_playlists). + #[derive(Debug, Deserialize, Serialize)] + pub struct UserPlaylistsResponse { + pub count: u64, + pub offset: u64, + pub playlist_count: u64, + pub playlists: Vec, + } +} + +// --------- GET /1/user/(playlist_user_name)/playlists/createdfor +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-user-(playlist_user_name)-playlists-createdfor + +response_type! { + /// Response type for [`Client::user_playlists_created_for`](super::Client::user_playlists_created_for). + #[derive(Debug, Deserialize, Serialize)] + pub struct UserPlaylistsCreatedForResponse { + pub count: u64, + pub offset: u64, + pub playlist_count: u64, + pub playlists: Vec, + } +} + +// --------- GET /1/user/(playlist_user_name)/playlists/collaborator +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-user-(playlist_user_name)-playlists-collaborator + +response_type! { + /// Response type for [`Client::user_playlists_collaborator`](super::Client::user_playlists_collaborator). + #[derive(Debug, Deserialize, Serialize)] + pub struct UserPlaylistsCollaboratorResponse { + pub count: u64, + pub offset: u64, + pub playlist_count: u64, + pub playlists: Vec, + } +} + +// --------- POST /1/playlist/create +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-create + +response_type! { + /// Response type for [`Client::playlist_create`](super::Client::playlist_create). + #[derive(Debug, Deserialize, Serialize)] + pub struct PlaylistCreateResponse { + pub playlist_mbid: String, + pub status: String, + } +} + +// --------- GET /1/playlist/search +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-playlist-search + +// TODO + +// --------- GET /1/playlist/(playlist_mbid) +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-playlist-(playlist_mbid) + +response_type! { + /// Response type for [`Client::playlist`](super::Client::get_playlist). + #[derive(Debug, Deserialize, Serialize)] + pub struct GetPlaylistResponse { + pub playlist: jspf::PlaylistInfo, + } +} + +// --------- GET /1/playlist/(playlist_mbid)/xspf +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-playlist-(playlist_mbid)-xspf + +// TODO + +// --------- POST /1/playlist/(playlist_mbid)/item/add +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-(playlist_mbid)-item-add +// Or +// --------- POST /1/playlist/(playlist_mbid)/item/add/(int: offset) +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-(playlist_mbid)-item-add-(int-offset) + +// TODO + +// --------- POST /1/playlist/(playlist_mbid)/item/move +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-(playlist_mbid)-item-move + +// TODO + +// --------- POST /1/playlist/(playlist_mbid)/item/delete +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-(playlist_mbid)-item-delete + +// TODO + +// --------- POST /1/playlist/(playlist_mbid)/delete +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-(playlist_mbid)-delete + +response_type! { + /// Response type for [`Client::playlist_delete`](super::Client::playlist_delete). + #[derive(Debug, Deserialize, Serialize)] + pub struct PlaylistDeleteResponse { + pub status: String, + } +} + +// --------- POST /1/playlist/(playlist_mbid)/copy +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-(playlist_mbid)-copy + +response_type! { + /// Response type for [`Client::playlist_copy`](super::Client::playlist_copy). + #[derive(Debug, Deserialize, Serialize)] + pub struct PlaylistCopyResponse { + pub playlist_mbid: String, + pub status: String, + } +} + +// --------- POST /1/playlist/(playlist_mbid)/export/(service) +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-(playlist_mbid)-export-(service) + +// TODO + +// --------- GET /1/playlist/import/(service) +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-playlist-import-(service) + +// TODO + +// --------- GET /1/playlist/spotify/(playlist_id)/tracks +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-playlist-spotify-(playlist_id)-tracks + +// TODO + +// --------- GET /1/playlist/apple_music/(playlist_id)/tracks +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#get--1-playlist-apple_music-(playlist_id)-tracks + +// TODO + +// --------- POST /1/playlist/export-jspf/(service) +// https://listenbrainz.readthedocs.io/en/latest/users/api/playlist.html#post--1-playlist-export-jspf-(service) + +// TODO diff --git a/src/raw/response/popularity/mod.rs b/src/raw/response/popularity/mod.rs new file mode 100644 index 0000000..e0d9f31 --- /dev/null +++ b/src/raw/response/popularity/mod.rs @@ -0,0 +1,3 @@ +//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/popularity.html +// Currently no api responses have been made for this page. +// The header comments will be made as needed diff --git a/src/raw/response/recommendations/mod.rs b/src/raw/response/recommendations/mod.rs new file mode 100644 index 0000000..66ca6cf --- /dev/null +++ b/src/raw/response/recommendations/mod.rs @@ -0,0 +1,3 @@ +//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/recommendation.html +// Currently no api responses have been made for this page. +// The header comments will be made as needed diff --git a/src/raw/response/recordings/mod.rs b/src/raw/response/recordings/mod.rs new file mode 100644 index 0000000..c7a33be --- /dev/null +++ b/src/raw/response/recordings/mod.rs @@ -0,0 +1,3 @@ +//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/recordings.html +// Currently no api responses have been made for this page. +// The header comments will be made as needed diff --git a/src/raw/response/settings/mod.rs b/src/raw/response/settings/mod.rs new file mode 100644 index 0000000..47224ef --- /dev/null +++ b/src/raw/response/settings/mod.rs @@ -0,0 +1,3 @@ +//TODO: https://listenbrainz.readthedocs.io/en/latest/users/api/settings.html +// Currently no api responses have been made for this page. +// The header comments will be made as needed diff --git a/src/raw/response/social/mod.rs b/src/raw/response/social/mod.rs new file mode 100644 index 0000000..6135edd --- /dev/null +++ b/src/raw/response/social/mod.rs @@ -0,0 +1,50 @@ +use serde::Deserialize; +use serde::Serialize; + +use crate::raw::response::response_type; + +// --------- GET /1/user/(user_name)/followers +// https://listenbrainz.readthedocs.io/en/latest/users/api/social.html#get--1-user-(user_name)-followers + +response_type! { + /// Response type for [`Client::user_followers`](super::Client::user_followers). + #[derive(Debug, Deserialize, Serialize)] + pub struct UserFollowersResponse { + pub followers: Vec, + pub user: String, + } +} + +// --------- GET /1/user/(user_name)/following +// https://listenbrainz.readthedocs.io/en/latest/users/api/social.html#get--1-user-(user_name)-following + +response_type! { + /// Response type for [`Client::user_following`](super::Client::user_following). + #[derive(Debug, Deserialize, Serialize)] + pub struct UserFollowingResponse { + pub following: Vec, + pub user: String, + } +} + +// --------- POST /1/user/(user_name)/follow +// https://listenbrainz.readthedocs.io/en/latest/users/api/social.html#post--1-user-(user_name)-follow + +response_type! { + /// Response type for [`Client::user_follow`](super::Client::user_follow). + #[derive(Debug, Deserialize, Serialize)] + pub struct UserFollowResponse { + pub status: String, + } +} + +// --------- POST /1/user/(user_name)/unfollow +// https://listenbrainz.readthedocs.io/en/latest/users/api/social.html#post--1-user-(user_name)-unfollow + +response_type! { + /// Response type for [`Client::user_unfollow`](super::Client::user_unfollow). + #[derive(Debug, Deserialize, Serialize)] + pub struct UserUnfollowResponse { + pub status: String, + } +} diff --git a/src/raw/response/statistics/mod.rs b/src/raw/response/statistics/mod.rs new file mode 100644 index 0000000..1948d7c --- /dev/null +++ b/src/raw/response/statistics/mod.rs @@ -0,0 +1,273 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use serde::Serialize; + +use crate::raw::response::response_type; + +// --------- GET /1/stats/user/(user_name)/artists +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-user-(user_name)-artists + +response_type! { + /// Response type of [`Client::stats_user_artists`](super::Client::stats_user_artists). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatsUserArtistsResponse { + pub payload: StatsUserArtistsPayload, + } +} + +/// Type of the [`StatsUserArtistsResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserArtistsPayload { + pub artists: Vec, + pub count: u64, + pub total_artist_count: u64, + pub user_id: String, + pub from_ts: i64, + pub to_ts: i64, + pub last_updated: i64, + pub range: String, +} + +/// Type of the [`StatsUserArtistsPayload::artists`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserArtistsArtist { + pub artist_mbids: Option>, + pub artist_msid: Option, + pub artist_name: String, + pub listen_count: u64, +} + +// --------- GET /1/stats/user/(user_name)/releases +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-user-(user_name)-releases + +response_type! { + /// Response type for [`Client::stats_user_releases`](super::Client::stats_user_releases). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatsUserReleasesResponse { + pub payload: StatsUserReleasesPayload, + } +} + +/// Type of the [`StatsUserReleasesResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserReleasesPayload { + pub releases: Vec, + pub count: u64, + pub total_release_count: u64, + pub user_id: String, + pub from_ts: i64, + pub to_ts: i64, + pub last_updated: i64, + pub range: String, +} + +/// Type of the [`StatsUserReleasesPayload::releases`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserReleasesRelease { + pub artist_mbids: Option>, + pub artist_msid: Option, + pub artist_name: String, + pub listen_count: u64, + pub release_mbid: Option, + pub release_msid: Option, + pub release_name: String, +} + +// --------- GET /1/stats/user/(user_name)/recordings +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-user-(user_name)-recordings + +response_type! { + /// Response type of [`Client::stats_user_recordings`](super::Client::stats_user_recordings). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatsUserRecordingsResponse { + pub payload: StatsUserRecordingsPayload, + } +} + +/// Type of the [`StatsUserRecordingsResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserRecordingsPayload { + pub recordings: Vec, + pub count: u64, + pub total_recording_count: u64, + pub user_id: String, + pub from_ts: i64, + pub to_ts: i64, + pub last_updated: i64, + pub range: String, +} + +/// Type of the [`StatsUserRecordingsPayload::recordings`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserRecordingsRecording { + pub artist_mbids: Option>, + pub artist_msid: Option, + pub artist_name: String, + pub listen_count: u64, + pub recording_mbid: Option, + pub recording_msid: Option, + pub release_mbid: Option, + pub release_msid: Option, + pub release_name: Option, + pub track_name: Option, +} + +// --------- GET /1/stats/user/(user_name)/listening-activity +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-user-(user_name)-listening-activity + +response_type! { + /// Response type for [`Client::stats_user_listening_activity`](super::Client::stats_user_listening_activity). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatsUserListeningActivityResponse { + pub payload: StatsUserListeningActivityPayload, + } +} + +/// Type of the [`StatsUserListeningActivityResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserListeningActivityPayload { + pub user_id: String, + pub listening_activity: Vec, + pub from_ts: i64, + pub to_ts: i64, + pub last_updated: i64, +} + +/// Type of the [`StatsUserListeningActivityPayload::listening_activity`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserListeningActivityListeningActivity { + pub listen_count: u64, + pub from_ts: i64, + pub to_ts: i64, + pub time_range: String, +} + +// --------- GET /1/stats/user/(user_name)/daily-activity +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-user-(user_name)-daily-activity + +response_type! { + /// Response type for [`Client::stats_user_daily_activity`](super::Client::stats_user_daily_activity). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatsUserDailyActivityResponse { + pub payload: StatsUserDailyActivityPayload, + } +} + +/// Type of the [`StatsUserDailyActivityResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserDailyActivityPayload { + pub user_id: String, + pub daily_activity: StatsUserDailyActivityDailyActivity, + pub from_ts: i64, + pub to_ts: i64, + pub last_updated: i64, + pub stats_range: String, +} + +/// Type of the [`StatsUserDailyActivityPayload::daily_activity`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserDailyActivityDailyActivity { + pub days: HashMap>, +} + +/// Type of the [`StatsUserDailyActivityDailyActivity::days`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserDailyActivityHour { + pub hour: u8, + pub listen_count: u64, +} + +// --------- GET /1/stats/user/(user_name)/artist-map +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-user-(user_name)-artist-map + +response_type! { + /// Response type of [`Client::stats_user_artist_map`](super::Client::stats_user_artist_map). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatsUserArtistMapResponse { + pub payload: StatsUserArtistMapPayload, + } +} + +/// Type of the [`StatsUserArtistMapResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserArtistMapPayload { + pub artist_map: Vec, + pub user_id: String, + pub from_ts: i64, + pub to_ts: i64, + pub last_updated: i64, + pub range: String, +} + +/// Type of the [`StatsUserArtistMapPayload::artist_map`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsUserArtistMapCountry { + pub country: String, + pub artist_count: u64, +} + +// --------- GET /1/stats/release-group/(release_group_mbid)/listeners +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-release-group-(release_group_mbid)-listeners + +response_type! { + /// Response type for [`Client::stats_release_group_listeners`](super::Client::stats_release_group_listeners). + #[derive(Debug, Deserialize, Serialize)] + pub struct StatsReleaseGroupListenersResponse { + pub payload: StatsReleaseGroupListenersPayload + } +} + +/// Type of the [`StatsReleaseGroupListenersResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsReleaseGroupListenersPayload { + pub artist_mbids: Vec, + pub artist_name: String, + pub caa_id: Option, + pub caa_release_mbid: Option, + pub from_ts: i64, + pub last_updated: i64, + pub listeners: Vec, + pub release_group_mbid: String, + pub release_group_name: String, + pub stats_range: String, + pub to_ts: i64, + pub total_listen_count: i64, +} + +/// Type of the [`StatsReleaseGroupListenersPayload::listeners`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsReleaseGroupListenersListeners { + pub listen_count: u64, + pub username_name: String, +} + +// --------- GET /1/stats/sitewide/artists +// https://listenbrainz.readthedocs.io/en/latest/users/api/statistics.html#get--1-stats-sitewide-artists +response_type! { + /// Response type for [`Client::stats_sitewide_artists`](super::Client::stats_sitewide_artists). + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] + pub struct StatsSitewideArtistsResponse { + pub payload: StatsSitewideArtistsPayload, + } +} + +/// Type of the [`StatsSitewideArtistsResponse::payload`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsSitewideArtistsPayload { + pub artists: Vec, + pub offset: u64, + pub count: u64, + pub range: String, + pub last_updated: i64, + pub from_ts: i64, + pub to_ts: i64, +} + +/// Type of the [`StatsSitewideArtistsPayload::artists`] field. +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub struct StatsSitewideArtistsArtist { + pub artist_mbids: Option>, + pub artist_name: String, + pub listen_count: u64, +}