Implement more endpoints

- users/{user_list}/recent-listens
- user/{user_name}/playing-now
- user/{user_name}/listens
- latest-import
This commit is contained in:
Koen Bolhuis 2021-01-10 17:59:15 +01:00
parent d7b36ff18d
commit 2d23b4cccf
3 changed files with 231 additions and 41 deletions

View File

@ -3,9 +3,9 @@
use std::fmt;
use std::io;
use serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned;
use thiserror::Error;
use serde::{Deserialize, Serialize};
use ureq::Agent;
pub mod models;
@ -19,11 +19,11 @@ enum Endpoint<'a> {
SubmitListens,
ValidateToken,
DeleteListen,
//UsersRecentListens(&'a [&'a str]),
UsersRecentListens(&'a [&'a str]),
UserListenCount(&'a str),
// UserPlayingNow(&'a str),
// UserListens(&'a str),
// LatestImport,
UserPlayingNow(&'a str),
UserListens(&'a str),
LatestImport,
// StatsSitewideArtists,
// StatsUserListeningActivity(&'a str),
// StatsUserDailyActivity(&'a str),
@ -40,14 +40,14 @@ impl<'a> fmt::Display for Endpoint<'a> {
Self::SubmitListens => "submit-listens",
Self::ValidateToken => "validate-token",
Self::DeleteListen => "delete-listen",
// Self::UsersRecentListens(users) => {
// let users = users.join(",");
// return write!(f, "users/{}/recent-listens", users);
// }
Self::UsersRecentListens(users) => {
// TODO: url-encode usernames with commas
return write!(f, "users/{}/recent-listens", users.join(","));
}
Self::UserListenCount(user) => return write!(f, "user/{}/listen-count", user),
// Self::UserPlayingNow(user) => return write!(f, "user/{}/playing-now", user),
// Self::UserListens(user) => return write!(f, "user/{}/listens", user),
// Self::LatestImport => "latest-import",
Self::UserPlayingNow(user) => return write!(f, "user/{}/playing-now", user),
Self::UserListens(user) => return write!(f, "user/{}/listens", user),
Self::LatestImport => "latest-import",
// Self::StatsSitewideArtists => "stats/sitewide/artists",
// Self::StatsUserListeningActivity(user) => return write!(f, "stats/user/{}/listening-activity", user),
// Self::StatsUserDailyActivity(user) => return write!(f, "stats/user/{}/daily-activity", user),
@ -62,7 +62,7 @@ impl<'a> fmt::Display for Endpoint<'a> {
}
/// Represents errors that can occor while interacting with the API.
#[derive(Error, Debug)]
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// The API returned a non-200 status code.
#[error("API error ({code}): {error}")]
@ -99,12 +99,10 @@ impl From<ApiError> for Error {
impl From<ureq::Error> for Error {
fn from(error: ureq::Error) -> Self {
match error {
ureq::Error::Status(_code, response) => {
match response.into_json::<ApiError>() {
Ok(api_error) => api_error.into(),
Err(err) => Error::ResponseJson(err),
}
}
ureq::Error::Status(_code, response) => match response.into_json::<ApiError>() {
Ok(api_error) => api_error.into(),
Err(err) => Error::ResponseJson(err),
},
ureq::Error::Transport(_) => Error::Http(error),
}
}
@ -126,14 +124,12 @@ impl Client {
}
}
fn get<R: DeserializeOwned>(&mut self, endpoint: Endpoint, query: &[(&str, &str)]) -> Result<R, Error> {
fn get<R: DeserializeOwned>(&mut self, endpoint: Endpoint) -> Result<R, Error> {
let endpoint = format!("{}{}", API_ROOT_URL, endpoint);
let mut request = self.agent.get(&endpoint);
for &(param, value) in query.iter() {
request = request.query(param, value);
}
request.call()?
self.agent
.get(&endpoint)
.call()?
.into_json()
.map_err(Error::ResponseJson)
}
@ -147,7 +143,8 @@ impl Client {
let endpoint = format!("{}{}", API_ROOT_URL, endpoint);
self.agent.post(&endpoint)
self.agent
.post(&endpoint)
.set("Authorization", &format!("Token {}", token))
.send_json(data)?
.into_json()
@ -155,32 +152,124 @@ impl Client {
}
/// Endpoint: `submit-listens`
pub fn submit_listens(&mut self, token: &str, data: SubmitListens) -> Result<SubmitListensResponse, Error> {
pub fn submit_listens(
&mut self,
token: &str,
data: SubmitListens,
) -> Result<SubmitListensResponse, Error> {
self.post(Endpoint::SubmitListens, token, data)
}
/// Endpoint: `validate-token`
pub fn validate_token(&mut self, token: &str) -> Result<ValidateTokenResponse, Error> {
self.get(Endpoint::ValidateToken, &[("token", token)])
let endpoint = format!("{}{}", API_ROOT_URL, Endpoint::ValidateToken);
self.agent
.get(&endpoint)
.query("token", token)
.call()?
.into_json()
.map_err(Error::ResponseJson)
}
/// Endpoint: `delete-listen`
pub fn delete_listen(&mut self, token: &str, data: DeleteListen) -> Result<DeleteListenResponse, Error> {
pub fn delete_listen(
&mut self,
token: &str,
data: DeleteListen,
) -> Result<DeleteListenResponse, Error> {
self.post(Endpoint::DeleteListen, token, data)
}
/// Endpoint: `user/{user_name}/listen-count`
pub fn user_listen_count(&mut self, user: &str) -> Result<UserListenCountResponse, Error> {
self.get(Endpoint::UserListenCount(user), &[])
/// Endpoint: `users/{user_list}/recent-listens
pub fn users_recent_listens(
&mut self,
user_list: &[&str],
) -> Result<UsersRecentListensResponse, Error> {
self.get(Endpoint::UsersRecentListens(user_list))
}
/// Endpoint: `status/get-dump-info`
pub fn status_get_dump_info(&mut self, id: Option<i64>) -> Result<StatusGetDumpInfoResponse, Error> {
if let Some(id) = id {
let id = id.to_string();
self.get(Endpoint::StatusGetDumpInfo, &[("id", &id)])
} else {
self.get(Endpoint::StatusGetDumpInfo, &[])
/// Endpoint: `user/{user_name}/listen-count`
pub fn user_listen_count(&mut self, user_name: &str) -> Result<UserListenCountResponse, Error> {
self.get(Endpoint::UserListenCount(user_name))
}
// UserPlayingNow(&'a str),
/// Endpoint: `user/{user_name}/playing-now`
pub fn user_playing_now(&mut self, user_name: &str) -> Result<UserPlayingNowResponse, Error> {
self.get(Endpoint::UserPlayingNow(user_name))
}
pub fn user_listens(
&mut self,
user_name: &str,
min_ts: Option<i64>,
max_ts: Option<i64>,
count: Option<u32>,
time_range: Option<u64>
) -> Result<UserListensResponse, Error> {
let endpoint = format!("{}{}", API_ROOT_URL, Endpoint::UserListens(user_name));
let mut request = self.agent.get(&endpoint);
if let Some(min_ts) = min_ts {
request = request.query("min_ts", &min_ts.to_string());
}
if let Some(max_ts) = max_ts {
request = request.query("max_ts", &max_ts.to_string());
}
if let Some(count) = count {
request = request.query("count", &count.to_string());
}
if let Some(time_range) = time_range {
request = request.query("time_range", &time_range.to_string());
}
request.call()?.into_json().map_err(Error::ResponseJson)
}
/// Endpoint: `latest-import` (GET)
pub fn get_latest_import(&mut self, user_name: &str) -> Result<GetLatestImportResponse, Error> {
let endpoint = format!("{}{}", API_ROOT_URL, Endpoint::LatestImport);
self.agent
.get(&endpoint)
.query("user_name", user_name)
.call()?
.into_json()
.map_err(Error::ResponseJson)
}
/// Endpoint: `latest-import` (POST)
pub fn update_latest_import(
&mut self,
token: &str,
data: UpdateLatestImport,
) -> Result<UpdateLatestImportResponse, Error> {
self.post(Endpoint::LatestImport, token, data)
}
// StatsSitewideArtists,
// StatsUserListeningActivity(&'a str),
// StatsUserDailyActivity(&'a str),
// StatsUserRecordings(&'a str),
// StatsUserArtistMap(&'a str),
// StatsUserReleases(&'a str),
// StatsUserArtists(&'a str),
/// Endpoint: `status/get-dump-info`
pub fn status_get_dump_info(
&mut self,
id: Option<i64>,
) -> Result<StatusGetDumpInfoResponse, Error> {
let endpoint = format!("{}{}", API_ROOT_URL, Endpoint::StatusGetDumpInfo);
let mut request = self.agent.get(&endpoint);
if let Some(id) = id {
request = request.query("id", &id.to_string());
}
request.call()?.into_json().map_err(Error::ResponseJson)
}
}

View File

@ -20,7 +20,7 @@ pub enum ListenType {
#[derive(Debug, Serialize)]
pub struct Payload<'a> {
pub listened_at: i64,
pub track_metadata: TrackMetadata<'a>
pub track_metadata: TrackMetadata<'a>,
}
#[derive(Debug, Serialize)]
@ -38,3 +38,10 @@ pub struct DeleteListen<'a> {
pub listened_at: i64,
pub recording_msid: &'a str,
}
// --------- latest-import (POST)
#[derive(Debug, Serialize)]
pub struct UpdateLatestImport {
pub ts: i64,
}

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
@ -33,6 +35,37 @@ pub struct DeleteListenResponse {
pub api_response: ApiResponse,
}
// --------- users/{user_list}/recent-listens
#[derive(Debug, Deserialize)]
pub struct UsersRecentListensResponse {
pub payload: UsersRecentListensPayload,
}
#[derive(Debug, Deserialize)]
pub struct UsersRecentListensPayload {
pub count: u64,
pub listens: Vec<UsersRecentListensListen>,
pub user_list: String,
}
#[derive(Debug, Deserialize)]
pub struct UsersRecentListensListen {
pub user_name: String,
pub inserted_at: String,
pub listened_at: i64,
pub recording_msid: String,
pub track_metadata: UsersRecentListensTrackMetadata,
}
#[derive(Debug, Deserialize)]
pub struct UsersRecentListensTrackMetadata {
pub artist_name: String,
pub track_name: String,
pub release_name: Option<String>,
pub additional_info: HashMap<String, serde_json::Value>,
}
// --------- user/{user_name}/listen-count
#[derive(Debug, Deserialize)]
@ -43,6 +76,67 @@ pub struct UserListenCountResponse {
pub count: u64,
}
// -------- user/{user_name}/playing-now
#[derive(Debug, Deserialize)]
pub struct UserPlayingNowResponse {
pub payload: UserPlayingNowPayload,
}
#[derive(Debug, Deserialize)]
pub struct UserPlayingNowPayload {
pub count: u8,
pub user_id: String,
pub listens: Vec<()>,
}
// -------- user/{user_name}/listens
#[derive(Debug, Deserialize)]
pub struct UserListensResponse {
pub payload: UserListensPayload,
}
#[derive(Debug, Deserialize)]
pub struct UserListensPayload {
pub count: u64,
pub latest_listen_ts: i64,
pub user_id: String,
pub listens: Vec<UserListensListen>,
}
#[derive(Debug, Deserialize)]
pub struct UserListensListen {
pub user_name: String,
pub inserted_at: String,
pub listened_at: i64,
pub recording_msid: String,
pub track_metadata: UserListensTrackMetadata,
}
#[derive(Debug, Deserialize)]
pub struct UserListensTrackMetadata {
pub artist_name: String,
pub track_name: String,
pub release_name: Option<String>,
pub additional_info: HashMap<String, serde_json::Value>,
}
// --------- latest-import (GET)
#[derive(Debug, Deserialize)]
pub struct GetLatestImportResponse {
pub latest_import: i64,
pub musicbrainz_id: String,
}
// --------- latest-import (POST)
#[derive(Debug, Deserialize)]
pub struct UpdateLatestImportResponse {
pub status: String,
}
// --------- status/get-dump-info
#[derive(Debug, Deserialize)]